From 1d2fb373425a81e38afd5cd21e1776ff100d9a79 Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Tue, 26 Nov 2019 09:12:29 -0800 Subject: [PATCH 01/14] WIP --- lib/k8s/client/runner/base.ex | 40 +++++++++++++++++------- lib/k8s/middleware.ex | 25 +++++++++++++++ lib/k8s/middleware/registry.ex | 9 ++++++ lib/k8s/middleware/request.ex | 18 +++++++++++ lib/k8s/middleware/request/base_url.ex | 18 +++++++++++ lib/k8s/middleware/request/initialize.ex | 25 +++++++++++++++ test/k8s/middleware_test.exs | 13 ++++++++ 7 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 lib/k8s/middleware.ex create mode 100644 lib/k8s/middleware/registry.ex create mode 100644 lib/k8s/middleware/request.ex create mode 100644 lib/k8s/middleware/request/base_url.ex create mode 100644 lib/k8s/middleware/request/initialize.ex create mode 100644 test/k8s/middleware_test.exs diff --git a/lib/k8s/client/runner/base.ex b/lib/k8s/client/runner/base.ex index ffc100a9..1cf945e1 100644 --- a/lib/k8s/client/runner/base.ex +++ b/lib/k8s/client/runner/base.ex @@ -80,21 +80,42 @@ defmodule K8s.Client.Runner.Base do run(operation, cluster_name, operation.data, opts) end + def apply_middlewares(_cluster_name, headers, body) do + middlewares = [ + fn headers, body -> + IO.puts("Inspecting some body: #{inspect(body)}") + [headers, body] + end + ] + + state_args = [headers, body] + + Enum.reduce(middlewares, state_args, fn middleware, args -> + apply(middleware, args) + end) + end + @doc """ Run an operation with an alternative HTTP Body (map) and pass `opts` to HTTPoison. See `run/2` """ @spec run(Operation.t(), atom, map(), keyword()) :: result_t - def run(%Operation{} = operation, cluster_name, body, opts \\ []) do - with {:ok, url} <- Cluster.url_for(operation, cluster_name), - {:ok, conn} <- Cluster.conn(cluster_name), - {:ok, request_options} <- RequestOptions.generate(conn), - {:ok, http_body} <- encode(body, operation.method) do - http_headers = K8s.http_provider().headers(operation.method, request_options) + def run(%Operation{} = operation, cluster, body, opts \\ []) do + with {:ok, url} <- Cluster.url_for(operation, cluster), + req <- %K8s.Middleware.Request{cluster: cluster, method: operation.method}, + {:ok, req} <- K8s.Middleware.Request.Initialize.call(req) do + + # ^ replace above with apply_middleware(cluster) + + # Operation.data and body are deconstructed above @ L79... + [http_headers, raw_body] = apply_middlewares(cluster, req.headers, body) + + {:ok, http_body} = encode(raw_body, operation.method) http_opts_params = build_http_params(opts[:params], operation.label_selector) opts_with_selector_params = Keyword.put(opts, :params, http_opts_params) - http_opts = Keyword.merge([ssl: request_options.ssl_options], opts_with_selector_params) + + http_opts = Keyword.merge(req.opts, opts_with_selector_params) K8s.http_provider().request( operation.method, @@ -107,10 +128,7 @@ defmodule K8s.Client.Runner.Base do end @spec encode(any(), atom()) :: {:ok, binary} | {:error, any} - def encode(body, http_method) when http_method in [:put, :patch, :post] do - Jason.encode(body) - end - + def encode(body, http_method) when http_method in [:put, :patch, :post], do: Jason.encode(body) def encode(_, _), do: {:ok, ""} @spec build_http_params(nil | keyword | map, nil | K8s.Selector.t()) :: map() diff --git a/lib/k8s/middleware.ex b/lib/k8s/middleware.ex new file mode 100644 index 00000000..c52fc3b0 --- /dev/null +++ b/lib/k8s/middleware.ex @@ -0,0 +1,25 @@ +# defmodule K8s.Middleware do +# use Agent + +# @doc """ + +# """ +# @spec start_link(map) :: :ok +# def start_link(%{} = middlwares) do +# Agent.start_link(fn -> middlwares end, name: __MODULE__) +# end + +# def list(cluster_name) do +# Agent.get(__MODULE__, fn state -> Map.get(state, cluster_name, []) end) +# end + +# def register(cluster_name, ) + +# def value do +# Agent.get(__MODULE__, & &1) +# end + +# def increment do +# Agent.update(__MODULE__, &(&1 + 1)) +# end +# end diff --git a/lib/k8s/middleware/registry.ex b/lib/k8s/middleware/registry.ex new file mode 100644 index 00000000..8e2b18f3 --- /dev/null +++ b/lib/k8s/middleware/registry.ex @@ -0,0 +1,9 @@ +# Registry should be the agent... MIddleware should be the API +# K8s.Middleware.request(cluster_name, ...?) +# Adds a piece of middleware to the stack +# K8s.Middleware.Registry.add(cluster_name, :request, func_or_module) +# +# K8s.Middleware.Registry.defaults(:request) +# +# Replaces the existing stack, including defaults +# K8s.Middleware.Registry.set(cluster_name, :request, list(func_or_module)) diff --git a/lib/k8s/middleware/request.ex b/lib/k8s/middleware/request.ex new file mode 100644 index 00000000..dca949a0 --- /dev/null +++ b/lib/k8s/middleware/request.ex @@ -0,0 +1,18 @@ +defmodule K8s.Middleware.Request do + @moduledoc "HTTP Request middleware" + + @typedoc "MIddleware Request type" + @type t :: %__MODULE__{ + cluster: atom(), + method: atom(), + url: String.t(), + body: String.t() | map() | list(map()) | nil, + headers: Keyword.t() | nil, + opts: Keyword.t() | nil + } + + defstruct cluster: nil, method: nil, url: nil, body: nil, headers: [], opts: [] + + @doc "Request middleware callback" + @callback call(t()) :: {:ok, t()} | :error +end diff --git a/lib/k8s/middleware/request/base_url.ex b/lib/k8s/middleware/request/base_url.ex new file mode 100644 index 00000000..5f2673d1 --- /dev/null +++ b/lib/k8s/middleware/request/base_url.ex @@ -0,0 +1,18 @@ +# defmodule K8s.Middleware.Request.BaseURL do +# @behaviour K8s.Middleware.Request + +# @doc """ + +# ## Examples +# iex> conn = K8s.Conn.from_file("./test/support/kube-config.yaml") +# ...> K8s.Cluster.Registry.add(:test_cluster, conn) +# ...> request = %K8s.Middleware.Request{cluster: :test_cluster} +# ...> K8s.Middleware.Request.BaseURL.call(request) +# {:ok, %K8s.Middleware.Request{cluster: :test_cluster, url: "https://localhost:6443"}} +# """ +# @impl true +# def call(%K8s.Middleware.Request{} = req) do +# {:ok, url} <- Cluster.url_for(operation, cluster_name) +# {:ok, req} +# end +# end diff --git a/lib/k8s/middleware/request/initialize.ex b/lib/k8s/middleware/request/initialize.ex new file mode 100644 index 00000000..9fd7ccb0 --- /dev/null +++ b/lib/k8s/middleware/request/initialize.ex @@ -0,0 +1,25 @@ +defmodule K8s.Middleware.Request.Initialize do + @behaviour K8s.Middleware.Request + @doc """ + + ## Examples + iex> conn = K8s.Conn.from_file("./test/support/kube-config.yaml") + ...> K8s.Cluster.Registry.add(:test_cluster, conn) + ...> request = %K8s.Middleware.Request{cluster: :test_cluster} + ...> K8s.Middleware.Request.Initialize.call(request) + {:ok, %K8s.Middleware.Request{cluster: :test_cluster, headers: [{"Accept", "application/json"}, {"Content-Type", "application/json"}], opts: [ssl: [cert: ""]]}} + """ + @impl true + def call(%K8s.Middleware.Request{cluster: cluster, method: method, headers: headers, opts: opts} = req) do + with {:ok, conn} <- K8s.Cluster.conn(cluster), + {:ok, request_options} <- K8s.Conn.RequestOptions.generate(conn) do + new_headers = K8s.http_provider().headers(method, request_options) + updated_headers = Keyword.merge(headers, new_headers) + updated_opts = Keyword.merge([ssl: request_options.ssl_options], opts) + + updated_request = %K8s.Middleware.Request{req| headers: updated_headers, opts: updated_opts} + + {:ok, updated_request} + end + end +end diff --git a/test/k8s/middleware_test.exs b/test/k8s/middleware_test.exs new file mode 100644 index 00000000..56ed6b12 --- /dev/null +++ b/test/k8s/middleware_test.exs @@ -0,0 +1,13 @@ +defmodule K8s.MiddlewareTest do + use ExUnit.Case, async: true + doctest K8s.Middleware.Request.Initialize + + # TODO: + # # K8s.Middleware.Request.EncodeBody + # # K8s.Middleware.Request.DefaultParams + # # K8s.Middleware.Request.DefaultHTTPOpts + # # K8s.Middleware.Request.BaseURL (DiscoveryCluster?) + # <- Actually this step is Cluster.url_for ... includes path... + # <- May need to visit after JIT Registry + # Decide on K8s.Middleware behavior callback error types... {:error, t()} | {:error, String.t(), t()} +end From bfb4d52aa196575108fafdfb239f75e897aad4e5 Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 12:07:22 -0800 Subject: [PATCH 02/14] Base.run/5 abstracted to middleware Refactored Base to use a middleware-based approach to handling requests. This is still a WIP. * [ ] Cluster middleware registry * [ ] Middleware error handling --- Makefile | 5 +- lib/k8s/client/runner/base.ex | 78 +++++++++---------- lib/k8s/middleware.ex | 17 +++- lib/k8s/middleware/request.ex | 3 +- lib/k8s/middleware/request/encode_body.ex | 25 ++++++ lib/k8s/middleware/request/initialize.ex | 22 ++---- test/k8s/client/runner/base_test.exs | 11 ++- .../middleware/request/encode_body_test.exs | 28 +++++++ .../middleware/request/initialize_test.exs | 23 ++++++ test/k8s/middleware_test.exs | 13 ---- 10 files changed, 150 insertions(+), 75 deletions(-) create mode 100644 lib/k8s/middleware/request/encode_body.ex create mode 100644 test/k8s/middleware/request/encode_body_test.exs create mode 100644 test/k8s/middleware/request/initialize_test.exs delete mode 100644 test/k8s/middleware_test.exs diff --git a/Makefile b/Makefile index daa30558..cd998f7c 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,9 @@ help: ## Show this help help: @grep -E '^[\/a-zA-Z0-9._%-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' +quality: ## Run code quality and test targets +quality: lint test analyze + clean: ## Remove build/doc dirs rm -rf _build rm -rf cover @@ -33,7 +36,7 @@ test/master: ## Run test suite against master K8S_SPEC=${MASTER_SWAGGER_PATH} mix test test/all: ## Run full test suite against 1.10+ -test/all: test/1.10 test/1.11 test/1.12 test/1.13 test/1.14 test/1.15 +test/all: test/1.10 test/1.11 test/1.12 test/1.13 test/1.14 test/1.15 test/%: ## Run full test suite against a specific k8s version K8S_SPEC=test/support/swagger/$*.json mix test diff --git a/lib/k8s/client/runner/base.ex b/lib/k8s/client/runner/base.ex index 1cf945e1..ab71d96a 100644 --- a/lib/k8s/client/runner/base.ex +++ b/lib/k8s/client/runner/base.ex @@ -6,8 +6,8 @@ defmodule K8s.Client.Runner.Base do @type result_t :: {:ok, map() | reference()} | {:error, atom} | {:error, binary()} alias K8s.Cluster - alias K8s.Conn.RequestOptions alias K8s.Operation + alias K8s.Middleware.Request @doc """ Runs a `K8s.Operation`. @@ -72,64 +72,62 @@ defmodule K8s.Client.Runner.Base do @doc """ Run an operation and pass `opts` to HTTPoison. + Destructures `Operation` data and passes as the HTTP body. See `run/2` """ - @spec run(Operation.t(), binary | atom, keyword()) :: result_t + @spec run(Operation.t(), atom, keyword()) :: result_t def run(%Operation{} = operation, cluster_name, opts) when is_list(opts) do run(operation, cluster_name, operation.data, opts) end - def apply_middlewares(_cluster_name, headers, body) do - middlewares = [ - fn headers, body -> - IO.puts("Inspecting some body: #{inspect(body)}") - [headers, body] - end - ] - - state_args = [headers, body] - - Enum.reduce(middlewares, state_args, fn middleware, args -> - apply(middleware, args) - end) - end - @doc """ - Run an operation with an alternative HTTP Body (map) and pass `opts` to HTTPoison. + Run an operation with an HTTP Body (map) and pass `opts` to HTTPoison. See `run/2` """ @spec run(Operation.t(), atom, map(), keyword()) :: result_t def run(%Operation{} = operation, cluster, body, opts \\ []) do - with {:ok, url} <- Cluster.url_for(operation, cluster), - req <- %K8s.Middleware.Request{cluster: cluster, method: operation.method}, - {:ok, req} <- K8s.Middleware.Request.Initialize.call(req) do - - # ^ replace above with apply_middleware(cluster) + with req <- new_request(cluster, operation, body, opts), + {:ok, url} <- Cluster.url_for(operation, cluster), + # TODO: handle error return here type + {:ok, req} <- apply_middleware(req) do + K8s.http_provider().request(req.method, url, req.body, req.headers, req.opts) + end + end - # Operation.data and body are deconstructed above @ L79... - [http_headers, raw_body] = apply_middlewares(cluster, req.headers, body) + # TODO: handle error return here type + @spec apply_middleware(Request.t()) :: {:ok, Request.t()} + defp apply_middleware(req) do + middlewares = K8s.Middleware.list(:request, req.cluster) - {:ok, http_body} = encode(raw_body, operation.method) + # TODO: handle error return here type + # case( K8s.Middleware.Request | K8s.Middleware.Error(mw, req, actual_error)) - http_opts_params = build_http_params(opts[:params], operation.label_selector) - opts_with_selector_params = Keyword.put(opts, :params, http_opts_params) + updated_request = + Enum.reduce_while(middlewares, req, fn middleware, req -> + case apply(middleware, :call, [req]) do + {:ok, updated_request} -> + {:cont, updated_request} - http_opts = Keyword.merge(req.opts, opts_with_selector_params) + # TODO: handle error return here type + _error -> + {:halt, :handler_error_return_type_here} + end + end) - K8s.http_provider().request( - operation.method, - url, - http_body, - http_headers, - http_opts - ) - end + {:ok, updated_request} end - @spec encode(any(), atom()) :: {:ok, binary} | {:error, any} - def encode(body, http_method) when http_method in [:put, :patch, :post], do: Jason.encode(body) - def encode(_, _), do: {:ok, ""} + @spec new_request(atom(), K8s.Operation.t(), list(map()) | map() | binary() | nil, Keyword.t()) :: + Request.t() + defp new_request(cluster, %Operation{} = operation, body, opts) do + req = %Request{cluster: cluster, method: operation.method, body: body} + http_opts_params = build_http_params(opts[:params], operation.label_selector) + opts_with_selector_params = Keyword.put(opts, :params, http_opts_params) + + http_opts = Keyword.merge(req.opts, opts_with_selector_params) + %Request{req | opts: http_opts} + end @spec build_http_params(nil | keyword | map, nil | K8s.Selector.t()) :: map() defp build_http_params(nil, nil), do: %{} diff --git a/lib/k8s/middleware.ex b/lib/k8s/middleware.ex index c52fc3b0..25e171dd 100644 --- a/lib/k8s/middleware.ex +++ b/lib/k8s/middleware.ex @@ -1,4 +1,17 @@ -# defmodule K8s.Middleware do +defmodule K8s.Middleware do + @moduledoc "Interface for interacting with cluster middleware" + + @doc "Retrieve a list of middleware registered to a cluster" + @spec list(:request | :response, atom()) :: list(module()) + def list(:request, _cluster) do + [ + K8s.Middleware.Request.Initialize, + K8s.Middleware.Request.EncodeBody + ] + end +end + +# Agent should be in Middleware.Registry # use Agent # @doc """ @@ -21,5 +34,5 @@ # def increment do # Agent.update(__MODULE__, &(&1 + 1)) -# end +# end # end diff --git a/lib/k8s/middleware/request.ex b/lib/k8s/middleware/request.ex index dca949a0..b30a085c 100644 --- a/lib/k8s/middleware/request.ex +++ b/lib/k8s/middleware/request.ex @@ -1,7 +1,7 @@ defmodule K8s.Middleware.Request do @moduledoc "HTTP Request middleware" - @typedoc "MIddleware Request type" + @typedoc "Middleware Request type" @type t :: %__MODULE__{ cluster: atom(), method: atom(), @@ -14,5 +14,6 @@ defmodule K8s.Middleware.Request do defstruct cluster: nil, method: nil, url: nil, body: nil, headers: [], opts: [] @doc "Request middleware callback" + # TODO: handle error return here type @callback call(t()) :: {:ok, t()} | :error end diff --git a/lib/k8s/middleware/request/encode_body.ex b/lib/k8s/middleware/request/encode_body.ex new file mode 100644 index 00000000..16b2173a --- /dev/null +++ b/lib/k8s/middleware/request/encode_body.ex @@ -0,0 +1,25 @@ +defmodule K8s.Middleware.Request.EncodeBody do + @moduledoc """ + Naive JSON body encoder. + + Encodes JSON payloads when given an modifiying HTTP verb, otherwise returns an empty string. + """ + @behaviour K8s.Middleware.Request + alias K8s.Middleware.Request + + @impl true + def call(%Request{method: method, body: body} = req) do + case encode(body, method) do + {:ok, encoded_body} -> + req = %Request{req | body: encoded_body} + {:ok, req} + + error -> + error + end + end + + @spec encode(any(), atom()) :: {:ok, binary} | {:error, any} + defp encode(body, http_method) when http_method in [:put, :patch, :post], do: Jason.encode(body) + defp encode(_, _), do: {:ok, ""} +end diff --git a/lib/k8s/middleware/request/initialize.ex b/lib/k8s/middleware/request/initialize.ex index 9fd7ccb0..d58095f4 100644 --- a/lib/k8s/middleware/request/initialize.ex +++ b/lib/k8s/middleware/request/initialize.ex @@ -1,24 +1,18 @@ defmodule K8s.Middleware.Request.Initialize do + @moduledoc """ + Initializes a request with connection details (header and HTTPoison opts) from `K8s.Conn.RequestOptions` + """ @behaviour K8s.Middleware.Request - @doc """ + alias K8s.Middleware.Request - ## Examples - iex> conn = K8s.Conn.from_file("./test/support/kube-config.yaml") - ...> K8s.Cluster.Registry.add(:test_cluster, conn) - ...> request = %K8s.Middleware.Request{cluster: :test_cluster} - ...> K8s.Middleware.Request.Initialize.call(request) - {:ok, %K8s.Middleware.Request{cluster: :test_cluster, headers: [{"Accept", "application/json"}, {"Content-Type", "application/json"}], opts: [ssl: [cert: ""]]}} - """ @impl true - def call(%K8s.Middleware.Request{cluster: cluster, method: method, headers: headers, opts: opts} = req) do + def call(%Request{cluster: cluster, method: method, headers: headers, opts: opts} = req) do with {:ok, conn} <- K8s.Cluster.conn(cluster), {:ok, request_options} <- K8s.Conn.RequestOptions.generate(conn) do - new_headers = K8s.http_provider().headers(method, request_options) - updated_headers = Keyword.merge(headers, new_headers) + request_option_headers = K8s.http_provider().headers(method, request_options) + updated_headers = Keyword.merge(headers, request_option_headers) updated_opts = Keyword.merge([ssl: request_options.ssl_options], opts) - - updated_request = %K8s.Middleware.Request{req| headers: updated_headers, opts: updated_opts} - + updated_request = %Request{req | headers: updated_headers, opts: updated_opts} {:ok, updated_request} end end diff --git a/test/k8s/client/runner/base_test.exs b/test/k8s/client/runner/base_test.exs index 608d58ae..f5d9f43b 100644 --- a/test/k8s/client/runner/base_test.exs +++ b/test/k8s/client/runner/base_test.exs @@ -15,7 +15,7 @@ defmodule K8s.Client.Runner.BaseTest do def request(:get, @namespaced_url, _, _, _), do: render(nil) - def request(:post, @namespaced_url, _, _, _), do: render(nil) + def request(:post, @namespaced_url, body, _, _), do: render(body) def request(:get, @namespaced_url <> "/test", _body, _headers, _opts) do render(nil) @@ -45,11 +45,11 @@ defmodule K8s.Client.Runner.BaseTest do def request( :post, @base_url <> "/api/v1/namespaces/default/pods/nginx/eviction", - _body, + body, _headers, _opts ) do - render(nil) + render(body) end end @@ -98,7 +98,10 @@ defmodule K8s.Client.Runner.BaseTest do labels = %{"env" => "test"} body = put_in(make_namespace("test"), ["metadata", "labels"], labels) - assert {:ok, _} = Base.run(operation, cluster, body) + assert {:ok, body} = Base.run(operation, cluster, body) + + assert body == + ~s({"apiVersion":"v1","kind":"Namespace","metadata":{"labels":{"env":"test"},"name":"test"}}) end test "running an operation with a custom HTTP body and options", %{ diff --git a/test/k8s/middleware/request/encode_body_test.exs b/test/k8s/middleware/request/encode_body_test.exs new file mode 100644 index 00000000..af7904d9 --- /dev/null +++ b/test/k8s/middleware/request/encode_body_test.exs @@ -0,0 +1,28 @@ +defmodule K8s.Middleware.Request.EncodeBodyTest do + use ExUnit.Case, async: true + + test "encode JSON payloads when given a modifying HTTP verb" do + data = %{"hello" => "world"} + request = %K8s.Middleware.Request{body: data, method: :put} + {:ok, %{body: body}} = K8s.Middleware.Request.EncodeBody.call(request) + + assert body == ~s({"hello":"world"}) + end + + test "returns an empty string if not a modifying verb" do + data = %{"hello" => "world"} + request = %K8s.Middleware.Request{body: data, method: :get} + {:ok, %{body: body}} = K8s.Middleware.Request.EncodeBody.call(request) + + assert body == "" + end + + # TODO: handle error return here type + # test "failure" do + # data = [should: :fail] + # request = %K8s.Middleware.Request{body: data, method: :post} + # {:ok, %{body: body}} = K8s.Middleware.Request.EncodeBody.call(request) + + # assert body == "" + # end +end diff --git a/test/k8s/middleware/request/initialize_test.exs b/test/k8s/middleware/request/initialize_test.exs new file mode 100644 index 00000000..b570c4a3 --- /dev/null +++ b/test/k8s/middleware/request/initialize_test.exs @@ -0,0 +1,23 @@ +defmodule K8s.Middleware.Request.InitializeTest do + use ExUnit.Case, async: true + + test "initializes a request headers from K8s.Conn.RequestOptions" do + conn = K8s.Conn.from_file("./test/support/kube-config.yaml") + K8s.Cluster.Registry.add(:test_cluster, conn) + + request = %K8s.Middleware.Request{cluster: :test_cluster} + {:ok, %{headers: headers}} = K8s.Middleware.Request.Initialize.call(request) + + assert headers == [{"Accept", "application/json"}, {"Content-Type", "application/json"}] + end + + test "initializes a HTTPoison options from K8s.Conn.RequestOptions" do + conn = K8s.Conn.from_file("./test/support/kube-config.yaml") + K8s.Cluster.Registry.add(:test_cluster, conn) + + request = %K8s.Middleware.Request{cluster: :test_cluster} + {:ok, %{opts: opts}} = K8s.Middleware.Request.Initialize.call(request) + + assert Keyword.has_key?(opts, :ssl) + end +end diff --git a/test/k8s/middleware_test.exs b/test/k8s/middleware_test.exs deleted file mode 100644 index 56ed6b12..00000000 --- a/test/k8s/middleware_test.exs +++ /dev/null @@ -1,13 +0,0 @@ -defmodule K8s.MiddlewareTest do - use ExUnit.Case, async: true - doctest K8s.Middleware.Request.Initialize - - # TODO: - # # K8s.Middleware.Request.EncodeBody - # # K8s.Middleware.Request.DefaultParams - # # K8s.Middleware.Request.DefaultHTTPOpts - # # K8s.Middleware.Request.BaseURL (DiscoveryCluster?) - # <- Actually this step is Cluster.url_for ... includes path... - # <- May need to visit after JIT Registry - # Decide on K8s.Middleware behavior callback error types... {:error, t()} | {:error, String.t(), t()} -end From e13381c6fd81861185eb9ce5ea0cd3ddb63a1336 Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 12:43:14 -0800 Subject: [PATCH 03/14] middleware error handling --- CHANGELOG.md | 3 ++ lib/k8s/client/runner/base.ex | 29 ++------------ lib/k8s/middleware.ex | 39 ++++++++++++++++++- lib/k8s/middleware/error.ex | 17 ++++++++ lib/k8s/middleware/request.ex | 3 +- .../middleware/request/encode_body_test.exs | 19 +++++---- 6 files changed, 73 insertions(+), 37 deletions(-) create mode 100644 lib/k8s/middleware/error.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a77658..3e3966bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added +- Request middleware support + ## [0.4.0] - 2019-08-29 ### Changed diff --git a/lib/k8s/client/runner/base.ex b/lib/k8s/client/runner/base.ex index ab71d96a..c8bc2a31 100644 --- a/lib/k8s/client/runner/base.ex +++ b/lib/k8s/client/runner/base.ex @@ -3,7 +3,8 @@ defmodule K8s.Client.Runner.Base do Base HTTP processor for `K8s.Client` """ - @type result_t :: {:ok, map() | reference()} | {:error, atom} | {:error, binary()} + @type result_t :: + {:ok, map() | reference()} | {:error, atom | binary() | K8s.Middleware.Error.t()} alias K8s.Cluster alias K8s.Operation @@ -89,35 +90,11 @@ defmodule K8s.Client.Runner.Base do def run(%Operation{} = operation, cluster, body, opts \\ []) do with req <- new_request(cluster, operation, body, opts), {:ok, url} <- Cluster.url_for(operation, cluster), - # TODO: handle error return here type - {:ok, req} <- apply_middleware(req) do + {:ok, req} <- K8s.Middleware.run(req) do K8s.http_provider().request(req.method, url, req.body, req.headers, req.opts) end end - # TODO: handle error return here type - @spec apply_middleware(Request.t()) :: {:ok, Request.t()} - defp apply_middleware(req) do - middlewares = K8s.Middleware.list(:request, req.cluster) - - # TODO: handle error return here type - # case( K8s.Middleware.Request | K8s.Middleware.Error(mw, req, actual_error)) - - updated_request = - Enum.reduce_while(middlewares, req, fn middleware, req -> - case apply(middleware, :call, [req]) do - {:ok, updated_request} -> - {:cont, updated_request} - - # TODO: handle error return here type - _error -> - {:halt, :handler_error_return_type_here} - end - end) - - {:ok, updated_request} - end - @spec new_request(atom(), K8s.Operation.t(), list(map()) | map() | binary() | nil, Keyword.t()) :: Request.t() defp new_request(cluster, %Operation{} = operation, body, opts) do diff --git a/lib/k8s/middleware.ex b/lib/k8s/middleware.ex index 25e171dd..8a87f371 100644 --- a/lib/k8s/middleware.ex +++ b/lib/k8s/middleware.ex @@ -1,14 +1,49 @@ defmodule K8s.Middleware do @moduledoc "Interface for interacting with cluster middleware" + alias K8s.Middleware.{Error, Request} + @doc "Retrieve a list of middleware registered to a cluster" @spec list(:request | :response, atom()) :: list(module()) def list(:request, _cluster) do [ - K8s.Middleware.Request.Initialize, - K8s.Middleware.Request.EncodeBody + Request.Initialize, + Request.EncodeBody ] end + + @doc """ + Applies middlewares registered to a `K8s.Cluster` to a `K8s.Middleware.Request` + """ + @spec run(Request.t()) :: {:ok, Request.t()} | {:error, Error.t()} + def run(req) do + middlewares = list(:request, req.cluster) + + result = + Enum.reduce_while(middlewares, req, fn middleware, req -> + case apply(middleware, :call, [req]) do + {:ok, updated_request} -> + {:cont, updated_request} + + {:error, error} -> + {:halt, error(middleware, req, error)} + end + end) + + case result do + %Request{} -> {:ok, result} + %Error{} -> {:error, result} + end + end + + @spec error(module(), Request.t(), any()) :: Error.t() + defp error(middleware, req, error) do + %K8s.Middleware.Error{ + middleware: middleware, + error: error, + request: req + } + end end # Agent should be in Middleware.Registry diff --git a/lib/k8s/middleware/error.ex b/lib/k8s/middleware/error.ex new file mode 100644 index 00000000..dc00439e --- /dev/null +++ b/lib/k8s/middleware/error.ex @@ -0,0 +1,17 @@ +defmodule K8s.Middleware.Error do + @moduledoc "Encapsulates middleware process errors" + + @typedoc """ + Middleware processing error + + * `middleware` middleware module that caused the error + * `request` `K8s.Middleware.Request` + * `error` actual error, can be `any()` type + """ + @type t :: %__MODULE__{ + request: K8s.Middleware.Request.t() | nil, + middleware: module(), + error: any() + } + defstruct [:request, :middleware, :error] +end diff --git a/lib/k8s/middleware/request.ex b/lib/k8s/middleware/request.ex index b30a085c..b0114654 100644 --- a/lib/k8s/middleware/request.ex +++ b/lib/k8s/middleware/request.ex @@ -14,6 +14,5 @@ defmodule K8s.Middleware.Request do defstruct cluster: nil, method: nil, url: nil, body: nil, headers: [], opts: [] @doc "Request middleware callback" - # TODO: handle error return here type - @callback call(t()) :: {:ok, t()} | :error + @callback call(t()) :: {:ok, t()} | {:error, any()} end diff --git a/test/k8s/middleware/request/encode_body_test.exs b/test/k8s/middleware/request/encode_body_test.exs index af7904d9..cf362fb2 100644 --- a/test/k8s/middleware/request/encode_body_test.exs +++ b/test/k8s/middleware/request/encode_body_test.exs @@ -17,12 +17,17 @@ defmodule K8s.Middleware.Request.EncodeBodyTest do assert body == "" end - # TODO: handle error return here type - # test "failure" do - # data = [should: :fail] - # request = %K8s.Middleware.Request{body: data, method: :post} - # {:ok, %{body: body}} = K8s.Middleware.Request.EncodeBody.call(request) + test "returns an error when the body cannot be encoded" do + data = [should: :fail] + request = %K8s.Middleware.Request{body: data, method: :post} + result = K8s.Middleware.Request.EncodeBody.call(request) - # assert body == "" - # end + assert result == + {:error, + %Protocol.UndefinedError{ + description: "Jason.Encoder protocol must always be explicitly implemented", + protocol: Jason.Encoder, + value: {:should, :fail} + }} + end end From 653bd353bc3a32e39f96e0f56ad1c5bf34366883 Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 12:56:00 -0800 Subject: [PATCH 04/14] wip - registry --- Makefile | 2 +- coveralls.json | 3 ++- lib/k8s/middleware.ex | 37 ++++++--------------------- lib/k8s/middleware/registry.ex | 30 +++++++++++++++------- test/k8s/middleware/registry_test.exs | 3 +++ test/k8s/middleware_test.exs | 3 +++ 6 files changed, 38 insertions(+), 40 deletions(-) create mode 100644 test/k8s/middleware/registry_test.exs create mode 100644 test/k8s/middleware_test.exs diff --git a/Makefile b/Makefile index cd998f7c..e921f9f6 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ help: @grep -E '^[\/a-zA-Z0-9._%-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' quality: ## Run code quality and test targets -quality: lint test analyze +quality: cov lint analyze clean: ## Remove build/doc dirs rm -rf _build diff --git a/coveralls.json b/coveralls.json index 6dba97d0..c656300f 100644 --- a/coveralls.json +++ b/coveralls.json @@ -13,6 +13,7 @@ "def.+(.+\/\/.+).+do" ], "coverage_options": { - "treat_no_relevant_lines_as_covered": true + "treat_no_relevant_lines_as_covered": true, + "minimum_coverage": 85 } } diff --git a/lib/k8s/middleware.ex b/lib/k8s/middleware.ex index 8a87f371..fddb7de1 100644 --- a/lib/k8s/middleware.ex +++ b/lib/k8s/middleware.ex @@ -3,15 +3,20 @@ defmodule K8s.Middleware do alias K8s.Middleware.{Error, Request} - @doc "Retrieve a list of middleware registered to a cluster" - @spec list(:request | :response, atom()) :: list(module()) - def list(:request, _cluster) do + def defaults(:request) do [ Request.Initialize, Request.EncodeBody ] end + @doc "Retrieve a list of middleware registered to a cluster" + @spec list(:request | :response, atom()) :: list(module()) + def list(:request, _cluster) do + # TODO interact w/ registry + defaults(:request) + end + @doc """ Applies middlewares registered to a `K8s.Cluster` to a `K8s.Middleware.Request` """ @@ -45,29 +50,3 @@ defmodule K8s.Middleware do } end end - -# Agent should be in Middleware.Registry -# use Agent - -# @doc """ - -# """ -# @spec start_link(map) :: :ok -# def start_link(%{} = middlwares) do -# Agent.start_link(fn -> middlwares end, name: __MODULE__) -# end - -# def list(cluster_name) do -# Agent.get(__MODULE__, fn state -> Map.get(state, cluster_name, []) end) -# end - -# def register(cluster_name, ) - -# def value do -# Agent.get(__MODULE__, & &1) -# end - -# def increment do -# Agent.update(__MODULE__, &(&1 + 1)) -# end -# end diff --git a/lib/k8s/middleware/registry.ex b/lib/k8s/middleware/registry.ex index 8e2b18f3..11cf4655 100644 --- a/lib/k8s/middleware/registry.ex +++ b/lib/k8s/middleware/registry.ex @@ -1,9 +1,21 @@ -# Registry should be the agent... MIddleware should be the API -# K8s.Middleware.request(cluster_name, ...?) -# Adds a piece of middleware to the stack -# K8s.Middleware.Registry.add(cluster_name, :request, func_or_module) -# -# K8s.Middleware.Registry.defaults(:request) -# -# Replaces the existing stack, including defaults -# K8s.Middleware.Registry.set(cluster_name, :request, list(func_or_module)) +defmodule K8s.Middleware.Registry do + @moduledoc """ + Cluster middleware registry + """ + use Agent + + @spec start_link(map()) :: :ok + def start_link(registry = %{}) do + Agent.start_link(fn -> registry end, name: __MODULE__) + end + + @doc "Adds a middleware to the end of the middleware stack" + @spec add(atom, :request | :response, module()) :: :ok + def add(cluster, type, middleware) do + end + + @doc "Sets/replaces the middleware stack" + @spec set(atom, :request | :response, list(module())) :: :ok + def set(cluster, type, middlewares) do + end +end diff --git a/test/k8s/middleware/registry_test.exs b/test/k8s/middleware/registry_test.exs new file mode 100644 index 00000000..9394a25a --- /dev/null +++ b/test/k8s/middleware/registry_test.exs @@ -0,0 +1,3 @@ +defmodule K8s.Middleware.RegistryTest do + use ExUnit.Case, async: true +end diff --git a/test/k8s/middleware_test.exs b/test/k8s/middleware_test.exs new file mode 100644 index 00000000..4ee97df6 --- /dev/null +++ b/test/k8s/middleware_test.exs @@ -0,0 +1,3 @@ +defmodule K8s.MiddlewareTest do + use ExUnit.Case, async: true +end From 15820c5533be3f3327befe474ce9c2cee43e5a5f Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 13:27:33 -0800 Subject: [PATCH 05/14] registry state impl --- lib/k8s/application.ex | 6 ++- lib/k8s/middleware/registry.ex | 32 ++++++++++++--- test/k8s/middleware/registry_test.exs | 56 ++++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/lib/k8s/application.ex b/lib/k8s/application.ex index 4e42b73b..b83dd4ce 100644 --- a/lib/k8s/application.ex +++ b/lib/k8s/application.ex @@ -8,7 +8,11 @@ defmodule K8s.Application do :ets.new(K8s.Conn, [:set, :public, :named_table]) :ets.new(K8s.Cluster.Group, [:set, :public, :named_table]) - children = [{K8s.Cluster.Registry, []}] + # TODO: register defaults for each cluster + children = [ + {K8s.Middleware.Registry, []}, + {K8s.Cluster.Registry, []} + ] opts = [strategy: :one_for_one, name: K8s.Supervisor] Supervisor.start_link(children, opts) diff --git a/lib/k8s/middleware/registry.ex b/lib/k8s/middleware/registry.ex index 11cf4655..396b1e42 100644 --- a/lib/k8s/middleware/registry.ex +++ b/lib/k8s/middleware/registry.ex @@ -1,21 +1,41 @@ defmodule K8s.Middleware.Registry do - @moduledoc """ - Cluster middleware registry - """ + @moduledoc "Cluster middleware registry" use Agent - @spec start_link(map()) :: :ok - def start_link(registry = %{}) do - Agent.start_link(fn -> registry end, name: __MODULE__) + @spec start_link(Keyword.t()) :: Agent.on_start() + def start_link(_opts) do + Agent.start_link(fn -> %{} end, name: __MODULE__) end @doc "Adds a middleware to the end of the middleware stack" @spec add(atom, :request | :response, module()) :: :ok def add(cluster, type, middleware) do + Agent.update(__MODULE__, fn registry -> + cluster_middlewares = Map.get(registry, cluster, %{}) + middleware_list = Map.get(cluster_middlewares, type, []) + + updated_middleware_list = middleware_list ++ [middleware] + updated_cluster_middlewares = Map.put(cluster_middlewares, type, updated_middleware_list) + + put_in(registry, [cluster], updated_cluster_middlewares) + end) end @doc "Sets/replaces the middleware stack" @spec set(atom, :request | :response, list(module())) :: :ok def set(cluster, type, middlewares) do + Agent.update(__MODULE__, fn registry -> + cluster_middlewares = Map.get(registry, cluster, %{}) + updated_cluster_middlewares = Map.put(cluster_middlewares, type, middlewares) + + put_in(registry, [cluster], updated_cluster_middlewares) + end) + end + + @doc "Returns middleware stack for a cluster and (request or response)" + @spec list(atom, :request | :response) :: :ok + def list(cluster, type) do + registry = Agent.get(__MODULE__, & &1[cluster]) || %{} + Map.get(registry, type, []) end end diff --git a/test/k8s/middleware/registry_test.exs b/test/k8s/middleware/registry_test.exs index 9394a25a..5a4dda76 100644 --- a/test/k8s/middleware/registry_test.exs +++ b/test/k8s/middleware/registry_test.exs @@ -1,3 +1,57 @@ defmodule K8s.Middleware.RegistryTest do - use ExUnit.Case, async: true + use ExUnit.Case, async: false + alias K8s.Middleware.Registry + + defmodule BarMiddleware do + @behaviour K8s.Middleware.Request + def call(req), do: {:ok, req} + end + + defmodule FooMiddleware do + @behaviour K8s.Middleware.Request + def call(req), do: {:ok, req} + end + + describe "list/2" do + test "returns an empty list when none are registered", %{test: id} do + assert [] == Registry.list(id, :request) + end + + test "returns registered middlewares modules", %{test: id} do + mw = K8s.Middleware.RegistryTest.FooMiddleware + :ok = Registry.add(id, :request, mw) + middlewares = Registry.list(id, :request) + + assert middlewares == [K8s.Middleware.RegistryTest.FooMiddleware] + end + end + + test "adding a middleware", %{test: id} do + mw = K8s.Middleware.RegistryTest.FooMiddleware + :ok = Registry.add(id, :request, mw) + + middlewares = Agent.get(Registry, & &1[id]) + assert middlewares == %{request: [K8s.Middleware.RegistryTest.FooMiddleware]} + end + + test "setting the middleware stack", %{test: id} do + mw = K8s.Middleware.RegistryTest.FooMiddleware + :ok = Registry.add(id, :request, mw) + + stack = [ + K8s.Middleware.RegistryTest.BarMiddleware, + K8s.Middleware.RegistryTest.FooMiddleware + ] + + :ok = Registry.set(id, :request, stack) + + middlewares = Agent.get(Registry, & &1[id]) + + assert middlewares == %{ + request: [ + K8s.Middleware.RegistryTest.BarMiddleware, + K8s.Middleware.RegistryTest.FooMiddleware + ] + } + end end From 99fb305023bfec1c013a60e04191010e7a139c2a Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 13:28:51 -0800 Subject: [PATCH 06/14] dialyzer --- coveralls.json | 2 +- test/k8s/middleware/registry_test.exs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/coveralls.json b/coveralls.json index c656300f..94cff1ad 100644 --- a/coveralls.json +++ b/coveralls.json @@ -14,6 +14,6 @@ ], "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 85 + "minimum_coverage": 88 } } diff --git a/test/k8s/middleware/registry_test.exs b/test/k8s/middleware/registry_test.exs index 5a4dda76..41dbdee0 100644 --- a/test/k8s/middleware/registry_test.exs +++ b/test/k8s/middleware/registry_test.exs @@ -4,11 +4,13 @@ defmodule K8s.Middleware.RegistryTest do defmodule BarMiddleware do @behaviour K8s.Middleware.Request + @impl true def call(req), do: {:ok, req} end defmodule FooMiddleware do @behaviour K8s.Middleware.Request + @impl true def call(req), do: {:ok, req} end From da8ed397e714490d923853bc8a81156bc8bf1a03 Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 13:36:32 -0800 Subject: [PATCH 07/14] typespecs --- lib/k8s/middleware.ex | 9 ++++++++- lib/k8s/middleware/registry.ex | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/k8s/middleware.ex b/lib/k8s/middleware.ex index fddb7de1..f1f83924 100644 --- a/lib/k8s/middleware.ex +++ b/lib/k8s/middleware.ex @@ -3,6 +3,13 @@ defmodule K8s.Middleware do alias K8s.Middleware.{Error, Request} + @typedoc "Middleware type" + @type type_t :: :request | :response + + @typedoc "List of middlewares" + @type stack_t :: list(module()) + + @spec defaults(K8s.Middleware.type_t()) :: stack_t def defaults(:request) do [ Request.Initialize, @@ -11,7 +18,7 @@ defmodule K8s.Middleware do end @doc "Retrieve a list of middleware registered to a cluster" - @spec list(:request | :response, atom()) :: list(module()) + @spec list(type_t, atom()) :: stack_t def list(:request, _cluster) do # TODO interact w/ registry defaults(:request) diff --git a/lib/k8s/middleware/registry.ex b/lib/k8s/middleware/registry.ex index 396b1e42..1e315b77 100644 --- a/lib/k8s/middleware/registry.ex +++ b/lib/k8s/middleware/registry.ex @@ -8,7 +8,7 @@ defmodule K8s.Middleware.Registry do end @doc "Adds a middleware to the end of the middleware stack" - @spec add(atom, :request | :response, module()) :: :ok + @spec add(atom, K8s.Middleware.type_t(), module()) :: :ok def add(cluster, type, middleware) do Agent.update(__MODULE__, fn registry -> cluster_middlewares = Map.get(registry, cluster, %{}) @@ -22,7 +22,7 @@ defmodule K8s.Middleware.Registry do end @doc "Sets/replaces the middleware stack" - @spec set(atom, :request | :response, list(module())) :: :ok + @spec set(atom, K8s.Middleware.type_t(), list(module())) :: :ok def set(cluster, type, middlewares) do Agent.update(__MODULE__, fn registry -> cluster_middlewares = Map.get(registry, cluster, %{}) @@ -33,7 +33,7 @@ defmodule K8s.Middleware.Registry do end @doc "Returns middleware stack for a cluster and (request or response)" - @spec list(atom, :request | :response) :: :ok + @spec list(atom, K8s.Middleware.type_t()) :: K8s.Middleware.stack_t() def list(cluster, type) do registry = Agent.get(__MODULE__, & &1[cluster]) || %{} Map.get(registry, type, []) From 344e818d9bf0711c2cf292e2f59a456cc886a72a Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 13:44:38 -0800 Subject: [PATCH 08/14] auto reg mw defaults at boot --- lib/k8s/cluster/registry.ex | 1 + lib/k8s/middleware.ex | 22 +++++++++------------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/k8s/cluster/registry.ex b/lib/k8s/cluster/registry.ex index 9eeb2623..fb74534b 100644 --- a/lib/k8s/cluster/registry.ex +++ b/lib/k8s/cluster/registry.ex @@ -33,6 +33,7 @@ defmodule K8s.Cluster.Registry do @spec add(atom(), K8s.Conn.t()) :: {:ok, atom()} | {:error, atom()} def add(cluster, conn) do with true <- :ets.insert(K8s.Conn, {cluster, conn}), + :ok <- K8s.Middleware.initialize(cluster), {:ok, resources_by_group} <- Discovery.resources_by_group(cluster) do K8s.Cluster.Group.insert_all(cluster, resources_by_group) K8s.Sys.Event.cluster_registered(%{}, %{cluster: cluster}) diff --git a/lib/k8s/middleware.ex b/lib/k8s/middleware.ex index f1f83924..d9564eb9 100644 --- a/lib/k8s/middleware.ex +++ b/lib/k8s/middleware.ex @@ -10,18 +10,14 @@ defmodule K8s.Middleware do @type stack_t :: list(module()) @spec defaults(K8s.Middleware.type_t()) :: stack_t - def defaults(:request) do - [ - Request.Initialize, - Request.EncodeBody - ] - end - - @doc "Retrieve a list of middleware registered to a cluster" - @spec list(type_t, atom()) :: stack_t - def list(:request, _cluster) do - # TODO interact w/ registry - defaults(:request) + def defaults(:request), do: [Request.Initialize, Request.EncodeBody] + def defaults(:response), do: [] + + @doc "Initialize a clusters middleware stacks" + @spec initialize(atom) :: :ok + def initialize(cluster) do + K8s.Middleware.Registry.set(cluster, :request, defaults(:request)) + K8s.Middleware.Registry.set(cluster, :response, defaults(:response)) end @doc """ @@ -29,7 +25,7 @@ defmodule K8s.Middleware do """ @spec run(Request.t()) :: {:ok, Request.t()} | {:error, Error.t()} def run(req) do - middlewares = list(:request, req.cluster) + middlewares = K8s.Middleware.Registry.list(req.cluster, :request) result = Enum.reduce_while(middlewares, req, fn middleware, req -> From 40b1446d9b6f097fe46ffd0cddbfcdda4c657a9d Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 14:03:05 -0800 Subject: [PATCH 09/14] httpoison update for hackney honor ciphor issue --- README.md | 1 + mix.lock | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f080c02c..7c7d87aa 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ * Kube config file parsing * Certificate and service account based auth * Pluggable auth providers +* HTTP Request middleware * Macro free; fast compile & fast startup ## Installation diff --git a/mix.lock b/mix.lock index fd1e309b..c80cc5fd 100644 --- a/mix.lock +++ b/mix.lock @@ -2,36 +2,36 @@ "assertions": {:hex, :assertions, "0.14.1", "352391bbcecbfce7d4594df1267ee4ca7a8b79981859a4befdf62dd798769c42", [:mix], [], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"}, - "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"}, - "credo": {:hex, :credo, "1.0.0", "aaa40fdd0543a0cf8080e8c5949d8c25f0a24e4fc8c1d83d06c388f5e5e0ea42", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "dialyxir": {:hex, :dialyxir, "1.0.0-rc.6", "78e97d9c0ff1b5521dd68041193891aebebce52fc3b93463c0a6806874557d7d", [:mix], [{:erlex, "~> 0.2.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, - "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, - "erlex": {:hex, :erlex, "0.2.1", "cee02918660807cbba9a7229cae9b42d1c6143b768c781fa6cee1eaf03ad860b", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "excoveralls": {:hex, :excoveralls, "0.10.4", "b86230f0978bbc630c139af5066af7cd74fd16536f71bc047d1037091f9f63a9", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"}, - "hackney": {:hex, :hackney, "1.15.0", "287a5d2304d516f63e56c469511c42b016423bcb167e61b611f6bad47e3ca60e", [:rebar3], [{:certifi, "2.4.2", [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.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "credo": {:hex, :credo, "1.0.5", "fdea745579f8845315fe6a3b43e2f9f8866839cfbc8562bb72778e9fdaa94214", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.7", "6287f8f2cb45df8584317a4be1075b8c9b8a69de8eeb82b4d9e6c761cf2664cd", [:mix], [{:erlex, ">= 0.2.5", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, + "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm"}, + "erlex": {:hex, :erlex, "0.2.5", "e51132f2f472e13d606d808f0574508eeea2030d487fc002b46ad97e738b0510", [:mix], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.12.1", "a553c59f6850d0aff3770e4729515762ba7c8e41eedde03208182a8dc9d0ce07", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [: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"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, - "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, "mix_test_watch": {:hex, :mix_test_watch, "0.9.0", "c72132a6071261893518fa08e121e911c9358713f62794a90c95db59042af375", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.2", "1d71150d5293d703a9c38d4329da57d3935faed2031d64bc19e77b654ef2d177", [:mix], [], "hexpm"}, "notion": {:hex, :notion, "0.2.0", "0facad61b5a071dc27fff3afb8630868178f3b6369d047b5eba1c3cc86d44e37", [:mix], [{:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, "plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, - "stream_data": {:hex, :stream_data, "0.4.2", "fa86b78c88ec4eaa482c0891350fcc23f19a79059a687760ddcf8680aac2799b", [:mix], [], "hexpm"}, - "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"}, + "stream_data": {:hex, :stream_data, "0.4.3", "62aafd870caff0849a5057a7ec270fad0eb86889f4d433b937d996de99e3db25", [:mix], [], "hexpm"}, + "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, "yamerl": {:hex, :yamerl, "0.7.0", "e51dba652dce74c20a88294130b48051ebbbb0be7d76f22de064f0f3ccf0aaf5", [:rebar3], [], "hexpm"}, "yaml_elixir": {:hex, :yaml_elixir, "2.4.0", "2f444abc3c994c902851fde56b6a9cb82895c291c05a0490a289035c2e62ae71", [:mix], [{:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm"}, From 474e4ebce2f67a978917fd23c6408c4524add082 Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 17:04:30 -0800 Subject: [PATCH 10/14] appease the dialyzer --- .gitignore | 4 ++++ .travis.yml | 3 +++ lib/k8s/client/runner/base.ex | 21 ++++++++++++++------- lib/k8s/client/runner/watch.ex | 4 ++-- lib/k8s/cluster.ex | 2 +- mix.exs | 3 ++- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index ce1fb630..a9fdb933 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,7 @@ k8s-*.tar # Ignore master version as its always pulled w/ make test/all test/support/swagger/master.json + +# Ignore dialyzer files +/priv/plts/*.plt +/priv/plts/*.plt.hash diff --git a/.travis.yml b/.travis.yml index e53fa42a..af9eee71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ language: elixir sudo: false +cache: + directories: + - priv/plts elixir: - 1.8 - 1.7 diff --git a/lib/k8s/client/runner/base.ex b/lib/k8s/client/runner/base.ex index c8bc2a31..dbdc91fd 100644 --- a/lib/k8s/client/runner/base.ex +++ b/lib/k8s/client/runner/base.ex @@ -4,7 +4,14 @@ defmodule K8s.Client.Runner.Base do """ @type result_t :: - {:ok, map() | reference()} | {:error, atom | binary() | K8s.Middleware.Error.t()} + {:ok, map() | reference()} + # | {:error, atom | binary() | K8s.Middleware.Error.t()} + | {:error, K8s.Middleware.Error.t()} + | {:error, :cluster_not_registered | :missing_required_param | :unsupported_api_version} + | {:error, binary()} + + @typedoc "Acceptable HTTP body types" + @type body_t :: list(map()) | map() | binary() | nil alias K8s.Cluster alias K8s.Operation @@ -88,22 +95,22 @@ defmodule K8s.Client.Runner.Base do """ @spec run(Operation.t(), atom, map(), keyword()) :: result_t def run(%Operation{} = operation, cluster, body, opts \\ []) do - with req <- new_request(cluster, operation, body, opts), - {:ok, url} <- Cluster.url_for(operation, cluster), + with {:ok, url} <- Cluster.url_for(operation, cluster), + req <- new_request(cluster, url, operation, body, opts), {:ok, req} <- K8s.Middleware.run(req) do - K8s.http_provider().request(req.method, url, req.body, req.headers, req.opts) + K8s.http_provider().request(req.method, req.url, req.body, req.headers, req.opts) end end - @spec new_request(atom(), K8s.Operation.t(), list(map()) | map() | binary() | nil, Keyword.t()) :: + @spec new_request(atom(), String.t(), K8s.Operation.t(), body_t, Keyword.t()) :: Request.t() - defp new_request(cluster, %Operation{} = operation, body, opts) do + defp new_request(cluster, url, %Operation{} = operation, body, opts) do req = %Request{cluster: cluster, method: operation.method, body: body} http_opts_params = build_http_params(opts[:params], operation.label_selector) opts_with_selector_params = Keyword.put(opts, :params, http_opts_params) http_opts = Keyword.merge(req.opts, opts_with_selector_params) - %Request{req | opts: http_opts} + %Request{req | opts: http_opts, url: url} end @spec build_http_params(nil | keyword | map, nil | K8s.Selector.t()) :: map() diff --git a/lib/k8s/client/runner/watch.ex b/lib/k8s/client/runner/watch.ex index b81c11ad..ab3f6cbc 100644 --- a/lib/k8s/client/runner/watch.ex +++ b/lib/k8s/client/runner/watch.ex @@ -78,8 +78,8 @@ defmodule K8s.Client.Runner.Watch do rv = parse_resource_version(payload) {:ok, rv} - {:error, error} -> - {:error, error} + error -> + error end end diff --git a/lib/k8s/cluster.ex b/lib/k8s/cluster.ex index e494a084..fcfbd189 100644 --- a/lib/k8s/cluster.ex +++ b/lib/k8s/cluster.ex @@ -18,7 +18,7 @@ defmodule K8s.Cluster do """ @spec url_for(Operation.t(), atom) :: {:ok, binary} | {:error, atom(), binary()} - def url_for(%Operation{api_version: api_version, name: name, verb: verb} = operation, cluster) do + def url_for(%Operation{api_version: api_version, name: name, verb: _verb} = operation, cluster) do with {:ok, conn} <- Cluster.conn(cluster), {:ok, name} <- Cluster.Group.resource_name_for_kind(cluster, api_version, name), operation <- Map.put(operation, :name, name), diff --git a/mix.exs b/mix.exs index 57b34aaa..68796f80 100644 --- a/mix.exs +++ b/mix.exs @@ -25,7 +25,8 @@ defmodule K8s.MixProject do elixirc_paths: elixirc_paths(Mix.env()), dialyzer: [ plt_add_apps: [:mix, :eex], - ignore_warnings: ".dialyzer_ignore.exs" + ignore_warnings: ".dialyzer_ignore.exs", + plt_file: {:no_warn, "priv/plts/k8s.plt"} ] ] end From 59269b12b973eb2f12cb87fb2cf72765e49c085e Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 17:16:09 -0800 Subject: [PATCH 11/14] K8s.Middleware.run/2 tests --- lib/k8s/middleware.ex | 4 ++++ test/k8s/middleware_test.exs | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/k8s/middleware.ex b/lib/k8s/middleware.ex index d9564eb9..ba6a0edb 100644 --- a/lib/k8s/middleware.ex +++ b/lib/k8s/middleware.ex @@ -26,7 +26,11 @@ defmodule K8s.Middleware do @spec run(Request.t()) :: {:ok, Request.t()} | {:error, Error.t()} def run(req) do middlewares = K8s.Middleware.Registry.list(req.cluster, :request) + run(req, middlewares) + end + @spec run(Request.t(), list(module())) :: {:ok, Request.t()} | {:error, Error.t()} + def run(req, middlewares) do result = Enum.reduce_while(middlewares, req, fn middleware, req -> case apply(middleware, :call, [req]) do diff --git a/test/k8s/middleware_test.exs b/test/k8s/middleware_test.exs index 4ee97df6..109478d4 100644 --- a/test/k8s/middleware_test.exs +++ b/test/k8s/middleware_test.exs @@ -1,3 +1,29 @@ defmodule K8s.MiddlewareTest do use ExUnit.Case, async: true + + defmodule EnvLabeler do + @behaviour K8s.Middleware.Request + + @impl true + def call(%{body: body} = req) do + updated_body = put_in(body, ["metadata", "labels"], %{"env" => "prod"}) + updated_req = %K8s.Middleware.Request{req | body: updated_body} + {:ok, updated_req} + end + end + + describe "run/2" do + test "Applies middleware to a request" do + req = %K8s.Middleware.Request{ + cluster: :foo, + url: "http://example.com", + method: :post, + body: %{"metadata" => %{"name" => "nginx"}} + } + + {:ok, %{body: body}} = K8s.Middleware.run(req, [K8s.MiddlewareTest.EnvLabeler]) + + assert body == %{"metadata" => %{"name" => "nginx", "labels" => %{"env" => "prod"}}} + end + end end From 284a4a7719f62fa9c6188afba6f8c8a0ac7873a5 Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 17:18:29 -0800 Subject: [PATCH 12/14] Bump version to 0.5.0 --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 68796f80..182d3cf8 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule K8s.MixProject do [ app: :k8s, description: "Kubernetes API Client for Elixir", - version: "0.4.0", + version: "0.5.0", elixir: "~> 1.6", start_permanent: Mix.env() == :prod, deps: deps(), From d1f7162f30bd2d0f0b5faf06c60068630a0ce677 Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 17:25:50 -0800 Subject: [PATCH 13/14] setting prerelease version --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 182d3cf8..4af00f67 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule K8s.MixProject do [ app: :k8s, description: "Kubernetes API Client for Elixir", - version: "0.5.0", + version: "0.5.0-alpha.1", elixir: "~> 1.6", start_permanent: Mix.env() == :prod, deps: deps(), From 2c7a9855b0fa4bc2acbdd968287dee5dd10e58d3 Mon Sep 17 00:00:00 2001 From: Cory O'Daniel Date: Wed, 27 Nov 2019 17:27:05 -0800 Subject: [PATCH 14/14] setting prerelease version --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 4af00f67..2a37f1a7 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule K8s.MixProject do [ app: :k8s, description: "Kubernetes API Client for Elixir", - version: "0.5.0-alpha.1", + version: "0.5.0-rc.1", elixir: "~> 1.6", start_permanent: Mix.env() == :prod, deps: deps(),