Tony Hansen | bb38402 | 2017-09-19 18:49:04 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # -*- indent-tabs-mode: nil -*- vi: set expandtab: |
| 3 | |
| 4 | import sys, os, argparse, time, re, posix, atexit, binascii |
| 5 | from pathlib import Path |
| 6 | |
| 7 | yamlOk = True |
| 8 | try: |
| 9 | import yaml |
| 10 | except: |
| 11 | yamlOk = False |
| 12 | jsonOk = True |
| 13 | try: |
| 14 | import simplejson as json |
| 15 | except: |
| 16 | try: |
| 17 | import json |
| 18 | except: |
| 19 | jsonOk = False |
| 20 | |
| 21 | def date(): |
| 22 | """ return a datestamp """ |
| 23 | return time.strftime("%Y-%m-%d %H:%M:%S") |
| 24 | |
| 25 | def infoMsg(msg): |
| 26 | """ generate an informational message to stdout """ |
| 27 | print("%s:INFO:%s" % (date(), msg)) |
| 28 | |
| 29 | def traceMsg(msg): |
| 30 | """ if verbose flag is on, generate an informational message to stdout """ |
| 31 | global args |
| 32 | if args.verbose: |
| 33 | infoMsg(msg) |
| 34 | |
| 35 | def warnMsg(msg): |
| 36 | """ generate a warning message to stdout """ |
| 37 | print("%s:WARNING:%s" % (date(), msg)) |
| 38 | |
| 39 | def die(msg): |
| 40 | """ generate a FATAL message to stdout and exit """ |
| 41 | print("%s:FATAL:%s" % (date(), msg)) |
| 42 | sys.exit(2) |
| 43 | |
| 44 | def displayCwd(): |
| 45 | """ display the working directory """ |
| 46 | infoMsg("working directory '" + os.getcwd() + "'") |
| 47 | |
| 48 | def cdCheck(dir): |
| 49 | """ cd to a new directory and die if we cannot """ |
| 50 | try: |
| 51 | traceMsg("cd %s" % dir) |
| 52 | os.chdir(dir) |
| 53 | except: |
| 54 | die("Cannot chdir(" + dir + ")") |
| 55 | |
| 56 | def removeDirPath(path, prmsg = True, gone_ok = False): |
| 57 | """ |
| 58 | remove a directory path |
| 59 | prmsg - print a message before proceeding |
| 60 | gone_ok - do not warn if a path does not exist |
| 61 | """ |
| 62 | if prmsg: |
| 63 | infoMsg("Removing path '%s'" % path) |
| 64 | nmlist = None |
| 65 | try: |
| 66 | nmlist = os.listdir(path) |
| 67 | except FileNotFoundError: |
| 68 | if not gone_ok: |
| 69 | warnMsg("path no longer exists: %s" % path) |
| 70 | return |
| 71 | except: |
| 72 | e = sys.exc_info()[0] |
| 73 | warnMsg("removing path (%s) gave this error: %s" % (path, e)) |
| 74 | return |
| 75 | |
| 76 | for nm in nmlist: |
| 77 | if nm != "." and nm != "..": |
| 78 | pathnm = path + "/" + nm |
| 79 | if os.path.isdir(pathnm): |
| 80 | removeDirPath(pathnm, prmsg = False) |
| 81 | else: |
| 82 | # infoMsg(">>>>removing file %s" % pathnm) |
| 83 | try: |
| 84 | os.remove(pathnm) |
| 85 | except: |
| 86 | e = sys.exc_info()[0] |
| 87 | warnMsg("Could not remove file (%s) because of %s" % (pathnm, e)) |
| 88 | |
| 89 | # infoMsg(">>>>removing directory %s" % pathnm) |
| 90 | try: |
| 91 | os.rmdir(path) |
| 92 | except FileNotFoundError: |
| 93 | if not gone_ok: |
| 94 | warnMsg("Could not remove directory (%s) because of FileNotFound" % path) |
| 95 | except: |
| 96 | e = sys.exc_info()[0] |
| 97 | warnMsg("Could not remove directory (%s) because of %s" % (path, e)) |
| 98 | |
| 99 | def verboseOsSystem(cmd): |
| 100 | """ execute a shell command, printing a trace message first """ |
| 101 | traceMsg("About to execute '%s'" % cmd) |
| 102 | os.system(cmd) |
| 103 | |
| 104 | def lndir(fr, to): |
| 105 | """ create a copy of a tree structure, using hard links where possible """ |
| 106 | global args |
| 107 | removeDirPath(to + "/" + fr, prmsg = args.verbose, gone_ok = True) |
| 108 | verboseOsSystem("find '%s' -print0 | cpio -pdml0 '%s'" % ( fr, to )) |
| 109 | |
| 110 | y = None |
| 111 | |
| 112 | def getParam(name, dflt = None): |
| 113 | """ |
| 114 | Retrieve the contents of a parameter file, rooted where specified. |
| 115 | Return None when it does not exist. |
| 116 | """ |
| 117 | global y, args |
| 118 | if y is None: |
| 119 | fname = args.directory + "/" + args.repackageyaml |
| 120 | if args.repackageyaml.endswith(".yaml"): |
| 121 | if not yamlOk: |
| 122 | die("YAML not available on this machine") |
| 123 | else: |
| 124 | with open(fname, "r") as fd: |
| 125 | try: |
| 126 | contents = fd.read() |
| 127 | contents = re.sub("^\t+", " ", contents, flags=re.M) |
| 128 | y = yaml.safe_load(contents) |
| 129 | except: |
| 130 | die("Invalid yaml in '%s'" % fname) |
| 131 | elif args.repackageyaml.endswith(".json"): |
| 132 | if not jsonOk: |
| 133 | die("JSON not available on this machine") |
| 134 | else: |
| 135 | with open(fname, "r") as fd: |
| 136 | try: |
| 137 | contents = fd.read() |
| 138 | y = json.loads(contents) |
| 139 | except: |
| 140 | type, value, traceback = sys.exc_info() |
| 141 | die("Invalid json in '%s': %s" % (fname, value)) |
| 142 | else: |
| 143 | die("%s must end either in .yaml or .json" % repackageyaml) |
| 144 | |
| 145 | e = "y" + name |
| 146 | inp = None |
| 147 | try: |
| 148 | inp = eval(e,{"__builtins__":None},{"y":y}) |
| 149 | except KeyError: |
| 150 | if dflt is not None: |
| 151 | return dflt |
| 152 | if inp is None: |
| 153 | die("The %s must be be set in %s" % (name, args.repackageyaml)) |
| 154 | return inp |
| 155 | |
| 156 | def cleanupTmpRoot(): |
| 157 | """ clean out the tmp directory """ |
| 158 | global TMPROOT |
| 159 | removeDirPath(TMPROOT, prmsg = args.verbose, gone_ok = True) |
| 160 | |
| 161 | def genDebianChangelog(fname): |
| 162 | """ generate a Debian change log, hard-coded to this for now """ |
| 163 | with open(fname, "w") as fd: |
| 164 | fd.write("OpenECOMP 1701 Demo\n") |
| 165 | |
| 166 | def uploadDocker(name,tag): |
| 167 | """ tag & push Docker image to nexus docker registry """ |
| 168 | ns = getParam( '["docker"]["namespace"]' ) |
| 169 | registry = getParam( '["docker"]["registry"]' ) |
| 170 | image = name + ":" + tag |
| 171 | repo = os.environ.get("DOCKERREGISTRY") + "/" + ns + "/" + image |
| 172 | repo = os.environ.get("DOCKERREGISTRY") + "/" + ns + "/" + image |
| 173 | verboseOsSystem("docker tag " + image + " " + repo) |
| 174 | verboseOsSystem("docker push " + repo) |
| 175 | i = 2 |
| 176 | while os.environ.get("DOCKERREGISTRY" + str(i)): |
| 177 | repo = os.environ.get("DOCKERREGISTRY" + str(i)) + "/" + ns + "/" + image |
| 178 | verboseOsSystem("docker tag " + image + " " + repo) |
| 179 | verboseOsSystem("docker push " + repo) |
| 180 | i += 1 |
| 181 | |
| 182 | # The Debian control archive contents can include the following files: |
| 183 | # |
| 184 | # control: A list of dependencies, and other useful information to indentify the package, such as |
| 185 | # a brief description of the package. |
| 186 | # md5sums: contains MD5 checksums of all files in the package in order to detect corrupt or incomplete files. |
| 187 | # preinst, postinst, prerm and postrm are optional scripts that are executed before or after installing, |
| 188 | # updating or removing the package. |
| 189 | # copyright: any needed copyright notice |
| 190 | # changelog: |
| 191 | # conffiles: Lists the files of the package that should be treated as configuration files. |
| 192 | # Configuration files are not overwritten during an update unless specified. |
| 193 | # debian-binary: contains the deb-package version, currently 2.0 |
| 194 | # templates: A file with error descriptions and dialogs during installation |
| 195 | # config: is an optional script that supports the debconf configuration mechanism. |
| 196 | # shlibs: list of shared library dependencies. |
| 197 | |
| 198 | def genDebianControl(fname): |
| 199 | """ generate a Debian control file """ |
| 200 | with open(fname, "w") as fd: |
| 201 | global APPL, VER, BNBR, MAINTAINER |
| 202 | fd.write("Package: %s\n" % APPL) |
| 203 | fd.write("Version: %s-%s\n" % (VER, BNBR)) |
| 204 | fd.write("Section: utils\n") |
| 205 | fd.write("Priority: optional\n") |
| 206 | fd.write("Architecture: all\n") |
| 207 | fd.write("Maintainer: %s\n" % MAINTAINER) |
| 208 | deps = getParam('["debian"]["externalDependencies"]') |
| 209 | depends = "" |
| 210 | sep = " " |
| 211 | if deps: |
| 212 | for dep in deps: |
| 213 | for d, v in dep.items(): |
| 214 | depends += sep + d + " (" + v + ")" |
| 215 | sep = ", " |
| 216 | fd.write("Depends:%s\n" % depends) |
| 217 | fd.write("Conflicts:\n") |
| 218 | fd.write("Replaces:\n") |
| 219 | desc = getParam( '["description"]' ) |
| 220 | desc = re.sub("^[ \t]*$", ".", desc, flags=re.M) |
| 221 | desc = re.sub("^[ \t]*", " ", desc, flags=re.M) |
| 222 | fd.write("Description:%s\n" % desc) |
| 223 | |
| 224 | def genDebianMd5sums(fname): |
| 225 | """ generate an MD5 listing of all of the staged files """ |
| 226 | global ROOTDIR |
| 227 | verboseOsSystem("cd '%s/stage' && find * -type f -exec md5sum -b {} + > %s" % (ROOTDIR, fname)) |
| 228 | |
| 229 | def genCopyright(fname, prefix = ""): |
| 230 | """ generate a copyright statement, with the given prefix on each line """ |
| 231 | with open(fname, "w") as fd: |
| 232 | fd.write(prefix + "Copyright (C) 2016 AT&T Intellectual Property. All rights reserved.\n") |
| 233 | fd.write(prefix + "\n") |
| 234 | fd.write(prefix + "This code is licensed under the Apache License, Version 2.0;\n") |
| 235 | fd.write(prefix + "you may not use this code for any purpose except in compliance\n") |
| 236 | fd.write(prefix + "with the Apache License. You may obtain a copy of the License\n") |
| 237 | fd.write(prefix + "at http://www.att.com/openecomp.html.\n") |
| 238 | |
| 239 | def isExe(fname): |
| 240 | """ check if a path exists and is executable """ |
| 241 | return os.path.exists(fname) and os.access(fname, os.X_OK) |
| 242 | |
| 243 | def isFileExe(fname): |
| 244 | """ check if a path exists as a file and is executable """ |
| 245 | return os.path.isfile(fname) and os.access(fname, os.X_OK) |
| 246 | |
| 247 | def genFileList(path, testFn): |
| 248 | """ generate a list of files, rooted at path, that all pass the given test """ |
| 249 | ret = [] |
| 250 | try: |
| 251 | nmlist = os.listdir(path) |
| 252 | except FileNotFoundError: |
| 253 | return ret |
| 254 | except: |
| 255 | e = sys.exc_info()[0] |
| 256 | warnMsg("error while listing path (%s): %s" % (path, e)) |
| 257 | return ret |
| 258 | |
| 259 | for nm in nmlist: |
| 260 | if nm != "." and nm != "..": |
| 261 | pathnm = path + "/" + nm |
| 262 | if os.path.isdir(pathnm): |
| 263 | more = genFileList(pathnm, testFn) |
| 264 | ret.extend(more) |
| 265 | elif testFn(pathnm): |
| 266 | ret.append(pathnm) |
| 267 | return ret |
| 268 | |
| 269 | |
| 270 | def createDockerTempFiles(L): |
| 271 | """ create the temp file structure needed to create a docker image """ |
| 272 | global args, ROOTDIR |
| 273 | removeDirPath(L, prmsg = args.verbose, gone_ok = True) |
| 274 | os.makedirs(L, exist_ok = True) |
| 275 | |
| 276 | cdCheck(ROOTDIR + "/stage") |
| 277 | copyList = [] |
| 278 | for i in os.listdir(): |
| 279 | if not i.startswith("."): |
| 280 | lndir(i, L) |
| 281 | copyList.append(i) |
| 282 | |
| 283 | posix.link(ROOTDIR + "/Dockerfile", L + "/Dockerfile") |
| 284 | |
| 285 | def genApplVerBnbrSuffix(suffix, whichBuildNumber): |
| 286 | """ Generate a number of constants used in building a package """ |
| 287 | global APPL, VER, BNBR, TIMESTAMP |
| 288 | applVer = APPL + "_" + VER |
| 289 | buildNumber = BNBR if whichBuildNumber == '{buildnumber}' else TIMESTAMP if whichBuildNumber == '{datetime}' else whichBuildNumber |
| 290 | if buildNumber.startswith("{") and buildNumber.endswith("}"): |
| 291 | die("Unrecognized buildnumber macro name found: %s" % buildNumber) |
| 292 | applVerBnbr = applVer + "-" + buildNumber |
| 293 | applVerBnbrSuffix = applVerBnbr + "." + suffix |
| 294 | applVerSuffix = applVer + "." + suffix |
| 295 | outdirApplVerBnbrSuffix = args.outputdirectory + "/" + applVerBnbrSuffix |
| 296 | return applVer, applVerBnbr, applVerBnbrSuffix, applVerSuffix, outdirApplVerBnbrSuffix |
| 297 | |
| 298 | def genApplVerBnbrSuffix2(suffix, buildString): |
| 299 | """ Generate a number of constants used in building a package """ |
| 300 | global APPL, VER, BNBR, TIMESTAMP |
| 301 | buildString = buildString.replace('{buildnumber}',BNBR).replace('{datetime}',TIMESTAMP).replace('{appname}',APPL).replace('{suffix}',suffix).replace('{version}',VER) |
| 302 | if buildString.find("{") != -1 and buildString.find("}") != -1: |
| 303 | die("Unrecognized buildstring macro name found: %s" % buildNumber) |
| 304 | return buildString |
| 305 | |
| 306 | def uploadAll(envName, groupId, outdirApplVerBnbrSuffix, suffix, applVer, applVerSuffix): |
| 307 | """ |
| 308 | Execute the various upload commands for a given package. |
| 309 | Take into account args.multipleuploadversions |
| 310 | """ |
| 311 | if args.allExtendedUploadVersions: |
| 312 | print("================ in extended -X upload ================") |
| 313 | for buildString in args.allExtendedUploadVersions: |
| 314 | print(">>>> buildString=%s" % buildString) |
| 315 | applVerBnbrSuffix = genApplVerBnbrSuffix2(suffix, buildString) |
| 316 | print(">>>> applVerBnbrSuffix=%s" % applVerBnbrSuffix) |
| 317 | verboseOsSystem(os.environ.get(envName).format(outdirApplVerBnbrSuffix, applVerBnbrSuffix, groupId, applVerSuffix, applVer)) |
| 318 | i = 2 |
| 319 | while os.environ.get(envName + str(i)): |
| 320 | verboseOsSystem(os.environ.get(envName + str(i)).format(outdirApplVerBnbrSuffix, applVerBnbrSuffix, groupId, applVerSuffix, applVer)) |
| 321 | i += 1 |
| 322 | else: |
| 323 | print("================ in regular -M upload ================") |
| 324 | for buildNumber in args.allUploadVersions: |
| 325 | ignored1, ignored2, applVerBnbrSuffix, ignored3, ignored4 = genApplVerBnbrSuffix(suffix, buildNumber) |
| 326 | verboseOsSystem(os.environ.get(envName).format(outdirApplVerBnbrSuffix, applVerBnbrSuffix, groupId, applVerSuffix, applVer)) |
| 327 | i = 2 |
| 328 | while os.environ.get(envName + str(i)): |
| 329 | verboseOsSystem(os.environ.get(envName + str(i)).format(outdirApplVerBnbrSuffix, applVerBnbrSuffix, groupId, applVerSuffix, applVer)) |
| 330 | i += 1 |
| 331 | |
| 332 | def buildDebian(): |
| 333 | """ Build a local debian formatted package """ |
| 334 | infoMsg( 'Building a Debian package ...' ) |
| 335 | global args, TMPROOT, ROOTDIR |
| 336 | if args.skipexecution: |
| 337 | return |
| 338 | |
| 339 | suffix = "deb" |
| 340 | applVer, applVerBnbr, applVerBnbrSuffix, applVerSuffix, outdirApplVerBnbrSuffix = genApplVerBnbrSuffix(suffix, '{buildnumber}') |
| 341 | |
| 342 | if args.usecache and os.path.exists(outdirApplVerBnbrSuffix): |
| 343 | infoMsg( "Already built %s" % applVerBnbrSuffix) |
| 344 | |
| 345 | else: |
| 346 | L = TMPROOT + "/debian" |
| 347 | LD = TMPROOT + "/debian/DEBIAN" |
| 348 | removeDirPath(L, prmsg = args.verbose, gone_ok = True) |
| 349 | os.makedirs(LD, exist_ok = True) |
| 350 | |
| 351 | cdCheck(ROOTDIR + "/stage") |
| 352 | for i in os.listdir(): |
| 353 | if not i.startswith("."): |
| 354 | lndir(i, L) |
| 355 | |
| 356 | genCopyright(LD + "/copyright") |
| 357 | genDebianControl(LD + "/control") |
| 358 | genDebianChangelog(LD + "/changelog") |
| 359 | genDebianMd5sums(LD + "/md5sums") |
| 360 | |
| 361 | cdCheck(ROOTDIR) |
| 362 | execUser = getParam('["executionUser"]') |
| 363 | fileUser = getParam('["fileUser"]') |
| 364 | fileGroup = getParam('["fileGroup"]') |
| 365 | isRoot = execUser == "root" |
| 366 | for cname in [ "preinst", "postinst", "prerm", "postrm" ]: |
| 367 | comCname = "common/" + cname |
| 368 | ldName = LD + "/" + cname |
| 369 | if isExe(comCname) or cname == "postinst": |
| 370 | traceMsg("linking %s to %s" % (comCname, ldName)) |
| 371 | if isRoot and isExe(comCname): |
| 372 | posix.link(comCname, ldName) |
| 373 | else: |
| 374 | with open(ldName, "w") as out: |
| 375 | if cname == "postinst" and fileUser != "root": |
| 376 | for nm in os.listdir("stage"): |
| 377 | t = getParam( '["directoryTreeTops"]["/' + nm + '"]', "n/a" ) |
| 378 | if t == "n/a": |
| 379 | t = "/" + nm |
| 380 | print("chown -R '%s:%s' '%s'" % (fileUser, fileGroup, t), file=out) |
| 381 | print("find '%s' -type d -exec chmod 755 {} +" % t, file=out) |
| 382 | print("find '%s' ! -type d -exec chmod 644 {} +" % t, file=out) |
| 383 | # list each executable file separately |
| 384 | for fname in genFileList("stage", isFileExe): |
| 385 | fname = fname[6:] # remove 'stage/' from beginning |
| 386 | print("chmod 755 '/%s'" % fname, file=out) |
| 387 | |
| 388 | if isExe(comCname): |
| 389 | with open(comCname, "r") as inp: |
| 390 | print("gawk '{\n" + |
| 391 | " f = $0\n" + |
| 392 | " for (i = 1; i <= length(f); i+=2) {\n" + |
| 393 | " printf(\"%c\", strtonum(\"0X\" substr(f,i,2)))\n" + |
| 394 | " }\n" + |
| 395 | "}' > /tmp/rep.$$ <<EOF", file=out) |
| 396 | for line in inp: |
| 397 | for c in line: |
| 398 | # print(">>%02x<<" % ord(c)) |
| 399 | print("%02x" % ord(c), file=out, end="") |
| 400 | print("", file=out) |
| 401 | print("EOF\n" + |
| 402 | "chmod a+x /tmp/rep.$$\n" + |
| 403 | "su " + execUser + " -c /tmp/rep.$$\n" + |
| 404 | "rm -f /tmp/rep.$$\n", file=out) |
| 405 | verboseOsSystem("chmod a+x " + ldName) |
| 406 | |
| 407 | elif os.path.exists(comCname): |
| 408 | die(comCname + " must be executable") |
| 409 | |
| 410 | cdCheck(TMPROOT) |
| 411 | |
| 412 | if args.skipbuild: |
| 413 | traceMsg('Skipping final build') |
| 414 | return |
| 415 | |
| 416 | verboseOsSystem(". '%s'; fakeroot -- dpkg-deb --verbose --build '%s'" % (args.environfile, L)) |
| 417 | os.makedirs(args.outputdirectory, exist_ok = True) |
| 418 | os.rename("debian.deb", outdirApplVerBnbrSuffix) |
| 419 | |
| 420 | if not os.path.exists(outdirApplVerBnbrSuffix): |
| 421 | infoMsg( "Unsuccesful in building %s" % applVerBnbrSuffix) |
| 422 | return |
| 423 | |
| 424 | infoMsg( "Successfully built %s" % applVerBnbrSuffix) |
| 425 | |
| 426 | if args.upload: |
| 427 | envName = "REPACKAGEDEBIANUPLOAD" |
| 428 | groupId = getParam('["debian"]["groupId"]', getParam('["groupId"]')) |
| 429 | uploadAll(envName, groupId, outdirApplVerBnbrSuffix, suffix, applVer, applVerSuffix) |
| 430 | |
| 431 | def buildTar(useGzip): |
| 432 | """ Build a local tarball formatted package """ |
| 433 | infoMsg( 'Building a tar package ...' ) |
| 434 | global args, TMPROOT, ROOTDIR |
| 435 | if args.skipexecution: |
| 436 | return |
| 437 | |
| 438 | suffix = "tgz" if useGzip else "tar" |
| 439 | applVer, applVerBnbr, applVerBnbrSuffix, applVerSuffix, outdirApplVerBnbrSuffix = genApplVerBnbrSuffix(suffix, '{buildnumber}') |
| 440 | |
| 441 | if args.usecache and os.path.isfile(outdirApplVerBnbrSuffix): |
| 442 | infoMsg( "Already built %s" % applVerBnbrSuffix) |
| 443 | |
| 444 | else: |
| 445 | L = TMPROOT + "/" + suffix |
| 446 | LD = L + "/" + applVerBnbr |
| 447 | removeDirPath(L, prmsg = args.verbose, gone_ok = True) |
| 448 | os.makedirs(LD, exist_ok = True) |
| 449 | |
| 450 | cdCheck(ROOTDIR + "/stage") |
| 451 | for i in os.listdir(): |
| 452 | if not i.startswith("."): |
| 453 | lndir(i, LD) |
| 454 | |
| 455 | cdCheck(L) |
| 456 | |
| 457 | if args.skipbuild: |
| 458 | traceMsg('Skipping final build') |
| 459 | return |
| 460 | |
| 461 | taropts = "-zc" if useGzip else "-c" |
| 462 | if args.verbose: taropts += "v" |
| 463 | taropts += "f" |
| 464 | verboseOsSystem(". '%s'; fakeroot -- tar %s tar.%s %s" % (args.environfile, taropts, suffix, applVerBnbr)) |
| 465 | os.makedirs(args.outputdirectory, exist_ok = True) |
| 466 | if args.verbose: |
| 467 | print("renaming tar.%s to %s" % (suffix, outdirApplVerBnbrSuffix)) |
| 468 | os.rename("tar.%s" % suffix, outdirApplVerBnbrSuffix) |
| 469 | |
| 470 | if not os.path.exists(outdirApplVerBnbrSuffix): |
| 471 | infoMsg( "Unsuccesful in building %s" % applVerBnbrSuffix) |
| 472 | return |
| 473 | |
| 474 | infoMsg( "Successfully built %s" % applVerBnbrSuffix) |
| 475 | |
| 476 | |
| 477 | if args.upload: |
| 478 | envName = "REPACKAGETGZUPLOAD" if useGzip else "REPACKAGETARUPLOAD" |
| 479 | groupId = getParam('["%s"]["groupId"]' % suffix, getParam('["groupId"]')) |
| 480 | uploadAll(envName, groupId, outdirApplVerBnbrSuffix, suffix, applVer, applVerSuffix) |
| 481 | |
| 482 | def buildDocker(): |
| 483 | """ Build a DOCKER image """ |
| 484 | image = getParam( '["docker"]["image"]', "n/a" ) |
| 485 | if image == "n/a": |
| 486 | global APPL |
| 487 | image = APPL |
| 488 | tag = getParam( '["docker"]["tag"]' ) |
| 489 | |
| 490 | infoMsg( 'Building a (local) docker image ...' ) |
| 491 | global args, TMPROOT |
| 492 | if args.skipexecution: |
| 493 | return |
| 494 | |
| 495 | L = TMPROOT + "/docker" |
| 496 | createDockerTempFiles(L) |
| 497 | |
| 498 | if args.skipbuild: |
| 499 | traceMsg('Skipping final build') |
| 500 | return |
| 501 | |
| 502 | cdCheck(L) |
| 503 | verboseOsSystem(". '%s'; docker build -t '%s:%s' ." % (args.environfile, image, tag)) |
| 504 | |
| 505 | if args.upload: |
| 506 | uploadDocker(image,tag) |
| 507 | |
| 508 | |
| 509 | def strToBool(string): |
| 510 | return True if (type(string) is str and string == "true") else False if (type(string) is str and string == "false") else string |
| 511 | |
| 512 | def main(): |
| 513 | """ the main executable function """ |
| 514 | |
| 515 | # |
| 516 | # deal with the program arguments - |
| 517 | # we build two different types of argument lists based on |
| 518 | # context. jenkins requires positional arguments while linux cmd line |
| 519 | # permits parameterized ones. the jenkins positional argument list is |
| 520 | # smaller |
| 521 | # |
| 522 | parser = argparse.ArgumentParser( |
| 523 | description="Build the specified packages. 'package-type' is one or more of " + |
| 524 | "docker, debian, tar, tgz" + |
| 525 | " (comma-separated), or 'all' to build all of them." |
| 526 | ) |
| 527 | |
| 528 | REPACKAGEYAML = "repackage.yaml" |
| 529 | REPACKAGEJSON = "repackage.json" |
| 530 | if os.environ.get("JENKINS"): |
| 531 | parser.add_argument("packagetype",help= "debian" + |
| 532 | "|docker|tar|tgz" + |
| 533 | "|all") |
| 534 | parser.add_argument("upload",help="upload package to appropriate repository",nargs='?',default="false") |
| 535 | parser.add_argument("directory", type=str, help="where to find the stage directory and %s. Defaults to '.'" % REPACKAGEYAML, default=".",nargs='?') |
| 536 | parser.add_argument("environfile", type=str, help="Optional environment file. Overrides $REPACKAGEENVFILE, defaults to /dev/null", default="/dev/null", nargs='?') |
| 537 | parser.add_argument("outputdirectory", type=str, help="Output directory. Defaults to 'output' under --directory path.", default=None, nargs='?') |
| 538 | parser.add_argument("verbose",help="turn on verbosity",nargs='?',default="true") |
| 539 | parser.add_argument("skipexecution",help="indcate packages and exit ",nargs='?',default="false") |
| 540 | parser.add_argument("skipbuild",help="skip actually bulding the packages",nargs='?',default="false") |
| 541 | parser.add_argument("usecache",help="if debian/tar/tgz artifact already exists use it",nargs='?',default="false") |
| 542 | parser.add_argument("keeptempfiles",help="keep temp files at exit",nargs='?',default="false") |
| 543 | else: |
| 544 | parser.add_argument("-n", "--skipexecution", help="indicate the packages and exit", action="store_true") |
| 545 | parser.add_argument("-c", "--usecache", help="if a debian/tar/tgz artifact already exists use it", action="store_true") |
| 546 | parser.add_argument("-N", "--skipbuild", help="skip actually building the packages", action="store_true") |
| 547 | parser.add_argument("-K", "--keeptempfiles", help="keep temp files at exit", action="store_true") |
| 548 | parser.add_argument("-v", "--verbose", help="turn on verbosity", action="store_true") |
| 549 | parser.add_argument("-b", "--packagetype", type=str, help="""The package-type may be specified multiple times or may use a ','-separated |
| 550 | or space-separated list. 'all' is an alias for all of them. Potential values are debian, docker""" + |
| 551 | ", tar or tgz", required=True) |
| 552 | parser.add_argument("-u", "--upload", action="store_true", help="""Depending on package type -- docker, debian, tar or tgz -- uploads the artifact to a remote repository. |
| 553 | For Docker, uses $DOCKERREGISTRY as the remote repository to push the image. |
| 554 | |
| 555 | For Debian, uses $REPACKAGEDEBIANUPLOAD as the command, with {0} as the local path to the debian image, {1} as the image name with build number, |
| 556 | and optionally {2} as groupId (may be used as part of the directory path), {3} as the image name without the build number, and {4} |
| 557 | as the image name with no build number and no .deb suffix. |
| 558 | For additional uploads, this will also look for $REPACKAGEDEBIANUPLOAD2, $REPACKAGEDEBIANUPLOAD3, etc., and repeat the upload. |
| 559 | |
| 560 | For tar, uses $REPACKAGETARUPLOAD as the command. Everything said about $REPACKAGEDEBIANUPLOAD applies to $REPACKAGETARUPLOAD. |
| 561 | For tgz, uses $REPACKAGETGZUPLOAD as the command. Everything said about $REPACKAGEDEBIANUPLOAD applies to $REPACKAGETGZUPLOAD. |
| 562 | |
| 563 | In addition, if --multipleuploadversions is used, the above will be executed using the list of upload version numbers specified there. |
| 564 | |
| 565 | This is typically used to create multiple versions (using --multipleuploadversions) on multiple remote repositories (using $REPACKAGE*UPLOAD). |
| 566 | """) |
| 567 | # For additional uploads, repackage will also look for $DOCKERREGISTRY2, $DOCKERREGISTRY3, etc. |
| 568 | parser.add_argument("-d", "--directory", type=str, help="where to find the stage directory and %s. Defaults to '.'" % REPACKAGEYAML, default=".") |
| 569 | parser.add_argument("-e", "--environfile", type=str, help="Optional environment file. Overrides $REPACKAGEENVFILE, defaults to /dev/null", default="/dev/null") |
| 570 | parser.add_argument("-o", "--outputdirectory", type=str, help="Output directory. Defaults to 'output' under --directory path.", default=None) |
| 571 | parser.add_argument("-y", "--repackageyaml", type=str, help="Name of parameter file. Defaults to '" + REPACKAGEYAML + "' or '" + REPACKAGEJSON + "' under --directory path.", default=REPACKAGEYAML) |
| 572 | parser.add_argument("-B", "--buildnumber", type=str, help="Build number. Defaults to $BUILD_NUMBER, which defaults to a date-based string.", default="") |
| 573 | parser.add_argument("-D", "--define", type=str, action='append', help="define an argument at runtime in key=value format") |
| 574 | parser.add_argument("-M", "--multipleuploadversions", type=str, help="Use multiple versions for upload. Comma-separated list of {datetime}, {buildnumber} or arbitrary strings. Defaults to {buildnumber}, which is the value from --buildnumber.", default="{buildnumber}") |
| 575 | parser.add_argument("-X", "--extendedmultipleuploadversions", type=str, help="""Use multiple names for upload. |
| 576 | Comma-separated list of arbitrary strings |
| 577 | that can contain any combination of {appname}, {suffix}, {datetime}, {buildnumber} and {version}. |
| 578 | There is no default, but if used, this overrides -M.""", default="") |
| 579 | |
| 580 | global args |
| 581 | args = parser.parse_args() |
| 582 | |
| 583 | # for some reason, the Jenkins branch leaves these as strings instead of the proper boolean values |
| 584 | args.upload = strToBool(args.upload) |
| 585 | args.verbose = strToBool(args.verbose) |
| 586 | args.skipexecution = strToBool(args.skipexecution) |
| 587 | args.skipbuild = strToBool(args.skipbuild) |
| 588 | args.usecache = strToBool(args.usecache) |
| 589 | args.keeptempfiles = strToBool(args.keeptempfiles) |
| 590 | |
| 591 | # arguments defined at runtime as key=value pairs |
| 592 | global rtdef |
| 593 | rtdef = {} |
| 594 | |
| 595 | if args.define: |
| 596 | for k in args.define: |
| 597 | tag, val = k.split("=") |
| 598 | rtdef[tag] = val |
| 599 | |
| 600 | for k, v in rtdef.items(): |
| 601 | traceMsg("runtime defined %s <- %s" % (k,v)) |
| 602 | |
| 603 | # check the -e/$REPACKAGEENVFILE value |
| 604 | if args.environfile == "": |
| 605 | if os.environ.get("REPACKAGEENVFILE") is not None: |
| 606 | args.environfile = os.environ["REPACKAGEENVFILE"] |
| 607 | if not os.path.isfile(args.environfile) and args.environfile != "/dev/null": |
| 608 | die("-e / $REPACKAGEENVFILE must be a file that can be sourced by the shell") |
| 609 | if not args.environfile.startswith("/"): |
| 610 | args.environfile = os.getcwd() + "/" + args.environfile |
| 611 | |
| 612 | allPackages = [ "debian", "tar", "tgz", |
| 613 | "docker" ] |
| 614 | args.builds = { } |
| 615 | for pkg in allPackages: |
| 616 | args.builds[pkg] = False |
| 617 | if args.packagetype == "all": |
| 618 | args.packagetype = ",".join(allPackages) |
| 619 | for build in re.split("[, \t]", args.packagetype): |
| 620 | args.builds[build] = True |
| 621 | |
| 622 | args.allUploadVersions = args.multipleuploadversions.split(",") |
| 623 | args.allExtendedUploadVersions = args.extendedmultipleuploadversions.split(",") if args.extendedmultipleuploadversions != "" else None |
| 624 | |
| 625 | if args.upload and args.builds["debian"]: |
| 626 | if os.environ.get("REPACKAGEDEBIANUPLOAD") is None: |
| 627 | die("-u requires $REPACKAGEDEBIANUPLOAD to be set when building debian") |
| 628 | elif not re.search("[{]0[}]", os.environ.get("REPACKAGEDEBIANUPLOAD")): |
| 629 | die("$REPACKAGEDEBIANUPLOAD is missing {0}") |
| 630 | elif not re.search("[{][13][}]", os.environ.get("REPACKAGEDEBIANUPLOAD")): |
| 631 | die("$REPACKAGEDEBIANUPLOAD is missing either {1}, {3} or {4}") |
| 632 | |
| 633 | if args.upload and args.builds["tar"]: |
| 634 | if os.environ.get("REPACKAGETARUPLOAD") is None: |
| 635 | die("-u requires $REPACKAGETARUPLOAD to be set when building tar") |
| 636 | elif not re.search("[{]0[}]", os.environ.get("REPACKAGETARUPLOAD")): |
| 637 | die("$REPACKAGETARUPLOAD is missing {0}") |
| 638 | elif not re.search("[{][134][}]", os.environ.get("REPACKAGETARUPLOAD")): |
| 639 | die("$REPACKAGETARUPLOAD is missing either {1}, {3} or {4}") |
| 640 | |
| 641 | if args.upload and args.builds["tgz"]: |
| 642 | if os.environ.get("REPACKAGETGZUPLOAD") is None: |
| 643 | die("-u requires $REPACKAGETGZUPLOAD to be set when building tgz") |
| 644 | elif not re.search("[{]0[}]", os.environ.get("REPACKAGETGZUPLOAD")): |
| 645 | die("$REPACKAGETGZUPLOAD is missing {0}") |
| 646 | elif not re.search("[{][134][}]", os.environ.get("REPACKAGETGZUPLOAD")): |
| 647 | die("$REPACKAGETGZUPLOAD is missing either {1}, {3} or {4}") |
| 648 | |
| 649 | if args.upload and args.builds["docker"] and os.environ.get("DOCKERREGISTRY") is None: |
| 650 | die("-u requires $DOCKERREGISTRY to be set when building docker") |
| 651 | |
| 652 | if not os.path.isdir(args.directory): |
| 653 | die("The root directory %s does not exist" % args.directory) |
| 654 | if not args.directory.startswith("/"): |
| 655 | args.directory = os.getcwd() + "/" + args.directory |
| 656 | if args.repackageyaml != REPACKAGEYAML: |
| 657 | if not os.path.exists(args.directory + "/" + args.repackageyaml): |
| 658 | die("The file %s/%s does not exist" % (args.directory, args.repackageyaml)) |
| 659 | else: |
| 660 | if os.path.exists(args.directory + "/" + REPACKAGEYAML): |
| 661 | args.repackageyaml = REPACKAGEYAML |
| 662 | elif os.path.exists(args.directory + "/" + REPACKAGEJSON): |
| 663 | args.repackageyaml = REPACKAGEJSON |
| 664 | else: |
| 665 | die("Either %s/%s or %s/%s must exist" % (args.directory, args.repackageyaml, args.directory, args.repackagejson)) |
| 666 | |
| 667 | if args.outputdirectory is None: |
| 668 | args.outputdirectory = args.directory + "/output" |
| 669 | else: |
| 670 | if not args.outputdirectory.startswith("/"): |
| 671 | args.outputdirectory = os.getcwd() + "/" + args.outputdirectory |
| 672 | if not os.path.isdir(args.outputdirectory): |
| 673 | die("The specified --outputdirectory %s does not exist" % args.outputdirectory) |
| 674 | |
| 675 | # establish some global variables used everywhere |
| 676 | global ROOTDIR, TMPROOT |
| 677 | ROOTDIR = args.directory |
| 678 | TMPROOT = args.directory + "/tmp" |
| 679 | |
| 680 | # and cd to our ROOTDIR |
| 681 | cdCheck(ROOTDIR) |
| 682 | |
| 683 | # unless -K is specified, remove any temp files at the end |
| 684 | if not args.keeptempfiles: |
| 685 | atexit.register(cleanupTmpRoot) |
| 686 | |
| 687 | # grab and share some variables that are used by lots of build functions |
| 688 | global APPL, MAINTAINER, VER, BNBR, TIMESTAMP |
| 689 | APPL = getParam( '["applicationName"]' ) |
| 690 | MAINTAINER = getParam( '["maintainer"]' ) |
| 691 | VER = getParam( '["version"]' ) |
| 692 | TIMESTAMP = time.strftime("%Y%m%d%H%M%S") |
| 693 | BNBR = args.buildnumber if args.buildnumber != "" else os.environ.get("BUILD_NUMBER") if os.environ.get("BUILD_NUMBER") is not None else TIMESTAMP |
| 694 | |
| 695 | # build whatever was requested |
| 696 | if args.builds["docker"]: |
| 697 | buildDocker() |
| 698 | if args.builds["debian"]: |
| 699 | buildDebian() |
| 700 | if args.builds["tar"]: |
| 701 | buildTar(False) |
| 702 | if args.builds["tgz"]: |
| 703 | buildTar(True) |
| 704 | |
| 705 | if __name__ == "__main__": |
| 706 | main() |