Skip to content

Commit

Permalink
docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mruoss committed Mar 28, 2024
1 parent e8fb6b5 commit ac6a02f
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 33 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/code_quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,34 @@ jobs:

- name: Run Credo
run: MIX_ENV=test mix credo --strict

- name: Retrieve PLT Cache
uses: actions/cache/restore@v4
id: plt-cache
with:
key: |
${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.ito-version }}-plt-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt-
path: |
priv/plts
# Create PLTs if no cache was found
- name: Create PLTs
if: steps.plt-cache.outputs.cache-hit != 'true'
run: |
mkdir -p priv/plts
MIX_ENV=test mix dialyzer --plt
- name: Save PLT cache
id: plt_cache_save
uses: actions/cache/save@v4
if: steps.plt-cache.outputs.cache-hit != 'true'
with:
key: |
${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.ito-version }}-plt-${{ hashFiles('**/mix.lock') }}
path: |
priv/plts
- name: Run dialyzer
run: MIX_ENV=test mix dialyzer --format github
47 changes: 41 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,65 @@
# Kubegen

Generate resource scoped Kubernetes clients with `Kubegen`.
Generate resource based Kubernetes clients with `Kubegen`.

[![Module Version](https://img.shields.io/hexpm/v/kubegen.svg)](https://hex.pm/packages/kubegen)
[![Last Updated](https://img.shields.io/github/last-commit/mruoss/kubegen.svg)](https://github.com/mruoss/kubegen/commits/main)

[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/kubegen/)
[![Total Download](https://img.shields.io/hexpm/dt/kubegen.svg)](https://hex.pm/packages/kubegen)
[![License](https://img.shields.io/hexpm/l/kubegen.svg)](https://github.com/mruoss/kubegen/blob/main/LICENSE)
[![License](https://img.shields.io/hexpm/l/kubegen.svg)](https://github.com/mruoss/kubegen/blob/main/LICENSE.md)

## Installation

`kubegen` is a code generator. Add the package as dev dependency:
`kubegen` is a code generator. Add the package as dev dependency. Make sure to
add `kubereq` to your list of dependencies as well:

```elixir
def deps do
[
{:kubegen, "~> 0.1.0", only: :dev, runtime: false}
{:kubegen, "~> 0.1.0", only: :dev, runtime: false},
{:kubereq, "~> 0.1.0"}
]
end
```

The docs can be found at <https://hexdocs.pm/kubegen>.

## Usage
## Configuration

### Configuration
Before you can generate clients, you need to create a configuration in your
`config.exs` under `config :kubegen, :default` or `config :kubegen, :mycluster`.
where a custom `:mycluster` identifier is passed as argument
(`mix kubegen -c mycluster`)

- `:module_prefix` - The prefix of the generated modules (e.g. `MyApp.K8sClient`)
- `:kubeconfig_pipeline` - The `Pluggable` pipeline responsible for loading the Kubernetes config. (e.g. `Kubereq.Kubeconfig.Default`)
- `:resources` - List of resources for which clients are generated.

The entries of `:resources` can be in the form of

- Group-Version-Kind in the case of Kubernetes core resources.
- Path to a local CRD YAML (multiple CRDs in one file are supported)
- URL to a public remote CRD Yaml (multiple CRDs in one file are supported)

### Example

```ex
config :kubegen, :default,
module_prefix: MyApp.K8sClient,
kubeconfig_pipeline: Kubereq.Kubeconfig.Default,
resources: [
"v1/ConfigMap",
"rbac.authorization.k8s.io/v1/ClusterRole",
"test/support/foos.example.com.yaml", # local CRD
"https://raw.githubusercontent.com/mruoss/kompost/main/priv/manifest/postgresdatabase.crd.yaml" # public remote CRD
]
```

### How to find the correct Group-Version-Kind identifier

Use `mix kubegen.search` to search for GVKs (e.g. `mix.kubegen.search Pod`)

### Generate Resource Clients

Now you can (re-)generate clients using `mix kubegen` or `mix kubegen -c mycluster`
4 changes: 3 additions & 1 deletion config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import Config

config :kubegen, :default,
module_prefix: Kubegen.K8sClient,
kubeconfig_pipeline: Kubereq.Kubeconfig.Default,
resources: [
"v1/ConfigMap",
"rbac.authorization.k8s.io/v1/ClusterRole",
"test/support/foos.example.com.yaml",
"v1/Namespace"
"v1/Namespace",
"https://raw.githubusercontent.com/mruoss/kompost/main/priv/manifest/postgresdatabase.crd.yaml"
]
32 changes: 22 additions & 10 deletions lib/kubegen/resource.ex
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
defmodule Kubegen.Resource do
@moduledoc false

alias Kubegen.Utils

@discovery elem(Code.eval_file("build/discovery.ex"), 0)

def generate(resources, module_prefix) do
def generate(resources, module_prefix, kubeconfig_pipeline) do
for resource <- resources,
{gvk, definition} <- get_definitions(resource),
{module, api_version} = derive_module_and_api_version(module_prefix, gvk),
generated_resource <- generate_resource(definition, module, api_version) do
generated_resource <-
generate_resource(definition, module, api_version, kubeconfig_pipeline) do
generated_resource
end
end

defp generate_resource(resource_definition, resource_module, api_version) do
main_resource = do_add_api(api_version, resource_definition, resource_module)
defp generate_resource(resource_definition, resource_module, api_version, kubeconfig_pipeline) do
main_resource =
do_add_api(api_version, resource_definition, resource_module, kubeconfig_pipeline)

subresources =
for subresource_definition <- List.wrap(resource_definition["subresources"]),
Expand All @@ -24,14 +28,15 @@ defmodule Kubegen.Resource do
do_add_api(
api_version,
subresource_definition,
subresource_resource_module
subresource_resource_module,
kubeconfig_pipeline
)
end

[main_resource | subresources]
end

defp do_add_api(api_version, resource_definition, api_module) do
defp do_add_api(api_version, resource_definition, api_module, kubeconfig_pipeline) do
%{
"name" => name,
"verbs" => verbs
Expand Down Expand Up @@ -64,8 +69,8 @@ defmodule Kubegen.Resource do
req_func =
quote do
defp req() do
Kubeconf.Default
|> Kubeconf.load()
unquote(kubeconfig_pipeline)
|> Kubereq.Kubeconfig.load()
|> Kubereq.new(@resource_path)
end
end
Expand All @@ -92,6 +97,14 @@ defmodule Kubegen.Resource do
|> YamlElixir.read_all_from_file!()
|> Enum.flat_map(&crd_to_gvk_and_discovery/1)

URI.parse(resource) ->
{:ok, _} = Application.ensure_all_started(:req)
%{status: 200} = req = Req.get!(resource)

req.body
|> YamlElixir.read_all_from_string!()
|> Enum.flat_map(&crd_to_gvk_and_discovery/1)

:otherwise ->
raise "Resource #{resource} was not found."
end
Expand Down Expand Up @@ -159,8 +172,7 @@ defmodule Kubegen.Resource do
api_module =
api
|> String.split(".")
|> Enum.map(&String.capitalize/1)
|> Enum.join("")
|> Enum.map_join("", &String.capitalize/1)

{[api_module, String.capitalize(version), kind], "#{api}/#{version}"}
end
Expand Down
2 changes: 2 additions & 0 deletions lib/kubegen/utils.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule Kubegen.Utils do
@moduledoc false

@spec put_newlines(Macro.t()) :: Macro.t()
def put_newlines({term, metadata, arguments}) do
end_of_expression =
Expand Down
67 changes: 61 additions & 6 deletions lib/mix/tasks/kubegen.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
defmodule Mix.Tasks.Kubegen do
@moduledoc """
@moduledoc ~S"""
(Re-)Generates clients for Kubernetes resources according to the config.
## Configuration
Prior to running this Mix task, you need to create a configuration in your
`config.exs` under `config :kubegen, :default` or `config :kubegen, :mycluster`.
where a custom `:mycluster` identifier is passed as argument
(`mix kubegen -c mycluster`)
* `:module_prefix` - The prefix of the generated modules (e.g. `MyApp.K8sClient`)
* `:kubeconfig_pipeline` - The `Pluggable` pipeline responsible for loading the Kubernetes config. (e.g. `Kubereq.Kubeconfig.Default`)
* `:resources` - List of resources for which clients are generated.
The entries of `:resources` can be in the form of
* Group-Version-Kind in the case of Kubernetes core resources.
* Path to a local CRD YAML (multiple CRDs in one file are supported)
* URL to a public remote CRD Yaml (multiple CRDs in one file are supported)
### Example
```
config :kubegen, :default,
module_prefix: MyApp.K8sClient,
kubeconfig_pipeline: Kubereq.Kubeconfig.Default,
resources: [
"v1/ConfigMap",
"rbac.authorization.k8s.io/v1/ClusterRole",
"test/support/foos.example.com.yaml", # local CRD
"https://raw.githubusercontent.com/mruoss/kompost/main/priv/manifest/postgresdatabase.crd.yaml" # public remote CRD
]
```
### How to find the correct Group-Version-Kind identifier
Use `mix kubegen.search` to search for GVKs (e.g. `mix.kubegen.search Pod`)
"""
@shortdoc "(Re-)generates Kubernetes Clients"

Expand All @@ -16,13 +52,13 @@ defmodule Mix.Tasks.Kubegen do

if length(argv) != 0, do: usage_and_exit()

cluster = parsed[:cluster] || "default" |> String.to_atom()
cluster = String.to_atom(parsed[:cluster] || "default")
config = Application.get_env(:kubegen, cluster)

if is_nil(config[:module_prefix]) do
Owl.IO.puts([
IO.ANSI.red(),
"Module prefix not set. Please set the moduel prefix in config.exs under :kubegen, #{inspect(cluster)}, :prefix.",
":module_prefix (Module prefix) not set. Please set the module prefix in config.exs under :kubegen, #{inspect(cluster)}, :prefix.",
IO.ANSI.reset(),
~s'''
Expand All @@ -37,10 +73,28 @@ defmodule Mix.Tasks.Kubegen do
exit({:shutdown, 65})
end

if is_nil(config[:kubeconfig_pipeline]) do
Owl.IO.puts([
IO.ANSI.red(),
":kubeconfig_pipeline (Kubeconfig Loader Pipeline) is not set. Please define the module defining a Pluggable pipeline for loading the Kubernetes configuration.",
IO.ANSI.reset(),
~s'''
Example:
config :kubegen, #{inspect(cluster)},
kubeconfig_pipeline: Kubereq.Kubeconfig.Default
'''
])

exit({:shutdown, 65})
end

if is_nil(config[:resources]) do
Owl.IO.puts([
IO.ANSI.red(),
"List of resoures not set. Please define the list of resources to be generated in config.exs under :kubegen, #{inspect(cluster)}, :resources.",
":resources (List of resoures) is not set. Please define the list of resources to be generated in config.exs under :kubegen, #{inspect(cluster)}, :resources.",
IO.ANSI.reset(),
~s'''
Expand All @@ -59,13 +113,14 @@ defmodule Mix.Tasks.Kubegen do
end

module_prefix = config[:module_prefix]
kubeconfig_pipeline = config[:kubeconfig_pipeline]
resources = config[:resources] |> Enum.uniq()

"lib/#{Macro.underscore(module_prefix)}/*"
|> Path.wildcard()
|> Enum.map(&File.rm_rf!/1)
|> Enum.each(&File.rm_rf!/1)

for {module_name, ast} <- Resource.generate(resources, module_prefix) do
for {module_name, ast} <- Resource.generate(resources, module_prefix, kubeconfig_pipeline) do
rendered =
ast
|> Code.quoted_to_algebra(escape: false, locals_without_parens: [step: 1, step: 2])
Expand Down
6 changes: 5 additions & 1 deletion lib/mix/tasks/kubegen.search.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ defmodule Mix.Tasks.Kubegen.Search do
@moduledoc ~S"""
Search Group-Version-Kind (GVK) for Core Resources.
Kubegen requires you to pass GVK as keys in `config.exs`. This mix tasks
lets you search for GVK for a specific resource kind
lets you search for GVK for a specific resource kind.
### Example
mix kubegen.search Pod
"""
@shortdoc "Search Group-Version-Kind for Core Resources."

Expand Down
25 changes: 17 additions & 8 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,23 @@ defmodule Kubegen.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:kubereq, path: "../kubereq", optional: true},
{:kubereq, "~> 0.1.0", optional: true},
{:owl, "~> 0.9.0"},

# Dev deps
{:dialyxir, "~> 1.4.0", only: [:dev, :test], runtime: false},

# Test deps
{:excoveralls, "~> 0.18", only: :test},
{:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false},

# Dev deps
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:ex_doc, "~> 0.31", only: :dev, runtime: false},
{:excoveralls, "~> 0.18", only: :test}
{:dialyxir, "~> 1.4.0", only: [:dev, :test], runtime: false}
]
end

defp docs do
[
# The main page in the docs
# main: "Pluggable.Token",
main: "readme",
source_ref: @version,
source_url: @source_url,
extras: [
Expand Down Expand Up @@ -78,7 +79,15 @@ defmodule Kubegen.MixProject do
"Changelog" => "https://hexdocs.pm/#{@app}/changelog.html",
"Sponsor" => "https://github.com/sponsors/mruoss"
},
files: ["lib", "mix.exs", "README.md", "LICENSE", "CHANGELOG.md", ".formatter.exs"]
files: [
"lib",
"build",
"mix.exs",
"README.md",
"LICENSE.md",
"CHANGELOG.md",
".formatter.exs"
]
]
end

Expand Down
Loading

0 comments on commit ac6a02f

Please sign in to comment.