diff --git a/lib/uplink/packages/deployment/manager.ex b/lib/uplink/packages/deployment/manager.ex index b838090..630feb2 100644 --- a/lib/uplink/packages/deployment/manager.ex +++ b/lib/uplink/packages/deployment/manager.ex @@ -38,9 +38,10 @@ defmodule Uplink.Packages.Deployment.Manager do @spec get_or_create(%App{}, map) :: {:ok, %Deployment{}} def get_or_create(%App{id: app_id} = app, params) do hash = Map.get(params, :hash) || Map.get(params, "hash") + channel = Map.get(params, :channel) || Map.get(params, "channel") Deployment - |> Repo.get_by(app_id: app_id, hash: hash) + |> Repo.get_by(app_id: app_id, hash: hash, channel: channel) |> case do nil -> create(app, params) @@ -60,17 +61,18 @@ defmodule Uplink.Packages.Deployment.Manager do {:error, %Ecto.Changeset{ - changes: %{hash: hash}, + changes: %{hash: hash, channel: channel}, errors: [ hash: {_, [ constraint: :unique, - constraint_name: "deployments_app_id_hash_index" + constraint_name: _ ]} ] }} -> - {:ok, Repo.get_by!(Deployment, app_id: app_id, hash: hash)} + {:ok, + Repo.get_by!(Deployment, app_id: app_id, hash: hash, channel: channel)} error -> error diff --git a/priv/repo/migrations/20240813072207_change_deployments_hash_unique_index.exs b/priv/repo/migrations/20240813072207_change_deployments_hash_unique_index.exs new file mode 100644 index 0000000..b088655 --- /dev/null +++ b/priv/repo/migrations/20240813072207_change_deployments_hash_unique_index.exs @@ -0,0 +1,8 @@ +defmodule Uplink.Repo.Migrations.ChangeDeploymentsHashUniqueIndex do + use Ecto.Migration + + def change do + drop index(:deployments, [:app_id, :hash], unique: true) + create index(:deployments, [:app_id, :hash, :channel], unique: true) + end +end diff --git a/test/uplink/packages/deployment/router_test.exs b/test/uplink/packages/deployment/router_test.exs index eb59089..38f7905 100644 --- a/test/uplink/packages/deployment/router_test.exs +++ b/test/uplink/packages/deployment/router_test.exs @@ -164,6 +164,73 @@ defmodule Uplink.Packages.Deployment.RouterTest do end end + describe "deployment with same hash different channel" do + setup do + signature = + :crypto.mac(:hmac, :sha256, Uplink.Secret.get(), @valid_body) + |> Base.encode16() + |> String.downcase() + + conn(:post, "/", @valid_body) + |> put_req_header("x-uplink-signature-256", "sha256=#{signature}") + |> put_req_header("content-type", "application/json") + |> Router.call(@opts) + + deployment = + Repo.get_by(Uplink.Packages.Deployment, + hash: "some-hash", + channel: "develop" + ) + + {:ok, signature: signature, deployment: deployment} + end + + test "return 201 for deployment with same hash different channel", %{ + deployment: existing_deployment + } do + %{ + "deployment" => + %{"metadata" => %{"channel" => channel} = metadata} = deployment + } = body = Jason.decode!(@valid_body) + + channel = Map.put(channel, "slug", "master") + + metadata = Map.put(metadata, "channel", channel) + + deployment = + deployment + |> Map.put("channel", "master") + |> Map.put("metadata", metadata) + + body = + body + |> Map.put("deployment", deployment) + |> Jason.encode!() + + new_signature = + :crypto.mac(:hmac, :sha256, Uplink.Secret.get(), body) + |> Base.encode16() + |> String.downcase() + + conn = + conn(:post, "/", body) + |> put_req_header("x-uplink-signature-256", "sha256=#{new_signature}") + |> put_req_header("content-type", "application/json") + |> Router.call(@opts) + + assert conn.status == 201 + + assert %{"data" => %{"id" => deployment_id}} = + Jason.decode!(conn.resp_body) + + deployment = Uplink.Repo.get(Uplink.Packages.Deployment, deployment_id) + + assert deployment.id != existing_deployment.id + + assert deployment.current_state == "preparing" + end + end + describe "repeated deployment push with different installation_id" do setup do original_signature =