Packages
livebook
0.17.2
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/hubs.ex
defmodule Livebook.Hubs do
alias Livebook.FileSystem
alias Livebook.Storage
alias Livebook.Hubs
alias Livebook.Hubs.Provider
alias Livebook.Secrets.Secret
require Logger
@namespace :hubs
@supervisor Livebook.HubsSupervisor
@type connected_hub :: %{
required(:pid) => pid(),
required(:hub) => Provider.t()
}
@type connected_hubs :: list(connected_hub())
@doc """
Gets a list of hubs from storage.
"""
@spec get_hubs() :: list(Provider.t())
def get_hubs do
for fields <- Storage.all(@namespace) do
to_struct(fields)
end
end
@doc """
Gets a list of metadata from storage.
"""
@spec get_metadata() :: list(Hubs.Metadata.t())
def get_metadata do
for hub <- get_hubs() do
Provider.to_metadata(hub)
end
end
@doc """
Gets one hub from storage.
"""
@spec fetch_hub(String.t()) :: {:ok, Provider.t()} | :error
def fetch_hub(id) do
with {:ok, data} <- Storage.fetch(@namespace, id) do
{:ok, to_struct(data)}
end
end
@doc """
Gets one hub from storage.
Raises `Livebook.Storage.NotFoundError` if the hub does not exist.
"""
@spec fetch_hub!(String.t()) :: Provider.t()
def fetch_hub!(id) do
Storage.fetch!(@namespace, id) |> to_struct()
end
@doc """
Checks if hub already exists.
"""
@spec hub_exists?(String.t()) :: boolean()
def hub_exists?(id) do
case Storage.fetch(@namespace, id) do
:error -> false
{:ok, _} -> true
end
end
@doc """
Saves a new hub to the configured ones.
"""
@spec save_hub(Provider.t()) :: Provider.t()
def save_hub(struct) do
attributes = Provider.dump(struct)
:ok = connect_hub(struct)
:ok = Storage.insert(@namespace, struct.id, Map.to_list(attributes))
:ok = Hubs.Broadcasts.hub_changed(struct.id)
struct
end
@doc """
Deletes a hub with the given id.
"""
@spec delete_hub(String.t()) :: :ok
def delete_hub(id) do
with {:ok, hub} <- fetch_hub(id) do
true = Provider.type(hub) != "personal"
:ok = maybe_unset_default_hub(hub.id)
:ok = Storage.delete(@namespace, id)
:ok = Hubs.Broadcasts.hub_deleted(hub.id)
:ok = disconnect_hub(hub)
end
:ok
end
@spec set_default_hub(String.t()) :: :ok
def set_default_hub(id) do
with {:ok, hub} <- fetch_hub(id) do
:ok = Storage.insert(:default_hub, "default_hub", [{:default_hub, hub.id}])
end
:ok
end
@spec unset_default_hub() :: :ok
def unset_default_hub() do
:ok = Storage.delete(:default_hub, "default_hub")
end
@spec get_default_hub() :: Provider.t()
def get_default_hub() do
with {:ok, %{default_hub: id}} <- Storage.fetch(:default_hub, "default_hub"),
{:ok, hub} <- fetch_hub(id) do
hub
else
_ -> fetch_hub!(Hubs.Personal.id())
end
end
defp maybe_unset_default_hub(hub_id) do
if get_default_hub().id == hub_id, do: unset_default_hub(), else: :ok
end
defp disconnect_hub(hub) do
# We use a task supervisor because the hub connection itself
# calls delete_hub (which calls this function), otherwise we deadlock.
Task.Supervisor.start_child(Livebook.TaskSupervisor, fn ->
# Since other processes may have been communicating
# with the hub, we don't want to terminate abruptly and
# make them crash, so we give it some time to shut down.
#
# The default backoff is 5.5s, so we round it down to 5s.
Process.sleep(30_000)
:ok = Provider.disconnect(hub)
end)
:ok
end
defp to_struct(%{id: "personal-" <> _} = fields) do
Provider.load(%Hubs.Personal{}, fields)
end
defp to_struct(%{id: "team-" <> _} = fields) do
Provider.load(Hubs.Team.new(), fields)
end
@doc """
Connects to the all available and connectable hubs.
## Example
iex> connect_hubs()
:ok
"""
@spec connect_hubs() :: :ok
def connect_hubs do
for hub <- get_hubs(), do: connect_hub(hub)
:ok
end
defp connect_hub(hub) do
if child_spec = Provider.connection_spec(hub) do
case DynamicSupervisor.start_child(@supervisor, child_spec) do
{:ok, _} ->
:ok
{:error, {:already_started, _pid}} ->
:ok
{:error, reason} ->
Logger.error("Could not start Workspace #{hub.id}: #{Exception.format_exit(reason)}")
end
end
:ok
end
@doc """
Gets a list of hub secrets.
It gets from all hubs with secret management.
"""
@spec get_secrets() :: list(Secret.t())
def get_secrets do
for hub <- get_hubs(),
secret <- Provider.get_secrets(hub),
do: secret
end
@doc """
Gets a list of secrets for given hub.
"""
@spec get_secrets(Provider.t()) :: list(Secret.t())
def get_secrets(hub) do
hub
|> Provider.get_secrets()
|> Enum.sort()
end
@doc """
Creates a secret for given hub.
"""
@spec create_secret(Provider.t(), Secret.t()) ::
:ok
| {:error, Provider.field_errors()}
| {:transport_error, String.t()}
def create_secret(hub, %Secret{} = secret) do
Provider.create_secret(hub, secret)
end
@doc """
Updates a secret for given hub.
"""
@spec update_secret(Provider.t(), Secret.t()) ::
:ok
| {:error, Provider.field_errors()}
| {:transport_error, String.t()}
def update_secret(hub, %Secret{} = secret) do
Provider.update_secret(hub, secret)
end
@doc """
Deletes a secret for given hub.
"""
@spec delete_secret(Provider.t(), Secret.t()) :: :ok | {:transport_error, String.t()}
def delete_secret(hub, %Secret{} = secret) do
Provider.delete_secret(hub, secret)
end
@doc """
Generates a notebook stamp.
"""
@spec notebook_stamp(Provider.t(), iodata(), map()) ::
{:ok, Provider.notebook_stamp()} | :skip | {:error, String.t()}
def notebook_stamp(hub, notebook_source, metadata) do
Provider.notebook_stamp(hub, notebook_source, metadata)
end
@doc """
Verifies a notebook stamp and returns the decrypted metadata.
"""
@spec verify_notebook_stamp(Provider.t(), iodata(), Provider.notebook_stamp()) ::
{:ok, metadata :: map()} | {:error, :invalid | :too_recent_version}
def verify_notebook_stamp(hub, notebook_source, stamp) do
Provider.verify_notebook_stamp(hub, notebook_source, stamp)
end
@doc """
Gets a list of file systems from all hubs.
"""
@spec get_file_systems() :: list(FileSystem.t())
def get_file_systems() do
file_systems = Enum.flat_map(get_hubs(), &Provider.get_file_systems/1)
local_file_system = Livebook.Config.local_file_system()
[local_file_system | Enum.sort_by(file_systems, & &1.id)]
end
@doc """
Gets a list of file systems for given hub.
"""
@spec get_file_systems(Provider.t(), keyword()) :: list(FileSystem.t())
def get_file_systems(hub, opts \\ []) do
hub_file_systems = Provider.get_file_systems(hub)
sorted_hub_file_systems = Enum.sort_by(hub_file_systems, & &1.id)
if opts[:hub_only],
do: sorted_hub_file_systems,
else: [Livebook.Config.local_file_system() | sorted_hub_file_systems]
end
@doc """
Creates a file system for given hub.
"""
@spec create_file_system(Provider.t(), FileSystem.t()) ::
:ok
| {:error, Provider.field_errors()}
| {:transport_error, String.t()}
def create_file_system(hub, file_system) do
Provider.create_file_system(hub, file_system)
end
@doc """
Updates a file system for given hub.
"""
@spec update_file_system(Provider.t(), FileSystem.t()) ::
:ok
| {:error, Provider.field_errors()}
| {:transport_error, String.t()}
def update_file_system(hub, file_system) do
Provider.update_file_system(hub, file_system)
end
@doc """
Deletes a file system for given hub.
"""
@spec delete_file_system(Provider.t(), FileSystem.t()) :: :ok | {:transport_error, String.t()}
def delete_file_system(hub, file_system) do
Provider.delete_file_system(hub, file_system)
end
@doc """
Gets a list of hub app specs.
"""
@spec get_app_specs() :: list(Livebook.Apps.AppSpec.t())
def get_app_specs() do
for hub <- get_hubs(),
Provider.connection_spec(hub),
app_spec <- Provider.get_app_specs(hub),
do: app_spec
end
end