Skip to content

Commit

Permalink
Merge branch 'release/v1.0.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
zacksiri committed Jul 3, 2024
2 parents 1f596e8 + 5a390f1 commit 18d3471
Show file tree
Hide file tree
Showing 29 changed files with 1,111 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Icepak github action will do the following:

- Process metdata for all the built items
- Interact with [polar](https://github.com/upmaru/polar) and publish images
- Test images before publishing images

## Installation

Expand Down
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,14 @@ runs:
uses: actions/cache@v4
with:
path: ~/.mix
key: ${{ runner.arch }}-icepak-0.1.7
key: ${{ runner.arch }}-icepak-1.0.1

- name: Install Pakman
if: steps.cache-icepak.outputs.cache-hit != 'true'
run: |
mix local.rebar --force
mix local.hex --force
mix escript.install hex icepak 0.1.7 --force
mix escript.install hex icepak 1.0.1 --force
shell: alpine.sh {0}
env:
MIX_ENV: prod
3 changes: 3 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import Config

config :icepak, :env, :test
config :icepak, :lexdee, Icepak.LexdeeMock
config :icepak, :polar, Icepak.PolarMock
config :lexdee, :environment, :test
13 changes: 13 additions & 0 deletions lib/icepak.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@ defmodule Icepak do
Documentation for `Icepak`.
"""

@architecture_mappings %{
"x86_64" => "amd64",
"aarch64" => "arm64",
"arm64" => "arm64",
"amd64" => "amd64"
}

def architecture_mappings, do: @architecture_mappings

defdelegate validate(options),
to: Icepak.Checks,
as: :perform

defdelegate push(options),
to: Icepak.Push,
as: :perform
Expand Down
3 changes: 2 additions & 1 deletion lib/icepak/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ defmodule Icepak.Application do
@impl true
def start(_type, _args) do
children = [
{Finch, finch_options(Application.get_env(:icepak, :env))}
{Finch, finch_options(Application.get_env(:icepak, :env))},
{Task.Supervisor, name: Icepak.TaskSupervisor}
# Starts a worker by calling: Icepak.Worker.start_link(arg)
# {Icepak.Worker, arg}
]
Expand Down
159 changes: 159 additions & 0 deletions lib/icepak/checks.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
defmodule Icepak.Checks do
alias Icepak.Item

@checks_mapping %{
"ipv4" => Icepak.Checks.IPv4,
"ipv6" => Icepak.Checks.IPv6
}

@lexdee Application.compile_env(:icepak, :lexdee) || Lexdee
@polar Application.compile_env(:icepak, :polar) || Icepak.Polar

require Logger

def perform(options) do
base_path =
options
|> Keyword.get(:path, System.get_env("GITHUB_WORKSPACE"))
|> Path.expand()

os = Keyword.fetch!(options, :os)
arch = Keyword.fetch!(options, :arch)
release = Keyword.fetch!(options, :release)
variant = Keyword.fetch!(options, :variant)
serial = Keyword.fetch!(options, :serial)

checks =
Keyword.fetch!(options, :checks)
|> String.split(",")

arch = Map.fetch!(Icepak.architecture_mappings(), arch)

storage_path = Path.join(["images", os, release, arch, variant, serial])

polar_client = @polar.authenticate()

item_params = %{
base_path: base_path,
storage_path: storage_path
}

items =
File.ls!(base_path)
|> Enum.filter(fn file_name ->
file_name in ["incus.tar.xz", "rootfs.squashfs", "disk.qcow2"]
end)
|> Enum.flat_map(&Item.prepare(&1, item_params))

product_key = Enum.join([os, release, arch, variant], ":")

%{status: 200, body: %{"data" => product}} = @polar.get_product(polar_client, product_key)
%{status: 200, body: %{"data" => version}} = @polar.get_version(polar_client, product, serial)

%{status: 201, body: %{"data" => _event}} =
@polar.transition_version(polar_client, version, %{"name" => "test"})

clusters = @polar.get_testing_clusters(polar_client)
polar_checks = @polar.get_testing_checks(polar_client)

checks =
Enum.filter(checks, fn c ->
c in Enum.map(polar_checks, fn pc -> pc.name end)
end)

assessment_events =
items
|> Enum.filter(fn i -> i.is_metadata end)
|> Enum.flat_map(
&handle_metadata(&1, %{
arch: arch,
checks: checks,
polar_checks: polar_checks,
product: product,
version: version,
polar_client: polar_client,
clusters: clusters
})
)
|> Enum.reject(fn {:ok, value} -> value == :skip end)

passes =
Enum.filter(assessment_events, fn {:ok, %{body: %{"data" => event}}} ->
event["name"] == "pass"
end)

if Enum.count(passes) == Enum.count(assessment_events) do
Logger.info("[Checks] ✅ Activating #{product["id"]} #{version["serial"]} all checks passed")

@polar.transition_version(polar_client, version, %{"name" => "activate"})
else
Logger.info(
"[Checks] ❌ Deactivating #{product["id"]} #{version["serial"]} some checks failed"
)

@polar.transition_version(polar_client, version, %{"name" => "deactivate"})
end
end

defp handle_metadata(metadata, state) do
[type, _, _] = String.split(metadata.name, ".")

cluster =
Enum.find(state.clusters, fn c ->
c.arch == state.arch and c.type == type
end)

if cluster do
results =
Enum.flat_map(
state.checks,
&handle_check(&1, %{
metadata: metadata,
polar_client: state.polar_client,
polar_checks: state.polar_checks,
product: state.product,
version: state.version,
cluster: cluster
})
)

client =
Lexdee.create_client(
cluster.endpoint,
cluster.certificate,
cluster.private_key,
timeout: 300_000
)

Enum.map(metadata.combined_hashes, &handle_cleanup(&1, client))

results
else
[]
end
end

defp handle_check(check, state) do
module = Map.fetch!(@checks_mapping, check)

polar_check =
Enum.find(state.polar_checks, fn pc ->
pc.name == check
end)

state = Map.put(state, :check, polar_check)

module.perform(state)
end

defp handle_cleanup(hash_item, client) do
Logger.info("[Checks] Deleting image #{hash_item.hash}")

with {:ok, %{body: delete_image}} <-
@lexdee.delete_image(client, hash_item.hash, query: [project: "icepak-test"]),
{:ok, _} <-
@lexdee.wait_for_operation(client, delete_image["id"], query: [timeout: 300]) do
:ok
end
end
end
58 changes: 58 additions & 0 deletions lib/icepak/checks/ipv4.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
defmodule Icepak.Checks.IPv4 do
@check_name "ipv4"

@callback perform(map) :: map

use Icepak.Checks.Setup

def handle_assessment(hash_item, options) do
cluster = Keyword.fetch!(options, :cluster)
polar_client = Keyword.fetch!(options, :polar_client)
version = Keyword.fetch!(options, :version)
check = Keyword.fetch!(options, :check)
product = Keyword.fetch!(options, :product)

with {:ok,
%{
client: client,
assessment: assessment,
project_name: project_name,
instance_name: instance_name,
instance_type: instance_type
} = environment} <-
prepare(@check_name, %{
polar_client: polar_client,
version: version,
hash_item: hash_item,
product: product,
cluster: cluster,
check: check
}) do
{:ok, %{body: %{"network" => network}}} =
@lexdee.get_state(client, "/1.0/instances/#{instance_name}",
query: [project: project_name]
)

%{"addresses" => addresses} = Map.get(network, "eth0")
inet = Enum.find(addresses, fn a -> a["family"] == "inet" end)

teardown(environment)

if not is_nil(inet) do
Logger.info("[#{@check_name}] ✅ Passed for #{instance_type} #{instance_name}")

@polar.transition_testing_assessment(polar_client, assessment, %{name: "pass"})
else
Logger.info("[#{@check_name}] ❌ Failed for #{instance_type} #{instance_name}")

@polar.transition_testing_assessment(polar_client, assessment, %{name: "fail"})
end
else
{:ok, :skip} ->
:skip

{:error, %{"error" => error}} ->
raise CheckFailError, error
end
end
end
58 changes: 58 additions & 0 deletions lib/icepak/checks/ipv6.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
defmodule Icepak.Checks.IPv6 do
@check_name "ipv6"

@callback perform(map) :: map

use Icepak.Checks.Setup

def handle_assessment(hash_item, options) do
cluster = Keyword.fetch!(options, :cluster)
polar_client = Keyword.fetch!(options, :polar_client)
version = Keyword.fetch!(options, :version)
check = Keyword.fetch!(options, :check)
product = Keyword.fetch!(options, :product)

with {:ok,
%{
client: client,
assessment: assessment,
project_name: project_name,
instance_name: instance_name,
instance_type: instance_type
} = environment} <-
prepare(@check_name, %{
polar_client: polar_client,
version: version,
hash_item: hash_item,
product: product,
cluster: cluster,
check: check
}) do
{:ok, %{body: %{"network" => network}}} =
@lexdee.get_state(client, "/1.0/instances/#{instance_name}",
query: [project: project_name]
)

%{"addresses" => addresses} = Map.get(network, "eth0")
inet6 = Enum.find(addresses, fn a -> a["family"] == "inet6" end)

teardown(environment)

if not is_nil(inet6) do
Logger.info("[#{@check_name}] ✅ Passed for #{instance_type} #{instance_name}")

@polar.transition_testing_assessment(polar_client, assessment, %{name: "pass"})
else
Logger.info("[#{@check_name}] ❌ Failed for #{instance_type} #{instance_name}")

@polar.transition_testing_assessment(polar_client, assessment, %{name: "fail"})
end
else
{:ok, :skip} ->
:skip

{:error, %{"error" => error}} ->
raise CheckFailError, error
end
end
end
Loading

0 comments on commit 18d3471

Please sign in to comment.