2 """Build OpenDaylight's RPMs using YAML build configs and Jinja2 templates."""
12 from string import Template
13 from urllib2 import urlopen
15 from requests.exceptions import HTTPError
19 from bs4 import BeautifulSoup
21 sys.stderr.write("We recommend using our included Vagrant env.\n")
22 sys.stderr.write("Else, do `pip install -r requirements.txt` in a venv.\n")
25 import cache.cache as cache
26 import specs.build_specs as build_specs
28 # Common paths used in this script
29 # This file is assumed to be in the root of the RPM build logic's dir structure
30 project_root = os.path.dirname(os.path.abspath(__file__))
31 cache_dir = os.path.join(project_root, "cache")
32 specs_dir = os.path.join(project_root, "specs")
33 rpmbuild_dir = os.path.join(os.path.expanduser("~"), "rpmbuild")
34 src_in_dir = os.path.join(rpmbuild_dir, "SOURCES")
35 spec_in_dir = os.path.join(rpmbuild_dir, "SPECS")
36 srpm_out_dir = os.path.join(rpmbuild_dir, "SRPMS")
37 rpm_out_dir = os.path.join(rpmbuild_dir, "RPMS", "noarch")
39 # Templates that can be specialized into common artifact names per-build
40 odl_template = Template("opendaylight-$version_major.$version_minor."
41 "$version_patch-$rpm_release.tar.gz")
42 specfile_template = Template("opendaylight-$version_major.$version_minor."
43 "$version_patch-$rpm_release.spec")
44 unitfile_tb_template = Template("opendaylight-$sysd_commit.service.tar.gz")
45 rpm_template = Template("opendaylight-$version_major.$version_minor."
46 "$version_patch-$rpm_release.el7.noarch.rpm")
47 srpm_template = Template("opendaylight-$version_major.$version_minor."
48 "$version_patch-$rpm_release.el7.src.rpm")
52 """Build the RPMs described by the given build description.
54 :param build: Description of an RPM build, typically from build_vars.yaml
58 # Specialize a series of name templates for the given build
59 odl_tarball = odl_template.substitute(build)
60 odl_rpm = rpm_template.substitute(build)
61 odl_srpm = srpm_template.substitute(build)
62 odl_specfile = specfile_template.substitute(build)
63 unitfile_tarball = unitfile_tb_template.substitute(build)
65 # After building strings from the name templates, build their full path
66 odl_tarball_path = os.path.join(cache_dir, odl_tarball)
67 unitfile_tarball_path = os.path.join(cache_dir, unitfile_tarball)
68 specfile_path = os.path.join(specs_dir, odl_specfile)
69 spec_in_path = os.path.join(spec_in_dir, odl_specfile)
70 rpm_out_path = os.path.join(rpm_out_dir, odl_rpm)
71 srpm_out_path = os.path.join(srpm_out_dir, odl_srpm)
73 # Call a helper function to cache the artifacts required for each build
74 cache.cache_build(build)
76 # Call helper script to build the required RPM .spec files
77 build_specs.build_spec(build)
79 # Clean up old rpmbuild dir structure if it exists
80 if os.path.isdir(rpmbuild_dir):
81 shutil.rmtree(rpmbuild_dir)
83 # Create rpmbuild dir structure
84 subprocess.call("rpmdev-setuptree")
86 # Move unitfile, tarball and specfile to correct rpmbuild dirs
87 shutil.copy(odl_tarball_path, src_in_dir)
88 shutil.copy(unitfile_tarball_path, src_in_dir)
89 shutil.copy(specfile_path, spec_in_dir)
91 # Call rpmbuild, build both SRPMs/RPMs
92 subprocess.call(["rpmbuild", "-ba", spec_in_path])
94 # Copy the RPMs/SRPMs from their output dir to the cache dir
95 shutil.copy(rpm_out_path, cache_dir)
96 shutil.copy(srpm_out_path, cache_dir)
99 def build_snapshot_rpm(build):
100 """Build latest snapshot RPMs fetching information from URL.
102 :param build: Description of an RPM build, from parent_dir URL
106 parent_dir = "https://nexus.opendaylight.org/content/repositories/" \
107 "opendaylight.snapshot/org/opendaylight/integration/"\
108 "distribution-karaf/"
110 # If the minor verison is given, get the sub-directory directly
111 # else, find the latest sub-directory
114 if build['version_minor']:
115 sub_dir = '0.' + build['version_major'] + '.' + \
116 build['version_minor'] + '-SNAPSHOT/'
117 snapshot_dir = parent_dir + sub_dir
119 subdir_url = urlopen(parent_dir)
120 content = subdir_url.read().decode('utf-8')
121 all_dirs = BeautifulSoup(content, 'html.parser')
123 # Loops through all the sub-directories present and stores the
124 # latest sub directory as sub-directories are already sorted
125 # in early to late order.
126 for tag in all_dirs.find_all('a', href=True):
127 # Checks if the sub-directory name is of the form
128 # '0.<major_version>.<minor_version>-SNAPSHOT'.
129 dir = re.search(r'\/(\d)\.(\d)\.(\d).(.*)\/', tag['href'])
130 # If the major version matches the argument provided
131 # store the minor version, else ignore.
133 if dir.group(2) == build['version_major']:
134 snapshot_dir = tag['href']
135 build['version_minor'] = dir.group(3)
138 req = requests.get(snapshot_dir)
139 req.raise_for_status()
141 print "Could not find the snapshot directory"
143 urlpath = urlopen(snapshot_dir)
144 content = urlpath.read().decode('utf-8')
145 html_content = BeautifulSoup(content, 'html.parser')
146 # Loops through all the files present in `snapshot_dir`
147 # and stores the url of latest tarball because files are
148 # already sorted in early to late order.
149 for tag in html_content.find_all('a', href=True):
150 if tag['href'].endswith('tar.gz'):
151 snapshot_url = tag['href']
154 build['download_url'] = snapshot_url
156 # Get changelog_date from the snapshot URL
157 # eg: 'distribution-karaf-0.5.2-20161202.230609-363.tar.gz'
158 # '\d{8}' searches for the date in the url
159 extract_date = re.search(r'\d{8}', snapshot_url)
160 extract_date = extract_date.group(0)
161 year = int(extract_date[:4])
162 month = int(extract_date[4:6])
163 date = int(extract_date[6:])
165 # %a: Abbreviated weekday name
166 # %b: Abbreviated month name
167 # %d: Zero padded decimal number
169 # `changelog_date` is in the format: 'Sat Dec 10 2016'
171 # https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
172 build['changelog_date'] = datetime.date(year,
174 date).strftime("%a %b %d %Y")
177 build['codename'] = "SNAPSHOT"
183 # When run as a script, accept a set of builds and execute them
184 if __name__ == "__main__":
185 # Load RPM build variables from a YAML config file
186 build_vars_path = os.path.join(project_root, "build_vars.yaml")
187 with open(build_vars_path) as rpm_var_file:
188 build_vars = yaml.load(rpm_var_file)
190 # Accept the version(s) of the build(s) to perform as args
191 # TODO: More docs on ArgParser and argument
192 parser = argparse.ArgumentParser(conflict_handler='resolve')
193 existing_build_group = parser.add_argument_group("Existing build")
194 existing_build_group.add_argument(
195 "-v", "--version", action="append", metavar="major minor patch rpm",
196 nargs="*", help="RPM version(s) to build"
198 new_build_group = parser.add_argument_group("New build")
199 new_build_group.add_argument(
200 "--major", help="Major (element) version to build")
201 new_build_group.add_argument("--minor", help="Minor (SR) version to build")
202 new_build_group.add_argument("--patch", help="Patch version to build")
203 new_build_group.add_argument("--rpm", help="RPM version to build")
204 new_build_group.add_argument(
205 "--sysd_commit", help="Version of ODL unitfile to package")
206 new_build_group.add_argument("--codename", help="Codename for ODL version")
207 new_build_group.add_argument(
208 "--download_url", help="Tarball to repackage into RPM")
209 new_build_group.add_argument(
210 "--changelog_date", help="Date this RPM was defined")
211 new_build_group.add_argument(
212 "--changelog_name", help="Name of person who defined RPM")
213 new_build_group.add_argument(
214 "--changelog_email", help="Email of person who defined RPM")
216 # Arguments needed to build RPM from latest snapshot
217 # given a stable major branch
218 latest_snap_group = parser.add_argument_group("Latest snapshot build")
219 latest_snap_group.add_argument("--build-latest-snap", action='store_true',
220 help="Build RPM from the latest snpashot")
221 latest_snap_group.add_argument("--major", required='true',
222 help="Stable branch from which "
223 "to build the snapshot")
224 latest_snap_group.add_argument("--minor", help="Minor version of the "
225 "stable branch to build the snapshot")
226 latest_snap_group.add_argument("--patch", help="Patch version to build")
227 latest_snap_group.add_argument("--rpm", help="RPM version to build")
228 latest_snap_group.add_argument("--sysd_commit",
229 help="Version of ODL unitfile to package")
230 latest_snap_group.add_argument("--codename",
231 help="Codename for ODL snapshot")
232 latest_snap_group.add_argument("--changelog_name",
233 help="Name of person who defined RPM")
234 latest_snap_group.add_argument("--changelog_email",
235 help="Email of person who defined RPM")
236 # Print help if no arguments are given
237 if len(sys.argv) == 1:
241 # Parse the given args
242 args = parser.parse_args()
244 # Build list of RPM builds to perform
247 # Build a list of requested versions as dicts of version components
249 version_keys = ["version_major", "version_minor", "version_patch",
251 # For each version arg, match all version components to build_vars name
252 for version in args.version:
253 versions.append(dict(zip(version_keys, version)))
255 # Find every RPM build that matches any version argument
256 # A passed version "matches" a build when the provided version
257 # components are a subset of the version components of a build. Any
258 # version components that aren't passed are simply not checked, so
259 # they can't fail the match, effectively wild-carding them.
260 for build in build_vars["builds"]:
261 for version in versions:
262 # Converts both dicts' key:value pairs to lists of tuples and
263 # checks that each tuple in the version list is present in the
265 if all(item in build.items() for item in version.items()):
268 builds.append({"version_major": args.major,
269 "version_minor": args.minor,
270 "version_patch": args.patch,
271 "rpm_release": args.rpm,
272 "sysd_commit": args.sysd_commit,
273 "codename": args.codename,
274 "download_url": args.download_url,
275 "changelog_date": args.changelog_date,
276 "changelog_name": args.changelog_name,
277 "changelog_email": args.changelog_email})
279 # If the flag `--build-latest-snap` is true, extract information
280 # from the snapshot URL, else directly build the RPM
282 if args.build_latest_snap:
283 build_snapshot_rpm(build)