Packages
livebook
0.17.1
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/k8s/pod.ex
defmodule Livebook.K8s.Pod do
@main_container_name "livebook-runtime"
@pvc_name_volume_name "livebook-home"
@default_pod_template """
apiVersion: v1
kind: Pod
metadata:
generateName: livebook-runtime-
spec:
containers:
- name: livebook-runtime
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: "1"
memory: 1Gi\
"""
@doc """
Returns the default pod template.
"""
@spec default_pod_template() :: String.t()
def default_pod_template(), do: @default_pod_template
@doc """
Set the namespace on the given manifest.
"""
@spec set_namespace(map(), String.t()) :: map()
def set_namespace(manifest, namespace) do
put_in(manifest, ["metadata", "namespace"], namespace)
end
@doc """
Adds "volume" and "volumeMount" configurations to `manifest` in order
to mount `pvc_name` under /home/livebook on the pod.
"""
@spec set_pvc_name(map(), String.t()) :: map()
def set_pvc_name(manifest, pvc_name) do
manifest
|> update_in(["spec", Access.key("volumes", [])], fn volumes ->
volume = %{
"name" => @pvc_name_volume_name,
"persistentVolumeClaim" => %{"claimName" => pvc_name}
}
[volume | volumes]
end)
|> update_in(
["spec", "containers", access_main_container(), Access.key("volumeMounts", [])],
fn volume_mounts ->
[%{"name" => @pvc_name_volume_name, "mountPath" => "/home/livebook"} | volume_mounts]
end
)
end
@doc """
Adds the list of `env_vars` to the main container of the given `manifest`.
"""
@spec add_env_vars(map(), list()) :: map()
def add_env_vars(manifest, env_vars) do
update_in(
manifest,
["spec", "containers", access_main_container(), Access.key("env", [])],
fn existing_vars -> env_vars ++ existing_vars end
)
end
@doc """
Sets the tag of the main container's image.
"""
@spec set_docker_tag(map(), String.t()) :: map()
def set_docker_tag(manifest, docker_tag) do
image = "ghcr.io/livebook-dev/livebook:#{docker_tag}"
put_in(manifest, ["spec", "containers", access_main_container(), "image"], image)
end
@doc """
Adds the `port` to the main container and adds a readiness probe.
"""
@spec add_container_port(map(), non_neg_integer()) :: map()
def add_container_port(manifest, port) do
readiness_probe = %{
"tcpSocket" => %{"port" => port},
"initialDelaySeconds" => 1,
"periodSeconds" => 1
}
manifest
|> update_in(
["spec", "containers", access_main_container(), Access.key("ports", [])],
&[%{"containerPort" => port} | &1]
)
|> put_in(["spec", "containers", access_main_container(), "readinessProbe"], readiness_probe)
end
@doc """
Turns the given `pod_template` into a Pod manifest.
"""
@spec pod_from_template(String.t()) :: map()
def pod_from_template(pod_template) do
pod_template
|> YamlElixir.read_from_string!()
|> do_pod_from_template()
end
defp do_pod_from_template(pod) do
pod
|> Map.merge(%{"apiVersion" => "v1", "kind" => "Pod"})
|> put_in(["spec", "restartPolicy"], "Never")
end
@doc """
Validates the given Pod manifest.
"""
@spec validate_pod_template(map(), String.t()) :: :ok | {:error, String.t()}
def validate_pod_template(pod, namespace)
def validate_pod_template(%{"apiVersion" => "v1", "kind" => "Pod"} = pod, namespace) do
with :ok <- validate_basics(pod),
:ok <- validate_main_container(pod),
:ok <- validate_namespace(pod, namespace) do
validate_container_image(pod)
end
end
def validate_pod_template(_other_input, _namespace) do
{:error, ~s/Make sure to define a valid resource of apiVersion "v1" and kind "Pod"./}
end
defp validate_basics(pod) do
cond do
not match?(%{"metadata" => %{}}, pod) ->
{:error, ".metadata is missing in your pod template."}
not match?(%{"spec" => %{"containers" => containers}} when is_list(containers), pod) ->
{:error, ".spec.containers is missing in your pod template."}
pod["metadata"]["name"] in [nil, ""] and pod["metadata"]["generateName"] in [nil, ""] ->
{:error,
"Make sure to define .metadata.name or .metadata.generateName in your pod template."}
true ->
:ok
end
end
defp validate_main_container(pod) do
if get_in(pod, ["spec", "containers", access_main_container()]) do
:ok
else
{:error,
~s/Main container is missing. The main container should be named "#{@main_container_name}"./}
end
end
defp validate_container_image(pod) do
if get_in(pod, ["spec", "containers", access_main_container(), "image"]) do
{:error,
"You can't set the container image of the main container. It's going to be overridden."}
else
:ok
end
end
defp validate_namespace(pod, namespace) do
template_ns = get_in(pod, ["metadata", "namespace"])
if template_ns == nil or template_ns == namespace do
:ok
else
{:error,
"The field .template.metadata.namespace has to be omitted or set to the namespace you selected."}
end
end
defp access_main_container() do
Access.find(&(&1["name"] == @main_container_name))
end
end