Skip to content

Commit

Permalink
feat(containers): application upgrade interface
Browse files Browse the repository at this point in the history
expose a way to upgrade an application to a newer release .

- does not allow downgrading
- the releases must be of the same application

Signed-off-by: Francesco Noacco <[email protected]>
  • Loading branch information
noaccOS committed Nov 8, 2024
1 parent e0de00a commit 38ac6ea
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 0 deletions.
1 change: 1 addition & 0 deletions backend/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ config :edgehog,
Edgehog.Astarte.Device.CreateNetworkRequestMock

config :edgehog, :astarte_deployment_command_module, Edgehog.Astarte.Device.DeploymentCommandMock
config :edgehog, :astarte_deployment_update_module, Edgehog.Astarte.Device.DeploymentUpdateMock
config :edgehog, :astarte_device_status_module, Edgehog.Astarte.Device.DeviceStatusMock
config :edgehog, :astarte_forwarder_session_module, Edgehog.Astarte.Device.ForwarderSessionMock
config :edgehog, :astarte_geolocation_module, Edgehog.Astarte.Device.GeolocationMock
Expand Down
42 changes: 42 additions & 0 deletions backend/lib/edgehog/astarte/device/deployment_update.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#
# 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.Astarte.Device.DeploymentUpdate do
@moduledoc false

@behaviour Edgehog.Astarte.Device.DeploymentUpdate.Behaviour

alias Astarte.Client.AppEngine
alias Edgehog.Error.AstarteAPIError

@interface "io.edgehog.devicemanager.apps.DeploymentUpdate"

@impl Edgehog.Astarte.Device.DeploymentUpdate.Behaviour
def update(client, device_id, data) do
api_call =
AppEngine.Devices.send_datastream(client, device_id, @interface, "/deployment", data)

with {:error, api_error} <- api_call do
reason = AstarteAPIError.exception(status: api_error.status, response: api_error.response)

{:error, reason}
end
end
end
32 changes: 32 additions & 0 deletions backend/lib/edgehog/astarte/device/deployment_update/behaviour.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# 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.Astarte.Device.DeploymentUpdate.Behaviour do
@moduledoc false

alias Astarte.Client.AppEngine
alias Edgehog.Astarte.Device.DeploymentUpdate.RequestData

@callback update(
client :: AppEngine.t(),
device_id :: String.t(),
data :: RequestData.t()
) :: :ok | {:error, term()}
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# 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.Astarte.Device.DeploymentUpdate.RequestData do
@moduledoc false

defstruct [:from, :to]

@type t :: %__MODULE__{
from: String.t(),
to: String.t()
}
end
46 changes: 46 additions & 0 deletions backend/lib/edgehog/containers/validations/is_upgrade.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#
# 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.Validations.IsUpgrade do
@moduledoc false

use Ash.Resource.Validation

@impl Ash.Resource.Validation
def validate(changeset, opts, _context) do
from = Ash.Changeset.get_argument(changeset, opts[:from])
to = Ash.Changeset.get_argument(changeset, opts[:to])

with {:ok, from_version} <- parse_version(from),
{:ok, to_version} <- parse_version(to) do
if Version.compare(to_version, from_version) == :gt do
:ok
else
{:error, field: opts[:to], message: "must be a newer release than from"}
end
end
end

defp parse_version(release) do
with :error <- release.version |> get_in() |> to_string() |> Version.parse() do
{:error, :invalid_release}
end
end
end
37 changes: 37 additions & 0 deletions backend/lib/edgehog/containers/validations/same_application.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#
# 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.Validations.SameApplication do
@moduledoc false

use Ash.Resource.Validation

@impl Ash.Resource.Validation
def validate(changeset, opts, _context) do
with {:ok, release_a} <- Ash.Changeset.fetch_argument(changeset, opts[:release_a]),
{:ok, release_b} <- Ash.Changeset.fetch_argument(changeset, opts[:release_b]) do
if release_a.application_id == release_b.application_id do
:ok
else
{:error, field: opts[:release_b], message: "must belong to the same application as from"}
end
end
end
end
26 changes: 26 additions & 0 deletions backend/lib/edgehog/devices/device/device.ex
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,32 @@ defmodule Edgehog.Devices.Device do

manual Edgehog.Devices.Device.ManualActions.SendApplicationCommand
end

update :update_application do
description "Updates an application to a newer release."

argument :from, :struct do
constraints instance_of: Release
allow_nil? false

description """
The release to be upgraded. Should be currently installed.
This argument is needed because there might be multiple versions of a single application installed on the device.
"""
end

argument :to, :struct do
constraints instance_of: Release
allow_nil? false
description "The new release of the application"
end

validate {Edgehog.Containers.Validations.SameApplication, [release_a: :from, release_b: :to]}

validate {Edgehog.Containers.Validations.IsUpgrade, [from: :from, to: :to]}

manual Edgehog.Devices.Device.ManualActions.UpdateApplication
end
end

attributes do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#
# 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.Devices.Device.ManualActions.UpdateApplication do
@moduledoc false
use Ash.Resource.ManualUpdate

alias Edgehog.Astarte.Device.DeploymentUpdate.RequestData
alias Edgehog.Containers.Deployment

@deployment_update Application.compile_env(
:edgehog,
:astarte_deployment_update_module,
Edgehog.Astarte.Device.DeploymentUpdate
)

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

with {:ok, from} <- Ash.Changeset.fetch_argument(changeset, :from),
{:ok, to} <- Ash.Changeset.fetch_argument(changeset, :to),
{:ok, from} <- fetch_deployment(device, from),
{:ok, to} <- fetch_deployment(device, to),
{:ok, device} <- Ash.load(device, :appengine_client),
data = %RequestData{from: from.id, to: to.id},
:ok <- @deployment_update.update(device.appengine_client, device.device_id, data) do
{:ok, device}
end
end

defp fetch_deployment(device, release) do
Ash.get(Deployment, %{device_id: device.id, release_id: release.id}, tenant: device.tenant_id)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"interface_name": "io.edgehog.devicemanager.apps.DeploymentUpdate",
"version_major": 0,
"version_minor": 1,
"type": "datastream",
"ownership": "server",
"aggregation": "object",
"mappings": [
{
"endpoint": "/deployment/from",
"type": "string",
"database_retention_policy": "use_ttl",
"database_retention_ttl": 31556952,
"description": "Id of the deployment to update from"
},
{
"endpoint": "/deployment/to",
"type": "string",
"database_retention_policy": "use_ttl",
"database_retention_ttl": 31556952,
"description": "Id of the deployment to update to"
}
]
}
5 changes: 5 additions & 0 deletions backend/test/support/astarte_mock_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ defmodule Edgehog.AstarteMockCase do
Edgehog.Astarte.Device.DeploymentCommand
)

Mox.stub_with(
Edgehog.Astarte.Device.DeploymentUpdateMock,
Edgehog.Astarte.Device.DeploymentUpdate
)

Mox.stub_with(
Edgehog.Astarte.Device.BaseImageMock,
Edgehog.Mocks.Astarte.Device.BaseImage
Expand Down
4 changes: 4 additions & 0 deletions backend/test/support/mocks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ Mox.defmock(Edgehog.Astarte.Device.DeploymentCommandMock,
for: Edgehog.Astarte.Device.DeploymentCommand.Behaviour
)

Mox.defmock(Edgehog.Astarte.Device.DeploymentUpdateMock,
for: Edgehog.Astarte.Device.DeploymentUpdate.Behaviour
)

Mox.defmock(Edgehog.Astarte.Device.AvailableContainersMock,
for: Edgehog.Astarte.Device.AvailableContainers.Behaviour
)
Expand Down

0 comments on commit 38ac6ea

Please sign in to comment.