From 83325468525d132d725658ec1f09199474cdef26 Mon Sep 17 00:00:00 2001 From: Erick Navarro Date: Wed, 20 Mar 2024 21:03:35 -0600 Subject: [PATCH] Add support for dynamic S3 configs per uploader In case definition.s3_config/0 is not defined it will use ExAws.Config.Defaults.defaults(:s3) which was the default option already --- documentation/examples/s3.md | 12 ++++++++++++ lib/waffle/definition.ex | 9 +++++++++ lib/waffle/storage/s3.ex | 29 +++++++++++++++++++---------- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/documentation/examples/s3.md b/documentation/examples/s3.md index a0d3f0d..c5d93e1 100644 --- a/documentation/examples/s3.md +++ b/documentation/examples/s3.md @@ -42,6 +42,18 @@ defmodule Avatar do def default_url(:thumb) do "https://placehold.it/100x100" end + + # if not defined it will use default S3 config which is obtained + # from config files + def s3_config({file, scope}) do + ExAws.Config.new(:s3, + access_key_id: "custom access key id", + secret_access_key: "custom secret access key", + host: "localhost", + scheme: "http://", + port: 9000 + ) + end end ``` diff --git a/lib/waffle/definition.ex b/lib/waffle/definition.ex index 73ed9fb..e99dfff 100644 --- a/lib/waffle/definition.ex +++ b/lib/waffle/definition.ex @@ -29,6 +29,15 @@ defmodule Waffle.Definition do use Waffle.Actions.Store use Waffle.Actions.Delete use Waffle.Actions.Url + + @doc """ + Define a custom configuration struct to connect to AWS S3 + """ + def s3_config({file, scope}) do + ExAws.Config.Defaults.defaults(:s3) + end + + defoverridable [{:s3_config, 1}] end end end diff --git a/lib/waffle/storage/s3.ex b/lib/waffle/storage/s3.ex index e03182e..bdaf807 100644 --- a/lib/waffle/storage/s3.ex +++ b/lib/waffle/storage/s3.ex @@ -130,7 +130,6 @@ defmodule Waffle.Storage.S3 do """ require Logger - alias ExAws.Config alias ExAws.Request.Url alias ExAws.S3 alias ExAws.S3.Upload @@ -139,6 +138,7 @@ defmodule Waffle.Storage.S3 do @default_expiry_time 60 * 5 def put(definition, version, {file, scope}) do + config = definition.s3_config({file, scope}) destination_dir = definition.storage_dir(version, {file, scope}) s3_bucket = s3_bucket(definition, {file, scope}) s3_key = Path.join(destination_dir, file.file_name) @@ -149,7 +149,7 @@ defmodule Waffle.Storage.S3 do |> ensure_keyword_list() |> Keyword.put(:acl, acl) - do_put(file, {s3_bucket, s3_key, s3_options}) + do_put(file, {s3_bucket, s3_key, s3_options}, config) end def url(definition, version, file_and_scope, options \\ []) do @@ -160,9 +160,11 @@ defmodule Waffle.Storage.S3 do end def delete(definition, version, {file, scope}) do + config = definition.s3_config({file, scope}) + s3_bucket(definition, {file, scope}) |> S3.delete_object(s3_key(definition, version, {file, scope})) - |> ExAws.request() + |> ExAws.request(config) :ok end @@ -182,10 +184,10 @@ defmodule Waffle.Storage.S3 do defp ensure_keyword_list(map) when is_map(map), do: Map.to_list(map) # If the file is stored as a binary in-memory, send to AWS in a single request - defp do_put(file = %Waffle.File{binary: file_binary}, {s3_bucket, s3_key, s3_options}) + defp do_put(file = %Waffle.File{binary: file_binary}, {s3_bucket, s3_key, s3_options}, config) when is_binary(file_binary) do S3.put_object(s3_bucket, s3_key, file_binary, s3_options) - |> ExAws.request() + |> ExAws.request(config) |> case do {:ok, _res} -> {:ok, file.file_name} {:error, error} -> {:error, error} @@ -193,11 +195,11 @@ defmodule Waffle.Storage.S3 do end # Stream the file and upload to AWS as a multi-part upload - defp do_put(file, {s3_bucket, s3_key, s3_options}) do + defp do_put(file, {s3_bucket, s3_key, s3_options}, config) do file.path |> Upload.stream_file() |> S3.upload(s3_bucket, s3_key, s3_options) - |> ExAws.request() + |> ExAws.request(config) |> case do {:ok, %{status_code: 200}} -> {:ok, file.file_name} {:ok, :done} -> {:ok, file.file_name} @@ -225,7 +227,7 @@ defmodule Waffle.Storage.S3 do # fallback to default, if neither is present. options = put_in(options[:expires_in], options[:expires_in] || @default_expiry_time) options = put_in(options[:virtual_host], virtual_host()) - config = Config.new(:s3, Application.get_all_env(:ex_aws)) + config = definition.s3_config(file_and_scope) s3_key = s3_key(definition, version, file_and_scope) s3_bucket = s3_bucket(definition, file_and_scope) {:ok, url} = S3.presigned_url(config, :get, s3_bucket, s3_key, options) @@ -248,9 +250,16 @@ defmodule Waffle.Storage.S3 do end defp default_host(definition, file_and_scope) do + config = definition.s3_config(file_and_scope) + host = Map.get(config, :host, "s3.amazonaws.com") || "s3.amazonaws.com" + port = Map.get(config, :port) + port = port in [80, 443] && "" || ":#{port}" + case virtual_host() do - true -> "https://#{s3_bucket(definition, file_and_scope)}.s3.amazonaws.com" - _ -> "https://s3.amazonaws.com/#{s3_bucket(definition, file_and_scope)}" + true -> + "#{config.scheme}#{s3_bucket(definition, file_and_scope)}.#{host}#{port}" + _ -> + "#{config.scheme}#{host}#{port}/#{s3_bucket(definition, file_and_scope)}" end end