Add support for other branches to the routes

Passing ?branch=<branch-name> 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.
This commit is contained in:
Brian C. Lane 2017-12-21 09:44:59 -08:00
parent 0af072c77e
commit 916a001d73
4 changed files with 67 additions and 23 deletions

View File

@ -245,6 +245,14 @@ def write_commit(repo, branch, filename, message, content):
:rtype: Git.OId :rtype: Git.OId
:raises: Can raise errors from Ggit :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) parent_commit = head_commit(repo, branch)
blob_id = repo.create_blob_from_buffer(content) blob_id = repo.create_blob_from_buffer(content)

View File

@ -66,6 +66,7 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_list(): def v0_recipes_list():
"""List the available recipes on a branch.""" """List the available recipes on a branch."""
branch = request.args.get("branch", "master")
try: try:
limit = int(request.args.get("limit", "20")) limit = int(request.args.get("limit", "20"))
offset = int(request.args.get("offset", "0")) offset = int(request.args.get("offset", "0"))
@ -73,13 +74,14 @@ def v0_api(api):
return jsonify(error={"msg":str(e)}), 400 return jsonify(error={"msg":str(e)}), 400
with api.config["GITLOCK"].lock: 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)) return jsonify(recipes=recipes, limit=limit, offset=offset, total=len(recipes))
@api.route("/api/v0/recipes/info/<recipe_names>") @api.route("/api/v0/recipes/info/<recipe_names>")
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_info(recipe_names): def v0_recipes_info(recipe_names):
"""Return the contents of the recipe, or a list of recipes""" """Return the contents of the recipe, or a list of recipes"""
branch = request.args.get("branch", "master")
recipes = [] recipes = []
changes = [] changes = []
errors = [] errors = []
@ -88,7 +90,7 @@ def v0_api(api):
# Get the workspace version (if it exists) # Get the workspace version (if it exists)
try: try:
with api.config["GITLOCK"].lock: 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: except Exception as e:
ws_recipe = None ws_recipe = None
exceptions.append(str(e)) exceptions.append(str(e))
@ -97,7 +99,7 @@ def v0_api(api):
# Get the git version (if it exists) # Get the git version (if it exists)
try: try:
with api.config["GITLOCK"].lock: 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: except Exception as e:
git_recipe = None git_recipe = None
exceptions.append(str(e)) exceptions.append(str(e))
@ -130,6 +132,7 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_changes(recipe_names): def v0_recipes_changes(recipe_names):
"""Return the changes to a recipe or list of recipes""" """Return the changes to a recipe or list of recipes"""
branch = request.args.get("branch", "master")
try: try:
limit = int(request.args.get("limit", "20")) limit = int(request.args.get("limit", "20"))
offset = int(request.args.get("offset", "0")) offset = int(request.args.get("offset", "0"))
@ -142,7 +145,7 @@ def v0_api(api):
filename = recipe_filename(recipe_name) filename = recipe_filename(recipe_name)
try: try:
with api.config["GITLOCK"].lock: 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: except Exception as e:
errors.append({"recipe":recipe_name, "msg":e}) errors.append({"recipe":recipe_name, "msg":e})
log.error("(v0_recipes_changes) %s", str(e)) log.error("(v0_recipes_changes) %s", str(e))
@ -158,6 +161,7 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_new(): def v0_recipes_new():
"""Commit a new recipe""" """Commit a new recipe"""
branch = request.args.get("branch", "master")
try: try:
if request.headers['Content-Type'] == "text/x-toml": if request.headers['Content-Type'] == "text/x-toml":
recipe = recipe_from_toml(request.data) recipe = recipe_from_toml(request.data)
@ -165,11 +169,11 @@ def v0_api(api):
recipe = recipe_from_dict(request.get_json(cache=False)) recipe = recipe_from_dict(request.get_json(cache=False))
with api.config["GITLOCK"].lock: 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 # Read the recipe with new version and write it to the workspace
recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe["name"]) recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe["name"])
workspace_write(api.config["GITLOCK"].repo, "master", recipe) workspace_write(api.config["GITLOCK"].repo, branch, recipe)
except Exception as e: except Exception as e:
log.error("(v0_recipes_new) %s", str(e)) log.error("(v0_recipes_new) %s", str(e))
return jsonify(status=False, error={"msg":str(e)}), 400 return jsonify(status=False, error={"msg":str(e)}), 400
@ -180,9 +184,10 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_delete(recipe_name): def v0_recipes_delete(recipe_name):
"""Delete a recipe from git""" """Delete a recipe from git"""
branch = request.args.get("branch", "master")
try: try:
with api.config["GITLOCK"].lock: 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: except Exception as e:
log.error("(v0_recipes_delete) %s", str(e)) log.error("(v0_recipes_delete) %s", str(e))
return jsonify(status=False, error={"msg":str(e)}), 400 return jsonify(status=False, error={"msg":str(e)}), 400
@ -193,6 +198,7 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_workspace(): def v0_recipes_workspace():
"""Write a recipe to the workspace""" """Write a recipe to the workspace"""
branch = request.args.get("branch", "master")
try: try:
if request.headers['Content-Type'] == "text/x-toml": if request.headers['Content-Type'] == "text/x-toml":
recipe = recipe_from_toml(request.data) recipe = recipe_from_toml(request.data)
@ -200,7 +206,7 @@ def v0_api(api):
recipe = recipe_from_dict(request.get_json(cache=False)) recipe = recipe_from_dict(request.get_json(cache=False))
with api.config["GITLOCK"].lock: 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: except Exception as e:
log.error("(v0_recipes_workspace) %s", str(e)) log.error("(v0_recipes_workspace) %s", str(e))
return jsonify(status=False, error={"msg":str(e)}), 400 return jsonify(status=False, error={"msg":str(e)}), 400
@ -211,9 +217,10 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_delete_workspace(recipe_name): def v0_recipes_delete_workspace(recipe_name):
"""Delete a recipe from the workspace""" """Delete a recipe from the workspace"""
branch = request.args.get("branch", "master")
try: try:
with api.config["GITLOCK"].lock: 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: except Exception as e:
log.error("(v0_recipes_delete_workspace) %s", str(e)) log.error("(v0_recipes_delete_workspace) %s", str(e))
return jsonify(status=False, error={"msg":str(e)}), 400 return jsonify(status=False, error={"msg":str(e)}), 400
@ -224,13 +231,14 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_undo(recipe_name, commit): def v0_recipes_undo(recipe_name, commit):
"""Undo changes to a recipe by reverting to a previous commit.""" """Undo changes to a recipe by reverting to a previous commit."""
branch = request.args.get("branch", "master")
try: try:
with api.config["GITLOCK"].lock: 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 # Read the new recipe and write it to the workspace
recipe = read_recipe_commit(api.config["GITLOCK"].repo, "master", recipe_name) recipe = read_recipe_commit(api.config["GITLOCK"].repo, branch, recipe_name)
workspace_write(api.config["GITLOCK"].repo, "master", recipe) workspace_write(api.config["GITLOCK"].repo, branch, recipe)
except Exception as e: except Exception as e:
log.error("(v0_recipes_undo) %s", str(e)) log.error("(v0_recipes_undo) %s", str(e))
return jsonify(status=False, error={"msg":str(e)}), 400 return jsonify(status=False, error={"msg":str(e)}), 400
@ -241,9 +249,10 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_tag(recipe_name): def v0_recipes_tag(recipe_name):
"""Tag a recipe's latest recipe commit as a 'revision'""" """Tag a recipe's latest recipe commit as a 'revision'"""
branch = request.args.get("branch", "master")
try: try:
with api.config["GITLOCK"].lock: 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: except Exception as e:
log.error("(v0_recipes_tag) %s", str(e)) log.error("(v0_recipes_tag) %s", str(e))
return jsonify(status=False, error={"msg":str(e)}), 400 return jsonify(status=False, error={"msg":str(e)}), 400
@ -254,13 +263,14 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_diff(recipe_name, from_commit, to_commit): def v0_recipes_diff(recipe_name, from_commit, to_commit):
"""Return the differences between two commits of a recipe""" """Return the differences between two commits of a recipe"""
branch = request.args.get("branch", "master")
try: try:
if from_commit == "NEWEST": if from_commit == "NEWEST":
with api.config["GITLOCK"].lock: 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: else:
with api.config["GITLOCK"].lock: 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: except Exception as e:
log.error("(v0_recipes_diff) %s", str(e)) log.error("(v0_recipes_diff) %s", str(e))
return jsonify(error={"msg":str(e)}), 400 return jsonify(error={"msg":str(e)}), 400
@ -268,13 +278,13 @@ def v0_api(api):
try: try:
if to_commit == "WORKSPACE": if to_commit == "WORKSPACE":
with api.config["GITLOCK"].lock: 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": elif to_commit == "NEWEST":
with api.config["GITLOCK"].lock: 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: else:
with api.config["GITLOCK"].lock: 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: except Exception as e:
log.error("(v0_recipes_diff) %s", str(e)) log.error("(v0_recipes_diff) %s", str(e))
return jsonify(error={"msg":str(e)}), 400 return jsonify(error={"msg":str(e)}), 400
@ -286,6 +296,7 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_freeze(recipe_names): def v0_recipes_freeze(recipe_names):
"""Return the recipe with the exact modules and packages selected by depsolve""" """Return the recipe with the exact modules and packages selected by depsolve"""
branch = request.args.get("branch", "master")
recipes = [] recipes = []
errors = [] errors = []
for recipe_name in [n.strip() for n in sorted(recipe_names.split(","), key=lambda n: n.lower())]: 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 recipe = None
try: try:
with api.config["GITLOCK"].lock: 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: except Exception:
pass pass
@ -302,7 +313,7 @@ def v0_api(api):
# No workspace version, get the git version (if it exists) # No workspace version, get the git version (if it exists)
try: try:
with api.config["GITLOCK"].lock: 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: except Exception as e:
errors.append({"recipe":recipe_name, "msg":str(e)}) errors.append({"recipe":recipe_name, "msg":str(e)})
log.error("(v0_recipes_freeze) %s", str(e)) log.error("(v0_recipes_freeze) %s", str(e))
@ -346,6 +357,7 @@ def v0_api(api):
@crossdomain(origin="*") @crossdomain(origin="*")
def v0_recipes_depsolve(recipe_names): def v0_recipes_depsolve(recipe_names):
"""Return the dependencies for a recipe""" """Return the dependencies for a recipe"""
branch = request.args.get("branch", "master")
recipes = [] recipes = []
errors = [] errors = []
for recipe_name in [n.strip() for n in sorted(recipe_names.split(","), key=lambda n: n.lower())]: 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 recipe = None
try: try:
with api.config["GITLOCK"].lock: 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: except Exception:
pass pass
@ -362,7 +374,7 @@ def v0_api(api):
# No workspace version, get the git version (if it exists) # No workspace version, get the git version (if it exists)
try: try:
with api.config["GITLOCK"].lock: 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: except Exception as e:
errors.append({"recipe":recipe_name, "msg":str(e)}) errors.append({"recipe":recipe_name, "msg":str(e)})
log.error("(v0_recipes_depsolve) %s", str(e)) log.error("(v0_recipes_depsolve) %s", str(e))

View File

@ -27,6 +27,7 @@ class LoraxLintConfig(PocketLintConfig):
FalsePositive(r"Module 'pylorax' has no 'version' member"), FalsePositive(r"Module 'pylorax' has no 'version' member"),
FalsePositive(r"Instance of 'int' has no .* member"), FalsePositive(r"Instance of 'int' has no .* member"),
FalsePositive(r"Catching too general exception Exception"), FalsePositive(r"Catching too general exception Exception"),
FalsePositive(r"^E0712.*: Catching an exception which doesn't inherit from (Base|)Exception: GError$"),
] ]
@property @property

View File

@ -460,3 +460,26 @@ class ServerTestCase(unittest.TestCase):
self.assertEqual(len(modules), 1) self.assertEqual(len(modules), 1)
self.assertEqual(modules[0]["name"], "bash") self.assertEqual(modules[0]["name"], "bash")
self.assertEqual(modules[0]["dependencies"][0]["name"], "basesystem") 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)