Skip to content

Commit

Permalink
docstrings, move upload routes to v1
Browse files Browse the repository at this point in the history
  • Loading branch information
evan-goode committed Aug 12, 2019
1 parent d98f5ca commit f01c394
Show file tree
Hide file tree
Showing 4 changed files with 487 additions and 188 deletions.
2 changes: 1 addition & 1 deletion src/pylorax/api/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
COMPOSE_ERROR = "ComposeError"

# Returned from the API for all errors from a /upload/* route.
UPLOAD_ERROR = "UploadError"
UPLOAD_ERROR = "UploadError" # TODO these errors should be more specific

# Returned from the API when invalid characters are used in a route path or in
# some identifier.
Expand Down
8 changes: 6 additions & 2 deletions src/pylorax/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ def bad_request(error):

# Register the v1 API on /api/v1/
# Use v0 routes by default
server.register_blueprint(v0_api, url_prefix="/api/v1/",
skip_rules=["/projects/source/info/<source_names>", "/projects/source/new"])
skip_rules = [
"/compose",
"/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/")
185 changes: 2 additions & 183 deletions src/pylorax/api/v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@
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.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, uuid_schedule_upload, uuid_remove_upload
from pylorax.api.queue import uuid_tar, uuid_image, uuid_cancel, uuid_log
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
Expand All @@ -73,9 +73,6 @@
from pylorax.api.utils import take_limits, blueprint_exists
from pylorax.api.workspace import workspace_read, workspace_write, workspace_delete

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

# The API functions don't actually get called by any code here
# pylint: disable=unused-variable

Expand Down Expand Up @@ -1499,21 +1496,6 @@ def v0_compose_start():
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"]
settings = compose["upload"]["settings"]
provider_name = compose["upload"]["provider"]
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

Expand All @@ -1526,15 +1508,6 @@ def v0_compose_start():
else:
return jsonify(status=False, errors=[{"id": BUILD_FAILED, "msg": str(e)}]), 400

if "upload" in compose:
upload_uuid = uuid_schedule_upload(
api.config["COMPOSER_CFG"],
build_id,
provider_name,
image_name,
settings
)

return jsonify(status=True, build_id=build_id)

@v0_api.route("/compose/types")
Expand Down Expand Up @@ -2048,157 +2021,3 @@ def v0_compose_log_tail(uuid):
return Response(uuid_log(api.config["COMPOSER_CFG"], uuid, size), direct_passthrough=True)
except RuntimeError as e:
return jsonify(status=False, errors=[{"id": COMPOSE_ERROR, "msg": str(e)}]), 400

@v0_api.route("/compose/uploads/schedule", defaults={'compose_uuid': ""}, methods=["POST"])
@v0_api.route("/compose/uploads/schedule/<compose_uuid>", methods=["POST"])
@checkparams([("compose_uuid", "", "no compose UUID given")])
def v0_compose_uploads_schedule(compose_uuid):
"""Schedule an upload of a compose to the associated cloud provider"""
if VALID_API_STRING.match(compose_uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 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"]
settings = parsed["settings"]
provider_name = parsed["provider"]
except KeyError as e:
return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": f'Missing parameter {str(e)}!'}]), 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_uuid = 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_uuid=upload_uuid)

@v0_api.route("/compose/uploads/delete", defaults={"compose_uuid": "", "upload_uuid": ""}, methods=["DELETE"])
@v0_api.route("/compose/uploads/delete/<compose_uuid>/<upload_uuid>", methods=["DELETE"])
@checkparams([("compose_uuid", "", "no compose UUID given"), ("upload_uuid", "", "no upload UUID given")])
def v0_compose_uploads_delete(compose_uuid, upload_uuid):
"""Delete an upload and disassociate it from its compose"""
if None in (VALID_API_STRING.match(compose_uuid), VALID_API_STRING.match(upload_uuid)):
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400

if not uuid_status(api.config["COMPOSER_CFG"], compose_uuid):
return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % compose_uuid}]), 400
uuid_remove_upload(api.config["COMPOSER_CFG"], compose_uuid, upload_uuid)
try:
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_uuid=upload_uuid)

@v0_api.route("/upload/info", defaults={"uuid": ""})
@v0_api.route("/upload/info/<uuid>")
@checkparams([("uuid", "", "no UUID given")])
def v0_upload_info(uuid):
"""Returns information about a given upload"""
if VALID_API_STRING.match(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"], uuid).summary()
except RuntimeError as error:
return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(error)}])
return jsonify(status=True, upload=upload)

@v0_api.route("/upload/log", defaults={"uuid": ""})
@v0_api.route("/upload/log/<uuid>")
@checkparams([("uuid", "", "no UUID given")])
def v0_upload_log(uuid):
"""Returns an upload's log"""
if VALID_API_STRING.match(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"], uuid)
except RuntimeError as error:
return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(error)}])
return jsonify(status=True, log=upload.upload_log)

@v0_api.route("/upload/reset", defaults={"uuid": ""}, methods=["POST"])
@v0_api.route("/upload/reset/<uuid>", methods=["POST"])
@checkparams([("uuid", "", "no UUID given")])
def v0_upload_reset(uuid):
"""Reset an upload so it can be attempted again"""
if VALID_API_STRING.match(uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 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"], uuid, image_name, settings)
except RuntimeError as error:
# TODO more specific errors
return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(error)}])
return jsonify(status=True, uuid=uuid)

@v0_api.route("/upload/cancel", defaults={"uuid": ""}, methods=["DELETE"])
@v0_api.route("/upload/cancel/<uuid>", methods=["DELETE"])
@checkparams([("uuid", "", "no UUID given")])
def v0_upload_cancel(uuid):
"""Cancel an upload that is either queued or in progress"""
if VALID_API_STRING.match(uuid) is None:
return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400

try:
cancel_upload(api.config["COMPOSER_CFG"]["upload"], uuid)
except RuntimeError as error:
# TODO more specific errors
return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(error)}])
return jsonify(status=True, uuid=uuid)

@v0_api.route("/upload/providers")
def v0_upload_providers():
"""Return the information about all upload providers, including their
display names, expected settings, and saved profiles. Refer to the
`resolve_provider` function."""

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

return jsonify(status=True, providers={provider_name: get_provider_info(provider_name) for provider_name in provider_names})

@v0_api.route("/upload/providers/save", methods=["POST"])
def v0_providers_save():
"""Update a provider's saved settings"""
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:
return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": f'Missing parameter {str(e)}!'}]), 400
try:
save_settings(api.config["COMPOSER_CFG"]["upload"], provider_name, profile, settings)
except Exception as e:
return jsonify(status=False, errors=[{"id": UPLOAD_ERROR, "msg": str(e)}])
return jsonify(status=True)
Loading

0 comments on commit f01c394

Please sign in to comment.