diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs index 11bb9da67..ebcccd6aa 100644 --- a/.dialyzer_ignore.exs +++ b/.dialyzer_ignore.exs @@ -1,7 +1,7 @@ [ {"lib/x509/certificate.ex", "Unknown type: X509.ASN1.record/1."}, {"lib/x509/certificate/extension.ex", "Unknown type: X509.ASN1.record/1."}, - {"lib/nerves_hub/deployments/orchestrator.ex", :unmatched_return, 1}, + {"lib/nerves_hub/managed_deployments/orchestrator.ex", :unmatched_return, 1}, {"lib/nerves_hub_web/channels/device_channel.ex", :unmatched_return, 1}, {"lib/nerves_hub_web/channels/device_socket.ex", :unmatched_return, 1} ] diff --git a/assets/css/_custom.scss b/assets/css/_custom.scss index 1352e5515..a76c3a3b5 100644 --- a/assets/css/_custom.scss +++ b/assets/css/_custom.scss @@ -219,7 +219,7 @@ line-height: 44px; } -.deployment-meta-grid { +.deployment-group-meta-grid { display: grid; grid-template-columns: max-content max-content max-content max-content 1fr; grid-column-gap: 5.5rem; @@ -397,7 +397,7 @@ html { } @media (max-width: 1000px) and (min-width: 480px) { - .collapse.deployment-form.show { + .collapse.deployment-group-form.show { display: grid !important; } } diff --git a/assets/css/_layout.scss b/assets/css/_layout.scss index e38061359..b75dcfef8 100644 --- a/assets/css/_layout.scss +++ b/assets/css/_layout.scss @@ -396,7 +396,7 @@ html { grid-column-gap: 2rem; grid-row-gap: 2rem; - &.deployment-form { + &.deployment-group-form { grid-template-columns: 360px 360px; grid-row-gap: 0; grid-column-gap: 3rem; diff --git a/assets/css/_table.scss b/assets/css/_table.scss index fdd848c71..1dd607145 100644 --- a/assets/css/_table.scss +++ b/assets/css/_table.scss @@ -111,7 +111,7 @@ html { gap: 6px; } - .deployment-state { + .deployment-group-state { background-size: 1.5rem; background-repeat: no-repeat; background-position: left center; @@ -128,10 +128,10 @@ html { } .certificate-status-expired { - color: var(--danger);; + color: var(--danger); } .certificate-status-expiring-soon { color: var(--warning); } -} +} \ No newline at end of file diff --git a/lib/nerves_hub/accounts/remove_account.ex b/lib/nerves_hub/accounts/remove_account.ex index 7f8fca296..22884c580 100644 --- a/lib/nerves_hub/accounts/remove_account.ex +++ b/lib/nerves_hub/accounts/remove_account.ex @@ -11,7 +11,6 @@ defmodule NervesHub.Accounts.RemoveAccount do alias NervesHub.Accounts.OrgMetric alias NervesHub.Accounts.OrgUser alias NervesHub.Accounts.User - alias NervesHub.Deployments.Deployment alias NervesHub.Devices.CACertificate alias NervesHub.Devices.Device alias NervesHub.Devices.DeviceCertificate @@ -19,6 +18,7 @@ defmodule NervesHub.Accounts.RemoveAccount do alias NervesHub.Firmwares.Firmware alias NervesHub.Firmwares.FirmwareDelta alias NervesHub.Firmwares.FirmwareTransfer + alias NervesHub.ManagedDeployments.DeploymentGroup alias NervesHub.Products.Product alias NervesHub.Scripts.Script @@ -32,7 +32,7 @@ defmodule NervesHub.Accounts.RemoveAccount do |> Multi.delete_all(:invites, &query_by_org_id(Invite, &1)) |> Multi.delete_all(:device_certificates, &query_by_org_id(DeviceCertificate, &1)) |> Multi.delete_all(:ca_certificates, &query_by_org_id(CACertificate, &1)) - |> Multi.delete_all(:deployments, &query_by_org_id(Deployment, &1)) + |> Multi.delete_all(:deployment_groups, &query_by_org_id(DeploymentGroup, &1)) |> Multi.delete_all(:firmware_deltas, &query_firmware_deltas/1) |> Multi.delete_all(:firmware_transfers, &query_by_org_id(FirmwareTransfer, &1)) |> Multi.delete_all(:pinned_devices, &query_by_user_id(PinnedDevice, &1)) diff --git a/lib/nerves_hub/application.ex b/lib/nerves_hub/application.ex index f71feb408..c77d1f11d 100644 --- a/lib/nerves_hub/application.ex +++ b/lib/nerves_hub/application.ex @@ -81,13 +81,13 @@ defmodule NervesHub.Application do defp deployments_orchestrator(_) do case Application.get_env(:nerves_hub, :app) do ["device"] -> - [NervesHub.Deployments.Supervisor] + [NervesHub.ManagedDeployments.Supervisor] _ -> [ - NervesHub.Deployments.Supervisor, + NervesHub.ManagedDeployments.Supervisor, ProcessHub.child_spec(%ProcessHub{hub_id: :deployment_orchestrators}), - NervesHub.Deployments.Distributed.OrchestratorRegistration + NervesHub.ManagedDeployments.Distributed.OrchestratorRegistration ] end end diff --git a/lib/nerves_hub/archives.ex b/lib/nerves_hub/archives.ex index e2ddcb4ca..d394ca9a0 100644 --- a/lib/nerves_hub/archives.ex +++ b/lib/nerves_hub/archives.ex @@ -8,8 +8,8 @@ defmodule NervesHub.Archives do require Logger alias NervesHub.Archives.Archive - alias NervesHub.Deployments.Deployment alias NervesHub.Fwup + alias NervesHub.ManagedDeployments.DeploymentGroup alias NervesHub.Products.Product alias NervesHub.Repo alias NervesHub.Workers.DeleteArchive @@ -67,12 +67,12 @@ defmodule NervesHub.Archives do |> Repo.one!() end - @spec archive_for_deployment(integer()) :: Archive.t() | nil - def archive_for_deployment(nil), do: nil + @spec archive_for_deployment_group(integer()) :: Archive.t() | nil + def archive_for_deployment_group(nil), do: nil - def archive_for_deployment(deployment_id) do + def archive_for_deployment_group(deployment_id) do Archive - |> join(:inner, [a], d in Deployment, on: d.archive_id == a.id) + |> join(:inner, [a], d in DeploymentGroup, on: d.archive_id == a.id) |> where([a, d], d.id == ^deployment_id) |> Repo.one() end diff --git a/lib/nerves_hub/audit_logs.ex b/lib/nerves_hub/audit_logs.ex index 0d0e06ad6..b32b70351 100644 --- a/lib/nerves_hub/audit_logs.ex +++ b/lib/nerves_hub/audit_logs.ex @@ -2,7 +2,7 @@ defmodule NervesHub.AuditLogs do import Ecto.Query alias NervesHub.AuditLogs.AuditLog - alias NervesHub.Deployments.Deployment + alias NervesHub.ManagedDeployments.DeploymentGroup alias NervesHub.Repo alias NimbleCSV.RFC4180, as: CSV @@ -60,8 +60,8 @@ defmodule NervesHub.AuditLogs do |> Flop.run(flop) end - defp query_for_feed(%Deployment{id: id}) do - resource_type = to_string(Deployment) + defp query_for_feed(%DeploymentGroup{id: id}) do + resource_type = to_string(DeploymentGroup) from(al in AuditLog, where: [resource_type: ^resource_type, resource_id: ^id]) |> order_by(desc: :inserted_at) diff --git a/lib/nerves_hub/audit_logs/templates/deployment_group_templates.ex b/lib/nerves_hub/audit_logs/templates/deployment_group_templates.ex new file mode 100644 index 000000000..704720632 --- /dev/null +++ b/lib/nerves_hub/audit_logs/templates/deployment_group_templates.ex @@ -0,0 +1,47 @@ +defmodule NervesHub.AuditLogs.DeploymentGroupTemplates do + @moduledoc """ + Templates for and handling of audit logging for deployment operations. + """ + alias NervesHub.Accounts.User + alias NervesHub.AuditLogs + alias NervesHub.Devices.Device + alias NervesHub.ManagedDeployments.DeploymentGroup + + @spec audit_deployment_created(User.t(), DeploymentGroup.t()) :: :ok + def audit_deployment_created(user, deployment_group) do + description = "User #{user.name} created deployment group #{deployment_group.name}" + AuditLogs.audit!(user, deployment_group, description) + end + + @spec audit_deployment_updated(User.t(), DeploymentGroup.t()) :: :ok + def audit_deployment_updated(user, deployment_group) do + description = "User #{user.name} updated deployment group #{deployment_group.name}" + AuditLogs.audit!(user, deployment_group, description) + end + + @spec audit_deployment_deleted(User.t(), DeploymentGroup.t()) :: :ok + def audit_deployment_deleted(user, deployment_group) do + description = "User #{user.name} deleted deployment group #{deployment_group.name}" + AuditLogs.audit!(user, deployment_group, description) + end + + @spec audit_deployment_toggle_active(User.t(), DeploymentGroup.t(), String.t()) :: :ok + def audit_deployment_toggle_active(user, deployment_group, status) do + description = "User #{user.name} marked deployment group #{deployment_group.name} #{status}" + AuditLogs.audit!(user, deployment_group, description) + end + + @spec audit_deployment_mismatch(Device.t(), DeploymentGroup.t(), String.t()) :: :ok + def audit_deployment_mismatch(device, deployment_group, reason) do + description = + "Device no longer matches deployment group #{deployment_group.name}'s requirements because of #{reason}" + + AuditLogs.audit!(device, deployment_group, description) + end + + @spec audit_deployment_group_change(DeploymentGroup.t(), String.t()) :: :ok + def audit_deployment_group_change(deployment_group, change_string) do + description = "Deployment group #{deployment_group.name} #{change_string}" + AuditLogs.audit!(deployment_group, deployment_group, description) + end +end diff --git a/lib/nerves_hub/audit_logs/templates/deployment_templates.ex b/lib/nerves_hub/audit_logs/templates/deployment_templates.ex deleted file mode 100644 index af27fce47..000000000 --- a/lib/nerves_hub/audit_logs/templates/deployment_templates.ex +++ /dev/null @@ -1,47 +0,0 @@ -defmodule NervesHub.AuditLogs.DeploymentTemplates do - @moduledoc """ - Templates for and handling of audit logging for deployment operations. - """ - alias NervesHub.Accounts.User - alias NervesHub.AuditLogs - alias NervesHub.Deployments.Deployment - alias NervesHub.Devices.Device - - @spec audit_deployment_created(User.t(), Deployment.t()) :: :ok - def audit_deployment_created(user, deployment) do - description = "User #{user.name} created deployment #{deployment.name}" - AuditLogs.audit!(user, deployment, description) - end - - @spec audit_deployment_updated(User.t(), Deployment.t()) :: :ok - def audit_deployment_updated(user, deployment) do - description = "User #{user.name} updated deployment #{deployment.name}" - AuditLogs.audit!(user, deployment, description) - end - - @spec audit_deployment_deleted(User.t(), Deployment.t()) :: :ok - def audit_deployment_deleted(user, deployment) do - description = "User #{user.name} deleted deployment #{deployment.name}" - AuditLogs.audit!(user, deployment, description) - end - - @spec audit_deployment_toggle_active(User.t(), Deployment.t(), String.t()) :: :ok - def audit_deployment_toggle_active(user, deployment, status) do - description = "User #{user.name} marked deployment #{deployment.name} #{status}" - AuditLogs.audit!(user, deployment, description) - end - - @spec audit_deployment_mismatch(Device.t(), Deployment.t(), String.t()) :: :ok - def audit_deployment_mismatch(device, deployment, reason) do - description = - "Device no longer matches deployment #{deployment.name}'s requirements because of #{reason}" - - AuditLogs.audit!(device, deployment, description) - end - - @spec audit_deployment_change(Deployment.t(), String.t()) :: :ok - def audit_deployment_change(deployment, change_string) do - description = "Deployment #{deployment.name} #{change_string}" - AuditLogs.audit!(deployment, deployment, description) - end -end diff --git a/lib/nerves_hub/audit_logs/templates/device_templates.ex b/lib/nerves_hub/audit_logs/templates/device_templates.ex index bf0e13709..5a8da5cbf 100644 --- a/lib/nerves_hub/audit_logs/templates/device_templates.ex +++ b/lib/nerves_hub/audit_logs/templates/device_templates.ex @@ -6,9 +6,9 @@ defmodule NervesHub.AuditLogs.DeviceTemplates do alias NervesHub.Accounts.User alias NervesHub.Archives.Archive alias NervesHub.AuditLogs - alias NervesHub.Deployments.Deployment alias NervesHub.Devices.Device alias NervesHub.Firmwares.Firmware + alias NervesHub.ManagedDeployments.DeploymentGroup require Logger @@ -34,10 +34,10 @@ defmodule NervesHub.AuditLogs.DeviceTemplates do AuditLogs.audit(device, device, description) end - @spec audit_pushed_available_update(User.t(), Device.t(), Deployment.t()) :: :ok - def audit_pushed_available_update(user, device, deployment) do + @spec audit_pushed_available_update(User.t(), Device.t(), DeploymentGroup.t()) :: :ok + def audit_pushed_available_update(user, device, deployment_group) do description = - "User #{user.name} pushed available firmware update #{deployment.firmware.version} #{deployment.firmware.uuid} to device #{device.identifier}" + "User #{user.name} pushed available firmware update #{deployment_group.firmware.version} #{deployment_group.firmware.uuid} to device #{device.identifier}" AuditLogs.audit!(user, device, description) end @@ -56,14 +56,14 @@ defmodule NervesHub.AuditLogs.DeviceTemplates do AuditLogs.audit!(device, device, description) end - @spec audit_firmware_upgrade_blocked(Deployment.t(), Device.t()) :: :ok - def audit_firmware_upgrade_blocked(deployment, device) do + @spec audit_firmware_upgrade_blocked(DeploymentGroup.t(), Device.t()) :: :ok + def audit_firmware_upgrade_blocked(deployment_group, device) do description = """ - Device #{device.identifier} automatically blocked firmware upgrades for #{deployment.penalty_timeout_minutes} minutes. - Device failure rate met for firmware #{deployment.firmware.uuid} in deployment #{deployment.name}. + Device #{device.identifier} automatically blocked firmware upgrades for #{deployment_group.penalty_timeout_minutes} minutes. + Device failure rate met for firmware #{deployment_group.firmware.uuid} in deployment group #{deployment_group.name}. """ - AuditLogs.audit!(deployment, device, description) + AuditLogs.audit!(deployment_group, device, description) end @spec audit_firmware_updated(Device.t()) :: :ok @@ -74,39 +74,43 @@ defmodule NervesHub.AuditLogs.DeviceTemplates do AuditLogs.audit!(device, device, description) end - @spec audit_device_deployment_update_triggered(Device.t(), Deployment.t(), UUIDv7.t()) :: :ok - def audit_device_deployment_update_triggered(device, deployment, reference_id) do - firmware = deployment.firmware + @spec audit_device_deployment_group_update_triggered( + Device.t(), + DeploymentGroup.t(), + UUIDv7.t() + ) :: :ok + def audit_device_deployment_group_update_triggered(device, deployment_group, reference_id) do + firmware = deployment_group.firmware description = - "Deployment #{deployment.name} update triggered device #{device.identifier} to update firmware #{firmware.uuid}" + "Deployment #{deployment_group.name} update triggered device #{device.identifier} to update firmware #{firmware.uuid}" - AuditLogs.audit_with_ref!(deployment, device, description, reference_id) + AuditLogs.audit_with_ref!(deployment_group, device, description, reference_id) end - @spec audit_device_deployment_update(User.t(), Device.t(), Deployment.t()) :: :ok - def audit_device_deployment_update(user, device, deployment) do + @spec audit_device_deployment_group_update(User.t(), Device.t(), DeploymentGroup.t()) :: :ok + def audit_device_deployment_group_update(user, device, deployment_group) do AuditLogs.audit!( user, device, - "User #{user.name} set #{device.identifier}'s deployment to #{deployment.name}" + "User #{user.name} set #{device.identifier}'s deployment group to #{deployment_group.name}" ) end - @spec audit_set_deployment(Device.t(), Deployment.t(), :one_found | :multiple_found) :: :ok - def audit_set_deployment(device, deployment, :one_found) do + @spec audit_set_deployment(Device.t(), DeploymentGroup.t(), :one_found | :multiple_found) :: :ok + def audit_set_deployment(device, deployment_group, :one_found) do AuditLogs.audit!( device, device, - "Updating #{device.identifier}'s deployment to #{deployment.name}" + "Updating #{device.identifier}'s deployment group to #{deployment_group.name}" ) end - def audit_set_deployment(device, deployment, :multiple_found) do + def audit_set_deployment(device, deployment_group, :multiple_found) do AuditLogs.audit!( device, device, - "Multiple matching deployments found, updating #{device.identifier}'s deployment to #{deployment.name}" + "Multiple matching deployments found, updating #{device.identifier}'s deployment group to #{deployment_group.name}" ) end diff --git a/lib/nerves_hub/deployments/inflight_deployment_check.ex b/lib/nerves_hub/deployments/inflight_deployment_check.ex deleted file mode 100644 index 340402405..000000000 --- a/lib/nerves_hub/deployments/inflight_deployment_check.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule NervesHub.Deployments.InflightDeploymentCheck do - use Ecto.Schema - - @timestamps_opts updated_at: false - - alias NervesHub.Deployments.Deployment - alias NervesHub.Devices.Device - - schema "inflight_deployment_checks" do - belongs_to(:device, Device) - belongs_to(:deployment, Deployment) - - timestamps() - end -end diff --git a/lib/nerves_hub/deployments/monitor.ex b/lib/nerves_hub/deployments/monitor.ex deleted file mode 100644 index 116702ce4..000000000 --- a/lib/nerves_hub/deployments/monitor.ex +++ /dev/null @@ -1,66 +0,0 @@ -defmodule NervesHub.Deployments.Monitor do - @moduledoc """ - Deployment Monitor starts a deployment orchestrator per deployment - - Listens for new deployments and starts as necessary - """ - - use GenServer - - alias NervesHub.DeploymentDynamicSupervisor - alias NervesHub.Deployments - alias NervesHub.Deployments.Orchestrator - alias Phoenix.PubSub - alias Phoenix.Socket.Broadcast - - defmodule State do - defstruct [:deployments] - end - - def start_link(_) do - GenServer.start_link(__MODULE__, []) - end - - def init(_) do - _ = PubSub.subscribe(NervesHub.PubSub, "deployment:monitor") - - {:ok, %State{}, {:continue, :boot}} - end - - def handle_continue(:boot, state) do - deployments = - Enum.into(Deployments.all(), %{}, fn deployment -> - {:ok, orchestrator_pid} = - DynamicSupervisor.start_child( - DeploymentDynamicSupervisor, - {Deployments.Orchestrator, deployment} - ) - - {deployment.id, %{orchestrator_pid: orchestrator_pid}} - end) - - {:noreply, %{state | deployments: deployments}} - end - - def handle_info(%Broadcast{event: "deployments/new", payload: payload}, state) do - {:ok, deployment} = Deployments.get(payload.deployment_id) - - {:ok, orchestrator_pid} = - DynamicSupervisor.start_child( - DeploymentDynamicSupervisor, - {Deployments.Orchestrator, deployment} - ) - - deployments = - Map.put(state.deployments, deployment.id, %{orchestrator_pid: orchestrator_pid}) - - {:noreply, %{state | deployments: deployments}} - end - - def handle_info(%Broadcast{event: "deployments/delete", payload: payload}, state) do - pid = GenServer.whereis(Orchestrator.name(payload.deployment_id)) - _ = DynamicSupervisor.terminate_child(DeploymentDynamicSupervisor, pid) - deployments = Map.delete(state.deployments, payload.deployment_id) - {:noreply, %{state | deployments: deployments}} - end -end diff --git a/lib/nerves_hub/deployments/supervisor.ex b/lib/nerves_hub/deployments/supervisor.ex deleted file mode 100644 index 1121b6b9e..000000000 --- a/lib/nerves_hub/deployments/supervisor.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule NervesHub.Deployments.Supervisor do - @moduledoc false - - use Supervisor - - def start_link(_) do - Supervisor.start_link(__MODULE__, [], name: __MODULE__) - end - - def init(_) do - children = [ - {Registry, keys: :unique, name: NervesHub.Deployments}, - NervesHub.Deployments.Monitor, - {DynamicSupervisor, strategy: :one_for_one, name: NervesHub.DeploymentDynamicSupervisor} - ] - - Supervisor.init(children, strategy: :one_for_one) - end -end diff --git a/lib/nerves_hub/devices.ex b/lib/nerves_hub/devices.ex index 026c2c34f..e497b8c59 100644 --- a/lib/nerves_hub/devices.ex +++ b/lib/nerves_hub/devices.ex @@ -13,9 +13,6 @@ defmodule NervesHub.Devices do alias NervesHub.AuditLogs alias NervesHub.AuditLogs.DeviceTemplates alias NervesHub.Certificate - alias NervesHub.Deployments - alias NervesHub.Deployments.Deployment - alias NervesHub.Deployments.Orchestrator alias NervesHub.Devices.CACertificate alias NervesHub.Devices.Device alias NervesHub.Devices.DeviceCertificate @@ -30,6 +27,9 @@ defmodule NervesHub.Devices do alias NervesHub.Firmwares alias NervesHub.Firmwares.Firmware alias NervesHub.Firmwares.FirmwareMetadata + alias NervesHub.ManagedDeployments + alias NervesHub.ManagedDeployments.DeploymentGroup + alias NervesHub.ManagedDeployments.Orchestrator alias NervesHub.Products alias NervesHub.Products.Product alias NervesHub.Repo @@ -78,17 +78,17 @@ defmodule NervesHub.Devices do |> where([d], d.product_id == ^product_id) |> join(:left, [d], o in assoc(d, :org)) |> join(:left, [d, o], p in assoc(d, :product)) - |> join(:left, [d, o, p], dp in assoc(d, :deployment)) - |> join(:left, [d, o, p, dp], f in assoc(dp, :firmware)) - |> join(:left, [d, o, p, dp, f], lc in assoc(d, :latest_connection), as: :latest_connection) - |> join(:left, [d, o, p, dp, f, lc], lh in assoc(d, :latest_health), as: :latest_health) + |> join(:left, [d, o, p], dg in assoc(d, :deployment_group)) + |> join(:left, [d, o, p, dg], f in assoc(dg, :firmware)) + |> join(:left, [d, o, p, dg, f], lc in assoc(d, :latest_connection), as: :latest_connection) + |> join(:left, [d, o, p, dg, f, lc], lh in assoc(d, :latest_health), as: :latest_health) |> Repo.exclude_deleted() |> sort_devices(sorting) |> Filtering.build_filters(filters) - |> preload([d, o, p, dp, f, latest_connection: lc, latest_health: lh], + |> preload([d, o, p, dg, f, latest_connection: lc, latest_health: lh], org: o, product: p, - deployment: {dp, firmware: f}, + deployment_group: {dg, firmware: f}, latest_connection: lc, latest_health: lh ) @@ -235,7 +235,7 @@ defmodule NervesHub.Devices do {:error, :not_found} device -> - {:ok, Repo.preload(device, [:org, :product, deployment: [:firmware]])} + {:ok, Repo.preload(device, [:org, :product, deployment_group: [:firmware]])} end end @@ -262,9 +262,9 @@ defmodule NervesHub.Devices do |> where(identifier: ^identifier) |> where(org_id: ^org_id) |> join(:left, [d], o in assoc(d, :org)) - |> join(:left, [d], dp in assoc(d, :deployment)) + |> join(:left, [d], dp in assoc(d, :deployment_group)) |> join_and_preload(preload_assoc) - |> preload([d, o, dp], org: o, deployment: dp) + |> preload([d, o, dp], org: o, deployment_group: dp) end defp join_and_preload(query, assocs) when is_list(assocs) do @@ -360,28 +360,6 @@ defmodule NervesHub.Devices do end end - def get_eligible_deployments(%Device{firmware_metadata: nil}), do: [] - - def get_eligible_deployments(%Device{firmware_metadata: meta} = device) do - from( - d in Deployment, - join: p in assoc(d, :product), - on: [org_id: ^device.org_id, name: ^meta.product], - join: f in assoc(d, :firmware), - on: f.product_id == p.id, - where: d.is_active, - where: d.healthy, - where: f.architecture == ^meta.architecture, - where: f.platform == ^meta.platform, - where: f.uuid != ^meta.uuid, - preload: [firmware: f] - ) - |> Repo.all() - |> Enum.filter(fn dep -> matches_deployment?(device, dep) end) - end - - def get_eligible_deployments(_), do: [] - @spec create_device(map) :: {:ok, Device.t()} | {:error, Changeset.t()} def create_device(params) do %Device{} @@ -653,7 +631,7 @@ defmodule NervesHub.Devices do @spec get_device_firmware_for_delta_generation_by_product(binary()) :: list({source_firmware_id(), target_firmware_id()}) def get_device_firmware_for_delta_generation_by_product(product_id) do - Deployment + DeploymentGroup |> where([dep], dep.product_id == ^product_id) |> join(:inner, [dep], dev in Device, on: dev.deployment_id == dep.id) |> join(:inner, [dep, dev], f in Firmware, @@ -665,10 +643,10 @@ defmodule NervesHub.Devices do |> Repo.all() end - @spec get_device_firmware_for_delta_generation_by_deployment(binary()) :: + @spec get_device_firmware_for_delta_generation_by_deployment_group(binary()) :: list({source_firmware_id(), target_firmware_id()}) - def get_device_firmware_for_delta_generation_by_deployment(deployment_id) do - Deployment + def get_device_firmware_for_delta_generation_by_deployment_group(deployment_id) do + DeploymentGroup |> where([dep], dep.id == ^deployment_id) |> join(:inner, [dep], dev in Device, on: dev.deployment_id == dep.id) |> join(:inner, [dep, dev], f in Firmware, @@ -716,19 +694,19 @@ defmodule NervesHub.Devices do The list is ordered by current connection age. Devices that have been online longer are updated first. """ - @spec available_for_update(Deployment.t(), non_neg_integer()) :: [Device.t()] - def available_for_update(deployment, count) do + @spec available_for_update(DeploymentGroup.t(), non_neg_integer()) :: [Device.t()] + def available_for_update(deployment_group, count) do now = DateTime.utc_now(:second) Device |> join(:inner, [d], dc in assoc(d, :latest_connection), as: :latest_connection) - |> join(:inner, [d], dp in assoc(d, :deployment), as: :deployment) - |> join(:inner, [deployment: dp], f in assoc(dp, :firmware), - on: [product_id: ^deployment.product_id], + |> join(:inner, [d], dg in assoc(d, :deployment_group), as: :deployment_group) + |> join(:inner, [deployment_group: dg], f in assoc(dg, :firmware), + on: [product_id: ^deployment_group.product_id], as: :firmware ) |> join(:left, [d], ifu in InflightUpdate, on: d.id == ifu.device_id, as: :inflight_update) - |> where(deployment_id: ^deployment.id) + |> where(deployment_id: ^deployment_group.id) |> where(updates_enabled: true) |> where([latest_connection: lc], lc.status == :connected) |> where([d], not is_nil(d.firmware_metadata)) @@ -749,22 +727,22 @@ defmodule NervesHub.Devices do def resolve_update(%{deployment_id: nil}), do: %UpdatePayload{update_available: false} def resolve_update(device) do - {:ok, deployment} = Deployments.get_deployment_for_device(device) + {:ok, deployment_group} = ManagedDeployments.get_deployment_group_for_device(device) - case verify_update_eligibility(device, deployment) do + case verify_update_eligibility(device, deployment_group) do {:ok, _device} -> - {:ok, url} = Firmwares.get_firmware_url(deployment.firmware) - {:ok, meta} = Firmwares.metadata_from_firmware(deployment.firmware) + {:ok, url} = Firmwares.get_firmware_url(deployment_group.firmware) + {:ok, meta} = Firmwares.metadata_from_firmware(deployment_group.firmware) %UpdatePayload{ update_available: true, firmware_url: url, firmware_meta: meta, - deployment: deployment, - deployment_id: deployment.id + deployment_group: deployment_group, + deployment_id: deployment_group.id } - {:error, :deployment_not_active, _device} -> + {:error, :deployment_group_not_active, _device} -> %UpdatePayload{update_available: false} {:error, :up_to_date, _device} -> @@ -793,9 +771,9 @@ defmodule NervesHub.Devices do @doc """ Returns true if Version.match? and all deployment tags are in device tags. """ - def matches_deployment?( + def matches_deployment_group?( %Device{tags: tags, firmware_metadata: %FirmwareMetadata{version: version}}, - %Deployment{conditions: %{"version" => requirement, "tags" => dep_tags}} + %DeploymentGroup{conditions: %{"version" => requirement, "tags" => dep_tags}} ) do if version_match?(version, requirement) and tags_match?(tags, dep_tags) do true @@ -804,23 +782,32 @@ defmodule NervesHub.Devices do end end - def matches_deployment?(_, _), do: false + def matches_deployment_group?(_, _), do: false - @spec update_deployment(Device.t(), Deployment.t()) :: Device.t() - def update_deployment(device, deployment) do + @spec update_deployment_group(Device.t(), DeploymentGroup.t()) :: Device.t() + def update_deployment_group(device, deployment_group) do device = device |> Ecto.Changeset.change() - |> Ecto.Changeset.put_change(:deployment_id, deployment.id) + |> Ecto.Changeset.put_change(:deployment_id, deployment_group.id) |> Repo.update!() - _ = broadcast(device, "devices/deployment-updated", %{deployment_id: deployment.id}) + _ = + broadcast(device, "devices/deployment-updated", %{deployment_id: deployment_group.id}) - Map.put(device, :deployment, deployment) + Map.put(device, :deployment_group, deployment_group) + end + + @spec update_many_deployment_groups([non_neg_integer()], DeploymentGroup.t()) :: + {integer(), nil | [term()]} + def update_many_deployment_groups(device_ids, deployment_group) do + Device + |> where([d], d.id in ^device_ids) + |> Repo.update_all(set: [deployment_id: deployment_group.id]) end - @spec clear_deployment(Device.t()) :: Device.t() - def clear_deployment(device) do + @spec clear_deployment_group(Device.t()) :: Device.t() + def clear_deployment_group(device) do device = device |> Ecto.Changeset.change() @@ -829,25 +816,25 @@ defmodule NervesHub.Devices do _ = broadcast(device, "devices/deployment-cleared") - Map.put(device, :deployment, nil) + Map.put(device, :deployment_group, nil) end - @spec failure_threshold_met?(Device.t(), Deployment.t()) :: boolean() - def failure_threshold_met?(%Device{} = device, %Deployment{} = deployment) do - Enum.count(device.update_attempts) >= deployment.device_failure_threshold + @spec failure_threshold_met?(Device.t(), DeploymentGroup.t()) :: boolean() + def failure_threshold_met?(%Device{} = device, %DeploymentGroup{} = deployment_group) do + Enum.count(device.update_attempts) >= deployment_group.device_failure_threshold end - @spec failure_rate_met?(Device.t(), Deployment.t()) :: boolean() - def failure_rate_met?(%Device{} = device, %Deployment{} = deployment) do + @spec failure_rate_met?(Device.t(), DeploymentGroup.t()) :: boolean() + def failure_rate_met?(%Device{} = device, %DeploymentGroup{} = deployment_group) do seconds_ago = - Timex.shift(DateTime.utc_now(), seconds: -deployment.device_failure_rate_seconds) + Timex.shift(DateTime.utc_now(), seconds: -deployment_group.device_failure_rate_seconds) attempts = Enum.filter(device.update_attempts, fn attempt -> DateTime.compare(seconds_ago, attempt) == :lt end) - Enum.count(attempts) >= deployment.device_failure_rate_amount + Enum.count(attempts) >= deployment_group.device_failure_rate_amount end @doc """ @@ -866,16 +853,16 @@ defmodule NervesHub.Devices do device.updates_enabled == false || device_in_penalty_box?(device, now) end - def device_matches_deployment?(device, deployment) do - device.firmware_metadata.uuid == deployment.firmware.uuid + def device_matches_deployment_group?(device, deployment_group) do + device.firmware_metadata.uuid == deployment_group.firmware.uuid end - def verify_update_eligibility(device, deployment, now \\ DateTime.utc_now()) do + def verify_update_eligibility(device, deployment_group, now \\ DateTime.utc_now()) do cond do - not deployment.is_active -> - {:error, :deployment_not_active, device} + not deployment_group.is_active -> + {:error, :deployment_group_not_active, device} - device_matches_deployment?(device, deployment) -> + device_matches_deployment_group?(device, deployment_group) -> {:error, :up_to_date, device} updates_blocked?(device, now) -> @@ -883,26 +870,26 @@ defmodule NervesHub.Devices do {:error, :updates_blocked, device} - failure_rate_met?(device, deployment) -> + failure_rate_met?(device, deployment_group) -> blocked_until = DateTime.utc_now() |> DateTime.truncate(:second) - |> DateTime.add(deployment.penalty_timeout_minutes * 60, :second) + |> DateTime.add(deployment_group.penalty_timeout_minutes * 60, :second) - DeviceTemplates.audit_firmware_upgrade_blocked(deployment, device) + DeviceTemplates.audit_firmware_upgrade_blocked(deployment_group, device) clear_inflight_update(device) {:ok, device} = update_device(device, %{updates_blocked_until: blocked_until}) {:error, :updates_blocked, device} - failure_threshold_met?(device, deployment) -> + failure_threshold_met?(device, deployment_group) -> blocked_until = DateTime.utc_now() |> DateTime.truncate(:second) - |> DateTime.add(deployment.penalty_timeout_minutes * 60, :second) + |> DateTime.add(deployment_group.penalty_timeout_minutes * 60, :second) - DeviceTemplates.audit_firmware_upgrade_blocked(deployment, device) + DeviceTemplates.audit_firmware_upgrade_blocked(deployment_group, device) clear_inflight_update(device) {:ok, device} = update_device(device, %{updates_blocked_until: blocked_until}) @@ -954,7 +941,7 @@ defmodule NervesHub.Devices do _ = if inflight_update do - Deployment + DeploymentGroup |> where([d], d.id == ^inflight_update.deployment_id) |> Repo.update_all(inc: [current_updated_devices: 1]) @@ -1013,27 +1000,28 @@ defmodule NervesHub.Devices do :ok end - def up_to_date_count(%Deployment{} = deployment) do + def up_to_date_count(%DeploymentGroup{} = deployment_group) do Device - |> where([d], d.deployment_id == ^deployment.id) - |> where([d], d.firmware_metadata["uuid"] == ^deployment.firmware.uuid) + |> where([d], d.deployment_id == ^deployment_group.id) + |> where([d], d.firmware_metadata["uuid"] == ^deployment_group.firmware.uuid) |> Repo.aggregate(:count) end - @spec updating_count(Deployment.t()) :: term() | nil - def updating_count(%Deployment{id: id}) do + @spec updating_count(DeploymentGroup.t()) :: term() | nil + def updating_count(%DeploymentGroup{id: id}) do InflightUpdate |> where([ifu], ifu.deployment_id == ^id) |> Repo.aggregate(:count) end - @spec waiting_for_update_count(Deployment.t()) :: term() | nil - def waiting_for_update_count(%Deployment{} = deployment) do + @spec waiting_for_update_count(DeploymentGroup.t()) :: term() | nil + def waiting_for_update_count(%DeploymentGroup{} = deployment_group) do Device - |> where([d], d.deployment_id == ^deployment.id) + |> where([d], d.deployment_id == ^deployment_group.id) |> where( [d], - is_nil(d.firmware_metadata) or d.firmware_metadata["uuid"] != ^deployment.firmware.uuid + is_nil(d.firmware_metadata) or + d.firmware_metadata["uuid"] != ^deployment_group.firmware.uuid ) |> Repo.aggregate(:count) end @@ -1199,11 +1187,11 @@ defmodule NervesHub.Devices do update_device(device, %{updates_blocked_until: blocked_until}) end - @spec move_many_to_deployment([integer()], integer()) :: + @spec move_many_to_deployment_group([integer()], integer()) :: {:ok, %{updated: non_neg_integer(), ignored: non_neg_integer()}} - def move_many_to_deployment(device_ids, deployment_id) do + def move_many_to_deployment_group(device_ids, deployment_id) do %{firmware: firmware} = - Deployment |> where(id: ^deployment_id) |> preload(:firmware) |> Repo.one() + DeploymentGroup |> where(id: ^deployment_id) |> preload(:firmware) |> Repo.one() {devices_updated_count, _} = Device @@ -1388,11 +1376,11 @@ defmodule NervesHub.Devices do Version.match?(version, requirement) end - defp tags_match?(nil, deployment_tags), do: tags_match?([], deployment_tags) + defp tags_match?(nil, deployment_group_tags), do: tags_match?([], deployment_group_tags) defp tags_match?(device_tags, nil), do: tags_match?(device_tags, []) - defp tags_match?(device_tags, deployment_tags) do - Enum.all?(deployment_tags, fn tag -> tag in device_tags end) + defp tags_match?(device_tags, deployment_group_tags) do + Enum.all?(deployment_group_tags, fn tag -> tag in device_tags end) end def maybe_copy_firmware_keys(%{firmware_metadata: %{uuid: uuid}, org_id: source}, %Org{ @@ -1432,37 +1420,37 @@ defmodule NervesHub.Devices do @doc """ Deployment orchestrator told a device to update """ - @spec told_to_update(Device.t() | integer(), Deployment.t()) :: + @spec told_to_update(Device.t() | integer(), DeploymentGroup.t()) :: {:ok, InflightUpdate.t()} | :error - def told_to_update(%Device{id: id}, deployment) do - told_to_update(id, deployment) + def told_to_update(%Device{id: id}, deployment_group) do + told_to_update(id, deployment_group) end - def told_to_update(device_id, deployment) do + def told_to_update(device_id, deployment_group) do expires_at = DateTime.utc_now() - |> DateTime.add(60 * deployment.inflight_update_expiration_minutes, :second) + |> DateTime.add(60 * deployment_group.inflight_update_expiration_minutes, :second) |> DateTime.truncate(:second) %{ device_id: device_id, - deployment_id: deployment.id, - firmware_id: deployment.firmware_id, - firmware_uuid: deployment.firmware.uuid, + deployment_id: deployment_group.id, + firmware_id: deployment_group.firmware_id, + firmware_uuid: deployment_group.firmware.uuid, expires_at: expires_at } |> InflightUpdate.create_changeset() |> Repo.insert() |> case do {:ok, inflight_update} -> - broadcast_update_request(device_id, inflight_update, deployment) + broadcast_update_request(device_id, inflight_update, deployment_group) {:ok, inflight_update} {:error, _changeset} -> # TODO this logic doesn't feel right. We should revise this approach. # Device already has an inflight update, fetch it - case Repo.get_by(InflightUpdate, device_id: device_id, deployment_id: deployment.id) do + case Repo.get_by(InflightUpdate, device_id: device_id, deployment_id: deployment_group.id) do nil -> Logger.error( "An inflight update could not be created or found for the device (#{device_id})" @@ -1471,7 +1459,7 @@ defmodule NervesHub.Devices do :error inflight_update -> - broadcast_update_request(device_id, inflight_update, deployment) + broadcast_update_request(device_id, inflight_update, deployment_group) {:ok, inflight_update} end @@ -1492,7 +1480,7 @@ defmodule NervesHub.Devices do def update_started!(inflight_update, device, deployment, reference_id) do Repo.transaction(fn -> - DeviceTemplates.audit_device_deployment_update_triggered( + DeviceTemplates.audit_device_deployment_group_update_triggered( device, deployment, reference_id @@ -1505,16 +1493,16 @@ defmodule NervesHub.Devices do end) end - def inflight_updates_for(%Deployment{} = deployment) do + def inflight_updates_for(%DeploymentGroup{} = deployment_group) do InflightUpdate - |> where([iu], iu.deployment_id == ^deployment.id) + |> where([iu], iu.deployment_id == ^deployment_group.id) |> preload([:device]) |> Repo.all() end - def count_inflight_updates_for(%Deployment{} = deployment) do + def count_inflight_updates_for(%DeploymentGroup{} = deployment_group) do InflightUpdate - |> where([iu], iu.deployment_id == ^deployment.id) + |> where([iu], iu.deployment_id == ^deployment_group.id) |> Repo.aggregate(:count) end @@ -1551,18 +1539,18 @@ defmodule NervesHub.Devices do |> Repo.preload(:product) end - defp broadcast_update_request(device_id, inflight_update, deployment) do + defp broadcast_update_request(device_id, inflight_update, deployment_group) do _ = - if deployment.orchestrator_strategy == :distributed do - {:ok, url} = Firmwares.get_firmware_url(deployment.firmware) - {:ok, meta} = Firmwares.metadata_from_firmware(deployment.firmware) + if deployment_group.orchestrator_strategy == :distributed do + {:ok, url} = Firmwares.get_firmware_url(deployment_group.firmware) + {:ok, meta} = Firmwares.metadata_from_firmware(deployment_group.firmware) update_payload = %UpdatePayload{ update_available: true, firmware_url: url, firmware_meta: meta, - deployment: deployment, - deployment_id: deployment.id + deployment_group: deployment_group, + deployment_id: deployment_group.id } payload = %{inflight_update: inflight_update, update_payload: update_payload} diff --git a/lib/nerves_hub/devices/device.ex b/lib/nerves_hub/devices/device.ex index b6618c09c..c84cb76d1 100644 --- a/lib/nerves_hub/devices/device.ex +++ b/lib/nerves_hub/devices/device.ex @@ -4,13 +4,13 @@ defmodule NervesHub.Devices.Device do import Ecto.Changeset alias NervesHub.Accounts.Org - alias NervesHub.Deployments.Deployment alias NervesHub.Devices.DeviceCertificate alias NervesHub.Devices.DeviceConnection alias NervesHub.Devices.DeviceHealth alias NervesHub.Devices.DeviceMetric alias NervesHub.Extensions.DeviceExtensionsSetting alias NervesHub.Firmwares.FirmwareMetadata + alias NervesHub.ManagedDeployments.DeploymentGroup alias NervesHub.Products.Product alias __MODULE__ @@ -36,7 +36,7 @@ defmodule NervesHub.Devices.Device do schema "devices" do belongs_to(:org, Org) belongs_to(:product, Product) - belongs_to(:deployment, Deployment) + belongs_to(:deployment_group, DeploymentGroup, foreign_key: :deployment_id) belongs_to(:latest_connection, DeviceConnection, type: :binary_id) belongs_to(:latest_health, DeviceHealth) diff --git a/lib/nerves_hub/devices/inflight_update.ex b/lib/nerves_hub/devices/inflight_update.ex index 70e964226..5e09b2b78 100644 --- a/lib/nerves_hub/devices/inflight_update.ex +++ b/lib/nerves_hub/devices/inflight_update.ex @@ -3,18 +3,17 @@ defmodule NervesHub.Devices.InflightUpdate do import Ecto.Changeset - alias NervesHub.Deployments.Deployment alias NervesHub.Devices.Device alias NervesHub.Devices.InflightUpdate alias NervesHub.Firmwares.Firmware - - @required_params [:device_id, :deployment_id, :firmware_id, :firmware_uuid, :expires_at] + alias NervesHub.ManagedDeployments.DeploymentGroup @type t :: %__MODULE__{} + @required_params [:device_id, :deployment_id, :firmware_id, :firmware_uuid, :expires_at] schema "inflight_updates" do belongs_to(:device, Device) - belongs_to(:deployment, Deployment) + belongs_to(:deployment_group, DeploymentGroup, foreign_key: :deployment_id) belongs_to(:firmware, Firmware) field(:firmware_uuid, Ecto.UUID) diff --git a/lib/nerves_hub/devices/update_payload.ex b/lib/nerves_hub/devices/update_payload.ex index 0c4948693..e9bbd750e 100644 --- a/lib/nerves_hub/devices/update_payload.ex +++ b/lib/nerves_hub/devices/update_payload.ex @@ -3,8 +3,8 @@ defmodule NervesHub.Devices.UpdatePayload do This struct represents the payload that gets dispatched to devices """ - alias NervesHub.Deployments.Deployment alias NervesHub.Firmwares.FirmwareMetadata + alias NervesHub.ManagedDeployments.DeploymentGroup @derive {Jason.Encoder, only: [ @@ -17,7 +17,7 @@ defmodule NervesHub.Devices.UpdatePayload do defstruct update_available: false, firmware_url: nil, firmware_meta: nil, - deployment: nil, + deployment_group: nil, deployment_id: nil @type t :: @@ -25,14 +25,14 @@ defmodule NervesHub.Devices.UpdatePayload do update_available: false, firmware_meta: nil, firmware_url: nil, - deployment: nil, + deployment_group: nil, deployment_id: nil } | %__MODULE__{ update_available: true, firmware_meta: FirmwareMetadata.t(), firmware_url: String.t(), - deployment: Deployment.t(), + deployment_group: DeploymentGroup.t(), deployment_id: non_neg_integer() } end diff --git a/lib/nerves_hub/firmwares.ex b/lib/nerves_hub/firmwares.ex index 6f4c8676c..2100d90e9 100644 --- a/lib/nerves_hub/firmwares.ex +++ b/lib/nerves_hub/firmwares.ex @@ -107,13 +107,13 @@ defmodule NervesHub.Firmwares do defp sort_firmware(query, sort), do: order_by(query, ^sort) - def get_firmwares_for_deployment(deployment) do - deployment = Repo.preload(deployment, [:firmware]) + def get_firmwares_for_deployment_group(deployment_group) do + deployment_group = Repo.preload(deployment_group, [:firmware]) Firmware - |> where([f], f.product_id == ^deployment.product_id) - |> where([f], f.platform == ^deployment.firmware.platform) - |> where([f], f.architecture == ^deployment.firmware.architecture) + |> where([f], f.product_id == ^deployment_group.product_id) + |> where([f], f.platform == ^deployment_group.firmware.platform) + |> where([f], f.architecture == ^deployment_group.firmware.architecture) |> order_by([f], [fragment("? collate numeric desc", f.version), desc: :inserted_at]) |> with_product() |> Repo.all() diff --git a/lib/nerves_hub/firmwares/firmware.ex b/lib/nerves_hub/firmwares/firmware.ex index 563f79928..981330599 100644 --- a/lib/nerves_hub/firmwares/firmware.ex +++ b/lib/nerves_hub/firmwares/firmware.ex @@ -5,7 +5,7 @@ defmodule NervesHub.Firmwares.Firmware do alias NervesHub.Accounts.Org alias NervesHub.Accounts.OrgKey - alias NervesHub.Deployments.Deployment + alias NervesHub.ManagedDeployments.DeploymentGroup alias NervesHub.Products.Product alias __MODULE__ @@ -46,7 +46,7 @@ defmodule NervesHub.Firmwares.Firmware do belongs_to(:org, Org, where: [deleted_at: nil]) belongs_to(:product, Product, where: [deleted_at: nil]) belongs_to(:org_key, OrgKey) - has_many(:deployments, Deployment) + has_many(:deployment_groups, DeploymentGroup) field(:architecture, :string) field(:author, :string) @@ -70,7 +70,7 @@ defmodule NervesHub.Firmwares.Firmware do |> cast(params, @required_params ++ @optional_params) |> validate_required(@required_params) |> unique_constraint(:uuid, name: :firmwares_product_id_uuid_index) - |> foreign_key_constraint(:deployments, name: :deployments_firmware_id_fkey) + |> foreign_key_constraint(:deployment_groups, name: :deployment_groups_firmware_id_fkey) end def update_changeset(%Firmware{} = firmware, params) do @@ -78,12 +78,12 @@ defmodule NervesHub.Firmwares.Firmware do |> cast(params, @required_params ++ @optional_params) |> validate_required(@required_params) |> unique_constraint(:uuid, name: :firmwares_product_id_uuid_index) - |> foreign_key_constraint(:deployments, name: :deployments_firmware_id_fkey) + |> foreign_key_constraint(:deployment_groups, name: :deployment_groups_firmware_id_fkey) end def delete_changeset(%Firmware{} = firmware, params) do firmware |> cast(params, @required_params ++ @optional_params) - |> no_assoc_constraint(:deployments, message: "Firmware has associated deployments") + |> no_assoc_constraint(:deployment_groups, message: "Firmware has associated deployments") end end diff --git a/lib/nerves_hub/logger.ex b/lib/nerves_hub/logger.ex index b63bd4dca..3c86e09e3 100644 --- a/lib/nerves_hub/logger.ex +++ b/lib/nerves_hub/logger.ex @@ -33,9 +33,9 @@ defmodule NervesHub.Logger do [:nerves_hub, :devices, :duplicate_connection], [:nerves_hub, :devices, :update, :automatic], [:nerves_hub, :devices, :update, :successful], - [:nerves_hub, :deployments, :set_deployment, :none_found], - [:nerves_hub, :deployments, :set_deployment, :one_found], - [:nerves_hub, :deployments, :set_deployment, :multiple_found] + [:nerves_hub, :managed_deployments, :set_deployment_group, :none_found], + [:nerves_hub, :managed_deployments, :set_deployment_group, :one_found], + [:nerves_hub, :managed_deployments, :set_deployment_group, :multiple_found] ] Enum.each(events, fn event -> @@ -120,24 +120,39 @@ defmodule NervesHub.Logger do ) end - def log_event([:nerves_hub, :deployments, :set_deployment, :none_found], _, metadata, _) do - Logger.info("No matching deployments", - event: "nerves_hub.deployments.set_deployment.none_found", + def log_event( + [:nerves_hub, :managed_deployments, :set_deployment_group, :none_found], + _, + metadata, + _ + ) do + Logger.info("No matching deployment groups", + event: "nerves_hub.managed_deployments.set_deployment_group.none_found", identifier: metadata[:device].identifier ) end - def log_event([:nerves_hub, :deployments, :set_deployment, :one_found], _, metadata, _) do + def log_event( + [:nerves_hub, :managed_deployments, :set_deployment_group, :one_found], + _, + metadata, + _ + ) do Logger.info("Deployment match found", - event: "nerves_hub.deployments.set_deployment.one_found", + event: "nerves_hub.managed_deployments.set_deployment_group.one_found", identifier: metadata[:device].identifier, deployment_id: metadata[:deployment].id ) end - def log_event([:nerves_hub, :deployments, :set_deployment, :multiple_found], _, metadata, _) do + def log_event( + [:nerves_hub, :managed_deployments, :set_deployment_group, :multiple_found], + _, + metadata, + _ + ) do Logger.info("More than one deployment match found, setting to the first", - event: "nerves_hub.deployments.set_deployment.multiple_found", + event: "nerves_hub.managed_deployments.set_deployment_group.multiple_found", identifier: metadata[:device].identifier, deployment_id: metadata[:deployment].id ) diff --git a/lib/nerves_hub/deployments.ex b/lib/nerves_hub/managed_deployments.ex similarity index 57% rename from lib/nerves_hub/deployments.ex rename to lib/nerves_hub/managed_deployments.ex index c8fd956b1..2abb86e34 100644 --- a/lib/nerves_hub/deployments.ex +++ b/lib/nerves_hub/managed_deployments.ex @@ -1,15 +1,16 @@ -defmodule NervesHub.Deployments do +defmodule NervesHub.ManagedDeployments do import Ecto.Query require Logger - alias NervesHub.AuditLogs.DeploymentTemplates + alias NervesHub.AuditLogs.DeploymentGroupTemplates alias NervesHub.AuditLogs.DeviceTemplates - alias NervesHub.Deployments.Deployment - alias NervesHub.Deployments.Distributed.Orchestrator, as: DistributedOrchestrator alias NervesHub.Deployments.InflightDeploymentCheck alias NervesHub.Devices alias NervesHub.Devices.Device + alias NervesHub.ManagedDeployments.DeploymentGroup + alias NervesHub.ManagedDeployments.Distributed.Orchestrator, as: DistributedOrchestrator + alias NervesHub.ManagedDeployments.InflightDeploymentCheck alias NervesHub.Products.Product alias NervesHub.Workers.FirmwareDeltaBuilder @@ -17,14 +18,14 @@ defmodule NervesHub.Deployments do alias Ecto.Changeset - @spec all() :: [Deployment.t()] + @spec all() :: [DeploymentGroup.t()] def all() do - Repo.all(Deployment) + Repo.all(DeploymentGroup) end - @spec should_run_in_distributed_orchestrator() :: [Deployment.t()] + @spec should_run_in_distributed_orchestrator() :: [DeploymentGroup.t()] def should_run_in_distributed_orchestrator() do - Deployment + DeploymentGroup |> where(is_active: true) |> where(orchestrator_strategy: :distributed) |> Repo.all() @@ -53,38 +54,38 @@ defmodule NervesHub.Deployments do |> Repo.exclude_deleted() |> group_by([d], d.deployment_id) - Deployment + DeploymentGroup |> join(:left, [d], dev in subquery(subquery), on: dev.deployment_id == d.id) |> join(:left, [d], f in assoc(d, :firmware)) |> where([d], d.product_id == ^product.id) - |> sort_deployments(sort_opts) + |> sort_deployment_groups(sort_opts) |> preload([_d, _dev, f], firmware: f) |> select_merge([_f, dev], %{device_count: dev.device_count}) |> Flop.run(flop) end - defp sort_deployments(query, {direction, :platform}) do + defp sort_deployment_groups(query, {direction, :platform}) do order_by(query, [_d, _dev, f], {^direction, f.platform}) end - defp sort_deployments(query, {direction, :architecture}) do + defp sort_deployment_groups(query, {direction, :architecture}) do order_by(query, [_d, _dev, f], {^direction, f.architecture}) end - defp sort_deployments(query, {direction, :device_count}) do + defp sort_deployment_groups(query, {direction, :device_count}) do order_by(query, [_d, dev], {^direction, dev.device_count}) end - defp sort_deployments(query, {direction, :firmware_version}) do + defp sort_deployment_groups(query, {direction, :firmware_version}) do order_by(query, [_d, _dev, f], {^direction, f.version}) end - defp sort_deployments(query, sort), do: order_by(query, ^sort) + defp sort_deployment_groups(query, sort), do: order_by(query, ^sort) - @spec get_deployments_by_product(Product.t()) :: [Deployment.t()] - def get_deployments_by_product(%Product{id: product_id}) do + @spec get_deployment_groups_by_product(Product.t()) :: [DeploymentGroup.t()] + def get_deployment_groups_by_product(%Product{id: product_id}) do from( - d in Deployment, + d in DeploymentGroup, join: f in assoc(d, :firmware), where: f.product_id == ^product_id, preload: [{:firmware, :product}] @@ -103,35 +104,36 @@ defmodule NervesHub.Deployments do |> Map.new() end - @spec get_device_count(Deployment.t()) :: term() | nil - def get_device_count(%Deployment{id: id}) do + @spec get_device_count(DeploymentGroup.t()) :: term() | nil + def get_device_count(%DeploymentGroup{id: id}) do Device |> where([d], d.deployment_id == ^id) |> Repo.exclude_deleted() |> Repo.aggregate(:count) end - @spec get_deployments_by_firmware(integer()) :: [Deployment.t()] - def get_deployments_by_firmware(firmware_id) do - Deployment + @spec get_deployment_groups_by_firmware(integer()) :: [DeploymentGroup.t()] + def get_deployment_groups_by_firmware(firmware_id) do + DeploymentGroup |> where([d], d.firmware_id == ^firmware_id) |> Repo.all() end - @spec get(integer()) :: {:ok, Deployment.t()} | {:error, :not_found} + @spec get(integer()) :: {:ok, DeploymentGroup.t()} | {:error, :not_found} def get(id) when is_integer(id) do - case Repo.get(Deployment, id) do + case Repo.get(DeploymentGroup, id) do nil -> {:error, :not_found} - deployment -> - {:ok, deployment} + deployment_group -> + {:ok, deployment_group} end end - @spec get_deployment_for_device(Device.t()) :: {:ok, Deployment.t()} | {:error, :not_found} - def get_deployment_for_device(%Device{deployment_id: deployment_id}) do - Deployment + @spec get_deployment_group_for_device(Device.t()) :: + {:ok, DeploymentGroup.t()} | {:error, :not_found} + def get_deployment_group_for_device(%Device{deployment_id: deployment_id}) do + DeploymentGroup |> where([d], d.id == ^deployment_id) |> join(:left, [d], f in assoc(d, :firmware)) |> preload([d, f], firmware: f) @@ -140,35 +142,36 @@ defmodule NervesHub.Deployments do nil -> {:error, :not_found} - deployment -> - {:ok, deployment} + deployment_group -> + {:ok, deployment_group} end end - @spec get_deployment(Product.t(), String.t()) :: {:ok, Deployment.t()} | {:error, :not_found} - def get_deployment(%Product{id: product_id}, deployment_id) do + @spec get_deployment_group(Product.t(), String.t()) :: + {:ok, DeploymentGroup.t()} | {:error, :not_found} + def get_deployment_group(%Product{id: product_id}, deployment_id) do from( - d in Deployment, + d in DeploymentGroup, where: d.id == ^deployment_id, join: f in assoc(d, :firmware), where: f.product_id == ^product_id, preload: [{:firmware, :product}] ) - |> Deployment.with_firmware() + |> DeploymentGroup.with_firmware() |> Repo.one() |> case do nil -> {:error, :not_found} - deployment -> - {:ok, deployment} + deployment_group -> + {:ok, deployment_group} end end - def get_deployment(%Deployment{id: id}), do: get_deployment(id) + def get_deployment_group(%DeploymentGroup{id: id}), do: get_deployment_group(id) - def get_deployment(deployment_id) do - Deployment + def get_deployment_group(deployment_id) do + DeploymentGroup |> where([d], d.id == ^deployment_id) |> join(:left, [d], f in assoc(d, :firmware), as: :firmware) |> preload([firmware: f], firmware: f) @@ -182,7 +185,7 @@ defmodule NervesHub.Deployments do end end - @spec get_by_product_and_name!(Product.t(), String.t(), boolean()) :: Deployment.t() + @spec get_by_product_and_name!(Product.t(), String.t(), boolean()) :: DeploymentGroup.t() def get_by_product_and_name!(product, name, with_device_count \\ false) def get_by_product_and_name!(product, name, true) do @@ -206,9 +209,9 @@ defmodule NervesHub.Deployments do |> Repo.one!() end - @spec get_by_product_and_platform(Product.t(), binary()) :: [Deployment.t()] + @spec get_by_product_and_platform(Product.t(), binary()) :: [DeploymentGroup.t()] def get_by_product_and_platform(product, platform) do - Deployment + DeploymentGroup |> where(product_id: ^product.id) |> join(:left, [d], f in assoc(d, :firmware)) |> where([_d, f], f.platform == ^platform) @@ -216,22 +219,22 @@ defmodule NervesHub.Deployments do |> Repo.all() end - @spec get_deployment_by_name(Product.t(), String.t()) :: - {:ok, Deployment.t()} | {:error, :not_found} - def get_deployment_by_name(product, name) do + @spec get_deployment_group_by_name(Product.t(), String.t()) :: + {:ok, DeploymentGroup.t()} | {:error, :not_found} + def get_deployment_group_by_name(product, name) do get_by_product_and_name_query(product, name) |> Repo.one() |> case do nil -> {:error, :not_found} - deployment -> - {:ok, deployment} + deployment_group -> + {:ok, deployment_group} end end defp get_by_product_and_name_query(%Product{id: product_id}, name) do - Deployment + DeploymentGroup |> where(name: ^name) |> where(product_id: ^product_id) |> join(:left, [d], f in assoc(d, :firmware)) @@ -240,19 +243,20 @@ defmodule NervesHub.Deployments do |> preload([d, f, a, p], firmware: f, archive: a, product: p) end - @spec delete_deployment(Deployment.t()) :: {:ok, Deployment.t()} | {:error, :not_found} - def delete_deployment(%Deployment{id: deployment_id}) do - Deployment + @spec delete_deployment_group(DeploymentGroup.t()) :: + {:ok, DeploymentGroup.t()} | {:error, :not_found} + def delete_deployment_group(%DeploymentGroup{id: deployment_id}) do + DeploymentGroup |> Repo.get!(deployment_id) |> Repo.delete() |> case do {:error, _changeset} -> {:error, :not_found} - {:ok, deployment} -> - _ = deployment_deleted_event(deployment) + {:ok, deployment_group} -> + _ = deployment_deleted_event(deployment_group) - {:ok, deployment} + {:ok, deployment_group} end end @@ -261,29 +265,30 @@ defmodule NervesHub.Deployments do - Records audit logs depending on changes """ - @spec update_deployment(Deployment.t(), map) :: {:ok, Deployment.t()} | {:error, Changeset.t()} - def update_deployment(deployment, params) do + @spec update_deployment_group(DeploymentGroup.t(), map) :: + {:ok, DeploymentGroup.t()} | {:error, Changeset.t()} + def update_deployment_group(deployment_group, params) do result = Repo.transaction(fn -> device_count = Device |> select([d], count(d)) - |> where([d], d.deployment_id == ^deployment.id) + |> where([d], d.deployment_id == ^deployment_group.id) |> Repo.one() changeset = - deployment - |> Deployment.with_firmware() - |> Deployment.changeset(params) + deployment_group + |> DeploymentGroup.with_firmware() + |> DeploymentGroup.changeset(params) |> Ecto.Changeset.put_change(:total_updating_devices, device_count) case Repo.update(changeset) do - {:ok, deployment} -> - deployment = Repo.preload(deployment, [:firmware], force: true) + {:ok, deployment_group} -> + deployment_group = Repo.preload(deployment_group, [:firmware], force: true) - audit_changes!(deployment, changeset) + audit_changes!(deployment_group, changeset) - {deployment, changeset} + {deployment_group, changeset} {:error, changeset} -> Repo.rollback(changeset) @@ -291,66 +296,74 @@ defmodule NervesHub.Deployments do end) case result do - {:ok, {deployment, changeset}} -> - _ = maybe_trigger_delta_generation(deployment, changeset) - :ok = broadcast(deployment, "deployments/update") + {:ok, {deployment_group, changeset}} -> + _ = maybe_trigger_delta_generation(deployment_group, changeset) + :ok = broadcast(deployment_group, "deployments/update") if Map.has_key?(changeset.changes, :is_active) do - if deployment.is_active do - deployment_activated_event(deployment) + if deployment_group.is_active do + deployment_activated_event(deployment_group) else - deployment_deactivated_event(deployment) + deployment_deactivated_event(deployment_group) end end if Map.has_key?(changeset.changes, :orchestrator_strategy) do - if deployment.orchestrator_strategy == :distributed do - start_deployments_distributed_orchestrator_event(deployment) + if deployment_group.orchestrator_strategy == :distributed do + start_deployments_distributed_orchestrator_event(deployment_group) else - shutdown_deployments_distributed_orchestrator_event(deployment) + shutdown_deployments_distributed_orchestrator_event(deployment_group) end end - {:ok, deployment} + {:ok, deployment_group} {:error, changeset} -> {:error, changeset} end end - defp audit_changes!(deployment, changeset) do + defp audit_changes!(deployment_group, changeset) do Enum.each(changeset.changes, fn {:archive_id, archive_id} -> # Trigger the new archive to get downloaded by devices payload = %{archive_id: archive_id} - _ = broadcast(deployment, "archives/updated", payload) + _ = broadcast(deployment_group, "archives/updated", payload) - DeploymentTemplates.audit_deployment_change(deployment, "has a new archive") + DeploymentGroupTemplates.audit_deployment_group_change( + deployment_group, + "has a new archive" + ) {:conditions, _new_conditions} -> - DeploymentTemplates.audit_deployment_change(deployment, "conditions changed") + DeploymentGroupTemplates.audit_deployment_group_change( + deployment_group, + "conditions changed" + ) {:is_active, is_active} when is_active != true -> - DeploymentTemplates.audit_deployment_change(deployment, "is inactive") + DeploymentGroupTemplates.audit_deployment_group_change(deployment_group, "is inactive") _ -> :ignore end) end - defp maybe_trigger_delta_generation(deployment, changeset) do + defp maybe_trigger_delta_generation(deployment_group, changeset) do # Firmware changed on active deployment - if deployment.is_active and Map.has_key?(changeset.changes, :firmware_id) do - deployment = Repo.preload(deployment, :product, force: true) + if deployment_group.is_active and Map.has_key?(changeset.changes, :firmware_id) do + deployment_group = Repo.preload(deployment_group, :product, force: true) - if deployment.product.delta_updatable do - trigger_delta_generation_for_deployment(deployment) + if deployment_group.product.delta_updatable do + trigger_delta_generation_for_deployment_group(deployment_group) end end end - defp trigger_delta_generation_for_deployment(deployment) do - NervesHub.Devices.get_device_firmware_for_delta_generation_by_deployment(deployment.id) + defp trigger_delta_generation_for_deployment_group(deployment_group) do + NervesHub.Devices.get_device_firmware_for_delta_generation_by_deployment_group( + deployment_group.id + ) |> Enum.uniq() |> Enum.each(fn {source_id, target_id} -> FirmwareDeltaBuilder.start(source_id, target_id) @@ -360,43 +373,38 @@ defmodule NervesHub.Deployments do @doc """ Delete any matching inflight deployment checks for devices """ - @spec delete_inflight_checks(Deployment.t()) :: :ok - def delete_inflight_checks(deployment) do + @spec delete_inflight_checks(DeploymentGroup.t()) :: :ok + def delete_inflight_checks(deployment_group) do _ = InflightDeploymentCheck - |> where([idc], idc.deployment_id == ^deployment.id) + |> where([idc], idc.deployment_id == ^deployment_group.id) |> Repo.delete_all() :ok end - @spec new_deployment() :: Changeset.t() - def new_deployment() do - Ecto.Changeset.change(%Deployment{}) - end - - @spec change_deployment(Deployment.t(), map()) :: Changeset.t() - def change_deployment(deployment, params) do - Deployment.changeset(deployment, params) + @spec new_deployment_group() :: Changeset.t() + def new_deployment_group() do + Ecto.Changeset.change(%DeploymentGroup{}) end - @spec create_deployment(map()) :: {:ok, Deployment.t()} | {:error, Changeset.t()} - def create_deployment(params) do - changeset = Deployment.creation_changeset(%Deployment{}, params) + @spec create_deployment_group(map()) :: {:ok, DeploymentGroup.t()} | {:error, Changeset.t()} + def create_deployment_group(params) do + changeset = DeploymentGroup.creation_changeset(%DeploymentGroup{}, params) case Repo.insert(changeset) do - {:ok, deployment} -> - deployment_created_event(deployment) + {:ok, deployment_group} -> + deployment_created_event(deployment_group) - {:ok, deployment} + {:ok, deployment_group} {:error, changeset} -> {:error, changeset} end end - @spec broadcast(Deployment.t() | atom(), String.t(), map()) :: :ok | {:error, term()} - def broadcast(deployment, event, payload \\ %{}) + @spec broadcast(DeploymentGroup.t() | atom(), String.t(), map()) :: :ok | {:error, term()} + def broadcast(deployment_group, event, payload \\ %{}) def broadcast(:none, event, payload) do Phoenix.Channel.Server.broadcast( @@ -416,7 +424,7 @@ defmodule NervesHub.Deployments do ) end - def broadcast(%Deployment{id: id}, event, payload) do + def broadcast(%DeploymentGroup{id: id}, event, payload) do Phoenix.Channel.Server.broadcast( NervesHub.PubSub, "deployment:#{id}", @@ -448,25 +456,27 @@ defmodule NervesHub.Deployments do Version.match?(device.firmware_metadata.version, version) end - defp version_match?(_device, _deployment), do: true + defp version_match?(_device, _deployment_group), do: true - @spec verify_deployment_membership(Device.t()) :: Device.t() - def verify_deployment_membership( + @spec verify_deployment_group_membership(Device.t()) :: Device.t() + def verify_deployment_group_membership( %Device{deployment_id: deployment_id, firmware_metadata: %{version: device_version}} = device ) when not is_nil(deployment_id) do - {:ok, deployment} = get_deployment_for_device(device) + {:ok, deployment_group} = get_deployment_group_for_device(device) bad_version = - if deployment.conditions["version"] != "" do - !Version.match?(device_version, deployment.conditions["version"]) + if deployment_group.conditions["version"] != "" do + !Version.match?(device_version, deployment_group.conditions["version"]) else false end - bad_platform = device.firmware_metadata.platform != deployment.firmware.platform - bad_architecture = device.firmware_metadata.architecture != deployment.firmware.architecture + bad_platform = device.firmware_metadata.platform != deployment_group.firmware.platform + + bad_architecture = + device.firmware_metadata.architecture != deployment_group.firmware.architecture reason = cond do @@ -489,7 +499,7 @@ defmodule NervesHub.Deployments do |> Ecto.Changeset.change(%{deployment_id: nil}) |> Repo.update!() - DeploymentTemplates.audit_deployment_mismatch(device, deployment, reason) + DeploymentGroupTemplates.audit_deployment_mismatch(device, deployment_group, reason) device else @@ -497,42 +507,42 @@ defmodule NervesHub.Deployments do end end - def verify_deployment_membership(device), do: device + def verify_deployment_group_membership(device), do: device @doc """ - If the device is missing a deployment, find a matching deployment + If the device is missing a deployment group, find a matching deployment group - Do nothing if a deployment is already set + Do nothing if a deployment group is already set """ - @spec set_deployment(Device.t()) :: Device.t() - def set_deployment(%{deployment_id: nil} = device) do - case matching_deployments(device, [true]) do + @spec set_deployment_group(Device.t()) :: Device.t() + def set_deployment_group(%{deployment_id: nil} = device) do + case matching_deployment_groups(device, [true]) do [] -> - set_deployment_telemetry(:none_found, device) + set_deployment_group_telemetry(:none_found, device) - %{device | deployment: nil} + %{device | deployment_group: nil} [deployment] -> - set_deployment_telemetry(:one_found, device, deployment) + set_deployment_group_telemetry(:one_found, device, deployment) DeviceTemplates.audit_set_deployment(device, deployment, :one_found) - Devices.update_deployment(device, deployment) + Devices.update_deployment_group(device, deployment) [deployment | _] -> - set_deployment_telemetry(:multiple_found, device, deployment) + set_deployment_group_telemetry(:multiple_found, device, deployment) DeviceTemplates.audit_set_deployment(device, deployment, :multiple_found) - Devices.update_deployment(device, deployment) + Devices.update_deployment_group(device, deployment) end end - def set_deployment(device) do + def set_deployment_group(device) do preload_with_firmware_and_archive(device) end - defp set_deployment_telemetry(result, device, deployment \\ nil) do + defp set_deployment_group_telemetry(result, device, deployment \\ nil) do metadata = %{device: device} metadata = @@ -543,38 +553,38 @@ defmodule NervesHub.Deployments do end :telemetry.execute( - [:nerves_hub, :deployments, :set_deployment, result], + [:nerves_hub, :managed_deployments, :set_deployment_group, result], %{count: 1}, metadata ) end - @spec preload_firmware_and_archive(Deployment.t()) :: Deployment.t() - def preload_firmware_and_archive(deployment) do - %Deployment{} = Repo.preload(deployment, [:archive, :firmware]) + @spec preload_firmware_and_archive(DeploymentGroup.t()) :: DeploymentGroup.t() + def preload_firmware_and_archive(deployment_group) do + %DeploymentGroup{} = Repo.preload(deployment_group, [:archive, :firmware]) end @spec preload_with_firmware_and_archive(Device.t(), boolean()) :: Device.t() def preload_with_firmware_and_archive(device, force \\ false) do - %Device{} = Repo.preload(device, [deployment: [:archive, :firmware]], force: force) + %Device{} = Repo.preload(device, [deployment_group: [:archive, :firmware]], force: force) end @doc """ - Find all potential deployments for a device + Find all potential deployment groups for a device Based on the product, firmware platform, firmware architecture, and device tags """ - @spec matching_deployments(Device.t(), [boolean()]) :: [Deployment.t()] - def matching_deployments(device, active \\ [true, false]) - def matching_deployments(%Device{firmware_metadata: nil}, _active), do: [] + @spec matching_deployment_groups(Device.t(), [boolean()]) :: [DeploymentGroup.t()] + def matching_deployment_groups(device, active \\ [true, false]) + def matching_deployment_groups(%Device{firmware_metadata: nil}, _active), do: [] - def matching_deployments(device, active) do - Deployment + def matching_deployment_groups(device, active) do + DeploymentGroup |> join(:inner, [d], assoc(d, :firmware), as: :firmware) |> preload([_, firmware: f], firmware: f) |> where([d], d.product_id == ^device.product_id) |> where([d], d.is_active in ^active) - |> ignore_same_deployment(device) + |> ignore_same_deployment_group(device) |> where([d, firmware: f], f.platform == ^device.firmware_metadata.platform) |> where([d, firmware: f], f.architecture == ^device.firmware_metadata.architecture) |> where([d], fragment("?->'tags' <@ to_jsonb(?::text[])", d.conditions, ^device.tags)) @@ -592,24 +602,24 @@ defmodule NervesHub.Deployments do ) end - defp ignore_same_deployment(query, %{deployment_id: nil}), do: query + defp ignore_same_deployment_group(query, %{deployment_id: nil}), do: query - defp ignore_same_deployment(query, %{deployment_id: deployment_id}) do + defp ignore_same_deployment_group(query, %{deployment_id: deployment_id}) do where(query, [d], d.id != ^deployment_id) end @doc """ - Find all eligible deployments for a device, based on the firmware platform, + Find all eligible deployment groups for a device, based on the firmware platform, firmware architecture, and product. - This is purposefully less-strict then Deployments.matching_deployments/2 + This is purposefully less-strict then Deployments.matching_deployment_groups/2 and should only be used when a human is choosing the deployment for a device. """ - @spec eligible_deployments(Device.t()) :: [Deployment.t()] - def eligible_deployments(%Device{firmware_metadata: nil}), do: [] + @spec eligible_deployment_groups(Device.t()) :: [DeploymentGroup.t()] + def eligible_deployment_groups(%Device{firmware_metadata: nil}), do: [] - def eligible_deployments(device) do - Deployment + def eligible_deployment_groups(device) do + DeploymentGroup |> join(:inner, [d], assoc(d, :firmware), as: :firmware) |> preload([_, firmware: f], firmware: f) |> where([d, _], d.product_id == ^device.product_id) @@ -618,18 +628,18 @@ defmodule NervesHub.Deployments do |> Repo.all() end - def deployment_created_event(deployment) do + def deployment_created_event(deployment_group) do # the old orchestrator - _ = broadcast(:monitor, "deployments/new", %{deployment_id: deployment.id}) + _ = broadcast(:monitor, "deployments/new", %{deployment_id: deployment_group.id}) # the new orchestrator - _ = DistributedOrchestrator.start_orchestrator(deployment) + _ = DistributedOrchestrator.start_orchestrator(deployment_group) :ok end - def start_deployments_distributed_orchestrator_event(deployment) do - _ = DistributedOrchestrator.start_orchestrator(deployment) + def start_deployments_distributed_orchestrator_event(deployment_group) do + _ = DistributedOrchestrator.start_orchestrator(deployment_group) :ok end @@ -646,17 +656,17 @@ defmodule NervesHub.Deployments do :ok end - def deployment_activated_event(deployment) do - _ = DistributedOrchestrator.start_orchestrator(deployment) + def deployment_activated_event(deployment_group) do + _ = DistributedOrchestrator.start_orchestrator(deployment_group) :ok end - def deployment_deactivated_event(deployment) do + def deployment_deactivated_event(deployment_group) do _ = Phoenix.Channel.Server.broadcast( NervesHub.PubSub, - "orchestrator:deployment:#{deployment.id}", + "orchestrator:deployment:#{deployment_group.id}", "deactivated", %{} ) @@ -664,13 +674,13 @@ defmodule NervesHub.Deployments do :ok end - def deployment_deleted_event(deployment) do + def deployment_deleted_event(deployment_group) do _ = - if deployment.orchestrator_strategy == :distributed do - broadcast(deployment, "deleted") + if deployment_group.orchestrator_strategy == :distributed do + broadcast(deployment_group, "deleted") end - _ = broadcast(:monitor, "deployments/delete", %{deployment_id: deployment.id}) + _ = broadcast(:monitor, "deployments/delete", %{deployment_id: deployment_group.id}) :ok end diff --git a/lib/nerves_hub/deployments/deployment.ex b/lib/nerves_hub/managed_deployments/deployment_group.ex similarity index 79% rename from lib/nerves_hub/deployments/deployment.ex rename to lib/nerves_hub/managed_deployments/deployment_group.ex index b5f4f7549..0a033759f 100644 --- a/lib/nerves_hub/deployments/deployment.ex +++ b/lib/nerves_hub/managed_deployments/deployment_group.ex @@ -1,4 +1,4 @@ -defmodule NervesHub.Deployments.Deployment do +defmodule NervesHub.ManagedDeployments.DeploymentGroup do use Ecto.Schema import Ecto.Changeset @@ -9,6 +9,7 @@ defmodule NervesHub.Deployments.Deployment do alias NervesHub.Devices.Device alias NervesHub.Devices.InflightUpdate alias NervesHub.Firmwares.Firmware + alias NervesHub.ManagedDeployments.DeploymentRelease alias NervesHub.Products.Product alias NervesHub.Repo @@ -49,8 +50,9 @@ defmodule NervesHub.Deployments.Deployment do belongs_to(:org, Org, where: [deleted_at: nil]) belongs_to(:archive, Archive) - has_many(:inflight_updates, InflightUpdate) - has_many(:devices, Device, on_delete: :nilify_all) + has_many(:inflight_updates, InflightUpdate, foreign_key: :deployment_id) + has_many(:devices, Device, foreign_key: :deployment_id, on_delete: :nilify_all) + has_many(:deployment_releases, DeploymentRelease) field(:conditions, :map) field(:device_failure_threshold, :integer, default: 3) @@ -83,7 +85,7 @@ defmodule NervesHub.Deployments.Deployment do timestamps() end - def creation_changeset(%Deployment{} = deployment, params) do + def creation_changeset(%DeploymentGroup{} = deployment, params) do # set product_id by getting it from firmware with_product_id = handle_product_id(deployment, params) @@ -104,27 +106,27 @@ defmodule NervesHub.Deployments.Deployment do end end - defp handle_product_id(%Deployment{}, %{firmware: %Firmware{product_id: p_id}} = params) do + defp handle_product_id(%DeploymentGroup{}, %{firmware: %Firmware{product_id: p_id}} = params) do params |> Map.put(:product_id, p_id) end - defp handle_product_id(%Deployment{firmware: %Firmware{product_id: p_id}}, params) do + defp handle_product_id(%DeploymentGroup{firmware: %Firmware{product_id: p_id}}, params) do params |> Map.put(:product_id, p_id) end - defp handle_product_id(%Deployment{} = d, %{firmware_id: f_id} = params) do + defp handle_product_id(%DeploymentGroup{} = d, %{firmware_id: f_id} = params) do handle_product_id(d, params |> Map.put(:firmware, Firmware |> Repo.get!(f_id))) end - defp handle_product_id(%Deployment{firmware_id: nil}, params) do + defp handle_product_id(%DeploymentGroup{firmware_id: nil}, params) do params end - defp handle_product_id(%Deployment{} = d, params) do + defp handle_product_id(%DeploymentGroup{} = d, params) do handle_product_id(d |> with_firmware(), params) end - def changeset(%Deployment{} = deployment, params) do + def changeset(%DeploymentGroup{} = deployment, params) do deployment |> cast(params, @required_fields ++ @optional_fields) |> validate_required(@required_fields) @@ -139,29 +141,23 @@ defmodule NervesHub.Deployments.Deployment do |> validate_conditions() end - def with_firmware(%Deployment{firmware: %Firmware{}} = d), do: d + def with_firmware(%DeploymentGroup{firmware: %Firmware{}} = d), do: d - def with_firmware(%Deployment{} = d) do + def with_firmware(%DeploymentGroup{} = d) do d |> Repo.preload(:firmware) end - def with_firmware(deployment_query) do - deployment_query - |> preload(:firmware) - end + def with_firmware(query), do: preload(query, :firmware) - def with_product(%Deployment{product: %Product{}} = d), do: d + def with_product(%DeploymentGroup{product: %Product{}} = d), do: d - def with_product(%Deployment{} = d) do + def with_product(%DeploymentGroup{} = d) do d |> Repo.preload(:product) end - def with_product(deployment_query) do - deployment_query - |> preload(:product) - end + def with_product(query), do: preload(query, :product) defp validate_conditions(changeset, _options \\ []) do validate_change(changeset, :conditions, fn :conditions, conditions -> diff --git a/lib/nerves_hub/managed_deployments/deployment_release.ex b/lib/nerves_hub/managed_deployments/deployment_release.ex new file mode 100644 index 000000000..0d96f11be --- /dev/null +++ b/lib/nerves_hub/managed_deployments/deployment_release.ex @@ -0,0 +1,39 @@ +defmodule NervesHub.ManagedDeployments.DeploymentRelease do + use Ecto.Schema + + import Ecto.Changeset + + alias NervesHub.Accounts.User + alias NervesHub.Archives.Archive + alias NervesHub.Firmwares.Firmware + alias NervesHub.ManagedDeployments.DeploymentGroup + + alias __MODULE__ + + @type t :: %__MODULE__{} + + @required_fields [ + :deployment_group_id, + :firmware_id, + :archive_id, + :created_by_id, + :status + ] + + @optional_fields [:archive_id] + + schema "deployment_releases" do + belongs_to(:deployment_group, DeploymentGroup) + belongs_to(:firmware, Firmware) + belongs_to(:archive, Archive) + belongs_to(:user, User, foreign_key: :created_by_id) + + timestamps() + end + + def changeset(%DeploymentRelease{} = deployment_release, params) do + deployment_release + |> cast(params, @required_fields ++ @optional_fields) + |> validate_required(@required_fields) + end +end diff --git a/lib/nerves_hub/deployments/distributed/orchestrator.ex b/lib/nerves_hub/managed_deployments/distributed/orchestrator.ex similarity index 59% rename from lib/nerves_hub/deployments/distributed/orchestrator.ex rename to lib/nerves_hub/managed_deployments/distributed/orchestrator.ex index fae692d10..a9ba23ee2 100644 --- a/lib/nerves_hub/deployments/distributed/orchestrator.ex +++ b/lib/nerves_hub/managed_deployments/distributed/orchestrator.ex @@ -1,4 +1,4 @@ -defmodule NervesHub.Deployments.Distributed.Orchestrator do +defmodule NervesHub.ManagedDeployments.Distributed.Orchestrator do @moduledoc """ Orchestration process to handle passing out updates to devices @@ -12,10 +12,10 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do require Logger - alias NervesHub.Deployments - alias NervesHub.Deployments.Deployment alias NervesHub.Devices alias NervesHub.Devices.Device + alias NervesHub.ManagedDeployments + alias NervesHub.ManagedDeployments.DeploymentGroup alias Phoenix.PubSub alias Phoenix.Socket.Broadcast @@ -23,51 +23,53 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do @maybe_trigger_interval 3_000 defmodule State do - defstruct deployment: nil, + defstruct deployment_group: nil, rate_limit?: true, timer_ref: nil, should_run?: false @type t :: %__MODULE__{ - deployment: Deployment.t(), + deployment_group: DeploymentGroup.t(), rate_limit?: boolean(), timer_ref: reference() | nil, should_run?: boolean() } end - def child_spec(deployment, rate_limit \\ true) do + def child_spec(deployment_group, rate_limit \\ true) do %{ - id: :"distributed_orchestrator_#{deployment.id}", - start: {__MODULE__, :start_link, [deployment, rate_limit]}, + id: :"distributed_orchestrator_#{deployment_group.id}", + start: {__MODULE__, :start_link, [deployment_group, rate_limit]}, restart: :transient } end - def start_link(deployment, rate_limit) do - GenServer.start_link(__MODULE__, {deployment, rate_limit}) + def start_link(deployment_group, rate_limit) do + GenServer.start_link(__MODULE__, {deployment_group, rate_limit}) end - def start_link(deployment) do - start_link(deployment, true) + def start_link(deployment_group) do + start_link(deployment_group, true) end - @decorate with_span("Deployments.Distributed.Orchestrator.init") - def init({deployment, rate_limit}) do - :ok = PubSub.subscribe(NervesHub.PubSub, "deployment:#{deployment.id}") - :ok = PubSub.subscribe(NervesHub.PubSub, "orchestrator:deployment:#{deployment.id}") + @decorate with_span("ManagedDeployments.Distributed.Orchestrator.init") + def init({deployment_group, rate_limit}) do + :ok = PubSub.subscribe(NervesHub.PubSub, "deployment:#{deployment_group.id}") + + :ok = + PubSub.subscribe(NervesHub.PubSub, "orchestrator:deployment:#{deployment_group.id}") # trigger every two minutes, plus a jitter between 1 and 10 seconds, as a back up interval = :timer.seconds(120 + :rand.uniform(20)) _ = :timer.send_interval(interval, :trigger_interval) - {:ok, deployment} = Deployments.get_deployment(deployment) + {:ok, deployment_group} = ManagedDeployments.get_deployment_group(deployment_group) send(self(), :maybe_trigger) state = %State{ - deployment: deployment, + deployment_group: deployment_group, rate_limit?: rate_limit, timer_ref: nil, should_run?: true @@ -93,21 +95,21 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do As devices update and reconnect, the new orchestrator is told that the update was successful, and the process is repeated. """ - @decorate with_span("Deployments.Distributed.Orchestrator.trigger_update#noop") - def trigger_update(%Deployment{is_active: false}) do + @decorate with_span("ManagedDeployments.Distributed.Orchestrator.trigger_update#noop") + def trigger_update(%DeploymentGroup{is_active: false}) do :ok end - @decorate with_span("Deployments.Distributed.Orchestrator.trigger_update") - def trigger_update(deployment) do - :telemetry.execute([:nerves_hub, :deployment, :trigger_update], %{count: 1}) + @decorate with_span("ManagedDeployments.Distributed.Orchestrator.trigger_update") + def trigger_update(deployment_group) do + :telemetry.execute([:nerves_hub, :deployment_group, :trigger_update], %{count: 1}) - slots = available_slots(deployment) + slots = available_slots(deployment_group) if slots > 0 do - available = Devices.available_for_update(deployment, slots) + available = Devices.available_for_update(deployment_group, slots) - updated_count = schedule_devices!(available, deployment) + updated_count = schedule_devices!(available, deployment_group) if length(available) != updated_count do # rerun the deployment check since some devices were skipped @@ -120,10 +122,10 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do Determine how many devices should update based on the deployment update limit and the number currently updating """ - @spec available_slots(Deployment.t()) :: non_neg_integer() - def available_slots(deployment) do + @spec available_slots(DeploymentGroup.t()) :: non_neg_integer() + def available_slots(deployment_group) do # Just in case inflight goes higher than concurrent, limit it to 0 - (deployment.concurrent_updates - Devices.count_inflight_updates_for(deployment)) + (deployment_group.concurrent_updates - Devices.count_inflight_updates_for(deployment_group)) |> max(0) |> round() end @@ -134,31 +136,31 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do Returns the number of devices that were allowed to update. """ - @spec schedule_devices!([Device.t()], Deployment.t()) :: non_neg_integer() - def schedule_devices!(available, deployment) do + @spec schedule_devices!([Device.t()], DeploymentGroup.t()) :: non_neg_integer() + def schedule_devices!(available, deployment_group) do Enum.count(available, fn device -> - case can_device_update?(device, deployment) do + case can_device_update?(device, deployment_group) do true -> - tell_device_to_update(device.id, deployment) + tell_device_to_update(device.id, deployment_group) false -> - _ = Devices.update_blocked_until(device, deployment) + _ = Devices.update_blocked_until(device, deployment_group) false end end) end - @spec can_device_update?(Device.t(), Deployment.t()) :: boolean() - defp can_device_update?(device, deployment) do - not (Devices.failure_rate_met?(device, deployment) or - Devices.failure_threshold_met?(device, deployment)) + @spec can_device_update?(Device.t(), DeploymentGroup.t()) :: boolean() + defp can_device_update?(device, deployment_group) do + not (Devices.failure_rate_met?(device, deployment_group) or + Devices.failure_threshold_met?(device, deployment_group)) end - @spec tell_device_to_update(integer(), Deployment.t()) :: boolean() - defp tell_device_to_update(device_id, deployment) do - :telemetry.execute([:nerves_hub, :deployment, :trigger_update, :device], %{count: 1}) + @spec tell_device_to_update(integer(), DeploymentGroup.t()) :: boolean() + defp tell_device_to_update(device_id, deployment_group) do + :telemetry.execute([:nerves_hub, :deployment_group, :trigger_update, :device], %{count: 1}) - case Devices.told_to_update(device_id, deployment) do + case Devices.told_to_update(device_id, deployment_group) do {:ok, _} -> true _ -> false end @@ -166,14 +168,14 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do # if rate limiting isn't enabled, run `trigger_update` defp maybe_trigger_update(%State{rate_limit?: false} = state) do - trigger_update(state.deployment) + trigger_update(state.deployment_group) {:noreply, state} end # if there is no "delay" timer set, run `trigger_update` defp maybe_trigger_update(%State{timer_ref: nil} = state) do - trigger_update(state.deployment) + trigger_update(state.deployment_group) timer_ref = Process.send_after(self(), :maybe_trigger, @maybe_trigger_interval) @@ -188,7 +190,7 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do # if we don't have a `timer_ref` we can run `trigger_update` def handle_info(:trigger_interval, %State{timer_ref: nil} = state) do - trigger_update(state.deployment) + trigger_update(state.deployment_group) {:noreply, state} end @@ -201,13 +203,13 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do # if the 'run again' boolean in the state is `true`, which indicates that indicates # that previous call has been skipped, then run `trigger_update` now def handle_info(:maybe_trigger, %State{rate_limit?: false} = state) do - trigger_update(state.deployment) + trigger_update(state.deployment_group) {:noreply, state} end def handle_info(:maybe_trigger, %State{should_run?: true} = state) do - trigger_update(state.deployment) + trigger_update(state.deployment_group) timer_ref = Process.send_after(self(), :maybe_trigger, @maybe_trigger_interval) @@ -220,7 +222,9 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do {:noreply, %{state | timer_ref: nil}} end - @decorate with_span("Deployments.Distributed.Orchestrator.handle_info:deployment/device-online") + @decorate with_span( + "ManagedDeployments.Distributed.Orchestrator.handle_info:deployment/device-online" + ) def handle_info( %Broadcast{ topic: "orchestrator:deployment:" <> _rest, @@ -229,14 +233,16 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do }, state ) do - if should_trigger?(payload, state.deployment) do + if should_trigger?(payload, state.deployment_group) do maybe_trigger_update(state) else {:noreply, state} end end - @decorate with_span("Deployments.Distributed.Orchestrator.handle_info:deployment/device-update") + @decorate with_span( + "ManagedDeployments.Distributed.Orchestrator.handle_info:deployment/device-update" + ) def handle_info( %Broadcast{topic: "orchestrator:deployment:" <> _, event: "device-updated"}, state @@ -244,16 +250,18 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do maybe_trigger_update(state) end - @decorate with_span("Deployments.Distributed.Orchestrator.handle_info:deployments/update") + @decorate with_span( + "ManagedDeployments.Distributed.Orchestrator.handle_info:deployments/update" + ) def handle_info( %Broadcast{topic: "deployment:" <> _, event: "deployments/update"}, state ) do - {:ok, deployment} = Deployments.get_deployment(state.deployment) + {:ok, deployment_group} = ManagedDeployments.get_deployment_group(state.deployment_group) # shutdown the orchestrator if the deployment is updated to use the old `:multi` strategy - if deployment.orchestrator_strategy == :distributed do - maybe_trigger_update(%{state | deployment: deployment}) + if deployment_group.orchestrator_strategy == :distributed do + maybe_trigger_update(%{state | deployment_group: deployment_group}) else {:stop, :normal, state} end @@ -263,11 +271,14 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do {:stop, :normal, state} end - def handle_info(%Broadcast{topic: "orchestrator:deployment:" <> _, event: "deactivated"}, state) do + def handle_info( + %Broadcast{topic: "orchestrator:deployment:" <> _, event: "deactivated"}, + state + ) do {:stop, :normal, state} end - # Catch all for unknown broadcasts on a deployment + # Catch all for unknown broadcasts on a deployment_group def handle_info(%Broadcast{topic: "deployment:" <> _}, state) do {:noreply, state} end @@ -277,7 +288,7 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do end def start_orchestrator( - %Deployment{is_active: true, orchestrator_strategy: :distributed} = deployment + %DeploymentGroup{is_active: true, orchestrator_strategy: :distributed} = deployment ) do if Application.get_env(:nerves_hub, :deploy_env) != "test" do ProcessHub.start_child(:deployment_orchestrators, child_spec(deployment)) @@ -288,12 +299,12 @@ defmodule NervesHub.Deployments.Distributed.Orchestrator do :ok end - defp should_trigger?(payload, deployment) do - not (firmware_match?(payload, deployment) or updates_blocked?(payload)) + defp should_trigger?(payload, deployment_group) do + not (firmware_match?(payload, deployment_group) or updates_blocked?(payload)) end - defp firmware_match?(payload, deployment) do - payload.firmware_uuid == deployment.firmware.uuid + defp firmware_match?(payload, deployment_group) do + payload.firmware_uuid == deployment_group.firmware.uuid end defp updates_blocked?(payload) do diff --git a/lib/nerves_hub/deployments/distributed/orchestrator_registration.ex b/lib/nerves_hub/managed_deployments/distributed/orchestrator_registration.ex similarity index 76% rename from lib/nerves_hub/deployments/distributed/orchestrator_registration.ex rename to lib/nerves_hub/managed_deployments/distributed/orchestrator_registration.ex index f427b7673..4ae779c62 100644 --- a/lib/nerves_hub/deployments/distributed/orchestrator_registration.ex +++ b/lib/nerves_hub/managed_deployments/distributed/orchestrator_registration.ex @@ -1,4 +1,4 @@ -defmodule NervesHub.Deployments.Distributed.OrchestratorRegistration do +defmodule NervesHub.ManagedDeployments.Distributed.OrchestratorRegistration do @moduledoc """ Registers deployment orchestrators with `ProcessHub`. @@ -7,8 +7,8 @@ defmodule NervesHub.Deployments.Distributed.OrchestratorRegistration do use GenServer - alias NervesHub.Deployments - alias NervesHub.Deployments.Distributed.Orchestrator + alias NervesHub.ManagedDeployments.Distributed.Orchestrator + alias NervesHub.ManagedDeployments def child_spec(_) do %{ @@ -31,7 +31,7 @@ defmodule NervesHub.Deployments.Distributed.OrchestratorRegistration do @impl GenServer def handle_info(:start_orchestrators, _) do _ = - Deployments.should_run_in_distributed_orchestrator() + ManagedDeployments.should_run_in_distributed_orchestrator() |> Enum.map(fn deployment -> Orchestrator.child_spec(deployment) end) diff --git a/lib/nerves_hub/managed_deployments/inflight_deployment_check.ex b/lib/nerves_hub/managed_deployments/inflight_deployment_check.ex new file mode 100644 index 000000000..0078317c2 --- /dev/null +++ b/lib/nerves_hub/managed_deployments/inflight_deployment_check.ex @@ -0,0 +1,15 @@ +defmodule NervesHub.ManagedDeployments.InflightDeploymentCheck do + use Ecto.Schema + + @timestamps_opts updated_at: false + + alias NervesHub.Devices.Device + alias NervesHub.ManagedDeployments.DeploymentGroup + + schema "inflight_deployment_checks" do + belongs_to(:device, Device) + belongs_to(:deployment_group, DeploymentGroup, foreign_key: :deployment_id) + + timestamps() + end +end diff --git a/lib/nerves_hub/managed_deployments/monitor.ex b/lib/nerves_hub/managed_deployments/monitor.ex new file mode 100644 index 000000000..a4b34ab8c --- /dev/null +++ b/lib/nerves_hub/managed_deployments/monitor.ex @@ -0,0 +1,66 @@ +defmodule NervesHub.ManagedDeployments.Monitor do + @moduledoc """ + Deployment Monitor starts a deployment orchestrator per deployment + + Listens for new deployment groups and starts as necessary + """ + + use GenServer + + alias NervesHub.ManagedDeployments + alias NervesHub.ManagedDeploymentDynamicSupervisor + alias NervesHub.ManagedDeployments.Orchestrator + alias Phoenix.PubSub + alias Phoenix.Socket.Broadcast + + defmodule State do + defstruct [:deployment_groups] + end + + def start_link(_) do + GenServer.start_link(__MODULE__, []) + end + + def init(_) do + _ = PubSub.subscribe(NervesHub.PubSub, "deployment_group:monitor") + + {:ok, %State{}, {:continue, :boot}} + end + + def handle_continue(:boot, state) do + deployment_groups = + Enum.into(ManagedDeployments.all(), %{}, fn deployment_group -> + {:ok, orchestrator_pid} = + DynamicSupervisor.start_child( + ManagedDeploymentDynamicSupervisor, + {ManagedDeployments.Orchestrator, deployment_group} + ) + + {deployment_group.id, %{orchestrator_pid: orchestrator_pid}} + end) + + {:noreply, %{state | deployment_groups: deployment_groups}} + end + + def handle_info(%Broadcast{event: "deployments/new", payload: payload}, state) do + {:ok, deployment_group} = ManagedDeployments.get(payload.deployment_id) + + {:ok, orchestrator_pid} = + DynamicSupervisor.start_child( + ManagedDeploymentDynamicSupervisor, + {ManagedDeployments.Orchestrator, deployment_group} + ) + + deployment_groups = + Map.put(state.deployments, deployment_group.id, %{orchestrator_pid: orchestrator_pid}) + + {:noreply, %{state | deployment_groups: deployment_groups}} + end + + def handle_info(%Broadcast{event: "deployments/delete", payload: payload}, state) do + pid = GenServer.whereis(Orchestrator.name(payload.deployment_id)) + _ = DynamicSupervisor.terminate_child(ManagedDeploymentDynamicSupervisor, pid) + deployment_groups = Map.delete(state.deployments, payload.deployment_id) + {:noreply, %{state | deployment_groups: deployment_groups}} + end +end diff --git a/lib/nerves_hub/deployments/orchestrator.ex b/lib/nerves_hub/managed_deployments/orchestrator.ex similarity index 57% rename from lib/nerves_hub/deployments/orchestrator.ex rename to lib/nerves_hub/managed_deployments/orchestrator.ex index a7e0c8663..39334a43b 100644 --- a/lib/nerves_hub/deployments/orchestrator.ex +++ b/lib/nerves_hub/managed_deployments/orchestrator.ex @@ -1,4 +1,4 @@ -defmodule NervesHub.Deployments.Orchestrator do +defmodule NervesHub.ManagedDeployments.Orchestrator do @moduledoc """ Orchestration process to handle passing out updates to devices @@ -13,25 +13,25 @@ defmodule NervesHub.Deployments.Orchestrator do require Logger - alias NervesHub.Deployments - alias NervesHub.Deployments.Deployment alias NervesHub.Devices alias NervesHub.Devices.Device + alias NervesHub.ManagedDeployments + alias NervesHub.ManagedDeployments.DeploymentGroup alias NervesHub.Repo alias Phoenix.PubSub alias Phoenix.Socket.Broadcast - def start_link(deployment) do - GenServer.start_link(__MODULE__, deployment, name: name(deployment)) + def start_link(deployment_group) do + GenServer.start_link(__MODULE__, deployment_group, name: name(deployment_group)) end def name(deployment_id) when is_integer(deployment_id) do - {:via, Registry, {Deployments, deployment_id}} + {:via, Registry, {ManagedDeployments, deployment_id}} end - def name(deployment), do: name(deployment.id) + def name(deployment_group), do: name(deployment_group.id) def device_updated(deployment_id) do GenServer.cast(name(deployment_id), :trigger) @@ -44,7 +44,7 @@ defmodule NervesHub.Deployments.Orchestrator do - the deployment - not updating - - not using the deployment's current firmware + - not using the deployment group's current firmware If there is space for the device based on the concurrent allowed updates the device is told to update. This is not guaranteed to be at or under the @@ -53,25 +53,24 @@ defmodule NervesHub.Deployments.Orchestrator do As devices update and reconnect, the new orchestrator is told that the update was successful, and the process is repeated. """ - @decorate with_span("Deployments.Orchestrator.trigger_update#noop") - def trigger_update(%Deployment{is_active: false}) do + @decorate with_span("ManagedDeployments.Orchestrator.trigger_update#noop") + def trigger_update(%DeploymentGroup{is_active: false}) do :ok end - @decorate with_span("Deployments.Orchestrator.trigger_update#noop") - def trigger_update(%Deployment{orchestrator_strategy: strategy}) when strategy != :multi do + @decorate with_span("ManagedDeployments.Orchestrator.trigger_update#noop") + def trigger_update(%DeploymentGroup{orchestrator_strategy: strategy}) when strategy != :multi do :ok end - @decorate with_span("Deployments.Orchestrator.trigger_update") - def trigger_update(deployment) do - :telemetry.execute([:nerves_hub, :deployment, :trigger_update], %{count: 1}) + @decorate with_span("ManagedDeployments.Orchestrator.trigger_update") + def trigger_update(deployment_group) do + :telemetry.execute([:nerves_hub, :deployment_group, :trigger_update], %{count: 1}) match_conditions = [ - {:and, {:==, {:map_get, :deployment_id, :"$1"}, deployment.id}, + {:and, {:==, {:map_get, :deployment_id, :"$1"}, deployment_group.id}, {:==, {:map_get, :updating, :"$1"}, false}, - {:==, {:map_get, :updates_enabled, :"$1"}, true}, - {:"/=", {:map_get, :firmware_uuid, :"$1"}, deployment.firmware.uuid}} + {:==, {:map_get, :updates_enabled, :"$1"}, true}} ] match_return = %{ @@ -86,7 +85,9 @@ defmodule NervesHub.Deployments.Orchestrator do ]) # Get a rough count of devices to update - count = deployment.concurrent_updates - Devices.count_inflight_updates_for(deployment) + count = + deployment_group.concurrent_updates - Devices.count_inflight_updates_for(deployment_group) + # Just in case inflight goes higher than concurrent, limit it to 0 count = max(count, 0) @@ -96,13 +97,14 @@ defmodule NervesHub.Deployments.Orchestrator do devices |> Enum.take(count) |> Enum.each(fn %{device_id: device_id, pid: pid} -> - :telemetry.execute([:nerves_hub, :deployment, :trigger_update, :device], %{count: 1}) + :telemetry.execute([:nerves_hub, :deployment_group, :trigger_update, :device], %{count: 1}) device = %Device{id: device_id} # Check again because other nodes are processing at the same time - if Devices.count_inflight_updates_for(deployment) < deployment.concurrent_updates do - case Devices.told_to_update(device, deployment) do + if Devices.count_inflight_updates_for(deployment_group) < + deployment_group.concurrent_updates do + case Devices.told_to_update(device, deployment_group) do {:ok, inflight_update} -> send(pid, {"deployments/update", inflight_update}) @@ -118,48 +120,43 @@ defmodule NervesHub.Deployments.Orchestrator do end) end - def init(deployment) do - {:ok, deployment, {:continue, :boot}} + def init(deployment_group) do + {:ok, deployment_group, {:continue, :boot}} end - @decorate with_span("Deployments.Orchestrator.boot") - def handle_continue(:boot, deployment) do - _ = PubSub.subscribe(NervesHub.PubSub, "deployment:#{deployment.id}") + @decorate with_span("ManagedDeployments.Orchestrator.boot") + def handle_continue(:boot, deployment_group) do + _ = PubSub.subscribe(NervesHub.PubSub, "deployment:#{deployment_group.id}") # trigger every 10 minutes, plus a jitter between 1 and 5 seconds, as a back up interval = (10 + :rand.uniform(10)) * 60 * 1000 _ = :timer.send_interval(interval, :trigger) - deployment = - deployment - |> Repo.reload() - |> Repo.preload([:firmware], force: true) + deployment_group = Repo.reload(deployment_group) - {:noreply, deployment} + {:noreply, deployment_group} end - def handle_cast(:trigger, deployment) do - trigger_update(deployment) - {:noreply, deployment} + def handle_cast(:trigger, deployment_group) do + trigger_update(deployment_group) + {:noreply, deployment_group} end - @decorate with_span("Deployments.Orchestrator.handle_info:deployments/update") - def handle_info(%Broadcast{event: "deployments/update"}, deployment) do - deployment = - deployment - |> Repo.reload() - |> Repo.preload([:firmware], force: true) + @decorate with_span("ManagedDeployments.Orchestrator.handle_info:deployments/update") + def handle_info(%Broadcast{event: "deployments/update"}, deployment_group) do + deployment_group = Repo.reload(deployment_group) - trigger_update(deployment) + trigger_update(deployment_group) - {:noreply, deployment} + {:noreply, deployment_group} end # Catch all for unknown broadcasts on a deployment - def handle_info(%Broadcast{topic: "deployment:" <> _}, deployment), do: {:noreply, deployment} + def handle_info(%Broadcast{topic: "deployment:" <> _}, deployment_group), + do: {:noreply, deployment_group} - def handle_info(:trigger, deployment) do - trigger_update(deployment) - {:noreply, deployment} + def handle_info(:trigger, deployment_group) do + trigger_update(deployment_group) + {:noreply, deployment_group} end end diff --git a/lib/nerves_hub/managed_deployments/supervisor.ex b/lib/nerves_hub/managed_deployments/supervisor.ex new file mode 100644 index 000000000..ee94eed48 --- /dev/null +++ b/lib/nerves_hub/managed_deployments/supervisor.ex @@ -0,0 +1,20 @@ +defmodule NervesHub.ManagedDeployments.Supervisor do + @moduledoc false + + use Supervisor + + def start_link(_) do + Supervisor.start_link(__MODULE__, [], name: __MODULE__) + end + + def init(_) do + children = [ + {Registry, keys: :unique, name: NervesHub.ManagedDeployments}, + NervesHub.ManagedDeployments.Monitor, + {DynamicSupervisor, + strategy: :one_for_one, name: NervesHub.ManagedDeploymentDynamicSupervisor} + ] + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/lib/nerves_hub/products/product.ex b/lib/nerves_hub/products/product.ex index df356a746..8f99f51e1 100644 --- a/lib/nerves_hub/products/product.ex +++ b/lib/nerves_hub/products/product.ex @@ -4,11 +4,11 @@ defmodule NervesHub.Products.Product do alias NervesHub.Accounts.Org alias NervesHub.Archives.Archive - alias NervesHub.Deployments.Deployment alias NervesHub.Devices.CACertificate alias NervesHub.Devices.Device alias NervesHub.Extensions.ProductExtensionsSetting alias NervesHub.Firmwares.Firmware + alias NervesHub.ManagedDeployments.DeploymentGroup alias NervesHub.Products.SharedSecretAuth alias NervesHub.Scripts.Script @@ -23,7 +23,7 @@ defmodule NervesHub.Products.Product do has_many(:jitp, CACertificate.JITP) has_many(:archives, Archive) has_many(:scripts, Script) - has_many(:deployments, Deployment) + has_many(:deployments, DeploymentGroup) has_many(:shared_secret_auths, SharedSecretAuth, preload_order: [desc: :deactivated_at, asc: :id] diff --git a/lib/nerves_hub/types.ex b/lib/nerves_hub/types.ex index 89f64d073..9f0fc5447 100644 --- a/lib/nerves_hub/types.ex +++ b/lib/nerves_hub/types.ex @@ -73,6 +73,8 @@ defmodule NervesHub.Types do [ "Elixir.NervesHub.Accounts.Org", "Elixir.NervesHub.Accounts.User", + "Elixir.NervesHub.ManagedDeployments.DeploymentGroup", + # TODO: (nshoes) remove after migrating audit_logs actor/resource type "Elixir.NervesHub.Deployments.Deployment", "Elixir.NervesHub.Devices.Device", "Elixir.NervesHub.Firmwares.Firmware", diff --git a/lib/nerves_hub/workers/firmware_delta_builder.ex b/lib/nerves_hub/workers/firmware_delta_builder.ex index 6572eda2c..e8cac4fed 100644 --- a/lib/nerves_hub/workers/firmware_delta_builder.ex +++ b/lib/nerves_hub/workers/firmware_delta_builder.ex @@ -7,7 +7,7 @@ defmodule NervesHub.Workers.FirmwareDeltaBuilder do states: [:available, :scheduled, :executing] ] - alias NervesHub.Deployments + alias NervesHub.ManagedDeployments alias NervesHub.Firmwares @impl Oban.Worker @@ -17,8 +17,8 @@ defmodule NervesHub.Workers.FirmwareDeltaBuilder do {:ok, _firmware_delta} = maybe_create_firmware_delta(source, target) - Enum.each(Deployments.get_deployments_by_firmware(target_id), fn deployment -> - Deployments.broadcast(deployment, "deployments/update") + Enum.each(ManagedDeployments.get_deployment_groups_by_firmware(target_id), fn deployment -> + ManagedDeployments.broadcast(deployment, "deployments/update") end) :ok diff --git a/lib/nerves_hub_web.ex b/lib/nerves_hub_web.ex index 6d3e17373..9f44e68ee 100644 --- a/lib/nerves_hub_web.ex +++ b/lib/nerves_hub_web.ex @@ -106,6 +106,10 @@ defmodule NervesHubWeb do def page_title(socket, page_title), do: assign(socket, :page_title, page_title) + @spec sidebar_tab( + Phoenix.Socket.t(), + :archives | :firmware | :deployments | :devices | :settings | :support_scripts + ) :: Phoenix.Socket.t() def sidebar_tab(socket, tab) do socket |> assign(:sidebar_tab, tab) diff --git a/lib/nerves_hub_web/channels/console_channel.ex b/lib/nerves_hub_web/channels/console_channel.ex index 4ae5dd1d7..21e669d0b 100644 --- a/lib/nerves_hub_web/channels/console_channel.ex +++ b/lib/nerves_hub_web/channels/console_channel.ex @@ -73,10 +73,9 @@ defmodule NervesHubWeb.ConsoleChannel do # now that the console is connected, push down the device's elixir, line by line device = socket.assigns.device device = Repo.preload(device, [:deployment]) - deployment = device.deployment - if deployment && deployment.connecting_code do - device.deployment.connecting_code + if device.deployment_group && device.deployment_group.connecting_code do + device.deployment_group.connecting_code |> String.graphemes() |> Enum.each(fn character -> push(socket, "dn", %{"data" => character}) diff --git a/lib/nerves_hub_web/channels/device_channel.ex b/lib/nerves_hub_web/channels/device_channel.ex index 004c416cf..e76fae53c 100644 --- a/lib/nerves_hub_web/channels/device_channel.ex +++ b/lib/nerves_hub_web/channels/device_channel.ex @@ -12,12 +12,12 @@ defmodule NervesHubWeb.DeviceChannel do alias NervesHub.Archives alias NervesHub.AuditLogs.DeviceTemplates - alias NervesHub.Deployments alias NervesHub.Devices alias NervesHub.Devices.Connections alias NervesHub.Devices.Device alias NervesHub.Firmwares alias NervesHub.Helpers.Logging + alias NervesHub.ManagedDeployments alias NervesHub.Repo alias Phoenix.Socket.Broadcast @@ -47,8 +47,8 @@ defmodule NervesHubWeb.DeviceChannel do def handle_info({:after_join, params}, %{assigns: %{device: device}} = socket) do device = device - |> Deployments.verify_deployment_membership() - |> Deployments.set_deployment() + |> ManagedDeployments.verify_deployment_group_membership() + |> ManagedDeployments.set_deployment_group() |> Map.put(:deployment, nil) maybe_send_public_keys(device, socket, params) @@ -60,6 +60,9 @@ defmodule NervesHubWeb.DeviceChannel do send(self(), :device_registration) + # Get device extension capabilities + push(socket, "extensions:get", %{}) + socket = socket |> assign(:device, device) @@ -133,7 +136,10 @@ defmodule NervesHubWeb.DeviceChannel do end @decorate with_span("Channels.DeviceChannel.handle_info:deployments/update") - def handle_info({"deployments/update", inflight_update}, %{assigns: %{device: device}} = socket) do + def handle_info( + {"deployments/update", inflight_update}, + %{assigns: %{device: device}} = socket + ) do payload = Devices.resolve_update(device) case payload.update_available do @@ -147,7 +153,7 @@ defmodule NervesHubWeb.DeviceChannel do Devices.update_started!( inflight_update, device, - payload.deployment, + payload.deployment_group, socket.assigns.reference_id ) @@ -176,7 +182,7 @@ defmodule NervesHubWeb.DeviceChannel do Devices.update_started!( inflight_update, device, - update_payload.deployment, + update_payload.deployment_group, socket.assigns.reference_id ) @@ -205,9 +211,12 @@ defmodule NervesHubWeb.DeviceChannel do {:noreply, update_device(socket, device)} end - @decorate with_span("Channels.DeviceChannel.handle_info:deployment-updated") + @decorate with_span("Channels.DeviceChannel.handle_info:deployment-group-updated") def handle_info( - %Broadcast{event: "devices/deployment-updated", payload: %{deployment_id: deployment_id}}, + %Broadcast{ + event: "devices/deployment-updated", + payload: %{deployment_id: deployment_id} + }, %{assigns: %{device: device}} = socket ) do device = %{device | deployment_id: deployment_id} @@ -500,10 +509,10 @@ defmodule NervesHubWeb.DeviceChannel do defp update_device(socket, device) do socket |> assign(:device, device) - |> update_deployment_subscription(device) + |> update_deployment_group_subscription(device) end - defp update_deployment_subscription(socket, device) do + defp update_deployment_group_subscription(socket, device) do deployment_channel = deployment_channel(device) if deployment_channel != socket.assigns.deployment_channel do @@ -530,7 +539,7 @@ defmodule NervesHubWeb.DeviceChannel do version_match = Version.match?(socket.assigns.device_api_version, ">= 2.0.0") if updates_enabled && version_match do - if archive = Archives.archive_for_deployment(device.deployment_id) do + if archive = Archives.archive_for_deployment_group(device.deployment_id) do if opts[:audit_log], do: DeviceTemplates.audit_device_archive_update_triggered( diff --git a/lib/nerves_hub_web/components/deployment_page/activity.ex b/lib/nerves_hub_web/components/deployment_group_page/activity.ex similarity index 92% rename from lib/nerves_hub_web/components/deployment_page/activity.ex rename to lib/nerves_hub_web/components/deployment_group_page/activity.ex index 472e7d9e1..73681a57a 100644 --- a/lib/nerves_hub_web/components/deployment_page/activity.ex +++ b/lib/nerves_hub_web/components/deployment_group_page/activity.ex @@ -1,4 +1,4 @@ -defmodule NervesHubWeb.Components.DeploymentPage.Activity do +defmodule NervesHubWeb.Components.DeploymentGroupPage.Activity do use NervesHubWeb, :live_component alias NervesHub.AuditLogs @@ -14,7 +14,7 @@ defmodule NervesHubWeb.Components.DeploymentPage.Activity do defp logs_and_pager_assigns(socket, page_number \\ 1, page_size \\ 25) do {logs, audit_pager} = - AuditLogs.logs_for_feed(socket.assigns.deployment, %{ + AuditLogs.logs_for_feed(socket.assigns.deployment_group, %{ page: page_number, page_size: page_size }) @@ -35,7 +35,7 @@ defmodule NervesHubWeb.Components.DeploymentPage.Activity do
Latest activity
- <.link href={~p"/org/#{@org.name}/#{@product.name}/deployments/#{@deployment.name}/audit_logs/download"}> + <.link href={~p"/org/#{@org.name}/#{@product.name}/deployment_groups/#{@deployment_group.name}/audit_logs/download"}> page_size, "page_number" => 1} url = - ~p"/org/#{socket.assigns.org.name}/#{socket.assigns.product.name}/devices/#{socket.assigns.deployment.name}/activity?#{params}" + ~p"/org/#{socket.assigns.org.name}/#{socket.assigns.product.name}/devices/#{socket.assigns.deployment_group.name}/activity?#{params}" socket |> logs_and_pager_assigns(1, String.to_integer(page_size)) @@ -103,7 +103,7 @@ defmodule NervesHubWeb.Components.DeploymentPage.Activity do params = %{"page_size" => socket.assigns.audit_pager.page_size, "page_number" => page_num} url = - ~p"/org/#{socket.assigns.org.name}/#{socket.assigns.product.name}/devices/#{socket.assigns.deployment.name}/activity?#{params}" + ~p"/org/#{socket.assigns.org.name}/#{socket.assigns.product.name}/devices/#{socket.assigns.deployment_group.name}/activity?#{params}" socket |> logs_and_pager_assigns( diff --git a/lib/nerves_hub_web/components/deployment_page/release_history.ex b/lib/nerves_hub_web/components/deployment_group_page/release_history.ex similarity index 82% rename from lib/nerves_hub_web/components/deployment_page/release_history.ex rename to lib/nerves_hub_web/components/deployment_group_page/release_history.ex index a89146f05..2f5eb084f 100644 --- a/lib/nerves_hub_web/components/deployment_page/release_history.ex +++ b/lib/nerves_hub_web/components/deployment_group_page/release_history.ex @@ -1,4 +1,4 @@ -defmodule NervesHubWeb.Components.DeploymentPage.ReleaseHistory do +defmodule NervesHubWeb.Components.DeploymentGroupPage.ReleaseHistory do use NervesHubWeb, :live_component def update(assigns, socket) do diff --git a/lib/nerves_hub_web/components/deployment_page/settings.ex b/lib/nerves_hub_web/components/deployment_group_page/settings.ex similarity index 87% rename from lib/nerves_hub_web/components/deployment_page/settings.ex rename to lib/nerves_hub_web/components/deployment_group_page/settings.ex index a2bc41475..2a77e31d7 100644 --- a/lib/nerves_hub_web/components/deployment_page/settings.ex +++ b/lib/nerves_hub_web/components/deployment_group_page/settings.ex @@ -1,25 +1,25 @@ -defmodule NervesHubWeb.Components.DeploymentPage.Settings do +defmodule NervesHubWeb.Components.DeploymentGroupPage.Settings do use NervesHubWeb, :live_component alias NervesHub.Archives alias NervesHub.AuditLogs - alias NervesHub.AuditLogs.DeploymentTemplates - alias NervesHub.Deployments - alias NervesHub.Deployments.Deployment + alias NervesHub.AuditLogs.DeploymentGroupTemplates alias NervesHub.Firmwares alias NervesHub.Firmwares.Firmware + alias NervesHub.ManagedDeployments + alias NervesHub.ManagedDeployments.DeploymentGroup @impl Phoenix.LiveComponent def update(assigns, socket) do - archives = Archives.all_by_product(assigns.deployment.product) - firmwares = Firmwares.get_firmwares_for_deployment(assigns.deployment) + archives = Archives.all_by_product(assigns.deployment_group.product) + firmwares = Firmwares.get_firmwares_for_deployment_group(assigns.deployment_group) - changeset = Deployment.changeset(assigns.deployment, %{}) + changeset = DeploymentGroup.changeset(assigns.deployment_group, %{}) socket |> assign(assigns) |> assign(:archives, archives) - |> assign(:firmware, assigns.deployment.firmware) + |> assign(:firmware, assigns.deployment_group.firmware) |> assign(:firmwares, firmwares) |> assign(:form, to_form(changeset)) |> assign( @@ -33,7 +33,7 @@ defmodule NervesHubWeb.Components.DeploymentPage.Settings do def render(assigns) do ~H"""
- <.form for={@form} class="w-full flex flex-col gap-4" phx-submit="update-deployment" phx-target={@myself}> + <.form for={@form} class="w-full flex flex-col gap-4" phx-submit="update-deployment-group" phx-target={@myself}>
General settings
@@ -83,7 +83,7 @@ defmodule NervesHubWeb.Components.DeploymentPage.Settings do

- These conditions are used for matching devices which don't have a configured deployment. + These conditions are used for matching devices which don't have a configured deployment group.

The matching is undertaken when a device connects to the platform. @@ -279,12 +279,12 @@ defmodule NervesHubWeb.Components.DeploymentPage.Settings do <.button type="link" style="danger" - phx-click="delete-deployment" + phx-click="delete-deployment-group" phx-target={@myself} aria-label="Delete" data-confirm={[ - "Are you sure you want to delete this deployment?", - @deployment.device_count > 0 && " All devices assigned to this deployment will be assigned a new deployment when they reconnect. ", + "Are you sure you want to delete this deployment group?", + @deployment_group.device_count > 0 && " All devices assigned to this deployment group will be assigned a new deployment when they reconnect. ", "This cannot be undone." ]} > @@ -298,54 +298,62 @@ defmodule NervesHubWeb.Components.DeploymentPage.Settings do end @impl Phoenix.LiveComponent - def handle_event("update-deployment", %{"deployment" => params}, socket) do - %{org_user: org_user, org: org, product: product, user: user, deployment: deployment} = + def handle_event("update-deployment-group", %{"deployment_group" => params}, socket) do + %{ + org_user: org_user, + org: org, + product: product, + user: user, + deployment_group: deployment_group + } = socket.assigns - authorized!(:"deployment:update", org_user) + authorized!(:"deployment_group:update", org_user) params = inject_conditions_map(params) - case Deployments.update_deployment(deployment, params) do + case ManagedDeployments.update_deployment_group(deployment_group, params) do {:ok, updated} -> # Use original deployment so changes will get # marked in audit log AuditLogs.audit!( user, updated, - "User #{user.name} updated deployment #{updated.name}" + "User #{user.name} updated deployment group #{updated.name}" ) # TODO: if we move away from slugs with deployment names we won't need # to use `push_navigate` here. socket - |> LiveToast.put_toast(:info, "Deployment updated") - |> push_navigate(to: ~p"/org/#{org.name}/#{product.name}/deployments/#{updated.name}") + |> LiveToast.put_toast(:info, "Deployment group updated") + |> push_navigate( + to: ~p"/org/#{org.name}/#{product.name}/deployment_groups/#{updated.name}" + ) |> noreply() {:error, changeset} -> socket |> send_toast( :error, - "An error occurred while updating the Deployment. Please check the form for errors." + "An error occurred while updating the deployment group. Please check the form for errors." ) |> assign(:form, to_form(changeset)) |> noreply() end end - def handle_event("delete-deployment", _params, socket) do - authorized!(:"deployment:delete", socket.assigns.org_user) + def handle_event("delete-deployment-group", _params, socket) do + authorized!(:"deployment_group:delete", socket.assigns.org_user) - %{deployment: deployment, org: org, product: product, user: user} = socket.assigns + %{deployment_group: deployment_group, org: org, product: product, user: user} = socket.assigns - {:ok, _} = Deployments.delete_deployment(deployment) + {:ok, _} = ManagedDeployments.delete_deployment_group(deployment_group) - DeploymentTemplates.audit_deployment_deleted(user, deployment) + DeploymentGroupTemplates.audit_deployment_deleted(user, deployment_group) socket - |> put_flash(:info, "Deployment successfully deleted") - |> push_navigate(to: ~p"/org/#{org.name}/#{product.name}/deployments") + |> put_flash(:info, "Deployment group successfully deleted") + |> push_navigate(to: ~p"/org/#{org.name}/#{product.name}/deployment_groups") |> noreply() end @@ -388,8 +396,8 @@ defmodule NervesHubWeb.Components.DeploymentPage.Settings do |> Enum.map(&[value: &1.id, key: firmware_display_name(&1)]) end - def archive_dropdown_options(acrhives) do - acrhives + def archive_dropdown_options(archives) do + archives |> Enum.sort_by( fn archive -> case Version.parse(archive.version) do @@ -412,16 +420,16 @@ defmodule NervesHubWeb.Components.DeploymentPage.Settings do defp help_message_for(field) do case field do :failure_threshold -> - "Maximum number of target devices from this deployment that can be in an unhealthy state before marking the deployment unhealthy." + "Maximum number of target devices from this deployment group that can be in an unhealthy state before marking the deployment group unhealthy." :failure_rate -> - "Maximum number of device install failures from this deployment within X seconds before being marked unhealthy." + "Maximum number of device install failures from this deployment group within X seconds before being marked unhealthy." :device_failure_rate -> - "Maximum number of device failures within X seconds a device can have for this deployment before being marked unhealthy." + "Maximum number of device failures within X seconds a device can have for this deployment group before being marked unhealthy." :device_failure_threshold -> - "Maximum number of install attempts and/or failures a device can have for this deployment before being marked unhealthy." + "Maximum number of install attempts and/or failures a device can have for this deployment group before being marked unhealthy." :penalty_timeout_minutes -> "Number of minutes a device is placed in the penalty box for reaching the failure rate and threshold." diff --git a/lib/nerves_hub_web/components/deployment_page/summary.ex b/lib/nerves_hub_web/components/deployment_group_page/summary.ex similarity index 72% rename from lib/nerves_hub_web/components/deployment_page/summary.ex rename to lib/nerves_hub_web/components/deployment_group_page/summary.ex index 80bb818ea..b1d6cbc6a 100644 --- a/lib/nerves_hub_web/components/deployment_page/summary.ex +++ b/lib/nerves_hub_web/components/deployment_group_page/summary.ex @@ -1,18 +1,18 @@ -defmodule NervesHubWeb.Components.DeploymentPage.Summary do +defmodule NervesHubWeb.Components.DeploymentGroupPage.Summary do use NervesHubWeb, :live_component alias NervesHub.Devices def update(%{update_inflight_info: true}, socket) do - deployment = socket.assigns.deployment + deployment_group = socket.assigns.deployment_group - inflight_updates = Devices.inflight_updates_for(deployment) + inflight_updates = Devices.inflight_updates_for(deployment_group) socket |> assign(:inflight_updates, inflight_updates) - |> assign(:up_to_date_count, Devices.up_to_date_count(deployment)) - |> assign(:waiting_for_update_count, Devices.waiting_for_update_count(deployment)) - |> assign(:updating_count, Devices.updating_count(deployment)) + |> assign(:up_to_date_count, Devices.up_to_date_count(deployment_group)) + |> assign(:waiting_for_update_count, Devices.waiting_for_update_count(deployment_group)) + |> assign(:updating_count, Devices.updating_count(deployment_group)) |> ok() end @@ -31,12 +31,12 @@ defmodule NervesHubWeb.Components.DeploymentPage.Summary do

0} class="w-full h-24 box-content flex items-center justify-center rounded border border-zinc-700 bg-zinc-900">
-
+
-
{deployment_percentage(@up_to_date_count, @deployment)}% of devices updated
+
{deployment_group_percentage(@up_to_date_count, @deployment_group)}% of devices updated
{@updating_count} device(s) updating - {@waiting_for_update_count} device(s) waiting
@@ -52,21 +52,21 @@ defmodule NervesHubWeb.Components.DeploymentPage.Summary do Firmware: <.link - navigate={~p"/org/#{@org.name}/#{@product.name}/firmware/#{@deployment.firmware.uuid}"} + navigate={~p"/org/#{@org.name}/#{@product.name}/firmware/#{@deployment_group.firmware.uuid}"} class="flex items-center gap-1 pl-1.5 pr-2.5 py-0.5 border border-zinc-700 rounded-full bg-zinc-800" > - {@deployment.firmware.version} ({String.slice(@deployment.firmware.uuid, 0..7)}) + {@deployment_group.firmware.version} ({String.slice(@deployment_group.firmware.uuid, 0..7)})
Archive: <.link - :if={@deployment.archive} - navigate={~p"/org/#{@org.name}/#{@product.name}/archives/#{@deployment.archive.uuid}"} + :if={@deployment_group.archive} + navigate={~p"/org/#{@org.name}/#{@product.name}/archives/#{@deployment_group.archive.uuid}"} class="flex items-center gap-1 pl-1.5 pr-2.5 py-0.5 border border-zinc-700 rounded-full bg-zinc-800" > - {@deployment.archive.version} ({String.slice(@deployment.archive.uuid, 0..7)}) + {@deployment_group.archive.version} ({String.slice(@deployment_group.archive.uuid, 0..7)}) No archive configured
@@ -101,46 +101,46 @@ defmodule NervesHubWeb.Components.DeploymentPage.Summary do
Concurrent device updates: - {@deployment.concurrent_updates} + {@deployment_group.concurrent_updates}
Minutes before expiring updates: - {@deployment.inflight_update_expiration_minutes} + {@deployment_group.inflight_update_expiration_minutes}
Failure rate: - {@deployment.failure_rate_amount} failures per {@deployment.failure_rate_seconds} seconds + {@deployment_group.failure_rate_amount} failures per {@deployment_group.failure_rate_seconds} seconds
Failure threshold: - {@deployment.failure_threshold} + {@deployment_group.failure_threshold}
Device failure rate: - {@deployment.device_failure_rate_amount} failures per {@deployment.device_failure_rate_seconds} seconds + {@deployment_group.device_failure_rate_amount} failures per {@deployment_group.device_failure_rate_seconds} seconds
Device failure threshold: - {@deployment.device_failure_threshold} + {@deployment_group.device_failure_threshold}
Device penalty box timeout: - {@deployment.penalty_timeout_minutes} + {@deployment_group.penalty_timeout_minutes}
-
+
Code sent on device connection: No code configured
-
+
Code sent on device connection: -
-    {@deployment.connecting_code}
+              
+    {@deployment_group.connecting_code}
               
@@ -151,14 +151,14 @@ defmodule NervesHubWeb.Components.DeploymentPage.Summary do
Tag selection: - No tags configured - - {tag} + No tags configured + + {tag}
Version requirement: - {@deployment.conditions["version"]} + {@deployment_group.conditions["version"]}
@@ -167,7 +167,7 @@ defmodule NervesHubWeb.Components.DeploymentPage.Summary do """ end - defp deployment_percentage(up_to_date_count, deployment) do - floor(up_to_date_count / deployment.device_count * 100) + defp deployment_group_percentage(up_to_date_count, deployment_group) do + floor(up_to_date_count / deployment_group.device_count * 100) end end diff --git a/lib/nerves_hub_web/components/device_page/details.ex b/lib/nerves_hub_web/components/device_page/details.ex index afe05c193..2b5a5568f 100644 --- a/lib/nerves_hub_web/components/device_page/details.ex +++ b/lib/nerves_hub_web/components/device_page/details.ex @@ -4,13 +4,13 @@ defmodule NervesHubWeb.Components.DevicePage.Details do require Logger alias NervesHub.AuditLogs.DeviceTemplates - alias NervesHub.Deployments alias NervesHub.Devices alias NervesHub.Devices.Alarms alias NervesHub.Devices.Device alias NervesHub.Devices.Metrics alias NervesHub.Devices.UpdatePayload alias NervesHub.Firmwares + alias NervesHub.ManagedDeployments alias NervesHub.Scripts alias NervesHub.Repo @@ -35,7 +35,7 @@ defmodule NervesHubWeb.Components.DevicePage.Details do def update(assigns, socket) do socket |> assign(assigns) - |> assign(:device, Repo.preload(assigns.device, :deployment)) + |> assign(:device, Repo.preload(assigns.device, :deployment_group)) |> assign(:device, Repo.preload(assigns.device, :latest_health)) |> assign_support_scripts() |> assign(:firmwares, Firmwares.get_firmware_for_device(assigns.device)) @@ -44,7 +44,7 @@ defmodule NervesHubWeb.Components.DevicePage.Details do |> assign(:alarms, Alarms.get_current_alarms_for_device(assigns.device)) |> assign(:extension_overrides, extension_overrides(assigns.device, assigns.product)) |> assign_metadata() - |> assign_deployments() + |> assign_deployment_groups() |> ok() end @@ -66,11 +66,14 @@ defmodule NervesHubWeb.Components.DevicePage.Details do assign(socket, :support_scripts, scripts) end - defp assign_deployments(%{assigns: %{device: %{status: :provisioned} = device}} = socket), - do: assign(socket, deployments: Deployments.eligible_deployments(device)) + defp assign_deployment_groups(%{assigns: %{device: %{status: :provisioned} = device}} = socket), + do: assign(socket, deployment_groups: ManagedDeployments.eligible_deployment_groups(device)) - defp assign_deployments(%{assigns: %{product: product}} = socket), - do: assign(socket, deployments: Deployments.get_deployments_by_product(product)) + defp assign_deployment_groups(%{assigns: %{product: product}} = socket), + do: + assign(socket, + deployment_groups: ManagedDeployments.get_deployment_groups_by_product(product) + ) def render(assigns) do ~H""" @@ -275,27 +278,27 @@ defmodule NervesHubWeb.Components.DevicePage.Details do
- Assigned deployment: - No assigned deployment + Assigned deployment group: + No assigned deployment group <.link - :if={@device.deployment} - navigate={~p"/org/#{@org.name}/#{@product.name}/deployments/#{@device.deployment.name}"} + :if={@device.deployment_group} + navigate={~p"/org/#{@org.name}/#{@product.name}/deployment_groups/#{@device.deployment_group.name}"} class="flex items-center gap-1 pl-1.5 pr-2.5 py-0.5 border border-zinc-700 rounded-full bg-zinc-800" > - {@device.deployment.name} + {@device.deployment_group.name}
- Please note: The device will be removed from the deployment upon connection if the aarch and platform don't match. + Please note: The device will be removed from the deployment group upon connection if the arch and platform don't match.
-
-
+
+
<.button type="submit" aria-label="Add to deployment" data-confirm="Are you sure you want to add the device to the deployment?"> - Add to deployment + Add to deployment group
@@ -343,7 +348,7 @@ defmodule NervesHubWeb.Components.DevicePage.Details do
Update available - An update is available in the assigned deployment. + An update is available in the assigned deployment group.
<.button @@ -495,20 +500,20 @@ defmodule NervesHubWeb.Components.DevicePage.Details do |> noreply() end - def handle_event("set-deployment", %{"deployment_id" => deployment_id}, socket) do - %{user: user, device: device, deployments: deployments} = socket.assigns + def handle_event("set-deployment-group", %{"deployment_id" => deployment_id}, socket) do + %{user: user, device: device, deployments_groups: deployment_groups} = socket.assigns - authorized!(:"device:set-deployment", socket.assigns.org_user) + authorized!(:"device:set-deployment-group", socket.assigns.org_user) - deployment = Enum.find(deployments, &(&1.id == String.to_integer(deployment_id))) - device = Devices.update_deployment(device, deployment) - _ = DeviceTemplates.audit_device_deployment_update(user, device, deployment) + deployment = Enum.find(deployment_groups, &(&1.id == String.to_integer(deployment_id))) + device = Devices.update_deployment_group(device, deployment) + _ = DeviceTemplates.audit_device_deployment_group_update(user, device, deployment) send(self(), :reload_device) socket |> assign(:device, device) - |> send_toast(:info, "Device successfully added to Deployment.") + |> send_toast(:info, "Device successfully added to Deployment Group.") |> noreply() end @@ -517,11 +522,11 @@ defmodule NervesHubWeb.Components.DevicePage.Details do %{device: device, user: user} = socket.assigns - deployment = NervesHub.Repo.preload(device.deployment, :firmware) + deployment_group = NervesHub.Repo.preload(device.deployment_group, :firmware) - case Devices.told_to_update(device, deployment) do + case Devices.told_to_update(device, deployment_group) do {:ok, _inflight_update} -> - DeviceTemplates.audit_pushed_available_update(user, device, deployment) + DeviceTemplates.audit_pushed_available_update(user, device, deployment_group) socket |> send_toast(:info, "Pushing available firmware update.") @@ -573,14 +578,14 @@ defmodule NervesHubWeb.Components.DevicePage.Details do |> noreply() end - def handle_event("remove-from-deployment", _, %{assigns: %{device: device}} = socket) do - device = Devices.clear_deployment(device) + def handle_event("remove-from-deployment-group", _, %{assigns: %{device: device}} = socket) do + device = Devices.clear_deployment_group(device) send(self(), :reload_device) socket |> assign(:device, device) - |> send_toast(:info, "Device successfully removed from the deployment") + |> send_toast(:info, "Device successfully removed from the deployment group") |> noreply() end diff --git a/lib/nerves_hub_web/components/navigation.ex b/lib/nerves_hub_web/components/navigation.ex index 4e629739c..e20243ef4 100644 --- a/lib/nerves_hub_web/components/navigation.ex +++ b/lib/nerves_hub_web/components/navigation.ex @@ -23,7 +23,7 @@ defmodule NervesHubWeb.Components.Navigation do stroke-linejoin="round" /> - <.nav_link label="Deployments" path={~p"/org/#{@org.name}/#{@product.name}/deployments"} selected={:deployments == @selected_tab}> + <.nav_link label="Deployments" path={~p"/org/#{@org.name}/#{@product.name}/deployment_groups"} selected={:deployments == @selected_tab}> put_status(:created) |> put_resp_header( "location", - Routes.api_deployment_path(conn, :show, org.name, product.name, deployment.name) + Routes.api_deployment_group_path( + conn, + :show, + org.name, + product.name, + deployment_group.name + ) ) - |> render("show.json", deployment: %{deployment | firmware: firmware}) + |> render("show.json", deployment_group: %{deployment_group | firmware: firmware}) end end end def show(%{assigns: %{org: _org, product: product}} = conn, %{"name" => name}) do - with {:ok, deployment} <- Deployments.get_deployment_by_name(product, name) do - render(conn, "show.json", deployment: deployment) + with {:ok, deployment_group} <- ManagedDeployments.get_deployment_group_by_name(product, name) do + render(conn, "show.json", deployment_group: deployment_group) end end def update(%{assigns: %{product: product, user: user}} = conn, %{ "name" => name, - "deployment" => deployment_params + "deployment" => deployment_group_params }) do - with {:ok, deployment} <- Deployments.get_deployment_by_name(product, name), - {:ok, deployment_params} <- update_params(product, deployment_params), - deployment_params <- whitelist(deployment_params, @whitelist_fields), - {:ok, %Deployment{} = updated_deployment} <- - Deployments.update_deployment(deployment, deployment_params) do - DeploymentTemplates.audit_deployment_updated(user, deployment) - - render(conn, "show.json", deployment: updated_deployment) + with {:ok, deployment_group} <- + ManagedDeployments.get_deployment_group_by_name(product, name), + {:ok, deployment_group_params} <- update_params(product, deployment_group_params), + deployment_group_params <- whitelist(deployment_group_params, @whitelist_fields), + {:ok, %DeploymentGroup{} = updated_deployment_group} <- + ManagedDeployments.update_deployment_group( + deployment_group, + deployment_group_params + ) do + DeploymentGroupTemplates.audit_deployment_updated(user, deployment_group) + + render(conn, "show.json", deployment_group: updated_deployment_group) end end def delete(%{assigns: %{product: product}} = conn, %{"name" => name}) do - with {:ok, deployment} <- Deployments.get_deployment_by_name(product, name), - {:ok, _deployment} <- Deployments.delete_deployment(deployment) do + with {:ok, deployment_group} <- + ManagedDeployments.get_deployment_group_by_name(product, name), + {:ok, _deployment_group} <- ManagedDeployments.delete_deployment_group(deployment_group) do send_resp(conn, :no_content, "") end end diff --git a/lib/nerves_hub_web/controllers/api/device_controller.ex b/lib/nerves_hub_web/controllers/api/device_controller.ex index 01339bd76..0071db598 100644 --- a/lib/nerves_hub_web/controllers/api/device_controller.ex +++ b/lib/nerves_hub_web/controllers/api/device_controller.ex @@ -40,7 +40,7 @@ defmodule NervesHubWeb.API.DeviceController do |> Map.put("product_id", product.id) with {:ok, device} <- Devices.create_device(params) do - device = Repo.preload(device, [:org, :product, deployment: [:firmware]]) + device = Repo.preload(device, [:org, :product, deployment_group: [:firmware]]) conn |> put_status(:created) @@ -85,7 +85,8 @@ defmodule NervesHubWeb.API.DeviceController do def update(%{assigns: %{org: org}} = conn, %{"identifier" => identifier} = params) do with {:ok, device} <- Devices.get_device_by_identifier(org, identifier), {:ok, updated_device} <- Devices.update_device(device, params) do - updated_device = Repo.preload(updated_device, [:org, :product, deployment: [:firmware]]) + updated_device = + Repo.preload(updated_device, [:org, :product, deployment_group: [:firmware]]) conn |> put_status(201) @@ -99,7 +100,7 @@ defmodule NervesHubWeb.API.DeviceController do {:ok, %DeviceCertificate{device_id: device_id}} <- Devices.get_device_certificate_by_x509(cert), {:ok, device} <- Devices.get_device_by_org(org, device_id) do - device = Repo.preload(device, [:org, :product, deployment: [:firmware]]) + device = Repo.preload(device, [:org, :product, deployment_group: [:firmware]]) conn |> put_status(200) diff --git a/lib/nerves_hub_web/controllers/api/fallback_controller.ex b/lib/nerves_hub_web/controllers/api/fallback_controller.ex index e3caa123f..a6945f4e0 100644 --- a/lib/nerves_hub_web/controllers/api/fallback_controller.ex +++ b/lib/nerves_hub_web/controllers/api/fallback_controller.ex @@ -51,6 +51,6 @@ defmodule NervesHubWeb.API.FallbackController do end defp conflict_error?(error) do - error in [:deployments, :firmwares, :devices] + error in [:deployment_groups, :firmwares, :devices] end end diff --git a/lib/nerves_hub_web/controllers/deployment_controller.ex b/lib/nerves_hub_web/controllers/deployment_group_controller.ex similarity index 54% rename from lib/nerves_hub_web/controllers/deployment_controller.ex rename to lib/nerves_hub_web/controllers/deployment_group_controller.ex index 22a166637..495158865 100644 --- a/lib/nerves_hub_web/controllers/deployment_controller.ex +++ b/lib/nerves_hub_web/controllers/deployment_group_controller.ex @@ -1,8 +1,8 @@ -defmodule NervesHubWeb.DeploymentController do +defmodule NervesHubWeb.DeploymentGroupController do use NervesHubWeb, :controller alias NervesHub.AuditLogs - alias NervesHub.Deployments + alias NervesHub.ManagedDeployments plug(:validate_role, org: :view) @@ -10,18 +10,21 @@ defmodule NervesHubWeb.DeploymentController do %{assigns: %{org: org, product: product}} = conn, %{"name" => deployment_name} ) do - {:ok, deployment} = Deployments.get_deployment_by_name(product, deployment_name) + {:ok, deployment_group} = + ManagedDeployments.get_deployment_group_by_name(product, deployment_name) - case AuditLogs.logs_for(deployment) do + case AuditLogs.logs_for(deployment_group) do [] -> conn - |> put_flash(:error, "No audit logs exist for this deployment.") - |> redirect(to: ~p"/org/#{org.name}/#{product.name}/deployments") + |> put_flash(:error, "No audit logs exist for this deployment group.") + |> redirect(to: ~p"/org/#{org.name}/#{product.name}/deployment_groups") audit_logs -> audit_logs = AuditLogs.format_for_csv(audit_logs) - send_download(conn, {:binary, audit_logs}, filename: "#{deployment.name}-audit-logs.csv") + send_download(conn, {:binary, audit_logs}, + filename: "#{deployment_group.name}-audit-logs.csv" + ) end end end diff --git a/lib/nerves_hub_web/helpers/authorization.ex b/lib/nerves_hub_web/helpers/authorization.ex index 3f171b839..ba3755319 100644 --- a/lib/nerves_hub_web/helpers/authorization.ex +++ b/lib/nerves_hub_web/helpers/authorization.ex @@ -30,7 +30,10 @@ defmodule NervesHubWeb.Helpers.Authorization do def authorized?(:"device:create", %OrgUser{role: role}), do: role_check(:manage, role) def authorized?(:"device:update", %OrgUser{role: role}), do: role_check(:manage, role) - def authorized?(:"device:set-deployment", %OrgUser{role: role}), do: role_check(:manage, role) + + def authorized?(:"device:set-deployment-group", %OrgUser{role: role}), + do: role_check(:manage, role) + def authorized?(:"device:push-update", %OrgUser{role: role}), do: role_check(:manage, role) def authorized?(:"device:toggle-updates", %OrgUser{role: role}), do: role_check(:manage, role) def authorized?(:"device:clear-penalty-box", %OrgUser{role: ur}), do: role_check(:manage, ur) @@ -47,10 +50,10 @@ defmodule NervesHubWeb.Helpers.Authorization do def authorized?(:"archive:upload", %OrgUser{role: role}), do: role_check(:manage, role) def authorized?(:"archive:delete", %OrgUser{role: role}), do: role_check(:manage, role) - def authorized?(:"deployment:create", %OrgUser{role: role}), do: role_check(:manage, role) - def authorized?(:"deployment:update", %OrgUser{role: role}), do: role_check(:manage, role) - def authorized?(:"deployment:toggle", %OrgUser{role: role}), do: role_check(:manage, role) - def authorized?(:"deployment:delete", %OrgUser{role: role}), do: role_check(:manage, role) + def authorized?(:"deployment_group:create", %OrgUser{role: role}), do: role_check(:manage, role) + def authorized?(:"deployment_group:update", %OrgUser{role: role}), do: role_check(:manage, role) + def authorized?(:"deployment_group:toggle", %OrgUser{role: role}), do: role_check(:manage, role) + def authorized?(:"deployment_group:delete", %OrgUser{role: role}), do: role_check(:manage, role) def authorized?(:"support_script:create", %OrgUser{role: role}), do: role_check(:manage, role) def authorized?(:"support_script:update", %OrgUser{role: role}), do: role_check(:manage, role) diff --git a/lib/nerves_hub_web/live/dashboard/index.ex b/lib/nerves_hub_web/live/dashboard/index.ex index a05f37951..3e39292c4 100644 --- a/lib/nerves_hub_web/live/dashboard/index.ex +++ b/lib/nerves_hub_web/live/dashboard/index.ex @@ -1,8 +1,8 @@ defmodule NervesHubWeb.Live.Dashboard.Index do use NervesHubWeb, :updated_live_view - alias NervesHub.Deployments alias NervesHub.Devices + alias NervesHub.ManagedDeployments alias Phoenix.Socket.Broadcast @@ -91,9 +91,9 @@ defmodule NervesHubWeb.Live.Dashboard.Index do devices = Devices.get_minimal_device_location_by_product(product) latest_firmwares = - Deployments.get_deployments_by_product(product) - |> Enum.reduce(%{}, fn deployment, acc -> - Map.put(acc, deployment.firmware.uuid, deployment.firmware.platform) + ManagedDeployments.get_deployment_groups_by_product(product) + |> Enum.reduce(%{}, fn deployment_group, acc -> + Map.put(acc, deployment_group.firmware.uuid, deployment_group.firmware.platform) end) map_markers = diff --git a/lib/nerves_hub_web/live/deployments/edit.ex b/lib/nerves_hub_web/live/deployment_groups/edit.ex similarity index 65% rename from lib/nerves_hub_web/live/deployments/edit.ex rename to lib/nerves_hub_web/live/deployment_groups/edit.ex index e54664893..27e8e4804 100644 --- a/lib/nerves_hub_web/live/deployments/edit.ex +++ b/lib/nerves_hub_web/live/deployment_groups/edit.ex @@ -1,56 +1,64 @@ -defmodule NervesHubWeb.Live.Deployments.Edit do +defmodule NervesHubWeb.Live.DeploymentGroups.Edit do use NervesHubWeb, :updated_live_view alias NervesHub.Archives - alias NervesHub.AuditLogs.DeploymentTemplates - alias NervesHub.Deployments - alias NervesHub.Deployments.Deployment + alias NervesHub.AuditLogs.DeploymentGroupTemplates alias NervesHub.Firmwares alias NervesHub.Firmwares.Firmware + alias NervesHub.ManagedDeployments + alias NervesHub.ManagedDeployments.DeploymentGroup @impl Phoenix.LiveView def mount(params, _session, socket) do %{"name" => name} = params %{product: product} = socket.assigns - deployment = Deployments.get_by_product_and_name!(product, name) + deployment_group = ManagedDeployments.get_by_product_and_name!(product, name) - current_device_count = Deployments.get_device_count(deployment) + current_device_count = ManagedDeployments.get_device_count(deployment_group) - archives = Archives.all_by_product(deployment.product) - firmwares = Firmwares.get_firmwares_for_deployment(deployment) + archives = Archives.all_by_product(deployment_group.product) + firmwares = Firmwares.get_firmwares_for_deployment_group(deployment_group) - changeset = Deployment.changeset(deployment, %{}) |> tags_to_string() + changeset = DeploymentGroup.changeset(deployment_group, %{}) |> tags_to_string() socket |> assign(:archives, archives) |> sidebar_tab(:deployments) - |> assign(:deployment, deployment) + |> assign(:deployment_group, deployment_group) |> assign(:current_device_count, current_device_count) - |> assign(:firmware, deployment.firmware) + |> assign(:firmware, deployment_group.firmware) |> assign(:firmwares, firmwares) |> assign(:form, to_form(changeset)) |> ok() end @impl Phoenix.LiveView - def handle_event("update-deployment", %{"deployment" => params}, socket) do - %{org_user: org_user, org: org, product: product, user: user, deployment: deployment} = + def handle_event("update-deployment-group", %{"deployment_group" => params}, socket) do + %{ + org_user: org_user, + org: org, + product: product, + user: user, + deployment_group: deployment_group + } = socket.assigns - authorized!(:"deployment:update", org_user) + authorized!(:"deployment_group:update", org_user) params = inject_conditions_map(params) - case Deployments.update_deployment(deployment, params) do + case ManagedDeployments.update_deployment_group(deployment_group, params) do {:ok, updated} -> # Use original deployment so changes will get # marked in audit log - DeploymentTemplates.audit_deployment_updated(user, updated) + DeploymentGroupTemplates.audit_deployment_updated(user, updated) socket - |> put_flash(:info, "Deployment updated") - |> push_navigate(to: ~p"/org/#{org.name}/#{product.name}/deployments/#{updated.name}") + |> put_flash(:info, "Deployment Group updated") + |> push_navigate( + to: ~p"/org/#{org.name}/#{product.name}/deployment_groups/#{updated.name}" + ) |> noreply() {:error, changeset} -> @@ -97,8 +105,8 @@ defmodule NervesHubWeb.Live.Deployments.Edit do |> Enum.map(&[value: &1.id, key: firmware_display_name(&1)]) end - def archive_dropdown_options(acrhives) do - acrhives + def archive_dropdown_options(archives) do + archives |> Enum.sort_by( fn archive -> case Version.parse(archive.version) do @@ -121,16 +129,16 @@ defmodule NervesHubWeb.Live.Deployments.Edit do defp help_message_for(field) do case field do :failure_threshold -> - "Maximum number of target devices from this deployment that can be in an unhealthy state before marking the deployment unhealthy" + "Maximum number of target devices from this deployment group that can be in an unhealthy state before deployment group is marked unhealthy" :failure_rate -> - "Maximum number of device install failures from this deployment within X seconds before being marked unhealthy" + "Maximum number of device install failures from this deployment group within X seconds before being marked unhealthy" :device_failure_rate -> - "Maximum number of device failures within X seconds a device can have for this deployment before being marked unhealthy" + "Maximum number of device failures within X seconds a device can have for this deployment group before being marked unhealthy" :device_failure_threshold -> - "Maximum number of install attempts and/or failures a device can have for this deployment before being marked unhealthy" + "Maximum number of install attempts and/or failures a device can have for this deployment group before being marked unhealthy" :penalty_timeout_minutes -> "Number of minutes a device is placed in the penalty box for reaching the failure rate and threshold" diff --git a/lib/nerves_hub_web/live/deployments/edit.html.heex b/lib/nerves_hub_web/live/deployment_groups/edit.html.heex similarity index 95% rename from lib/nerves_hub_web/live/deployments/edit.html.heex rename to lib/nerves_hub_web/live/deployment_groups/edit.html.heex index 209fd81d6..344b98ffc 100644 --- a/lib/nerves_hub_web/live/deployments/edit.html.heex +++ b/lib/nerves_hub_web/live/deployment_groups/edit.html.heex @@ -1,7 +1,7 @@ -<.link navigate={~p"/org/#{@org.name}/#{@product.name}/deployments/#{@deployment.name}"} class="back-link"> - Back to Deployment +<.link navigate={~p"/org/#{@org.name}/#{@product.name}/deployment_groups/#{@deployment_group.name}"} class="back-link"> + Back to Deployment Group -

Edit Deployment

+

Edit Deployment Group

Firmware version details
@@ -34,11 +34,11 @@
-<.form :let={f} for={@form} phx-submit="update-deployment"> +<.form :let={f} for={@form} phx-submit="update-deployment-group"> {hidden_input(f, :firmware_id, value: @firmware.id)}
- + {text_input(f, :name, class: "form-control", id: "name_input")}
{error_tag(f, :name)}
@@ -66,7 +66,7 @@

Device matching conditions

-

These conditions are used for matching devices which don't have a configured deployment. The matching is undertaken when a device connects to the platform.

+

These conditions are used for matching devices which don't have a configured deployment group. The matching is undertaken when a device connects to the platform.