Skip to content

Commit

Permalink
Add ability to select release channel via credential
Browse files Browse the repository at this point in the history
  • Loading branch information
zacksiri committed Jun 19, 2024
1 parent f24b81d commit 26d4fbd
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 6 deletions.
6 changes: 5 additions & 1 deletion 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 Down Expand Up @@ -32,6 +33,8 @@ defmodule Polar.Accounts.Space.Credential do
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
10 changes: 10 additions & 0 deletions lib/polar/streams/product.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ defmodule Polar.Streams.Product do
has_one :latest_version, Version, where: [current_state: "active"]

has_many :active_versions, Version, where: [current_state: "active"]
has_many :testing_versions, Version, where: [current_state: "testing"]

timestamps(type: :utc_datetime_usec)
end
Expand All @@ -54,6 +55,15 @@ defmodule Polar.Streams.Product do
|> validate_inclusion(:arch, ["arm64", "amd64"])
end

def scope(:testing, queryable) do
from(
p in queryable,
join: v in assoc(p, :testing_versions),
where: not is_nil(v.product_id),
group_by: [:id]
)
end

def scope(:active, queryable) do
from(
p in queryable,
Expand Down
17 changes: 17 additions & 0 deletions lib/polar/streams/release_channel.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule Polar.Streams.ReleaseChannel do
def entries,
do: %{
"active" => %{
scope: [:active],
preload: [active_versions: [:items]]
},
"testing" => %{
scope: [:testing],
preload: [testing_versions: [:items]]
}
}

def valid_names do
Map.keys(entries())
end
end
8 changes: 7 additions & 1 deletion lib/polar_web/controllers/stream_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ defmodule PolarWeb.StreamController do
alias Polar.Accounts
alias Polar.Streams

alias Polar.Streams.ReleaseChannel

action_fallback PolarWeb.FallbackController

def index(conn, %{"space_token" => space_token}) do
credential = Accounts.get_space_credential(token: space_token)

if credential do
products = Streams.list_products([:active])
release_channel =
ReleaseChannel.entries()
|> Map.fetch!(credential.release_channel)

products = Streams.list_products(release_channel.scope)

render(conn, :index, %{products: products})
end
Expand Down
10 changes: 8 additions & 2 deletions lib/polar_web/controllers/streams/image_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ defmodule PolarWeb.Streams.ImageController do
alias Polar.Accounts
alias Polar.Streams

alias Polar.Streams.ReleaseChannel

action_fallback PolarWeb.FallbackController

def index(conn, %{"space_token" => space_token}) do
credential = Accounts.get_space_credential(token: space_token)

if credential do
release_channel =
ReleaseChannel.entries()
|> Map.fetch!(credential.release_channel)

products =
Streams.list_products([:active])
|> Repo.preload(active_versions: [:items])
Streams.list_products(release_channel.scope)
|> Repo.preload(release_channel.preload)

render(conn, :index, %{products: products, credential: credential})
end
Expand Down
10 changes: 8 additions & 2 deletions lib/polar_web/controllers/streams/image_json.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ defmodule PolarWeb.Streams.ImageJSON do
{Product.key(product), product_attributes(product, params)}
end

defp product_attributes(product, params) do
defp product_attributes(product, %{credential: credential} = params) do
versions_key =
"#{credential.release_channel}_versions"
|> String.to_existing_atom()

versions = get_in(product, [Access.key!(versions_key)])

%{
aliases: Enum.join(product.aliases, ","),
arch: product.arch,
Expand All @@ -30,7 +36,7 @@ defmodule PolarWeb.Streams.ImageJSON do
requirements: product.requirements,
variant: product.variant,
versions:
Enum.map(product.active_versions, &render_version(&1, params))
Enum.map(versions, &render_version(&1, params))
|> Enum.into(%{})
}
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Polar.Repo.Migrations.AddReleaseChannelToSpaceCredentials do
use Ecto.Migration

def change do
alter table(:space_credentials) do
add :release_channel, :string, default: "active"
end
end
end
159 changes: 159 additions & 0 deletions test/polar_web/controllers/streams/image_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
defmodule PolarWeb.Streams.ImageControllerTest do
use PolarWeb.ConnCase

alias Polar.Accounts
alias Polar.Streams

alias Polar.Streams.Product

import Polar.AccountsFixtures

setup do
user = user_fixture()

password = Accounts.generate_automation_password()

_bot = bot_fixture(%{password: password})

{:ok, space} = Accounts.create_space(user, %{name: "some-test-item"})

{:ok, %Product{} = product_with_testing} =
Streams.create_product(%{
aliases: ["alpine/3.19", "alpine/3.19/default"],
arch: "amd64",
os: "Alpine",
release: "3.19",
release_title: "3.19",
variant: "default",
requirements: %{
secureboot: false
}
})

{:ok, version} =
Streams.create_version(product_with_testing, %{
serial: "20240209_13:00",
items: [
%{
name: "lxd.tar.gz",
file_type: "lxd.tar.gz",
hash: "35363f3d086271ed5402d61ab18ec03987bed51758c00079b8c9d372ff6d62dd",
size: 876,
is_metadata: true,
path: "images/alpine/edge/amd64/default/20240209_13:00/incus.tar.xz"
},
%{
name: "root.squashfs",
file_type: "squashfs",
hash: "47cc4070da1bf17d8364c390…3603f4ed7e9e46582e690d2",
size: 2_982_800,
path: "images/alpine/edge/amd64/default/20240209_13:00/rootfs.tar.xz"
}
]
})

{:ok, %{resource: _testing_version}} =
Eventful.Transit.perform(version, user, "test")

{:ok, %Product{} = product_without_testing} =
Streams.create_product(%{
aliases: ["alpine/3.20", "alpine/3.20/default"],
arch: "amd64",
os: "Alpine",
release: "3.20",
release_title: "3.20",
variant: "default",
requirements: %{
secureboot: false
}
})

{:ok, version} =
Streams.create_version(product_without_testing, %{
serial: "20240209_13:00",
items: [
%{
name: "lxd.tar.gz",
file_type: "lxd.tar.gz",
hash: "35363f3d086271ed5402d61ab18ec03987bed51758c00079b8c9d372ff6d62aa",
size: 876,
is_metadata: true,
path: "images/alpine/edge/amd64/default/20240209_13:00/incus.tar.xz"
},
%{
name: "root.squashfs",
file_type: "squashfs",
hash: "67cc4070da1bf17d8364c390…3603f4ed7e9e46582e690d1",
size: 2_982_800,
path: "images/alpine/edge/amd64/default/20240209_13:00/rootfs.tar.xz"
}
]
})

{:ok, %{resource: testing_version}} =
Eventful.Transit.perform(version, user, "test")

{:ok, %{resource: _active_version}} =
Eventful.Transit.perform(testing_version, user, "activate")

{:ok,
space: space,
user: user,
product_with_testing: product_with_testing,
product_without_testing: product_without_testing}
end

describe "product with testing version" do
setup %{space: space, user: user} do
{:ok, credential} =
Accounts.create_space_credential(space, user, %{
expires_in: 1_296_000,
name: "test-02",
type: "lxd",
release_channel: "testing"
})

{:ok, credential: credential}
end

test "GET /spaces/:space_token/images.json", %{
credential: credential,
product_without_testing: product_without_testing
} do
conn = get(build_conn(), ~s"/spaces/#{credential.token}/streams/v1/images.json")

assert %{"products" => products} = json_response(conn, 200)

product_keys = Map.keys(products)

assert Product.key(product_without_testing) not in product_keys
end
end

describe "product with active version" do
setup %{space: space, user: user} do
{:ok, credential} =
Accounts.create_space_credential(space, user, %{
expires_in: 1_296_000,
name: "test-02",
type: "lxd",
release_channel: "active"
})

{:ok, credential: credential}
end

test "GET /spaces/:space_token/images.json", %{
credential: credential,
product_with_testing: product_with_testing
} do
conn = get(build_conn(), ~s"/spaces/#{credential.token}/streams/v1/images.json")

assert %{"products" => products} = json_response(conn, 200)

product_keys = Map.keys(products)

assert Product.key(product_with_testing) not in product_keys
end
end
end

0 comments on commit 26d4fbd

Please sign in to comment.