mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-04 12:04:20 +08:00
Implement authorization to deploy apps (#3044)
This commit is contained in:
parent
f0a1a3cfac
commit
9043edb748
14 changed files with 344 additions and 27 deletions
|
@ -164,6 +164,14 @@ defmodule Livebook.Hubs.TeamClient do
|
|||
GenServer.call(registry_name(id), {:check_app_access, groups, slug})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns if the given user has access to deploy apps to given deployment group.
|
||||
"""
|
||||
@spec user_can_deploy?(String.t(), pos_integer() | nil, String.t()) :: boolean()
|
||||
def user_can_deploy?(id, user_id, deployment_group_id) do
|
||||
GenServer.call(registry_name(id), {:user_can_deploy?, user_id, deployment_group_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns if the Team client is connected.
|
||||
"""
|
||||
|
@ -338,6 +346,30 @@ defmodule Livebook.Hubs.TeamClient do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_call({:user_can_deploy?, user_id, id}, _caller, state) do
|
||||
# App servers/Offline instances should not be able to deploy apps
|
||||
if state.deployment_group_id || user_id == nil do
|
||||
{:reply, false, state}
|
||||
else
|
||||
case fetch_deployment_group(id, state) do
|
||||
{:ok, deployment_group} ->
|
||||
deployment_user = %Teams.DeploymentUser{
|
||||
user_id: to_string(user_id),
|
||||
deployment_group_id: id
|
||||
}
|
||||
|
||||
authorized? =
|
||||
not deployment_group.deploy_auth or
|
||||
deployment_user in deployment_group.deployment_users
|
||||
|
||||
{:reply, authorized?, state}
|
||||
|
||||
_ ->
|
||||
{:reply, false, state}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:connected, state) do
|
||||
Hubs.Broadcasts.hub_connected(state.hub.id)
|
||||
|
@ -499,6 +531,7 @@ defmodule Livebook.Hubs.TeamClient do
|
|||
agent_keys = Enum.map(deployment_group.agent_keys, &build_agent_key/1)
|
||||
environment_variables = build_environment_variables(state, deployment_group)
|
||||
authorization_groups = build_authorization_groups(deployment_group)
|
||||
deployment_users = build_deployment_users(deployment_group)
|
||||
|
||||
%Teams.DeploymentGroup{
|
||||
id: deployment_group.id,
|
||||
|
@ -512,7 +545,9 @@ defmodule Livebook.Hubs.TeamClient do
|
|||
url: nullify(deployment_group.url),
|
||||
teams_auth: deployment_group.teams_auth,
|
||||
groups_auth: deployment_group.groups_auth,
|
||||
authorization_groups: authorization_groups
|
||||
deploy_auth: deployment_group.deploy_auth,
|
||||
authorization_groups: authorization_groups,
|
||||
deployment_users: deployment_users
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -530,7 +565,8 @@ defmodule Livebook.Hubs.TeamClient do
|
|||
clustering: nullify(deployment_group_created.clustering),
|
||||
url: nullify(deployment_group_created.url),
|
||||
teams_auth: deployment_group_created.teams_auth,
|
||||
authorization_groups: []
|
||||
authorization_groups: [],
|
||||
deployment_users: []
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -539,6 +575,7 @@ defmodule Livebook.Hubs.TeamClient do
|
|||
agent_keys = Enum.map(deployment_group_updated.agent_keys, &build_agent_key/1)
|
||||
environment_variables = build_environment_variables(state, deployment_group_updated)
|
||||
authorization_groups = build_authorization_groups(deployment_group_updated)
|
||||
deployment_users = build_deployment_users(deployment_group_updated)
|
||||
|
||||
{:ok, deployment_group} = fetch_deployment_group(deployment_group_updated.id, state)
|
||||
|
||||
|
@ -552,7 +589,9 @@ defmodule Livebook.Hubs.TeamClient do
|
|||
url: nullify(deployment_group_updated.url),
|
||||
teams_auth: deployment_group_updated.teams_auth,
|
||||
groups_auth: deployment_group_updated.groups_auth,
|
||||
authorization_groups: authorization_groups
|
||||
deploy_auth: deployment_group_updated.deploy_auth,
|
||||
authorization_groups: authorization_groups,
|
||||
deployment_users: deployment_users
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -596,6 +635,15 @@ defmodule Livebook.Hubs.TeamClient do
|
|||
end
|
||||
end
|
||||
|
||||
defp build_deployment_users(%{deployment_users: deployment_users}) do
|
||||
for deployment_user <- deployment_users do
|
||||
%Teams.DeploymentUser{
|
||||
user_id: deployment_user.user_id,
|
||||
deployment_group_id: deployment_user.deployment_group_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp put_agent(state, agent) do
|
||||
state = remove_agent(state, agent)
|
||||
|
||||
|
@ -696,11 +744,19 @@ defmodule Livebook.Hubs.TeamClient do
|
|||
|
||||
with {:ok, current_deployment_group} <- fetch_deployment_group(deployment_group.id, state) do
|
||||
if state.deployment_group_id == deployment_group.id and
|
||||
(current_deployment_group.authorization_groups != deployment_group.authorization_groups or
|
||||
(current_deployment_group.authorization_groups !=
|
||||
deployment_group.authorization_groups or
|
||||
current_deployment_group.groups_auth != deployment_group.groups_auth or
|
||||
current_deployment_group.teams_auth != deployment_group.teams_auth) do
|
||||
Teams.Broadcasts.server_authorization_updated(deployment_group)
|
||||
end
|
||||
|
||||
if state.deployment_group_id == nil and
|
||||
(current_deployment_group.deployment_users !=
|
||||
deployment_group.deployment_users or
|
||||
current_deployment_group.deploy_auth != deployment_group.deploy_auth) do
|
||||
Teams.Broadcasts.deployment_users_updated(deployment_group)
|
||||
end
|
||||
end
|
||||
|
||||
put_deployment_group(state, deployment_group)
|
||||
|
|
|
@ -294,4 +294,12 @@ defmodule Livebook.Teams do
|
|||
defp add_external_errors(struct, errors_map) do
|
||||
struct |> Ecto.Changeset.change() |> add_external_errors(errors_map)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given user has access to deploy apps to given deployment group.
|
||||
"""
|
||||
@spec user_can_deploy?(Team.t(), Teams.DeploymentGroup.t()) :: boolean()
|
||||
def user_can_deploy?(%Team{} = team, %Teams.DeploymentGroup{} = deployment_group) do
|
||||
TeamClient.user_can_deploy?(team.id, team.user_id, deployment_group.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,6 +34,7 @@ defmodule Livebook.Teams.Broadcasts do
|
|||
* `{:deployment_group_created, DeploymentGroup.t()}`
|
||||
* `{:deployment_group_updated, DeploymentGroup.t()}`
|
||||
* `{:deployment_group_deleted, DeploymentGroup.t()}`
|
||||
* `{:deployment_users_updated, DeploymentGroup.t()}`
|
||||
|
||||
Topic `#{@app_server_topic}`:
|
||||
|
||||
|
@ -97,6 +98,14 @@ defmodule Livebook.Teams.Broadcasts do
|
|||
broadcast(@deployment_groups_topic, {:deployment_group_deleted, deployment_group})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Broadcasts under `#{@deployment_groups_topic}` topic when hub received an updated deployment group that changed which users have access to deploy apps.
|
||||
"""
|
||||
@spec deployment_users_updated(Teams.DeploymentGroup.t()) :: broadcast()
|
||||
def deployment_users_updated(%Teams.DeploymentGroup{} = deployment_group) do
|
||||
broadcast(@deployment_groups_topic, {:deployment_users_updated, deployment_group})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Broadcasts under `#{@app_deployments_topic}` topic when hub received to start a new app deployment.
|
||||
"""
|
||||
|
@ -138,7 +147,7 @@ defmodule Livebook.Teams.Broadcasts do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Broadcasts under `#{@app_server_topic}` topic when hub received a updated deployment group that changed which groups have access to the server.
|
||||
Broadcasts under `#{@app_server_topic}` topic when hub received an updated deployment group that changed which groups have access to the server.
|
||||
"""
|
||||
@spec server_authorization_updated(Teams.DeploymentGroup.t()) :: broadcast()
|
||||
def server_authorization_updated(%Teams.DeploymentGroup{} = deployment_group) do
|
||||
|
|
|
@ -15,6 +15,7 @@ defmodule Livebook.Teams.DeploymentGroup do
|
|||
teams_auth: boolean(),
|
||||
groups_auth: boolean(),
|
||||
authorization_groups: Ecto.Schema.embeds_many(Teams.AuthorizationGroup.t()),
|
||||
deployment_users: Ecto.Schema.embeds_many(Teams.DeploymentUser.t()),
|
||||
secrets: Ecto.Schema.has_many(Secrets.Secret.t()),
|
||||
agent_keys: Ecto.Schema.has_many(Teams.AgentKey.t()),
|
||||
environment_variables: Ecto.Schema.has_many(Teams.EnvironmentVariable.t())
|
||||
|
@ -29,11 +30,13 @@ defmodule Livebook.Teams.DeploymentGroup do
|
|||
field :url, :string
|
||||
field :teams_auth, :boolean, default: true
|
||||
field :groups_auth, :boolean, default: false
|
||||
field :deploy_auth, :boolean, default: false
|
||||
|
||||
has_many :secrets, Secrets.Secret
|
||||
has_many :agent_keys, Teams.AgentKey
|
||||
has_many :environment_variables, Teams.EnvironmentVariable
|
||||
embeds_many :authorization_groups, Teams.AuthorizationGroup
|
||||
embeds_many :deployment_users, Teams.DeploymentUser
|
||||
end
|
||||
|
||||
def changeset(deployment_group, attrs \\ %{}) do
|
||||
|
|
14
lib/livebook/teams/deployment_user.ex
Normal file
14
lib/livebook/teams/deployment_user.ex
Normal file
|
@ -0,0 +1,14 @@
|
|||
defmodule Livebook.Teams.DeploymentUser do
|
||||
use Ecto.Schema
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
user_id: String.t() | nil,
|
||||
deployment_group_id: String.t() | nil
|
||||
}
|
||||
|
||||
@primary_key false
|
||||
embedded_schema do
|
||||
field :user_id, :string
|
||||
field :deployment_group_id, :string
|
||||
end
|
||||
end
|
|
@ -8,6 +8,7 @@ defmodule Livebook.Teams.Requests do
|
|||
@deploy_key_prefix Teams.Constants.deploy_key_prefix()
|
||||
@error_message "Something went wrong, try again later or please file a bug if it persists"
|
||||
@unauthorized_error_message "You are not authorized to perform this action, make sure you have the access and you are not in a Livebook App Server/Offline instance"
|
||||
@unauthorized_app_deployment_error_message "You are not authorized to perform this action, make sure you have the access to deploy apps to this deployment group"
|
||||
|
||||
@typep api_result :: {:ok, map()} | error_result()
|
||||
@typep error_result :: {:error, map() | String.t()} | {:transport_error, String.t()}
|
||||
|
@ -300,6 +301,11 @@ defmodule Livebook.Teams.Requests do
|
|||
defp upload(path, content, params, team) do
|
||||
build_req(team)
|
||||
|> Req.Request.put_header("content-length", "#{byte_size(content)}")
|
||||
|> Req.Request.append_response_steps(
|
||||
livebook_put_private: fn {request, response} ->
|
||||
{request, Req.Response.put_private(response, :livebook_app_deployment, true)}
|
||||
end
|
||||
)
|
||||
|> Req.post(url: path, params: params, body: content)
|
||||
|> handle_response()
|
||||
|> dispatch_messages(team)
|
||||
|
@ -337,10 +343,20 @@ defmodule Livebook.Teams.Requests do
|
|||
|
||||
defp handle_response(response) do
|
||||
case response do
|
||||
{:ok, %{status: status} = response} when status in 200..299 -> {:ok, response.body}
|
||||
{:ok, %{status: status} = response} when status in [410, 422] -> return_error(response)
|
||||
{:ok, %{status: 401}} -> {:transport_error, @unauthorized_error_message}
|
||||
_otherwise -> {:transport_error, @error_message}
|
||||
{:ok, %{status: status} = response} when status in 200..299 ->
|
||||
{:ok, response.body}
|
||||
|
||||
{:ok, %{status: status} = response} when status in [410, 422] ->
|
||||
return_error(response)
|
||||
|
||||
{:ok, %{status: 401, private: %{livebook_app_deployment: true}}} ->
|
||||
{:transport_error, @unauthorized_app_deployment_error_message}
|
||||
|
||||
{:ok, %{status: 401}} ->
|
||||
{:transport_error, @unauthorized_error_message}
|
||||
|
||||
_otherwise ->
|
||||
{:transport_error, @error_message}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
|||
messages={@messages}
|
||||
action={@action}
|
||||
initial?={@initial?}
|
||||
authorized={@authorized}
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
|
@ -162,7 +163,12 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
|||
<.remix_icon icon="rocket-line" /> Deploy
|
||||
</.button>
|
||||
<% else %>
|
||||
<.button color="blue" outlined phx-click="deploy_app">
|
||||
<.button
|
||||
disabled={!@authorized[@deployment_group.id]}
|
||||
color="blue"
|
||||
outlined
|
||||
phx-click="deploy_app"
|
||||
>
|
||||
<.remix_icon icon="rocket-line" /> Deploy anyway
|
||||
</.button>
|
||||
<% end %>
|
||||
|
@ -198,6 +204,7 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
|||
deployment_group={@deployment_group}
|
||||
num_agents={@num_agents}
|
||||
num_app_deployments={@num_app_deployments}
|
||||
authorized={@authorized[@deployment_group.id]}
|
||||
active
|
||||
/>
|
||||
</div>
|
||||
|
@ -224,7 +231,12 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
|||
<.button color="blue" phx-click="go_add_agent">
|
||||
<.remix_icon icon="add-line" /> Add app server
|
||||
</.button>
|
||||
<.button color="blue" outlined phx-click="deploy_app">
|
||||
<.button
|
||||
disabled={!@authorized[@deployment_group.id]}
|
||||
color="blue"
|
||||
outlined
|
||||
phx-click="deploy_app"
|
||||
>
|
||||
<.remix_icon icon="rocket-line" /> Deploy anyway
|
||||
</.button>
|
||||
</div>
|
||||
|
@ -250,6 +262,7 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
|||
deployment_group={deployment_group}
|
||||
num_agents={@num_agents}
|
||||
num_app_deployments={@num_app_deployments}
|
||||
authorized={@authorized[deployment_group.id]}
|
||||
selectable
|
||||
/>
|
||||
</div>
|
||||
|
@ -301,6 +314,7 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
|||
|
||||
attr :active, :boolean, default: false
|
||||
attr :selectable, :boolean, default: false
|
||||
attr :authorized, :boolean, default: true
|
||||
attr :deployment_group, :map, required: true
|
||||
attr :num_agents, :map, required: true
|
||||
attr :num_app_deployments, :map, required: true
|
||||
|
@ -310,29 +324,45 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
|||
~H"""
|
||||
<div
|
||||
class={[
|
||||
"border p-3 rounded-lg",
|
||||
@selectable && "cursor-pointer",
|
||||
if(@active,
|
||||
do: "border-blue-600 bg-blue-50",
|
||||
else: "border-gray-200"
|
||||
)
|
||||
"border p-3 rounded-lg relative",
|
||||
cond do
|
||||
!@authorized -> "!block cursor-not-allowed tooltip top opacity-50 bg-gray-50"
|
||||
@selectable -> "cursor-pointer border-blue-600 bg-blue-50"
|
||||
true -> "cursor-pointer border-gray-200"
|
||||
end
|
||||
]}
|
||||
phx-click={@selectable && "select_deployment_group"}
|
||||
data-tooltip={!@authorized && "You are not authorized to deploy to this deployment group"}
|
||||
phx-click={@selectable && @authorized && "select_deployment_group"}
|
||||
phx-value-id={@deployment_group.id}
|
||||
{@rest}
|
||||
>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex gap-2 items-center text-gray-700">
|
||||
<h3 class="text-sm">
|
||||
<div class="flex gap-2 items-center">
|
||||
<h3 class={[
|
||||
"text-sm",
|
||||
if(@authorized, do: "text-gray-700", else: "text-gray-500")
|
||||
]}>
|
||||
<span class="font-semibold">{@deployment_group.name}</span>
|
||||
<span :if={url = @deployment_group.url}>({url})</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-sm text-gray-700 border-l border-gray-300 pl-2">
|
||||
<div class="flex gap-2 flex-shrink-0">
|
||||
<div class={[
|
||||
"text-sm border-l pl-2",
|
||||
if(@authorized,
|
||||
do: "border-gray-300 text-gray-700",
|
||||
else: "border-gray-300 text-gray-500"
|
||||
)
|
||||
]}>
|
||||
App servers: {@num_agents[@deployment_group.id] || 0}
|
||||
</div>
|
||||
<div class="text-sm text-gray-700 border-l border-gray-300 pl-2">
|
||||
<div class={[
|
||||
"text-sm border-l pl-2",
|
||||
if(@authorized,
|
||||
do: "border-gray-300 text-gray-700",
|
||||
else: "border-gray-300 text-gray-500"
|
||||
)
|
||||
]}>
|
||||
Apps deployed: {@num_app_deployments[@deployment_group.id] || 0}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -440,6 +470,11 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
|||
{:noreply, socket |> assign_app_deployments() |> assign_app_deployment()}
|
||||
end
|
||||
|
||||
def handle_info({:deployment_users_updated, deployment_group}, socket)
|
||||
when deployment_group.hub_id == socket.assigns.hub.id do
|
||||
{:noreply, assign_deployment_groups(socket)}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{:operation, {:set_notebook_deployment_group, _client_id, deployment_group_id}},
|
||||
socket
|
||||
|
@ -453,13 +488,20 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
|||
def handle_info(_message, socket), do: {:noreply, socket}
|
||||
|
||||
defp assign_deployment_groups(socket) do
|
||||
hub = socket.assigns.hub
|
||||
|
||||
deployment_groups =
|
||||
socket.assigns.hub
|
||||
hub
|
||||
|> Teams.get_deployment_groups()
|
||||
|> Enum.filter(&(&1.mode == :online))
|
||||
|> Enum.sort_by(& &1.name)
|
||||
|
||||
assign(socket, deployment_groups: deployment_groups)
|
||||
authorized =
|
||||
for deployment_group <- deployment_groups, into: %{} do
|
||||
{deployment_group.id, Teams.user_can_deploy?(hub, deployment_group)}
|
||||
end
|
||||
|
||||
assign(socket, deployment_groups: deployment_groups, authorized: authorized)
|
||||
end
|
||||
|
||||
defp assign_app_deployments(socket) do
|
||||
|
|
|
@ -24,4 +24,10 @@ defmodule LivebookProto.DeploymentGroup do
|
|||
json_name: "authorizationGroups"
|
||||
|
||||
field :groups_auth, 13, type: :bool, json_name: "groupsAuth"
|
||||
field :deploy_auth, 14, type: :bool, json_name: "deployAuth"
|
||||
|
||||
field :deployment_users, 15,
|
||||
repeated: true,
|
||||
type: LivebookProto.DeploymentUser,
|
||||
json_name: "deploymentUsers"
|
||||
end
|
||||
|
|
|
@ -23,4 +23,10 @@ defmodule LivebookProto.DeploymentGroupUpdated do
|
|||
json_name: "authorizationGroups"
|
||||
|
||||
field :groups_auth, 12, type: :bool, json_name: "groupsAuth"
|
||||
field :deploy_auth, 13, type: :bool, json_name: "deployAuth"
|
||||
|
||||
field :deployment_users, 14,
|
||||
repeated: true,
|
||||
type: LivebookProto.DeploymentUser,
|
||||
json_name: "deploymentUsers"
|
||||
end
|
||||
|
|
6
proto/lib/livebook_proto/deployment_user.pb.ex
Normal file
6
proto/lib/livebook_proto/deployment_user.pb.ex
Normal file
|
@ -0,0 +1,6 @@
|
|||
defmodule LivebookProto.DeploymentUser do
|
||||
use Protobuf, protoc_gen_elixir_version: "0.14.1", syntax: :proto3
|
||||
|
||||
field :user_id, 1, type: :string, json_name: "userId"
|
||||
field :deployment_group_id, 2, type: :string, json_name: "deploymentGroupId"
|
||||
end
|
|
@ -68,6 +68,8 @@ message DeploymentGroup {
|
|||
bool teams_auth = 11;
|
||||
repeated AuthorizationGroup authorization_groups = 12;
|
||||
bool groups_auth = 13;
|
||||
bool deploy_auth = 14;
|
||||
repeated DeploymentUser deployment_users = 15;
|
||||
}
|
||||
|
||||
message DeploymentGroupCreated {
|
||||
|
@ -95,6 +97,8 @@ message DeploymentGroupUpdated {
|
|||
bool teams_auth = 10;
|
||||
repeated AuthorizationGroup authorization_groups = 11;
|
||||
bool groups_auth = 12;
|
||||
bool deploy_auth = 13;
|
||||
repeated DeploymentUser deployment_users = 14;
|
||||
}
|
||||
|
||||
message DeploymentGroupDeleted {
|
||||
|
@ -213,6 +217,11 @@ message AuthorizationGroup {
|
|||
string group_name = 2;
|
||||
}
|
||||
|
||||
message DeploymentUser {
|
||||
string user_id = 1;
|
||||
string deployment_group_id = 2;
|
||||
}
|
||||
|
||||
message Event {
|
||||
oneof type {
|
||||
SecretCreated secret_created = 1;
|
||||
|
@ -258,5 +267,4 @@ message BillingStatusCanceling {
|
|||
int64 cancel_at = 1;
|
||||
}
|
||||
|
||||
message BillingStatusCanceled {
|
||||
}
|
||||
message BillingStatusCanceled {}
|
||||
|
|
|
@ -112,6 +112,58 @@ defmodule LivebookCLI.Integration.DeployTest do
|
|||
end
|
||||
end
|
||||
|
||||
test "fails with unauthorized deploy key",
|
||||
%{team: team, node: node, org: org, tmp_dir: tmp_dir} do
|
||||
title = "Test CLI Deploy App"
|
||||
slug = Utils.random_short_id()
|
||||
app_path = Path.join(tmp_dir, "#{slug}.livemd")
|
||||
{key, _} = TeamsRPC.create_deploy_key(node, org: org)
|
||||
|
||||
deployment_group =
|
||||
TeamsRPC.create_deployment_group(node, org: org, url: @url, deploy_auth: true)
|
||||
|
||||
hub_id = team.id
|
||||
deployment_group_id = to_string(deployment_group.id)
|
||||
|
||||
stamp_notebook(app_path, """
|
||||
<!-- livebook:{"app_settings":{"access_type":"public","slug":"#{slug}"},"hub_id":"#{hub_id}"} -->
|
||||
|
||||
# #{title}
|
||||
|
||||
## Test Section
|
||||
|
||||
```elixir
|
||||
IO.puts("Hello from CLI deployed app!")
|
||||
```
|
||||
""")
|
||||
|
||||
output =
|
||||
ExUnit.CaptureIO.capture_io(fn ->
|
||||
assert_raise(LivebookCLI.Error, "Some app deployments failed.", fn ->
|
||||
assert deploy(
|
||||
key,
|
||||
team.teams_key,
|
||||
deployment_group.id,
|
||||
app_path
|
||||
) == :ok
|
||||
end)
|
||||
end)
|
||||
|
||||
assert output =~ "* Preparing to deploy notebook #{slug}.livemd"
|
||||
|
||||
assert output =~
|
||||
"* Test CLI Deploy App failed to deploy. Transport error: You are not authorized to perform this action, make sure you have the access to deploy apps to this deployment group"
|
||||
|
||||
refute_receive {:app_deployment_started,
|
||||
%{
|
||||
title: ^title,
|
||||
slug: ^slug,
|
||||
deployment_group_id: ^deployment_group_id,
|
||||
hub_id: ^hub_id,
|
||||
deployed_by: "CLI"
|
||||
}}
|
||||
end
|
||||
|
||||
test "fails with invalid deploy key", %{team: team, node: node, org: org, tmp_dir: tmp_dir} do
|
||||
slug = Utils.random_short_id()
|
||||
app_path = Path.join(tmp_dir, "#{slug}.livemd")
|
||||
|
|
|
@ -571,6 +571,56 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
|||
"App deployment created successfully"
|
||||
end
|
||||
|
||||
test "shows tooltip message if user is unauthorized to deploy apps",
|
||||
%{team: team, node: node, org: org, conn: conn, session: session} do
|
||||
Session.set_notebook_hub(session.pid, team.id)
|
||||
|
||||
deployment_group = TeamsRPC.create_deployment_group(node, mode: :online, org: org)
|
||||
id = to_string(deployment_group.id)
|
||||
assert_receive {:deployment_group_created, %{id: ^id, deploy_auth: false}}
|
||||
|
||||
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
|
||||
|
||||
view
|
||||
|> element("a", "Deploy with Livebook Teams")
|
||||
|> render_click()
|
||||
|
||||
# Step: configuring valid app settings
|
||||
|
||||
assert render(view) =~ "You must configure your app before deploying it."
|
||||
|
||||
slug = Livebook.Utils.random_short_id()
|
||||
|
||||
view
|
||||
|> element(~s/#app-settings-modal form/)
|
||||
|> render_submit(%{"app_settings" => %{"slug" => slug}})
|
||||
|
||||
# From this point forward we are in a child LV
|
||||
view = find_live_child(view, "app-teams")
|
||||
assert render(view) =~ "App deployment with Livebook Teams"
|
||||
|
||||
# show the deployment group being able to select to deploy an app
|
||||
assert has_element?(
|
||||
view,
|
||||
~s/[phx-click="select_deployment_group"][phx-value-id="#{deployment_group.id}"]/
|
||||
)
|
||||
|
||||
# then, we update the deployment group, so it will
|
||||
# update the view and show the tooltip with unauthorized error message
|
||||
{:ok, deployment_group} = TeamsRPC.toggle_deployment_authorization(node, deployment_group)
|
||||
assert_receive {:deployment_group_updated, %{id: ^id, deploy_auth: true}}
|
||||
|
||||
refute has_element?(
|
||||
view,
|
||||
~s/[phx-click="select_deployment_group"][phx-value-id="#{deployment_group.id}"]/
|
||||
)
|
||||
|
||||
assert has_element?(
|
||||
view,
|
||||
~s/[data-tooltip="You are not authorized to deploy to this deployment group"][phx-value-id="#{deployment_group.id}"]/
|
||||
)
|
||||
end
|
||||
|
||||
test "shows an error when the deployment size is higher than the maximum size of 20MB",
|
||||
%{team: team, conn: conn, session: session} do
|
||||
Session.set_notebook_hub(session.pid, team.id)
|
||||
|
@ -603,5 +653,42 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
|||
assert render(view) =~
|
||||
"Failed to pack files: the notebook and its attachments have exceeded the maximum size of 20MB"
|
||||
end
|
||||
|
||||
test "shows an error when the deployment is unauthorized",
|
||||
%{team: team, org: org, node: node, conn: conn, session: session} do
|
||||
Session.set_notebook_hub(session.pid, team.id)
|
||||
|
||||
slug = Livebook.Utils.random_short_id()
|
||||
app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug}
|
||||
Session.set_app_settings(session.pid, app_settings)
|
||||
|
||||
deployment_group = TeamsRPC.create_deployment_group(node, mode: :online, org: org)
|
||||
id = to_string(deployment_group.id)
|
||||
assert_receive {:deployment_group_created, %{id: ^id, deploy_auth: false}}
|
||||
|
||||
{:ok, _} = TeamsRPC.toggle_deployment_authorization(node, deployment_group)
|
||||
assert_receive {:deployment_group_updated, %{id: ^id, deploy_auth: true}}
|
||||
|
||||
Session.set_notebook_deployment_group(session.pid, id)
|
||||
assert_receive {:operation, {:set_notebook_deployment_group, _, ^id}}
|
||||
|
||||
%{files_dir: files_dir} = session
|
||||
image_file = FileSystem.File.resolve(files_dir, "image.jpg")
|
||||
:ok = FileSystem.File.write(image_file, :crypto.strong_rand_bytes(1024 * 1024))
|
||||
Session.add_file_entries(session.pid, [%{type: :attachment, name: "image.jpg"}])
|
||||
|
||||
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}/app-teams")
|
||||
|
||||
# From this point forward we are in a child LV
|
||||
view = find_live_child(view, "app-teams")
|
||||
assert render(view) =~ "App deployment with Livebook Teams"
|
||||
|
||||
view
|
||||
|> element("button", "Deploy")
|
||||
|> render_click()
|
||||
|
||||
assert render(view) =~
|
||||
"You are not authorized to perform this action, make sure you have the access to deploy apps to this deployment group"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -208,4 +208,8 @@ defmodule Livebook.TeamsRPC do
|
|||
def toggle_groups_authorization(node, deployment_group) do
|
||||
:erpc.call(node, TeamsRPC, :toggle_groups_authorization, [deployment_group])
|
||||
end
|
||||
|
||||
def toggle_deployment_authorization(node, deployment_group) do
|
||||
:erpc.call(node, TeamsRPC, :toggle_deployment_authorization, [deployment_group])
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue