Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/testing state for image version #10

Merged
merged 25 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
34796cd
Setup new states for version
zacksiri Jun 19, 2024
b3dd153
Clean up all old tests
zacksiri Jun 19, 2024
f24b81d
Add test for create version event controller
zacksiri Jun 19, 2024
26d4fbd
Add ability to select release channel via credential
zacksiri Jun 19, 2024
1497e32
Add radio select for release channel
zacksiri Jun 19, 2024
51339b4
Add release channel creation testing
zacksiri Jun 19, 2024
0745d36
Setup ability to manage lxd cluster pool for testing images
zacksiri Jun 19, 2024
e1ef16f
Setup data structures for tracking tests
zacksiri Jun 19, 2024
79f32ad
Clean up code and fix build errors
zacksiri Jun 19, 2024
b1fdefe
Setup cloak key in ci
zacksiri Jun 19, 2024
3d34da9
Add credential module for cluster
zacksiri Jun 21, 2024
297bee4
Setup oban for background jobs
zacksiri Jun 21, 2024
31191a2
Add connect worker
zacksiri Jun 21, 2024
a3d7ca7
Implement transit for cluster
zacksiri Jun 21, 2024
e0b2556
implement transtions and state machine for cluster
zacksiri Jun 21, 2024
98f74f2
start oban in application
zacksiri Jun 21, 2024
83be729
Add test for transitions and connect cluster
zacksiri Jun 21, 2024
7779de8
fix typo in connect worker
zacksiri Jun 21, 2024
d5bb35b
Add cluster controller to publish namespace
zacksiri Jun 24, 2024
0a1ff0e
Clean up event controller for publish api
zacksiri Jun 24, 2024
aa5d3a7
Add ability to create check
zacksiri Jun 24, 2024
9746776
Add ability to list checks
zacksiri Jun 24, 2024
0a8812a
Add test for transitioning assessments
zacksiri Jun 24, 2024
df74f09
Publish api can now create assessment
zacksiri Jun 24, 2024
fad50e1
Add test for create event for assessment
zacksiri Jun 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,4 @@ jobs:
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}
DEFAULT_CDN_HOST: some.domain.com
POLAR_CLOAK_KEY: ${{secrets.POLAR_CLOAK_KEY}}
4 changes: 4 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ config :polar,
ecto_repos: [Polar.Repo],
generators: [timestamp_type: :utc_datetime]

config :polar, Polar.Vault, json_library: Jason

config :polar, Oban, engine: Oban.Engines.Basic, queues: [default: 3], repo: Polar.Repo

# Configures the endpoint
config :polar, PolarWeb.Endpoint,
url: [host: "localhost"],
Expand Down
15 changes: 15 additions & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ config :polar, Polar.Assets,
endpoint: System.get_env("AWS_S3_ENDPOINT"),
default_cdn_host: default_cdn_host

cloak_key =
System.get_env("CLOAK_KEY") || System.get_env("POLAR_CLOAK_KEY") ||
raise """
environment variable CLOAK_KEY or POLAR_CLOAK_KEY is missing.
You can generate one using 32 |> :crypto.strong_rand_bytes() |> Base.encode64()
"""

config :polar, Polar.Vault,
ciphers: [
default: {
Cloak.Ciphers.AES.GCM,
tag: "AES.GCM.V1", key: Base.decode64!(cloak_key)
}
]

if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
Expand Down
4 changes: 4 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ config :polar, PolarWeb.Endpoint,
secret_key_base: "6KFZ6zNwk0UXHww8AnEmReHHKixN5GmuKJLFVB+/YvfcvgVaKMwM3G4SvSNz5Z8s",
server: false

config :polar, Oban, testing: :manual

config :polar, :lexdee, Polar.LexdeeMock

# In test we don't send emails.
config :polar, Polar.Mailer, adapter: Swoosh.Adapters.Test

Expand Down
8 changes: 6 additions & 2 deletions lib/polar/accounts/space/credential.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Polar.Accounts.Space.Credential do
import Ecto.Changeset

alias Polar.Accounts.Space
alias Polar.Streams.ReleaseChannel

alias __MODULE__.Transitions
alias __MODULE__.Event
Expand All @@ -27,11 +28,13 @@ defmodule Polar.Accounts.Space.Credential do
field :name, :string

field :token, :binary
field :type, :string
field :type, :string, default: "lxd"

field :expires_in, :integer, virtual: true
field :expires_at, :utc_datetime

field :release_channel, :string, default: "active"

belongs_to :space, Space

timestamps(type: :utc_datetime_usec)
Expand All @@ -46,12 +49,13 @@ defmodule Polar.Accounts.Space.Credential do
expires_in_range_values = Enum.map(@expires_in_range, fn r -> r.value end)

credential
|> cast(attrs, [:name, :expires_in, :type])
|> cast(attrs, [:name, :expires_in, :type, :release_channel])
|> generate_token()
|> validate_inclusion(:expires_in, expires_in_range_values)
|> validate_inclusion(:type, types())
|> maybe_set_expires_at()
|> validate_required([:token, :type, :name])
|> validate_inclusion(:release_channel, ReleaseChannel.valid_names())
|> unique_constraint(:name, name: :space_credentials_space_id_name_index)
end

Expand Down
2 changes: 2 additions & 0 deletions lib/polar/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ defmodule Polar.Application do
children = [
PolarWeb.Telemetry,
Polar.Repo,
{Oban, Application.fetch_env!(:polar, Oban)},
{DNSCluster, query: Application.get_env(:polar, :dns_cluster_query) || :ignore},
{Phoenix.PubSub, name: Polar.PubSub},
# Start the Finch HTTP client for sending emails
{Finch, name: Polar.Finch},
Polar.Vault,
# Start a worker by calling: Polar.Worker.start_link(arg)
# {Polar.Worker, arg},
# Start to serve requests, typically the last entry
Expand Down
3 changes: 3 additions & 0 deletions lib/polar/encrypted/map.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Polar.Encrypted.Map do
use Cloak.Ecto.Map, vault: Polar.Vault
end
27 changes: 27 additions & 0 deletions lib/polar/machines.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule Polar.Machines do
alias __MODULE__.Cluster

defdelegate list_clusters(scope),
to: Cluster.Manager,
as: :list

defdelegate create_cluster(params),
to: Cluster.Manager,
as: :create

alias __MODULE__.Check

defdelegate list_checks(),
to: Check.Manager,
as: :list

defdelegate create_check(params),
to: Check.Manager,
as: :create

alias __MODULE__.Assessment

defdelegate create_assessment(version, params),
to: Assessment.Manager,
as: :create
end
44 changes: 44 additions & 0 deletions lib/polar/machines/assessment.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
defmodule Polar.Machines.Assessment do
use Ecto.Schema
import Ecto.Changeset

alias Polar.Streams
alias Polar.Machines.Check
alias Polar.Machines.Cluster

alias __MODULE__.Event
alias __MODULE__.Transitions

use Eventful.Transitable

Transitions
|> governs(:current_state, on: Event)

@valid_attrs ~w(
check_id
cluster_id
)a

@required_attrs ~w(
check_id
cluster_id
)a

schema "assessments" do
field :current_state, :string, default: "created"

belongs_to :check, Check
belongs_to :cluster, Cluster

belongs_to :version, Streams.Version

timestamps(type: :utc_datetime_usec)
end

@doc false
def changeset(assessment, attrs) do
assessment
|> cast(attrs, @valid_attrs)
|> validate_required(@required_attrs)
end
end
12 changes: 12 additions & 0 deletions lib/polar/machines/assessment/event.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule Polar.Machines.Assessment.Event do
alias Polar.Machines.Assessment
alias Polar.Accounts.User

use Eventful,
parent: {:assessment, Assessment},
actor: {:user, User}

alias Assessment.Transitions

handle(:transitions, using: Transitions)
end
10 changes: 10 additions & 0 deletions lib/polar/machines/assessment/manager.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule Polar.Machines.Assessment.Manager do
alias Polar.Repo
alias Polar.Machines.Assessment

def create(version, params) do
%Assessment{version_id: version.id}
|> Assessment.changeset(params)
|> Repo.insert()
end
end
17 changes: 17 additions & 0 deletions lib/polar/machines/assessment/transit.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defimpl Eventful.Transit, for: Polar.Machines.Assessment do
alias Polar.Machines.Assessment.Event

def perform(assessment, user, event_name, options \\ []) do
comment = Keyword.get(options, :comment)
domain = Keyword.get(options, :domain, "transitions")
parameters = Keyword.get(options, :parameters, %{})

assessment
|> Event.handle(user, %{
domain: domain,
name: event_name,
comment: comment,
parameters: parameters
})
end
end
24 changes: 24 additions & 0 deletions lib/polar/machines/assessment/transitions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule Polar.Machines.Assessment.Transitions do
@behaviour Eventful.Handler
use Eventful.Transition, repo: Polar.Repo

alias Polar.Machines.Assessment

Assessment
|> transition(
[from: "created", to: "running", via: "run"],
fn changes -> transit(changes) end
)

Assessment
|> transition(
[from: "running", to: "passed", via: "pass"],
fn changes -> transit(changes) end
)

Assessment
|> transition(
[from: "running", to: "failed", via: "fail"],
fn changes -> transit(changes) end
)
end
29 changes: 29 additions & 0 deletions lib/polar/machines/check.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule Polar.Machines.Check do
use Ecto.Schema
import Ecto.Changeset

schema "checks" do
field :name, :string, virtual: true

field :slug, :string
field :description, :string

timestamps(type: :utc_datetime_usec)
end

@doc false
def changeset(check, attrs) do
check
|> cast(attrs, [:name, :description])
|> validate_required([:name, :description])
|> generate_slug()
end

defp generate_slug(changeset) do
if name = get_change(changeset, :name) do
put_change(changeset, :slug, Slug.slugify(name))
else
changeset
end
end
end
14 changes: 14 additions & 0 deletions lib/polar/machines/check/manager.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule Polar.Machines.Check.Manager do
alias Polar.Repo
alias Polar.Machines.Check

def list() do
Repo.all(Check)
end

def create(params) do
%Check{}
|> Check.changeset(params)
|> Repo.insert()
end
end
97 changes: 97 additions & 0 deletions lib/polar/machines/cluster.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
defmodule Polar.Machines.Cluster do
use Ecto.Schema
import Ecto.Changeset

alias __MODULE__.Credential
alias __MODULE__.Transitions
alias __MODULE__.Event

use Eventful.Transitable

Transitions
|> governs(:current_state, on: Event)

@valid_attrs ~w(
name
type
arch
credential_endpoint
credential_password
credential_password_confirmation
)a

@required_attrs ~w(
name
type
arch
credential_endpoint
credential_password
credential_password_confirmation
)a

schema "clusters" do
field :name, :string
field :current_state, :string, default: "created"

field :type, :string, default: "lxd"
field :arch, :string

field :credential_endpoint, :string, virtual: true
field :credential_password, :string, virtual: true
field :credential_password_confirmation, :string, virtual: true
field :credential, Polar.Encrypted.Map

timestamps(type: :utc_datetime_usec)
end

@doc false
def changeset(cluster, attrs) do
cluster
|> cast(attrs, @valid_attrs)
|> validate_required(@required_attrs)
|> validate_inclusion(:type, ["lxd", "incus"])
|> validate_inclusion(:arch, ["amd64", "arm64"])
|> process_credential()
end

def update_changeset(cluster, attrs) do
cluster
|> cast(attrs, [:credential_endpoint])
|> maybe_update_credential()
end

defp maybe_update_credential(%{data: %{credential: credential}} = changeset) do
if changeset.valid? do
endpoint = get_change(changeset, :credential_endpoint)

credential =
%Credential{
endpoint: credential["endpoint"],
private_key: credential["private_key"],
certificate: credential["certificate"]
}
|> Credential.update!(%{endpoint: "https://#{endpoint}"})

put_change(changeset, :credential, credential)
else
changeset
end
end

defp process_credential(changeset) do
if changeset.valid? do
endpoint = get_change(changeset, :credential_endpoint)

credential =
Credential.create!(%{
endpoint: "https://#{endpoint}",
password: get_change(changeset, :credential_password),
password_confirmation: get_change(changeset, :credential_password_confirmation)
})

put_change(changeset, :credential, credential)
else
changeset
end
end
end
Loading