python utility to manage a tripleo based cloud
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

container_image.py 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. # Copyright 2015 Red Hat, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. #
  15. import copy
  16. import datetime
  17. import json
  18. import logging
  19. import os
  20. import shutil
  21. import sys
  22. import tempfile
  23. import time
  24. from osc_lib import exceptions as oscexc
  25. from osc_lib.i18n import _
  26. import six
  27. import yaml
  28. from tripleo_common.image.builder import buildah
  29. from tripleo_common.image import image_uploader
  30. from tripleo_common.image import kolla_builder
  31. from tripleoclient import command
  32. from tripleoclient import constants
  33. from tripleoclient import exceptions
  34. from tripleoclient import utils
  35. def build_env_file(params, command_options):
  36. f = six.StringIO()
  37. f.write('# Generated with the following on %s\n#\n' %
  38. datetime.datetime.now().isoformat())
  39. f.write('# openstack %s\n#\n\n' %
  40. ' '.join(command_options))
  41. yaml.safe_dump({'parameter_defaults': params}, f,
  42. default_flow_style=False)
  43. return f.getvalue()
  44. class UploadImage(command.Command):
  45. """Push overcloud container images to registries."""
  46. auth_required = False
  47. log = logging.getLogger(__name__ + ".UploadImage")
  48. def get_parser(self, prog_name):
  49. parser = super(UploadImage, self).get_parser(prog_name)
  50. parser.add_argument(
  51. "--config-file",
  52. dest="config_files",
  53. metavar='<yaml config file>',
  54. default=[],
  55. action="append",
  56. required=True,
  57. help=_("YAML config file specifying the image build. May be "
  58. "specified multiple times. Order is preserved, and later "
  59. "files will override some options in previous files. "
  60. "Other options will append."),
  61. )
  62. parser.add_argument(
  63. "--cleanup",
  64. dest="cleanup",
  65. metavar='<full, partial, none>',
  66. default=image_uploader.CLEANUP_FULL,
  67. help=_("Cleanup behavior for local images left after upload. "
  68. "The default 'full' will attempt to delete all local "
  69. "images. 'partial' will leave images required for "
  70. "deployment on this host. 'none' will do no cleanup.")
  71. )
  72. return parser
  73. def take_action(self, parsed_args):
  74. self.log.debug("take_action(%s)" % parsed_args)
  75. if parsed_args.cleanup not in image_uploader.CLEANUP:
  76. raise oscexc.CommandError('--cleanup must be one of: %s' %
  77. ', '.join(image_uploader.CLEANUP))
  78. uploader = image_uploader.ImageUploadManager(
  79. parsed_args.config_files, cleanup=parsed_args.cleanup)
  80. try:
  81. uploader.upload()
  82. except KeyboardInterrupt: # ctrl-c
  83. self.log.warning('Upload was interrupted by ctrl-c.')
  84. class BuildImage(command.Command):
  85. """Build overcloud container images with kolla-build."""
  86. auth_required = False
  87. log = logging.getLogger(__name__ + ".BuildImage")
  88. @staticmethod
  89. def images_from_deps(images, dep):
  90. '''Builds a list from the dependencies depth-first. '''
  91. if isinstance(dep, list):
  92. for v in dep:
  93. BuildImage.images_from_deps(images, v)
  94. elif isinstance(dep, dict):
  95. for k, v in dep.items():
  96. images.append(k)
  97. BuildImage.images_from_deps(images, v)
  98. else:
  99. images.append(dep)
  100. def get_parser(self, prog_name):
  101. default_kolla_conf = os.path.join(
  102. sys.prefix, 'share', 'tripleo-common', 'container-images',
  103. 'tripleo_kolla_config_overrides.conf')
  104. parser = super(BuildImage, self).get_parser(prog_name)
  105. parser.add_argument(
  106. "--config-file",
  107. dest="config_files",
  108. metavar='<yaml config file>',
  109. default=[],
  110. action="append",
  111. help=_("YAML config file specifying the images to build. May be "
  112. "specified multiple times. Order is preserved, and later "
  113. "files will override some options in previous files. "
  114. "Other options will append. If not specified, the default "
  115. "set of containers will be built."),
  116. )
  117. parser.add_argument(
  118. "--kolla-config-file",
  119. dest="kolla_config_files",
  120. metavar='<config file>',
  121. default=[default_kolla_conf],
  122. action="append",
  123. required=True,
  124. help=_("Path to a Kolla config file to use. Multiple config files "
  125. "can be specified, with values in later files taking "
  126. "precedence. By default, tripleo kolla conf file {conf} "
  127. "is added.").format(conf=default_kolla_conf),
  128. )
  129. parser.add_argument(
  130. '--list-images',
  131. dest='list_images',
  132. action='store_true',
  133. default=False,
  134. help=_('Show the images which would be built instead of '
  135. 'building them.')
  136. )
  137. parser.add_argument(
  138. '--list-dependencies',
  139. dest='list_dependencies',
  140. action='store_true',
  141. default=False,
  142. help=_('Show the image build dependencies instead of '
  143. 'building them.')
  144. )
  145. parser.add_argument(
  146. "--exclude",
  147. dest="excludes",
  148. metavar='<container-name>',
  149. default=[],
  150. action="append",
  151. help=_("Name of a container to match against the list of "
  152. "containers to be built to skip. Can be specified multiple "
  153. "times."),
  154. )
  155. parser.add_argument(
  156. '--use-buildah',
  157. dest='use_buildah',
  158. action='store_true',
  159. default=False,
  160. help=_('Use Buildah instead of Docker to build the images '
  161. 'with Kolla.')
  162. )
  163. return parser
  164. def take_action(self, parsed_args):
  165. self.log.debug("take_action(%s)" % parsed_args)
  166. fd, path = tempfile.mkstemp(prefix='kolla_conf_')
  167. with os.fdopen(fd, 'w') as tmp:
  168. tmp.write('[DEFAULT]\n')
  169. if parsed_args.list_images or parsed_args.list_dependencies:
  170. tmp.write('list_dependencies=true')
  171. kolla_config_files = list(parsed_args.kolla_config_files)
  172. kolla_config_files.append(path)
  173. kolla_tmp_dir = None
  174. if parsed_args.use_buildah:
  175. # A temporary directory is needed to let Kolla generates the
  176. # Dockerfiles that will be used by Buildah to build the images.
  177. kolla_tmp_dir = tempfile.mkdtemp(prefix='kolla-')
  178. try:
  179. builder = kolla_builder.KollaImageBuilder(parsed_args.config_files)
  180. result = builder.build_images(kolla_config_files,
  181. parsed_args.excludes,
  182. parsed_args.use_buildah,
  183. kolla_tmp_dir)
  184. if parsed_args.use_buildah:
  185. deps = json.loads(result)
  186. kolla_cfg = utils.get_read_config(kolla_config_files)
  187. bb = buildah.BuildahBuilder(
  188. kolla_tmp_dir, deps,
  189. utils.get_from_cfg(kolla_cfg, "base"),
  190. utils.get_from_cfg(kolla_cfg, "type"),
  191. utils.get_from_cfg(kolla_cfg, "tag"),
  192. utils.get_from_cfg(kolla_cfg, "namespace"),
  193. utils.get_from_cfg(kolla_cfg, "registry"))
  194. bb.build_all()
  195. elif parsed_args.list_dependencies:
  196. deps = json.loads(result)
  197. yaml.safe_dump(deps, self.app.stdout, indent=2,
  198. default_flow_style=False)
  199. elif parsed_args.list_images:
  200. deps = json.loads(result)
  201. images = []
  202. BuildImage.images_from_deps(images, deps)
  203. yaml.safe_dump(images, self.app.stdout,
  204. default_flow_style=False)
  205. elif result:
  206. self.app.stdout.write(result)
  207. finally:
  208. os.remove(path)
  209. class PrepareImageFiles(command.Command):
  210. """Generate files defining the images, tags and registry."""
  211. auth_required = False
  212. log = logging.getLogger(__name__ + ".PrepareImageFiles")
  213. def get_parser(self, prog_name):
  214. parser = super(PrepareImageFiles, self).get_parser(prog_name)
  215. try:
  216. roles_file = utils.rel_or_abs_path(
  217. constants.OVERCLOUD_ROLES_FILE,
  218. constants.TRIPLEO_HEAT_TEMPLATES)
  219. except exceptions.DeploymentError:
  220. roles_file = None
  221. defaults = kolla_builder.container_images_prepare_defaults()
  222. parser.add_argument(
  223. "--template-file",
  224. dest="template_file",
  225. default=kolla_builder.DEFAULT_TEMPLATE_FILE,
  226. metavar='<yaml template file>',
  227. help=_("YAML template file which the images config file will be "
  228. "built from.\n"
  229. "Default: %s") % kolla_builder.DEFAULT_TEMPLATE_FILE,
  230. )
  231. parser.add_argument(
  232. "--pull-source",
  233. dest="pull_source",
  234. metavar='<location>',
  235. help=_("Location of image registry to pull images from. "
  236. "(DEPRECATED. Include the registry in --namespace)"),
  237. )
  238. parser.add_argument(
  239. "--push-destination",
  240. dest="push_destination",
  241. metavar='<location>',
  242. help=_("Location of image registry to push images to. "
  243. "If specified, a push_destination will be set for every "
  244. "image entry."),
  245. )
  246. parser.add_argument(
  247. "--tag",
  248. dest="tag",
  249. default=defaults['tag'],
  250. metavar='<tag>',
  251. help=_("Override the default tag substitution. "
  252. "If --tag-from-label is specified, "
  253. "start discovery with this tag.\n"
  254. "Default: %s") % defaults['tag'],
  255. )
  256. parser.add_argument(
  257. "--tag-from-label",
  258. dest="tag_from_label",
  259. metavar='<image label>',
  260. help=_("Use the value of the specified label(s) to discover the "
  261. "tag. Labels can be combined in a template format, "
  262. "for example: {version}-{release}"),
  263. )
  264. parser.add_argument(
  265. "--namespace",
  266. dest="namespace",
  267. default=defaults['namespace'],
  268. metavar='<namespace>',
  269. help=_("Override the default namespace substitution.\n"
  270. "Default: %s") % defaults['namespace'],
  271. )
  272. parser.add_argument(
  273. "--prefix",
  274. dest="prefix",
  275. default=defaults['name_prefix'],
  276. metavar='<prefix>',
  277. help=_("Override the default name prefix substitution.\n"
  278. "Default: %s") % defaults['name_prefix'],
  279. )
  280. parser.add_argument(
  281. "--suffix",
  282. dest="suffix",
  283. default=defaults['name_suffix'],
  284. metavar='<suffix>',
  285. help=_("Override the default name suffix substitution.\n"
  286. "Default: %s") % defaults['name_suffix'],
  287. )
  288. parser.add_argument(
  289. '--set',
  290. metavar='<variable=value>',
  291. action='append',
  292. help=_('Set the value of a variable in the template, even if it '
  293. 'has no dedicated argument such as "--suffix".')
  294. )
  295. parser.add_argument(
  296. "--exclude",
  297. dest="excludes",
  298. metavar='<regex>',
  299. default=[],
  300. action="append",
  301. help=_("Pattern to match against resulting imagename entries to "
  302. "exclude from the final output. Can be specified multiple "
  303. "times."),
  304. )
  305. parser.add_argument(
  306. "--include",
  307. dest="includes",
  308. metavar='<regex>',
  309. default=[],
  310. action="append",
  311. help=_("Pattern to match against resulting imagename entries to "
  312. "include in final output. Can be specified multiple "
  313. "times, entries not matching any --include will be "
  314. "excluded. --exclude is ignored if --include is used."),
  315. )
  316. parser.add_argument(
  317. "--images-file",
  318. dest="output_images_file",
  319. metavar='<file path>',
  320. help=_("File to write resulting image entries to, as well as "
  321. "stdout. Any existing file will be overwritten."
  322. "(DEPRECATED. Use --output-images-file instead)"),
  323. )
  324. parser.add_argument(
  325. "--output-images-file",
  326. dest="output_images_file",
  327. metavar='<file path>',
  328. help=_("File to write resulting image entries to, as well as "
  329. "stdout. Any existing file will be overwritten."),
  330. )
  331. parser.add_argument(
  332. '--service-environment-file', metavar='<file path>',
  333. action='append', dest='environment_files',
  334. help=_('Environment files specifying which services are '
  335. 'containerized. Entries will be filtered to only contain '
  336. 'images used by containerized services. (Can be specified '
  337. 'more than once.)'
  338. "(DEPRECATED. Use --environment-file instead)"),
  339. )
  340. parser.add_argument(
  341. '--environment-file', '-e', metavar='<file path>',
  342. action='append', dest='environment_files',
  343. help=_('Environment files specifying which services are '
  344. 'containerized. Entries will be filtered to only contain '
  345. 'images used by containerized services. (Can be specified '
  346. 'more than once.)')
  347. )
  348. parser.add_argument(
  349. '--environment-directory', metavar='<HEAT ENVIRONMENT DIRECTORY>',
  350. action='append', dest='environment_directories',
  351. default=[os.path.expanduser(constants.DEFAULT_ENV_DIRECTORY)],
  352. help=_('Environment file directories that are automatically '
  353. 'added to the update command. Entries will be filtered '
  354. 'to only contain images used by containerized services. '
  355. 'Can be specified more than once. Files in directories are '
  356. 'loaded in ascending sort order.')
  357. )
  358. parser.add_argument(
  359. "--env-file",
  360. dest="output_env_file",
  361. metavar='<file path>',
  362. help=_("File to write heat environment file which specifies all "
  363. "image parameters. Any existing file will be overwritten."
  364. "(DEPRECATED. Use --output-env-file instead)"),
  365. )
  366. parser.add_argument(
  367. "--output-env-file",
  368. dest="output_env_file",
  369. metavar='<file path>',
  370. help=_("File to write heat environment file which specifies all "
  371. "image parameters. Any existing file will be overwritten."),
  372. )
  373. parser.add_argument(
  374. '--roles-file', '-r', dest='roles_file',
  375. default=roles_file,
  376. help=_(
  377. 'Roles file, overrides the default %s in the t-h-t templates '
  378. 'directory used for deployment. May be an '
  379. 'absolute path or the path relative to the templates dir.'
  380. ) % constants.OVERCLOUD_ROLES_FILE
  381. )
  382. parser.add_argument(
  383. '--modify-role',
  384. dest='modify_role',
  385. help=_('Name of ansible role to run between every image upload '
  386. 'pull and push.')
  387. )
  388. parser.add_argument(
  389. '--modify-vars',
  390. dest='modify_vars',
  391. help=_('Ansible variable file containing variables to use when '
  392. 'invoking the role --modify-role.')
  393. )
  394. return parser
  395. def parse_set_values(self, subs, set_values):
  396. if not set_values:
  397. return
  398. for s in set_values:
  399. try:
  400. (n, v) = s.split(('='), 1)
  401. subs[n] = v
  402. except ValueError:
  403. msg = _('Malformed --set(%s). '
  404. 'Use the variable=value format.') % s
  405. raise oscexc.CommandError(msg)
  406. def take_action(self, parsed_args):
  407. self.log.debug("take_action(%s)" % parsed_args)
  408. roles_data = utils.fetch_roles_file(parsed_args.roles_file) or set()
  409. env = utils.build_prepare_env(
  410. parsed_args.environment_files,
  411. parsed_args.environment_directories
  412. )
  413. if roles_data:
  414. service_filter = kolla_builder.build_service_filter(
  415. env, roles_data)
  416. else:
  417. service_filter = None
  418. mapping_args = {
  419. 'tag': parsed_args.tag,
  420. 'namespace': parsed_args.namespace,
  421. 'name_prefix': parsed_args.prefix,
  422. 'name_suffix': parsed_args.suffix,
  423. }
  424. self.parse_set_values(mapping_args, parsed_args.set)
  425. pd = env.get('parameter_defaults', {})
  426. kolla_builder.set_neutron_driver(pd, mapping_args)
  427. output_images_file = (parsed_args.output_images_file
  428. or 'container_images.yaml')
  429. modify_role = None
  430. modify_vars = None
  431. append_tag = None
  432. if parsed_args.modify_role:
  433. modify_role = parsed_args.modify_role
  434. append_tag = time.strftime('-modified-%Y%m%d%H%M%S')
  435. if parsed_args.modify_vars:
  436. modify_vars = yaml.safe_load(open(parsed_args.modify_vars).read())
  437. prepare_data = kolla_builder.container_images_prepare(
  438. excludes=parsed_args.excludes,
  439. includes=parsed_args.includes,
  440. service_filter=service_filter,
  441. pull_source=parsed_args.pull_source,
  442. push_destination=parsed_args.push_destination,
  443. mapping_args=mapping_args,
  444. output_env_file=parsed_args.output_env_file,
  445. output_images_file=output_images_file,
  446. tag_from_label=parsed_args.tag_from_label,
  447. modify_role=modify_role,
  448. modify_vars=modify_vars,
  449. append_tag=append_tag,
  450. template_file=parsed_args.template_file
  451. )
  452. if parsed_args.output_env_file:
  453. params = prepare_data[parsed_args.output_env_file]
  454. if os.path.exists(parsed_args.output_env_file):
  455. self.log.warn("Output env file exists, moving it to backup.")
  456. shutil.move(parsed_args.output_env_file,
  457. parsed_args.output_env_file + ".backup")
  458. with os.fdopen(os.open(parsed_args.output_env_file,
  459. os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o666),
  460. 'w') as f:
  461. f.write(build_env_file(params, self.app.command_options))
  462. result = prepare_data[output_images_file]
  463. result_str = yaml.safe_dump({'container_images': result},
  464. default_flow_style=False)
  465. sys.stdout.write(result_str)
  466. if parsed_args.output_images_file:
  467. with os.fdopen(os.open(parsed_args.output_images_file,
  468. os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o666),
  469. 'w') as f:
  470. f.write(result_str)
  471. class DiscoverImageTag(command.Command):
  472. """Discover the versioned tag for an image."""
  473. auth_required = False
  474. log = logging.getLogger(__name__ + ".DiscoverImageTag")
  475. def get_parser(self, prog_name):
  476. parser = super(DiscoverImageTag, self).get_parser(prog_name)
  477. parser.add_argument(
  478. "--image",
  479. dest="image",
  480. metavar='<container image>',
  481. required=True,
  482. help=_("Fully qualified name of the image to discover the tag for "
  483. "(Including registry and stable tag)."),
  484. )
  485. parser.add_argument(
  486. "--tag-from-label",
  487. dest="tag_from_label",
  488. metavar='<image label>',
  489. help=_("Use the value of the specified label(s) to discover the "
  490. "tag. Labels can be combined in a template format, "
  491. "for example: {version}-{release}"),
  492. )
  493. return parser
  494. def take_action(self, parsed_args):
  495. self.log.debug("take_action(%s)" % parsed_args)
  496. uploader = image_uploader.ImageUploadManager([])
  497. print(uploader.discover_image_tag(
  498. image=parsed_args.image,
  499. tag_from_label=parsed_args.tag_from_label
  500. ))
  501. class TripleOImagePrepareDefault(command.Command):
  502. """Generate a default ContainerImagePrepare parameter."""
  503. auth_required = False
  504. log = logging.getLogger(__name__ + ".TripleoImagePrepare")
  505. def get_parser(self, prog_name):
  506. parser = super(TripleOImagePrepareDefault, self).get_parser(prog_name)
  507. parser.add_argument(
  508. "--output-env-file",
  509. dest="output_env_file",
  510. metavar='<file path>',
  511. help=_("File to write environment file containing default "
  512. "ContainerImagePrepare value."),
  513. )
  514. parser.add_argument(
  515. '--local-push-destination',
  516. dest='push_destination',
  517. action='store_true',
  518. default=False,
  519. help=_('Include a push_destination to trigger upload to a local '
  520. 'registry.')
  521. )
  522. return parser
  523. def take_action(self, parsed_args):
  524. self.log.debug("take_action(%s)" % parsed_args)
  525. cip = copy.deepcopy(kolla_builder.CONTAINER_IMAGE_PREPARE_PARAM)
  526. if parsed_args.push_destination:
  527. for entry in cip:
  528. entry['push_destination'] = True
  529. params = {
  530. 'ContainerImagePrepare': cip
  531. }
  532. env_data = build_env_file(params, self.app.command_options)
  533. self.app.stdout.write(env_data)
  534. if parsed_args.output_env_file:
  535. if os.path.exists(parsed_args.output_env_file):
  536. self.log.warn("Output env file exists, moving it to backup.")
  537. shutil.move(parsed_args.output_env_file,
  538. parsed_args.output_env_file + ".backup")
  539. with os.fdopen(os.open(parsed_args.output_env_file,
  540. os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o666),
  541. 'w') as f:
  542. f.write(build_env_file(params, self.app.command_options))
  543. class TripleOImagePrepare(command.Command):
  544. """Prepare and upload containers from a single command."""
  545. auth_required = False
  546. log = logging.getLogger(__name__ + ".TripleoImagePrepare")
  547. def get_parser(self, prog_name):
  548. parser = super(TripleOImagePrepare, self).get_parser(prog_name)
  549. try:
  550. roles_file = utils.rel_or_abs_path(
  551. constants.OVERCLOUD_ROLES_FILE,
  552. constants.TRIPLEO_HEAT_TEMPLATES)
  553. except exceptions.DeploymentError:
  554. roles_file = None
  555. parser.add_argument(
  556. '--environment-file', '-e', metavar='<file path>',
  557. action='append', dest='environment_files',
  558. help=_('Environment file containing the ContainerImagePrepare '
  559. 'parameter which specifies all prepare actions. '
  560. 'Also, environment files specifying which services are '
  561. 'containerized. Entries will be filtered to only contain '
  562. 'images used by containerized services. (Can be specified '
  563. 'more than once.)')
  564. )
  565. parser.add_argument(
  566. '--environment-directory', metavar='<HEAT ENVIRONMENT DIRECTORY>',
  567. action='append', dest='environment_directories',
  568. default=[os.path.expanduser(constants.DEFAULT_ENV_DIRECTORY)],
  569. help=_('Environment file directories that are automatically '
  570. 'added to the environment. '
  571. 'Can be specified more than once. Files in directories are '
  572. 'loaded in ascending sort order.')
  573. )
  574. parser.add_argument(
  575. '--roles-file', '-r', dest='roles_file',
  576. default=roles_file,
  577. help=_(
  578. 'Roles file, overrides the default %s in the t-h-t templates '
  579. 'directory used for deployment. May be an '
  580. 'absolute path or the path relative to the templates dir.'
  581. ) % constants.OVERCLOUD_ROLES_FILE
  582. )
  583. parser.add_argument(
  584. "--output-env-file",
  585. dest="output_env_file",
  586. metavar='<file path>',
  587. help=_("File to write heat environment file which specifies all "
  588. "image parameters. Any existing file will be overwritten."),
  589. )
  590. parser.add_argument(
  591. '--dry-run',
  592. dest='dry_run',
  593. action='store_true',
  594. default=False,
  595. help=_('Do not perform any pull, modify, or push operations. '
  596. 'The environment file will still be populated as if these '
  597. 'operations were performed.')
  598. )
  599. parser.add_argument(
  600. "--cleanup",
  601. dest="cleanup",
  602. metavar='<full, partial, none>',
  603. default=image_uploader.CLEANUP_FULL,
  604. help=_("Cleanup behavior for local images left after upload. "
  605. "The default 'full' will attempt to delete all local "
  606. "images. 'partial' will leave images required for "
  607. "deployment on this host. 'none' will do no cleanup.")
  608. )
  609. return parser
  610. def take_action(self, parsed_args):
  611. self.log.debug("take_action(%s)" % parsed_args)
  612. if parsed_args.cleanup not in image_uploader.CLEANUP:
  613. raise oscexc.CommandError('--cleanup must be one of: %s' %
  614. ', '.join(image_uploader.CLEANUP))
  615. roles_data = utils.fetch_roles_file(parsed_args.roles_file)
  616. env = utils.build_prepare_env(
  617. parsed_args.environment_files,
  618. parsed_args.environment_directories
  619. )
  620. params = kolla_builder.container_images_prepare_multi(
  621. env, roles_data, dry_run=parsed_args.dry_run,
  622. cleanup=parsed_args.cleanup)
  623. env_data = build_env_file(params, self.app.command_options)
  624. if parsed_args.output_env_file:
  625. if os.path.exists(parsed_args.output_env_file):
  626. self.log.warn("Output env file exists, moving it to backup.")
  627. shutil.move(parsed_args.output_env_file,
  628. parsed_args.output_env_file + ".backup")
  629. with os.fdopen(os.open(parsed_args.output_env_file,
  630. os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o666),
  631. 'w') as f:
  632. f.write(build_env_file(params, self.app.command_options))
  633. else:
  634. self.app.stdout.write(env_data)