Skip to content

Commit 45d7f65

Browse files
authored
Merge pull request #1007 from operable/vanstee/cogctl-bundle-install
Add api route for installing via registry
2 parents 7891c9c + e3eaa38 commit 45d7f65

File tree

7 files changed

+120
-20
lines changed

7 files changed

+120
-20
lines changed

lib/cog/commands/bundle/install.ex

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
defmodule Cog.Commands.Bundle.Install do
22
alias Cog.Repository.Bundles
3-
alias Cog.BundleRegistry
43
alias Cog.Models.BundleVersion
54
require Cog.Commands.Helpers, as: Helpers
65

@@ -25,22 +24,13 @@ defmodule Cog.Commands.Bundle.Install do
2524
def install(req, [bundle]),
2625
do: install(req, [bundle, "latest"])
2726
def install(_req, [bundle, version]) do
28-
case BundleRegistry.get_config(bundle, version) do
29-
{:ok, %{"name" => name, "version" => version} = config} ->
30-
revised_config = %{"name" => name,
31-
"version" => version,
32-
"config_file" => config}
33-
34-
case Bundles.install(revised_config) do
35-
{:ok, %BundleVersion{bundle: bundle} = bundle_version} ->
36-
bundle = %{bundle | versions: [bundle_version]}
37-
rendered = Cog.V1.BundlesView.render("show.json", %{bundle: bundle})
38-
{:ok, "bundle-install", rendered[:bundle]}
39-
{:error, {:db_errors, [version: {"has already been taken", []}]}} ->
40-
{:error, {:already_installed, bundle, version}}
41-
{:error, error} ->
42-
{:error, error}
43-
end
27+
case Bundles.install_from_registry(bundle, version) do
28+
{:ok, %BundleVersion{bundle: bundle} = bundle_version} ->
29+
bundle = %{bundle | versions: [bundle_version]}
30+
rendered = Cog.V1.BundlesView.render("show.json", %{bundle: bundle})
31+
{:ok, "bundle-install", rendered[:bundle]}
32+
{:error, {:db_errors, [version: {"has already been taken", []}]}} ->
33+
{:error, {:already_installed, bundle, version}}
4434
{:error, error} ->
4535
{:error, error}
4636
end

lib/cog/repository/bundles.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule Cog.Repository.Bundles do
99
alias Cog.Models.{Bundle, BundleVersion, BundleDynamicConfig, CommandVersion, Rule}
1010
alias Cog.Repository.Rules
1111
alias Cog.Queries
12+
alias Cog.BundleRegistry
1213

1314
alias Cog.Models.Types.VersionTriple
1415
alias Ecto.Adapters.SQL
@@ -52,6 +53,18 @@ defmodule Cog.Repository.Bundles do
5253
def install(params),
5354
do: __install(params)
5455

56+
def install_from_registry(bundle, version) do
57+
case BundleRegistry.get_config(bundle, version) do
58+
{:ok, %{"name" => name, "version" => version} = config} ->
59+
revised_config = %{"name" => name,
60+
"version" => version,
61+
"config_file" => config}
62+
install(revised_config)
63+
{:error, error} ->
64+
{:error, error}
65+
end
66+
end
67+
5568
# TODO: clean this up so we only need the config file part;
5669
# everything else is redundant
5770
defp __install(%{"name" => _name, "version" => _version, "config_file" => _config}=params) do

test/controllers/v1/bundles_controller_test.exs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
defmodule Cog.V1.BundlesControllerTest do
2-
3-
require Logger
4-
52
use Cog.ModelCase
63
use Cog.ConnCase
4+
use ExVCR.Mock
75

86
alias Cog.Repository.Bundles
97

@@ -145,6 +143,24 @@ defmodule Cog.V1.BundlesControllerTest do
145143
assert json_response(conn, 422)["errors"]
146144
end
147145

146+
test "installs a registered bundle", %{authed: requestor} do
147+
ExVCR.Config.cassette_library_dir("test/fixtures/cassettes")
148+
149+
use_cassette "installs_a_registered_bundle" do
150+
conn = api_request(requestor, :post, "/v1/bundles/install/heroku/0.0.4")
151+
assert json_response(conn, 201)
152+
end
153+
end
154+
155+
test "fails to install a missing registered bundle", %{authed: requestor} do
156+
ExVCR.Config.cassette_library_dir("test/fixtures/cassettes")
157+
158+
use_cassette "installs_a_missing_registered_bundle" do
159+
conn = api_request(requestor, :post, "/v1/bundles/install/herooku/0.0.4")
160+
assert json_response(conn, 404)
161+
end
162+
end
163+
148164
test "shows disabled bundle", %{authed: requestor} do
149165
{:ok, _version3} = Bundles.install(%{"name" => "foo", "version" => "3.0.0", "config_file" => %{}})
150166
{:ok, _version2} = Bundles.install(%{"name" => "foo", "version" => "2.0.0", "config_file" => %{}})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"request": {
4+
"body": "",
5+
"headers": {
6+
"Accepts": "application/json"
7+
},
8+
"method": "get",
9+
"options": [],
10+
"request_body": "",
11+
"url": "https://bundles.operable.io/api/bundles/herooku/0.0.4"
12+
},
13+
"response": {
14+
"body": "\"Internal server error\"",
15+
"headers": {
16+
"Connection": "keep-alive",
17+
"Server": "Cowboy",
18+
"Date": "Fri, 30 Sep 2016 17:44:44 GMT",
19+
"Content-Length": "23",
20+
"Content-Type": "application/json; charset=utf-8",
21+
"Cache-Control": "max-age=0, private, must-revalidate",
22+
"Strict-Transport-Security": "max-age=31536000",
23+
"X-Request-Id": "e3f4d96a-7861-4b1b-991b-af305198f932",
24+
"Via": "1.1 vegur"
25+
},
26+
"status_code": 404,
27+
"type": "ok"
28+
}
29+
}
30+
]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"request": {
4+
"body": "",
5+
"headers": {
6+
"Accepts": "application/json"
7+
},
8+
"method": "get",
9+
"options": [],
10+
"request_body": "",
11+
"url": "https://bundles.operable.io/api/bundles/heroku/0.0.4"
12+
},
13+
"response": {
14+
"body": "{\"version\":\"0.0.4\",\"templates\":{\"user_list\":{\"body\":\"| Email | Role |\\n| ----- | ---- |\\n~each var=$results~\\n| ~$item.email~ | ~$item.role~ |\\n~end~\\n\"},\"release_list\":{\"body\":\"| Name | Description |\\n| ---- | ----------- |\\n~each var=$results~\\n| ~$item.name~ | ~$item.descr~ |\\n~end~\\n\"},\"release_info\":{\"body\":\"~if cond=length($results) == 1~\\n~each var=$results~\\n```\\nName: ~$item.name~\\nDescription: ~$item.descr~\\nReleased By: ~$item.user~\\nReleased At: ~$item.created_at~\\nCommit: ~$item.commit~\\n```\\n~end~\\n~end~\\n~if cond=length($results) > 1~\\n| Name | Description |\\n| ---- | ----------- |\\n~each var=$results~\\n| ~$item.name~ | ~$item.descr~ |\\n~end~\\n~end~\\n\"},\"ps_list\":{\"body\":\"| Process | Command | State |\\n| ------- | ------- | ----- |\\n~each var=$results~\\n| ~$item.process~ | ~$item.command~ | ~$item.pretty_state~ |\\n~end~\\n\"},\"config_list\":{\"body\":\"| Key | Value |\\n| --- | ----- |\\n~each var=$results~\\n| ~$item.key~ | ~$item.value~ |\\n~end~\\n\"},\"app_list\":{\"body\":\"| Name | Owner |\\n| ---- | ----- |\\n~each var=$results~\\n| ~$item.name~ | ~$item.owner_email~ |\\n~end~\"},\"app_info\":{\"body\":\"~if cond=length($results) == 1~\\n~each var=$results~\\n```\\nDynos: ~$item.dynos~\\nGit URL: ~$item.git_url~\\nOwner: ~$item.owner_email~\\nRegion: ~$item.region~\\nRepo Size: ~$item.repo_size~\\nSlug Size: ~$item.slug_size~\\nStack: ~$item.stack~\\nWeb URL: ~$item.web_url~\\n```\\n~end~\\n~end~\\n~if cond=length($results) > 1~\\n| Dynos | Git URL | Web URL |\\n| ----- | ------- | ------- |\\n~each var=$results~\\n| ~$item.dynos~ | ~$item.git_url~ | ~$item.web_url~ |\\n~end~\\n~end~\"}},\"permissions\":[\"heroku:read\",\"heroku:write\",\"heroku:admin\"],\"name\":\"heroku\",\"long_description\":\"This bundle provides functionality similar to the heroku toolbelt. In fact,\\nit actually uses the same underlying ruby library, so the data returned will\\nlook familiar.\\n\\nAll commands will require credentials which can be provided by setting them\\nwith dynamic config. You must set either HEROKU_API_KEY or both\\nHEROKU_USERNAME and HEROKU_PASSWORD.\\n\",\"homepage\":\"https://github.com/cogcmd/heroku\",\"docker\":{\"tag\":\"0.0.3\",\"image\":\"cogcmd/heroku\"},\"description\":\"Manage heroku apps and organizations\",\"commands\":{\"user\":{\"subcommands\":{\"remove <email>\":\"Remove user by email\",\"list\":\"List all users\",\"add <email>\":\"Add user by email\"},\"rules\":[\"when command is heroku:user must have heroku:read\",\"when command is heroku:user with arg[0] == 'add' must have heroku:write\",\"when command is heroku:user with arg[0] == 'remove' must have heroku:write\"],\"options\":{\"app\":{\"type\":\"string\",\"required\":true}},\"executable\":\"/home/bundle/cog-command\",\"examples\":\"List all users:\\n\\n heroku:user list --app frosty-mountain-7639\\n\\nAdd a new user:\\n\\n heroku:user add [email protected] --app frosty-mountain-7639\\n\\nRemove an existing user:\\n\\n heroku:user add [email protected] --app frosty-mountain-7639\\n\",\"description\":\"List and manage organization membership\",\"arguments\":\"[list | add <email> | remove <email>]\"},\"release\":{\"subcommands\":{\"rollback <release>\":\"Rollback the app to a previous release\",\"list\":\"List all releases\",\"info <release>\":\"Display details about a release\"},\"rules\":[\"when command is heroku:release must have heroku:read\",\"when command is heroku:release with arg[0] == 'rollback' must have heroku:write\"],\"options\":{\"app\":{\"type\":\"string\",\"required\":true}},\"executable\":\"/home/bundle/cog-command\",\"examples\":\"List all releases:\\n\\n heroku:release list --app frosty-mountain-7639\\n\\nLook at a release:\\n\\n heroku:release info v285 --app frosty-mountain-7639\\n\\nRollback to an older release:\\n\\n heroku:release rollback v284 --app frosty-mountain-7639\\n\",\"description\":\"List and rollback to older releases\",\"arguments\":\"[list | info <release> | rollback <release>]\"},\"ps\":{\"subcommands\":{\"scale <dyno=amount> ...\":\"Scale dynos to the specified amount\",\"restart <dyno>\":\"Restart a dyno\",\"list\":\"List all processes (default)\"},\"rules\":[\"when command is heroku:ps must have heroku:read\",\"when command is heroku:ps with arg[0] == 'scale' must have heroku:write\",\"when command is heroku:ps with arg[0] == 'restart' must have heroku:write\"],\"options\":{\"app\":{\"type\":\"string\",\"required\":true}},\"executable\":\"/home/bundle/cog-command\",\"examples\":\"List all processes:\\n\\n heroku:ps --app frosty-mountain-7639\\n\\nScale up the web and api processes:\\n\\n heroku:ps scale web=4 api=8 --app frosty-mountain-7639\\n\\nRestart the web.1 process:\\n\\n heroku:ps restart web.1 --app frosty-mountain-7639\\n\",\"description\":\"List and modify processes of an app\",\"arguments\":\"[list | scale <dyno=amount> ... | restart <dyno>]\"},\"config\":{\"subcommands\":{\"unset\":\"Unset an environment variable\",\"set\":\"Set an environment variable\",\"list\":\"List all environment variables\"},\"rules\":[\"when command is heroku:config must have heroku:admin\",\"when command is heroku:config must have heroku:read\",\"when command is heroku:config with arg[0] == 'set' must have heroku:write\",\"when command is heroku:config with arg[0] == 'unset' must have heroku:write\"],\"options\":{\"app\":{\"type\":\"string\",\"required\":true}},\"executable\":\"/home/bundle/cog-command\",\"examples\":\"List all environment variables:\\n\\n heroku:config --app frosty-mountain-7639\\n\\nSet a new environment variable:\\n\\n heroku:config set LOG_LEVEL=warn --app frosty-mountain-7639\\n\\nUnset a new environment variable:\\n\\n heroku:config unset DEBUG --app frosty-mountain-7639\\n\",\"description\":\"List and modify environment variables for an app\",\"arguments\":\"[list | set <name=value> | unset <name>]\"},\"app\":{\"subcommands\":{\"list\":\"List all apps (default)\",\"info <app>\":\"Display details about an app\"},\"rules\":[\"when command is heroku:app must have heroku:read\"],\"executable\":\"/home/bundle/cog-command\",\"examples\":\"List all apps:\\n\\n heroku:app list\\n\\nView details about a specific app:\\n\\n heroku:app info frosty-mountain-7639\\n\",\"description\":\"List and display info about apps\",\"arguments\":\"[list | info <app>]\"}},\"cog_bundle_version\":4,\"author\":\"Patrick Van Stee <[email protected]>\"}",
15+
"headers": {
16+
"Connection": "keep-alive",
17+
"Server": "Cowboy",
18+
"Date": "Fri, 30 Sep 2016 17:49:30 GMT",
19+
"Content-Length": "6046",
20+
"Content-Type": "application/json; charset=utf-8",
21+
"Cache-Control": "max-age=0, private, must-revalidate",
22+
"Strict-Transport-Security": "max-age=31536000",
23+
"X-Request-Id": "c70acf65-06bb-4bd7-9ffd-20fc55529186",
24+
"Via": "1.1 vegur"
25+
},
26+
"status_code": 200,
27+
"type": "ok"
28+
}
29+
}
30+
]

web/controllers/v1/bundles_controller.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ defmodule Cog.V1.BundlesController do
5959
def create(conn, _params),
6060
do: send_resp(conn, 400, "")
6161

62+
def install(conn, %{"bundle" => bundle, "version" => version}) do
63+
case Repository.Bundles.install_from_registry(bundle, version) do
64+
{:ok, bundle_version} ->
65+
conn
66+
|> put_status(:created)
67+
|> put_resp_header("location", Cog.Router.Helpers.bundle_version_path(conn, :show, bundle_version.bundle.id, bundle_version.id))
68+
|> render(Cog.V1.BundleVersionView, "show.json", %{bundle_version: bundle_version, warnings: []})
69+
{:error, error} ->
70+
send_failure(conn, error)
71+
end
72+
end
73+
6274
########################################################################
6375
# Bundle Creation Helpers
6476

@@ -162,6 +174,14 @@ defmodule Cog.V1.BundlesController do
162174
errors = Enum.map(errors, fn({_, {message, []}}) -> message end)
163175
{:unprocessable_entity, %{errors: msg ++ errors}}
164176
end
177+
defp error({:not_found, bundle}) do
178+
msg = ["Bundle #{inspect bundle} not found."]
179+
{:not_found, %{errors: msg}}
180+
end
181+
defp error({:not_found, bundle, version}) do
182+
msg = ["Bundle #{inspect bundle} version #{inspect version} not found."]
183+
{:not_found, %{errors: msg}}
184+
end
165185
defp error(err) do
166186
msg = inspect(err)
167187
{:unprocessable_entity, %{error: msg}}

web/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ defmodule Cog.Router do
4545
# Bundles
4646

4747
resources "/v1/bundles", V1.BundlesController, only: [:index, :create, :show, :delete]
48+
post "/v1/bundles/install/:bundle/:version/", V1.BundlesController, :install
4849

4950
resources "/v1/bundles/:bundle_id/versions/", V1.BundleVersionController, only: [:index, :show, :delete]
5051

0 commit comments

Comments
 (0)