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

feat(containers): run ready actions when the deployment is ready #731

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 11 additions & 2 deletions backend/lib/edgehog/containers/containers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ defmodule Edgehog.Containers do

alias Edgehog.Containers.Application
alias Edgehog.Containers.Deployment
alias Edgehog.Containers.DeploymentReadyAction
alias Edgehog.Containers.DeploymentReadyAction.Upgrade
alias Edgehog.Containers.ImageCredentials
alias Edgehog.Containers.Release

Expand Down Expand Up @@ -103,6 +105,7 @@ defmodule Edgehog.Containers do
define :delete_deployment, action: :destroy
define :deployment_update_status, action: :update_status
define :deployments_with_release, action: :filter_by_release, args: [:release_id]
define :run_ready_actions, action: :run_ready_actions
end

resource Edgehog.Containers.Image do
Expand All @@ -124,13 +127,19 @@ defmodule Edgehog.Containers do
resource Edgehog.Containers.Network
resource Edgehog.Containers.Volume

resource Edgehog.Containers.DeploymentReadyAction
resource Edgehog.Containers.DeploymentReadyAction.Upgrade
resource DeploymentReadyAction
resource Upgrade

resource Edgehog.Containers.ContainerNetwork do
define :containers_with_network,
action: :containers_by_network,
args: [:network_id]
end

resource DeploymentReadyAction do
define :run_ready_action, action: :run
end

resource Upgrade
end
end
12 changes: 12 additions & 0 deletions backend/lib/edgehog/containers/deployment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ defmodule Edgehog.Containers.Deployment do
manual {ManualActions.SendDeploymentCommand, command: :delete}
end

update :run_ready_actions do
description """
Executes deployment callbacks
"""

manual ManualActions.RunReadyActions
end

action :send_deploy_request do
argument :deployment, :struct do
constraints instance_of: __MODULE__
Expand Down Expand Up @@ -144,6 +152,10 @@ defmodule Edgehog.Containers.Deployment do
attribute_type :uuid
public? true
end

has_many :ready_actions, Edgehog.Containers.DeploymentReadyAction do
public? true
end
end

identities do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckDeployments do
@moduledoc false
use Ash.Resource.Change

alias Edgehog.Containers
alias Edgehog.Devices

@impl Ash.Resource.Change
Expand All @@ -37,7 +38,13 @@ defmodule Edgehog.Containers.Deployment.Changes.CheckDeployments do
Enum.map(available_deployments, & &1.id)

if deployment.id in available_deployments do
Ash.Changeset.change_attribute(changeset, :status, :ready)
changeset
|> Ash.Changeset.change_attribute(:status, :ready)
|> Ash.Changeset.after_transaction(fn _changeset, transaction_result ->
with {:ok, deployment} <- transaction_result do
Containers.run_ready_actions(deployment)
end
end)
else
changeset
end
Expand Down
4 changes: 4 additions & 0 deletions backend/lib/edgehog/containers/deployment_ready_action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ defmodule Edgehog.Containers.DeploymentReadyAction do
change manage_relationship(:deployment, on_no_match: {:create, :deploy}, on_match: :ignore)
change ManualActions.DeploymentReadyActionAddRelationship
end

update :run do
manual ManualActions.RunReadyAction
end
end

attributes do
Expand Down
59 changes: 59 additions & 0 deletions backend/lib/edgehog/containers/manual_actions/run_ready_action.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#
# This file is part of Edgehog.
#
# Copyright 2024 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.ManualActions.RunReadyAction do
@moduledoc false

use Ash.Resource.ManualUpdate

alias Edgehog.Devices

require Ash.Query

@impl Ash.Resource.ManualUpdate
def update(changeset, _opts, _context) do
ready_action = changeset.data
action_type = ready_action.action_type

extra_loads =
case action_type do
:upgrade_deployment -> [upgrade_deployment: [:upgrade_target]]
end

# We always load the device
loads = extra_loads ++ [deployment: [:device]]

with {:ok, ready_action} <- Ash.load(ready_action, loads),
:ok <- run_ready_action(ready_action, action_type) do
{:ok, ready_action}
end
end

defp run_ready_action(action, :upgrade_deployment) do
with {:ok, _device} <-
Devices.update_application(
action.deployment.device,
action.upgrade_deployment.upgrade_target,
action.deployment
) do
:ok
end
end
end
45 changes: 45 additions & 0 deletions backend/lib/edgehog/containers/manual_actions/run_ready_actions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# This file is part of Edgehog.
#
# Copyright 2024 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.ManualActions.RunReadyActions do
@moduledoc false

use Ash.Resource.ManualUpdate

alias Edgehog.Containers

require Ash.Query
require Logger

@impl Ash.Resource.ManualUpdate
def update(changeset, _opts, _context) do
deployment = changeset.data

with {:ok, deployment} <- Ash.load(deployment, :ready_actions) do
ready_actions_results = Enum.map(deployment.ready_actions, &Containers.run_ready_action/1)

for {status, result} <- ready_actions_results, status == :error do
Logger.error("Error running ready action for deployment #{deployment.id}: #{inspect(result)}")
end

{:ok, deployment}
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,18 @@ defmodule EdgehogWeb.Schema.Mutation.SendDeploymentUpgradeTest do

import Edgehog.ContainersFixtures

alias Edgehog.Astarte.Device.AvailableContainers.ContainerStatus
alias Edgehog.Astarte.Device.AvailableContainersMock
alias Edgehog.Astarte.Device.AvailableDeployments.DeploymentStatus
alias Edgehog.Astarte.Device.AvailableDeploymentsMock
alias Edgehog.Astarte.Device.AvailableImages.ImageStatus
alias Edgehog.Astarte.Device.AvailableImagesMock
alias Edgehog.Astarte.Device.AvailableNetworks.NetworkStatus
alias Edgehog.Astarte.Device.AvailableNetworksMock
alias Edgehog.Astarte.Device.CreateDeploymentRequestMock
alias Edgehog.Astarte.Device.DeploymentUpdateMock
alias Edgehog.Containers
alias Edgehog.Containers.Deployment

describe "sendDeploymentUpgrade" do
setup %{tenant: tenant} do
Expand All @@ -48,7 +59,7 @@ defmodule EdgehogWeb.Schema.Mutation.SendDeploymentUpgradeTest do
}
end

test "correctly sends the deployment upgrade with valid data", args do
test "correctly sends the deployment request with valid data", args do
%{deployment_0_0_1: deployment_0_0_1, release_0_0_2: release_0_0_2, tenant: tenant} =
args

Expand All @@ -66,6 +77,26 @@ defmodule EdgehogWeb.Schema.Mutation.SendDeploymentUpgradeTest do
|> Map.fetch!(:deployment_id) == deployment_id
end

test "sends the deployment upgrade once the new deployment reaches :ready state", args do
%{deployment_0_0_1: deployment_0_0_1, release_0_0_2: release_0_0_2, tenant: tenant} =
args

expect(CreateDeploymentRequestMock, :send_create_deployment_request, fn _, _, _ -> :ok end)
expect(DeploymentUpdateMock, :update, fn _, _, _ -> :ok end)

result =
[tenant: tenant, deployment: deployment_0_0_1, target: release_0_0_2]
|> send_deployment_upgrade_mutation()
|> extract_result!()

{:ok, %{id: deployment_id}} = AshGraphql.Resource.decode_relay_id(result["id"])

deployment = Ash.get!(Deployment, deployment_id, tenant: tenant)
set_resource_expectations([deployment_0_0_1, deployment])

Containers.deployment_update_status!(deployment)
end

test "fails if the deployments do not belong to the same application", args do
%{deployment_0_0_1: deployment_0_0_1, release_0_0_2: release_0_0_2, tenant: tenant} =
args
Expand Down Expand Up @@ -142,4 +173,37 @@ defmodule EdgehogWeb.Schema.Mutation.SendDeploymentUpgradeTest do

error
end

defp set_resource_expectations(deployments) do
deployments =
Enum.map(deployments, &Ash.load!(&1, release: [containers: [:image, :networks]]))

containers =
deployments
|> Enum.map(& &1.release)
|> Enum.flat_map(& &1.containers)
|> Enum.uniq_by(& &1.id)

available_containers = Enum.map(containers, &%ContainerStatus{id: &1.id, status: "Created"})

available_images =
containers
|> Enum.flat_map(& &1.image)
|> Enum.map(&%ImageStatus{id: &1.id, pulled: false})
|> Enum.uniq()

available_networks =
containers
|> Enum.flat_map(& &1.network)
|> Enum.map(&%NetworkStatus{id: &1.id, created: false})
|> Enum.uniq()

available_deployments =
Enum.map(deployments, &%DeploymentStatus{id: &1.id, status: :stopped})

expect(AvailableImagesMock, :get, fn _, _ -> {:ok, available_images} end)
expect(AvailableNetworksMock, :get, fn _, _ -> {:ok, available_networks} end)
expect(AvailableContainersMock, :get, fn _, _ -> {:ok, available_containers} end)
expect(AvailableDeploymentsMock, :get, fn _, _ -> {:ok, available_deployments} end)
end
end