mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-04 12:04:20 +08:00
108 lines
3.8 KiB
Elixir
108 lines
3.8 KiB
Elixir
defmodule Livebook.Teams.AppDeployment do
|
|
use Ecto.Schema
|
|
alias Livebook.FileSystem
|
|
|
|
@max_size 20 * 1024 * 1024
|
|
|
|
@type t :: %__MODULE__{
|
|
id: String.t() | nil,
|
|
slug: String.t() | nil,
|
|
version: String.t() | nil,
|
|
sha: String.t() | nil,
|
|
title: String.t() | nil,
|
|
multi_session: boolean(),
|
|
access_type: Livebook.Notebook.AppSettings.access_type(),
|
|
hub_id: String.t() | nil,
|
|
deployment_group_id: String.t() | nil,
|
|
file: binary() | nil,
|
|
deployed_by: String.t() | nil,
|
|
deployed_at: DateTime.t() | nil,
|
|
authorization_groups: Ecto.Schema.embeds_many(Livebook.Teams.AuthorizationGroup.t())
|
|
}
|
|
|
|
@access_types Livebook.Notebook.AppSettings.access_types()
|
|
|
|
@primary_key {:id, :string, autogenerate: false}
|
|
embedded_schema do
|
|
field :slug, :string
|
|
field :version, :string
|
|
field :sha, :string
|
|
field :title, :string
|
|
field :multi_session, :boolean
|
|
field :access_type, Ecto.Enum, values: @access_types
|
|
field :hub_id, :string
|
|
field :deployment_group_id, :string
|
|
field :file, :string
|
|
field :deployed_by, :string
|
|
field :deployed_at, :utc_datetime
|
|
|
|
embeds_many :authorization_groups, Livebook.Teams.AuthorizationGroup
|
|
end
|
|
|
|
@doc """
|
|
Creates a new app deployment from notebook.
|
|
"""
|
|
@spec new(Livebook.Notebook.t() | String.t(), Livebook.FileSystem.File.t()) ::
|
|
{:ok, t()} | {:warning, list(String.t())} | {:error, FileSystem.error()}
|
|
def new(%Livebook.Notebook{} = notebook, files_dir) do
|
|
case Livebook.LiveMarkdown.notebook_to_livemd(notebook) do
|
|
{source, []} -> new(notebook, source, files_dir)
|
|
{_, warnings} -> {:warning, warnings}
|
|
end
|
|
end
|
|
|
|
@stamp_error "notebook does not have a stamp, disabling access to secrets and remote files"
|
|
|
|
def new(source, files_dir) when is_binary(source) do
|
|
case Livebook.LiveMarkdown.notebook_from_livemd(source) do
|
|
{notebook, %{warnings: [], stamp_verified?: true}} -> new(notebook, source, files_dir)
|
|
{_, %{warnings: [], stamp_verified?: false}} -> {:warning, [@stamp_error]}
|
|
{_, %{warnings: warnings}} -> {:warning, warnings}
|
|
end
|
|
end
|
|
|
|
def new(%Livebook.Notebook{} = notebook, source, files_dir) do
|
|
with {:ok, files} <- build_and_check_file_entries(notebook, source, files_dir),
|
|
{:ok, {_, zip_content}} <- :zip.create(~c"app_deployment.zip", files, [:memory]),
|
|
:ok <- validate_size(zip_content) do
|
|
md5_hash = :crypto.hash(:md5, zip_content)
|
|
shasum = Base.encode16(md5_hash, case: :lower)
|
|
|
|
{:ok,
|
|
%__MODULE__{
|
|
slug: notebook.app_settings.slug,
|
|
sha: shasum,
|
|
title: notebook.name,
|
|
multi_session: notebook.app_settings.multi_session,
|
|
access_type: notebook.app_settings.access_type,
|
|
hub_id: notebook.hub_id,
|
|
deployment_group_id: notebook.deployment_group_id,
|
|
file: zip_content
|
|
}}
|
|
end
|
|
end
|
|
|
|
defp build_and_check_file_entries(notebook, source, files_dir) do
|
|
notebook.file_entries
|
|
|> Enum.filter(&(&1.type == :attachment))
|
|
|> Enum.reduce_while({:ok, [{~c"notebook.livemd", source}]}, fn %{name: name}, {:ok, acc} ->
|
|
file = Livebook.FileSystem.File.resolve(files_dir, name)
|
|
|
|
with {:ok, true} <- Livebook.FileSystem.File.exists?(file),
|
|
{:ok, content} <- Livebook.FileSystem.File.read(file) do
|
|
{:cont, {:ok, [{to_charlist("files/" <> name), content} | acc]}}
|
|
else
|
|
{:ok, false} -> {:halt, {:error, "files/#{name}: doesn't exist"}}
|
|
{:error, reason} -> {:halt, {:error, "files/#{name}: #{reason}"}}
|
|
end
|
|
end)
|
|
end
|
|
|
|
defp validate_size(data) do
|
|
if byte_size(data) <= @max_size do
|
|
:ok
|
|
else
|
|
{:error, "the notebook and its attachments have exceeded the maximum size of 20MB"}
|
|
end
|
|
end
|
|
end
|