Skip to content
This repository has been archived by the owner on Feb 2, 2024. It is now read-only.

Commit

Permalink
Use ExAws instead of Erlcloud
Browse files Browse the repository at this point in the history
  • Loading branch information
stavro committed Dec 11, 2015
1 parent cfe271a commit 1101fba
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 57 deletions.
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## v0.2.0 (12-11-2015)
* (Breaking Change) Erlcloud has been removed in favor of ExAws.
* (Enhancement) Added a configuration parameter to generate urls in the `virtual_host` style.

### Upgrade Instructions
Since `erlcloud` has been removed from `arc`, you must also remove it from your dependency graph as well as your application list. In its place, add `ex_aws` and `httpoison` to your dependencies as well as application list. Next, remove the aws credential configuration from arc:

```elixir
# BEFORE
config :arc,
access_key_id: "###",
secret_access_key: "###",
bucket: "uploads"

#AFTER
config :arc,
bucket: "uploads"

# (this is the default ex_aws config... if your keys are not in environment variables you can override it here)
config :ex_aws,
access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, :instance_role],
secret_access_key: [{:system, "AWS_SECRET_ACCESS_KEY"}, :instance_role]
```

Read more about how ExAws manages configuration [here](https://github.com/CargoSense/ex_aws).

## v0.1.4 (11-10-2015)
* (Enhancement: Local Storage) Filenames which contain path separators will flatten out as expected prior to moving copying the file to its destination.

Expand Down
44 changes: 36 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,22 @@ Add the latest stable release to your `mix.exs` file:
```elixir
defp deps do
[
{:arc, "~> 0.1.4"}
arc: "~> 0.2.0",
ex_aws: "~> 0.4.10", # Required if using Amazon S3
httpoison: "~> 0.7" # Required if using Amazon S3
]
end
```

and add `erlcloud` as an application startup dependency in your application's `mix.exs` file:
If you plan on using Amazon's S3 Storage, you must also add `ex_aws` and `httpoison` the following applications as startup dependencies your application's `mix.exs` file:

```elixir
def application do
[
mod: { MyApp, [] },
applications: [
:other_app_dependencies,
:erlcloud
:ex_aws,
:httpoison
]
]
end
Expand Down Expand Up @@ -153,17 +155,27 @@ end

### S3 Configuration

[Erlcloud](https://github.com/gleber/erlcloud) is used to support Amazon S3.
[ExAws](https://github.com/CargoSense/ex_aws) is used to support Amazon S3.

To store your attachments in Amazon S3, you'll need to provide your AWS credentials and bucket destination in your application config:
To store your attachments in Amazon S3, you'll need to provide a bucket destination in your application config:

```elixir
config :arc,
access_key_id: "AKIAGJAVFNWDALJDLSA",
secret_access_key: "ncakAIWd+DaklwFAS51dDQo1i4EFAs\DASZGq",
bucket: "uploads"
```

In addition, ExAws must be configured with the appropriate Amazon S3 credentials.

ExAws has by default the equivalent including the following in your mix.exs

```elixir
config :ex_aws,
access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, :instance_role],
secret_access_key: [{:system, "AWS_SECRET_ACCESS_KEY"}, :instance_role]
```

This means it will first look for the AWS standard AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables, and fall back using instance meta-data if those don't exist. You should set those environment variables to your credentials, or configure an instance that this library runs on to have an iam role.

### Storage Directory

Arc requires the specification of a storage directory path (not including the bucket name).
Expand Down Expand Up @@ -278,6 +290,22 @@ Avatar.url(nil) #=> "http://example.com/images/placeholders/profile_image.png"
Avatar.url({nil, scope}) #=> "http://example.com/images/placeholders/profile_image.png"
```

**Virtual Host**

To support AWS regions other than US Standard, it is convenient to generate urls in the [`virtual_host`](http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html) style. This will generate urls in the style: `https://#{bucket}.s3.amazonaws.com` instead of `https://s3.amazonaws.com/#{bucket}`.

To use this style of url generation, your bucket name must be DNS compliant.

This can be enabled with:

```elixir
config :arc,
virtual_host: true
```

> When using virtual hosted–style buckets with SSL, the SSL wild card certificate only matches buckets that do not contain periods. To work around this, use HTTP or write your own certificate verification logic.

**Asset Host**

You may optionally specify an asset host rather than using the default `bucket.s3.amazonaws.com` format.
Expand Down
42 changes: 16 additions & 26 deletions lib/arc/storage/s3.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ defmodule Arc.Storage.S3 do

def put(definition, version, {file, scope}) do
destination_dir = definition.storage_dir(version, {file, scope})
s3_key = Path.join(destination_dir, file.file_name) |> String.to_char_list
s3_key = Path.join(destination_dir, file.file_name)
binary = File.read!(file.path)
acl = definition.acl(version, {file, scope})
:erlcloud_s3.put_object(bucket, s3_key, binary, [acl: acl], erlcloud_config)
ExAws.S3.put_object(bucket, s3_key, binary, [acl: acl])
file.file_name
end

Expand All @@ -19,8 +19,8 @@ defmodule Arc.Storage.S3 do

def delete(definition, version, {file, scope}) do
destination_dir = definition.storage_dir(version, {file, scope})
s3_key = Path.join(destination_dir, file.file_name) |> String.to_char_list
:erlcloud_s3.delete_object(bucket, s3_key, erlcloud_config)
s3_key = Path.join(destination_dir, file.file_name)
ExAws.S3.delete_object(bucket, s3_key)
end

#
Expand All @@ -32,9 +32,9 @@ defmodule Arc.Storage.S3 do
end

defp build_signed_url(definition, version, file_and_scope, options) do
expire_in = Keyword.get(options, :expire_in, @default_expiry_time)
make_get_url(expire_in, bucket_name, s3_key(definition, version, file_and_scope), erlcloud_config)
|> Path.join("")
expires_in = Keyword.get(options, :expire_in, @default_expiry_time)
{:ok, url} = ExAws.S3.presigned_url(:get, bucket, s3_key(definition, version, file_and_scope), [expires_in: expires_in, virtual_host: virtual_host])
url
end

defp s3_key(definition, version, file_and_scope) do
Expand All @@ -45,31 +45,21 @@ defmodule Arc.Storage.S3 do
end

defp host do
Application.get_env(:arc, :asset_host) || "https://s3.amazonaws.com/#{bucket_name}"
Application.get_env(:arc, :asset_host) || default_host
end

defp bucket_name do
Application.get_env(:arc, :bucket)
defp default_host do
case virtual_host do
true -> "https://#{bucket}.s3.amazonaws.com"
_ -> "https://s3.amazonaws.com/#{bucket}"
end
end

defp erlcloud_config do
:erlcloud_s3.new(
to_char_list(Application.get_env(:arc, :access_key_id)),
to_char_list(Application.get_env(:arc, :secret_access_key)),
's3.amazonaws.com'
)
defp virtual_host do
Application.get_env(:arc, :virtual_host) || false
end

defp bucket do
bucket = Application.fetch_env!(:arc, :bucket)
to_char_list(bucket)
end

defp make_get_url(expire, bucket_name, s3_key, config) do
:erlcloud_s3.make_get_url(expire,
to_char_list(bucket_name),
to_char_list(s3_key),
config
)
Application.fetch_env!(:arc, :bucket)
end
end
20 changes: 5 additions & 15 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Arc.Mixfile do
use Mix.Project

@version "0.1.4"
@version "0.2.0"

def project do
[app: :arc,
Expand All @@ -26,26 +26,16 @@ defmodule Arc.Mixfile do
links: %{"GitHub" => "https://github.com/stavro/arc"},
files: ~w(mix.exs README.md CHANGELOG.md lib)]
end
# Configuration for the OTP application
#
# Type `mix help compile.app` for more information

def application do
[applications: [:logger]]
end

# Dependencies can be Hex packages:
#
# {:mydep, "~> 0.3.0"}
#
# Or git/path repositories:
#
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
#
# Type `mix help deps` for more examples and options
defp deps do
[
{:erlcloud, "~> 0.9.0"},
{:mock, "~> 0.1.1", only: :test}
{:ex_aws, "~> 0.4.10", optional: true},
{:httpoison, "~> 0.7", optional: true},
{:mock, "~> 0.1.1", only: :test}
]
end
end
11 changes: 7 additions & 4 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
%{"erlcloud": {:hex, :erlcloud, "0.9.2"},
"jsx": {:hex, :jsx, "2.1.1"},
"lhttpc": {:hex, :lhttpc, "1.3.0"},
%{"certifi": {:hex, :certifi, "0.3.0"},
"ex_aws": {:hex, :ex_aws, "0.4.13"},
"hackney": {:hex, :hackney, "1.4.7"},
"httpoison": {:hex, :httpoison, "0.8.0"},
"idna": {:hex, :idna, "1.0.2"},
"meck": {:hex, :meck, "0.8.2"},
"mimerl": {:hex, :mimerl, "1.0.2"},
"mock": {:hex, :mock, "0.1.1"},
"mogrify": {:hex, :mogrify, "0.1.0"}}
"ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}}
23 changes: 19 additions & 4 deletions test/storage/s3_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,26 @@ defmodule ArcTest.Storage.S3 do
def acl(:private, _), do: :private
end

def env_bucket do
System.get_env("ARC_TEST_BUCKET")
end

setup_all do
:erlcloud.start
Application.put_env :arc, :bucket, System.get_env("ARC_TEST_BUCKET")
Application.put_env :arc, :access_key_id, System.get_env("ARC_TEST_S3_KEY")
Application.put_env :arc, :secret_access_key, System.get_env("ARC_TEST_S3_SECRET")
Application.put_env :arc, :virtual_host, false
Application.put_env :arc, :bucket, env_bucket
# Application.put_env :ex_aws, :s3, [scheme: "https://", host: "s3.amazonaws.com", region: "us-west-2"]
Application.put_env :ex_aws, :access_key_id, System.get_env("ARC_TEST_S3_KEY")
Application.put_env :ex_aws, :secret_access_key, System.get_env("ARC_TEST_S3_SECRET")
end

test "virtual_host" do
Application.put_env :arc, :virtual_host, false
url = Arc.Storage.S3.url(DummyDefinition, :original, {Arc.File.new(@img), nil})
assert "https://s3.amazonaws.com/#{env_bucket}/arctest/uploads/image.png", url

Application.put_env :arc, :virtual_host, false
url = Arc.Storage.S3.url(DummyDefinition, :original, {Arc.File.new(@img), nil})
assert "https://#{env_bucket}.s3.amazonaws.com/arctest/uploads/image.png", url
end

@tag :s3
Expand Down

0 comments on commit 1101fba

Please sign in to comment.