diff --git a/docs/composer.cli.rst b/docs/composer.cli.rst index 50ff52f2..c1366a8e 100644 --- a/docs/composer.cli.rst +++ b/docs/composer.cli.rst @@ -52,6 +52,14 @@ composer.cli.projects module :undoc-members: :show-inheritance: +composer.cli.providers module +----------------------------- + +.. automodule:: composer.cli.providers + :members: + :undoc-members: + :show-inheritance: + composer.cli.sources module --------------------------- @@ -68,6 +76,14 @@ composer.cli.status module :undoc-members: :show-inheritance: +composer.cli.upload module +-------------------------- + +.. automodule:: composer.cli.upload + :members: + :undoc-members: + :show-inheritance: + composer.cli.utilities module ----------------------------- diff --git a/docs/html/.buildinfo b/docs/html/.buildinfo index 54cfc865..f57824d9 100644 --- a/docs/html/.buildinfo +++ b/docs/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: f8c32864d71075ed86014ee145636981 +config: ddd1819963711304e50ae8967f60783e tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/html/.doctrees/composer-cli.doctree b/docs/html/.doctrees/composer-cli.doctree index 0f18d8a5..82e66449 100644 Binary files a/docs/html/.doctrees/composer-cli.doctree and b/docs/html/.doctrees/composer-cli.doctree differ diff --git a/docs/html/.doctrees/composer.cli.doctree b/docs/html/.doctrees/composer.cli.doctree index e7375591..ed6215d2 100644 Binary files a/docs/html/.doctrees/composer.cli.doctree and b/docs/html/.doctrees/composer.cli.doctree differ diff --git a/docs/html/.doctrees/composer.doctree b/docs/html/.doctrees/composer.doctree index 385bb51b..17e8da96 100644 Binary files a/docs/html/.doctrees/composer.doctree and b/docs/html/.doctrees/composer.doctree differ diff --git a/docs/html/.doctrees/environment.pickle b/docs/html/.doctrees/environment.pickle index 6092882e..441481c9 100644 Binary files a/docs/html/.doctrees/environment.pickle and b/docs/html/.doctrees/environment.pickle differ diff --git a/docs/html/.doctrees/index.doctree b/docs/html/.doctrees/index.doctree index bb82811c..9f150f3c 100644 Binary files a/docs/html/.doctrees/index.doctree and b/docs/html/.doctrees/index.doctree differ diff --git a/docs/html/.doctrees/intro.doctree b/docs/html/.doctrees/intro.doctree index 41392cbc..ec1909d3 100644 Binary files a/docs/html/.doctrees/intro.doctree and b/docs/html/.doctrees/intro.doctree differ diff --git a/docs/html/.doctrees/lifted.doctree b/docs/html/.doctrees/lifted.doctree new file mode 100644 index 00000000..d50b7740 Binary files /dev/null and b/docs/html/.doctrees/lifted.doctree differ diff --git a/docs/html/.doctrees/livemedia-creator.doctree b/docs/html/.doctrees/livemedia-creator.doctree index 7397f304..687c240d 100644 Binary files a/docs/html/.doctrees/livemedia-creator.doctree and b/docs/html/.doctrees/livemedia-creator.doctree differ diff --git a/docs/html/.doctrees/lorax-composer.doctree b/docs/html/.doctrees/lorax-composer.doctree index 1bc583c8..4b0e4cc4 100644 Binary files a/docs/html/.doctrees/lorax-composer.doctree and b/docs/html/.doctrees/lorax-composer.doctree differ diff --git a/docs/html/.doctrees/lorax.doctree b/docs/html/.doctrees/lorax.doctree index f9be5789..1eec4d45 100644 Binary files a/docs/html/.doctrees/lorax.doctree and b/docs/html/.doctrees/lorax.doctree differ diff --git a/docs/html/.doctrees/modules.doctree b/docs/html/.doctrees/modules.doctree index c96b257e..79e48e0e 100644 Binary files a/docs/html/.doctrees/modules.doctree and b/docs/html/.doctrees/modules.doctree differ diff --git a/docs/html/.doctrees/product-images.doctree b/docs/html/.doctrees/product-images.doctree index f1addd6a..2025b6c8 100644 Binary files a/docs/html/.doctrees/product-images.doctree and b/docs/html/.doctrees/product-images.doctree differ diff --git a/docs/html/.doctrees/pylorax.api.doctree b/docs/html/.doctrees/pylorax.api.doctree index 0c572ee6..37052167 100644 Binary files a/docs/html/.doctrees/pylorax.api.doctree and b/docs/html/.doctrees/pylorax.api.doctree differ diff --git a/docs/html/.doctrees/pylorax.doctree b/docs/html/.doctrees/pylorax.doctree index 9ba391ac..bf3d0256 100644 Binary files a/docs/html/.doctrees/pylorax.doctree and b/docs/html/.doctrees/pylorax.doctree differ diff --git a/docs/html/_modules/composer/cli.html b/docs/html/_modules/composer/cli.html index 142bc38f..c7eb8e52 100644 --- a/docs/html/_modules/composer/cli.html +++ b/docs/html/_modules/composer/cli.html @@ -8,7 +8,7 @@ - composer.cli — Lorax 31.9 documentation + composer.cli — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
@@ -181,14 +181,18 @@ from composer.cli.compose import compose_cmd from composer.cli.sources import sources_cmd from composer.cli.status import status_cmd +from composer.cli.upload import upload_cmd +from composer.cli.providers import providers_cmd command_map = { "blueprints": blueprints_cmd, - "modules": modules_cmd, - "projects": projects_cmd, - "compose": compose_cmd, - "sources": sources_cmd, - "status": status_cmd + "modules": modules_cmd, + "projects": projects_cmd, + "compose": compose_cmd, + "sources": sources_cmd, + "status": status_cmd, + "upload": upload_cmd, + "providers": providers_cmd } diff --git a/docs/html/_modules/composer/cli/blueprints.html b/docs/html/_modules/composer/cli/blueprints.html index 23420d7f..31657bf5 100644 --- a/docs/html/_modules/composer/cli/blueprints.html +++ b/docs/html/_modules/composer/cli/blueprints.html @@ -8,7 +8,7 @@ - composer.cli.blueprints — Lorax 31.9 documentation + composer.cli.blueprints — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
diff --git a/docs/html/_modules/composer/cli/cmdline.html b/docs/html/_modules/composer/cli/cmdline.html index d2b90ed1..13997bef 100644 --- a/docs/html/_modules/composer/cli/cmdline.html +++ b/docs/html/_modules/composer/cli/cmdline.html @@ -8,7 +8,7 @@ - composer.cli.cmdline — Lorax 31.9 documentation + composer.cli.cmdline — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
@@ -195,7 +195,7 @@ help="Path to the socket file to listen on") parser.add_argument("--log", dest="logfile", default=None, metavar="LOG", help="Path to logfile (./composer-cli.log)") - parser.add_argument("-a", "--api", dest="api_version", default="0", metavar="APIVER", + parser.add_argument("-a", "--api", dest="api_version", default="1", metavar="APIVER", help="API Version to use") parser.add_argument("--test", dest="testmode", default=0, type=int, metavar="TESTMODE", help="Pass test mode to compose. 1=Mock compose with fail. 2=Mock compose with finished.") diff --git a/docs/html/_modules/composer/cli/compose.html b/docs/html/_modules/composer/cli/compose.html index 45828c73..176e21d0 100644 --- a/docs/html/_modules/composer/cli/compose.html +++ b/docs/html/_modules/composer/cli/compose.html @@ -8,7 +8,7 @@ - composer.cli.compose — Lorax 31.9 documentation + composer.cli.compose — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
@@ -178,6 +178,7 @@ from datetime import datetime import sys import json +import toml from composer import http_client as client from composer.cli.help import compose_help @@ -370,7 +371,7 @@ :param testmode: Set to 1 to simulate a failed compose, set to 2 to simulate a finished one. :type testmode: int - compose start <blueprint-name> <compose-type> + compose start <blueprint-name> <compose-type> [<image-name> <provider> <profile> | <image-name> <profile.toml>] """ if len(args) == 0: log.error("start is missing the blueprint name and output type") @@ -378,12 +379,30 @@ if len(args) == 1: log.error("start is missing the output type") return 1 + if len(args) == 3: + log.error("start is missing the provider and profile details") + return 1 config = { "blueprint_name": args[0], "compose_type": args[1], "branch": "master" } + if len(args) == 4: + config["upload"] = {"image_name": args[2]} + # profile TOML file (maybe) + try: + config["upload"].update(toml.load(args[3])) + except toml.TomlDecodeError as e: + log.error(str(e)) + return 1 + elif len(args) == 5: + config["upload"] = { + "image_name": args[2], + "provider": args[3], + "profile": args[4] + } + if testmode: test_url = "?test=%d" % testmode else: @@ -395,6 +414,10 @@ return rc print("Compose %s added to the queue" % result["build_id"]) + + if "upload_id" in result and result["upload_id"]: + print ("Upload %s added to the upload queue" % result["upload_id"]) + return rc
[docs]def compose_log(socket_path, api_version, args, show_json=False, testmode=0): diff --git a/docs/html/_modules/composer/cli/modules.html b/docs/html/_modules/composer/cli/modules.html index d887ce91..3b752ddb 100644 --- a/docs/html/_modules/composer/cli/modules.html +++ b/docs/html/_modules/composer/cli/modules.html @@ -8,7 +8,7 @@ - composer.cli.modules — Lorax 31.9 documentation + composer.cli.modules — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
diff --git a/docs/html/_modules/composer/cli/projects.html b/docs/html/_modules/composer/cli/projects.html index 8fb0f45d..51fb5bc7 100644 --- a/docs/html/_modules/composer/cli/projects.html +++ b/docs/html/_modules/composer/cli/projects.html @@ -8,7 +8,7 @@ - composer.cli.projects — Lorax 31.9 documentation + composer.cli.projects — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
diff --git a/docs/html/_modules/composer/cli/providers.html b/docs/html/_modules/composer/cli/providers.html new file mode 100644 index 00000000..bd0db2c0 --- /dev/null +++ b/docs/html/_modules/composer/cli/providers.html @@ -0,0 +1,522 @@ + + + + + + + + + + + composer.cli.providers — Lorax 32.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +

Source code for composer.cli.providers

+#
+# Copyright (C) 2019  Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+import logging
+log = logging.getLogger("composer-cli")
+
+import json
+import toml
+import os
+
+from composer import http_client as client
+from composer.cli.help import providers_help
+from composer.cli.utilities import handle_api_result, toml_filename
+
+
[docs]def providers_cmd(opts): + """Process providers commands + + :param opts: Cmdline arguments + :type opts: argparse.Namespace + :returns: Value to return from sys.exit() + :rtype: int + + This dispatches the providers commands to a function + """ + cmd_map = { + "list": providers_list, + "info": providers_info, + "show": providers_show, + "push": providers_push, + "save": providers_save, + "delete": providers_delete, + "template": providers_template + } + if opts.args[1] == "help" or opts.args[1] == "--help": + print(providers_help) + return 0 + elif opts.args[1] not in cmd_map: + log.error("Unknown providers command: %s", opts.args[1]) + return 1 + + return cmd_map[opts.args[1]](opts.socket, opts.api_version, opts.args[2:], opts.json, opts.testmode)
+ +
[docs]def providers_list(socket_path, api_version, args, show_json=False, testmode=0): + """Return the list of providers + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + providers list + """ + api_route = client.api_url(api_version, "/upload/providers") + r = client.get_url_json(socket_path, api_route) + results = r["providers"] + if not results: + return 0 + + if show_json: + print(json.dumps(results, indent=4)) + else: + if len(args) == 1: + if args[0] not in results: + log.error("%s is not a valid provider", args[0]) + return 1 + print("\n".join(sorted(results[args[0]]["profiles"].keys()))) + else: + print("\n".join(sorted(results.keys()))) + + return 0
+ +
[docs]def providers_info(socket_path, api_version, args, show_json=False, testmode=0): + """Show information about each provider + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + providers info <PROVIDER> + """ + if len(args) == 0: + log.error("info is missing the provider name") + return 1 + + api_route = client.api_url(api_version, "/upload/providers") + r = client.get_url_json(socket_path, api_route) + results = r["providers"] + if not results: + return 0 + + if show_json: + print(json.dumps(results, indent=4)) + else: + if args[0] not in results: + log.error("%s is not a valid provider", args[0]) + return 1 + p = results[args[0]] + print("%s supports these image types: %s" % (p["display"], ", ".join(p["supported_types"]))) + print("Settings:") + for k in p["settings-info"]: + f = p["settings-info"][k] + print(" %-20s: %s is a %s" % (k, f["display"], f["type"])) + + return 0
+ +
[docs]def providers_show(socket_path, api_version, args, show_json=False, testmode=0): + """Return details about a provider + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + providers show <provider> <profile> + """ + if len(args) == 0: + log.error("show is missing the provider name") + return 1 + if len(args) == 1: + log.error("show is missing the profile name") + return 1 + + api_route = client.api_url(api_version, "/upload/providers") + r = client.get_url_json(socket_path, api_route) + results = r["providers"] + if not results: + return 0 + + if show_json: + print(json.dumps(results, indent=4)) + else: + if args[0] not in results: + log.error("%s is not a valid provider", args[0]) + return 1 + if args[1] not in results[args[0]]["profiles"]: + log.error("%s is not a valid %s profile", args[1], args[0]) + return 1 + + # Print the details for this profile + # fields are different for each provider, so we just print out the key:values + for k in results[args[0]]["profiles"][args[1]]: + print("%s: %s" % (k, results[args[0]]["profiles"][args[1]][k])) + return 0
+ +
[docs]def providers_push(socket_path, api_version, args, show_json=False, testmode=0): + """Add a new provider profile or overwrite an existing one + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + providers push <profile.toml> + + """ + if len(args) == 0: + log.error("push is missing the profile TOML file") + return 1 + if not os.path.exists(args[0]): + log.error("Missing profile TOML file: %s", args[0]) + return 1 + + api_route = client.api_url(api_version, "/upload/providers/save") + profile = toml.load(args[0]) + result = client.post_url_json(socket_path, api_route, json.dumps(profile)) + return handle_api_result(result, show_json)[0]
+ +
[docs]def providers_save(socket_path, api_version, args, show_json=False, testmode=0): + """Save a provider's profile to a TOML file + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + providers save <provider> <profile> + + """ + if len(args) == 0: + log.error("save is missing the provider name") + return 1 + if len(args) == 1: + log.error("save is missing the profile name") + return 1 + + api_route = client.api_url(api_version, "/upload/providers") + r = client.get_url_json(socket_path, api_route) + results = r["providers"] + if not results: + return 0 + + if show_json: + print(json.dumps(results, indent=4)) + else: + if args[0] not in results: + log.error("%s is not a valid provider", args[0]) + return 1 + if args[1] not in results[args[0]]["profiles"]: + log.error("%s is not a valid %s profile", args[1], args[0]) + return 1 + + profile = { + "provider": args[0], + "profile": args[1], + "settings": results[args[0]]["profiles"][args[1]] + } + open(toml_filename(args[1]), "w").write(toml.dumps(profile)) + + return 0
+ +
[docs]def providers_delete(socket_path, api_version, args, show_json=False, testmode=0): + """Delete a profile from a provider + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + providers delete <provider> <profile> + + """ + if len(args) == 0: + log.error("delete is missing the provider name") + return 1 + if len(args) == 1: + log.error("delete is missing the profile name") + return 1 + + api_route = client.api_url(api_version, "/upload/providers/delete/%s/%s" % (args[0], args[1])) + result = client.delete_url_json(socket_path, api_route) + return handle_api_result(result, show_json)[0]
+ +
[docs]def providers_template(socket_path, api_version, args, show_json=False, testmode=0): + """Return a TOML template for setting the provider's fields + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + providers template <provider> + """ + if len(args) == 0: + log.error("template is missing the provider name") + return 1 + + api_route = client.api_url(api_version, "/upload/providers") + r = client.get_url_json(socket_path, api_route) + results = r["providers"] + if not results: + return 0 + + if show_json: + print(json.dumps(results, indent=4)) + return 0 + + if args[0] not in results: + log.error("%s is not a valid provider", args[0]) + return 1 + + template = {"provider": args[0]} + settings = results[args[0]]["settings-info"] + template["settings"] = dict([(k, settings[k]["display"]) for k in settings]) + print(toml.dumps(template)) + + return 0
+
+ +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/html/_modules/composer/cli/sources.html b/docs/html/_modules/composer/cli/sources.html index 15f9c6bd..d73f3bb5 100644 --- a/docs/html/_modules/composer/cli/sources.html +++ b/docs/html/_modules/composer/cli/sources.html @@ -8,7 +8,7 @@ - composer.cli.sources — Lorax 31.9 documentation + composer.cli.sources — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
diff --git a/docs/html/_modules/composer/cli/status.html b/docs/html/_modules/composer/cli/status.html index d03db009..13b7aeff 100644 --- a/docs/html/_modules/composer/cli/status.html +++ b/docs/html/_modules/composer/cli/status.html @@ -8,7 +8,7 @@ - composer.cli.status — Lorax 31.9 documentation + composer.cli.status — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
diff --git a/docs/html/_modules/composer/cli/upload.html b/docs/html/_modules/composer/cli/upload.html new file mode 100644 index 00000000..dcc8cfdb --- /dev/null +++ b/docs/html/_modules/composer/cli/upload.html @@ -0,0 +1,477 @@ + + + + + + + + + + + composer.cli.upload — Lorax 32.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +

Source code for composer.cli.upload

+#
+# Copyright (C) 2019  Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+import logging
+log = logging.getLogger("composer-cli")
+
+import json
+import toml
+import os
+
+from composer import http_client as client
+from composer.cli.help import upload_help
+from composer.cli.utilities import handle_api_result
+
+
[docs]def upload_cmd(opts): + """Process upload commands + + :param opts: Cmdline arguments + :type opts: argparse.Namespace + :returns: Value to return from sys.exit() + :rtype: int + + This dispatches the upload commands to a function + """ + cmd_map = { + "list": upload_list, + "info": upload_info, + "start": upload_start, + "log": upload_log, + "cancel": upload_cancel, + "delete": upload_delete, + "reset": upload_reset, + } + if opts.args[1] == "help" or opts.args[1] == "--help": + print(upload_help) + return 0 + elif opts.args[1] not in cmd_map: + log.error("Unknown upload command: %s", opts.args[1]) + return 1 + + return cmd_map[opts.args[1]](opts.socket, opts.api_version, opts.args[2:], opts.json, opts.testmode)
+ +
[docs]def upload_list(socket_path, api_version, args, show_json=False, testmode=0): + """Return the composes and their associated upload uuids and status + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + upload list + """ + api_route = client.api_url(api_version, "/compose/finished") + r = client.get_url_json(socket_path, api_route) + results = r["finished"] + if not results: + return 0 + + if show_json: + print(json.dumps(results, indent=4)) + else: + compose_fmt = "{id} {queue_status} {blueprint} {version} {compose_type}" + upload_fmt = ' {uuid} "{image_name}" {provider_name} {status}' + for c in results: + print(compose_fmt.format(**c)) + print("\n".join(upload_fmt.format(**u) for u in c["uploads"])) + print() + + return 0
+ +
[docs]def upload_info(socket_path, api_version, args, show_json=False, testmode=0): + """Return detailed information about the upload + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + upload info <uuid> + + This returns information about the upload, including uuid, name, status, service, and image. + """ + if len(args) == 0: + log.error("info is missing the upload uuid") + return 1 + + api_route = client.api_url(api_version, "/upload/info/%s" % args[0]) + result = client.get_url_json(socket_path, api_route) + (rc, exit_now) = handle_api_result(result, show_json) + if exit_now: + return rc + + image_path = result["upload"]["image_path"] + print("%s %-8s %-15s %-8s %s" % (result["upload"]["uuid"], + result["upload"]["status"], + result["upload"]["image_name"], + result["upload"]["provider_name"], + os.path.basename(image_path) if image_path else "UNFINISHED")) + + return rc
+ +
[docs]def upload_start(socket_path, api_version, args, show_json=False, testmode=0): + """Start upload up a build uuid image + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + upload start <build-uuid> <image-name> [<provider> <profile> | <profile.toml>] + """ + if len(args) == 0: + log.error("start is missing the compose build id") + return 1 + if len(args) == 1: + log.error("start is missing the image name") + return 1 + if len(args) == 2: + log.error("start is missing the provider and profile details") + return 1 + + body = {"image_name": args[1]} + if len(args) == 3: + try: + body.update(toml.load(args[2])) + except toml.TomlDecodeError as e: + log.error(str(e)) + return 1 + elif len(args) == 4: + body["provider"] = args[2] + body["profile"] = args[3] + else: + log.error("start has incorrect number of arguments") + return 1 + + api_route = client.api_url(api_version, "/compose/uploads/schedule/%s" % args[0]) + result = client.post_url_json(socket_path, api_route, json.dumps(body)) + (rc, exit_now) = handle_api_result(result, show_json) + if exit_now: + return rc + + print("Upload %s added to the queue" % result["upload_id"]) + return rc
+ +
[docs]def upload_log(socket_path, api_version, args, show_json=False, testmode=0): + """Return the upload log + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + upload log <build-uuid> + """ + if len(args) == 0: + log.error("log is missing the upload uuid") + return 1 + + api_route = client.api_url(api_version, "/upload/log/%s" % args[0]) + result = client.get_url_json(socket_path, api_route) + (rc, exit_now) = handle_api_result(result, show_json) + if exit_now: + return rc + + print("Upload log for %s:\n" % result["upload_id"]) + print(result["log"]) + + return 0
+ +
[docs]def upload_cancel(socket_path, api_version, args, show_json=False, testmode=0): + """Cancel the queued or running upload + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + upload cancel <build-uuid> + """ + if len(args) == 0: + log.error("cancel is missing the upload uuid") + return 1 + + api_route = client.api_url(api_version, "/upload/cancel/%s" % args[0]) + result = client.delete_url_json(socket_path, api_route) + return handle_api_result(result, show_json)[0]
+ +
[docs]def upload_delete(socket_path, api_version, args, show_json=False, testmode=0): + """Delete an upload and remove it from the build + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + upload delete <build-uuid> + """ + if len(args) == 0: + log.error("delete is missing the upload uuid") + return 1 + + api_route = client.api_url(api_version, "/upload/delete/%s" % args[0]) + result = client.delete_url_json(socket_path, api_route) + return handle_api_result(result, show_json)[0]
+ +
[docs]def upload_reset(socket_path, api_version, args, show_json=False, testmode=0): + """Reset the upload and execute it again + + :param socket_path: Path to the Unix socket to use for API communication + :type socket_path: str + :param api_version: Version of the API to talk to. eg. "0" + :type api_version: str + :param args: List of remaining arguments from the cmdline + :type args: list of str + :param show_json: Set to True to show the JSON output instead of the human readable output + :type show_json: bool + :param testmode: unused in this function + :type testmode: int + + upload reset <build-uuid> + """ + if len(args) == 0: + log.error("reset is missing the upload uuid") + return 1 + + api_route = client.api_url(api_version, "/upload/reset/%s" % args[0]) + result = client.post_url_json(socket_path, api_route, json.dumps({})) + return handle_api_result(result, show_json)[0]
+
+ +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/html/_modules/composer/cli/utilities.html b/docs/html/_modules/composer/cli/utilities.html index 720f518c..faa533ed 100644 --- a/docs/html/_modules/composer/cli/utilities.html +++ b/docs/html/_modules/composer/cli/utilities.html @@ -8,7 +8,7 @@ - composer.cli.utilities — Lorax 31.9 documentation + composer.cli.utilities — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
diff --git a/docs/html/_modules/composer/http_client.html b/docs/html/_modules/composer/http_client.html index b5090db2..dd336dd0 100644 --- a/docs/html/_modules/composer/http_client.html +++ b/docs/html/_modules/composer/http_client.html @@ -8,7 +8,7 @@ - composer.http_client — Lorax 31.9 documentation + composer.http_client — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
diff --git a/docs/html/_modules/composer/unix_socket.html b/docs/html/_modules/composer/unix_socket.html index c423fdea..93f93a88 100644 --- a/docs/html/_modules/composer/unix_socket.html +++ b/docs/html/_modules/composer/unix_socket.html @@ -8,7 +8,7 @@ - composer.unix_socket — Lorax 31.9 documentation + composer.unix_socket — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
diff --git a/docs/html/_modules/index.html b/docs/html/_modules/index.html index 9cc1f9f4..ce38c526 100644 --- a/docs/html/_modules/index.html +++ b/docs/html/_modules/index.html @@ -8,7 +8,7 @@ - Overview: module code — Lorax 31.9 documentation + Overview: module code — Lorax 32.1 documentation @@ -58,7 +58,7 @@
- 31.9 + 32.1
@@ -158,11 +158,17 @@
  • composer.cli.compose
  • composer.cli.modules
  • composer.cli.projects
  • +
  • composer.cli.providers
  • composer.cli.sources
  • composer.cli.status
  • +
  • composer.cli.upload
  • composer.cli.utilities
  • composer.http_client
  • composer.unix_socket
  • +
  • lifted.config
  • +
  • lifted.providers
  • +
  • lifted.queue
  • +
  • lifted.upload
  • pylorax
  • -
    [docs]def repo_to_source(repo, system_source): +
    [docs]def repo_to_source(repo, system_source, api=1): """Return a Weldr Source dict created from the DNF Repository :param repo: DNF Repository :type repo: dnf.RepoDict :param system_source: True if this source is an immutable system source :type system_source: bool + :param api: Select which api version of the dict to return (default 1) + :type api: int :returns: A dict with Weldr Source fields filled in :rtype: dict @@ -585,15 +588,23 @@ "gpgkey_url": [ "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-28-x86_64" ], - "name": "fedora", + "id": "fedora", + "name": "Fedora $releasever - $basearch", "proxy": "http://proxy.brianlane.com:8123", "system": true "type": "yum-metalink", "url": "https://mirrors.fedoraproject.org/metalink?repo=fedora-28&arch=x86_64" } + The ``name`` field has changed in v1 of the API. + In v0 of the API ``name`` is the repo.id, in v1 it is the repo.name and a new field, + ``id`` has been added for the repo.id + """ - source = {"name": repo.id, "system": system_source} + if api==0: + source = {"name": repo.id, "system": system_source} + else: + source = {"id": repo.id, "name": repo.name, "system": system_source} if repo.baseurl: source["url"] = repo.baseurl[0] source["type"] = "yum-baseurl" @@ -625,11 +636,65 @@ return source
    +
    [docs]def source_to_repodict(source): + """Return a tuple suitable for use with dnf.add_new_repo + + :param source: A Weldr source dict + :type source: dict + :returns: A tuple of dnf.Repo attributes + :rtype: (str, list, dict) + + Return a tuple with (id, baseurl|(), kwargs) that can be used + with dnf.repos.add_new_repo + """ + kwargs = {} + if "id" in source: + # This is an API v1 source definition + repoid = source["id"] + if "name" in source: + kwargs["name"] = source["name"] + else: + repoid = source["name"] + + # This will allow errors to be raised so we can catch them + # without this they are logged, but the repo is silently disabled + kwargs["skip_if_unavailable"] = False + + if source["type"] == "yum-baseurl": + baseurl = [source["url"]] + elif source["type"] == "yum-metalink": + kwargs["metalink"] = source["url"] + baseurl = () + elif source["type"] == "yum-mirrorlist": + kwargs["mirrorlist"] = source["url"] + baseurl = () + + if "proxy" in source: + kwargs["proxy"] = source["proxy"] + + if source["check_ssl"]: + kwargs["sslverify"] = True + else: + kwargs["sslverify"] = False + + if source["check_gpg"]: + kwargs["gpgcheck"] = True + else: + kwargs["gpgcheck"] = False + + if "gpgkey_urls" in source: + kwargs["gpgkey"] = tuple(source["gpgkey_urls"]) + + return (repoid, baseurl, kwargs)
    + +
    [docs]def source_to_repo(source, dnf_conf): """Return a dnf Repo object created from a source dict :param source: A Weldr source dict :type source: dict + :param dnf_conf: The dnf Config object + :type dnf_conf: dnf.conf :returns: A dnf Repo object :rtype: dnf.Repo @@ -641,41 +706,25 @@ "gpgkey_urls": [ "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-28-x86_64" ], - "name": "fedora", + "id": "fedora", + "name": "Fedora $releasever - $basearch", "proxy": "http://proxy.brianlane.com:8123", "system": True "type": "yum-metalink", "url": "https://mirrors.fedoraproject.org/metalink?repo=fedora-28&arch=x86_64" } + If the ``id`` field is included it is used for the repo id, otherwise ``name`` is used. + v0 of the API only used ``name``, v1 added the distinction between ``id`` and ``name``. """ - repo = dnf.repo.Repo(source["name"], dnf_conf) - # This will allow errors to be raised so we can catch them - # without this they are logged, but the repo is silently disabled - repo.skip_if_unavailable = False + repoid, baseurl, kwargs = source_to_repodict(source) + repo = dnf.repo.Repo(repoid, dnf_conf) + if baseurl: + repo.baseurl = baseurl - if source["type"] == "yum-baseurl": - repo.baseurl = source["url"] - elif source["type"] == "yum-metalink": - repo.metalink = source["url"] - elif source["type"] == "yum-mirrorlist": - repo.mirrorlist = source["url"] - - if "proxy" in source: - repo.proxy = source["proxy"] - - if source["check_ssl"]: - repo.sslverify = True - else: - repo.sslverify = False - - if source["check_gpg"]: - repo.gpgcheck = True - else: - repo.gpgcheck = False - - if "gpgkey_urls" in source: - repo.gpgkey = tuple(source["gpgkey_urls"]) + # Apply the rest of the kwargs to the Repo object + for k, v in kwargs.items(): + setattr(repo, k, v) repo.enable() @@ -709,11 +758,13 @@ sources.extend(get_source_ids(f)) return sources
    -
    [docs]def delete_repo_source(source_glob, source_name): +
    [docs]def delete_repo_source(source_glob, source_id): """Delete a source from a repo file :param source_glob: A glob of the repo sources to search :type source_glob: str + :param source_id: The repo id to delete + :type source_id: str :returns: None :raises: ProjectsError if there was a problem @@ -721,16 +772,16 @@ If it is the last one in the file, delete the file. WARNING: This will delete ANY source, the caller needs to ensure that a system - source_name isn't passed to it. + source_id isn't passed to it. """ found = False for f in glob(source_glob): try: cfg = ConfigParser() cfg.read(f) - if source_name in cfg.sections(): + if source_id in cfg.sections(): found = True - cfg.remove_section(source_name) + cfg.remove_section(source_id) # If there are other sections, rewrite the file without the deleted one if len(cfg.sections()) > 0: with open(f, "w") as cfg_file: @@ -739,9 +790,69 @@ # No sections left, just delete the file os.unlink(f) except Exception as e: - raise ProjectsError("Problem deleting repo source %s: %s" % (source_name, str(e))) + raise ProjectsError("Problem deleting repo source %s: %s" % (source_id, str(e))) if not found: - raise ProjectsError("source %s not found" % source_name)
    + raise ProjectsError("source %s not found" % source_id)
    + +
    [docs]def new_repo_source(dbo, repoid, source, repo_dir): + """Add a new repo source from a Weldr source dict + + :param dbo: dnf base object + :type dbo: dnf.Base + :param id: The repo id (API v0 uses the name, v1 uses the id) + :type id: str + :param source: A Weldr source dict + :type source: dict + :returns: None + :raises: ... + + Make sure access to the dbo has been locked before calling this. + The `id` parameter will the the 'name' field for API v0, and the 'id' field for API v1 + + DNF variables will be substituted at load time, and on restart. + """ + try: + # Remove it from the RepoDict (NOTE that this isn't explicitly supported by the DNF API) + # If this repo already exists, delete it and replace it with the new one + repos = list(r.id for r in dbo.repos.iter_enabled()) + if repoid in repos: + del dbo.repos[repoid] + + # Add the repo and substitute any dnf variables + _, baseurl, kwargs = source_to_repodict(source) + log.debug("repoid=%s, baseurl=%s, kwargs=%s", repoid, baseurl, kwargs) + r = dbo.repos.add_new_repo(repoid, dbo.conf, baseurl, **kwargs) + r.enable() + + log.info("Updating repository metadata after adding %s", repoid) + dbo.fill_sack(load_system_repo=False) + dbo.read_comps() + + # Remove any previous sources with this id, ignore it if it isn't found + try: + delete_repo_source(joinpaths(repo_dir, "*.repo"), repoid) + except ProjectsError: + pass + + # Make sure the source id can't contain a path traversal by taking the basename + source_path = joinpaths(repo_dir, os.path.basename("%s.repo" % repoid)) + # Write the un-substituted version of the repo to disk + with open(source_path, "w") as f: + repo = source_to_repo(source, dbo.conf) + f.write(dnf_repo_to_file_repo(repo)) + except Exception as e: + log.error("(new_repo_source) adding %s failed: %s", repoid, str(e)) + + # Cleanup the mess, if loading it failed we don't want to leave it in memory + repos = list(r.id for r in dbo.repos.iter_enabled()) + if repoid in repos: + del dbo.repos[repoid] + + log.info("Updating repository metadata after adding %s failed", repoid) + dbo.fill_sack(load_system_repo=False) + dbo.read_comps() + + raise
    diff --git a/docs/html/_modules/pylorax/api/queue.html b/docs/html/_modules/pylorax/api/queue.html index 7ee7a36b..f6cdf46a 100644 --- a/docs/html/_modules/pylorax/api/queue.html +++ b/docs/html/_modules/pylorax/api/queue.html @@ -8,7 +8,7 @@ - pylorax.api.queue — Lorax 31.9 documentation + pylorax.api.queue — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    @@ -196,6 +196,8 @@ from pylorax.creator import run_creator from pylorax.sysutils import joinpaths, read_tail +from lifted.queue import create_upload, get_uploads, ready_upload, delete_upload +
    [docs]def check_queues(cfg): """Check to make sure the new and run queue symlinks are correct @@ -251,7 +253,7 @@ lib_dir = cfg.get("composer", "lib_dir") share_dir = cfg.get("composer", "share_dir") tmp = cfg.get("composer", "tmp") - monitor_cfg = DataHolder(composer_dir=lib_dir, share_dir=share_dir, uid=uid, gid=gid, tmp=tmp) + monitor_cfg = DataHolder(cfg=cfg, composer_dir=lib_dir, share_dir=share_dir, uid=uid, gid=gid, tmp=tmp) p = mp.Process(target=monitor, args=(monitor_cfg,)) p.daemon = True p.start()
    @@ -321,6 +323,11 @@ log.info("Finished building %s, results are in %s", dst, os.path.realpath(dst)) open(joinpaths(dst, "STATUS"), "w").write("FINISHED\n") write_timestamp(dst, TS_FINISHED) + + upload_cfg = cfg.cfg["upload"] + for upload in get_uploads(upload_cfg, uuid_get_uploads(cfg.cfg, uuids[0])): + log.info("Readying upload %s", upload.uuid) + uuid_ready_upload(cfg.cfg, uuids[0], upload.uuid) except Exception: import traceback log.error("traceback: %s", traceback.format_exc()) @@ -456,11 +463,15 @@ raise RuntimeError("Cannot find ks template for build %s" % os.path.basename(results_dir)) return t[0] -
    [docs]def compose_detail(results_dir): +
    [docs]def compose_detail(cfg, results_dir, api=1): """Return details about the build. + :param cfg: Configuration settings (required for api=1) + :type cfg: ComposerConfig :param results_dir: The directory containing the metadata and results for the build :type results_dir: str + :param api: Select which api version of the dict to return (default 1) + :type api: int :returns: A dictionary with details about the compose :rtype: dict :raises: IOError if it cannot read the directory, STATUS, or blueprint file. @@ -473,6 +484,7 @@ * blueprint - Blueprint name * version - Blueprint version * image_size - Size of the image, if finished. 0 otherwise. + * uploads - For API v1 details about uploading the image are included Various timestamps are also included in the dict. These are all Unix UTC timestamps. It is possible for these timestamps to not always exist, in which case they will be @@ -496,22 +508,31 @@ times = timestamp_dict(results_dir) - return {"id": build_id, - "queue_status": status, - "job_created": times.get(TS_CREATED), - "job_started": times.get(TS_STARTED), - "job_finished": times.get(TS_FINISHED), - "compose_type": compose_type, - "blueprint": blueprint["name"], - "version": blueprint["version"], - "image_size": image_size - }
    + detail = {"id": build_id, + "queue_status": status, + "job_created": times.get(TS_CREATED), + "job_started": times.get(TS_STARTED), + "job_finished": times.get(TS_FINISHED), + "compose_type": compose_type, + "blueprint": blueprint["name"], + "version": blueprint["version"], + "image_size": image_size, + } -
    [docs]def queue_status(cfg): + if api == 1: + # Get uploads for this build_id + upload_uuids = uuid_get_uploads(cfg, build_id) + summaries = [upload.summary() for upload in get_uploads(cfg["upload"], upload_uuids)] + detail["uploads"] = summaries + return detail
    + +
    [docs]def queue_status(cfg, api=1): """Return details about what is in the queue. :param cfg: Configuration settings :type cfg: ComposerConfig + :param api: Select which api version of the dict to return (default 1) + :type api: int :returns: A list of the new composes, and a list of the running composes :rtype: dict @@ -525,7 +546,7 @@ new_details = [] for n in new_queue: try: - d = compose_detail(n) + d = compose_detail(cfg, n, api) except IOError: continue new_details.append(d) @@ -533,7 +554,7 @@ run_details = [] for r in run_queue: try: - d = compose_detail(r) + d = compose_detail(cfg, r, api) except IOError: continue run_details.append(d) @@ -543,32 +564,36 @@ "run": run_details }
    -
    [docs]def uuid_status(cfg, uuid): +
    [docs]def uuid_status(cfg, uuid, api=1): """Return the details of a specific UUID compose :param cfg: Configuration settings :type cfg: ComposerConfig :param uuid: The UUID of the build :type uuid: str + :param api: Select which api version of the dict to return (default 1) + :type api: int :returns: Details about the build :rtype: dict or None - Returns the same dict as `compose_details()` + Returns the same dict as `compose_detail()` """ uuid_dir = joinpaths(cfg.get("composer", "lib_dir"), "results", uuid) try: - return compose_detail(uuid_dir) + return compose_detail(cfg, uuid_dir, api) except IOError: return None
    -
    [docs]def build_status(cfg, status_filter=None): +
    [docs]def build_status(cfg, status_filter=None, api=1): """Return the details of finished or failed builds :param cfg: Configuration settings :type cfg: ComposerConfig :param status_filter: What builds to return. None == all, "FINISHED", or "FAILED" :type status_filter: str - :returns: A list of the build details (from compose_details) + :param api: Select which api version of the dict to return (default 1) + :type api: int + :returns: A list of the build details (from compose_detail) :rtype: list of dicts This returns a list of build details for each of the matching builds on the @@ -588,11 +613,132 @@ try: status = open(joinpaths(build, "STATUS"), "r").read().strip() if status in status_filter: - results.append(compose_detail(build)) + results.append(compose_detail(cfg, build, api)) except IOError: pass return results
    +def _upload_list_path(cfg, uuid): + """Return the path to the UPLOADS file + + :param cfg: Configuration settings + :type cfg: ComposerConfig + :param uuid: The UUID of the build + :type uuid: str + :returns: Path to the UPLOADS file listing the build's associated uploads + :rtype: str + :raises: RuntimeError if the uuid is not found + """ + results_dir = joinpaths(cfg.get("composer", "lib_dir"), "results", uuid) + if not os.path.isdir(results_dir): + raise RuntimeError(f'"{uuid}" is not a valid build uuid!') + return joinpaths(results_dir, "UPLOADS") + +
    [docs]def uuid_schedule_upload(cfg, uuid, provider_name, image_name, settings): + """Schedule an upload of an image + + :param cfg: Configuration settings + :type cfg: ComposerConfig + :param uuid: The UUID of the build + :type uuid: str + :param provider_name: The name of the cloud provider, e.g. "azure" + :type provider_name: str + :param image_name: Path of the image to upload + :type image_name: str + :param settings: Settings to use for the selected provider + :type settings: dict + :returns: uuid of the upload + :rtype: str + :raises: RuntimeError if the uuid is not a valid build uuid + """ + status = uuid_status(cfg, uuid) + if status is None: + raise RuntimeError(f'"{uuid}" is not a valid build uuid!') + + upload = create_upload(cfg["upload"], provider_name, image_name, settings) + uuid_add_upload(cfg, uuid, upload.uuid) + return upload.uuid
    + +
    [docs]def uuid_get_uploads(cfg, uuid): + """Return the list of uploads for a build uuid + + :param cfg: Configuration settings + :type cfg: ComposerConfig + :param uuid: The UUID of the build + :type uuid: str + :returns: The upload UUIDs associated with the build UUID + :rtype: frozenset + """ + try: + with open(_upload_list_path(cfg, uuid)) as uploads_file: + return frozenset(uploads_file.read().split()) + except FileNotFoundError: + return frozenset()
    + +
    [docs]def uuid_add_upload(cfg, uuid, upload_uuid): + """Add an upload UUID to a build + + :param cfg: Configuration settings + :type cfg: ComposerConfig + :param uuid: The UUID of the build + :type uuid: str + :param upload_uuid: The UUID of the upload + :type upload_uuid: str + :returns: None + :rtype: None + """ + if upload_uuid not in uuid_get_uploads(cfg, uuid): + with open(_upload_list_path(cfg, uuid), "a") as uploads_file: + print(upload_uuid, file=uploads_file) + status = uuid_status(cfg, uuid) + if status and status["queue_status"] == "FINISHED": + uuid_ready_upload(cfg, uuid, upload_uuid)
    + +
    [docs]def uuid_remove_upload(cfg, upload_uuid): + """Remove an upload UUID from the build + + :param cfg: Configuration settings + :type cfg: ComposerConfig + :param upload_uuid: The UUID of the upload + :type upload_uuid: str + :returns: None + :rtype: None + :raises: RuntimeError if the upload_uuid is not found + """ + for build_uuid in (os.path.basename(b) for b in glob(joinpaths(cfg.get("composer", "lib_dir"), "results/*"))): + uploads = uuid_get_uploads(cfg, build_uuid) + if upload_uuid not in uploads: + continue + + uploads = uploads - frozenset((upload_uuid,)) + with open(_upload_list_path(cfg, build_uuid), "w") as uploads_file: + for upload in uploads: + print(upload, file=uploads_file) + return + + raise RuntimeError(f"{upload_uuid} is not a valid upload id!")
    + +
    [docs]def uuid_ready_upload(cfg, uuid, upload_uuid): + """Set an upload to READY if the build is in FINISHED state + + :param cfg: Configuration settings + :type cfg: ComposerConfig + :param uuid: The UUID of the build + :type uuid: str + :param upload_uuid: The UUID of the upload + :type upload_uuid: str + :returns: None + :rtype: None + :raises: RuntimeError if the build uuid is invalid or not in FINISHED state. + """ + status = uuid_status(cfg, uuid) + if not status: + raise RuntimeError(f"{uuid} is not a valid build id!") + if status["queue_status"] != "FINISHED": + raise RuntimeError(f"Build {uuid} is not finished!") + _, image_path = uuid_image(cfg, uuid) + ready_upload(cfg["upload"], upload_uuid, image_path)
    +
    [docs]def uuid_cancel(cfg, uuid): """Cancel a build and delete its results @@ -668,10 +814,14 @@ uuid_dir = joinpaths(cfg.get("composer", "lib_dir"), "results", uuid) if not uuid_dir or len(uuid_dir) < 10: raise RuntimeError("Directory length is too short: %s" % uuid_dir) + + for upload in get_uploads(cfg["upload"], uuid_get_uploads(cfg, uuid)): + delete_upload(cfg["upload"], upload.uuid) + shutil.rmtree(uuid_dir) return True
    -
    [docs]def uuid_info(cfg, uuid): +
    [docs]def uuid_info(cfg, uuid, api=1): """Return information about the composition :param cfg: Configuration settings @@ -712,22 +862,27 @@ raise RuntimeError("Missing deps.toml for %s" % uuid) deps_dict = toml.loads(open(deps_path, "r").read()) - details = compose_detail(uuid_dir) + details = compose_detail(cfg, uuid_dir, api) commit_path = joinpaths(uuid_dir, "COMMIT") if not os.path.exists(commit_path): raise RuntimeError("Missing commit hash for %s" % uuid) commit_id = open(commit_path, "r").read().strip() - return {"id": uuid, + info = {"id": uuid, "config": cfg_dict, "blueprint": frozen_dict, "commit": commit_id, "deps": deps_dict, "compose_type": details["compose_type"], "queue_status": details["queue_status"], - "image_size": details["image_size"] - }
    + "image_size": details["image_size"], + } + if api == 1: + upload_uuids = uuid_get_uploads(cfg, uuid) + summaries = [upload.summary() for upload in get_uploads(cfg["upload"], upload_uuids)] + info["uploads"] = summaries + return info
    [docs]def uuid_tar(cfg, uuid, metadata=False, image=False, logs=False): """Return a tar of the build data diff --git a/docs/html/_modules/pylorax/api/recipes.html b/docs/html/_modules/pylorax/api/recipes.html index 14e6bac0..99bc4eaa 100644 --- a/docs/html/_modules/pylorax/api/recipes.html +++ b/docs/html/_modules/pylorax/api/recipes.html @@ -8,7 +8,7 @@ - pylorax.api.recipes — Lorax 31.9 documentation + pylorax.api.recipes — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    @@ -999,7 +999,7 @@ # Skip files with errors, but try the others try: commit_recipe_file(repo, branch, joinpaths(directory, f)) - except (RecipeFileError, toml.TomlError): + except (RecipeError, RecipeFileError, toml.TomlError): pass
    [docs]def tag_recipe_commit(repo, branch, recipe_name): diff --git a/docs/html/_modules/pylorax/api/server.html b/docs/html/_modules/pylorax/api/server.html index 1846b11d..22f26e3f 100644 --- a/docs/html/_modules/pylorax/api/server.html +++ b/docs/html/_modules/pylorax/api/server.html @@ -8,7 +8,7 @@ - pylorax.api.server — Lorax 31.9 documentation + pylorax.api.server — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    @@ -184,6 +184,7 @@ from pylorax import vernum from pylorax.api.errors import HTTP_ERROR from pylorax.api.v0 import v0_api +from pylorax.api.v1 import v1_api from pylorax.sysutils import joinpaths GitLock = namedtuple("GitLock", ["repo", "lock", "dir"]) @@ -211,9 +212,9 @@ return send_from_directory(docs_path, path) @server.route("/api/status") -def v0_status(): +def api_status(): """ - `/api/v0/status` + `/api/status` ^^^^^^^^^^^^^^^^ Return the status of the API Server:: @@ -231,7 +232,7 @@ """ return jsonify(backend="lorax-composer", build=vernum, - api="0", + api="1", db_version="0", schema_version="0", db_supported=True, @@ -243,6 +244,21 @@ # Register the v0 API on /api/v0/ server.register_blueprint(v0_api, url_prefix="/api/v0/") + +# Register the v1 API on /api/v1/ +# Use v0 routes by default +skip_rules = [ + "/compose", + "/compose/queue", + "/compose/finished", + "/compose/failed", + "/compose/status/<uuids>", + "/compose/info/<uuid>", + "/projects/source/info/<source_names>", + "/projects/source/new", +] +server.register_blueprint(v0_api, url_prefix="/api/v1/", skip_rules=skip_rules) +server.register_blueprint(v1_api, url_prefix="/api/v1/")
    diff --git a/docs/html/_modules/pylorax/api/timestamp.html b/docs/html/_modules/pylorax/api/timestamp.html index a8909dd8..0d0a8bb6 100644 --- a/docs/html/_modules/pylorax/api/timestamp.html +++ b/docs/html/_modules/pylorax/api/timestamp.html @@ -8,7 +8,7 @@ - pylorax.api.timestamp — Lorax 31.9 documentation + pylorax.api.timestamp — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/api/toml.html b/docs/html/_modules/pylorax/api/toml.html index 2d94f9f0..50445141 100644 --- a/docs/html/_modules/pylorax/api/toml.html +++ b/docs/html/_modules/pylorax/api/toml.html @@ -8,7 +8,7 @@ - pylorax.api.toml — Lorax 31.9 documentation + pylorax.api.toml — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    @@ -189,6 +189,15 @@
    [docs]def dumps(o): # strip the result, because `toml.dumps` adds a lot of newlines return toml.dumps(o, encoder=toml.TomlEncoder(dict)).strip()
    + +
    [docs]def load(file): + try: + return toml.load(file) + except toml.TomlDecodeError as e: + raise TomlError(e.msg, e.doc, e.pos)
    + +
    [docs]def dump(o, file): + return toml.dump(o, file)
    diff --git a/docs/html/_modules/pylorax/api/utils.html b/docs/html/_modules/pylorax/api/utils.html new file mode 100644 index 00000000..85e7c0b0 --- /dev/null +++ b/docs/html/_modules/pylorax/api/utils.html @@ -0,0 +1,249 @@ + + + + + + + + + + + pylorax.api.utils — Lorax 32.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + +
    + +
    + + + + + + + + + + + + + + + + + +
    + + + + +
    +
    +
    +
    + +

    Source code for pylorax.api.utils

    +#
    +# Copyright (C) 2019  Red Hat, Inc.
    +#
    +# This program is free software; you can redistribute it and/or modify
    +# it under the terms of the GNU General Public License as published by
    +# the Free Software Foundation; either version 2 of the License, or
    +# (at your option) any later version.
    +#
    +# This program is distributed in the hope that it will be useful,
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    +# GNU General Public License for more details.
    +#
    +# You should have received a copy of the GNU General Public License
    +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    +""" API utility functions
    +"""
    +from pylorax.api.recipes import RecipeError, RecipeFileError, read_recipe_commit
    +
    +
    [docs]def take_limits(iterable, offset, limit): + """ Apply offset and limit to an iterable object + + :param iterable: The object to limit + :type iterable: iter + :param offset: The number of items to skip + :type offset: int + :param limit: The total number of items to return + :type limit: int + :returns: A subset of the iterable + """ + return iterable[offset:][:limit]
    + +
    [docs]def blueprint_exists(api, branch, blueprint_name): + """Return True if the blueprint exists + + :param api: flask object + :type api: Flask + :param branch: Branch name + :type branch: str + :param recipe_name: Recipe name to read + :type recipe_name: str + """ + try: + with api.config["GITLOCK"].lock: + read_recipe_commit(api.config["GITLOCK"].repo, branch, blueprint_name) + + return True + except (RecipeError, RecipeFileError): + return False
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/html/_modules/pylorax/api/v0.html b/docs/html/_modules/pylorax/api/v0.html index cda9f971..3bb43a68 100644 --- a/docs/html/_modules/pylorax/api/v0.html +++ b/docs/html/_modules/pylorax/api/v0.html @@ -8,7 +8,7 @@ - pylorax.api.v0 — Lorax 31.9 documentation + pylorax.api.v0 — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    @@ -216,54 +216,24 @@ from pylorax.sysutils import joinpaths from pylorax.api.checkparams import checkparams from pylorax.api.compose import start_build, compose_types -from pylorax.api.errors import * # pylint: disable=wildcard-import +from pylorax.api.errors import * # pylint: disable=wildcard-import,unused-wildcard-import from pylorax.api.flask_blueprint import BlueprintSkip from pylorax.api.projects import projects_list, projects_info, projects_depsolve from pylorax.api.projects import modules_list, modules_info, ProjectsError, repo_to_source -from pylorax.api.projects import get_repo_sources, delete_repo_source, source_to_repo, dnf_repo_to_file_repo +from pylorax.api.projects import get_repo_sources, delete_repo_source, new_repo_source from pylorax.api.queue import queue_status, build_status, uuid_delete, uuid_status, uuid_info from pylorax.api.queue import uuid_tar, uuid_image, uuid_cancel, uuid_log -from pylorax.api.recipes import RecipeError, list_branch_files, read_recipe_commit, recipe_filename, list_commits +from pylorax.api.recipes import list_branch_files, read_recipe_commit, recipe_filename, list_commits from pylorax.api.recipes import recipe_from_dict, recipe_from_toml, commit_recipe, delete_recipe, revert_recipe from pylorax.api.recipes import tag_recipe_commit, recipe_diff, RecipeFileError from pylorax.api.regexes import VALID_API_STRING, VALID_BLUEPRINT_NAME import pylorax.api.toml as toml +from pylorax.api.utils import take_limits, blueprint_exists from pylorax.api.workspace import workspace_read, workspace_write, workspace_delete # The API functions don't actually get called by any code here # pylint: disable=unused-variable -
    [docs]def take_limits(iterable, offset, limit): - """ Apply offset and limit to an iterable object - - :param iterable: The object to limit - :type iterable: iter - :param offset: The number of items to skip - :type offset: int - :param limit: The total number of items to return - :type limit: int - :returns: A subset of the iterable - """ - return iterable[offset:][:limit]
    - -
    [docs]def blueprint_exists(branch, blueprint_name): - """Return True if the blueprint exists - - :param api: flask object - :type api: Flask - :param branch: Branch name - :type branch: str - :param recipe_name: Recipe name to read - :type recipe_name: str - """ - try: - with api.config["GITLOCK"].lock: - read_recipe_commit(api.config["GITLOCK"].repo, branch, blueprint_name) - - return True - except (RecipeError, RecipeFileError): - return False
    - # Create the v0 routes Blueprint with skip_routes support v0_api = BlueprintSkip("v0_routes", __name__) @@ -811,7 +781,7 @@ if VALID_API_STRING.match(branch) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in branch argument"}]), 400 - if not blueprint_exists(branch, blueprint_name): + if not blueprint_exists(api, branch, blueprint_name): return jsonify(status=False, errors=[{"id": UNKNOWN_BLUEPRINT, "msg": "Unknown blueprint name: %s" % blueprint_name}]) try: @@ -1332,7 +1302,7 @@ if not repo: errors.append({"id": UNKNOWN_SOURCE, "msg": "%s is not a valid source" % source}) continue - sources[repo.id] = repo_to_source(repo, repo.id in system_sources) + sources[repo.id] = repo_to_source(repo, repo.id in system_sources, api=0) if out_fmt == "toml" and not errors: # With TOML output we just want to dump the raw sources, skipping the errors @@ -1394,46 +1364,9 @@ try: # Remove it from the RepoDict (NOTE that this isn't explicitly supported by the DNF API) with api.config["DNFLOCK"].lock: - dbo = api.config["DNFLOCK"].dbo - # If this repo already exists, delete it and replace it with the new one - repos = list(r.id for r in dbo.repos.iter_enabled()) - if source["name"] in repos: - del dbo.repos[source["name"]] - - repo = source_to_repo(source, dbo.conf) - dbo.repos.add(repo) - - log.info("Updating repository metadata after adding %s", source["name"]) - dbo.fill_sack(load_system_repo=False) - dbo.read_comps() - - # Write the new repo to disk, replacing any existing ones - repo_dir = api.config["COMPOSER_CFG"].get("composer", "repo_dir") - - # Remove any previous sources with this name, ignore it if it isn't found - try: - delete_repo_source(joinpaths(repo_dir, "*.repo"), source["name"]) - except ProjectsError: - pass - - # Make sure the source name can't contain a path traversal by taking the basename - source_path = joinpaths(repo_dir, os.path.basename("%s.repo" % source["name"])) - with open(source_path, "w") as f: - f.write(dnf_repo_to_file_repo(repo)) + repo_dir = api.config["COMPOSER_CFG"].get("composer", "repo_dir") + new_repo_source(api.config["DNFLOCK"].dbo, source["name"], source, repo_dir) except Exception as e: - log.error("(v0_projects_source_add) adding %s failed: %s", source["name"], str(e)) - - # Cleanup the mess, if loading it failed we don't want to leave it in memory - repos = list(r.id for r in dbo.repos.iter_enabled()) - if source["name"] in repos: - with api.config["DNFLOCK"].lock: - dbo = api.config["DNFLOCK"].dbo - del dbo.repos[source["name"]] - - log.info("Updating repository metadata after adding %s failed", source["name"]) - dbo.fill_sack(load_system_repo=False) - dbo.read_comps() - return jsonify(status=False, errors=[{"id": PROJECTS_ERROR, "msg": str(e)}]), 400 return jsonify(status=True) @@ -1681,7 +1614,7 @@ if VALID_BLUEPRINT_NAME.match(blueprint_name) is None: errors.append({"id": INVALID_CHARS, "msg": "Invalid characters in API path"}) - if not blueprint_exists(branch, blueprint_name): + if not blueprint_exists(api, branch, blueprint_name): errors.append({"id": UNKNOWN_BLUEPRINT, "msg": "Unknown blueprint name: %s" % blueprint_name}) if errors: @@ -1762,7 +1695,7 @@ ] } """ - return jsonify(queue_status(api.config["COMPOSER_CFG"])) + return jsonify(queue_status(api.config["COMPOSER_CFG"], api=0))
    [docs]@v0_api.route("/compose/finished") def v0_compose_finished(): @@ -1797,7 +1730,7 @@ ] } """ - return jsonify(finished=build_status(api.config["COMPOSER_CFG"], "FINISHED"))
    + return jsonify(finished=build_status(api.config["COMPOSER_CFG"], "FINISHED", api=0))
    [docs]@v0_api.route("/compose/failed") def v0_compose_failed(): @@ -1823,7 +1756,7 @@ ] } """ - return jsonify(failed=build_status(api.config["COMPOSER_CFG"], "FAILED"))
    + return jsonify(failed=build_status(api.config["COMPOSER_CFG"], "FAILED", api=0))
    [docs]@v0_api.route("/compose/status", defaults={'uuids': ""}) @v0_api.route("/compose/status/<uuids>") @@ -1872,14 +1805,14 @@ errors = [] if uuids.strip() == '*': - queue_status_dict = queue_status(api.config["COMPOSER_CFG"]) + queue_status_dict = queue_status(api.config["COMPOSER_CFG"], api=0) queue_new = queue_status_dict["new"] queue_running = queue_status_dict["run"] - candidates = queue_new + queue_running + build_status(api.config["COMPOSER_CFG"]) + candidates = queue_new + queue_running + build_status(api.config["COMPOSER_CFG"], api=0) else: candidates = [] for uuid in [n.strip().lower() for n in uuids.split(",")]: - details = uuid_status(api.config["COMPOSER_CFG"], uuid) + details = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if details is None: errors.append({"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}) else: @@ -1920,7 +1853,7 @@ if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 @@ -1962,7 +1895,7 @@ results = [] errors = [] for uuid in [n.strip().lower() for n in uuids.split(",")]: - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: errors.append({"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}) elif status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -2031,7 +1964,7 @@ return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 try: - info = uuid_info(api.config["COMPOSER_CFG"], uuid) + info = uuid_info(api.config["COMPOSER_CFG"], uuid, api=0) except Exception as e: return jsonify(status=False, errors=[{"id": COMPOSE_ERROR, "msg": str(e)}]), 400 @@ -2060,7 +1993,7 @@ if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 if status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -2090,7 +2023,7 @@ if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 elif status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -2118,7 +2051,7 @@ if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 elif status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -2143,7 +2076,7 @@ if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 elif status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -2200,7 +2133,7 @@ except ValueError as e: return jsonify(status=False, errors=[{"id": COMPOSE_ERROR, "msg": str(e)}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 elif status["queue_status"] == "WAITING": diff --git a/docs/html/_modules/pylorax/api/v1.html b/docs/html/_modules/pylorax/api/v1.html new file mode 100644 index 00000000..69649bce --- /dev/null +++ b/docs/html/_modules/pylorax/api/v1.html @@ -0,0 +1,1242 @@ + + + + + + + + + + + pylorax.api.v1 — Lorax 32.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + +
    + +
    + + + + + + + + + + + + + + + + + +
    + + + + +
    +
    +
    +
    + +

    Source code for pylorax.api.v1

    +#
    +# Copyright (C) 2019  Red Hat, Inc.
    +#
    +# This program is free software; you can redistribute it and/or modify
    +# it under the terms of the GNU General Public License as published by
    +# the Free Software Foundation; either version 2 of the License, or
    +# (at your option) any later version.
    +#
    +# This program is distributed in the hope that it will be useful,
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    +# GNU General Public License for more details.
    +#
    +# You should have received a copy of the GNU General Public License
    +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    +#
    +""" Setup v1 of the API server
    +
    +"""
    +import logging
    +log = logging.getLogger("lorax-composer")
    +
    +from flask import jsonify, request
    +from flask import current_app as api
    +
    +from lifted.queue import get_upload, reset_upload, cancel_upload, delete_upload
    +from lifted.providers import list_providers, resolve_provider, load_profiles, validate_settings, save_settings
    +from lifted.providers import load_settings, delete_profile
    +from pylorax.api.checkparams import checkparams
    +from pylorax.api.compose import start_build
    +from pylorax.api.errors import BAD_COMPOSE_TYPE, BUILD_FAILED, INVALID_CHARS, MISSING_POST, PROJECTS_ERROR
    +from pylorax.api.errors import SYSTEM_SOURCE, UNKNOWN_BLUEPRINT, UNKNOWN_SOURCE, UNKNOWN_UUID, UPLOAD_ERROR
    +from pylorax.api.errors import COMPOSE_ERROR
    +from pylorax.api.flask_blueprint import BlueprintSkip
    +from pylorax.api.queue import queue_status, build_status, uuid_status, uuid_schedule_upload, uuid_remove_upload
    +from pylorax.api.queue import uuid_info
    +from pylorax.api.projects import get_repo_sources, repo_to_source
    +from pylorax.api.projects import new_repo_source
    +from pylorax.api.regexes import VALID_API_STRING, VALID_BLUEPRINT_NAME
    +import pylorax.api.toml as toml
    +from pylorax.api.utils import blueprint_exists
    +
    +
    +# Create the v1 routes Blueprint with skip_routes support
    +v1_api = BlueprintSkip("v1_routes", __name__)
    +
    +
    [docs]@v1_api.route("/projects/source/info", defaults={'source_ids': ""}) +@v1_api.route("/projects/source/info/<source_ids>") +@checkparams([("source_ids", "", "no source names given")]) +def v1_projects_source_info(source_ids): + """Return detailed info about the list of sources + + **/api/v1/projects/source/info/<source-ids>** + + Return information about the comma-separated list of source ids. Or all of the + sources if '*' is passed. Note that general globbing is not supported, only '*'. + + Immutable system sources will have the "system" field set to true. User added sources + will have it set to false. System sources cannot be changed or deleted. + + Example:: + + { + "errors": [], + "sources": { + "fedora": { + "check_gpg": true, + "check_ssl": true, + "gpgkey_urls": [ + "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-28-x86_64" + ], + "id": "fedora", + "name": "Fedora $releasever - $basearch", + "proxy": "http://proxy.brianlane.com:8123", + "system": true, + "type": "yum-metalink", + "url": "https://mirrors.fedoraproject.org/metalink?repo=fedora-28&arch=x86_64" + } + } + } + + In v0 the ``name`` field was used for the id (a short name for the repo). In v1 ``name`` changed + to ``id`` and ``name`` is now used for the longer descriptive name of the repository. + """ + if VALID_API_STRING.match(source_ids) is None: + return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 + + out_fmt = request.args.get("format", "json") + if VALID_API_STRING.match(out_fmt) is None: + return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in format argument"}]), 400 + + # Return info on all of the sources + if source_ids == "*": + with api.config["DNFLOCK"].lock: + source_ids = ",".join(r.id for r in api.config["DNFLOCK"].dbo.repos.iter_enabled()) + + sources = {} + errors = [] + system_sources = get_repo_sources("/etc/yum.repos.d/*.repo") + for source in source_ids.split(","): + with api.config["DNFLOCK"].lock: + repo = api.config["DNFLOCK"].dbo.repos.get(source, None) + if not repo: + errors.append({"id": UNKNOWN_SOURCE, "msg": "%s is not a valid source" % source}) + continue + sources[repo.id] = repo_to_source(repo, repo.id in system_sources, api=1) + + if out_fmt == "toml" and not errors: + # With TOML output we just want to dump the raw sources, skipping the errors + return toml.dumps(sources) + elif out_fmt == "toml" and errors: + # TOML requested, but there was an error + return jsonify(status=False, errors=errors), 400 + else: + return jsonify(sources=sources, errors=errors)
    + +
    [docs]@v1_api.route("/projects/source/new", methods=["POST"]) +def v1_projects_source_new(): + """Add a new package source. Or change an existing one + + **POST /api/v1/projects/source/new** + + Add (or change) a source for use when depsolving blueprints and composing images. + + The ``proxy`` and ``gpgkey_urls`` entries are optional. All of the others are required. The supported + types for the urls are: + + * ``yum-baseurl`` is a URL to a yum repository. + * ``yum-mirrorlist`` is a URL for a mirrorlist. + * ``yum-metalink`` is a URL for a metalink. + + If ``check_ssl`` is true the https certificates must be valid. If they are self-signed you can either set + this to false, or add your Certificate Authority to the host system. + + If ``check_gpg`` is true the GPG key must either be installed on the host system, or ``gpgkey_urls`` + should point to it. + + You can edit an existing source (other than system sources), by doing a POST + of the new version of the source. It will overwrite the previous one. + + Example:: + + { + "id": "custom-source-1", + "name": "Custom Package Source #1", + "url": "https://url/path/to/repository/", + "type": "yum-baseurl", + "check_ssl": true, + "check_gpg": true, + "gpgkey_urls": [ + "https://url/path/to/gpg-key" + ] + } + + In v0 the ``name`` field was used for the id (a short name for the repo). In v1 ``name`` changed + to ``id`` and ``name`` is now used for the longer descriptive name of the repository. + """ + if request.headers['Content-Type'] == "text/x-toml": + source = toml.loads(request.data) + else: + source = request.get_json(cache=False) + + # Check for id in source, return error if not + if "id" not in source: + return jsonify(status=False, errors=[{"id": UNKNOWN_SOURCE, "msg": "'id' field is missing from API v1 request."}]), 400 + + system_sources = get_repo_sources("/etc/yum.repos.d/*.repo") + if source["id"] in system_sources: + return jsonify(status=False, errors=[{"id": SYSTEM_SOURCE, "msg": "%s is a system source, it cannot be changed." % source["id"]}]), 400 + + try: + # Remove it from the RepoDict (NOTE that this isn't explicitly supported by the DNF API) + with api.config["DNFLOCK"].lock: + repo_dir = api.config["COMPOSER_CFG"].get("composer", "repo_dir") + new_repo_source(api.config["DNFLOCK"].dbo, source["id"], source, repo_dir) + except Exception as e: + return jsonify(status=False, errors=[{"id": PROJECTS_ERROR, "msg": str(e)}]), 400 + + return jsonify(status=True)
    + +
    [docs]@v1_api.route("/compose", methods=["POST"]) +def v1_compose_start(): + """Start a compose + + The body of the post should have these fields: + blueprint_name - The blueprint name from /blueprints/list/ + compose_type - The type of output to create, from /compose/types + branch - Optional, defaults to master, selects the git branch to use for the blueprint. + + **POST /api/v1/compose** + + Start a compose. The content type should be 'application/json' and the body of the POST + should look like this. The "upload" object is optional. + + The upload object can specify either a pre-existing profile to use (as returned by + `/uploads/providers`) or one-time use settings for the provider. + + Example with upload profile:: + + { + "blueprint_name": "http-server", + "compose_type": "tar", + "branch": "master", + "upload": { + "image_name": "My Image", + "provider": "azure", + "profile": "production-azure-settings" + } + } + + Example with upload settings:: + + { + "blueprint_name": "http-server", + "compose_type": "tar", + "branch": "master", + "upload": { + "image_name": "My Image", + "provider": "azure", + "settings": { + "resource_group": "SOMEBODY", + "storage_account_name": "ONCE", + "storage_container": "TOLD", + "location": "ME", + "subscription_id": "THE", + "client_id": "WORLD", + "secret": "IS", + "tenant": "GONNA" + } + } + } + + Pass it the name of the blueprint, the type of output (from + '/api/v1/compose/types'), and the blueprint branch to use. 'branch' is + optional and will default to master. It will create a new build and add + it to the queue. It returns the build uuid and a status if it succeeds. + If an "upload" is given, it will schedule an upload to run when the build + finishes. + + Example response:: + + { + "build_id": "e6fa6db4-9c81-4b70-870f-a697ca405cdf", + "upload_id": "572eb0d0-5348-4600-9666-14526ba628bb", + "status": true + } + """ + # Passing ?test=1 will generate a fake FAILED compose. + # Passing ?test=2 will generate a fake FINISHED compose. + try: + test_mode = int(request.args.get("test", "0")) + except ValueError: + test_mode = 0 + + compose = request.get_json(cache=False) + + errors = [] + if not compose: + return jsonify(status=False, errors=[{"id": MISSING_POST, "msg": "Missing POST body"}]), 400 + + if "blueprint_name" not in compose: + errors.append({"id": UNKNOWN_BLUEPRINT, "msg": "No 'blueprint_name' in the JSON request"}) + else: + blueprint_name = compose["blueprint_name"] + + if "branch" not in compose or not compose["branch"]: + branch = "master" + else: + branch = compose["branch"] + + if "compose_type" not in compose: + errors.append({"id": BAD_COMPOSE_TYPE, "msg": "No 'compose_type' in the JSON request"}) + else: + compose_type = compose["compose_type"] + + if VALID_BLUEPRINT_NAME.match(blueprint_name) is None: + errors.append({"id": INVALID_CHARS, "msg": "Invalid characters in API path"}) + + if not blueprint_exists(api, branch, blueprint_name): + errors.append({"id": UNKNOWN_BLUEPRINT, "msg": "Unknown blueprint name: %s" % blueprint_name}) + + if "upload" in compose: + try: + image_name = compose["upload"]["image_name"] + + if "profile" in compose["upload"]: + # Load a specific profile for this provider + profile = compose["upload"]["profile"] + provider_name = compose["upload"]["provider"] + settings = load_settings(api.config["COMPOSER_CFG"]["upload"], provider_name, profile) + else: + provider_name = compose["upload"]["provider"] + settings = compose["upload"]["settings"] + except KeyError as e: + errors.append({"id": UPLOAD_ERROR, "msg": f'Missing parameter {str(e)}!'}) + try: + provider = resolve_provider(api.config["COMPOSER_CFG"]["upload"], provider_name) + if "supported_types" in provider and compose_type not in provider["supported_types"]: + raise RuntimeError(f'Type "{compose_type}" is not supported by provider "{provider_name}"!') + validate_settings(api.config["COMPOSER_CFG"]["upload"], provider_name, settings, image_name) + except Exception as e: + errors.append({"id": UPLOAD_ERROR, "msg": str(e)}) + + if errors: + return jsonify(status=False, errors=errors), 400 + + try: + build_id = start_build(api.config["COMPOSER_CFG"], api.config["DNFLOCK"], api.config["GITLOCK"], + branch, blueprint_name, compose_type, test_mode) + except Exception as e: + if "Invalid compose type" in str(e): + return jsonify(status=False, errors=[{"id": BAD_COMPOSE_TYPE, "msg": str(e)}]), 400 + else: + return jsonify(status=False, errors=[{"id": BUILD_FAILED, "msg": str(e)}]), 400 + + if "upload" in compose: + upload_id = uuid_schedule_upload( + api.config["COMPOSER_CFG"], + build_id, + provider_name, + image_name, + settings + ) + else: + upload_id = "" + + return jsonify(status=True, build_id=build_id, upload_id=upload_id)
    + +
    [docs]@v1_api.route("/compose/queue") +def v1_compose_queue(): + """Return the status of the new and running queues + + **/api/v1/compose/queue** + + Return the status of the build queue. It includes information about the builds waiting, + and the build that is running. + + Example:: + + { + "new": [ + { + "id": "45502a6d-06e8-48a5-a215-2b4174b3614b", + "blueprint": "glusterfs", + "queue_status": "WAITING", + "job_created": 1517362647.4570868, + "version": "0.0.6" + }, + { + "id": "6d292bd0-bec7-4825-8d7d-41ef9c3e4b73", + "blueprint": "kubernetes", + "queue_status": "WAITING", + "job_created": 1517362659.0034983, + "version": "0.0.1" + } + ], + "run": [ + { + "id": "745712b2-96db-44c0-8014-fe925c35e795", + "blueprint": "glusterfs", + "queue_status": "RUNNING", + "job_created": 1517362633.7965999, + "job_started": 1517362633.8001345, + "version": "0.0.6", + "uploads": [ + { + "creation_time": 1568150660.524401, + "image_name": "glusterfs server", + "image_path": null, + "provider_name": "azure", + "settings": { + "client_id": "need", + "location": "need", + "resource_group": "group", + "secret": "need", + "storage_account_name": "need", + "storage_container": "need", + "subscription_id": "need", + "tenant": "need" + }, + "status": "WAITING", + "uuid": "21898dfd-9ac9-4e22-bb1d-7f12d0129e65" + } + ] + } + ] + } + """ + return jsonify(queue_status(api.config["COMPOSER_CFG"], api=1))
    + +
    [docs]@v1_api.route("/compose/finished") +def v1_compose_finished(): + """Return the list of finished composes + + **/api/v1/compose/finished** + + Return the details on all of the finished composes on the system. + + Example:: + + { + "finished": [ + { + "id": "70b84195-9817-4b8a-af92-45e380f39894", + "blueprint": "glusterfs", + "queue_status": "FINISHED", + "job_created": 1517351003.8210032, + "job_started": 1517351003.8230415, + "job_finished": 1517359234.1003145, + "version": "0.0.6" + }, + { + "id": "e695affd-397f-4af9-9022-add2636e7459", + "blueprint": "glusterfs", + "queue_status": "FINISHED", + "job_created": 1517362289.7193348, + "job_started": 1517362289.9751132, + "job_finished": 1517363500.1234567, + "version": "0.0.6", + "uploads": [ + { + "creation_time": 1568150660.524401, + "image_name": "glusterfs server", + "image_path": "/var/lib/lorax/composer/results/e695affd-397f-4af9-9022-add2636e7459/disk.vhd", + "provider_name": "azure", + "settings": { + "client_id": "need", + "location": "need", + "resource_group": "group", + "secret": "need", + "storage_account_name": "need", + "storage_container": "need", + "subscription_id": "need", + "tenant": "need" + }, + "status": "WAITING", + "uuid": "21898dfd-9ac9-4e22-bb1d-7f12d0129e65" + } + ] + } + ] + } + """ + return jsonify(finished=build_status(api.config["COMPOSER_CFG"], "FINISHED", api=1))
    + +
    [docs]@v1_api.route("/compose/failed") +def v1_compose_failed(): + """Return the list of failed composes + + **/api/v1/compose/failed** + + Return the details on all of the failed composes on the system. + + Example:: + + { + "failed": [ + { + "id": "8c8435ef-d6bd-4c68-9bf1-a2ef832e6b1a", + "blueprint": "http-server", + "queue_status": "FAILED", + "job_created": 1517523249.9301329, + "job_started": 1517523249.9314211, + "job_finished": 1517523255.5623411, + "version": "0.0.2", + "uploads": [ + { + "creation_time": 1568150660.524401, + "image_name": "http-server", + "image_path": null, + "provider_name": "azure", + "settings": { + "client_id": "need", + "location": "need", + "resource_group": "group", + "secret": "need", + "storage_account_name": "need", + "storage_container": "need", + "subscription_id": "need", + "tenant": "need" + }, + "status": "WAITING", + "uuid": "21898dfd-9ac9-4e22-bb1d-7f12d0129e65" + } + ] + } + ] + } + """ + return jsonify(failed=build_status(api.config["COMPOSER_CFG"], "FAILED", api=1))
    + +
    [docs]@v1_api.route("/compose/status", defaults={'uuids': ""}) +@v1_api.route("/compose/status/<uuids>") +@checkparams([("uuids", "", "no UUIDs given")]) +def v1_compose_status(uuids): + """Return the status of the listed uuids + + **/api/v1/compose/status/<uuids>[?blueprint=<blueprint_name>&status=<compose_status>&type=<compose_type>]** + + Return the details for each of the comma-separated list of uuids. A uuid of '*' will return + details for all composes. + + Example:: + + { + "uuids": [ + { + "id": "8c8435ef-d6bd-4c68-9bf1-a2ef832e6b1a", + "blueprint": "http-server", + "queue_status": "FINISHED", + "job_created": 1517523644.2384307, + "job_started": 1517523644.2551234, + "job_finished": 1517523689.9864314, + "version": "0.0.2" + }, + { + "id": "45502a6d-06e8-48a5-a215-2b4174b3614b", + "blueprint": "glusterfs", + "queue_status": "FINISHED", + "job_created": 1517363442.188399, + "job_started": 1517363442.325324, + "job_finished": 1517363451.653621, + "version": "0.0.6", + "uploads": [ + { + "creation_time": 1568150660.524401, + "image_name": "glusterfs server", + "image_path": null, + "provider_name": "azure", + "settings": { + "client_id": "need", + "location": "need", + "resource_group": "group", + "secret": "need", + "storage_account_name": "need", + "storage_container": "need", + "subscription_id": "need", + "tenant": "need" + }, + "status": "WAITING", + "uuid": "21898dfd-9ac9-4e22-bb1d-7f12d0129e65" + } + ] + } + ] + } + """ + if VALID_API_STRING.match(uuids) is None: + return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 + + blueprint = request.args.get("blueprint", None) + status = request.args.get("status", None) + compose_type = request.args.get("type", None) + + results = [] + errors = [] + + if uuids.strip() == '*': + queue_status_dict = queue_status(api.config["COMPOSER_CFG"], api=1) + queue_new = queue_status_dict["new"] + queue_running = queue_status_dict["run"] + candidates = queue_new + queue_running + build_status(api.config["COMPOSER_CFG"], api=1) + else: + candidates = [] + for uuid in [n.strip().lower() for n in uuids.split(",")]: + details = uuid_status(api.config["COMPOSER_CFG"], uuid, api=1) + if details is None: + errors.append({"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}) + else: + candidates.append(details) + + for details in candidates: + if blueprint is not None and details['blueprint'] != blueprint: + continue + + if status is not None and details['queue_status'] != status: + continue + + if compose_type is not None and details['compose_type'] != compose_type: + continue + + results.append(details) + + return jsonify(uuids=results, errors=errors)
    + +
    [docs]@v1_api.route("/compose/info", defaults={'uuid': ""}) +@v1_api.route("/compose/info/<uuid>") +@checkparams([("uuid", "", "no UUID given")]) +def v1_compose_info(uuid): + """Return detailed info about a compose + + **/api/v1/compose/info/<uuid>** + + Get detailed information about the compose. The returned JSON string will + contain the following information: + + * id - The uuid of the comoposition + * config - containing the configuration settings used to run Anaconda + * blueprint - The depsolved blueprint used to generate the kickstart + * commit - The (local) git commit hash for the blueprint used + * deps - The NEVRA of all of the dependencies used in the composition + * compose_type - The type of output generated (tar, iso, etc.) + * queue_status - The final status of the composition (FINISHED or FAILED) + + Example:: + + { + "commit": "7078e521a54b12eae31c3fd028680da7a0815a4d", + "compose_type": "tar", + "config": { + "anaconda_args": "", + "armplatform": "", + "compress_args": [], + "compression": "xz", + "image_name": "root.tar.xz", + ... + }, + "deps": { + "packages": [ + { + "arch": "x86_64", + "epoch": "0", + "name": "acl", + "release": "14.el7", + "version": "2.2.51" + } + ] + }, + "id": "c30b7d80-523b-4a23-ad52-61b799739ce8", + "queue_status": "FINISHED", + "blueprint": { + "description": "An example kubernetes master", + ... + }, + "uploads": [ + { + "creation_time": 1568150660.524401, + "image_name": "glusterfs server", + "image_path": "/var/lib/lorax/composer/results/c30b7d80-523b-4a23-ad52-61b799739ce8/disk.vhd", + "provider_name": "azure", + "settings": { + "client_id": "need", + "location": "need", + "resource_group": "group", + "secret": "need", + "storage_account_name": "need", + "storage_container": "need", + "subscription_id": "need", + "tenant": "need" + }, + "status": "FAILED", + "uuid": "21898dfd-9ac9-4e22-bb1d-7f12d0129e65" + } + ] + } + """ + if VALID_API_STRING.match(uuid) is None: + return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 + + try: + info = uuid_info(api.config["COMPOSER_CFG"], uuid, api=1) + except Exception as e: + return jsonify(status=False, errors=[{"id": COMPOSE_ERROR, "msg": str(e)}]), 400 + + if info is None: + return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 + else: + return jsonify(**info)
    + +
    [docs]@v1_api.route("/compose/uploads/schedule", defaults={'compose_uuid': ""}, methods=["POST"]) +@v1_api.route("/compose/uploads/schedule/<compose_uuid>", methods=["POST"]) +@checkparams([("compose_uuid", "", "no compose UUID given")]) +def v1_compose_uploads_schedule(compose_uuid): + """Schedule an upload of a compose to a given cloud provider + + **POST /api/v1/uploads/schedule/<compose_uuid>** + + The body can specify either a pre-existing profile to use (as returned by + `/uploads/providers`) or one-time use settings for the provider. + + Example with upload profile:: + + { + "image_name": "My Image", + "provider": "azure", + "profile": "production-azure-settings" + } + + Example with upload settings:: + + { + "image_name": "My Image", + "provider": "azure", + "settings": { + "resource_group": "SOMEBODY", + "storage_account_name": "ONCE", + "storage_container": "TOLD", + "location": "ME", + "subscription_id": "THE", + "client_id": "WORLD", + "secret": "IS", + "tenant": "GONNA" + } + } + + Example response:: + + { + "status": true, + "upload_id": "572eb0d0-5348-4600-9666-14526ba628bb" + } + """ + if VALID_API_STRING.match(compose_uuid) is None: + error = {"id": INVALID_CHARS, "msg": "Invalid characters in API path"} + return jsonify(status=False, errors=[error]), 400 + + parsed = request.get_json(cache=False) + if not parsed: + return jsonify(status=False, errors=[{"id": MISSING_POST, "msg": "Missing POST body"}]), 400 + + try: + image_name = parsed["image_name"] + provider_name = parsed["provider"] + if "profile" in parsed: + # Load a specific profile for this provider + profile = parsed["profile"] + settings = load_settings(api.config["COMPOSER_CFG"]["upload"], provider_name, profile) + else: + settings = parsed["settings"] + except KeyError as e: + error = {"id": UPLOAD_ERROR, "msg": f'Missing parameter {str(e)}!'} + return jsonify(status=False, errors=[error]), 400 + try: + compose_type = uuid_status(api.config["COMPOSER_CFG"], compose_uuid)["compose_type"] + provider = resolve_provider(api.config["COMPOSER_CFG"]["upload"], provider_name) + if "supported_types" in provider and compose_type not in provider["supported_types"]: + raise RuntimeError( + f'Type "{compose_type}" is not supported by provider "{provider_name}"!' + ) + except Exception as e: + return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(e)}]), 400 + + try: + upload_id = uuid_schedule_upload( + api.config["COMPOSER_CFG"], + compose_uuid, + provider_name, + image_name, + settings + ) + except RuntimeError as e: + return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(e)}]), 400 + return jsonify(status=True, upload_id=upload_id)
    + +
    [docs]@v1_api.route("/upload/delete", defaults={"upload_uuid": ""}, methods=["DELETE"]) +@v1_api.route("/upload/delete/<upload_uuid>", methods=["DELETE"]) +@checkparams([("upload_uuid", "", "no upload UUID given")]) +def v1_compose_uploads_delete(upload_uuid): + """Delete an upload and disassociate it from its compose + + **DELETE /api/v1/upload/delete/<upload_uuid>** + + Example response:: + + { + "status": true, + "upload_id": "572eb0d0-5348-4600-9666-14526ba628bb" + } + """ + if VALID_API_STRING.match(upload_uuid) is None: + error = {"id": INVALID_CHARS, "msg": "Invalid characters in API path"} + return jsonify(status=False, errors=[error]), 400 + + try: + uuid_remove_upload(api.config["COMPOSER_CFG"], upload_uuid) + delete_upload(api.config["COMPOSER_CFG"]["upload"], upload_uuid) + except RuntimeError as error: + return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(error)}]) + return jsonify(status=True, upload_id=upload_uuid)
    + +
    [docs]@v1_api.route("/upload/info", defaults={"upload_uuid": ""}) +@v1_api.route("/upload/info/<upload_uuid>") +@checkparams([("upload_uuid", "", "no UUID given")]) +def v1_upload_info(upload_uuid): + """Returns information about a given upload + + **GET /api/v1/upload/info/<upload_uuid>** + + Example response:: + + { + "status": true, + "upload": { + "creation_time": 1565620940.069004, + "image_name": "My Image", + "image_path": "/var/lib/lorax/composer/results/b6218e8f-0fa2-48ec-9394-f5c2918544c4/disk.vhd", + "provider_name": "azure", + "settings": { + "resource_group": "SOMEBODY", + "storage_account_name": "ONCE", + "storage_container": "TOLD", + "location": "ME", + "subscription_id": "THE", + "client_id": "WORLD", + "secret": "IS", + "tenant": "GONNA" + }, + "status": "FAILED", + "uuid": "b637c411-9d9d-4279-b067-6c8d38e3b211" + } + } + """ + if VALID_API_STRING.match(upload_uuid) is None: + return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 + + try: + upload = get_upload(api.config["COMPOSER_CFG"]["upload"], upload_uuid).summary() + except RuntimeError as error: + return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(error)}]) + return jsonify(status=True, upload=upload)
    + +
    [docs]@v1_api.route("/upload/log", defaults={"upload_uuid": ""}) +@v1_api.route("/upload/log/<upload_uuid>") +@checkparams([("upload_uuid", "", "no UUID given")]) +def v1_upload_log(upload_uuid): + """Returns an upload's log + + **GET /api/v1/upload/log/<upload_uuid>** + + Example response:: + + { + "status": true, + "upload_id": "b637c411-9d9d-4279-b067-6c8d38e3b211", + "log": "< PLAY [localhost] >..." + } + """ + if VALID_API_STRING.match(upload_uuid) is None: + error = {"id": INVALID_CHARS, "msg": "Invalid characters in API path"} + return jsonify(status=False, errors=[error]), 400 + + try: + upload = get_upload(api.config["COMPOSER_CFG"]["upload"], upload_uuid) + except RuntimeError as error: + return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(error)}]) + return jsonify(status=True, upload_id=upload_uuid, log=upload.upload_log)
    + +
    [docs]@v1_api.route("/upload/reset", defaults={"upload_uuid": ""}, methods=["POST"]) +@v1_api.route("/upload/reset/<upload_uuid>", methods=["POST"]) +@checkparams([("upload_uuid", "", "no UUID given")]) +def v1_upload_reset(upload_uuid): + """Reset an upload so it can be attempted again + + **POST /api/v1/upload/reset/<upload_uuid>** + + Optionally pass in a new image name and/or new settings. + + Example request:: + + { + "image_name": "My renamed image", + "settings": { + "resource_group": "ROLL", + "storage_account_name": "ME", + "storage_container": "I", + "location": "AIN'T", + "subscription_id": "THE", + "client_id": "SHARPEST", + "secret": "TOOL", + "tenant": "IN" + } + } + + Example response:: + + { + "status": true, + "upload_id": "c75d5d62-9d26-42fc-a8ef-18bb14679fc7" + } + """ + if VALID_API_STRING.match(upload_uuid) is None: + error = {"id": INVALID_CHARS, "msg": "Invalid characters in API path"} + return jsonify(status=False, errors=[error]), 400 + + parsed = request.get_json(cache=False) + image_name = parsed.get("image_name") if parsed else None + settings = parsed.get("settings") if parsed else None + + try: + reset_upload(api.config["COMPOSER_CFG"]["upload"], upload_uuid, image_name, settings) + except RuntimeError as error: + return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(error)}]) + return jsonify(status=True, upload_id=upload_uuid)
    + +
    [docs]@v1_api.route("/upload/cancel", defaults={"upload_uuid": ""}, methods=["DELETE"]) +@v1_api.route("/upload/cancel/<upload_uuid>", methods=["DELETE"]) +@checkparams([("upload_uuid", "", "no UUID given")]) +def v1_upload_cancel(upload_uuid): + """Cancel an upload that is either queued or in progress + + **DELETE /api/v1/uploads/cancel/<upload_uuid>** + + Example response:: + + { + "status": true, + "upload_id": "037a3d56-b421-43e9-9935-c98350c89996" + } + """ + if VALID_API_STRING.match(upload_uuid) is None: + error = {"id": INVALID_CHARS, "msg": "Invalid characters in API path"} + return jsonify(status=False, errors=[error]), 400 + + try: + cancel_upload(api.config["COMPOSER_CFG"]["upload"], upload_uuid) + except RuntimeError as error: + return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(error)}]) + return jsonify(status=True, upload_id=upload_uuid)
    + +
    [docs]@v1_api.route("/upload/providers") +def v1_upload_providers(): + """Return the information about all upload providers, including their + display names, expected settings, and saved profiles. Refer to the + `resolve_provider` function. + + **GET /api/v1/upload/providers** + + Example response:: + + { + "providers": { + "azure": { + "display": "Azure", + "profiles": { + "default": { + "client_id": "example", + ... + } + }, + "settings-info": { + "client_id": { + "display": "Client ID", + "placeholder": "", + "regex": "", + "type": "string" + }, + ... + }, + "supported_types": ["vhd"] + }, + ... + } + } + """ + + ucfg = api.config["COMPOSER_CFG"]["upload"] + + provider_names = list_providers(ucfg) + + def get_provider_info(provider_name): + provider = resolve_provider(ucfg, provider_name) + provider["profiles"] = load_profiles(ucfg, provider_name) + return provider + + providers = {provider_name: get_provider_info(provider_name) + for provider_name in provider_names} + return jsonify(status=True, providers=providers)
    + +
    [docs]@v1_api.route("/upload/providers/save", methods=["POST"]) +def v1_providers_save(): + """Save provider settings as a profile for later use + + **POST /api/v1/upload/providers/save** + + Example request:: + + { + "provider": "azure", + "profile": "my-profile", + "settings": { + "resource_group": "SOMEBODY", + "storage_account_name": "ONCE", + "storage_container": "TOLD", + "location": "ME", + "subscription_id": "THE", + "client_id": "WORLD", + "secret": "IS", + "tenant": "GONNA" + } + } + + Saving to an existing profile will overwrite it. + + Example response:: + + { + "status": true + } + """ + parsed = request.get_json(cache=False) + + if parsed is None: + return jsonify(status=False, errors=[{"id": MISSING_POST, "msg": "Missing POST body"}]), 400 + + try: + provider_name = parsed["provider"] + profile = parsed["profile"] + settings = parsed["settings"] + except KeyError as e: + error = {"id": UPLOAD_ERROR, "msg": f'Missing parameter {str(e)}!'} + return jsonify(status=False, errors=[error]), 400 + try: + save_settings(api.config["COMPOSER_CFG"]["upload"], provider_name, profile, settings) + except Exception as e: + error = {"id": UPLOAD_ERROR, "msg": str(e)} + return jsonify(status=False, errors=[error]) + return jsonify(status=True)
    + +
    [docs]@v1_api.route("/upload/providers/delete", defaults={"provider_name": "", "profile": ""}, methods=["DELETE"]) +@v1_api.route("/upload/providers/delete/<provider_name>/<profile>", methods=["DELETE"]) +@checkparams([("provider_name", "", "no provider name given"), ("profile", "", "no profile given")]) +def v1_providers_delete(provider_name, profile): + """Delete a provider's profile settings + + **DELETE /api/v1/upload/providers/delete/<provider_name>/<profile>** + + Example response:: + + { + "status": true + } + """ + if None in (VALID_API_STRING.match(provider_name), VALID_API_STRING.match(profile)): + error = {"id": INVALID_CHARS, "msg": "Invalid characters in API path"} + return jsonify(status=False, errors=[error]), 400 + + try: + delete_profile(api.config["COMPOSER_CFG"]["upload"], provider_name, profile) + except Exception as e: + error = {"id": UPLOAD_ERROR, "msg": str(e)} + return jsonify(status=False, errors=[error]) + return jsonify(status=True)
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/html/_modules/pylorax/api/workspace.html b/docs/html/_modules/pylorax/api/workspace.html index c0b41f5a..26646899 100644 --- a/docs/html/_modules/pylorax/api/workspace.html +++ b/docs/html/_modules/pylorax/api/workspace.html @@ -8,7 +8,7 @@ - pylorax.api.workspace — Lorax 31.9 documentation + pylorax.api.workspace — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/base.html b/docs/html/_modules/pylorax/base.html index 7ef9769a..9631e843 100644 --- a/docs/html/_modules/pylorax/base.html +++ b/docs/html/_modules/pylorax/base.html @@ -8,7 +8,7 @@ - pylorax.base — Lorax 31.9 documentation + pylorax.base — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/buildstamp.html b/docs/html/_modules/pylorax/buildstamp.html index 1f250e51..a4ce6c27 100644 --- a/docs/html/_modules/pylorax/buildstamp.html +++ b/docs/html/_modules/pylorax/buildstamp.html @@ -8,7 +8,7 @@ - pylorax.buildstamp — Lorax 31.9 documentation + pylorax.buildstamp — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/cmdline.html b/docs/html/_modules/pylorax/cmdline.html index 7742fa0a..fe6b1668 100644 --- a/docs/html/_modules/pylorax/cmdline.html +++ b/docs/html/_modules/pylorax/cmdline.html @@ -8,7 +8,7 @@ - pylorax.cmdline — Lorax 31.9 documentation + pylorax.cmdline — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    @@ -463,8 +463,6 @@ parser.add_argument("--volid", default=None, help="volume id") parser.add_argument("--squashfs-only", action="store_true", default=False, help="Use a plain squashfs filesystem for the runtime.") - parser.add_argument("--squashfs_args", - help="additional squashfs args") parser.add_argument("--timeout", default=None, type=int, help="Cancel installer after X minutes") diff --git a/docs/html/_modules/pylorax/creator.html b/docs/html/_modules/pylorax/creator.html index 52e1426c..9748aa91 100644 --- a/docs/html/_modules/pylorax/creator.html +++ b/docs/html/_modules/pylorax/creator.html @@ -8,7 +8,7 @@ - pylorax.creator — Lorax 31.9 documentation + pylorax.creator — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    @@ -206,7 +206,7 @@ # Default parameters for rebuilding initramfs, override with --dracut-args -DRACUT_DEFAULT = ["--xz", "--add", "livenet dmsquash-live convertfs pollcdrom qemu qemu-net", +DRACUT_DEFAULT = ["--xz", "--add", "livenet dmsquash-live dmsquash-live-ntfs convertfs pollcdrom qemu qemu-net", "--omit", "plymouth", "--no-hostonly", "--debug", "--no-early-microcode"] RUNTIME = "images/install.img" @@ -278,8 +278,13 @@ """ compression = opts.compression or "xz" arch = ArchData(opts.arch or os.uname().machine) - if compression == "xz" and arch.bcj: + if compression == "xz" and arch.bcj and not opts.compress_args: + # default to bcj when using xz compressargs = ["-Xbcj", arch.bcj] + elif opts.compress_args: + compressargs = [] + for arg in opts.compress_args: + compressargs += arg.split(" ", 1) else: compressargs = [] return (compression, compressargs)
    diff --git a/docs/html/_modules/pylorax/decorators.html b/docs/html/_modules/pylorax/decorators.html index f5f3faca..d597a669 100644 --- a/docs/html/_modules/pylorax/decorators.html +++ b/docs/html/_modules/pylorax/decorators.html @@ -8,7 +8,7 @@ - pylorax.decorators — Lorax 31.9 documentation + pylorax.decorators — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/discinfo.html b/docs/html/_modules/pylorax/discinfo.html index be6b360f..3e17069e 100644 --- a/docs/html/_modules/pylorax/discinfo.html +++ b/docs/html/_modules/pylorax/discinfo.html @@ -8,7 +8,7 @@ - pylorax.discinfo — Lorax 31.9 documentation + pylorax.discinfo — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/dnfbase.html b/docs/html/_modules/pylorax/dnfbase.html index 140b56fe..0198c933 100644 --- a/docs/html/_modules/pylorax/dnfbase.html +++ b/docs/html/_modules/pylorax/dnfbase.html @@ -8,7 +8,7 @@ - pylorax.dnfbase — Lorax 31.9 documentation + pylorax.dnfbase — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/dnfhelper.html b/docs/html/_modules/pylorax/dnfhelper.html index bc22d1b9..c9053a95 100644 --- a/docs/html/_modules/pylorax/dnfhelper.html +++ b/docs/html/_modules/pylorax/dnfhelper.html @@ -8,7 +8,7 @@ - pylorax.dnfhelper — Lorax 31.9 documentation + pylorax.dnfhelper — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/executils.html b/docs/html/_modules/pylorax/executils.html index 967bdfab..fee98790 100644 --- a/docs/html/_modules/pylorax/executils.html +++ b/docs/html/_modules/pylorax/executils.html @@ -8,7 +8,7 @@ - pylorax.executils — Lorax 31.9 documentation + pylorax.executils — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/imgutils.html b/docs/html/_modules/pylorax/imgutils.html index 9350f261..4e23f367 100644 --- a/docs/html/_modules/pylorax/imgutils.html +++ b/docs/html/_modules/pylorax/imgutils.html @@ -8,7 +8,7 @@ - pylorax.imgutils — Lorax 31.9 documentation + pylorax.imgutils — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/installer.html b/docs/html/_modules/pylorax/installer.html index 0fdb0aaa..ed6fcda2 100644 --- a/docs/html/_modules/pylorax/installer.html +++ b/docs/html/_modules/pylorax/installer.html @@ -8,7 +8,7 @@ - pylorax.installer — Lorax 31.9 documentation + pylorax.installer — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/ltmpl.html b/docs/html/_modules/pylorax/ltmpl.html index 32712ccf..c3cb0d49 100644 --- a/docs/html/_modules/pylorax/ltmpl.html +++ b/docs/html/_modules/pylorax/ltmpl.html @@ -8,7 +8,7 @@ - pylorax.ltmpl — Lorax 31.9 documentation + pylorax.ltmpl — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/monitor.html b/docs/html/_modules/pylorax/monitor.html index c21b45ce..f34be678 100644 --- a/docs/html/_modules/pylorax/monitor.html +++ b/docs/html/_modules/pylorax/monitor.html @@ -8,7 +8,7 @@ - pylorax.monitor — Lorax 31.9 documentation + pylorax.monitor — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/mount.html b/docs/html/_modules/pylorax/mount.html index c34e4d59..eb9d4c9b 100644 --- a/docs/html/_modules/pylorax/mount.html +++ b/docs/html/_modules/pylorax/mount.html @@ -8,7 +8,7 @@ - pylorax.mount — Lorax 31.9 documentation + pylorax.mount — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/sysutils.html b/docs/html/_modules/pylorax/sysutils.html index 789a6bca..55c009ff 100644 --- a/docs/html/_modules/pylorax/sysutils.html +++ b/docs/html/_modules/pylorax/sysutils.html @@ -8,7 +8,7 @@ - pylorax.sysutils — Lorax 31.9 documentation + pylorax.sysutils — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/treebuilder.html b/docs/html/_modules/pylorax/treebuilder.html index 81fc62c4..85409a6d 100644 --- a/docs/html/_modules/pylorax/treebuilder.html +++ b/docs/html/_modules/pylorax/treebuilder.html @@ -8,7 +8,7 @@ - pylorax.treebuilder — Lorax 31.9 documentation + pylorax.treebuilder — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_modules/pylorax/treeinfo.html b/docs/html/_modules/pylorax/treeinfo.html index 08d15fca..cc24cef7 100644 --- a/docs/html/_modules/pylorax/treeinfo.html +++ b/docs/html/_modules/pylorax/treeinfo.html @@ -8,7 +8,7 @@ - pylorax.treeinfo — Lorax 31.9 documentation + pylorax.treeinfo — Lorax 32.1 documentation @@ -58,7 +58,7 @@
    - 31.9 + 32.1
    diff --git a/docs/html/_sources/composer-cli.rst.txt b/docs/html/_sources/composer-cli.rst.txt index 36cc92ec..c5857f32 100644 --- a/docs/html/_sources/composer-cli.rst.txt +++ b/docs/html/_sources/composer-cli.rst.txt @@ -43,6 +43,10 @@ The available types of images is displayed by ``composer-cli compose types``. Currently this consists of: alibaba, ami, ext4-filesystem, google, hyper-v, live-iso, openstack, partitioned-disk, qcow2, tar, vhd, vmdk +You can optionally start an upload of the finished image, see `Image Uploads`_ for +more information. + + Monitor the build status ------------------------ @@ -60,3 +64,115 @@ Downloading the final image is done with ``composer-cli compose image UUID`` and save the qcow2 image as ``UUID-disk.qcow2`` which you can then use to boot a VM like this:: qemu-kvm --name test-image -m 1024 -hda ./UUID-disk.qcow2 + + +Image Uploads +------------- + +``composer-cli`` can upload the images to a number of services, including AWS, +OpenStack, and vSphere. The upload can be started when the build is finished, +by using ``composer-cli compose start ...`` or an existing image can be uploaded +with ``composer-cli upload start ...``. In order to access the service you need +to pass authentication details to composer-cli using a TOML file, or reference +a previously saved profile. + + +Providers +--------- + +Providers are the services providers with Ansible playbook support under +``/usr/share/lorax/lifted/providers/``, you will need to gather some provider +specific information in order to authenticate with it. You can view the +required fields using ``composer-cli providers template ``, eg. for AWS +you would run:: + + composer-cli upload template aws + +The output looks like this:: + + provider = "aws" + + [settings] + aws_access_key = "AWS Access Key" + aws_bucket = "AWS Bucket" + aws_region = "AWS Region" + aws_secret_key = "AWS Secret Key" + +Save this into an ``aws-credentials.toml`` file and use it when running ``start``. + +AWS +^^^ + +The access key and secret key can be created by going to the +``IAM->Users->Security Credentials`` section and creating a new access key. The +secret key will only be shown when it is first created so make sure to record +it in a secure place. The region should be the region that you want to use the +AMI in, and the bucket can be an existing bucket, or a new one, following the +normal AWS bucket naming rules. It will be created if it doesn't already exist. + +When uploading the image it is first uploaded to the s3 bucket, and then +converted to an AMI. If the conversion is successful the s3 object will be +deleted. If it fails, re-trying after correcting the problem will re-use the +object if you have not deleted it in the meantime, speeding up the process. + + +Profiles +-------- + +Profiles store the authentication settings associated with a specific provider. +Providers can have multiple profiles, as long as their names are unique. For +example, you may have one profile for testing and another for production +uploads. + +Profiles are created by pushing the provider settings template to the server using +``composer-cli providers push `` where ``PROFILE.TOML`` is the same as the +provider template, but with the addition of a ``profile`` field. For example, an AWS +profile named ``test-uploads`` would look like this:: + + provider = "aws" + profile = "test-uploads" + + [settings] + aws_access_key = "AWS Access Key" + aws_bucket = "AWS Bucket" + aws_region = "AWS Region" + aws_secret_key = "AWS Secret Key" + +You can view the profile by using ``composer-cli providers aws test-uploads``. + + +Build an image and upload results +--------------------------------- + +If you have a profile named ``test-uploads``:: + + composer-cli compose start example-http-server ami "http image" aws test-uploads + +Or if you have the settings stored in a TOML file:: + + composer-cli compose start example-http-server ami "http image" aws-settings.toml + +It will return the UUID of the image build, and the UUID of the upload. Once +the build has finished successfully it will start the upload process, which you +can monitor with ``composer-cli upload info `` + +You can also view the upload logs from the Ansible playbook with:: + + ``composer-cli upload log `` + +The type of the image must match the type supported by the provider. + + +Upload an existing image +------------------------ + +You can upload previously built images, as long as they are in the ``FINISHED`` state, using ``composer-cli upload start ...```. If you have a profile named ``test-uploads``:: + + composer-cli upload start "http-image" aws test-uploads + +Or if you have the settings stored in a TOML file:: + + composer-cli upload start "http-image" aws-settings.toml + +This will output the UUID of the upload, which can then be used to monitor the status in the same way +described above. diff --git a/docs/html/_sources/composer.cli.rst.txt b/docs/html/_sources/composer.cli.rst.txt index 50ff52f2..c1366a8e 100644 --- a/docs/html/_sources/composer.cli.rst.txt +++ b/docs/html/_sources/composer.cli.rst.txt @@ -52,6 +52,14 @@ composer.cli.projects module :undoc-members: :show-inheritance: +composer.cli.providers module +----------------------------- + +.. automodule:: composer.cli.providers + :members: + :undoc-members: + :show-inheritance: + composer.cli.sources module --------------------------- @@ -68,6 +76,14 @@ composer.cli.status module :undoc-members: :show-inheritance: +composer.cli.upload module +-------------------------- + +.. automodule:: composer.cli.upload + :members: + :undoc-members: + :show-inheritance: + composer.cli.utilities module ----------------------------- diff --git a/docs/html/_sources/lifted.rst.txt b/docs/html/_sources/lifted.rst.txt new file mode 100644 index 00000000..a9c4b004 --- /dev/null +++ b/docs/html/_sources/lifted.rst.txt @@ -0,0 +1,46 @@ +lifted package +============== + +Submodules +---------- + +lifted.config module +-------------------- + +.. automodule:: lifted.config + :members: + :undoc-members: + :show-inheritance: + +lifted.providers module +----------------------- + +.. automodule:: lifted.providers + :members: + :undoc-members: + :show-inheritance: + +lifted.queue module +------------------- + +.. automodule:: lifted.queue + :members: + :undoc-members: + :show-inheritance: + +lifted.upload module +-------------------- + +.. automodule:: lifted.upload + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: lifted + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/html/_sources/lorax-composer.rst.txt b/docs/html/_sources/lorax-composer.rst.txt index e76e5668..e1dd9d18 100644 --- a/docs/html/_sources/lorax-composer.rst.txt +++ b/docs/html/_sources/lorax-composer.rst.txt @@ -156,14 +156,26 @@ The names must match the names exactly, and the versions can be an exact match or a filesystem-like glob of the version using ``*`` wildcards and ``?`` character matching. -NOTE: As of lorax-composer-29.2-1 the versions are not used for depsolving, -that is planned for a future release. And currently there are no differences -between ``packages`` and ``modules`` in ``lorax-composer``. +NOTE: Currently there are no differences between ``packages`` and ``modules`` +in ``lorax-composer``. Both are treated like an rpm package dependency. + +For example, to install ``tmux-2.9a`` and ``openssh-server-8.*``, you would add +this to your blueprint:: + + [[packages]] + name = "tmux" + version = "2.9a" + + [[packages]] + name = "openssh-server" + version = "8.*" + + [[groups]] ~~~~~~~~~~ -These entries describe a group of packages to be installed into the image. Package groups are +The ``groups`` entries describe a group of packages to be installed into the image. Package groups are defined in the repository metadata. Each group has a descriptive name used primarily for display in user interfaces and an ID more commonly used in kickstart files. Here, the ID is the expected way of listing a group. @@ -172,6 +184,16 @@ Groups have three different ways of categorizing their packages: mandatory, def For purposes of blueprints, mandatory and default packages will be installed. There is no mechanism for selecting optional packages. +For example, if you want to install the ``anaconda-tools`` group you would add this to your +blueprint:: + + [[groups]] + name="anaconda-tools" + +``groups`` is a TOML list, so each group needs to be listed separately, like ``packages`` but with +no version number. + + Customizations ~~~~~~~~~~~~~~ diff --git a/docs/html/_sources/modules.rst.txt b/docs/html/_sources/modules.rst.txt index 15b5f96f..cc16b668 100644 --- a/docs/html/_sources/modules.rst.txt +++ b/docs/html/_sources/modules.rst.txt @@ -5,4 +5,5 @@ src :maxdepth: 4 composer + lifted pylorax diff --git a/docs/html/_sources/pylorax.api.rst.txt b/docs/html/_sources/pylorax.api.rst.txt index 673489c2..25a05cee 100644 --- a/docs/html/_sources/pylorax.api.rst.txt +++ b/docs/html/_sources/pylorax.api.rst.txt @@ -132,6 +132,14 @@ pylorax.api.toml module :undoc-members: :show-inheritance: +pylorax.api.utils module +------------------------ + +.. automodule:: pylorax.api.utils + :members: + :undoc-members: + :show-inheritance: + pylorax.api.v0 module --------------------- @@ -140,6 +148,14 @@ pylorax.api.v0 module :undoc-members: :show-inheritance: +pylorax.api.v1 module +--------------------- + +.. automodule:: pylorax.api.v1 + :members: + :undoc-members: + :show-inheritance: + pylorax.api.workspace module ---------------------------- diff --git a/docs/html/_static/documentation_options.js b/docs/html/_static/documentation_options.js index 4aabf8d2..505ad010 100644 --- a/docs/html/_static/documentation_options.js +++ b/docs/html/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '31.9', + VERSION: '32.1', LANGUAGE: 'None', COLLAPSE_INDEX: false, FILE_SUFFIX: '.html', diff --git a/docs/html/composer-cli.html b/docs/html/composer-cli.html index 1e02321c..75aab6e3 100644 --- a/docs/html/composer-cli.html +++ b/docs/html/composer-cli.html @@ -8,7 +8,7 @@ - composer-cli — Lorax 31.9 documentation + composer-cli — Lorax 32.1 documentation @@ -60,7 +60,7 @@
    - 31.9 + 32.1
    @@ -100,6 +100,14 @@
  • Build an image
  • Monitor the build status
  • Download the image
  • +
  • Image Uploads
  • +
  • Providers +
  • +
  • Profiles
  • +
  • Build an image and upload results
  • +
  • Upload an existing image
  • Product and Updates Images
  • @@ -183,9 +191,7 @@ group. They do not need to be root, but all of the composer-cli cmdline arguments¶

    Lorax Composer commandline tool

    -
    usage: composer-cli [-h] [-j] [-s SOCKET] [--log LOG] [-a APIVER]
    -                    [--test TESTMODE] [-V]
    -                    ...
    +
    usage: composer-cli [-h] [-j] [-s SOCKET] [--log LOG] [-a APIVER] [--test TESTMODE] [-V] ...
     

    -
    compose start <BLUEPRINT> <TYPE>

    Start a compose using the selected blueprint and output type.

    +
    compose start <BLUEPRINT> <TYPE> [<IMAGE-NAME> <PROVIDER> <PROFILE> | <IMAGE-NAME> <PROFILE.TOML>]

    Start a compose using the selected blueprint and output type. Optionally start an upload.

    compose types

    List the supported output types.

    @@ -296,6 +302,30 @@ TO-COMMIT can be a commit hash, NEWEST, or WORKSPACE

    status show Show API server status.

    +
    +
    upload info <UPLOAD-UUID>

    Details about an upload

    +
    +
    upload start <BUILD-UUID> <IMAGE-NAME> [<PROVIDER> <PROFILE>|<PROFILE.TOML>]

    Upload a build image to the selected provider.

    +
    +
    upload log <UPLOAD-UUID>

    Show the upload log

    +
    +
    upload cancel <UPLOAD-UUID>

    Cancel an upload with that is queued or in progress

    +
    +
    upload delete <UPLOAD-UUID>

    Delete the upload and remove it from the build

    +
    +
    upload reset <UPLOAD-UUID>

    Reset the upload so that it can be tried again

    +
    +
    providers list <PROVIDER>

    List the available providers, or list the <provider's> available profiles

    +
    +
    providers show <PROVIDER> <PROFILE>

    show the details of a specific provider's profile

    +
    +
    providers push <PROFILE.TOML>

    Add a new profile, or overwrite an existing one

    +
    +
    providers save <PROVIDER> <PROFILE>

    Save the profile's details to a TOML file named <PROFILE>.toml

    +
    +
    providers delete <PROVIDER> <PROFILE>

    Delete a profile from a provider

    +
    +

    Monitor the build status¶

    @@ -334,6 +366,104 @@ save the qcow2 image as +

    Image Uploads¶

    +

    composer-cli can upload the images to a number of services, including AWS, +OpenStack, and vSphere. The upload can be started when the build is finished, +by using composer-cli compose start ... or an existing image can be uploaded +with composer-cli upload start .... In order to access the service you need +to pass authentication details to composer-cli using a TOML file, or reference +a previously saved profile.

    +
    +
    +

    Providers¶

    +

    Providers are the services providers with Ansible playbook support under +/usr/share/lorax/lifted/providers/, you will need to gather some provider +specific information in order to authenticate with it. You can view the +required fields using composer-cli providers template <PROVIDER>, eg. for AWS +you would run:

    +
    composer-cli upload template aws
    +
    +
    +

    The output looks like this:

    +
    provider = "aws"
    +
    +[settings]
    +aws_access_key = "AWS Access Key"
    +aws_bucket = "AWS Bucket"
    +aws_region = "AWS Region"
    +aws_secret_key = "AWS Secret Key"
    +
    +
    +

    Save this into an aws-credentials.toml file and use it when running start.

    +
    +

    AWS¶

    +

    The access key and secret key can be created by going to the +IAM->Users->Security Credentials section and creating a new access key. The +secret key will only be shown when it is first created so make sure to record +it in a secure place. The region should be the region that you want to use the +AMI in, and the bucket can be an existing bucket, or a new one, following the +normal AWS bucket naming rules. It will be created if it doesn't already exist.

    +

    When uploading the image it is first uploaded to the s3 bucket, and then +converted to an AMI. If the conversion is successful the s3 object will be +deleted. If it fails, re-trying after correcting the problem will re-use the +object if you have not deleted it in the meantime, speeding up the process.

    +
    +
    +
    +

    Profiles¶

    +

    Profiles store the authentication settings associated with a specific provider. +Providers can have multiple profiles, as long as their names are unique. For +example, you may have one profile for testing and another for production +uploads.

    +

    Profiles are created by pushing the provider settings template to the server using +composer-cli providers push <PROFILE.TOML> where PROFILE.TOML is the same as the +provider template, but with the addition of a profile field. For example, an AWS +profile named test-uploads would look like this:

    +
    provider = "aws"
    +profile = "test-uploads"
    +
    +[settings]
    +aws_access_key = "AWS Access Key"
    +aws_bucket = "AWS Bucket"
    +aws_region = "AWS Region"
    +aws_secret_key = "AWS Secret Key"
    +
    +
    +

    You can view the profile by using composer-cli providers aws test-uploads.

    +
    +
    +

    Build an image and upload results¶

    +

    If you have a profile named test-uploads:

    +
    composer-cli compose start example-http-server ami "http image" aws test-uploads
    +
    +
    +

    Or if you have the settings stored in a TOML file:

    +
    composer-cli compose start example-http-server ami "http image" aws-settings.toml
    +
    +
    +

    It will return the UUID of the image build, and the UUID of the upload. Once +the build has finished successfully it will start the upload process, which you +can monitor with composer-cli upload info <UPLOAD-UUID>

    +

    You can also view the upload logs from the Ansible playbook with:

    +
    ``composer-cli upload log <UPLOAD-UUID>``
    +
    +
    +

    The type of the image must match the type supported by the provider.

    +
    +
    +

    Upload an existing image¶

    +

    You can upload previously built images, as long as they are in the FINISHED state, using composer-cli upload start ...`. If you have a profile named test-uploads:

    +
    composer-cli upload start <UUID> "http-image" aws test-uploads
    +
    +
    +

    Or if you have the settings stored in a TOML file:

    +
    composer-cli upload start <UUID> "http-image" aws-settings.toml
    +
    +
    +

    This will output the UUID of the upload, which can then be used to monitor the status in the same way +described above.

    +
    diff --git a/docs/html/composer.cli.html b/docs/html/composer.cli.html index 23f2c048..c34e0d3f 100644 --- a/docs/html/composer.cli.html +++ b/docs/html/composer.cli.html @@ -8,7 +8,7 @@ - composer.cli package — Lorax 31.9 documentation + composer.cli package — Lorax 32.1 documentation @@ -35,7 +35,7 @@ - + @@ -60,7 +60,7 @@
    - 31.9 + 32.1
    @@ -104,6 +104,7 @@
  • Module contents
  • +
  • lifted package
  • pylorax package
  • @@ -189,10 +190,10 @@
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -205,13 +206,13 @@

    Process blueprints commands

    Parameters
    -

    opts (argparse.Namespace) -- Cmdline arguments

    +

    opts (argparse.Namespace) -- Cmdline arguments

    Returns

    Value to return from sys.exit()

    Return type
    -

    int

    +

    int

    This dispatches the blueprints commands to a function

    @@ -224,10 +225,10 @@
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -241,10 +242,10 @@
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -258,10 +259,10 @@
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -279,10 +280,10 @@
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -298,10 +299,10 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -315,10 +316,10 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -332,10 +333,10 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -349,10 +350,10 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -366,10 +367,10 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -383,10 +384,10 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -401,10 +402,10 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -418,10 +419,10 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -435,10 +436,10 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -451,13 +452,13 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file

    Return comma-separated list of the dict's name/user fields

    Parameters
    -

    d (dict) -- key/values

    +

    d (dict) -- key/values

    Returns

    String of the dict's keys and values

    Return type
    -

    str

    +

    str

    root, norm

    @@ -470,8 +471,8 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • change (dict) -- The individual blueprint change dict

    • -
    • indent (int) -- Number of spaces to indent

    • +
    • change (dict) -- The individual blueprint change dict

    • +
    • indent (int) -- Number of spaces to indent

    @@ -483,13 +484,13 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file

    Return the dict as a human readable single line

    Parameters
    -

    d (dict) -- key/values

    +

    d (dict) -- key/values

    Returns

    String of the dict's keys and values

    Return type
    -

    str

    +

    str

    key="str", key="str1,str2", ...

    @@ -501,7 +502,7 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file

    Generate nice diff entry string.

    Parameters
    -

    diff (dict) -- Difference entry dict

    +

    diff (dict) -- Difference entry dict

    Returns

    Nice string

    @@ -528,11 +529,11 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -546,13 +547,13 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file

    Process compose commands

    Parameters
    -

    opts (argparse.Namespace) -- Cmdline arguments

    +

    opts (argparse.Namespace) -- Cmdline arguments

    Returns

    Value to return from sys.exit()

    Return type
    -

    int

    +

    int

    This dispatches the compose commands to a function

    @@ -565,11 +566,11 @@ blueprints freeze save <blueprint,...> Save the frozen blueprint to a file
    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -585,11 +586,11 @@ or failed, not a running compose.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -605,11 +606,11 @@ of compose that was selected.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -630,11 +631,11 @@ of compose that was selected.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -650,11 +651,11 @@ during the build.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -669,11 +670,11 @@ during the build.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -688,11 +689,11 @@ during the build.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -708,15 +709,15 @@ It is saved as uuid.tar

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- Set to 1 to simulate a failed compose, set to 2 to simulate a finished one.

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- Set to 1 to simulate a failed compose, set to 2 to simulate a finished one.

    -

    compose start <blueprint-name> <compose-type>

    +

    compose start <blueprint-name> <compose-type> [<image-name> <provider> <profile> | <image-name> <profile.toml>]

    @@ -726,11 +727,11 @@ It is saved as uuid.tar

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -745,11 +746,11 @@ and failed so raw JSON output is not available.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • -
    • testmode (int) -- unused in this function

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    @@ -769,13 +770,13 @@ include this extra information.

    Process modules commands

    Parameters
    -

    opts (argparse.Namespace) -- Cmdline arguments

    +

    opts (argparse.Namespace) -- Cmdline arguments

    Returns

    Value to return from sys.exit()

    Return type
    -

    int

    +

    int

    @@ -789,13 +790,13 @@ include this extra information.

    Process projects commands

    Parameters
    -

    opts (argparse.Namespace) -- Cmdline arguments

    +

    opts (argparse.Namespace) -- Cmdline arguments

    Returns

    Value to return from sys.exit()

    Return type
    -

    int

    +

    int

    @@ -807,10 +808,10 @@ include this extra information.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -824,16 +825,163 @@ include this extra information.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    projects list

    + +
    +

    composer.cli.providers module¶

    +
    +
    +composer.cli.providers.providers_cmd(opts)[source]¶
    +

    Process providers commands

    +
    +
    Parameters
    +

    opts (argparse.Namespace) -- Cmdline arguments

    +
    +
    Returns
    +

    Value to return from sys.exit()

    +
    +
    Return type
    +

    int

    +
    +
    +

    This dispatches the providers commands to a function

    +
    + +
    +
    +composer.cli.providers.providers_delete(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Delete a profile from a provider

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    providers delete <provider> <profile>

    +
    + +
    +
    +composer.cli.providers.providers_info(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Show information about each provider

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    providers info <PROVIDER>

    +
    + +
    +
    +composer.cli.providers.providers_list(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Return the list of providers

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    providers list

    +
    + +
    +
    +composer.cli.providers.providers_push(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Add a new provider profile or overwrite an existing one

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    providers push <profile.toml>

    +
    + +
    +
    +composer.cli.providers.providers_save(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Save a provider's profile to a TOML file

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    providers save <provider> <profile>

    +
    + +
    +
    +composer.cli.providers.providers_show(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Return details about a provider

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    providers show <provider> <profile>

    +
    + +
    +
    +composer.cli.providers.providers_template(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Return a TOML template for setting the provider's fields

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    providers template <provider>

    +
    +

    composer.cli.sources module¶

    @@ -844,10 +992,10 @@ include this extra information.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -860,13 +1008,13 @@ include this extra information.

    Process sources commands

    Parameters
    -

    opts (argparse.Namespace) -- Cmdline arguments

    +

    opts (argparse.Namespace) -- Cmdline arguments

    Returns

    Value to return from sys.exit()

    Return type
    -

    int

    +

    int

    @@ -878,10 +1026,10 @@ include this extra information.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -895,10 +1043,10 @@ include this extra information.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -912,10 +1060,10 @@ include this extra information.

    Parameters
      -
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • -
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • args (list of str) -- List of remaining arguments from the cmdline

    • -
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    @@ -931,17 +1079,165 @@ include this extra information.

    Process status commands

    Parameters
    -

    opts (argparse.Namespace) -- Cmdline arguments

    +

    opts (argparse.Namespace) -- Cmdline arguments

    Returns

    Value to return from sys.exit()

    Return type
    -

    int

    +

    int

    +
    +
    +

    composer.cli.upload module¶

    +
    +
    +composer.cli.upload.upload_cancel(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Cancel the queued or running upload

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    upload cancel <build-uuid>

    +
    + +
    +
    +composer.cli.upload.upload_cmd(opts)[source]¶
    +

    Process upload commands

    +
    +
    Parameters
    +

    opts (argparse.Namespace) -- Cmdline arguments

    +
    +
    Returns
    +

    Value to return from sys.exit()

    +
    +
    Return type
    +

    int

    +
    +
    +

    This dispatches the upload commands to a function

    +
    + +
    +
    +composer.cli.upload.upload_delete(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Delete an upload and remove it from the build

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    upload delete <build-uuid>

    +
    + +
    +
    +composer.cli.upload.upload_info(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Return detailed information about the upload

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    upload info <uuid>

    +

    This returns information about the upload, including uuid, name, status, service, and image.

    +
    + +
    +
    +composer.cli.upload.upload_list(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Return the composes and their associated upload uuids and status

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    upload list

    +
    + +
    +
    +composer.cli.upload.upload_log(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Return the upload log

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    upload log <build-uuid>

    +
    + +
    +
    +composer.cli.upload.upload_reset(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Reset the upload and execute it again

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    upload reset <build-uuid>

    +
    + +
    +
    +composer.cli.upload.upload_start(socket_path, api_version, args, show_json=False, testmode=0)[source]¶
    +

    Start upload up a build uuid image

    +
    +
    Parameters
    +
      +
    • socket_path (str) -- Path to the Unix socket to use for API communication

    • +
    • api_version (str) -- Version of the API to talk to. eg. "0"

    • +
    • args (list of str) -- List of remaining arguments from the cmdline

    • +
    • show_json (bool) -- Set to True to show the JSON output instead of the human readable output

    • +
    • testmode (int) -- unused in this function

    • +
    +
    +
    +

    upload start <build-uuid> <image-name> [<provider> <profile> | <profile.toml>]

    +
    +

    composer.cli.utilities module¶

    @@ -970,13 +1266,13 @@ include this extra information.

    Convert a blueprint name into a filename.toml

    Parameters
    -

    blueprint_name (str) -- The blueprint's name

    +

    blueprint_name (str) -- The blueprint's name

    Returns

    The blueprint name with ' ' converted to - and .toml appended

    Return type
    -

    str

    +

    str

    @@ -987,10 +1283,10 @@ include this extra information.

    Log any errors, return the correct value

    Parameters
    -

    result (dict) -- JSON result from the http query

    +

    result (dict) -- JSON result from the http query

    Return type
    -

    tuple

    +

    tuple

    Returns

    (rc, should_exit_now)

    @@ -1006,13 +1302,13 @@ not to continue processing the results.

    Return the package info as a NEVRA

    Parameters
    -

    pkg (dict) -- The package details

    +

    pkg (dict) -- The package details

    Returns

    name-[epoch:]version-release-arch

    Return type
    -

    str

    +

    str

    @@ -1023,13 +1319,13 @@ not to continue processing the results.

    Convert a blueprint name into a filename.toml

    Parameters
    -

    blueprint_name (str) -- The blueprint's name

    +

    blueprint_name (str) -- The blueprint's name

    Returns

    The blueprint name with ' ' converted to - and .toml appended

    Return type
    -

    str

    +

    str

    @@ -1043,7 +1339,7 @@ not to continue processing the results.

    Main program execution

    Parameters
    -

    opts (argparse.Namespace) -- Cmdline arguments

    +

    opts (argparse.Namespace) -- Cmdline arguments

    @@ -1059,7 +1355,7 @@ not to continue processing the results.