Release Automation
[releng.git] / releases / scripts / create_jobs.py
1 #!/usr/bin/env python2
2 # SPDX-License-Identifier: Apache-2.0
3 ##############################################################################
4 # Copyright (c) 2018 The Linux Foundation and others.
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 ##############################################################################
10 """
11 Create Gerrit Branches
12 """
13
14 import argparse
15 import logging
16 import os
17 import re
18 import yaml
19 import subprocess
20
21 # import ruamel
22 from ruamel.yaml import YAML
23
24
25 logging.basicConfig(level=logging.INFO)
26
27
28 def has_string(filepath, string):
29     """
30     Return True if the given filepath contains the regex string
31     """
32     with open(filepath) as yaml_file:
33         for line in yaml_file:
34             if string.search(line):
35                 return True
36     return False
37
38
39 def jjb_files(project, release):
40     """
41     Return sets of YAML file names that contain 'stream' for a given
42     project, and file that already contain the stream.
43     """
44     files, skipped = set(), set()
45     file_ending = re.compile(r'ya?ml$')
46     search_string = re.compile(r'^\s+stream:')
47     release_string = re.compile(r'- %s:' % release)
48     jjb_path = os.path.join('jjb', project)
49
50     if not os.path.isdir(jjb_path):
51         logging.warn("JJB directory does not exist at %s, skipping job "
52                      "creation", jjb_path)
53         return (files, skipped)
54
55     for file_name in os.listdir(jjb_path):
56         file_path = os.path.join(jjb_path, file_name)
57         if os.path.isfile(file_path) and file_ending.search(file_path):
58             if has_string(file_path, release_string):
59                 skipped.add(file_path)
60             elif has_string(file_path, search_string):
61                 files.add(file_path)
62     return (files, skipped)
63
64
65 def main():
66     """
67     Create Jenkins Jobs for stable branches in Release File
68     """
69     parser = argparse.ArgumentParser()
70     parser.add_argument('--file', '-f',
71                         type=argparse.FileType('r'),
72                         required=True)
73     args = parser.parse_args()
74
75     project_yaml = yaml.safe_load(args.file)
76
77     # Get the release name from the file path
78     release = os.path.split(os.path.dirname(args.file.name))[1]
79
80     create_jobs(release, project_yaml)
81
82
83 def create_jobs(release, project_yaml):
84     """Add YAML to JJB files for release stream"""
85     logger = logging.getLogger(__file__)
86
87     # We assume here project keep their subrepo jobs under the part
88     # project name. Otherwise we'll have to look for jjb/<repo> for each
89     # branch listed.
90     project, _ = next(iter(project_yaml['branches'][0]['location'].items()))
91
92     yaml_parser = YAML()
93     yaml_parser.preserve_quotes = True
94     yaml_parser.explicit_start = True
95     # yaml_parser.indent(mapping=4, sequence=0, offset=0)
96     # These are some esoteric values that produce indentation matching our jjb
97     # configs
98     # yaml_parser.indent(mapping=3, sequence=3, offset=2)
99     # yaml_parser.indent(sequence=4, offset=2)
100     yaml_parser.indent(mapping=2, sequence=4, offset=2)
101
102     (job_files, skipped_files) = jjb_files(project, release)
103
104     if skipped_files:
105         logger.info("Jobs already exists for %s in files: %s",
106                     project, ', '.join(skipped_files))
107     # Exit if there are not jobs to create
108     if not job_files:
109         return
110     logger.info("Creating Jenkins Jobs for %s in files: %s",
111                 project, ', '.join(job_files))
112
113     stable_branch_stream = """\
114       %s:
115           branch: 'stable/{stream}'
116           gs-pathname: '/{stream}'
117           disabled: false
118     """ % release
119
120     stable_branch_yaml = yaml_parser.load(stable_branch_stream)
121     stable_branch_yaml[release].yaml_set_anchor(release, always_dump=True)
122
123     for job_file in job_files:
124         yaml_jjb = yaml_parser.load(open(job_file))
125         if 'stream' not in yaml_jjb[0]['project']:
126             continue
127
128         # TODO: Some JJB files don't have 'stream'
129         project_config = yaml_jjb[0]['project']['stream']
130         # There is an odd issue where just appending adds a newline before the
131         # branch config, so we append (presumably after master) instead.
132         project_config.insert(1, stable_branch_yaml)
133
134         # NOTE: In the future, we may need to override one or multiple of the
135         #       following ruamal Emitter methods:
136         #         * ruamel.yaml.emitter.Emitter.expect_block_sequence_item
137         #         * ruamel.yaml.emitter.Emitter.write_indent
138         #       To hopefully replace the need to shell out to sed...
139         yaml_parser.dump(yaml_jjb, open(job_file, 'w'))
140         args = ['sed', '-i', 's/^  //', job_file]
141         subprocess.Popen(args, stdout=subprocess.PIPE, shell=False)
142
143
144 if __name__ == "__main__":
145     main()