From 916a001d735b88510fabd43830969bdce7038f7f Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Thu, 21 Dec 2017 09:44:59 -0800 Subject: [PATCH] Add support for other branches to the routes Passing ?branch= will use the specified branch instead of master. The new branch will not exist until a /recipes/new?branch=new-branch POST is made. At that time the branch will be created based on the current master branch and the new commit will be added to it. --- src/pylorax/api/recipes.py | 8 +++++ src/pylorax/api/v0.py | 58 ++++++++++++++++++++++-------------- tests/pylint/runpylint.py | 1 + tests/pylorax/test_server.py | 23 ++++++++++++++ 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/pylorax/api/recipes.py b/src/pylorax/api/recipes.py index 68ea34b4..d4e89989 100644 --- a/src/pylorax/api/recipes.py +++ b/src/pylorax/api/recipes.py @@ -245,6 +245,14 @@ def write_commit(repo, branch, filename, message, content): :rtype: Git.OId :raises: Can raise errors from Ggit """ + try: + parent_commit = head_commit(repo, branch) + except GLib.GError: + # Branch doesn't exist, make a new one based on master + master_head = head_commit(repo, "master") + repo.create_branch(branch, master_head, 0) + parent_commit = head_commit(repo, branch) + parent_commit = head_commit(repo, branch) blob_id = repo.create_blob_from_buffer(content) diff --git a/src/pylorax/api/v0.py b/src/pylorax/api/v0.py index 63992b03..81dc04ff 100644 --- a/src/pylorax/api/v0.py +++ b/src/pylorax/api/v0.py @@ -66,6 +66,7 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_list(): """List the available recipes on a branch.""" + branch = request.args.get("branch", "master") try: limit = int(request.args.get("limit", "20")) offset = int(request.args.get("offset", "0")) @@ -73,13 +74,14 @@ def v0_api(api): return jsonify(error={"msg":str(e)}), 400 with api.config["GITLOCK"].lock: - recipes = take_limits(map(lambda f: f[:-5], list_branch_files(api.config["GITLOCK"].repo, "master")), offset, limit) + recipes = take_limits(map(lambda f: f[:-5], list_branch_files(api.config["GITLOCK"].repo, branch)), offset, limit) return jsonify(recipes=recipes, limit=limit, offset=offset, total=len(recipes)) @api.route("/api/v0/recipes/info/") @crossdomain(origin="*") def v0_recipes_info(recipe_names): """Return the contents of the recipe, or a list of recipes""" + branch = request.args.get("branch", "master") recipes = [] changes = [] errors = [] @@ -88,7 +90,7 @@ def v0_api(api): # Get the workspace version (if it exists) try: with api.config["GITLOCK"].lock: - ws_recipe = workspace_read(api.config["GITLOCK"].repo, "master", recipe_name) + ws_recipe = workspace_read(api.config["GITLOCK"].repo, branch, recipe_name) except Exception as e: ws_recipe = None exceptions.append(str(e)) @@ -97,7 +99,7 @@ def v0_api(api): # Get the git version (if it exists) try: with api.config["GITLOCK"].lock: - git_recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name) + git_recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name) except Exception as e: git_recipe = None exceptions.append(str(e)) @@ -130,6 +132,7 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_changes(recipe_names): """Return the changes to a recipe or list of recipes""" + branch = request.args.get("branch", "master") try: limit = int(request.args.get("limit", "20")) offset = int(request.args.get("offset", "0")) @@ -142,7 +145,7 @@ def v0_api(api): filename = recipe_filename(recipe_name) try: with api.config["GITLOCK"].lock: - commits = take_limits(list_commits(api.config["GITLOCK"].repo, "master", filename), offset, limit) + commits = take_limits(list_commits(api.config["GITLOCK"].repo, branch, filename), offset, limit) except Exception as e: errors.append({"recipe":recipe_name, "msg":e}) log.error("(v0_recipes_changes) %s", str(e)) @@ -158,6 +161,7 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_new(): """Commit a new recipe""" + branch = request.args.get("branch", "master") try: if request.headers['Content-Type'] == "text/x-toml": recipe = recipe_from_toml(request.data) @@ -165,11 +169,11 @@ def v0_api(api): recipe = recipe_from_dict(request.get_json(cache=False)) with api.config["GITLOCK"].lock: - commit_recipe(api.config["GITLOCK"].repo, "master", recipe) + commit_recipe(api.config["GITLOCK"].repo, branch, recipe) # Read the recipe with new version and write it to the workspace - recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe["name"]) - workspace_write(api.config["GITLOCK"].repo, "master", recipe) + recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe["name"]) + workspace_write(api.config["GITLOCK"].repo, branch, recipe) except Exception as e: log.error("(v0_recipes_new) %s", str(e)) return jsonify(status=False, error={"msg":str(e)}), 400 @@ -180,9 +184,10 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_delete(recipe_name): """Delete a recipe from git""" + branch = request.args.get("branch", "master") try: with api.config["GITLOCK"].lock: - delete_recipe(api.config["GITLOCK"].repo, "master", recipe_name) + delete_recipe(api.config["GITLOCK"].repo, branch, recipe_name) except Exception as e: log.error("(v0_recipes_delete) %s", str(e)) return jsonify(status=False, error={"msg":str(e)}), 400 @@ -193,6 +198,7 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_workspace(): """Write a recipe to the workspace""" + branch = request.args.get("branch", "master") try: if request.headers['Content-Type'] == "text/x-toml": recipe = recipe_from_toml(request.data) @@ -200,7 +206,7 @@ def v0_api(api): recipe = recipe_from_dict(request.get_json(cache=False)) with api.config["GITLOCK"].lock: - workspace_write(api.config["GITLOCK"].repo, "master", recipe) + workspace_write(api.config["GITLOCK"].repo, branch, recipe) except Exception as e: log.error("(v0_recipes_workspace) %s", str(e)) return jsonify(status=False, error={"msg":str(e)}), 400 @@ -211,9 +217,10 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_delete_workspace(recipe_name): """Delete a recipe from the workspace""" + branch = request.args.get("branch", "master") try: with api.config["GITLOCK"].lock: - workspace_delete(api.config["GITLOCK"].repo, "master", recipe_name) + workspace_delete(api.config["GITLOCK"].repo, branch, recipe_name) except Exception as e: log.error("(v0_recipes_delete_workspace) %s", str(e)) return jsonify(status=False, error={"msg":str(e)}), 400 @@ -224,13 +231,14 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_undo(recipe_name, commit): """Undo changes to a recipe by reverting to a previous commit.""" + branch = request.args.get("branch", "master") try: with api.config["GITLOCK"].lock: - revert_recipe(api.config["GITLOCK"].repo, "master", recipe_name, commit) + revert_recipe(api.config["GITLOCK"].repo, branch, recipe_name, commit) # Read the new recipe and write it to the workspace - recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name) - workspace_write(api.config["GITLOCK"].repo, "master", recipe) + recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name) + workspace_write(api.config["GITLOCK"].repo, branch, recipe) except Exception as e: log.error("(v0_recipes_undo) %s", str(e)) return jsonify(status=False, error={"msg":str(e)}), 400 @@ -241,9 +249,10 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_tag(recipe_name): """Tag a recipe's latest recipe commit as a 'revision'""" + branch = request.args.get("branch", "master") try: with api.config["GITLOCK"].lock: - tag_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name) + tag_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name) except Exception as e: log.error("(v0_recipes_tag) %s", str(e)) return jsonify(status=False, error={"msg":str(e)}), 400 @@ -254,13 +263,14 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_diff(recipe_name, from_commit, to_commit): """Return the differences between two commits of a recipe""" + branch = request.args.get("branch", "master") try: if from_commit == "NEWEST": with api.config["GITLOCK"].lock: - old_recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name) + old_recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name) else: with api.config["GITLOCK"].lock: - old_recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name, from_commit) + old_recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name, from_commit) except Exception as e: log.error("(v0_recipes_diff) %s", str(e)) return jsonify(error={"msg":str(e)}), 400 @@ -268,13 +278,13 @@ def v0_api(api): try: if to_commit == "WORKSPACE": with api.config["GITLOCK"].lock: - new_recipe = workspace_read(api.config["GITLOCK"].repo, "master", recipe_name) + new_recipe = workspace_read(api.config["GITLOCK"].repo, branch, recipe_name) elif to_commit == "NEWEST": with api.config["GITLOCK"].lock: - new_recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name) + new_recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name) else: with api.config["GITLOCK"].lock: - new_recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name, to_commit) + new_recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name, to_commit) except Exception as e: log.error("(v0_recipes_diff) %s", str(e)) return jsonify(error={"msg":str(e)}), 400 @@ -286,6 +296,7 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_freeze(recipe_names): """Return the recipe with the exact modules and packages selected by depsolve""" + branch = request.args.get("branch", "master") recipes = [] errors = [] for recipe_name in [n.strip() for n in sorted(recipe_names.split(","), key=lambda n: n.lower())]: @@ -294,7 +305,7 @@ def v0_api(api): recipe = None try: with api.config["GITLOCK"].lock: - recipe = workspace_read(api.config["GITLOCK"].repo, "master", recipe_name) + recipe = workspace_read(api.config["GITLOCK"].repo, branch, recipe_name) except Exception: pass @@ -302,7 +313,7 @@ def v0_api(api): # No workspace version, get the git version (if it exists) try: with api.config["GITLOCK"].lock: - recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name) + recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name) except Exception as e: errors.append({"recipe":recipe_name, "msg":str(e)}) log.error("(v0_recipes_freeze) %s", str(e)) @@ -346,6 +357,7 @@ def v0_api(api): @crossdomain(origin="*") def v0_recipes_depsolve(recipe_names): """Return the dependencies for a recipe""" + branch = request.args.get("branch", "master") recipes = [] errors = [] for recipe_name in [n.strip() for n in sorted(recipe_names.split(","), key=lambda n: n.lower())]: @@ -354,7 +366,7 @@ def v0_api(api): recipe = None try: with api.config["GITLOCK"].lock: - recipe = workspace_read(api.config["GITLOCK"].repo, "master", recipe_name) + recipe = workspace_read(api.config["GITLOCK"].repo, branch, recipe_name) except Exception: pass @@ -362,7 +374,7 @@ def v0_api(api): # No workspace version, get the git version (if it exists) try: with api.config["GITLOCK"].lock: - recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name) + recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name) except Exception as e: errors.append({"recipe":recipe_name, "msg":str(e)}) log.error("(v0_recipes_depsolve) %s", str(e)) diff --git a/tests/pylint/runpylint.py b/tests/pylint/runpylint.py index b7b7f3a5..c51716d7 100755 --- a/tests/pylint/runpylint.py +++ b/tests/pylint/runpylint.py @@ -27,6 +27,7 @@ class LoraxLintConfig(PocketLintConfig): FalsePositive(r"Module 'pylorax' has no 'version' member"), FalsePositive(r"Instance of 'int' has no .* member"), FalsePositive(r"Catching too general exception Exception"), + FalsePositive(r"^E0712.*: Catching an exception which doesn't inherit from (Base|)Exception: GError$"), ] @property diff --git a/tests/pylorax/test_server.py b/tests/pylorax/test_server.py index 35acd48a..36406d7c 100644 --- a/tests/pylorax/test_server.py +++ b/tests/pylorax/test_server.py @@ -460,3 +460,26 @@ class ServerTestCase(unittest.TestCase): self.assertEqual(len(modules), 1) self.assertEqual(modules[0]["name"], "bash") self.assertEqual(modules[0]["dependencies"][0]["name"], "basesystem") + + def test_recipe_new_branch(self): + """Test the /api/v0/recipes/new route with a new branch""" + test_recipe = {"description": "An example GlusterFS server with samba", + "name":"glusterfs", + "version": "0.2.0", + "modules":[{"name":"glusterfs", "version":"3.7.*"}, + {"name":"glusterfs-cli", "version":"3.7.*"}], + "packages":[{"name":"samba", "version":"4.2.*"}, + {"name":"tmux", "version":"2.2"}]} + + resp = self.server.post("/api/v0/recipes/new?branch=test", + data=json.dumps(test_recipe), + content_type="application/json") + data = json.loads(resp.data) + self.assertEqual(data, {"status":True}) + + resp = self.server.get("/api/v0/recipes/info/glusterfs?branch=test") + data = json.loads(resp.data) + self.assertNotEqual(data, None) + recipes = data.get("recipes") + self.assertEqual(len(recipes), 1) + self.assertEqual(recipes[0], test_recipe)