Add new package-installs system
We currently support package-installs definitions which has some limitations and oddities. This new format requires only one definition which does not reside in our run-parts directories and follows a consistent naming scheme (package-installs.yaml). Change-Id: Ie51a7c4fdc15634ae8e069728e5e07cc1dc36095
This commit is contained in:
		| @@ -1,8 +1,32 @@ | |||||||
| The package-installs element allows for a declarative method of installing and | The package-installs element allows for a declarative method of installing and | ||||||
| uninstalling packages for an image build. Adding a file under your elements | uninstalling packages for an image build. This is done by creating a | ||||||
| pre-install.d, install.d, or post-install.d directories called | package-installs.yaml or package-installs.json file in the element directory. | ||||||
| package-installs-<element-name> will cause the list of packages in that file to |  | ||||||
| be installed at the beginning of the respective phase. |  | ||||||
|  |  | ||||||
| If the package name in the file starts with a "-", then that package will be |  | ||||||
| removed at the end of the install.d phase. | example package-installs.yaml: | ||||||
|  | libxml2: | ||||||
|  | grub2: | ||||||
|  |   phase: pre-install.d | ||||||
|  | networkmanager: | ||||||
|  |   uninstall: True | ||||||
|  |  | ||||||
|  | example package-installs.json: | ||||||
|  | { | ||||||
|  |   "libxml2": null, | ||||||
|  |   "grub2": {"phase": "pre-install.d"}, | ||||||
|  |   "networkmanager": {"uninstall": true} | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Setting phase or uninstall properties for a package overrides the following | ||||||
|  | default values: | ||||||
|  |  | ||||||
|  | phase: install.d | ||||||
|  | uninstall: False | ||||||
|  |  | ||||||
|  |  | ||||||
|  | DEPRECATED: Adding a file under your elements pre-install.d, install.d, or | ||||||
|  | post-install.d directories called package-installs-<element-name> will cause | ||||||
|  | the list of packages in that file to be installed at the beginning of the | ||||||
|  | respective phase.  If the package name in the file starts with a "-", then | ||||||
|  | that package will be removed at the end of the install.d phase. | ||||||
|   | |||||||
							
								
								
									
										146
									
								
								elements/package-installs/bin/package-installs-v2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										146
									
								
								elements/package-installs/bin/package-installs-v2
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,146 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  |  | ||||||
|  | # Copyright 2014 Hewlett-Packard Development Company, L.P. | ||||||
|  | # | ||||||
|  | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||||
|  | # not use this file except in compliance with the License. You may obtain | ||||||
|  | # a copy of the License at | ||||||
|  | # | ||||||
|  | #     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | # Unless required by applicable law or agreed to in writing, software | ||||||
|  | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||||
|  | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||||
|  | # License for the specific language governing permissions and limitations | ||||||
|  | # under the License. | ||||||
|  |  | ||||||
|  | import argparse | ||||||
|  | from json import load as json_load | ||||||
|  | import os | ||||||
|  | import subprocess | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | from yaml import load as yaml_load | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PackageInstalls(object): | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def phase_to_attr(cls, phase): | ||||||
|  |         return phase.replace('.', '_').replace('-', '_') | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def from_yaml_path(cls, path): | ||||||
|  |         with open(path) as fp: | ||||||
|  |             return PackageInstalls.from_native_objs(yaml_load(fp)) | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def from_json_path(cls, path): | ||||||
|  |         with open(path) as fp: | ||||||
|  |             return PackageInstalls.from_native_objs(json_load(fp)) | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def from_native_objs(cls, objs): | ||||||
|  |         init_args = {} | ||||||
|  |         for pkg_name, params in objs.items(): | ||||||
|  |             uninstall = False | ||||||
|  |             phase = "install.d" | ||||||
|  |  | ||||||
|  |             try: | ||||||
|  |                 phase = params["phase"] | ||||||
|  |             except (KeyError, TypeError): | ||||||
|  |                 pass | ||||||
|  |  | ||||||
|  |             try: | ||||||
|  |                 uninstall = bool(params["uninstall"]) | ||||||
|  |             except (KeyError, TypeError): | ||||||
|  |                 pass | ||||||
|  |  | ||||||
|  |             init_arg = PackageInstalls.phase_to_attr(phase) | ||||||
|  |             if uninstall: | ||||||
|  |                 init_arg = init_arg + '_uninst' | ||||||
|  |  | ||||||
|  |             init_args[init_arg] = init_args.get(init_arg, []) + [pkg_name] | ||||||
|  |         return PackageInstalls(**init_args) | ||||||
|  |  | ||||||
|  |     def __init__(self, **phase_installs): | ||||||
|  |         for phase, pkgs in phase_installs.items(): | ||||||
|  |             setattr(self, phase, pkgs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PackageInstallsController(object): | ||||||
|  |  | ||||||
|  |     def __init__(self, path='/usr/share/package-installs'): | ||||||
|  |         self.path = path | ||||||
|  |  | ||||||
|  |     def package_installs(self): | ||||||
|  |         for yaml_path in os.listdir(self.path): | ||||||
|  |             full_path = os.path.join(self.path, yaml_path) | ||||||
|  |             if full_path.endswith('.yaml'): | ||||||
|  |                 pi = PackageInstalls.from_yaml_path(full_path) | ||||||
|  |             elif full_path.endswith('.json'): | ||||||
|  |                 pi = PackageInstalls.from_json_path(full_path) | ||||||
|  |             else: | ||||||
|  |                 print("No decoder known for %s, skipping" % full_path) | ||||||
|  |                 continue | ||||||
|  |             yield (yaml_path[:-5], pi) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def main(): | ||||||
|  |     parser = argparse.ArgumentParser( | ||||||
|  |         description="Install or uninstall packages for a specific phase based" | ||||||
|  |                     " on package-installs files.") | ||||||
|  |     parser.add_argument('--phase', | ||||||
|  |                         help="Install phase to filter on. Valid options are" | ||||||
|  |                              " 'install.d' or pre-install.d") | ||||||
|  |     parser.add_argument('--uninstall', action="store_true", | ||||||
|  |                         help="Only show packages to uninstall. By default only" | ||||||
|  |                              " packages to install are shown") | ||||||
|  |     args, extra = parser.parse_known_args() | ||||||
|  |  | ||||||
|  |     if not args.phase: | ||||||
|  |         print("Please specify an install phase.") | ||||||
|  |         sys.exit(1) | ||||||
|  |  | ||||||
|  |     pi_c = PackageInstallsController() | ||||||
|  |     pkgs = [] | ||||||
|  |     for element, pi in pi_c.package_installs(): | ||||||
|  |         installs_attr = PackageInstalls.phase_to_attr(args.phase) | ||||||
|  |         if args.uninstall: | ||||||
|  |             installs_attr += '_uninst' | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             phase_installs = getattr(pi, installs_attr) | ||||||
|  |         except AttributeError: | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         for pkg in phase_installs: | ||||||
|  |             print("Installing %s from %s" % (phase_installs, element)) | ||||||
|  |             pkg_map_args = ["pkg-map", "--missing-ok", "--element", element] | ||||||
|  |             pkg_map_args += phase_installs | ||||||
|  |  | ||||||
|  |             try: | ||||||
|  |                 map_output = subprocess.check_output(pkg_map_args) | ||||||
|  |             except subprocess.CalledProcessError as e: | ||||||
|  |                 if e.returncode == 1: | ||||||
|  |                     print("pkg-map failed with error %s" % e.output) | ||||||
|  |                     sys.exit(1) | ||||||
|  |                 elif e.returncode == 2: | ||||||
|  |                     pkgs += phase_installs | ||||||
|  |                     continue | ||||||
|  |             pkgs += map_output.strip().split('\n') | ||||||
|  |  | ||||||
|  |     install_args = ["install-packages"] | ||||||
|  |     if args.uninstall: | ||||||
|  |         install_args.append("-e") | ||||||
|  |     install_args.extend(pkgs) | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         subprocess.check_output(install_args) | ||||||
|  |     except subprocess.CalledProcessError as e: | ||||||
|  |         print("install failed with error %s" % e.output) | ||||||
|  |         sys.exit(1) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     main() | ||||||
							
								
								
									
										14
									
								
								elements/package-installs/extra-data.d/11-create-package-installs-dir
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										14
									
								
								elements/package-installs/extra-data.d/11-create-package-installs-dir
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -eux | ||||||
|  | set -o pipefail | ||||||
|  |  | ||||||
|  | dest_dir="$TMP_MOUNT_PATH/usr/share/package-installs/" | ||||||
|  | sudo mkdir -p $dest_dir | ||||||
|  |  | ||||||
|  | for ELEMENT in $IMAGE_ELEMENT ; do | ||||||
|  |     for DIR in ${ELEMENTS_PATH//:/ }; do | ||||||
|  |         if [ -f "$DIR/$ELEMENT/package-installs.yaml" ]; then | ||||||
|  |             sudo cp $DIR/$ELEMENT/package-installs.yaml $dest_dir/$ELEMENT.yaml | ||||||
|  |         fi | ||||||
|  |     done | ||||||
|  | done | ||||||
| @@ -4,3 +4,4 @@ set -eux | |||||||
| set -o pipefail | set -o pipefail | ||||||
|  |  | ||||||
| package-installs -d $(dirname $0) | package-installs -d $(dirname $0) | ||||||
|  | package-installs-v2 --phase install.d | ||||||
|   | |||||||
| @@ -4,3 +4,4 @@ set -eux | |||||||
| set -o pipefail | set -o pipefail | ||||||
|  |  | ||||||
| package-uninstalls -d $(dirname $0) | package-uninstalls -d $(dirname $0) | ||||||
|  | package-installs-v2 --phase install.d --uninstall | ||||||
|   | |||||||
| @@ -4,3 +4,4 @@ set -eux | |||||||
| set -o pipefail | set -o pipefail | ||||||
|  |  | ||||||
| package-installs -d $(dirname $0) | package-installs -d $(dirname $0) | ||||||
|  | package-installs-v2 --phase post-install.d | ||||||
|   | |||||||
| @@ -4,3 +4,4 @@ set -eux | |||||||
| set -o pipefail | set -o pipefail | ||||||
|  |  | ||||||
| package-uninstalls -d $(dirname $0) | package-uninstalls -d $(dirname $0) | ||||||
|  | package-installs-v2 --phase post-install.d --uninstall | ||||||
|   | |||||||
| @@ -4,3 +4,4 @@ set -eux | |||||||
| set -o pipefail | set -o pipefail | ||||||
|  |  | ||||||
| package-installs -d $(dirname $0) | package-installs -d $(dirname $0) | ||||||
|  | package-installs-v2 --phase pre-install.d | ||||||
|   | |||||||
| @@ -4,3 +4,4 @@ set -eux | |||||||
| set -o pipefail | set -o pipefail | ||||||
|  |  | ||||||
| package-uninstalls -d $(dirname $0) | package-uninstalls -d $(dirname $0) | ||||||
|  | package-installs-v2 --phase pre-install.d --uninstall | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							| @@ -28,6 +28,6 @@ commands = bash -c 'if [ ! -d ./.testrepository ] ; then testr init ; fi' | |||||||
| downloadcache = ~/cache/pip | downloadcache = ~/cache/pip | ||||||
|  |  | ||||||
| [flake8] | [flake8] | ||||||
| ignore = E125,H202,H803 | ignore = E125,H202,H302,H803 | ||||||
| builtins = _ | builtins = _ | ||||||
| exclude =  .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build | exclude =  .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Gregory Haynes
					Gregory Haynes