-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Begin working on native helm repository client
This does not cover various forms of authentication. It also doesn't cover including this in the attributes for a helm service quite yet.
- Loading branch information
1 parent
d750f18
commit 354db97
Showing
23 changed files
with
467 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
defmodule Console.Deployments.Helm.Agent do | ||
use GenServer, restart: :transient | ||
alias Console.Deployments.Git | ||
alias Console.Deployments.Helm.{AgentCache, Discovery} | ||
alias Console.Schema.HelmRepository | ||
require Logger | ||
|
||
defmodule State, do: defstruct [:repo, :cache] | ||
|
||
@poll :timer.minutes(2) | ||
@jitter 15 | ||
|
||
def registry(), do: __MODULE__ | ||
|
||
def fetch(pid, chart, vsn), do: GenServer.call(pid, {:fetch, chart, vsn}) | ||
|
||
def start(url) do | ||
GenServer.start(__MODULE__, url, name: via(url)) | ||
end | ||
|
||
def start_link([url]), do: start_link(url) | ||
def start_link(url) do | ||
GenServer.start_link(__MODULE__, url, name: via(url)) | ||
end | ||
|
||
defp via(url), do: {:via, Registry, {registry(), {:helm, url}}} | ||
|
||
def init(url) do | ||
{:ok, repo} = Git.upsert_helm_repository(url) | ||
schedule_pull() | ||
:timer.send_interval(@poll, :move) | ||
send self(), :pull | ||
{:ok, %State{repo: repo, cache: AgentCache.new(repo)}} | ||
end | ||
|
||
def handle_call({:fetch, c, v}, _, %State{cache: cache} = state) do | ||
with {:ok, l, cache} <- AgentCache.fetch(cache, c, v), | ||
{:ok, f} <- File.open(l.file) do | ||
{:reply, {:ok, f, l.digest}, %{state | cache: cache}} | ||
else | ||
err -> {:reply, err, state} | ||
end | ||
end | ||
|
||
def handle_info(:pull, %State{repo: repo, cache: cache} = state) do | ||
with {:ok, repo} <- Git.upsert_helm_repository(repo.url), | ||
{:ok, cache} <- AgentCache.refresh(cache), | ||
{:ok, repo} <- refresh(repo) do | ||
schedule_pull() | ||
{:noreply, %{state | cache: cache, repo: repo}} | ||
else | ||
err -> | ||
schedule_pull() | ||
Logger.error "Failed to resync helm repo: #{inspect(err)}" | ||
{:noreply, state} | ||
end | ||
end | ||
|
||
def handle_info({:refresh, c, v}, %State{cache: cache} = state) do | ||
case AgentCache.write(cache, c, v) do | ||
{:ok, _, cache} -> {:noreply, %{state | cache: cache}} | ||
_ -> {:noreply, state} | ||
end | ||
end | ||
|
||
def handle_info(:move, %State{repo: repo} = state) do | ||
case Discovery.local?(repo.url) do | ||
true -> {:noreply, state} | ||
false -> {:stop, {:shutdown, :moved}, state} | ||
end | ||
end | ||
|
||
def handle_info(_, state), do: {:noreply, state} | ||
|
||
defp refresh(%HelmRepository{} = repo) do | ||
HelmRepository.changeset(repo, %{pulled_at: Timex.now(), health: :pullable}) | ||
|> Console.Repo.update() | ||
end | ||
|
||
defp schedule_pull(), do: Process.send_after(self(), :pull, @poll + jitter()) | ||
|
||
defp jitter() do | ||
:rand.uniform(@jitter) | ||
|> :timer.seconds() | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
defmodule Console.Deployments.Helm.AgentCache do | ||
alias Console.Helm.Client | ||
alias Console.Deployments.Helm.Utils | ||
require Logger | ||
|
||
defstruct [:repo, :index, :dir, cache: %{}] | ||
|
||
defmodule Line do | ||
@expiry [minutes: -10] | ||
defstruct [:file, :chart, :vsn, :digest, :touched] | ||
|
||
def new(file, chart, vsn, digest) do | ||
%__MODULE__{file: file, chart: chart, vsn: vsn, digest: digest, touched: Timex.now()} | ||
end | ||
|
||
def touch(%__MODULE__{} = mod), do: %{mod | touched: Timex.now()} | ||
|
||
def expire(%__MODULE__{file: f}), do: File.rm(f) | ||
|
||
def expired?(%__MODULE__{touched: touched}) do | ||
Timex.now() | ||
|> Timex.shift(@expiry) | ||
|> Timex.after?(touched) | ||
end | ||
end | ||
|
||
def new(repo) do | ||
{:ok, dir} = Briefly.create(directory: true) | ||
%__MODULE__{repo: repo, dir: dir, cache: %{}} | ||
end | ||
|
||
def refresh(%__MODULE__{} = cache) do | ||
case Client.index(cache.repo.url) do | ||
{:ok, indx} -> {:ok, sweep(%{cache | index: indx})} | ||
_ -> {:error, "could not fetch index"} | ||
end | ||
end | ||
|
||
def fetch(%__MODULE__{index: nil} = cache, chart, vsn) do | ||
with {:ok, cache} <- refresh(cache), | ||
do: fetch(cache, chart, vsn) | ||
end | ||
|
||
def fetch(%__MODULE__{cache: lines} = cache, chart, vsn) do | ||
case lines[{chart, vsn}] do | ||
%Line{} = l -> {:ok, l, put_in(cache.cache[{chart, vsn}], Line.touch(l))} | ||
nil -> write(cache, chart, vsn) | ||
end | ||
end | ||
|
||
def write(%__MODULE__{} = cache, chart, vsn) do | ||
path = Path.join(cache.dir, "#{chart}.#{vsn}.tgz") | ||
with {:ok, url, digest} <- Client.chart(cache.index, chart, vsn), | ||
{:ok, tmp} <- Briefly.create(), | ||
{:ok, _} <- Client.download(url, File.stream!(tmp)), | ||
:ok <- Utils.clean_chart(tmp, path, chart), | ||
line <- Line.new(path, chart, vsn, digest), | ||
do: {:ok, line, put_in(cache.cache[{chart, vsn}], line)} | ||
end | ||
|
||
defp sweep(%__MODULE__{cache: lines} = cache) do | ||
{keep, expire} = Enum.split_with(lines, fn {_, l} -> !Line.expired?(l) end) | ||
Enum.each(expire, &Line.expire/1) | ||
Enum.each(keep, fn l -> send(self(), {:refresh, l.chart, l.vsn}) end) | ||
%{cache | cache: Map.new(keep)} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
defmodule Console.Deployments.Helm.Discovery do | ||
alias Console.Deployments.Helm.{Supervisor, Agent} | ||
|
||
def agent(url) do | ||
case maybe_rpc(url, Supervisor, :start_child, [url]) do | ||
{:ok, pid} -> {:ok, pid} | ||
{:error, {:already_started, pid}} -> {:ok, pid} | ||
err -> err | ||
end | ||
end | ||
|
||
def fetch(url, chart, vsn) do | ||
with {:ok, pid} <- agent(url), | ||
do: Agent.fetch(pid, chart, vsn) | ||
end | ||
|
||
defp maybe_rpc(id, module, func, args) do | ||
me = node() | ||
case worker_node(id) do | ||
^me -> apply(module, func, args) | ||
node -> :rpc.call(node, module, func, args) | ||
end | ||
end | ||
|
||
def worker_node(url), do: HashRing.key_to_node(ring(), url) | ||
|
||
def local?(url), do: worker_node(url) == node() | ||
|
||
defp ring() do | ||
HashRing.new() | ||
|> HashRing.add_nodes([node() | Node.list()]) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
defmodule Console.Deployments.Helm.Supervisor do | ||
use DynamicSupervisor | ||
alias Console.Deployments.Helm.Agent | ||
|
||
def start_link(init_arg \\ :ok) do | ||
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__) | ||
end | ||
|
||
def start_child(url) do | ||
DynamicSupervisor.start_child(__MODULE__, {Agent, url}) | ||
end | ||
|
||
@impl true | ||
def init(_init_arg) do | ||
DynamicSupervisor.init(strategy: :one_for_one) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
defmodule Console.Deployments.Helm.Utils do | ||
alias Console.Deployments.Tar | ||
|
||
def clean_chart(path, to, chart) when is_binary(path) do | ||
file = File.open!(path) | ||
try do | ||
clean_chart(file, to, chart) | ||
after | ||
File.close(file) | ||
end | ||
end | ||
|
||
def clean_chart(f, to, chart) do | ||
with {:ok, contents} <- Tar.tar_stream(f), | ||
do: Tar.tarball(to, remove_prefix(contents, chart)) | ||
end | ||
|
||
defp remove_prefix(contents, chart) do | ||
Enum.map(contents, fn {path, content} -> | ||
{String.trim_leading(path, "#{chart}/"), content} | ||
end) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.