aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkballou <kballou@devnulllabs.io>2016-12-27 10:02:42 -0700
committerkballou <kballou@devnulllabs.io>2016-12-27 10:40:28 -0700
commit6e36a9c3fb482cc993a3fa6511c51cc19b59d5a2 (patch)
tree2b70dbdb2ca4ff29927b61a2c0b845945de3b848
parentb79f51aac09b6a612725e1fae5af283c4025aa34 (diff)
downloadexdatadog-6e36a9c3fb482cc993a3fa6511c51cc19b59d5a2.tar.gz
exdatadog-6e36a9c3fb482cc993a3fa6511c51cc19b59d5a2.tar.xz
Add base datadog client
This adds a base HTTPoison Datadog Client library.
-rw-r--r--.travis.yml13
-rw-r--r--config/.credo.exs65
-rw-r--r--config/config.exs7
-rw-r--r--config/dev.exs1
-rw-r--r--config/docs.exs1
-rw-r--r--config/prod.exs1
-rw-r--r--config/test.exs5
-rw-r--r--lib/exdatadog.ex123
-rw-r--r--lib/exdatadog/client.ex33
-rw-r--r--lib/exdatadog/config.ex15
-rw-r--r--mix.exs45
-rw-r--r--mix.lock18
-rw-r--r--test/exdatadog/client_test.exs39
-rw-r--r--test/exdatadog/config_test.exs30
-rw-r--r--test/exdatadog_test.exs64
-rw-r--r--test/test_helper.exs1
16 files changed, 461 insertions, 0 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..914743f
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,13 @@
+sudo: false
+language: elixir
+elixir:
+ - 1.3.2
+ - 1.3.3
+ - 1.3.4
+otp_release:
+ - 18.3
+ - 19.0
+ - 19.1
+ - 19.2
+script:
+ - mix test --cover
diff --git a/config/.credo.exs b/config/.credo.exs
new file mode 100644
index 0000000..3440473
--- /dev/null
+++ b/config/.credo.exs
@@ -0,0 +1,65 @@
+%{
+ configs: [
+ %{
+ name: "default",
+ files: %{
+ included: ["lib/", "test/"],
+ excluded: [~r"/_build/", ~r"/deps/"]
+ },
+ requires: [],
+ check_for_updates: true,
+ checks: [
+ {Credo.Check.Consistency.ExceptionNames},
+ {Credo.Check.Consistency.LineEndings},
+ {Credo.Check.Consistency.SpaceAroundOperators},
+ {Credo.Check.Consistency.SpaceInParentheses},
+ {Credo.Check.Consistency.TabsOrSpaces},
+
+ {Credo.Check.Design.AliasUsage, priority: :low},
+
+ {Credo.Check.Design.DuplicatedCode, excluded_macros: []},
+
+ {Credo.Check.Design.TagTODO, exit_status: 2},
+ {Credo.Check.Design.TagFIXME},
+
+ {Credo.Check.Readability.FunctionNames},
+ {Credo.Check.Readability.LargeNumbers},
+ {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80},
+ {Credo.Check.Readability.ModuleAttributeNames},
+ {Credo.Check.Readability.ModuleDoc},
+ {Credo.Check.Readability.ModuleNames},
+ {Credo.Check.Readability.ParenthesesInCondition},
+ {Credo.Check.Readability.PredicateFunctionNames},
+ {Credo.Check.Readability.TrailingBlankLine},
+ {Credo.Check.Readability.TrailingWhiteSpace},
+ {Credo.Check.Readability.VariableNames},
+
+ {Credo.Check.Refactor.ABCSize},
+ {Credo.Check.Refactor.CondStatements},
+ {Credo.Check.Refactor.FunctionArity},
+ {Credo.Check.Refactor.MatchInCondition},
+ {Credo.Check.Refactor.PipeChainStart},
+ {Credo.Check.Refactor.CyclomaticComplexity},
+ {Credo.Check.Refactor.NegatedConditionsInUnless},
+ {Credo.Check.Refactor.NegatedConditionsWithElse},
+ {Credo.Check.Refactor.Nesting},
+ {Credo.Check.Refactor.UnlessWithElse},
+
+ {Credo.Check.Warning.IExPry},
+ {Credo.Check.Warning.IoInspect},
+ {Credo.Check.Warning.NameRedeclarationByAssignment},
+ {Credo.Check.Warning.NameRedeclarationByCase},
+ {Credo.Check.Warning.NameRedeclarationByDef},
+ {Credo.Check.Warning.NameRedeclarationByFn},
+ {Credo.Check.Warning.OperationOnSameValues},
+ {Credo.Check.Warning.BoolOperationOnSameValues},
+ {Credo.Check.Warning.UnusedEnumOperation},
+ {Credo.Check.Warning.UnusedKeywordOperation},
+ {Credo.Check.Warning.UnusedListOperation},
+ {Credo.Check.Warning.UnusedStringOperation},
+ {Credo.Check.Warning.UnusedTupleOperation},
+ {Credo.Check.Warning.OperationWithConstantResult},
+ ]
+ }
+ ]
+}
diff --git a/config/config.exs b/config/config.exs
new file mode 100644
index 0000000..2d6e3c7
--- /dev/null
+++ b/config/config.exs
@@ -0,0 +1,7 @@
+use Mix.Config
+
+config :exdatadog,
+ api_key: {:system, "DATADOG_API_KEY"},
+ app_key: {:system, "DATADOG_APP_KEY"}
+
+import_config "#{Mix.env}.exs"
diff --git a/config/dev.exs b/config/dev.exs
new file mode 100644
index 0000000..d2d855e
--- /dev/null
+++ b/config/dev.exs
@@ -0,0 +1 @@
+use Mix.Config
diff --git a/config/docs.exs b/config/docs.exs
new file mode 100644
index 0000000..d2d855e
--- /dev/null
+++ b/config/docs.exs
@@ -0,0 +1 @@
+use Mix.Config
diff --git a/config/prod.exs b/config/prod.exs
new file mode 100644
index 0000000..d2d855e
--- /dev/null
+++ b/config/prod.exs
@@ -0,0 +1 @@
+use Mix.Config
diff --git a/config/test.exs b/config/test.exs
new file mode 100644
index 0000000..e9f574d
--- /dev/null
+++ b/config/test.exs
@@ -0,0 +1,5 @@
+use Mix.Config
+
+config :exdatadog,
+ api_key: "1234",
+ app_key: "abcd"
diff --git a/lib/exdatadog.ex b/lib/exdatadog.ex
new file mode 100644
index 0000000..cb1c559
--- /dev/null
+++ b/lib/exdatadog.ex
@@ -0,0 +1,123 @@
+defmodule Exdatadog do
+ @moduledoc """
+ HTTPoison Client for exdatadog
+ """
+ use HTTPoison.Base
+
+ alias Exdatadog.Client
+ alias HTTPoison.Response
+
+ @user_agent [{"user-agent", "exdatadog"}]
+
+ @type response :: {integer, any} | map
+
+ @spec process_response_body(binary) :: map
+ def process_response_body(""), do: nil
+ def process_response_body(body), do: Poison.decode!(body)
+
+ @spec process_response(HTTPoison.Response.t) :: response
+ def process_response(%Response{status_code: status_code, body: body}) do
+ {status_code, body}
+ end
+
+ def post(path, client, body) do
+ _request(:post, url(client, path), client.auth, body)
+ end
+
+ def get(path, client, params \\ [], _ \\ []) do
+ url =
+ client
+ |> url(path)
+ |> add_params_to_url(params)
+
+ _request(:get, url, client.auth)
+
+ end
+
+ def _request(method, url, auth, body \\ "") do
+ json_request(method,
+ add_params_to_url(url, auth_params(auth)),
+ body,
+ @user_agent)
+ end
+
+ def json_request(method, url, body \\ "", headers \\ [], options \\ []) do
+ raw_request(method, url, Poison.encode!(body), headers, options)
+ end
+
+ def raw_request(method, url, body \\ "", headers \\ [], options \\ []) do
+ method
+ |> request!(url, body, headers, options)
+ |> process_response
+ end
+
+ @spec url(Exdatadog.Client.t, binary) :: binary
+ defp url(_client = %Client{endpoint: endpoint}, path) do
+ endpoint <> path
+ end
+
+ @doc """
+ Take an existing URI and add addition parameters, merging as necessary
+
+ ## Examples
+ iex> add_params_to_url("http://example.com/wat", [])
+ "http://example.com/wat"
+ iex> add_params_to_url("http://example.com/wat", [q: 1])
+ "http://example.com/wat?q=1"
+ iex> add_params_to_url("http://example.com/wat", [q: 1, t: 2])
+ "http://example.com/wat?q=1&t=2"
+ iex> add_params_to_url("http://example.com/wat", %{q: 1, t: 2})
+ "http://example.com/wat?q=1&t=2"
+ iex> add_params_to_url("http://example.com/wat?q=1&t=2", [])
+ "http://example.com/wat?q=1&t=2"
+ iex> add_params_to_url("http://example.com/wat?q=1", [t: 2])
+ "http://example.com/wat?q=1&t=2"
+ iex> add_params_to_url("http://example.com/wat?q=1", [q: 3, t: 2])
+ "http://example.com/wat?q=3&t=2"
+ iex> add_params_to_url("http://example.com/wat?q=1&s=4", [q: 3, t: 2])
+ "http://example.com/wat?q=3&s=4&t=2"
+ iex> add_params_to_url("http://example.com/wat?q=1&s=4", %{q: 3, t: 2})
+ "http://example.com/wat?q=3&s=4&t=2"
+
+ """
+ @spec add_params_to_url(binary, list) :: binary
+ def add_params_to_url(url, params) do
+ url
+ |> URI.parse
+ |> merge_uri_params(params)
+ |> to_string
+ end
+
+ defp merge_uri_params(uri, []), do: uri
+ defp merge_uri_params(%URI{query: nil} = uri, params)
+ when is_list(params) or is_map(params) do
+ uri
+ |> Map.put(:query, URI.encode_query(params))
+ end
+ defp merge_uri_params(%URI{} = uri, params)
+ when is_list(params) or is_map(params) do
+ uri
+ |> Map.update!(:query, fn q ->
+ q
+ |> URI.decode_query
+ |> Map.merge(param_list_to_map_with_string_keys(params))
+ |> URI.encode_query
+ end)
+ end
+
+ defp param_list_to_map_with_string_keys(list)
+ when is_list(list) or is_map(list) do
+ for {key, value} <- list, into: Map.new do
+ {"#{key}", value}
+ end
+ end
+
+ def auth_params(%{api_key: api_key, app_key: app_key}) do
+ [application_key: app_key, api_key: api_key]
+ end
+ def auth_params(%{api_key: api_key}) do
+ [api_key: api_key]
+ end
+ def auth_params(_), do: []
+
+end
diff --git a/lib/exdatadog/client.ex b/lib/exdatadog/client.ex
new file mode 100644
index 0000000..02c1017
--- /dev/null
+++ b/lib/exdatadog/client.ex
@@ -0,0 +1,33 @@
+defmodule Exdatadog.Client do
+ @moduledoc """
+ Datadog Client record for endpoint and authentication data
+ """
+
+ import Exdatadog.Config, only: [get_env_var: 2]
+
+ defstruct auth: nil, endpoint: "https://app.datadoghq.com/"
+
+ @type auth :: %{api_key: binary, app_key: binary} | %{api_key: binary}
+ @type t :: %__MODULE__{auth: auth, endpoint: binary}
+
+ @spec new() :: t
+ def new() do
+ auth = %{api_key: get_env_var(:exdatadog, :api_key),
+ app_key: get_env_var(:exdatadog, :app_key)}
+ %__MODULE__{auth: auth}
+ end
+
+ @spec new(auth) :: t
+ def new(auth), do: %__MODULE__{auth: auth}
+
+ @spec new(auth, binary) :: t
+ def new(auth, endpoint) do
+ endpoint = if String.ends_with?(endpoint, "/") do
+ endpoint
+ else
+ endpoint <> "/"
+ end
+ %__MODULE__{auth: auth, endpoint: endpoint}
+ end
+
+end
diff --git a/lib/exdatadog/config.ex b/lib/exdatadog/config.ex
new file mode 100644
index 0000000..f686309
--- /dev/null
+++ b/lib/exdatadog/config.ex
@@ -0,0 +1,15 @@
+defmodule Exdatadog.Config do
+ @moduledoc """
+ Provides helper functions for interacting with Application variables
+ """
+
+ def get_env_var(app, key, default \\ nil) do
+ app
+ |> Application.get_env(key, default)
+ |> case do
+ {:system, env_key} -> System.get_env(env_key)
+ env_var -> env_var
+ end
+ end
+
+end
diff --git a/mix.exs b/mix.exs
new file mode 100644
index 0000000..d481956
--- /dev/null
+++ b/mix.exs
@@ -0,0 +1,45 @@
+defmodule Exdatadog.Mixfile do
+ use Mix.Project
+
+ def project do
+ [app: :exdatadog,
+ description: "Elixir Datadog API Client",
+ package: package(),
+ version: "0.1.0",
+ elixir: "~> 1.3",
+ build_embedded: Mix.env == :prod,
+ start_permanent: Mix.env == :prod,
+ deps: deps(),
+ test_coverage: [tool: ExCoveralls],
+ docs: docs()]
+ end
+
+ def application do
+ [applications: [:logger, :httpoison]]
+ end
+
+ defp deps do
+ [{:credo, "~> 0.5.0", only: :dev},
+ {:excoveralls, "~> 0.5", only: :test},
+ {:earmark, "~> 0.1", only: :docs},
+ {:ex_doc, "~> 0.11", only: :docs},
+ {:exvcr, "~> 0.8.4", only: :test},
+ {:httpoison, "~> 0.10.0"},
+ {:meck, "~> 0.8", only: :test},
+ {:poison, "~> 2.2.0"}]
+ end
+
+ defp docs do
+ [extras: ["README.md"]]
+ end
+
+ defp package do
+ [maintainers: ["Kenny Ballou"],
+ licenses: ["Apache 2.0"],
+ links: %{"Git" => "https://git.devnulllabs.io/exdatadog.git",
+ "GitHub" => "https://github.com/kennyballou/exdatadog.git",
+ "Hex" => "https://hex.pm/packages/exdatadog",
+ "Hex Docs" => "https://hexdocs.pm/exdatadog"},
+ files: ~w(mix.exs README.md LICENSE lib)]
+ end
+end
diff --git a/mix.lock b/mix.lock
new file mode 100644
index 0000000..eecdfcc
--- /dev/null
+++ b/mix.lock
@@ -0,0 +1,18 @@
+%{"bunt": {:hex, :bunt, "0.1.6", "5d95a6882f73f3b9969fdfd1953798046664e6f77ec4e486e6fafc7caad97c6f", [:mix], []},
+ "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], []},
+ "credo": {:hex, :credo, "0.5.3", "0c405b36e7651245a8ed63c09e2d52c2e2b89b6d02b1570c4d611e0fcbecf4a2", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, optional: false]}]},
+ "earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], []},
+ "ex_doc": {:hex, :ex_doc, "0.12.0", "b774aabfede4af31c0301aece12371cbd25995a21bb3d71d66f5c2fe074c603f", [:mix], [{:earmark, "~> 0.2", [hex: :earmark, optional: false]}]},
+ "exactor": {:hex, :exactor, "2.2.2", "90b27d72c05614801a60f8400afd4e4346dfc33ea9beffe3b98a794891d2ff96", [:mix], []},
+ "excoveralls": {:hex, :excoveralls, "0.5.7", "5d26e4a7cdf08294217594a1b0643636accc2ad30e984d62f1d166f70629ff50", [:mix], [{:exjsx, "~> 3.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]},
+ "exjsx": {:hex, :exjsx, "3.2.1", "1bc5bf1e4fd249104178f0885030bcd75a4526f4d2a1e976f4b428d347614f0f", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, optional: false]}]},
+ "exvcr": {:hex, :exvcr, "0.8.4", "23ce3a7ff428ed98127292da1b6d04418064391d1045c00cd8ee2da37c5b839b", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, optional: false]}, {:exjsx, "~> 3.2", [hex: :exjsx, optional: false]}, {:httpoison, "~> 0.8", [hex: :httpoison, optional: true]}, {:httpotion, "~> 3.0", [hex: :httpotion, optional: true]}, {:ibrowse, "~> 4.2.2", [hex: :ibrowse, optional: true]}, {:meck, "~> 0.8.3", [hex: :meck, optional: false]}]},
+ "hackney": {:hex, :hackney, "1.6.5", "8c025ee397ac94a184b0743c73b33b96465e85f90a02e210e86df6cbafaa5065", [:rebar3], [{:certifi, "0.7.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
+ "httpoison": {:hex, :httpoison, "0.10.0", "4727b3a5e57e9a4ff168a3c2883e20f1208103a41bccc4754f15a9366f49b676", [:mix], [{:hackney, "~> 1.6.3", [hex: :hackney, optional: false]}]},
+ "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []},
+ "jsx": {:hex, :jsx, "2.8.1", "1453b4eb3615acb3e2cd0a105d27e6761e2ed2e501ac0b390f5bbec497669846", [:mix, :rebar3], []},
+ "meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:make, :rebar], []},
+ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
+ "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
+ "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
+ "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}}
diff --git a/test/exdatadog/client_test.exs b/test/exdatadog/client_test.exs
new file mode 100644
index 0000000..dc63c1b
--- /dev/null
+++ b/test/exdatadog/client_test.exs
@@ -0,0 +1,39 @@
+defmodule Exdatadog.Client.Test do
+ @moduledoc """
+ Provides Tests for the Exdatadog.Client module
+ """
+ use ExUnit.Case, async: false
+
+ @endpoint "https://app.datadoghq.com/"
+
+ alias Exdatadog.Client
+
+ test "can create new client" do
+ expected = %Client{endpoint: @endpoint, auth: %{api_key: "1234",
+ app_key: "abcd"}}
+ assert Client.new() == expected
+ end
+
+ test "can create new client with api auth" do
+ actual = Client.new(%{api_key: "1234"})
+ assert actual == %Client{endpoint: @endpoint, auth: %{api_key: "1234"}}
+ end
+
+ test "can create new client with api and app auth" do
+ actual = Client.new(%{api_key: "1234", app_key: "abcd"})
+ assert actual == %Client{endpoint: @endpoint, auth: %{api_key: "1234",
+ app_key: "abcd"}}
+ end
+
+ test "can create new client with auth and custom endpoint" do
+ actual = Client.new(%{api_key: "1234"}, "https://test.datadoghq.com/")
+ assert actual == %Client{endpoint: "https://test.datadoghq.com/",
+ auth: %{api_key: "1234"}}
+ end
+
+ test "trailing / appended to endpoint without" do
+ actual = Client.new(nil, "https://test.datadoghq.com")
+ assert actual == %Client{endpoint: "https://test.datadoghq.com/", auth: nil}
+ end
+
+end
diff --git a/test/exdatadog/config_test.exs b/test/exdatadog/config_test.exs
new file mode 100644
index 0000000..2be0c38
--- /dev/null
+++ b/test/exdatadog/config_test.exs
@@ -0,0 +1,30 @@
+defmodule Exdatadog.Config.Test do
+ @moduledoc """
+ Provides tests for Exdatadog.Config
+ """
+ use ExUnit.Case
+
+ import Exdatadog.Config
+
+ setup_all do
+ System.put_env("TEST_VAR", "BAR")
+ Application.put_env(:test_app, :test_key, {:system, "TEST_VAR"})
+ Application.put_env(:test_app, :test_foo, "FOO")
+
+ on_exit fn ->
+ System.delete_env("TEST_VAR")
+ Application.delete_env(:test_app, :test_key)
+ Application.delete_env(:test_app, :test_foo)
+ end
+
+ end
+
+ test "can read variable from application settings" do
+ assert get_env_var(:test_app, :test_foo) == "FOO"
+ end
+
+ test "can read environment variables for settings" do
+ assert get_env_var(:test_app, :test_key) == "BAR"
+ end
+
+end
diff --git a/test/exdatadog_test.exs b/test/exdatadog_test.exs
new file mode 100644
index 0000000..5e752ab
--- /dev/null
+++ b/test/exdatadog_test.exs
@@ -0,0 +1,64 @@
+defmodule ExdatadogTest do
+ @moduledoc """
+ Provides tests for the Exdatadog module
+ """
+ use ExUnit.Case
+
+ alias HTTPoison.Response
+ import Exdatadog
+
+ doctest Exdatadog
+
+ setup_all do
+ :meck.new(Poison, [:no_link])
+
+ on_exit fn ->
+ :meck.unload(Poison)
+ end
+ end
+
+ test "auth_params using api_key" do
+ assert auth_params(%{api_key: "1234"}) == [api_key: "1234"]
+ end
+
+ test "auth_params using api_key and app_key" do
+ expected = [application_key: "abcd", api_key: "1234"]
+ assert auth_params(%{api_key: "1234", app_key: "abcd"}) == expected
+ end
+
+ test "auth_params with no auth" do
+ assert auth_params(%{}) == []
+ assert auth_params(nil) == []
+ end
+
+ test "process_response with 200" do
+ assert process_response(%Response{status_code: 200,
+ headers: %{},
+ body: "json"}) == {200, "json"}
+ assert :meck.validate(Poison)
+ end
+
+ test "process_response with non-200" do
+ assert process_response(%Response{status_code: 404,
+ headers: %{},
+ body: "json"}) == {404, "json"}
+
+ assert :meck.validate(Poison)
+ end
+
+ test "process_resposne_body with nil body" do
+ assert process_response_body("") == nil
+ end
+
+ test "process_response_body with content" do
+ :meck.expect(Poison, :decode!, 1, :decoded_json)
+ assert process_response_body("json") == :decoded_json
+ end
+
+ test "process_response with empty body" do
+ assert process_response(%Response{status_code: 202,
+ headers: %{},
+ body: nil}) == {202, nil}
+ end
+
+end
diff --git a/test/test_helper.exs b/test/test_helper.exs
new file mode 100644
index 0000000..869559e
--- /dev/null
+++ b/test/test_helper.exs
@@ -0,0 +1 @@
+ExUnit.start()