Skip to content

Commit

Permalink
Merge pull request #812 from lusergit/feat/image-deployment
Browse files Browse the repository at this point in the history
feat(containers): image deployment caching
  • Loading branch information
davidebriani authored Feb 13, 2025
2 parents ac226ee + 3968f12 commit 1e4b6dc
Show file tree
Hide file tree
Showing 11 changed files with 612 additions and 31 deletions.
9 changes: 9 additions & 0 deletions backend/lib/edgehog/containers/containers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ defmodule Edgehog.Containers do
define :fetch_image, action: :read, get_by: [:id]
end

resource Edgehog.Containers.Image.Deployment do
define :deploy_image, action: :deploy, args: [:image, :device]
define :fetch_image_deployment, action: :read, get_by_identity: :image_instance
define :mark_image_deployment_as_sent, action: :mark_as_sent
define :mark_image_deployment_as_unpulled, action: :mark_as_unpulled
define :mark_image_deployment_as_pulled, action: :mark_as_pulled
define :mark_image_deployment_as_errored, action: :mark_as_errored, args: [:message]
end

resource Edgehog.Containers.ImageCredentials

resource Edgehog.Containers.Release do
Expand Down
22 changes: 13 additions & 9 deletions backend/lib/edgehog/containers/deployment/changes/check_images.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#
# This file is part of Edgehog.
#
# Copyright 2024 SECO Mind Srl
# Copyright 2024 - 2025 SECO Mind Srl
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -22,22 +22,26 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckImages do
@moduledoc false
use Ash.Resource.Change

alias Edgehog.Containers

@impl Ash.Resource.Change
def change(changeset, _opts, _context) do
def change(changeset, _opts, context) do
deployment = changeset.data
%{tenant: tenant} = context

with :sent <- deployment.status,
{:ok, deployment} <-
Ash.load(deployment, device: :available_images, release: [containers: [:image]]) do
available_images_ids =
Enum.map(deployment.device.available_images, & &1.id)
Ash.load(deployment, device: [], release: [containers: [:image]]) do
device = deployment.device

missing_images =
images_ready? =
deployment.release.containers
|> Enum.map(& &1.image.id)
|> Enum.reject(&(&1 in available_images_ids))
|> Enum.map(& &1.image)
|> Enum.uniq_by(& &1.id)
|> Enum.map(&Containers.fetch_image_deployment!(&1.id, device.id, tenant: tenant, load: [:ready?]))
|> Enum.all?(& &1.ready?)

if missing_images == [] do
if images_ready? do
Ash.Changeset.change_attribute(changeset, :status, :created_images)
else
changeset
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#
# This file is part of Edgehog.
#
# Copyright 2025 SECO Mind Srl
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#

defmodule Edgehog.Containers.Image.Changes.DeployImageOnDevice do
@moduledoc false
use Ash.Resource.Change

alias Edgehog.Containers
alias Edgehog.Devices

@impl Ash.Resource.Change
@spec change(Ash.Changeset.t(), any(), %{:tenant => any(), optional(any()) => any()}) ::
Ash.Changeset.t()
def change(changeset, _opts, context) do
%{tenant: tenant} = context
image = Ash.Changeset.get_argument(changeset, :image)
device = Ash.Changeset.get_argument(changeset, :device)

Ash.Changeset.after_action(changeset, fn _changeset, image_deployment ->
with {:ok, _device} <- Devices.send_create_image_request(device, image, tenant: tenant) do
Containers.mark_image_deployment_as_sent(image_deployment)
end
end)
end
end
118 changes: 118 additions & 0 deletions backend/lib/edgehog/containers/image/deployment.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#
# This file is part of Edgehog.
#
# Copyright 2025 SECO Mind Srl
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#

defmodule Edgehog.Containers.Image.Deployment do
@moduledoc false
use Edgehog.MultitenantResource,
domain: Edgehog.Containers,
extensions: [AshGraphql.Resource]

alias Edgehog.Containers.Image
alias Edgehog.Containers.Image.Changes
alias Edgehog.Devices.Device

graphql do
type :image_deployment
end

actions do
defaults [:read, :destroy, create: [:image_id, :device_id, :state]]

create :deploy do
description """
Deploys an image on a device.
"""

argument :image, :struct do
constraints instance_of: Image
allow_nil? false
end

argument :device, :struct do
constraints instance_of: Device
allow_nil? false
end

change set_attribute(:state, :created)
change manage_relationship(:image, type: :append)
change manage_relationship(:device, type: :append)
change Changes.DeployImageOnDevice
end

update :mark_as_sent do
change set_attribute(:state, :sent)
end

update :mark_as_unpulled do
change set_attribute(:state, :unpulled)
end

update :mark_as_pulled do
change set_attribute(:state, :pulled)
end

update :mark_as_errored do
argument :message, :string do
allow_nil? false
end

change set_attribute(:last_message, arg(:message))
change set_attribute(:state, :error)
end
end

attributes do
uuid_primary_key :id

attribute :last_message, :string

attribute :state, :atom,
constraints: [
one_of: [:created, :sent, :pulled, :unpulled, :error]
]

timestamps()
end

relationships do
belongs_to :image, Edgehog.Containers.Image do
attribute_type :uuid
end

belongs_to :device, Edgehog.Devices.Device
end

calculations do
calculate :ready?, :boolean, expr(state in [:pulled, :unpulled])
end

identities do
identity :image_instance, [:image_id, :device_id]
end

postgres do
table "image_deployments"

references do
reference :image, on_delete: :delete
reference :device, on_delete: :delete
end
end
end
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#
# This file is part of Edgehog.
#
# Copyright 2024 SECO Mind Srl
# Copyright 2024 - 2025 SECO Mind Srl
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -28,6 +28,7 @@ defmodule Edgehog.Containers.Image do

graphql do
type :image
paginate_relationship_with devices: :relay
end

actions do
Expand All @@ -51,6 +52,12 @@ defmodule Edgehog.Containers.Image do
attribute_type :uuid
public? true
end

many_to_many :devices, Edgehog.Devices.Device do
through Edgehog.Containers.Image.Deployment
join_relationship :image_deployments
public? true
end
end

identities do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ defmodule Edgehog.Containers.ManualActions.SendDeployRequest do
|> Enum.flat_map(& &1.volumes)
|> Enum.uniq_by(& &1.id)

with :ok <- send_create_image_requests(device, images),
with :ok <- deploy_images(device, images),
:ok <- send_create_volume_requests(device, volumes),
:ok <- deploy_networks(device, networks),
:ok <- deploy_containers(device, containers),
Expand Down Expand Up @@ -80,15 +80,33 @@ defmodule Edgehog.Containers.ManualActions.SendDeployRequest do
end
end

defp send_create_image_requests(device, images) do
defp deploy_images(device, images) do
images =
images
|> Enum.reject(&image_deployed?(&1, device))
|> Enum.uniq_by(& &1.id)

Enum.reduce_while(images, :ok, fn image, _acc ->
case Devices.send_create_image_request(device, image) do
{:ok, _device} -> {:cont, :ok}
{:error, reason} -> {:halt, {:error, reason}}
case Containers.deploy_image(image, device, tenant: image.tenant_id) do
{:ok, _image_deployment} ->
{:cont, :ok}

{:error, reason} ->
{:halt, {:error, reason}}
end
end)
end

defp image_deployed?(image, device) do
case Containers.fetch_image_deployment(image.id, device.id, tenant: image.tenant_id) do
{:ok, _deployment} ->
true

_ ->
false
end
end

defp send_create_volume_requests(device, volumes) do
Enum.reduce_while(volumes, :ok, fn volume, _acc ->
case Devices.send_create_volume_request(device, volume) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,20 @@ defmodule Edgehog.Triggers.Handler.ManualActions.HandleTrigger do
|> Ash.create(tenant: tenant)
end

defp handle_event(%IncomingData{interface: @available_images} = event, tenant, _realm_id, _device_id, _timestamp) do
defp handle_event(%IncomingData{interface: @available_images} = event, tenant, realm_id, device_id, _timestamp) do
device = Devices.fetch_device_by_identity!(device_id, realm_id, tenant: tenant)

case String.split(event.path, "/") do
["", image_id, "pulled"] ->
image_deployment =
Containers.fetch_image_deployment!(image_id, device.id, tenant: tenant)

if event.value do
Containers.mark_image_deployment_as_pulled!(image_deployment, tenant: tenant)
else
Containers.mark_image_deployment_as_unpulled!(image_deployment, tenant: tenant)
end

containers = Containers.containers_with_image!(image_id, tenant: tenant)

releases =
Expand Down
Loading

0 comments on commit 1e4b6dc

Please sign in to comment.