Skip to content

Commit

Permalink
less macros, more introspection
Browse files Browse the repository at this point in the history
Replace some of the macro functions with compile time creation of some
maps based on the swagger operations.

Makes reading the code a bit easier to read and test.
  • Loading branch information
coryodaniel committed Jan 14, 2019
1 parent 8829cf9 commit 86baf1c
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 73 deletions.
76 changes: 5 additions & 71 deletions lib/k8s/client/codegen.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
defmodule K8s.Client.Codegen do
@moduledoc false
alias K8s.Client.Swagger

@operations Swagger.build(Swagger.spec())

defmacro __using__(_opts) do
Expand All @@ -13,51 +12,16 @@ defmodule K8s.Client.Codegen do

@doc false
defmacro __before_compile__(_env) do
op_map_funcs = build_op_kind_map(@operations)

func_names =
@operations
|> Enum.map(fn {_name, metadata} -> gen_func_name(metadata) end)
|> Enum.map(fn {_name, metadata} -> Swagger.gen_action_name(metadata) end)
|> Enum.uniq()

header_funcs = make_header_functions(func_names)
funcs = make_functions(@operations)
base_case_funcs = make_base_case_functions(func_names)

[op_map_funcs] ++ header_funcs ++ funcs ++ base_case_funcs
end

# Build a map of downcased k8s-style resource kind name (eg; deployment).
# This is to help w/ calls to client so they aren't resticted to constant-style names (eg HorizontalPodAutoscaler)
# K8s.Client.get("apps/v1", "Deployment")
# K8s.Client.get("apps/v1", "deployment")
# K8s.Client.get("apps/v1", :deployment)
defp build_op_kind_map(operations) do
op_kind_map =
operations
|> Map.values
|> Enum.reduce(%{}, fn(op, agg) ->
kind = op["kind"]
downkind = String.downcase(kind)

agg
|> Map.put(downkind, kind)
|> Map.put(kind, kind)
end)

quote do
def op_map() do
unquote(Macro.escape(op_kind_map))
end

def proper_kind_name(name) when is_atom(name) do
name |> Atom.to_string |> proper_kind_name
end

def proper_kind_name(name) when is_binary(name) do
Map.get(op_map(), name, name)
end
end
header_funcs ++ funcs ++ base_case_funcs
end

# Make "header" functions that destructure maps into argument lists.
Expand Down Expand Up @@ -101,12 +65,8 @@ defmodule K8s.Client.Codegen do
path_with_args = metadata["path"]
kind = metadata["kind"]
api_version = metadata["api_version"]
func_name = gen_func_name(metadata)

arg_names =
~r/{([a-z]+)}/
|> Regex.scan(path_with_args)
|> Enum.map(fn match -> match |> List.last() |> String.to_atom() end)
func_name = Swagger.gen_action_name(metadata)
arg_names = Swagger.find_args(path_with_args)

quote do
def unquote(:"#{func_name}")(
Expand All @@ -115,7 +75,7 @@ defmodule K8s.Client.Codegen do
opts
) do
case valid_opts?(unquote(arg_names), opts) do
:ok -> replace_path_vars(unquote(path_with_args), opts)
:ok -> Swagger.replace_path_vars(unquote(path_with_args), opts)
error -> error
end
end
Expand All @@ -136,16 +96,6 @@ defmodule K8s.Client.Codegen do
end
end

@doc """
Generate a `K8s.Client` function name
"""
@spec gen_func_name(map()) :: binary()
@spec gen_func_name(map(), binary()) :: binary()
def gen_func_name(metadata = %{"action" => name}), do: gen_func_name(metadata, name)
def gen_func_name(%{"all_namespaces" => true}, name), do: "#{name}_all_namespaces"
def gen_func_name(_, "deletecollection"), do: "delete_collection"
def gen_func_name(_, name), do: name

@doc """
Validates path options
Expand All @@ -167,20 +117,4 @@ defmodule K8s.Client.Codegen do
missing -> {:error, "Missing required parameter: #{Enum.join(missing, ", ")}"}
end
end

@doc """
Replaces path variables with options.
## Examples
iex> K8s.Client.Codegen.replace_path_vars("/foo/{name}", name: "bar")
"/foo/bar"
"""
@spec replace_path_vars(binary(), keyword(atom())) :: binary()
def replace_path_vars(path_template, opts) do
Regex.replace(~r/\{(\w+?)\}/, path_template, fn _, var ->
opts[String.to_existing_atom(var)]
end)
end
end
26 changes: 26 additions & 0 deletions lib/k8s/client/routes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,30 @@ defmodule K8s.Client.Routes do
"""

use K8s.Client.Codegen
alias K8s.Client.Swagger

@operations Swagger.build(Swagger.spec())
@operation_kind_map Swagger.operation_kind_map(@operations)

def operation_kind_map(), do: @operation_kind_map

@doc """
Gets the proper kubernets Kind name given an atom, or downcased string.
## Examples
iex> K8s.Client.Routes.proper_kind_name(:deployment)
"Deployment"
iex> K8s.Client.Routes.proper_kind_name(:Deployment)
"Deployment"
iex> K8s.Client.Routes.proper_kind_name("deployment")
"Deployment"
iex> K8s.Client.Routes.proper_kind_name(:horizontalpodautoscaler)
"HorizontalPodAutoscaler"
"""
def proper_kind_name(name) when is_atom(name), do: name |> Atom.to_string |> proper_kind_name
def proper_kind_name(name) when is_binary(name), do: Map.get(operation_kind_map(), name, name)
end
69 changes: 69 additions & 0 deletions lib/k8s/client/swagger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,75 @@ defmodule K8s.Client.Swagger do

def build(_), do: %{}

@doc """
Map metadata to an `K8s.Client` action name
"""
@spec gen_action_name(map()) :: binary()
def gen_action_name(metadata = %{"action" => name}), do: gen_action_name(metadata, name)

@spec gen_action_name(map(), binary()) :: binary()
def gen_action_name(%{"all_namespaces" => true}, name), do: "#{name}_all_namespaces"
def gen_action_name(_, "deletecollection"), do: "delete_collection"
def gen_action_name(_, name), do: name

@doc """
Find arguments in a URL path.
"""
@spec find_args(binary()) :: list(atom())
def find_args(path_with_args) do
~r/{([a-z]+)}/
|> Regex.scan(path_with_args)
|> Enum.map(fn match -> match |> List.last() |> String.to_atom() end)
end

@doc """
Build a map of downcased k8s-style resource kind name (eg; deployment).
## Examples
Allow client calls to provide name variants so they aren't resticted to constant-style names (eg HorizontalPodAutoscaler).
```elixir
K8s.Client.get("apps/v1", "Deployment")
"Deployment"
K8s.Client.get("apps/v1", "deployment")
"Deployment"
K8s.Client.get("apps/v1", :deployment)
"Deployment"
```
"""
def operation_kind_map(operations) do
operations
|> Map.values
|> Enum.reduce(%{}, fn(op, agg) ->
kind = op["kind"]
downkind = String.downcase(kind)

agg
|> Map.put(downkind, kind)
|> Map.put(kind, kind)
end)
end


@doc """
Replaces path variables with options.
## Examples
iex> K8s.Client.Codegen.replace_path_vars("/foo/{name}", name: "bar")
"/foo/bar"
"""
@spec replace_path_vars(binary(), keyword(atom())) :: binary()
def replace_path_vars(path_template, opts) do
Regex.replace(~r/\{(\w+?)\}/, path_template, fn _, var ->
opts[String.to_existing_atom(var)]
end)
end

defp api_version("", version), do: version
defp api_version(group, version), do: "#{group}/#{version}"

Expand Down
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
defmodule K8s.MixProject do
defmodule K8s.Client.MixProject do
use Mix.Project

def project do
[
app: :k8s_client,
description: "An experimental k8s client.",
version: "0.1.1",
version: "0.1.3",
elixir: "~> 1.7",
start_permanent: Mix.env() == :prod,
deps: deps(),
Expand Down
2 changes: 2 additions & 0 deletions test/k8s/client/routes_test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
defmodule K8s.Client.RoutesTest do
use ExUnit.Case, async: true
doctest K8s.Client.Routes
alias K8s.Client.Routes
alias K8s.Client.Swagger


@default_k8s_spec System.get_env("K8S_SPEC") || "priv/swagger/1.13.json"
@swagger Jason.decode!(File.read!(@default_k8s_spec))

Expand Down

0 comments on commit 86baf1c

Please sign in to comment.