aboutsummaryrefslogtreecommitdiff
path: root/modules/private/buildbot
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2019-05-22 20:55:28 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2019-05-22 20:55:28 +0200
commit8d213e2b1c934f6861f76aad5eb7c11097fa97de (patch)
tree23f8a2d5692deaeffffa1ab5f098b2d24b9e2217 /modules/private/buildbot
parenta1a8649a2be768685eb04c246c114fce36b8096f (diff)
downloadNix-8d213e2b1c934f6861f76aad5eb7c11097fa97de.tar.gz
Nix-8d213e2b1c934f6861f76aad5eb7c11097fa97de.tar.zst
Nix-8d213e2b1c934f6861f76aad5eb7c11097fa97de.zip
Move rest of the modules outside of nixops
Diffstat (limited to 'modules/private/buildbot')
-rw-r--r--modules/private/buildbot/common/build_helpers.py256
-rw-r--r--modules/private/buildbot/common/master.cfg69
-rw-r--r--modules/private/buildbot/default.nix198
-rw-r--r--modules/private/buildbot/projects/caldance/__init__.py190
-rw-r--r--modules/private/buildbot/projects/cryptoportfolio/__init__.py169
-rw-r--r--modules/private/buildbot/projects/test/__init__.py188
6 files changed, 1070 insertions, 0 deletions
diff --git a/modules/private/buildbot/common/build_helpers.py b/modules/private/buildbot/common/build_helpers.py
new file mode 100644
index 0000000..384b1ac
--- /dev/null
+++ b/modules/private/buildbot/common/build_helpers.py
@@ -0,0 +1,256 @@
1from buildbot.plugins import util, steps, schedulers
2from buildbot_buildslist import BuildsList
3
4__all__ = [
5 "force_scheduler", "deploy_scheduler", "hook_scheduler",
6 "clean_branch", "package_and_upload", "SlackStatusPush",
7 "XMPPStatusPush"
8 ]
9
10# Small helpers"
11@util.renderer
12def clean_branch(props):
13 if props.hasProperty("branch") and len(props["branch"]) > 0:
14 return props["branch"].replace("/", "_")
15 else:
16 return "HEAD"
17
18def package_and_upload(package, package_dest, package_url):
19 return [
20 steps.ShellCommand(name="build package",
21 logEnviron=False, haltOnFailure=True, workdir="source",
22 command=["git", "archive", "HEAD", "-o", package]),
23
24 steps.FileUpload(name="upload package", workersrc=package,
25 workdir="source", masterdest=package_dest,
26 url=package_url, mode=0o644),
27
28 steps.ShellCommand(name="cleanup package", logEnviron=False,
29 haltOnFailure=True, workdir="source", alwaysRun=True,
30 command=["rm", "-f", package]),
31 ]
32
33# Schedulers
34def force_scheduler(name, builders):
35 return schedulers.ForceScheduler(name=name,
36 label="Force build", buttonName="Force build",
37 reason=util.StringParameter(name="reason", label="Reason", default="Force build"),
38 codebases=[
39 util.CodebaseParameter("",
40 branch=util.StringParameter(
41 name="branch", label="Git reference (tag, branch)", required=True),
42 revision=util.FixedParameter(name="revision", default=""),
43 repository=util.FixedParameter(name="repository", default=""),
44 project=util.FixedParameter(name="project", default=""),
45 ),
46 ],
47 username=util.FixedParameter(name="username", default="Web button"),
48 builderNames=builders)
49
50def deploy_scheduler(name, builders):
51 return schedulers.ForceScheduler(name=name,
52 builderNames=builders,
53 label="Deploy built package", buttonName="Deploy",
54 username=util.FixedParameter(name="username", default="Web button"),
55 codebases=[
56 util.CodebaseParameter(codebase="",
57 branch=util.FixedParameter(name="branch", default=""),
58 revision=util.FixedParameter(name="revision", default=""),
59 repository=util.FixedParameter(name="repository", default=""),
60 project=util.FixedParameter(name="project", default=""))],
61 reason=util.FixedParameter(name="reason", default="Deploy"),
62 properties=[
63 util.ChoiceStringParameter(label="Environment",
64 name="environment", default="integration",
65 choices=["integration", "production"]),
66 BuildsList(label="Build to deploy", name="build"),
67 ]
68 )
69
70def hook_scheduler(project, timer=10):
71 return schedulers.AnyBranchScheduler(
72 change_filter=util.ChangeFilter(category="hooks", project=project),
73 name=project, treeStableTimer=timer, builderNames=["{}_build".format(project)])
74
75# Slack/XMPP status push
76from buildbot.reporters.http import HttpStatusPushBase
77from twisted.internet import defer
78from twisted.python import log
79from buildbot.util import httpclientservice
80from buildbot.reporters import utils
81from buildbot.process import results
82from twisted.words.protocols.jabber.jid import JID
83from wokkel import client, xmppim
84from functools import partial
85
86class SlackStatusPush(HttpStatusPushBase):
87 name = "SlackStatusPush"
88
89 @defer.inlineCallbacks
90 def reconfigService(self, serverUrl, **kwargs):
91 yield HttpStatusPushBase.reconfigService(self, **kwargs)
92 self._http = yield httpclientservice.HTTPClientService.getService(
93 self.master, serverUrl)
94
95 @defer.inlineCallbacks
96 def send(self, build):
97 yield utils.getDetailsForBuild(self.master, build, wantProperties=True)
98 response = yield self._http.post("", json=self.format(build))
99 if response.code != 200:
100 log.msg("%s: unable to upload status: %s" %
101 (response.code, response.content))
102
103 def format(self, build):
104 colors = [
105 "#36A64F", # success
106 "#F1E903", # warnings
107 "#DA0505", # failure
108 "#FFFFFF", # skipped
109 "#000000", # exception
110 "#FFFFFF", # retry
111 "#D02CA9", # cancelled
112 ]
113
114 if "environment" in build["properties"]:
115 msg = "{} environment".format(build["properties"]["environment"][0])
116 if "build" in build["properties"]:
117 msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg
118 elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0:
119 msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"])
120 else:
121 msg = "build"
122
123 if build["complete"]:
124 timedelta = int((build["complete_at"] - build["started_at"]).total_seconds())
125 hours, rest = divmod(timedelta, 3600)
126 minutes, seconds = divmod(rest, 60)
127 if hours > 0:
128 duration = "{}h {}min {}s".format(hours, minutes, seconds)
129 elif minutes > 0:
130 duration = "{}min {}s".format(minutes, seconds)
131 else:
132 duration = "{}s".format(seconds)
133
134 text = "Build <{}|{}> of {}'s {} was {} in {}.".format(
135 build["url"], build["buildid"],
136 build["builder"]["name"],
137 msg,
138 results.Results[build["results"]],
139 duration,
140 )
141 fields = [
142 {
143 "title": "Build",
144 "value": "<{}|{}>".format(build["url"], build["buildid"]),
145 "short": True,
146 },
147 {
148 "title": "Project",
149 "value": build["builder"]["name"],
150 "short": True,
151 },
152 {
153 "title": "Build status",
154 "value": results.Results[build["results"]],
155 "short": True,
156 },
157 {
158 "title": "Build duration",
159 "value": duration,
160 "short": True,
161 },
162 ]
163 if "environment" in build["properties"]:
164 fields.append({
165 "title": "Environment",
166 "value": build["properties"]["environment"][0],
167 "short": True,
168 })
169 if "build" in build["properties"]:
170 fields.append({
171 "title": "Archive",
172 "value": build["properties"]["build"][0],
173 "short": True,
174 })
175 attachments = [{
176 "fallback": "",
177 "color": colors[build["results"]],
178 "fields": fields
179 }]
180 else:
181 text = "Build <{}|{}> of {}'s {} started.".format(
182 build["url"], build["buildid"],
183 build["builder"]["name"],
184 msg,
185 )
186 attachments = []
187
188 return {
189 "username": "Buildbot",
190 "icon_url": "http://docs.buildbot.net/current/_static/icon.png",
191 "text": text,
192 "attachments": attachments,
193 }
194
195class XMPPStatusPush(HttpStatusPushBase):
196 name = "XMPPStatusPush"
197
198 @defer.inlineCallbacks
199 def reconfigService(self, password, recipients, **kwargs):
200 yield HttpStatusPushBase.reconfigService(self, **kwargs)
201 self.password = password
202 self.recipients = recipients
203
204 @defer.inlineCallbacks
205 def send(self, build):
206 yield utils.getDetailsForBuild(self.master, build, wantProperties=True)
207 body = self.format(build)
208 factory = client.DeferredClientFactory(JID("notify_bot@immae.fr/buildbot"), self.password)
209 d = client.clientCreator(factory)
210 def send_message(recipient, stream):
211 message = xmppim.Message(recipient=JID(recipient), body=body)
212 message.stanzaType = 'chat'
213 stream.send(message.toElement())
214 # To allow chaining
215 return stream
216 for recipient in self.recipients:
217 d.addCallback(partial(send_message, recipient))
218 d.addCallback(lambda _: factory.streamManager.xmlstream.sendFooter())
219 d.addErrback(log.err)
220
221 def format(self, build):
222 if "environment" in build["properties"]:
223 msg = "{} environment".format(build["properties"]["environment"][0])
224 if "build" in build["properties"]:
225 msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg
226 elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0:
227 msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"])
228 else:
229 msg = "build"
230
231 if build["complete"]:
232 timedelta = int((build["complete_at"] - build["started_at"]).total_seconds())
233 hours, rest = divmod(timedelta, 3600)
234 minutes, seconds = divmod(rest, 60)
235 if hours > 0:
236 duration = "{}h {}min {}s".format(hours, minutes, seconds)
237 elif minutes > 0:
238 duration = "{}min {}s".format(minutes, seconds)
239 else:
240 duration = "{}s".format(seconds)
241
242 text = "Build {} ( {} ) of {}'s {} was {} in {}.".format(
243 build["buildid"], build["url"],
244 build["builder"]["name"],
245 msg,
246 results.Results[build["results"]],
247 duration,
248 )
249 else:
250 text = "Build {} ( {} ) of {}'s {} started.".format(
251 build["buildid"], build["url"],
252 build["builder"]["name"],
253 msg,
254 )
255
256 return text
diff --git a/modules/private/buildbot/common/master.cfg b/modules/private/buildbot/common/master.cfg
new file mode 100644
index 0000000..abe08e0
--- /dev/null
+++ b/modules/private/buildbot/common/master.cfg
@@ -0,0 +1,69 @@
1# -*- python -*-
2# ex: set filetype=python:
3
4from buildbot.plugins import secrets, util, webhooks
5from buildbot.util import bytes2unicode
6import re
7import os
8from buildbot_config import E, configure
9import json
10
11class CustomBase(webhooks.base):
12 def getChanges(self, request):
13 try:
14 content = request.content.read()
15 args = json.loads(bytes2unicode(content))
16 except Exception as e:
17 raise ValueError("Error loading JSON: " + str(e))
18
19 args.setdefault("comments", "")
20 args.setdefault("repository", "")
21 args.setdefault("author", args.get("who"))
22
23 return ([args], None)
24
25userInfoProvider = util.LdapUserInfo(
26 uri=E.LDAP_URL,
27 bindUser=E.LDAP_ADMIN_USER,
28 bindPw=open(E.SECRETS_FILE + "/ldap", "r").read().rstrip(),
29 accountBase=E.LDAP_BASE,
30 accountPattern=E.LDAP_PATTERN,
31 accountFullName='cn',
32 accountEmail='mail',
33 avatarData="jpegPhoto",
34 groupBase=E.LDAP_BASE,
35 groupName="cn",
36 groupMemberPattern=E.LDAP_GROUP_PATTERN,
37 )
38
39c = BuildmasterConfig = {
40 "title": E.TITLE,
41 "titleURL": E.TITLE_URL,
42 "db": {
43 "db_url": "sqlite:///state.sqlite"
44 },
45 "protocols": { "pb": { "port": E.PB_SOCKET } },
46 "workers": [],
47 "change_source": [],
48 "schedulers": [],
49 "builders": [],
50 "services": [],
51 "secretsProviders": [
52 secrets.SecretInAFile(E.SECRETS_FILE),
53 ],
54 "www": {
55 "change_hook_dialects": { "base": { "custom_class": CustomBase } },
56 "plugins": {
57 "waterfall_view": {},
58 "console_view": {},
59 "grid_view": {},
60 "buildslist": {},
61 },
62 "auth": util.RemoteUserAuth(
63 header=b"X-Remote-User",
64 userInfoProvider=userInfoProvider,
65 headerRegex=re.compile(br"(?P<username>[^ @]+)")),
66 }
67 }
68
69configure(c)
diff --git a/modules/private/buildbot/default.nix b/modules/private/buildbot/default.nix
new file mode 100644
index 0000000..fa6a6f2
--- /dev/null
+++ b/modules/private/buildbot/default.nix
@@ -0,0 +1,198 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 varDir = "/var/lib/buildbot";
4 buildbot_common = pkgs.python3Packages.buildPythonPackage rec {
5 name = "buildbot_common";
6 src = ./common;
7 format = "other";
8 installPhase = ''
9 mkdir -p $out/${pkgs.python3.pythonForBuild.sitePackages}
10 cp -a $src $out/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_common
11 '';
12 };
13 buildbot = pkgs.python3Packages.buildbot-full;
14in
15{
16 options = {
17 myServices.buildbot.enable = lib.mkOption {
18 type = lib.types.bool;
19 default = false;
20 description = ''
21 Whether to enable buildbot.
22 '';
23 };
24 };
25
26 config = lib.mkIf config.myServices.buildbot.enable {
27 ids.uids.buildbot = myconfig.env.buildbot.user.uid;
28 ids.gids.buildbot = myconfig.env.buildbot.user.gid;
29
30 users.groups.buildbot.gid = config.ids.gids.buildbot;
31 users.users.buildbot = {
32 name = "buildbot";
33 uid = config.ids.uids.buildbot;
34 group = "buildbot";
35 description = "Buildbot user";
36 home = varDir;
37 extraGroups = [ "keys" ];
38 };
39
40 services.websites.tools.vhostConfs.git.extraConfig = lib.attrsets.mapAttrsToList (k: project: ''
41 RedirectMatch permanent "^/buildbot/${project.name}$" "/buildbot/${project.name}/"
42 RewriteEngine On
43 RewriteRule ^/buildbot/${project.name}/ws(.*)$ unix:///run/buildbot/${project.name}.sock|ws://git.immae.eu/ws$1 [P,NE,QSA,L]
44 ProxyPass /buildbot/${project.name}/ unix:///run/buildbot/${project.name}.sock|http://${project.name}-git.immae.eu/
45 ProxyPassReverse /buildbot/${project.name}/ unix:///run/buildbot/${project.name}.sock|http://${project.name}-git.immae.eu/
46 <Location /buildbot/${project.name}/>
47 Use LDAPConnect
48 Require ldap-group cn=users,ou=${project.name},cn=buildbot,ou=services,dc=immae,dc=eu
49
50 SetEnvIf X-Url-Scheme https HTTPS=1
51 ProxyPreserveHost On
52 </Location>
53 <Location /buildbot/${project.name}/change_hook/base>
54 <RequireAny>
55 Require local
56 Require ldap-group cn=users,ou=${project.name},cn=buildbot,ou=services,dc=immae,dc=eu
57 Include /var/secrets/buildbot/${project.name}/webhook-httpd-include
58 </RequireAny>
59 </Location>
60 '') myconfig.env.buildbot.projects;
61
62 system.activationScripts = lib.attrsets.mapAttrs' (k: project: lib.attrsets.nameValuePair "buildbot-${project.name}" {
63 deps = [ "users" "wrappers" ];
64 text = project.activationScript;
65 }) myconfig.env.buildbot.projects;
66
67 secrets.keys = (
68 lib.lists.flatten (
69 lib.attrsets.mapAttrsToList (k: project:
70 lib.attrsets.mapAttrsToList (k: v:
71 {
72 permissions = "0600";
73 user = "buildbot";
74 group = "buildbot";
75 text = v;
76 dest = "buildbot/${project.name}/${k}";
77 }
78 ) project.secrets
79 ++ [
80 {
81 permissions = "0600";
82 user = "wwwrun";
83 group = "wwwrun";
84 text = lib.optionalString (lib.attrsets.hasAttr "webhookTokens" project) ''
85 Require expr "req('Access-Key') in { ${builtins.concatStringsSep ", " (map (x: "'${x}'") project.webhookTokens)} }"
86 '';
87 dest = "buildbot/${project.name}/webhook-httpd-include";
88 }
89 ]
90 ) myconfig.env.buildbot.projects
91 )
92 ) ++ [
93 {
94 permissions = "0600";
95 user = "buildbot";
96 group = "buildbot";
97 text = myconfig.env.buildbot.ldap.password;
98 dest = "buildbot/ldap";
99 }
100 {
101 permissions = "0600";
102 user = "buildbot";
103 group = "buildbot";
104 text = builtins.readFile "${myconfig.privateFiles}/buildbot_ssh_key";
105 dest = "buildbot/ssh_key";
106 }
107 ];
108
109 systemd.services = lib.attrsets.mapAttrs' (k: project: lib.attrsets.nameValuePair "buildbot-${project.name}" {
110 description = "Buildbot Continuous Integration Server ${project.name}.";
111 after = [ "network-online.target" ];
112 wantedBy = [ "multi-user.target" ];
113 path = project.packages pkgs ++ (project.pythonPackages buildbot.pythonModule pkgs);
114 preStart = let
115 master-cfg = "${buildbot_common}/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_common/master.cfg";
116 tac_file = pkgs.writeText "buildbot.tac" ''
117 import os
118
119 from twisted.application import service
120 from buildbot.master import BuildMaster
121
122 basedir = '${varDir}/${project.name}'
123 rotateLength = 10000000
124 maxRotatedFiles = 10
125 configfile = '${master-cfg}'
126
127 # Default umask for server
128 umask = None
129
130 # if this is a relocatable tac file, get the directory containing the TAC
131 if basedir == '.':
132 import os
133 basedir = os.path.abspath(os.path.dirname(__file__))
134
135 # note: this line is matched against to check that this is a buildmaster
136 # directory; do not edit it.
137 application = service.Application('buildmaster')
138 from twisted.python.logfile import LogFile
139 from twisted.python.log import ILogObserver, FileLogObserver
140 logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength,
141 maxRotatedFiles=maxRotatedFiles)
142 application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
143
144 m = BuildMaster(basedir, configfile, umask)
145 m.setServiceParent(application)
146 m.log_rotation.rotateLength = rotateLength
147 m.log_rotation.maxRotatedFiles = maxRotatedFiles
148 '';
149 in ''
150 if [ ! -f ${varDir}/${project.name}/buildbot.tac ]; then
151 ${buildbot}/bin/buildbot create-master -c "${master-cfg}" "${varDir}/${project.name}"
152 rm -f ${varDir}/${project.name}/master.cfg.sample
153 rm -f ${varDir}/${project.name}/buildbot.tac
154 fi
155 ln -sf ${tac_file} ${varDir}/${project.name}/buildbot.tac
156 # different buildbots may be trying that simultaneously, add the || true to avoid complaining in case of race
157 install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/ssh_key ${varDir}/buildbot_key || true
158 buildbot_secrets=${varDir}/${project.name}/secrets
159 install -m 0700 -o buildbot -g buildbot -d $buildbot_secrets
160 install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/ldap $buildbot_secrets/ldap
161 ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList
162 (k: v: "install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/${project.name}/${k} $buildbot_secrets/${k}") project.secrets
163 )}
164 '';
165 environment = let
166 project_env = lib.attrsets.mapAttrs' (k: v: lib.attrsets.nameValuePair "BUILDBOT_${k}" v) project.environment;
167 buildbot_config = pkgs.python3Packages.buildPythonPackage (rec {
168 name = "buildbot_config-${project.name}";
169 src = ./projects + "/${project.name}";
170 format = "other";
171 installPhase = ''
172 mkdir -p $out/${pkgs.python3.pythonForBuild.sitePackages}
173 cp -a $src $out/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_config
174 '';
175 });
176 HOME = "${varDir}/${project.name}";
177 PYTHONPATH = "${buildbot.pythonModule.withPackages (self: project.pythonPackages self pkgs ++ [
178 pkgs.python3Packages.wokkel
179 pkgs.python3Packages.treq pkgs.python3Packages.ldap3 buildbot
180 pkgs.python3Packages.buildbot-worker
181 buildbot_common buildbot_config
182 ])}/${buildbot.pythonModule.sitePackages}${if project.pythonPathHome then ":${varDir}/${project.name}/.local/${pkgs.python3.pythonForBuild.sitePackages}" else ""}";
183 in project_env // { inherit PYTHONPATH HOME; };
184
185 serviceConfig = {
186 Type = "forking";
187 User = "buildbot";
188 Group = "buildbot";
189 RuntimeDirectory = "buildbot";
190 RuntimeDirectoryPreserve = "yes";
191 StateDirectory = "buildbot";
192 SupplementaryGroups = "keys";
193 WorkingDirectory = "${varDir}/${project.name}";
194 ExecStart = "${buildbot}/bin/buildbot start";
195 };
196 }) myconfig.env.buildbot.projects;
197 };
198}
diff --git a/modules/private/buildbot/projects/caldance/__init__.py b/modules/private/buildbot/projects/caldance/__init__.py
new file mode 100644
index 0000000..2c0bad5
--- /dev/null
+++ b/modules/private/buildbot/projects/caldance/__init__.py
@@ -0,0 +1,190 @@
1from buildbot.plugins import *
2from buildbot_common.build_helpers import *
3import os
4from buildbot.util import bytes2unicode
5import json
6
7__all__ = [ "configure", "E" ]
8
9class E():
10 PROJECT = "caldance"
11 BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT)
12 SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT)
13 PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT)
14 RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT)
15 RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT)
16 GIT_URL = "gitolite@git.immae.eu:perso/simon_descarpentries/www.cal-dance.com"
17 SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key"
18 SSH_HOST_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIFbhFTl2A2RJn5L51yxJM4XfCS2ZaiSX/jo9jFSdghF"
19 LDAP_HOST = "ldap.immae.eu"
20 LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu"
21 LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu"
22 XMPP_RECIPIENTS = os.environ["BUILDBOT_XMPP_RECIPIENTS"].split(" ")
23
24 PUPPET_HOST = {
25 "integration": "root@caldance.immae.eu",
26 }
27
28 # master.cfg
29 SECRETS_FILE = os.getcwd() + "/secrets"
30 LDAP_URL = "ldaps://ldap.immae.eu:636"
31 LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu"
32 LDAP_BASE = "dc=immae,dc=eu"
33 LDAP_PATTERN = "(uid=%(username)s)"
34 LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=caldance,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))"
35 TITLE_URL = "https://caldance.immae.eu"
36 TITLE = "Caldance"
37
38class CustomBase(webhooks.base):
39 def getChanges(self, request):
40 try:
41 content = request.content.read()
42 args = json.loads(bytes2unicode(content))
43 except Exception as e:
44 raise ValueError("Error loading JSON: " + str(e))
45
46 args.setdefault("comments", "")
47 args.setdefault("repository", "")
48 args.setdefault("author", args.get("who", "unknown"))
49
50 if args["category"] == "deploy_webhook":
51 args = {
52 "category": "deploy_webhook",
53 "comments": "",
54 "repository": "",
55 "author": "webhook",
56 "project": "Caldance",
57 "properties": {
58 "environment": args.get("environment", "integration"),
59 "build": "caldance_{}.tar.gz".format(args.get("build", "master"))
60 }
61 }
62
63 return ([args], None)
64
65def deploy_hook_scheduler(project, timer=1):
66 return schedulers.AnyBranchScheduler(
67 change_filter=util.ChangeFilter(category="deploy_webhook", project=project),
68 name="{}_deploy".format(project), treeStableTimer=timer, builderNames=["{}_deploy".format(project)])
69
70def configure(c):
71 c["buildbotURL"] = E.BUILDBOT_URL
72 c["www"]["port"] = E.SOCKET
73
74 c["www"]["change_hook_dialects"]["base"] = {
75 "custom_class": CustomBase
76 }
77 c['workers'].append(worker.LocalWorker("generic-worker"))
78 c['workers'].append(worker.LocalWorker("deploy-worker"))
79
80 c['schedulers'].append(hook_scheduler("Caldance", timer=1))
81 c['schedulers'].append(force_scheduler("force_caldance", ["Caldance_build"]))
82 c['schedulers'].append(deploy_scheduler("deploy_caldance", ["Caldance_deploy"]))
83 c['schedulers'].append(deploy_hook_scheduler("Caldance", timer=1))
84
85 c['builders'].append(factory("caldance"))
86
87 c['builders'].append(deploy_factory("caldance"))
88
89 c['services'].append(SlackStatusPush(
90 name="slack_status_caldance",
91 builders=["Caldance_build", "Caldance_deploy"],
92 serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip()))
93 c['services'].append(XMPPStatusPush(
94 name="xmpp_status_caldance",
95 builders=["Caldance_build", "Caldance_deploy"],
96 recipients=E.XMPP_RECIPIENTS,
97 password=open(E.SECRETS_FILE + "/notify_xmpp_password", "r").read().rstrip()))
98
99def factory(project, ignore_fails=False):
100 release_file = "{1}/{0}_%(kw:clean_branch)s.tar.gz"
101
102 package = util.Interpolate("{0}_%(kw:clean_branch)s.tar.gz".format(project), clean_branch=clean_branch)
103 package_dest = util.Interpolate(release_file.format(project, E.RELEASE_PATH), clean_branch=clean_branch)
104 package_url = util.Interpolate(release_file.format(project, E.RELEASE_URL), clean_branch=clean_branch)
105
106 factory = util.BuildFactory()
107 factory.addStep(steps.Git(logEnviron=False, repourl=E.GIT_URL,
108 sshPrivateKey=open(E.SSH_KEY_PATH).read().rstrip(),
109 sshHostKey=E.SSH_HOST_KEY, mode="full", method="copy"))
110 factory.addSteps(package_and_upload(package, package_dest, package_url))
111
112 return util.BuilderConfig(
113 name="{}_build".format(project.capitalize()),
114 workernames=["generic-worker"], factory=factory)
115
116def compute_build_infos(project):
117 @util.renderer
118 def compute(props):
119 import re, hashlib
120 build_file = props.getProperty("build")
121 package_dest = "{1}/{0}".format(build_file, E.RELEASE_PATH)
122 version = re.match(r"{0}_(.*).tar.gz".format(project), build_file).group(1)
123 with open(package_dest, "rb") as f:
124 sha = hashlib.sha256(f.read()).hexdigest()
125 return {
126 "build_version": version,
127 "build_hash": sha,
128 }
129 return compute
130
131@util.renderer
132def puppet_host(props):
133 environment = props["environment"] if props.hasProperty("environment") else "integration"
134 return E.PUPPET_HOST.get(environment, "host.invalid")
135
136def deploy_factory(project):
137 package_dest = util.Interpolate("{0}/%(prop:build)s".format(E.RELEASE_PATH))
138
139 factory = util.BuildFactory()
140 factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest]))
141 factory.addStep(steps.SetProperties(properties=compute_build_infos(project)))
142 factory.addStep(LdapPush(environment=util.Property("environment"),
143 project=project, build_version=util.Property("build_version"),
144 build_hash=util.Property("build_hash"), ldap_password=util.Secret("ldap")))
145 factory.addStep(steps.MasterShellCommand(command=[
146 "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host]))
147 return util.BuilderConfig(name="{}_deploy".format(project.capitalize()), workernames=["deploy-worker"], factory=factory)
148
149from twisted.internet import defer
150from buildbot.process.buildstep import FAILURE
151from buildbot.process.buildstep import SUCCESS
152from buildbot.process.buildstep import BuildStep
153
154class LdapPush(BuildStep):
155 name = "LdapPush"
156 renderables = ["environment", "project", "build_version", "build_hash", "ldap_password"]
157
158 def __init__(self, **kwargs):
159 self.environment = kwargs.pop("environment")
160 self.project = kwargs.pop("project")
161 self.build_version = kwargs.pop("build_version")
162 self.build_hash = kwargs.pop("build_hash")
163 self.ldap_password = kwargs.pop("ldap_password")
164 self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST)
165 super().__init__(**kwargs)
166
167 def run(self):
168 import json
169 from ldap3 import Reader, Writer, Server, Connection, ObjectDef
170 server = Server(self.ldap_host)
171 conn = Connection(server,
172 user=E.LDAP_DN,
173 password=self.ldap_password)
174 conn.bind()
175 obj = ObjectDef("immaePuppetClass", conn)
176 r = Reader(conn, obj,
177 "cn=caldance.{},{}".format(self.environment, E.LDAP_ROLES_BASE))
178 r.search()
179 if len(r) > 0:
180 w = Writer.from_cursor(r)
181 for value in w[0].immaePuppetJson.values:
182 config = json.loads(value)
183 if "role::caldance::{}_version".format(self.project) in config:
184 config["role::caldance::{}_version".format(self.project)] = self.build_version
185 config["role::caldance::{}_sha256".format(self.project)] = self.build_hash
186 w[0].immaePuppetJson -= value
187 w[0].immaePuppetJson += json.dumps(config, indent=" ")
188 w.commit()
189 return defer.succeed(SUCCESS)
190 return defer.succeed(FAILURE)
diff --git a/modules/private/buildbot/projects/cryptoportfolio/__init__.py b/modules/private/buildbot/projects/cryptoportfolio/__init__.py
new file mode 100644
index 0000000..5d70f95
--- /dev/null
+++ b/modules/private/buildbot/projects/cryptoportfolio/__init__.py
@@ -0,0 +1,169 @@
1from buildbot.plugins import *
2from buildbot_common.build_helpers import *
3import os
4
5__all__ = [ "configure", "E" ]
6
7class E():
8 PROJECT = "cryptoportfolio"
9 BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT)
10 SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT)
11 PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT)
12 RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT)
13 RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT)
14 GIT_URL = "https://git.immae.eu/perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/{0}.git"
15 SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key"
16 LDAP_HOST = "ldap.immae.eu"
17 LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu"
18 LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu"
19
20 PUPPET_HOST = {
21 "production": "root@cryptoportfolio.immae.eu",
22 "integration": "root@cryptoportfolio-dev.immae.eu"
23 }
24
25 # master.cfg
26 SECRETS_FILE = os.getcwd() + "/secrets"
27 LDAP_URL = "ldaps://ldap.immae.eu:636"
28 LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu"
29 LDAP_BASE = "dc=immae,dc=eu"
30 LDAP_PATTERN = "(uid=%(username)s)"
31 LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=cryptoportfolio,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))"
32 TITLE_URL = "https://git.immae.eu"
33 TITLE = "Cryptoportfolio"
34
35# eval .. dans .zshrc_local
36# mkdir -p $BUILD/go
37# export GOPATH=$BUILD/go
38# go get -u github.com/golang/dep/cmd/dep
39# export PATH=$PATH:$BUILD/go/bin
40# go get git.immae.eu/Cryptoportfolio/Front.git
41# cd $BUILD/go/src/git.immae.eu/Cryptoportfolio/Front.git
42# git checkout dev
43# dep ensure
44def configure(c):
45 c["buildbotURL"] = E.BUILDBOT_URL
46 c["www"]["port"] = E.SOCKET
47
48 c['workers'].append(worker.LocalWorker("generic-worker"))
49 c['workers'].append(worker.LocalWorker("deploy-worker"))
50
51 c['schedulers'].append(hook_scheduler("Trader"))
52 c['schedulers'].append(hook_scheduler("Front"))
53 c['schedulers'].append(force_scheduler(
54 "force_cryptoportfolio", ["Trader_build", "Front_build"]))
55 c['schedulers'].append(deploy_scheduler("deploy_cryptoportfolio",
56 ["Trader_deploy", "Front_deploy"]))
57
58 c['builders'].append(factory("trader"))
59 c['builders'].append(factory("front", ignore_fails=True))
60
61 c['builders'].append(deploy_factory("trader"))
62 c['builders'].append(deploy_factory("front"))
63
64 c['services'].append(SlackStatusPush(
65 name="slack_status_cryptoportfolio",
66 builders=["Front_build", "Trader_build", "Front_deploy", "Trader_deploy"],
67 serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip()))
68
69def factory(project, ignore_fails=False):
70 release_file = "{1}/{0}/{0}_%(kw:clean_branch)s.tar.gz"
71
72 url = E.GIT_URL.format(project.capitalize())
73
74 package = util.Interpolate("{0}_%(kw:clean_branch)s.tar.gz".format(project), clean_branch=clean_branch)
75 package_dest = util.Interpolate(release_file.format(project, E.RELEASE_PATH), clean_branch=clean_branch)
76 package_url = util.Interpolate(release_file.format(project, E.RELEASE_URL), clean_branch=clean_branch)
77
78 factory = util.BuildFactory()
79 factory.addStep(steps.Git(logEnviron=False, repourl=url,
80 mode="full", method="copy"))
81 factory.addStep(steps.ShellCommand(name="make install",
82 logEnviron=False, haltOnFailure=(not ignore_fails),
83 warnOnFailure=ignore_fails, flunkOnFailure=(not ignore_fails),
84 command=["make", "install"]))
85 factory.addStep(steps.ShellCommand(name="make test",
86 logEnviron=False, haltOnFailure=(not ignore_fails),
87 warnOnFailure=ignore_fails, flunkOnFailure=(not ignore_fails),
88 command=["make", "test"]))
89 factory.addSteps(package_and_upload(package, package_dest, package_url))
90
91 return util.BuilderConfig(
92 name="{}_build".format(project.capitalize()),
93 workernames=["generic-worker"], factory=factory)
94
95def compute_build_infos(project):
96 @util.renderer
97 def compute(props):
98 import re, hashlib
99 build_file = props.getProperty("build")
100 package_dest = "{2}/{0}/{1}".format(project, build_file, E.RELEASE_PATH)
101 version = re.match(r"{0}_(.*).tar.gz".format(project), build_file).group(1)
102 with open(package_dest, "rb") as f:
103 sha = hashlib.sha256(f.read()).hexdigest()
104 return {
105 "build_version": version,
106 "build_hash": sha,
107 }
108 return compute
109
110@util.renderer
111def puppet_host(props):
112 environment = props["environment"] if props.hasProperty("environment") else "integration"
113 return E.PUPPET_HOST.get(environment, "host.invalid")
114
115def deploy_factory(project):
116 package_dest = util.Interpolate("{1}/{0}/%(prop:build)s".format(project, E.RELEASE_PATH))
117
118 factory = util.BuildFactory()
119 factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest]))
120 factory.addStep(steps.SetProperties(properties=compute_build_infos(project)))
121 factory.addStep(LdapPush(environment=util.Property("environment"),
122 project=project, build_version=util.Property("build_version"),
123 build_hash=util.Property("build_hash"), ldap_password=util.Secret("ldap")))
124 factory.addStep(steps.MasterShellCommand(command=[
125 "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host]))
126 return util.BuilderConfig(name="{}_deploy".format(project.capitalize()), workernames=["deploy-worker"], factory=factory)
127
128from twisted.internet import defer
129from buildbot.process.buildstep import FAILURE
130from buildbot.process.buildstep import SUCCESS
131from buildbot.process.buildstep import BuildStep
132
133class LdapPush(BuildStep):
134 name = "LdapPush"
135 renderables = ["environment", "project", "build_version", "build_hash", "ldap_password"]
136
137 def __init__(self, **kwargs):
138 self.environment = kwargs.pop("environment")
139 self.project = kwargs.pop("project")
140 self.build_version = kwargs.pop("build_version")
141 self.build_hash = kwargs.pop("build_hash")
142 self.ldap_password = kwargs.pop("ldap_password")
143 self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST)
144 super().__init__(**kwargs)
145
146 def run(self):
147 import json
148 from ldap3 import Reader, Writer, Server, Connection, ObjectDef
149 server = Server(self.ldap_host)
150 conn = Connection(server,
151 user=E.LDAP_DN,
152 password=self.ldap_password)
153 conn.bind()
154 obj = ObjectDef("immaePuppetClass", conn)
155 r = Reader(conn, obj,
156 "cn=cryptoportfolio.{},{}".format(self.environment, E.LDAP_ROLES_BASE))
157 r.search()
158 if len(r) > 0:
159 w = Writer.from_cursor(r)
160 for value in w[0].immaePuppetJson.values:
161 config = json.loads(value)
162 if "role::cryptoportfolio::{}_version".format(self.project) in config:
163 config["role::cryptoportfolio::{}_version".format(self.project)] = self.build_version
164 config["role::cryptoportfolio::{}_sha256".format(self.project)] = self.build_hash
165 w[0].immaePuppetJson -= value
166 w[0].immaePuppetJson += json.dumps(config, indent=" ")
167 w.commit()
168 return defer.succeed(SUCCESS)
169 return defer.succeed(FAILURE)
diff --git a/modules/private/buildbot/projects/test/__init__.py b/modules/private/buildbot/projects/test/__init__.py
new file mode 100644
index 0000000..e6b8d51
--- /dev/null
+++ b/modules/private/buildbot/projects/test/__init__.py
@@ -0,0 +1,188 @@
1from buildbot.plugins import *
2from buildbot_common.build_helpers import *
3import os
4from buildbot.util import bytes2unicode
5import json
6
7__all__ = [ "configure", "E" ]
8
9class E():
10 PROJECT = "test"
11 BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT)
12 SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT)
13 PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT)
14 RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT)
15 RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT)
16 GIT_URL = "https://git.immae.eu/perso/Immae/TestProject.git"
17 SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key"
18 PUPPET_HOST = "root@backup-1.v.immae.eu"
19 LDAP_HOST = "ldap.immae.eu"
20 LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu"
21 LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu"
22 XMPP_RECIPIENTS = os.environ["BUILDBOT_XMPP_RECIPIENTS"].split(" ")
23
24 # master.cfg
25 SECRETS_FILE = os.getcwd() + "/secrets"
26 LDAP_URL = "ldaps://ldap.immae.eu:636"
27 LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu"
28 LDAP_BASE = "dc=immae,dc=eu"
29 LDAP_PATTERN = "(uid=%(username)s)"
30 LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=test,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))"
31 TITLE_URL = "https://git.immae.eu/?p=perso/Immae/TestProject.git;a=summary"
32 TITLE = "Test project"
33
34class CustomBase(webhooks.base):
35 def getChanges(self, request):
36 try:
37 content = request.content.read()
38 args = json.loads(bytes2unicode(content))
39 except Exception as e:
40 raise ValueError("Error loading JSON: " + str(e))
41
42 args.setdefault("comments", "")
43 args.setdefault("repository", "")
44 args.setdefault("author", args.get("who", "unknown"))
45
46 if args["category"] == "deploy_webhook":
47 args = {
48 "category": "deploy_webhook",
49 "comments": "",
50 "repository": "",
51 "author": "unknown",
52 "project": "TestProject",
53 "properties": {
54 "environment": args.get("environment", "integration"),
55 "build": "test_{}.tar.gz".format(args.get("branch", "master"))
56 }
57 }
58
59 return ([args], None)
60
61def deploy_hook_scheduler(project, timer=1):
62 return schedulers.AnyBranchScheduler(
63 change_filter=util.ChangeFilter(category="deploy_webhook", project=project),
64 name="{}_deploy".format(project), treeStableTimer=timer, builderNames=["{}_deploy".format(project)])
65
66def configure(c):
67 c["buildbotURL"] = E.BUILDBOT_URL
68 c["www"]["port"] = E.SOCKET
69
70 c["www"]["change_hook_dialects"]["base"] = {
71 "custom_class": CustomBase
72 }
73 c['workers'].append(worker.LocalWorker("generic-worker-test"))
74 c['workers'].append(worker.LocalWorker("deploy-worker-test"))
75
76 c['schedulers'].append(hook_scheduler("TestProject", timer=1))
77 c['schedulers'].append(force_scheduler("force_test", ["TestProject_build"]))
78 c['schedulers'].append(deploy_scheduler("deploy_test", ["TestProject_deploy"]))
79 c['schedulers'].append(deploy_hook_scheduler("TestProject", timer=1))
80
81 c['builders'].append(factory())
82 c['builders'].append(deploy_factory())
83
84 c['services'].append(SlackStatusPush(
85 name="slack_status_test_project",
86 builders=["TestProject_build", "TestProject_deploy"],
87 serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip()))
88 c['services'].append(XMPPStatusPush(
89 name="xmpp_status_test_project",
90 builders=["TestProject_build", "TestProject_deploy"],
91 recipients=E.XMPP_RECIPIENTS,
92 password=open(E.SECRETS_FILE + "/notify_xmpp_password", "r").read().rstrip()))
93
94def factory():
95 package = util.Interpolate("test_%(kw:clean_branch)s.tar.gz", clean_branch=clean_branch)
96 package_dest = util.Interpolate("{}/test_%(kw:clean_branch)s.tar.gz".format(E.RELEASE_PATH), clean_branch=clean_branch)
97 package_url = util.Interpolate("{}/test_%(kw:clean_branch)s.tar.gz".format(E.RELEASE_URL), clean_branch=clean_branch)
98
99 factory = util.BuildFactory()
100 factory.addStep(steps.Git(logEnviron=False,
101 repourl=E.GIT_URL, mode="full", method="copy"))
102 factory.addStep(steps.ShellCommand(name="env",
103 logEnviron=False, command=["env"]))
104 factory.addStep(steps.ShellCommand(name="pwd",
105 logEnviron=False, command=["pwd"]))
106 factory.addStep(steps.ShellCommand(name="true",
107 logEnviron=False, command=["true"]))
108 factory.addStep(steps.ShellCommand(name="echo",
109 logEnviron=False, command=["echo", package]))
110 factory.addSteps(package_and_upload(package, package_dest, package_url))
111
112 return util.BuilderConfig(name="TestProject_build", workernames=["generic-worker-test"], factory=factory)
113
114
115def compute_build_infos():
116 @util.renderer
117 def compute(props):
118 import re, hashlib
119 build_file = props.getProperty("build")
120 package_dest = "{}/{}".format(E.RELEASE_PATH, build_file)
121 version = re.match(r"{0}_(.*).tar.gz".format("test"), build_file).group(1)
122 with open(package_dest, "rb") as f:
123 sha = hashlib.sha256(f.read()).hexdigest()
124 return {
125 "build_version": version,
126 "build_hash": sha,
127 }
128 return compute
129
130@util.renderer
131def puppet_host(props):
132 return E.PUPPET_HOST
133
134def deploy_factory():
135 package_dest = util.Interpolate("{}/%(prop:build)s".format(E.RELEASE_PATH))
136
137 factory = util.BuildFactory()
138 factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest]))
139 factory.addStep(steps.SetProperties(properties=compute_build_infos()))
140 factory.addStep(LdapPush(environment=util.Property("environment"),
141 build_version=util.Property("build_version"),
142 build_hash=util.Property("build_hash"),
143 ldap_password=util.Secret("ldap")))
144 factory.addStep(steps.MasterShellCommand(command=[
145 "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host]))
146 return util.BuilderConfig(name="TestProject_deploy", workernames=["deploy-worker-test"], factory=factory)
147
148from twisted.internet import defer
149from buildbot.process.buildstep import FAILURE
150from buildbot.process.buildstep import SUCCESS
151from buildbot.process.buildstep import BuildStep
152
153class LdapPush(BuildStep):
154 name = "LdapPush"
155 renderables = ["environment", "build_version", "build_hash", "ldap_password"]
156
157 def __init__(self, **kwargs):
158 self.environment = kwargs.pop("environment")
159 self.build_version = kwargs.pop("build_version")
160 self.build_hash = kwargs.pop("build_hash")
161 self.ldap_password = kwargs.pop("ldap_password")
162 self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST)
163 super().__init__(**kwargs)
164
165 def run(self):
166 import json
167 from ldap3 import Reader, Writer, Server, Connection, ObjectDef
168 server = Server(self.ldap_host)
169 conn = Connection(server,
170 user=E.LDAP_DN,
171 password=self.ldap_password)
172 conn.bind()
173 obj = ObjectDef("immaePuppetClass", conn)
174 r = Reader(conn, obj,
175 "cn=test.{},{}".format(self.environment, E.LDAP_ROLES_BASE))
176 r.search()
177 if len(r) > 0:
178 w = Writer.from_cursor(r)
179 for value in w[0].immaePuppetJson.values:
180 config = json.loads(value)
181 if "test_version" in config:
182 config["test_version"] = self.build_version
183 config["test_sha256"] = self.build_hash
184 w[0].immaePuppetJson -= value
185 w[0].immaePuppetJson += json.dumps(config, indent=" ")
186 w.commit()
187 return defer.succeed(SUCCESS)
188 return defer.succeed(FAILURE)