~mrazik/cupstream2distro/cu2d-update-ci

« back to all changes in this revision

Viewing changes to jenkins/cu2d-update-stack

  • Committer: Martin Mrazik
  • Date: 2013-02-27 08:24:45 UTC
  • mfrom: (206.1.17 tests)
  • Revision ID: martin.mrazik@canonical.com-20130227082445-8hb91e6dpnq5j8x6
merged with trunk and removed the templates which now should go to lp:cupstream2distro-config

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/python
2
 
"""Create/Update jenkins jobs for a given stack
3
 
 
4
 
- Reads stack configuration from YAML configuration file
5
 
- Create/updates the jenkins jobs on the server configured in the credentials
6
 
file
7
 
 
8
 
"""
9
 
# Copyright (C) 2012, Canonical Ltd (http://www.canonical.com/)
10
 
#
11
 
# Author: Jean-Baptiste Lallement <jean-baptiste.lallement@canonical.com>
12
 
#
13
 
# This software is free software: you can redistribute it
14
 
# and/or modify it under the terms of the GNU General Public License
15
 
# as published by the Free Software Foundation, either version 3 of
16
 
# the License, or (at your option) any later version.
17
 
#
18
 
# This software is distributed in the hope that it will
19
 
# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
20
 
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 
# GNU General Public License for more details.
22
 
#
23
 
# You should have received a copy of the GNU General Public License
24
 
# along with this software.  If not, see <http://www.gnu.org/licenses/>.
25
 
 
26
 
import os
27
 
import logging
28
 
import sys
29
 
import yaml
30
 
import jinja2
31
 
import jenkins
32
 
import argparse
33
 
import subprocess
34
 
 
35
 
sys.path.append('..')  # add local cupstream2distro
36
 
from cupstream2distro import launchpadmanager
37
 
 
38
 
BINDIR = os.path.dirname(__file__)
39
 
PREFIX = 'cu2d'
40
 
TEMPLATES = {
41
 
    'master': 'master-config.xml.tmpl',
42
 
    'waitonstacks': 'waitonstacks-stack-config.xml.tmpl',
43
 
    'prepare-master': 'prepare-config.xml.tmpl',
44
 
    'prepare-project': 'prepare-project-config.xml.tmpl',
45
 
    'build': 'build-stack-config.xml.tmpl',
46
 
    'check': 'check-stack-config.xml.tmpl',
47
 
    'publish': 'publish-stack-config.xml.tmpl'
48
 
}
49
 
DEFAULT_CREDENTIALS = os.path.expanduser('~/.cu2d.cred')
50
 
 
51
 
 
52
 
def load_jenkins_credentials(path):
53
 
    """ Load Credentials from credentials configuration file """
54
 
    if not os.path.exists(path):
55
 
        return False
56
 
 
57
 
    logging.debug('Loading credentials from %s', path)
58
 
    cred = yaml.load(file(path, 'r'))
59
 
    return False if not 'jenkins' in cred else cred['jenkins']
60
 
 
61
 
 
62
 
def load_stack_cfg(path):
63
 
    """ Load stack configuration from file
64
 
 
65
 
    TODO: Verify that mandatory settings are defined
66
 
    """
67
 
 
68
 
    if not os.path.exists(path):
69
 
        return False
70
 
 
71
 
    logging.debug('Loading stack configuration from %s', path)
72
 
    cfg = yaml.load(file(path, 'r'))
73
 
    return False if not 'stack' in cfg else cfg['stack']
74
 
 
75
 
 
76
 
def setup_branch(stack):
77
 
    """Configure the branch so that lp-propose target the wanted one
78
 
 
79
 
    :param stack: dictionary with configuration of the stack
80
 
    """
81
 
 
82
 
    if stack['projects']:
83
 
        lp = launchpadmanager.get_launchpad(use_cred_file=None)
84
 
        for prj in stack['projects']:
85
 
            if type(prj) == dict:
86
 
                srcname = prj.keys()[0]
87
 
                lpb = prj[srcname]
88
 
            else:
89
 
                lpb = "lp:{}".format(prj)
90
 
 
91
 
            current_branch = lp.branches.getByUrl(url=lpb)
92
 
            if not current_branch:
93
 
                logging.error("No branch exist on launchpad for {}".format(lpb))
94
 
                sys.exit(1)
95
 
 
96
 
            # set the bzr config so that targeted branch is the right one when using the short name
97
 
            logging.info("Set branch {} as target for {}".format(current_branch.unique_name, lpb))
98
 
            cmd = ["bzr", "config", "-d", "lp:{}".format(current_branch.unique_name), "public_branch={}".format(lpb)]
99
 
            if subprocess.call(cmd) != 0:
100
 
                logging.error("Can't set the target branch {} for {}".format(current_branch.unique_name, lpb))
101
 
                sys.exit(1)
102
 
 
103
 
 
104
 
def setup_job(jkh, jjenv, jobname, tmplname, ctx, update=False):
105
 
    """ Generate template and create or update jenkins job
106
 
 
107
 
    :param jkh: jenkins handle
108
 
    :param jjenv: handle to jinja environment
109
 
    :param jobname: jenkins' job name
110
 
    :param tmplname: template name
111
 
    :param ctx: jinja context (dict) to merge with the template
112
 
    :param update: update existing job if True
113
 
    """
114
 
    logging.debug('Generating job: %s', jobname)
115
 
    tmpl = jjenv.get_template(tmplname)
116
 
    jkcfg = tmpl.render(ctx)
117
 
    if not jkh.job_exists(jobname):
118
 
        logging.info("Creating Jenkins Job %s ", jobname)
119
 
        jkh.create_job(jobname, jkcfg)
120
 
    else:
121
 
        if update:
122
 
            logging.info("Reconfiguring Jenkins Job %s ", jobname)
123
 
            jkh.reconfig_job(jobname, jkcfg)
124
 
        else:
125
 
            logging.debug('update set to %s. Skipping reconfiguration of '
126
 
                          '%s', update, jobname)
127
 
    return True
128
 
 
129
 
 
130
 
def update_jenkins(jkcfg, stack, update=False):
131
 
    """ Add/update jenkins jobs
132
 
 
133
 
    :param jkcfg: dictionary with the credentials
134
 
    :param stack: dictionary with configuration of the stack
135
 
    :param update: Update existing jobs if true
136
 
 
137
 
    :return: True on success
138
 
    """
139
 
    if not 'tmpldir' in stack:
140
 
        tmpldir = os.path.join(BINDIR, 'templates')
141
 
    else:
142
 
        tmpldir = stack['tmpldir']
143
 
 
144
 
    tmpldir = os.path.abspath(tmpldir)
145
 
    logging.debug('Templates directory: %s', tmpldir)
146
 
 
147
 
    if not os.path.exists(tmpldir):
148
 
        logging.error('Template directory doesn\'t exist')
149
 
        return False
150
 
 
151
 
    if not jkcfg['url']:
152
 
        logging.error("Please provide a URL to the jenkins instance.")
153
 
        sys.exit(1)
154
 
 
155
 
    jjenv = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpldir))
156
 
 
157
 
    if 'username' in jkcfg:
158
 
        jkh = jenkins.Jenkins(jkcfg['url'],
159
 
                                  username=jkcfg['username'],
160
 
                                  password=jkcfg['password'])
161
 
    else:
162
 
        jkh = jenkins.Jenkins(jkcfg['url'])
163
 
 
164
 
    basename = [PREFIX, stack['name'], stack['release']]
165
 
    ctxbase = {
166
 
        'stack': stack['name'],
167
 
        'release': stack['release']
168
 
    }
169
 
    subjobs = {}
170
 
 
171
 
    # wait that other stack are published if there are some stack dependencies
172
 
    if 'dependencies' in stack:
173
 
        jobname = "-".join(basename + ['0waitonstacks'])
174
 
        subjobs['jobwaitonstacks'] = jobname
175
 
        ctx = dict(ctxbase)
176
 
        ctx['opts'] = ''
177
 
        setup_job(jkh, jjenv, jobname, TEMPLATES['waitonstacks'],
178
 
                  ctx, update)
179
 
 
180
 
    # prepare by project
181
 
    ctx = dict(ctxbase)
182
 
    projects = []
183
 
    if stack['projects']:
184
 
        for prj in stack['projects']:
185
 
            if type(prj) == dict:
186
 
                srcname = prj.keys()[0]
187
 
                lpb = prj[srcname]
188
 
            else:
189
 
                srcname = prj
190
 
                lpb = ''
191
 
            jobname = "-".join(basename + ['1.1prepare', srcname])
192
 
            projects.append(jobname)
193
 
            if not lpb:
194
 
                lpb = "lp:%s" % srcname
195
 
 
196
 
            ctx['opts'] = '-s %s -p %s -b %s -n %s' % (
197
 
                stack['series'], stack['ppa'], lpb, srcname)
198
 
            ctx['projectname'] = srcname
199
 
            setup_job(jkh, jjenv, jobname, TEMPLATES['prepare-project'],
200
 
                      ctx, update)
201
 
 
202
 
    # Main prepare
203
 
    jobname = "-".join(basename + ['1.0prepare'])
204
 
    subjobs['jobprepare'] = jobname
205
 
    ctx = dict(ctxbase)
206
 
    ctx['projects'] = ','.join(projects)
207
 
    setup_job(jkh, jjenv, jobname, TEMPLATES['prepare-master'],
208
 
              ctx, update)
209
 
 
210
 
    # build
211
 
    jobname = "-".join(basename + ['2.1build'])
212
 
    subjobs['jobbuild'] = jobname
213
 
    ctx = dict(ctxbase)
214
 
    ctx['opts'] = '-s %s -p %s' % (stack['series'], stack['ppa'])
215
 
    setup_job(jkh, jjenv, jobname, TEMPLATES['build'],
216
 
              ctx, update)
217
 
 
218
 
    # check
219
 
    if 'extracheck' in stack and stack['extracheck']:
220
 
        jobname = "-".join(basename + ['2.2check'])
221
 
        subjobs['jobbuild'] += ',' + jobname
222
 
        ctx = dict(ctxbase)
223
 
        ctx['opts'] = '-a -s %s -p %s' % (stack['series'], stack['ppa'])
224
 
        ctx['extracheck'] = stack['extracheck']
225
 
        setup_job(jkh, jjenv, jobname, TEMPLATES['check'],
226
 
                  ctx, update)
227
 
 
228
 
    # publish
229
 
    jobname = "-".join(basename + ['3.0publish'])
230
 
    subjobs['jobpublish'] = jobname
231
 
    ctx = dict(ctxbase)
232
 
    ctx['opts'] = '-s %s -p %s -j cu2d-%s-%s' % (stack['series'], stack['ppa'],
233
 
                                        stack['name'], stack['release'])
234
 
    setup_job(jkh, jjenv, jobname, TEMPLATES['publish'],
235
 
              ctx, update)
236
 
 
237
 
    # Master job
238
 
    # Process at the end because it must know information about subjobs
239
 
    jobname = "-".join(basename)
240
 
    ctx = dict(ctxbase)
241
 
    ctx.update(subjobs)
242
 
    if 'schedule' in stack:
243
 
        ctx['schedule'] = stack['schedule']
244
 
    if 'dependencies' in stack:
245
 
        ctx['dependencies'] = True
246
 
    setup_job(jkh, jjenv, jobname, TEMPLATES['master'],
247
 
              ctx, update)
248
 
 
249
 
    return True
250
 
 
251
 
 
252
 
def set_logging(debugmode=False):
253
 
    """Initialize logging"""
254
 
    logging.basicConfig(
255
 
        level=logging.DEBUG if debugmode else logging.INFO,
256
 
        format="%(asctime)s %(levelname)s %(message)s"
257
 
        )
258
 
    logging.debug('Debug mode enabled')
259
 
 
260
 
 
261
 
def main():
262
 
    ''' Main routine '''
263
 
    parser = argparse.ArgumentParser(
264
 
        description='Create/Update the configuration of the Jenkins jobs '
265
 
            'for a stack.',
266
 
        epilog = """Example:
267
 
To update the indicator stack run the following command:
268
 
    $ ./cu2d-update-stack -dU ./etc/indicators-head.cfg
269
 
        """,
270
 
        formatter_class=argparse.RawTextHelpFormatter)
271
 
    parser.add_argument('-C', '--credentials', metavar='CREDENTIALFILE',
272
 
                        default=DEFAULT_CREDENTIALS,
273
 
                        help='use Jenkins and load credentials from '
274
 
                        'CREDENTIAL FILE\n(default: %s)' % DEFAULT_CREDENTIALS)
275
 
    parser.add_argument('-U', '--update-jobs', action='store_true',
276
 
                        default=False,
277
 
                        help='by default only new jobs are added. This '
278
 
                        'option enables \nupdate of existing jobs from '
279
 
                        'configuration template.')
280
 
    parser.add_argument('-S', '--no-setupbranch', action='store_true',
281
 
                        default=False,
282
 
                        help='Skip branch setup (useful if you do not have '
283
 
                        'privileges to configure branchs but need to update '
284
 
                        'job configuration. In this case, only update of '
285
 
                        'existing jobs is allowed')
286
 
    parser.add_argument('-d', '--debug', action='store_true', default=False,
287
 
                        help='enable debug mode')
288
 
    parser.add_argument('stackcfg', help='Path to a configuration file for '
289
 
                        'the stack')
290
 
 
291
 
    args = parser.parse_args()
292
 
    set_logging(args.debug)
293
 
 
294
 
    stackcfg = load_stack_cfg(args.stackcfg)
295
 
    if not stackcfg:
296
 
        logging.error('Stack configuration failed to load. Aborting!')
297
 
 
298
 
    if not args.no_setupbranch:
299
 
        setup_branch(stackcfg)
300
 
 
301
 
    credentials = None
302
 
    if args.credentials:
303
 
        credentialsfile = args.credentials
304
 
        credentials = load_jenkins_credentials(
305
 
            os.path.expanduser(credentialsfile))
306
 
        if not credentials:
307
 
            logging.error('Credentials not found. Aborting!')
308
 
            sys.exit(1)
309
 
        if not update_jenkins(credentials, stackcfg, args.update_jobs):
310
 
            logging.error('Failed to configure jenkins jobs. Aborting!')
311
 
            sys.exit(2)
312
 
 
313
 
if __name__ == "__main__":
314
 
    main()