Skip to content

Commit 5160ae4

Browse files
committed
Add function to deploy the app using a CLI session
1 parent e604e05 commit 5160ae4

3 files changed

Lines changed: 114 additions & 0 deletions

File tree

lib/livebook/teams.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,19 @@ defmodule Livebook.Teams do
265265
end
266266
end
267267

268+
@doc """
269+
Deploys the given app deployment to given deployment group using a deploy key.
270+
"""
271+
@spec deploy_app_from_cli(Team.t(), Teams.AppDeployment.t(), String.t()) ::
272+
{:ok, String.t()} | {:error, map()} | {:transport_error, String.t()}
273+
def deploy_app_from_cli(%Team{} = team, %Teams.AppDeployment{} = app_deployment, name) do
274+
case Requests.deploy_app_from_cli(team, app_deployment, name) do
275+
{:ok, %{"url" => url}} -> {:ok, url}
276+
{:error, %{"errors" => errors}} -> {:error, errors}
277+
any -> any
278+
end
279+
end
280+
268281
defp map_teams_field_to_livebook_field(map, teams_field, livebook_field) do
269282
if value = map[teams_field] do
270283
Map.put_new(map, livebook_field, value)

lib/livebook/teams/requests.ex

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,26 @@ defmodule Livebook.Teams.Requests do
239239
post("/api/v1/cli/auth", %{}, config)
240240
end
241241

242+
@doc """
243+
Send a request to Livebook Team API to deploy an app using a deploy key.
244+
"""
245+
@spec deploy_app_from_cli(Team.t(), Teams.AppDeployment.t(), String.t()) :: api_result()
246+
def deploy_app_from_cli(team, app_deployment, deployment_group_name) do
247+
secret_key = Teams.derive_key(team.teams_key)
248+
249+
params = %{
250+
title: app_deployment.title,
251+
slug: app_deployment.slug,
252+
multi_session: app_deployment.multi_session,
253+
access_type: app_deployment.access_type,
254+
deployment_group_name: deployment_group_name,
255+
sha: app_deployment.sha
256+
}
257+
258+
encrypted_content = Teams.encrypt(app_deployment.file, secret_key)
259+
upload("/api/v1/cli/org/apps", encrypted_content, params, team)
260+
end
261+
242262
@doc """
243263
Normalizes errors map into errors for the given schema.
244264
"""
@@ -283,6 +303,7 @@ defmodule Livebook.Teams.Requests do
283303
defp upload(path, content, params, team) do
284304
build_req(team)
285305
|> Req.Request.put_header("content-length", "#{byte_size(content)}")
306+
|> Req.Request.put_private(:cli, path =~ "cli")
286307
|> Req.Request.put_private(:deploy, true)
287308
|> Req.post(url: path, params: params, body: content)
288309
|> handle_response()
@@ -322,6 +343,9 @@ defmodule Livebook.Teams.Requests do
322343

323344
defp transform_response({request, response}) do
324345
case {request, response} do
346+
{request, %{status: 404}} when request.private.cli and request.private.deploy ->
347+
{request, %{response | status: 422, body: %{"errors" => %{"name" => ["does not exist"]}}}}
348+
325349
{request, %{status: 400, body: %{"errors" => %{"detail" => error}}}}
326350
when request.private.deploy ->
327351
{request, %{response | status: 422, body: %{"errors" => %{"file" => [error]}}}}

test/livebook_teams/teams_test.exs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,4 +297,81 @@ defmodule Livebook.TeamsTest do
297297
refute Livebook.Hubs.hub_exists?(team.id)
298298
end
299299
end
300+
301+
describe "deploy_app_from_cli/2" do
302+
@describetag teams_for: :user
303+
304+
@tag :tmp_dir
305+
test "deploys app to Teams using a CLI session",
306+
%{team: team, node: node, tmp_dir: tmp_dir, org: org} do
307+
%{id: id, name: name} =
308+
TeamsRPC.create_deployment_group(node,
309+
name: "angry-cat-#{Ecto.UUID.generate()}",
310+
url: "http://localhost:4123",
311+
mode: :online,
312+
org: org
313+
)
314+
315+
id = to_string(id)
316+
hub_id = "team-#{org.name}"
317+
slug = Utils.random_short_id()
318+
title = "MyNotebook-#{slug}"
319+
app_settings = %{Notebook.AppSettings.new() | slug: slug}
320+
321+
notebook = %{
322+
Notebook.new()
323+
| app_settings: app_settings,
324+
name: title,
325+
hub_id: hub_id,
326+
deployment_group_id: id
327+
}
328+
329+
files_dir = FileSystem.File.local(tmp_dir)
330+
331+
# stamp the notebook
332+
assert {:ok, app_deployment} = Teams.AppDeployment.new(notebook, files_dir)
333+
334+
# fetch the cli session
335+
{key, _deploy_key} = TeamsRPC.create_deploy_key(node, org: org)
336+
config = %{teams_key: team.teams_key, session_token: key}
337+
assert {:ok, team} = Teams.fetch_cli_session(config)
338+
339+
# deploy the app
340+
assert {:ok, _url} = Teams.deploy_app_from_cli(team, app_deployment, name)
341+
342+
sha = app_deployment.sha
343+
multi_session = app_settings.multi_session
344+
access_type = app_settings.access_type
345+
346+
assert_receive {:app_deployment_started,
347+
%Livebook.Teams.AppDeployment{
348+
slug: ^slug,
349+
sha: ^sha,
350+
title: ^title,
351+
deployed_by: "CLI",
352+
multi_session: ^multi_session,
353+
access_type: ^access_type,
354+
deployment_group_id: ^id
355+
} = app_deployment2}
356+
357+
assert Teams.deploy_app_from_cli(team, app_deployment, "foo") ==
358+
{:error, %{"name" => ["does not exist"]}}
359+
360+
assert Teams.deploy_app_from_cli(team, %{app_deployment | slug: "@abc"}, name) ==
361+
{:error, %{"slug" => ["should only contain alphanumeric characters and dashes"]}}
362+
363+
assert Teams.deploy_app_from_cli(team, %{app_deployment | multi_session: nil}, name) ==
364+
{:error, %{"multi_session" => ["can't be blank"]}}
365+
366+
assert Teams.deploy_app_from_cli(team, %{app_deployment | access_type: nil}, name) ==
367+
{:error, %{"access_type" => ["can't be blank"]}}
368+
369+
assert Teams.deploy_app_from_cli(team, %{app_deployment | access_type: :abc}, name) ==
370+
{:error, %{"access_type" => ["is invalid"]}}
371+
372+
# force app deployment to be stopped
373+
TeamsRPC.toggle_app_deployment(node, app_deployment2.id, team.org_id)
374+
assert_receive {:app_deployment_stopped, ^app_deployment2}
375+
end
376+
end
300377
end

0 commit comments

Comments
 (0)