~andrewjbeach/juju-ci-tools/make-local-patcher

747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
1
#!/usr/bin/python
749.1.18 by Curtis Hovey
Get the build-revision from the job parameters.
2
"""Manage the blessed juju revision testing candiates."""
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
3
4
from __future__ import print_function
5
6
from argparse import ArgumentParser
822.1.1 by John George
While publishing weekly streams, backup the buildvars.json files to S3, to maintain a record of the builds that have been published.
7
import datetime
749.1.3 by Curtis Hovey
Added extract command to canidate module.
8
import json
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
9
import os
10
import shutil
749.1.3 by Curtis Hovey
Added extract command to canidate module.
11
import subprocess
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
12
import sys
13
import traceback
14
15
from jujuci import (
849.1.1 by Aaron Bentley
Use credentials for candidate download.
16
    add_credential_args,
776.1.8 by Curtis Hovey
Move juju-ci job names to jujuci.py.
17
    BUILD_REVISION,
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
18
    get_build_data,
19
    get_artifacts,
849.1.1 by Aaron Bentley
Use credentials for candidate download.
20
    get_credentials,
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
21
    JENKINS_URL,
776.1.8 by Curtis Hovey
Move juju-ci job names to jujuci.py.
22
    PUBLISH_REVISION
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
23
)
822.1.1 by John George
While publishing weekly streams, backup the buildvars.json files to S3, to maintain a record of the builds that have been published.
24
from utility import (
889.6.6 by Aaron Bentley
Extract extract_deb.
25
    extract_deb,
889.6.1 by Aaron Bentley
Make utility for determining architecture.
26
    get_deb_arch,
1522.1.1 by Aaron Bentley
Use common code for finding revision build in candidates.
27
    get_revision_build,
1001.1.1 by John George
Add openstack_basic_check.py
28
    run_command,
822.1.1 by John George
While publishing weekly streams, backup the buildvars.json files to S3, to maintain a record of the builds that have been published.
29
    s3_cmd,
30
    temp_dir,
31
)
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
32
33
849.1.1 by Aaron Bentley
Use credentials for candidate download.
34
def find_publish_revision_number(credentials, br_number, limit=20):
747.2.2 by Curtis Hovey
Fix doc.
35
    """Return the publish-revsion number paired with build-revision number."""
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
36
    found_number = None
37
    job_number = 'lastSuccessfulBuild'
38
    for i in range(limit):
39
        build_data = get_build_data(
849.1.1 by Aaron Bentley
Use credentials for candidate download.
40
            JENKINS_URL, credentials, PUBLISH_REVISION, build=job_number)
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
41
        if not build_data:
42
            return None
43
        # Ensure we have the real job number (an int), not an alias.
44
        job_number = build_data['number']
1522.1.1 by Aaron Bentley
Use common code for finding revision build in candidates.
45
        if get_revision_build(build_data) == str(br_number):
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
46
            found_number = job_number
47
            break
48
        job_number = job_number - 1
49
    return found_number
50
51
749.1.3 by Curtis Hovey
Added extract command to canidate module.
52
def prepare_dir(dir_path, dry_run=False, verbose=False):
1063.2.1 by seman.said at canonical
Updated to extract OS X client from the artifacts directory.
53
    """Create to clean a directory."""
749.1.3 by Curtis Hovey
Added extract command to canidate module.
54
    if os.path.isdir(dir_path):
55
        if verbose:
56
            print('Cleaning %s' % dir_path)
57
        if not dry_run:
58
            shutil.rmtree(dir_path)
59
    else:
60
        if verbose:
61
            print('Creating %s' % dir_path)
62
    if not dry_run:
63
        os.makedirs(dir_path)
64
65
1052.2.1 by seman.said at canonical
Updated to rename the candidate directories to version numbers rather than branches.
66
def download_candidate_files(credentials, release_number, path, br_number,
749.1.19 by Curtis Hovey
Rename update_candidate to download_candidate_files.
67
                             pr_number=None, dry_run=False, verbose=False):
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
68
    """Download the files from the build-revision and publish-revision jobs.
69
70
    The buildvars.json for the specific build-revision number is downloaded.
71
    All the binary and source packages from the last successful build of
72
    publish revision are downloaded.
73
    """
1052.2.1 by seman.said at canonical
Updated to rename the candidate directories to version numbers rather than branches.
74
    artifact_dir_name = '%s-artifacts' % release_number
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
75
    candidate_dir = os.path.join(path, artifact_dir_name)
749.1.3 by Curtis Hovey
Added extract command to canidate module.
76
    prepare_dir(candidate_dir, dry_run, verbose)
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
77
    get_artifacts(
849.1.1 by Aaron Bentley
Use credentials for candidate download.
78
        credentials, BUILD_REVISION, br_number, 'buildvars.json',
79
        candidate_dir, dry_run=dry_run, verbose=verbose)
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
80
    if not pr_number:
849.1.1 by Aaron Bentley
Use credentials for candidate download.
81
        pr_number = find_publish_revision_number(credentials, br_number)
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
82
    get_artifacts(
849.1.1 by Aaron Bentley
Use credentials for candidate download.
83
        credentials, PUBLISH_REVISION, pr_number, 'juju-core*', candidate_dir,
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
84
        dry_run=dry_run, verbose=verbose)
85
86
749.1.3 by Curtis Hovey
Added extract command to canidate module.
87
def get_artifact_dirs(path):
749.1.14 by Curtis Hovey
Added tests for run_command.
88
    """List the directories that contain artifacts."""
749.1.3 by Curtis Hovey
Added extract command to canidate module.
89
    dirs = []
90
    for name in os.listdir(path):
91
        artifacts_path = os.path.join(path, name)
92
        if name.endswith('-artifacts') and os.path.isdir(artifacts_path):
93
            dirs.append(name)
94
    return dirs
95
96
97
def get_package(artifacts_path, version):
749.1.14 by Curtis Hovey
Added tests for run_command.
98
    """Return the path to the expected juju-core package for the localhost."""
749.1.3 by Curtis Hovey
Added extract command to canidate module.
99
    release = subprocess.check_output(['lsb_release', '-sr']).strip()
889.6.1 by Aaron Bentley
Make utility for determining architecture.
100
    arch = get_deb_arch()
1287 by Curtis Hovey
Revert juju-core2 support.
101
    package_name = 'juju-core_{}-0ubuntu1~{}.1~juju1_{}.deb'.format(
102
        version, release, arch)
749.1.3 by Curtis Hovey
Added extract command to canidate module.
103
    package_path = os.path.join(artifacts_path, package_name)
104
    return package_path
105
106
107
def extract_candidates(path, dry_run=False, verbose=False):
749.1.14 by Curtis Hovey
Added tests for run_command.
108
    """Extract all the candidate juju binaries for the local machine.
109
1052.2.1 by seman.said at canonical
Updated to rename the candidate directories to version numbers rather than branches.
110
    Each candidate will be extracted to a directory named after the version
749.1.14 by Curtis Hovey
Added tests for run_command.
111
    the artifacts (packages) were made from. Thus the package that matches
112
    the localhost's series and architecture in the master-artifacts/ directory
113
    will be extracted to a sibling directory named "master/" The buildvars.json
114
    data will be copied to the top of "master" to provide information about
115
    the origin of the binaries.
116
    """
749.1.3 by Curtis Hovey
Added extract command to canidate module.
117
    for dir_name in get_artifact_dirs(path):
118
        artifacts_path = os.path.join(path, dir_name)
119
        buildvars_path = os.path.join(artifacts_path, 'buildvars.json')
120
        with open(buildvars_path) as bf:
121
            buildvars = json.load(bf)
122
        version = buildvars['version']
1086.1.1 by seman.said at canonical
Remove extracting OSX client into the candidate directory.
123
        package_path = get_package(artifacts_path, version)
124
        candidate_path = os.path.join(path, version)
749.1.3 by Curtis Hovey
Added extract command to canidate module.
125
        if verbose:
126
            print('extracting %s to %s' % (package_path, candidate_path))
127
        prepare_dir(candidate_path, dry_run, verbose)
128
        if not dry_run:
1086.1.1 by seman.said at canonical
Remove extracting OSX client into the candidate directory.
129
            extract_deb(package_path, candidate_path)
749.1.3 by Curtis Hovey
Added extract command to canidate module.
130
        if verbose:
749.1.4 by Curtis Hovey
Cope buildvars.json to the candiate path, not the bin dir.
131
            print('Copying %s to %s' % (buildvars_path, candidate_path))
749.1.3 by Curtis Hovey
Added extract command to canidate module.
132
        if not dry_run:
749.1.12 by Curtis Hovey
Added tests for extract_candidates.
133
            new_path = os.path.join(candidate_path, 'buildvars.json')
134
            shutil.copyfile(buildvars_path, new_path)
935.1.1 by Curtis Hovey
Copy stats after copying buildvars.
135
            shutil.copystat(buildvars_path, new_path)
749.1.3 by Curtis Hovey
Added extract command to canidate module.
136
137
749.1.6 by Curtis Hovey
Added an untested publish function.
138
def get_scripts(juju_release_tools=None):
749.1.14 by Curtis Hovey
Added tests for run_command.
139
    """Return a tuple paths to the assemble_script and publish_scripts."""
749.1.6 by Curtis Hovey
Added an untested publish function.
140
    assemble_script = 'assemble-streams.bash'
141
    publish_script = 'publish-public-tools.bash'
142
    if juju_release_tools:
143
        assemble_script = os.path.join(
144
            juju_release_tools, assemble_script)
145
        publish_script = os.path.join(
146
            juju_release_tools, publish_script)
147
    return assemble_script, publish_script
148
149
150
def publish_candidates(path, streams_path,
151
                       juju_release_tools=None, dry_run=False, verbose=False):
749.1.14 by Curtis Hovey
Added tests for run_command.
152
    """Assemble and publish weekly streams from the candidates."""
822.1.1 by John George
While publishing weekly streams, backup the buildvars.json files to S3, to maintain a record of the builds that have been published.
153
    timestamp = datetime.datetime.utcnow().strftime('%Y_%m_%dT%H_%M_%S')
749.1.14 by Curtis Hovey
Added tests for run_command.
154
    with temp_dir() as debs_path:
155
        for dir_name in get_artifact_dirs(path):
156
            artifacts_path = os.path.join(path, dir_name)
822.1.1 by John George
While publishing weekly streams, backup the buildvars.json files to S3, to maintain a record of the builds that have been published.
157
            branch_name = dir_name.split('-')[0]
749.1.14 by Curtis Hovey
Added tests for run_command.
158
            for deb_name in os.listdir(artifacts_path):
159
                deb_path = os.path.join(artifacts_path, deb_name)
749.1.17 by Curtis Hovey
Added tests for publish_candidates.
160
                if verbose:
161
                    print('Copying %s' % deb_path)
749.1.14 by Curtis Hovey
Added tests for run_command.
162
                new_path = os.path.join(debs_path, deb_name)
163
                shutil.copyfile(deb_path, new_path)
822.1.1 by John George
While publishing weekly streams, backup the buildvars.json files to S3, to maintain a record of the builds that have been published.
164
                if deb_name == 'buildvars.json':
165
                    # buildvars.json is also in the artifacts_path; copied by
166
                    # download_candidate_files(). Set it aside so it can be
167
                    # sync'd to S3 as a record of what was published.
168
                    buildvar_dir = '{}/weekly/{}/{}'.format(
169
                        path, timestamp, branch_name)
170
                    if not os.path.isdir(buildvar_dir):
171
                        os.makedirs(buildvar_dir)
172
                    buildvar_path = '{}/{}'.format(buildvar_dir, deb_name)
173
                    shutil.copyfile(deb_path, buildvar_path)
749.1.14 by Curtis Hovey
Added tests for run_command.
174
        assemble_script, publish_script = get_scripts(juju_release_tools)
175
        # XXX sinzui 2014-12-01: IGNORE uses the local juju, but when
176
        # testing juju's that change generate-tools, we may need to use
177
        # the highest version.
178
        command = [
980.1.2 by Curtis Hovey
Hush lint.
179
            assemble_script, '-t', debs_path, 'weekly', 'IGNORE',
749.1.14 by Curtis Hovey
Added tests for run_command.
180
            streams_path]
749.1.6 by Curtis Hovey
Added an untested publish function.
181
        run_command(command, dry_run=dry_run, verbose=verbose)
1003.3.4 by Curtis Hovey
Use publish to retry stream publication.
182
    publish(streams_path, publish_script, dry_run=dry_run, verbose=verbose)
822.1.1 by John George
While publishing weekly streams, backup the buildvars.json files to S3, to maintain a record of the builds that have been published.
183
    # Sync buildvars.json files out to s3.
184
    url = 's3://juju-qa-data/juju-releases/weekly/'
185
    s3_path = '{}/weekly/{}'.format(path, timestamp)
186
    if verbose:
187
        print('Calling s3cmd to sync %s out to %s' % (s3_path, url))
188
    if not dry_run:
189
        s3_cmd(['sync', s3_path, url])
749.1.21 by Curtis Hovey
publish_candidates calls extract_candidates when the streams are created.
190
    extract_candidates(path, dry_run=dry_run, verbose=verbose)
749.1.6 by Curtis Hovey
Added an untested publish function.
191
192
1003.3.3 by Curtis Hovey
Added publish() to retry jobs.
193
def publish(streams_path, publish_script, dry_run=False, verbose=False):
1003.3.2 by Curtis Hovey
Incremental change for a function to test.
194
    juju_dist_path = os.path.join(streams_path, 'juju-dist')
1003.3.6 by Curtis Hovey
Don't create command more than once.
195
    command = [publish_script, 'weekly', juju_dist_path, 'cpc']
1003.3.2 by Curtis Hovey
Incremental change for a function to test.
196
    for attempt in range(3):
197
        try:
198
            run_command(command, dry_run=dry_run, verbose=verbose)
199
            break
200
        except subprocess.CalledProcessError:
201
            # Raise an error when the third attempt fails; the cloud is ill.
202
            if attempt == 2:
203
                raise
204
205
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
206
def parse_args(args=None):
207
    """Return the argument parser for this program."""
208
    parser = ArgumentParser("Manage the successful Juju CI candidates.")
209
    parser.add_argument(
210
        '-d', '--dry-run', action='store_true', default=False,
211
        help='Do not make changes.')
212
    parser.add_argument(
213
        '-v', '--verbose', action='store_true', default=False,
214
        help='Increase verbosity.')
749.1.2 by Curtis Hovey
Use subparsers to manage the args for the update and future publish and extract
215
    subparsers = parser.add_subparsers(help='sub-command help', dest="command")
749.1.19 by Curtis Hovey
Rename update_candidate to download_candidate_files.
216
    # ./candidate download -b 1234 master ~/candidate
217
    parser_update = subparsers.add_parser(
849.1.1 by Aaron Bentley
Use credentials for candidate download.
218
        'download', help='download a candidate')
749.1.2 by Curtis Hovey
Use subparsers to manage the args for the update and future publish and extract
219
    parser_update.add_argument(
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
220
        '-b', '--br-number', default='lastSuccessfulBuild',
221
        help="The specific build-revision number.")
749.1.2 by Curtis Hovey
Use subparsers to manage the args for the update and future publish and extract
222
    parser_update.add_argument(
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
223
        '-p', '--pr-number',
224
        help="The specific publish-revision-revision number.")
749.1.2 by Curtis Hovey
Use subparsers to manage the args for the update and future publish and extract
225
    parser_update.add_argument(
1052.2.1 by seman.said at canonical
Updated to rename the candidate directories to version numbers rather than branches.
226
        'release_number', help='The successfully test branch release number.')
749.1.2 by Curtis Hovey
Use subparsers to manage the args for the update and future publish and extract
227
    parser_update.add_argument(
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
228
        'path', help='The path to save the candiate data to.')
849.1.1 by Aaron Bentley
Use credentials for candidate download.
229
    add_credential_args(parser_update)
749.1.3 by Curtis Hovey
Added extract command to canidate module.
230
    # ./candidate extract master ~/candidate
749.1.6 by Curtis Hovey
Added an untested publish function.
231
    parser_extract = subparsers.add_parser(
232
        'extract',
233
        help='extract candidates that match the local series and arch.')
749.1.3 by Curtis Hovey
Added extract command to canidate module.
234
    parser_extract.add_argument(
749.1.5 by Curtis Hovey
Fixed help.
235
        'path', help='The path to the candiate data dir.')
749.1.8 by Curtis Hovey
Added tests for prepare_dir.
236
    # ./candidate --juju-release-tools $JUJU_RELEASE_TOOLS \
237
    #    publish ~/candidate ~/streams
749.1.6 by Curtis Hovey
Added an untested publish function.
238
    parser_publish = subparsers.add_parser(
239
        'publish', help='Publish streams for the candidates')
240
    parser_publish.add_argument(
241
        '-t', '--juju-release-tools',
242
        help='The path to the juju-release-tools dir.')
243
    parser_publish.add_argument(
244
        'path', help='The path to the candiate data dir.')
245
    parser_publish.add_argument(
246
        'streams_path', help='The path to the streams data dir.')
849.1.1 by Aaron Bentley
Use credentials for candidate download.
247
    parsed_args = parser.parse_args(args)
248
    return parsed_args, get_credentials(parsed_args)
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
249
250
251
def main(argv):
747.2.2 by Curtis Hovey
Fix doc.
252
    """Manage successful Juju CI candiates."""
849.1.1 by Aaron Bentley
Use credentials for candidate download.
253
    args, credentials = parse_args(argv)
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
254
    try:
749.1.19 by Curtis Hovey
Rename update_candidate to download_candidate_files.
255
        if args.command == 'download':
256
            download_candidate_files(
1052.2.1 by seman.said at canonical
Updated to rename the candidate directories to version numbers rather than branches.
257
                credentials, args.release_number, args.path, args.br_number,
849.1.1 by Aaron Bentley
Use credentials for candidate download.
258
                args.pr_number, dry_run=args.dry_run, verbose=args.verbose)
749.1.3 by Curtis Hovey
Added extract command to canidate module.
259
        elif args.command == 'extract':
260
            extract_candidates(
261
                args.path, dry_run=args.dry_run, verbose=args.verbose)
749.1.6 by Curtis Hovey
Added an untested publish function.
262
        elif args.command == 'publish':
263
            publish_candidates(
264
                args.path, args.streams_path,
265
                juju_release_tools=args.juju_release_tools,
266
                dry_run=args.dry_run, verbose=args.verbose)
747.2.1 by Curtis Hovey
Added an untested, but usable script to collect the candidate artificats
267
    except Exception as e:
268
        print(e)
269
        if args.verbose:
270
            traceback.print_tb(sys.exc_info()[2])
271
        return 2
272
    if args.verbose:
273
        print("Done.")
274
    return 0
275
276
277
if __name__ == '__main__':
278
    sys.exit(main(sys.argv[1:]))