Skip to content

Commit

Permalink
blob upload
Browse files Browse the repository at this point in the history
  • Loading branch information
kaaboaye committed Nov 30, 2019
1 parent 70626ad commit c23bf20
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ erl_crash.dump
azurex-*.tar
.elixir_ls
.vscode

/config/config.exs
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
sudo: false

language: elixir

elixir: "1.9"
opt_release: "22.0"

script:
- |
mix format --check-formatted
MIX_ENV=prod mix compile --warnings-as-errors
mix dialyzer --halt-exit-status
8 changes: 8 additions & 0 deletions config/config-example.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Config

config :azurex, Azurex.Blob.Config,
api_url: "http://{name}.blob.core.windows.net",
default_container: "default container",
storage_account_name: "name",
storage_account_key: "base64 encoded key",
storage_account_connection_string: "connection string"
122 changes: 122 additions & 0 deletions lib/azurex/authorization/shared_key.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
defmodule Azurex.Authorization.SharedKey do
@moduledoc """
Implements Azure Rest Api Authorization method.
It is based on: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
As defined in 26 November 2019
"""

@spec sign(HTTPoison.Request.t(), keyword) :: HTTPoison.Request.t()
def sign(request, opts \\ []) do
storage_account_name = Keyword.fetch!(opts, :storage_account_name)
storage_account_key = Keyword.fetch!(opts, :storage_account_key)
content_type = Keyword.get(opts, :content_type)

request = put_standard_headers(request, content_type)

method = get_method(request)
size = get_size(request)
headers = get_headers_signature(request)
uri_signature = get_uri_signature(request, storage_account_name)

signature =
[
# HTTP Verb
method,
# Content-Encoding
"",
# Content-Language
"",
# Content-Length
size,
# Content-MD5
"",
# Content-Type
content_type || "",
# Date
"",
# If-Modified-Since
"",
# If-Match
"",
# If-None-Match
"",
# If-Unmodified-Since
"",
# Range
"",
# CanonicalizedHeaders
headers,
# CanonicalizedResource
uri_signature
]
|> Enum.join("\n")

put_signature(request, signature, storage_account_name, storage_account_key)
end

defp put_standard_headers(request, content_type) do
now =
Timex.now("GMT") |> Timex.format!("{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} {Zname}")

headers =
if content_type,
do: [{"content-type", content_type} | request.headers],
else: request.headers

headers = [
{"x-ms-version", "2017-04-17"},
{"x-ms-date", now}
| headers
]

struct(request, headers: headers)
end

defp get_method(request), do: request.method |> Atom.to_string() |> String.upcase()

defp get_size(request) do
size = request.body |> byte_size()
if size != 0, do: size, else: ""
end

defp get_headers_signature(request) do
request.headers
|> Enum.map(fn {k, v} -> {String.downcase(k), v} end)
|> Enum.filter(fn {k, _v} -> String.starts_with?(k, "x-ms-") end)
|> Enum.group_by(fn {k, _v} -> k end, fn {_k, v} -> v end)
|> Enum.sort_by(fn {k, _v} -> k end)
|> Enum.map(fn {k, v} ->
v = v |> Enum.sort() |> Enum.join(",")
k <> ":" <> v
end)
|> Enum.join("\n")
end

defp get_uri_signature(request, storage_account_name) do
uri = URI.parse(request.url)
path = uri.path || "/"
query = URI.query_decoder(uri.query || "")

[
"/",
storage_account_name,
path
| Enum.flat_map(query, fn {k, v} ->
["\n", k, ":", v]
end)
]
|> IO.iodata_to_binary()
end

defp put_signature(request, signature, storage_account_name, storage_account_key) do
signature =
:crypto.mac(:hmac, :sha256, storage_account_key, signature)
|> Base.encode64()

authorization = {"authorization", "SharedKey #{storage_account_name}:#{signature}"}

headers = [authorization | request.headers]
struct(request, headers: headers)
end
end
46 changes: 46 additions & 0 deletions lib/azurex/blob.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule Azurex.Blob do
alias Azurex.Blob.Config
alias Azurex.Authorization.SharedKey

def list_containers do
%HTTPoison.Request{
url: Config.api_url() <> "?comp=list"
}
|> SharedKey.sign(
storage_account_name: Config.storage_account_name(),
storage_account_key: Config.storage_account_key()
)
|> HTTPoison.request()
end

@spec put_blob(String.t(), binary, String.t(), keyword) ::
:ok
| {:error, HTTPoison.AsyncResponse.t() | HTTPoison.Error.t() | HTTPoison.Response.t()}
def put_blob(name, blob, content_type, opts \\ []) do
query =
if timeout = Keyword.get(opts, :timeout),
do: "?" <> URI.encode_query([{"timeout", timeout}]),
else: ""

%HTTPoison.Request{
method: :put,
url: "#{Config.api_url()}/#{Config.default_container()}/#{name}#{query}",
body: blob,
headers: [
{"x-ms-blob-type", "BlockBlob"}
]
}
|> IO.inspect()
|> SharedKey.sign(
storage_account_name: Config.storage_account_name(),
storage_account_key: Config.storage_account_key(),
content_type: content_type
)
|> HTTPoison.request()
|> case do
{:ok, %{status_code: 201}} -> :ok
{:ok, err} -> {:error, err}
{:error, err} -> {:error, err}
end
end
end
20 changes: 20 additions & 0 deletions lib/azurex/blob/config.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Azurex.Blob.Config do
@moduledoc """
Azurex Blob Config
"""

defp conf, do: Application.get_env(:azurex, __MODULE__, [])

def api_url, do: Keyword.get(conf(), :api_url)

def default_container, do: Keyword.get(conf(), :default_container)
def storage_account_name, do: Keyword.get(conf(), :storage_account_name)

def storage_account_key do
key = Keyword.get(conf(), :storage_account_key)
if key, do: Base.decode64!(key)
end

def storage_account_connection_string,
do: Keyword.get(conf(), :storage_account_connection_string)
end
5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ defmodule Azurex.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
{:dialyxir, "~> 1.0.0-rc.7", only: [:dev], runtime: false},
{:httpoison, "~> 1.6"},
{:timex, "~> 3.6"}
]
end
end
17 changes: 17 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
%{
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.7", "6287f8f2cb45df8584317a4be1075b8c9b8a69de8eeb82b4d9e6c761cf2664cd", [:mix], [{:erlex, ">= 0.2.5", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"},
"erlex": {:hex, :erlex, "0.2.5", "e51132f2f472e13d606d808f0574508eeea2030d487fc002b46ad97e738b0510", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.17.1", "8baab33482df4907b3eae22f719da492cee3981a26e649b9c2be1c0192616962", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "1.0.2", "6c4242c93332b8590a7979eaf5e11e77d971e579805c44931207e32aa6ad3db1", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
}

0 comments on commit c23bf20

Please sign in to comment.