Add function to deploy the app using a CLI session

This commit is contained in:
Alexandre de Souza 2025-07-17 12:39:04 -03:00
parent e604e05d09
commit 5160ae4a96
No known key found for this signature in database
GPG key ID: E39228FFBA346545
3 changed files with 114 additions and 0 deletions

View file

@ -265,6 +265,19 @@ defmodule Livebook.Teams do
end
end
@doc """
Deploys the given app deployment to given deployment group using a deploy key.
"""
@spec deploy_app_from_cli(Team.t(), Teams.AppDeployment.t(), String.t()) ::
{:ok, String.t()} | {:error, map()} | {:transport_error, String.t()}
def deploy_app_from_cli(%Team{} = team, %Teams.AppDeployment{} = app_deployment, name) do
case Requests.deploy_app_from_cli(team, app_deployment, name) do
{:ok, %{"url" => url}} -> {:ok, url}
{:error, %{"errors" => errors}} -> {:error, errors}
any -> any
end
end
defp map_teams_field_to_livebook_field(map, teams_field, livebook_field) do
if value = map[teams_field] do
Map.put_new(map, livebook_field, value)

View file

@ -239,6 +239,26 @@ defmodule Livebook.Teams.Requests do
post("/api/v1/cli/auth", %{}, config)
end
@doc """
Send a request to Livebook Team API to deploy an app using a deploy key.
"""
@spec deploy_app_from_cli(Team.t(), Teams.AppDeployment.t(), String.t()) :: api_result()
def deploy_app_from_cli(team, app_deployment, deployment_group_name) do
secret_key = Teams.derive_key(team.teams_key)
params = %{
title: app_deployment.title,
slug: app_deployment.slug,
multi_session: app_deployment.multi_session,
access_type: app_deployment.access_type,
deployment_group_name: deployment_group_name,
sha: app_deployment.sha
}
encrypted_content = Teams.encrypt(app_deployment.file, secret_key)
upload("/api/v1/cli/org/apps", encrypted_content, params, team)
end
@doc """
Normalizes errors map into errors for the given schema.
"""
@ -283,6 +303,7 @@ 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.put_private(:cli, path =~ "cli")
|> Req.Request.put_private(:deploy, true)
|> Req.post(url: path, params: params, body: content)
|> handle_response()
@ -322,6 +343,9 @@ defmodule Livebook.Teams.Requests do
defp transform_response({request, response}) do
case {request, response} do
{request, %{status: 404}} when request.private.cli and request.private.deploy ->
{request, %{response | status: 422, body: %{"errors" => %{"name" => ["does not exist"]}}}}
{request, %{status: 400, body: %{"errors" => %{"detail" => error}}}}
when request.private.deploy ->
{request, %{response | status: 422, body: %{"errors" => %{"file" => [error]}}}}

View file

@ -297,4 +297,81 @@ defmodule Livebook.TeamsTest do
refute Livebook.Hubs.hub_exists?(team.id)
end
end
describe "deploy_app_from_cli/2" do
@describetag teams_for: :user
@tag :tmp_dir
test "deploys app to Teams using a CLI session",
%{team: team, node: node, tmp_dir: tmp_dir, org: org} do
%{id: id, name: name} =
TeamsRPC.create_deployment_group(node,
name: "angry-cat-#{Ecto.UUID.generate()}",
url: "http://localhost:4123",
mode: :online,
org: org
)
id = to_string(id)
hub_id = "team-#{org.name}"
slug = Utils.random_short_id()
title = "MyNotebook-#{slug}"
app_settings = %{Notebook.AppSettings.new() | slug: slug}
notebook = %{
Notebook.new()
| app_settings: app_settings,
name: title,
hub_id: hub_id,
deployment_group_id: id
}
files_dir = FileSystem.File.local(tmp_dir)
# stamp the notebook
assert {:ok, app_deployment} = Teams.AppDeployment.new(notebook, files_dir)
# fetch the cli session
{key, _deploy_key} = TeamsRPC.create_deploy_key(node, org: org)
config = %{teams_key: team.teams_key, session_token: key}
assert {:ok, team} = Teams.fetch_cli_session(config)
# deploy the app
assert {:ok, _url} = Teams.deploy_app_from_cli(team, app_deployment, name)
sha = app_deployment.sha
multi_session = app_settings.multi_session
access_type = app_settings.access_type
assert_receive {:app_deployment_started,
%Livebook.Teams.AppDeployment{
slug: ^slug,
sha: ^sha,
title: ^title,
deployed_by: "CLI",
multi_session: ^multi_session,
access_type: ^access_type,
deployment_group_id: ^id
} = app_deployment2}
assert Teams.deploy_app_from_cli(team, app_deployment, "foo") ==
{:error, %{"name" => ["does not exist"]}}
assert Teams.deploy_app_from_cli(team, %{app_deployment | slug: "@abc"}, name) ==
{:error, %{"slug" => ["should only contain alphanumeric characters and dashes"]}}
assert Teams.deploy_app_from_cli(team, %{app_deployment | multi_session: nil}, name) ==
{:error, %{"multi_session" => ["can't be blank"]}}
assert Teams.deploy_app_from_cli(team, %{app_deployment | access_type: nil}, name) ==
{:error, %{"access_type" => ["can't be blank"]}}
assert Teams.deploy_app_from_cli(team, %{app_deployment | access_type: :abc}, name) ==
{:error, %{"access_type" => ["is invalid"]}}
# force app deployment to be stopped
TeamsRPC.toggle_app_deployment(node, app_deployment2.id, team.org_id)
assert_receive {:app_deployment_stopped, ^app_deployment2}
end
end
end