README.md

# K8s

[K8s](https://hexdocs.pm/k8s/readme.html) - Kubernetes API Client for Elixir

[![Build Status](https://travis-ci.org/coryodaniel/k8s.svg?branch=master)](https://travis-ci.org/coryodaniel/k8s)
[![Coverage Status](https://coveralls.io/repos/github/coryodaniel/k8s/badge.svg?branch=master)](https://coveralls.io/github/coryodaniel/k8s?branch=master)
[![Hex.pm](http://img.shields.io/hexpm/v/k8s.svg?style=flat)](https://hex.pm/packages/k8s)
[![Documentation](https://img.shields.io/badge/documentation-on%20hexdocs-green.svg)](https://hexdocs.pm/k8s/)
![Hex.pm](https://img.shields.io/hexpm/l/k8s.svg?style=flat)

## Features

* A client API for humans
* Kubernetes resources, groups, and CRDs are autodiscovered at boot time. No swagger file to include or override.
* Client supports standard HTTP calls, async batches, wait on status, and watchers
* Supports multiple clusters
* Supports multiple authentication credentials
  * serviceaccount
  * token
  * certificate
  * auth-provider
* Supports multiple kubernetes API
* Tested against kubernetes swagger specs: 1.10+ and master
* CRD support
* Kubernetes resource and version helper functions
* Kube config file parsing
* Certificate and service account based auth
* Pluggable auth providers
* Macro free; fast compile & fast startup

## Installation

The package can be installed by adding `k8s` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:k8s, "~> 0.2"}
  ]
end
```

## Usage

Check out the [Usage Guide](https://hexdocs.pm/k8s/usage.html) for in-depth examples.

Most functions are also written using doctests.

* [K8s.Client doctests](https://hexdocs.pm/k8s/K8s.Client.html)
* [K8s.Cluster doctests](https://hexdocs.pm/k8s/K8s.Cluster.html)
* [K8s.Conf doctests](https://hexdocs.pm/k8s/K8s.Conf.html)
* [K8s.Resource doctests](https://hexdocs.pm/k8s/K8s.Resource.html)
* [K8s.Version doctests](https://hexdocs.pm/k8s/K8s.Version.html)

## Testing `K8s` operations in your application

`K8s` ships with a [`K8s.Client.DynamicHTTPProvider`](./lib/k8s/client/dynamic_http_provider.ex) for stubbing HTTP responses to kubernetes API requests.

This provider is used throughout the test suite for mocking HTTP responses.

```elixir
defmodule MyApp.ResourceTest do
  use ExUnit.Case, async: true

  defmodule K8sMock do
    @base_url "https://localhost:6443"
    @namespaces_url @base_url <> "/api/v1/namespaces"

    def request(:get, @namespaces_url, _, _, _) do
      namespaces = [%{"metadata" => %{"name" => "default"}}]
      body = Jason.encode!(namespaces)
      {:ok, %HTTPoison.Response{status_code: 200, body: body}}
    end
  end

  setup do
    DynamicHTTPProvider.register(self(), __MODULE__.K8sMock)
  end

  test "gets namespaces" do
    operation = K8s.Client.get("v1", :namespaces)
    assert {:ok, namespaces} = K8s.Client.run(operation, :default)
    assert namespaces == [%{"metadata" => %{"name" => "default"}}]
  end
end
```

To see advanced examples of usage, check out these examples in the test suite:

* [client/runner/base](./test/k8s/client/runner/base_test.exs)
* [client/runner/stream](./test/k8s/client/runner/stream_test.exs)
* [client/runner/watch](./test/k8s/client/runner/watch_test.exs)
* [discovery](./test/k8s/discovery_test.exs)

## Contributing

### Adding support for a new version of kubernetes

Download the swagger spec for the new version. `k8s` doesn't use swagger to define the API, but it is used to drive property tests.

```shell
export NEW_VERSION_NUMBER=1.1n
make get/${NEW_VERSION_NUMBER}
```

```shell
make test/${NEW_VERSION_NUMBER}
```

Mock discovery [responses](.test/support/discovery) exist to simulate runtime API discovery using the [`FileDriver`](./lib/k8s/cluster/discover/file_driver.ex)

If new resources or APIs were added to kubernetes in the new version you will likely see one of these errors: `unsupported_api_version` and `unsupported_resource`.

### Unsupported API Version errors

This error occurs when a new API is added to kubernetes.

Example: `{:error, :unsupported_api_version, "scheduling.k8s.io/v1"}`

Add the `:unsupported_api_version` to [this](test/support/discovery/resource_definitions.json) mock configuration and rerun the test suite.

A config entry looks like:

```javascript
// ...
 {
    "groupVersion": "scheduling.k8s.io/v1", // add the api version
    "kind": "APIResourceList", // leave as is
    "resources": [ // add a list of resources provided by the new `groupVersion`
      {
        "kind": "PriorityClass", // `kind` name
        "name": "priorityclasses", // plural name
        "namespaced": false, // namespaced or not
        "singularName": "",
        "verbs": [ // list of verbs supported by the new API
          "create",
          "delete",
          "deletecollection",
          "get",
          "list",
          "patch",
          "update",
          "watch"
        ]
      }
    ]
  },
// ...
```

### Unsupported Resource errors

This error occurs when a new resource type is added to an existing API, similar to above you will need to add the `resource` to the list of `resources`.

The following config is missing `runtimeclasses` as a resource.

```javascript
  {
    "groupVersion": "node.k8s.io/v1beta1",
    "kind": "APIResourceList",
    "resources": [
      {
        "kind": "Node",
        "name": "nodes",
        "namespaced": false,
        "singularName": "",
        "verbs": [
          "create",
          "delete",
          "deletecollection",
          "get",
          "list",
          "patch",
          "update",
          "watch"
        ]
      }
    ]
  },
```

After mocking:

```javascript
  {
    "groupVersion": "node.k8s.io/v1beta1",
    "kind": "APIResourceList",
    "resources": [
      {
        "kind": "Node",
        "name": "nodes",
        "namespaced": false,
        "singularName": "",
        "verbs": [
          "create",
          "delete",
          "deletecollection",
          "get",
          "list",
          "patch",
          "update",
          "watch"
        ]
      },
      {
        "kind": "RuntimeClass",
        "name": "runtimeclasses",
        "namespaced": false,
        "singularName": "",
        "verbs": [
          "create",
          "delete",
          "deletecollection",
          "get",
          "list",
          "patch",
          "update",
          "watch"
        ]
      }
    ]
  },
```