Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 7 additions & 17 deletions lib/cog/commands/bundle/install.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
defmodule Cog.Commands.Bundle.Install do
alias Cog.Repository.Bundles
alias Cog.BundleRegistry
alias Cog.Models.BundleVersion
require Cog.Commands.Helpers, as: Helpers

Expand All @@ -25,22 +24,13 @@ defmodule Cog.Commands.Bundle.Install do
def install(req, [bundle]),
do: install(req, [bundle, "latest"])
def install(_req, [bundle, version]) do
case BundleRegistry.get_config(bundle, version) do
{:ok, %{"name" => name, "version" => version} = config} ->
revised_config = %{"name" => name,
"version" => version,
"config_file" => config}

case Bundles.install(revised_config) do
{:ok, %BundleVersion{bundle: bundle} = bundle_version} ->
bundle = %{bundle | versions: [bundle_version]}
rendered = Cog.V1.BundlesView.render("show.json", %{bundle: bundle})
{:ok, "bundle-install", rendered[:bundle]}
{:error, {:db_errors, [version: {"has already been taken", []}]}} ->
{:error, {:already_installed, bundle, version}}
{:error, error} ->
{:error, error}
end
case Bundles.install_from_registry(bundle, version) do
{:ok, %BundleVersion{bundle: bundle} = bundle_version} ->
bundle = %{bundle | versions: [bundle_version]}
rendered = Cog.V1.BundlesView.render("show.json", %{bundle: bundle})
{:ok, "bundle-install", rendered[:bundle]}
{:error, {:db_errors, [version: {"has already been taken", []}]}} ->
{:error, {:already_installed, bundle, version}}
{:error, error} ->
{:error, error}
end
Expand Down
13 changes: 13 additions & 0 deletions lib/cog/repository/bundles.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Cog.Repository.Bundles do
alias Cog.Models.{Bundle, BundleVersion, BundleDynamicConfig, CommandVersion, Rule}
alias Cog.Repository.Rules
alias Cog.Queries
alias Cog.BundleRegistry

alias Cog.Models.Types.VersionTriple
alias Ecto.Adapters.SQL
Expand Down Expand Up @@ -52,6 +53,18 @@ defmodule Cog.Repository.Bundles do
def install(params),
do: __install(params)

def install_from_registry(bundle, version) do
case BundleRegistry.get_config(bundle, version) do
{:ok, %{"name" => name, "version" => version} = config} ->
revised_config = %{"name" => name,
"version" => version,
"config_file" => config}
install(revised_config)
{:error, error} ->
{:error, error}
end
end

# TODO: clean this up so we only need the config file part;
# everything else is redundant
defp __install(%{"name" => _name, "version" => _version, "config_file" => _config}=params) do
Expand Down
22 changes: 19 additions & 3 deletions test/controllers/v1/bundles_controller_test.exs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
defmodule Cog.V1.BundlesControllerTest do

require Logger

use Cog.ModelCase
use Cog.ConnCase
use ExVCR.Mock

alias Cog.Repository.Bundles

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

test "installs a registered bundle", %{authed: requestor} do
ExVCR.Config.cassette_library_dir("test/fixtures/cassettes")

use_cassette "installs_a_registered_bundle" do
conn = api_request(requestor, :post, "/v1/bundles/install/heroku/0.0.4")
assert json_response(conn, 201)
end
end

test "fails to install a missing registered bundle", %{authed: requestor} do
ExVCR.Config.cassette_library_dir("test/fixtures/cassettes")

use_cassette "installs_a_missing_registered_bundle" do
conn = api_request(requestor, :post, "/v1/bundles/install/herooku/0.0.4")
assert json_response(conn, 404)
end
end

test "shows disabled bundle", %{authed: requestor} do
{:ok, _version3} = Bundles.install(%{"name" => "foo", "version" => "3.0.0", "config_file" => %{}})
{:ok, _version2} = Bundles.install(%{"name" => "foo", "version" => "2.0.0", "config_file" => %{}})
Expand Down
30 changes: 30 additions & 0 deletions test/fixtures/cassettes/installs_a_missing_registered_bundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"request": {
"body": "",
"headers": {
"Accepts": "application/json"
},
"method": "get",
"options": [],
"request_body": "",
"url": "https://bundles.operable.io/api/bundles/herooku/0.0.4"
},
"response": {
"body": "\"Internal server error\"",
"headers": {
"Connection": "keep-alive",
"Server": "Cowboy",
"Date": "Fri, 30 Sep 2016 17:44:44 GMT",
"Content-Length": "23",
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "max-age=0, private, must-revalidate",
"Strict-Transport-Security": "max-age=31536000",
"X-Request-Id": "e3f4d96a-7861-4b1b-991b-af305198f932",
"Via": "1.1 vegur"
},
"status_code": 404,
"type": "ok"
}
}
]
30 changes: 30 additions & 0 deletions test/fixtures/cassettes/installs_a_registered_bundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"request": {
"body": "",
"headers": {
"Accepts": "application/json"
},
"method": "get",
"options": [],
"request_body": "",
"url": "https://bundles.operable.io/api/bundles/heroku/0.0.4"
},
"response": {
"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]>\"}",
"headers": {
"Connection": "keep-alive",
"Server": "Cowboy",
"Date": "Fri, 30 Sep 2016 17:49:30 GMT",
"Content-Length": "6046",
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "max-age=0, private, must-revalidate",
"Strict-Transport-Security": "max-age=31536000",
"X-Request-Id": "c70acf65-06bb-4bd7-9ffd-20fc55529186",
"Via": "1.1 vegur"
},
"status_code": 200,
"type": "ok"
}
}
]
20 changes: 20 additions & 0 deletions web/controllers/v1/bundles_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ defmodule Cog.V1.BundlesController do
def create(conn, _params),
do: send_resp(conn, 400, "")

def install(conn, %{"bundle" => bundle, "version" => version}) do
case Repository.Bundles.install_from_registry(bundle, version) do
{:ok, bundle_version} ->
conn
|> put_status(:created)
|> put_resp_header("location", Cog.Router.Helpers.bundle_version_path(conn, :show, bundle_version.bundle.id, bundle_version.id))
|> render(Cog.V1.BundleVersionView, "show.json", %{bundle_version: bundle_version, warnings: []})
{:error, error} ->
send_failure(conn, error)
end
end

########################################################################
# Bundle Creation Helpers

Expand Down Expand Up @@ -162,6 +174,14 @@ defmodule Cog.V1.BundlesController do
errors = Enum.map(errors, fn({_, {message, []}}) -> message end)
{:unprocessable_entity, %{errors: msg ++ errors}}
end
defp error({:not_found, bundle}) do
msg = ["Bundle #{inspect bundle} not found."]
{:not_found, %{errors: msg}}
end
defp error({:not_found, bundle, version}) do
msg = ["Bundle #{inspect bundle} version #{inspect version} not found."]
{:not_found, %{errors: msg}}
end
defp error(err) do
msg = inspect(err)
{:unprocessable_entity, %{error: msg}}
Expand Down
1 change: 1 addition & 0 deletions web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ defmodule Cog.Router do
# Bundles

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

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

Expand Down