From 260b3dfcdbc4073d6756446c9af0eae6d0ad8d4c Mon Sep 17 00:00:00 2001 From: Keith Brings Date: Sat, 1 Oct 2022 04:49:47 +0700 Subject: [PATCH 1/4] Adding ability to override project/account used when making calls for use cases where you need to pull or move data between different projects or accounts/tables with different credentials/security settings. --- .gitignore | 2 ++ README.md | 34 +++++++++++++++++++++++++ lib/diplomat.ex | 17 +++++++++++++ lib/diplomat/client.ex | 16 +++++++++--- lib/diplomat/key.ex | 2 +- lib/diplomat/query.ex | 4 +-- test/diplomat/account_override_test.exs | 27 ++++++++++++++++++++ 7 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 test/diplomat/account_override_test.exs diff --git a/.gitignore b/.gitignore index 7b1b629..bdd183b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ config/*credentials.json .#* /doc/ .elixir_ls/ +.idea/ +*.iml diff --git a/README.md b/README.md index 4ff5d84..b22a82a 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,37 @@ Diplomat.Query.new( %{name: "20,000 Leagues Under The Sea"} ) |> Diplomat.Query.execute ``` + + +#### Use multiple accounts with Diplomat +Configure Goth with additional accounts. +```elixir +{:ok, alternative_account} = Jason.decode(File.read!("priv/goth/alternative-account.json")) +Goth.Config.add_config(alternative_account) +``` + +Require Diplomat and use the with_account option to set current (and only current) process to use alternative process within block. + +The account name will be the client_email value from the additional Goth configuration you added by default. + +```elixir +# copy data from prod to stage environment + +# 1. Fetch data from production account +prod_data = Diplomat.with_account(alternative_account["client_email"]) do + Diplomat.Query.new( + "select * from `Book` where name = @name", + %{name: "20,000 Leagues Under The Sea"} + ) |> Diplomat.Query.execute +end + +# 2. Write to stage/dev account (default environment) +Enum.map(prod_data, fn(entity) -> + Diplomat.Entity.upsert(entity) +end) +``` + + + + + diff --git a/lib/diplomat.ex b/lib/diplomat.ex index a575749..572da5e 100644 --- a/lib/diplomat.ex +++ b/lib/diplomat.ex @@ -9,4 +9,21 @@ defmodule Diplomat do defmodule Proto do use Protobuf, from: Path.expand("datastore_v1beta3.proto", __DIR__), doc: false end + + defmacro with_account(account, [do: block]) do + account = Macro.expand(account, __ENV__) + quote do + Process.put(:diplomat_account_queue, [unquote(account)] ++ Process.get(:diplomat_account_queue, [])) + Process.put(:diplomat_account, unquote(account)) + diplomat__response = (unquote(block)) + diplomat__account_queue = case Process.get(:diplomat_account_queue, []) do + [_|t] -> t + _ -> [] + end + Process.put(:diplomat_account_queue, diplomat__account_queue) + Process.put(:diplomat_account, List.first(diplomat__account_queue)) + diplomat__response + end + end + end diff --git a/lib/diplomat/client.ex b/lib/diplomat/client.ex index bf6379f..00d09ed 100644 --- a/lib/diplomat/client.ex +++ b/lib/diplomat/client.ex @@ -164,8 +164,15 @@ defmodule Diplomat.Client do defp token_module, do: Application.get_env(:diplomat, :token_module, Goth.Token) - defp project do - {:ok, project_id} = Goth.Config.get(:project_id) + def diplomat_account() do + Process.get(:diplomat_account) + end + + def project do + {:ok, project_id} = case diplomat_account() do + nil -> Goth.Config.get(:project_id) + account -> Goth.Config.get(account, :project_id) + end project_id end @@ -173,7 +180,10 @@ defmodule Diplomat.Client do defp api_scope("v1"), do: "https://www.googleapis.com/auth/datastore" defp auth_header do - {:ok, token} = token_module().for_scope(api_scope()) + {:ok, token} = case diplomat_account() do + nil -> {:ok, token} = token_module().for_scope(api_scope()) + account -> {:ok, token} = token_module().for_scope({account, api_scope()}) + end {"Authorization", "#{token.type} #{token.token}"} end diff --git a/lib/diplomat/key.ex b/lib/diplomat/key.ex index 71f667c..22fa6cd 100644 --- a/lib/diplomat/key.ex +++ b/lib/diplomat/key.ex @@ -92,7 +92,7 @@ defmodule Diplomat.Key do nil _ -> - {:ok, global_project_id} = Goth.Config.get(:project_id) + global_project_id = Diplomat.Client.project() PbPartition.new( project_id: key.project_id || global_project_id, diff --git a/lib/diplomat/query.ex b/lib/diplomat/query.ex index 472e17c..f057d9c 100644 --- a/lib/diplomat/query.ex +++ b/lib/diplomat/query.ex @@ -43,7 +43,7 @@ defmodule Diplomat.Query do @spec execute(t, String.t() | nil) :: [Entity.t()] | Client.error() def execute(%__MODULE__{} = q, namespace \\ nil) do - {:ok, project} = Goth.Config.get(:project_id) + project = Diplomat.Client.project() RunQueryRequest.new( query_type: {:gql_query, q |> Query.proto()}, @@ -54,7 +54,7 @@ defmodule Diplomat.Query do @spec execute_with_pagination(t, String.t() | nil) :: [QueryResultBatch.t()] | Client.error() def execute_with_pagination(%__MODULE__{} = q, namespace \\ nil) do - {:ok, project} = Goth.Config.get(:project_id) + project = Diplomat.Client.project() RunQueryRequest.new( query_type: {:gql_query, q |> Query.proto()}, diff --git a/test/diplomat/account_override_test.exs b/test/diplomat/account_override_test.exs new file mode 100644 index 0000000..137e182 --- /dev/null +++ b/test/diplomat/account_override_test.exs @@ -0,0 +1,27 @@ +defmodule Diplomat.AccountOverrideTest do + use ExUnit.Case + require Diplomat + + test "Verify account overrides are applied correctly" do + assert Diplomat.Client.diplomat_account() == nil + r = Diplomat.with_account(:alternative_account_email) do + assert Diplomat.Client.diplomat_account() == :alternative_account_email + a = Diplomat.with_account(:alternative_account2_email) do + assert Diplomat.Client.diplomat_account() == :alternative_account2_email + :alpha + end + assert a == :alpha + assert Diplomat.Client.diplomat_account() == :alternative_account_email + b = Diplomat.with_account(:alternative_account3_email) do + assert Diplomat.Client.diplomat_account() == :alternative_account3_email + :beta + end + assert b == :beta + assert Diplomat.Client.diplomat_account() == :alternative_account_email + :omega + end + assert Diplomat.Client.diplomat_account() == nil + assert r == :omega + end + +end From bbf2c97b8ecff075ff93e875a3c5a5d2ecd63d8c Mon Sep 17 00:00:00 2001 From: Keith Brings Date: Sat, 1 Oct 2022 04:55:45 +0700 Subject: [PATCH 2/4] Including necessary require Diplomat line in doc for new functionality. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b22a82a..522ba72 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Require Diplomat and use the with_account option to set current (and only curren The account name will be the client_email value from the additional Goth configuration you added by default. ```elixir +require Diplomat # copy data from prod to stage environment # 1. Fetch data from production account From 94eed5f55ddddb96162995aeb0c8f20ca5f4e09c Mon Sep 17 00:00:00 2001 From: Keith Brings Date: Sat, 1 Oct 2022 04:57:07 +0700 Subject: [PATCH 3/4] Removing redundant {:ok, token} clauses when extracting Goth auth token. --- lib/diplomat/client.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/diplomat/client.ex b/lib/diplomat/client.ex index 00d09ed..c462da0 100644 --- a/lib/diplomat/client.ex +++ b/lib/diplomat/client.ex @@ -181,8 +181,8 @@ defmodule Diplomat.Client do defp auth_header do {:ok, token} = case diplomat_account() do - nil -> {:ok, token} = token_module().for_scope(api_scope()) - account -> {:ok, token} = token_module().for_scope({account, api_scope()}) + nil -> token_module().for_scope(api_scope()) + account -> token_module().for_scope({account, api_scope()}) end {"Authorization", "#{token.type} #{token.token}"} end From 49b39cd48b25f388969309e1e019109f5c0cfb90 Mon Sep 17 00:00:00 2001 From: Keith Brings Date: Sat, 1 Oct 2022 06:35:03 +0700 Subject: [PATCH 4/4] Readme update as upsert requires overriding the entity project_id before calling --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 522ba72..c3a3674 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ require Diplomat # copy data from prod to stage environment # 1. Fetch data from production account -prod_data = Diplomat.with_account(alternative_account["client_email"]) do +prod_entities = Diplomat.with_account(alternative_account["client_email"]) do Diplomat.Query.new( "select * from `Book` where name = @name", %{name: "20,000 Leagues Under The Sea"} @@ -67,9 +67,11 @@ prod_data = Diplomat.with_account(alternative_account["client_email"]) do end # 2. Write to stage/dev account (default environment) -Enum.map(prod_data, fn(entity) -> - Diplomat.Entity.upsert(entity) -end) +target_project = Diplomat.Client.project() +stage_entities = Enum.map(prod_entities, fn(entity) -> + put_in(entity, [Access.key(:key), Access.key(:project_id)], target_project) + end) +Diplomat.Entity.upsert(stage_entities) ```