From a47c9136c5271fde78e8086c519562adb4b42db7 Mon Sep 17 00:00:00 2001 From: Michael Ruoss Date: Thu, 28 Mar 2024 21:47:57 +0100 Subject: [PATCH] consolidate Kubeconf --- .github/workflows/code_quality.yaml | 36 +++- .gitignore | 5 +- README.md | 88 ++++++++-- lib/kubereq.ex | 18 +- lib/kubereq/access.ex | 154 ++++++++++++++++++ lib/kubereq/kubeconfig.ex | 107 ++++++++++++ lib/kubereq/kubeconfig/access.ex | 154 ++++++++++++++++++ lib/kubereq/kubeconfig/default.ex | 28 ++++ lib/kubereq/kubeconfig/env.ex | 46 ++++++ lib/kubereq/kubeconfig/file.ex | 80 +++++++++ lib/kubereq/kubeconfig/service_account.ex | 88 ++++++++++ mix.exs | 1 - test/kubereq/kubeconfig/access_test.exs | 4 + test/kubereq/kubeconfig/env_test.exs | 42 +++++ test/kubereq/kubeconfig/file_test.exs | 46 ++++++ .../kubeconfig/service_account_test.exs | 36 ++++ test/kubereq/step/auth_test.exs | 12 +- test/kubereq/step/base_url_test.exs | 2 +- test/kubereq/step/compression_test.exs | 8 +- test/kubereq/step/impersonate_test.exs | 2 +- test/kubereq/step/tls_test.exs | 10 +- test/kubereq_integration_test.exs | 8 +- test/support/kubeconfig-unit.yaml | 75 +++++++++ test/support/kubeconfig.yaml | 19 --- test/support/kubeconfig/tls/ca.crt | 18 ++ test/support/kubeconfig/tls/certificate.pem | 18 ++ test/support/kubeconfig/tls/key.pem | 27 +++ test/support/kubeconfig/tls/namespace | 1 + test/support/kubeconfig/tls/token | 1 + 29 files changed, 1071 insertions(+), 63 deletions(-) create mode 100644 lib/kubereq/access.ex create mode 100644 lib/kubereq/kubeconfig.ex create mode 100644 lib/kubereq/kubeconfig/access.ex create mode 100644 lib/kubereq/kubeconfig/default.ex create mode 100644 lib/kubereq/kubeconfig/env.ex create mode 100644 lib/kubereq/kubeconfig/file.ex create mode 100644 lib/kubereq/kubeconfig/service_account.ex create mode 100644 test/kubereq/kubeconfig/access_test.exs create mode 100644 test/kubereq/kubeconfig/env_test.exs create mode 100644 test/kubereq/kubeconfig/file_test.exs create mode 100644 test/kubereq/kubeconfig/service_account_test.exs create mode 100644 test/support/kubeconfig-unit.yaml delete mode 100644 test/support/kubeconfig.yaml create mode 100644 test/support/kubeconfig/tls/ca.crt create mode 100644 test/support/kubeconfig/tls/certificate.pem create mode 100644 test/support/kubeconfig/tls/key.pem create mode 100644 test/support/kubeconfig/tls/namespace create mode 100644 test/support/kubeconfig/tls/token diff --git a/.github/workflows/code_quality.yaml b/.github/workflows/code_quality.yaml index 5383a9a..af14504 100644 --- a/.github/workflows/code_quality.yaml +++ b/.github/workflows/code_quality.yaml @@ -7,6 +7,7 @@ on: env: MIX_ENV: test + KUBECONFIG: test/support/kubeconfig-integration.yaml jobs: code-quality: @@ -14,6 +15,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: engineerd/setup-kind@v0.5.0 + id: kind + with: + version: v0.20.0 + image: kindest/node:${{ matrix.k8s_version }} + name: kubereq - name: Setup elixir id: beam @@ -47,8 +54,31 @@ jobs: - name: Compile run: MIX_ENV=test mix compile --warnings-as-errors - - name: Unit Tests - run: MIX_ENV=test mix test - - name: Run Credo run: MIX_ENV=test mix credo --strict + + - name: Run Coverage + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MIX_ENV: test + run: TEST_WAIT_TIMEOUT=10000 mix coveralls.github --include integration --timeout 20000 + + # 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 diff --git a/.gitignore b/.gitignore index 34e359f..d325391 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,7 @@ kubereq-*.tar /tmp/ # Ignore dialyzer files -/priv/plts/ \ No newline at end of file +/priv/plts/ + +# integration cluster config +/test/support/kubeconfig-integration.yaml \ No newline at end of file diff --git a/README.md b/README.md index 608fc5b..607b59c 100644 --- a/README.md +++ b/README.md @@ -38,19 +38,85 @@ resource kind you need. Check out [`kubegen`](https://github.com/mruoss/kubegen) ## Build your own clients -### Loading the Kubernetes Configuration +### Define how to load the Kubernetes Config -Before you can connect to the Kubernetes API Server you need to load the -cluster's Kubernetes configuration (Kubeconfig). This library expects you to use -[`kubeconf`](https://github.com/mruoss/kubeconf) to load the configuration. +In order to get started quickly, you can just use the default pipeline +(`Kubereq.Kubeconfig.Default`) which tries to load the Kubernetes configuration +one-by-one from well-known sources. -Once you have loaded the configuration and filled the `%Kubeconf{}` struct, you -can get a `Req` request using `Kubereq.new/2`. +If you need more sophisticated rules, you can build your own Kubeconfig loader +pipeline by creating a module `use`-ing [`Pluggable.StepBuilder`](https://hexdocs.pm/pluggable/Pluggable.StepBuilder.html) +and adding `Pluggable` steps defined by this module. The mechanism is exactly +the same as you know from the `Plug` library. + +In fact, the default pipeline mentioned above is implemented defining a set of +steps. + +```ex +defmodule Kubereq.Kubeconfig.Default do + use Pluggable.StepBuilder + + step Kubereq.Kubeconfig.ENV + step Kubereq.Kubeconfig.File, path: ".kube/config", relative_to_home?: true + step Kubereq.Kubeconfig.ServiceAccount +end +``` + +### Load the Kubernetes Config + +With the pipeline defined or implemented, you can now call +`Kubereq.Kubeconfig.load/1` to load the config: + +```ex +Kubereq.Kubeconfig.load(Kubereq.Kubeconfig.Default) +``` + +If your pipelines requires options, you can pass a tuple to +`Kubereq.Kubeconfig.load/1`: + +```ex +Kubereq.Kubeconfig.load({Kubereq.Kubeconfig.File, path: ".kube/config", relative_to_home?: true}) +``` + +Instead of creating a new module, you can also pass a list of steps to +`Kubereq.Kubeconfig.load/1`: + +```ex +Kubereq.Kubeconfig.load([ + Kubereq.Kubeconfig.ENV, + {Kubereq.Kubeconfig.File, path: ".kube/config", relative_to_home?: true}, + Kubereq.Kubeconfig.ServiceAccount +]) +``` ### Building the `Req.Request` struct -`Kubereq.new/2` creates a `%Req.Request{}` struct which allows you to make -requests to the Kubernetes API Server for **a specific resource kind**. It expects -the `kubeconf` as first argument and the `path` to the resource as second -argument. The path should contain placeholders for `:namespace` and `:name` -which are filled once you make a request to a specific resource. +Once you have loaded the, you can pass it to `Kubereq.new/2` to get a +`%Req.Request{}` struct which is prepared to make requests to the Kubernetes +API Server for **a specific resource kind**. It expects the `kubeconf` as first +argument and the `path` to the resource as second argument. The path should +contain placeholders for `:namespace` and `:name` which are filled once you make +a request to a specific resource. + +The following example builds a `%Req.Request{}` which acts as client for running +operations on `ConfigMaps`: + +```ex +kubeconfig = Kubereq.Kubeconfig.load(Kubereq.Kubeconfig.Default) +req = Kubereq.new(kubeconfig, "api/v1/namespaces/:namespace/configmaps/:name") +``` + +### Running Operations + +With the `req` built above, you can now use the other functions defined by +`Kubereq` to run operations - in this example on `ConfigMaps`. + +```ex +kubeconfig = Kubereq.Kubeconfig.load(Kubereq.Kubeconfig.Default) +req = Kubereq.new(kubeconfig, "api/v1/namespaces/:namespace/configmaps/:name") + +{:ok, resp} = Kubereq.get(req, "my-namespace", "my-config-map") +``` + +`resp` is a `Req.Response.t()` and you can check for `req.status` and get +`req.body` etc. diff --git a/lib/kubereq.ex b/lib/kubereq.ex index 2700d95..de7fca4 100644 --- a/lib/kubereq.ex +++ b/lib/kubereq.ex @@ -1,11 +1,11 @@ defmodule Kubereq do @moduledoc ~S""" Kubereq defines a set of Request Steps for `Req`. All steps combined turn - a Kubernetes configuration in the form of a `%Kubeconf{}` struct into a + a Kubernetes configuration in the form of a `%Kubereq.Kubeconfig{}` struct into a `%Req.Request{}` struct containing all headers and options required to connect to the cluster and perform the given operations. - In order to build `%Kubeconf{}` struct you can either use the steps defined + In order to build `%Kubereq.Kubeconfig{}` struct you can either use the steps defined in the `Kubeconf` library or create your own Kubernetes configuration loader module combining those steps. @@ -23,7 +23,7 @@ defmodule Kubereq do @resource_path "api/v1/namespaces/:namespace/configmaps/:name" defp req() do - kubeconfig = Kubeconf.load(Kubeconf.Default) + kubeconfig = Kubereq.Kubeconfig.load(Kubereq.Kubeconfig.Default) Kubereq.new(kubeconfig, @resource_path) end @@ -48,16 +48,16 @@ defmodule Kubereq do @doc """ Prepares a `Req.Request` struct for making HTTP requests to a Kubernetes cluster. The `kubeconfig` is the Kubernetes configuration in the form of a - `%Kubeconf{}` struct and should contain all informations required to connect + `%Kubereq.Kubeconfig{}` struct and should contain all informations required to connect to the Kubernetes cluster. ### Examples - iex> kubeconfig = Kubeconf.load(Kubeconf.Default) + iex> kubeconfig = Kubereq.Kubeconfig.load(Kubereq.Kubeconfig.Default) ...> Kubereq.new(kubeconfig) %Request.Req{...} """ - @spec new(kubeconfig :: Kubeconf.t()) :: + @spec new(kubeconfig :: Kubereq.Kubeconfig.t()) :: Req.Request.t() def new(kubeconfig) do Req.new() @@ -75,7 +75,7 @@ defmodule Kubereq do @doc """ Prepares a `Req.Request` struct for a specific resource on a specific Kubernetes cluster. The `kubeconfig` is the Kubernetes configuration in the - form of a `%Kubeconf{}` struct and should contain all informations required to + form of a `%Kubereq.Kubeconfig{}` struct and should contain all informations required to connect to the Kubernetes cluster. The parameter `resource_path` should be the path on which the Kubernetes API @@ -89,11 +89,11 @@ defmodule Kubereq do Prepare a `Req.Request` for ConfigMaps: - iex> kubeconfig = Kubeconf.load(Kubeconf.Default) + iex> kubeconfig = Kubereq.Kubeconfig.load(Kubereq.Kubeconfig.Default) ...> Kubereq.new(kubeconfig, "api/v1/namespaces/:namespace/configmaps/:name") %Request.Req{...} """ - @spec new(kubeconfig :: Kubeconf.t(), resource_path :: binary()) :: + @spec new(kubeconfig :: Kubereq.Kubeconfig.t(), resource_path :: binary()) :: Req.Request.t() def new(kubeconfig, resource_path) do new(kubeconfig) diff --git a/lib/kubereq/access.ex b/lib/kubereq/access.ex new file mode 100644 index 0000000..3f5ab3d --- /dev/null +++ b/lib/kubereq/access.ex @@ -0,0 +1,154 @@ +defmodule Kubereq.Access do + @moduledoc """ + Helper module to access maps in lists. + """ + + @doc ~S""" + Returns a function that accesses the first element for which `fun` returns a truthy value. + + The returned function is typically passed as an accessor to `Kernel.get_in/2`, + `Kernel.get_and_update_in/3`, and friends. + + ## Examples + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> get_in(list, [Kubereq.Access.find(&(&1.name == "john")), :salary]) + 10 + iex> get_and_update_in(list, [Kubereq.Access.find(&(&1.name == "john")), :salary], fn prev -> + ...> {prev, 15} + ...> end) + {10, [%{name: "john", salary: 15}, %{name: "francine", salary: 30}]} + + `find/1` can also be used to pop elements out of a list or + a key inside of a list: + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> pop_in(list, [Kubereq.Access.find(&(&1.name == "francine"))]) + {%{name: "francine", salary: 30}, [%{name: "john", salary: 10}]} + iex> pop_in(list, [Kubereq.Access.find(&(&1.name == "francine")), :name]) + {"francine", [%{salary: 30}, %{name: "john", salary: 10}]} + + When no match is found, the given default is used. This can be used to + specify defaults and safely traverse missing items. + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> get_in(list, [Kubereq.Access.find(&(&1.name == "adam"), %{name: "adam", salary: 50}), :salary]) + 50 + iex> get_and_update_in(list, [Kubereq.Access.find(&(&1.name == "adam"), %{name: "adam"}), :salary], fn prev -> + ...> {prev, 50} + ...> end) + {nil, [%{name: "adam", salary: 50}, %{name: "john", salary: 10}, %{name: "francine", salary: 30}]} + + When multiple items exist for which `fun` return a truthy value, the first one is accessed. + + iex> list = [%{name: "john", salary: 10}, %{name: "john", salary: 30}] + iex> get_in(list, [Kubereq.Access.find(&(&1.name == "john")), :salary]) + 10 + + An error is raised if the accessed structure is not a list: + + iex> get_in(%{}, [Kubereq.Access.find(&(&1.name == "john"))]) + ** (RuntimeError) Kubereq.Access.find/1 expected a list, got: %{} + + """ + @spec find((term -> boolean), term()) :: Access.access_fun(data :: list, current_value :: list) + def find(func, default \\ nil) when is_function(func) do + fn op, data, next -> find(op, data, func, default, next) end + end + + defp find(:get, data, func, default, next) when is_list(data) do + data |> Enum.find(default, func) |> next.() + end + + defp find(:get_and_update, data, func, default, next) when is_list(data) do + get_and_update_find(data, func, next, default, false) + end + + defp find(_op, data, _default, _func, _next) do + raise "Kubereq.Access.find/1 expected a list, got: #{inspect(data)}" + end + + @doc ~S""" + Returns a function that accesses the first element for which `fun` returns a truthy value. + + The returned function is typically passed as an accessor to `Kernel.get_in/2`, + `Kernel.get_and_update_in/3`, and friends. + + Similar to find/2, but the returned function raises if the no item is found for which `fun` returns a truthy value. + + ## Examples + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> get_in(list, [Kubereq.Access.find!(&(&1.name == "john")), :salary]) + 10 + iex> get_and_update_in(list, [Kubereq.Access.find!(&(&1.name == "john")), :salary], fn prev -> + ...> {prev, 15} + ...> end) + {10, [%{name: "john", salary: 15}, %{name: "francine", salary: 30}]} + + `find/1` can also be used to pop elements out of a list or + a key inside of a list: + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> pop_in(list, [Kubereq.Access.find!(&(&1.name == "francine"))]) + {%{name: "francine", salary: 30}, [%{name: "john", salary: 10}]} + iex> pop_in(list, [Kubereq.Access.find!(&(&1.name == "francine")), :name]) + {"francine", [%{salary: 30}, %{name: "john", salary: 10}]} + iex> get_in(list, [Kubereq.Access.find!(&(&1.name == "adam")), :salary]) + ** (ArgumentError) There is no item in the list for which the given function returns a truthy value. + + When multiple items exist for which `fun` return a truthy value, the first one is accessed. + + iex> list = [%{name: "john", salary: 10}, %{name: "john", salary: 30}] + iex> get_in(list, [Kubereq.Access.find!(&(&1.name == "john")), :salary]) + 10 + + An error is raised if the accessed structure is not a list: + + iex> get_in(%{}, [Kubereq.Access.find!(&(&1.name == "john"))]) + ** (RuntimeError) Kubereq.Access.find!/1 expected a list, got: %{} + """ + @spec find!((term -> boolean)) :: Access.access_fun(data :: list, current_value :: list) + def find!(func) when is_function(func) do + fn op, data, next -> find!(op, data, func, next) end + end + + defp find!(:get, data, func, next) when is_list(data) do + case Enum.find(data, func) do + nil -> + raise ArgumentError, + "There is no item in the list for which the given function returns a truthy value." + + item -> + next.(item) + end + end + + defp find!(:get_and_update, data, func, next) when is_list(data) do + get_and_update_find(data, func, next) + end + + defp find!(_op, data, _func, _next) do + raise "Kubereq.Access.find!/1 expected a list, got: #{inspect(data)}" + end + + defp get_and_update_find(data, func, next, default \\ nil, raise? \\ true) do + {value, rest} = + case Enum.find_index(data, func) do + nil when raise? -> + raise ArgumentError, + "There is no item in the list for which the given function returns a truthy value." + + nil -> + {default, data} + + index -> + List.pop_at(data, index) + end + + case next.(value) do + {get, update} -> {get, [update | rest]} + :pop -> {value, rest} + end + end +end diff --git a/lib/kubereq/kubeconfig.ex b/lib/kubereq/kubeconfig.ex new file mode 100644 index 0000000..0714e1f --- /dev/null +++ b/lib/kubereq/kubeconfig.ex @@ -0,0 +1,107 @@ +defmodule Kubereq.Kubeconfig do + @moduledoc """ + This is the `Pluggable.Token` for the pipeline loading the Kubernetes config. + """ + + alias Kubereq.Access + + @derive Pluggable.Token + + @typedoc """ + The `%Kubereq.Kubeconfig{}` struct holds information required to connect to + Kubernetes clusters + + For descriptions of the fields, refer to the + [kubeconfig.v1](https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/) documentation. + """ + @type t :: %__MODULE__{ + clusters: list(map()), + users: list(map()), + contexts: list(map()), + current_cluster: map(), + current_user: map(), + halted: boolean(), + assigns: map() + } + + defstruct clusters: [], + users: [], + contexts: [], + current_cluster: nil, + current_user: nil, + halted: false, + assigns: %{} + + @doc """ + Creates a new `%Kubereq.Kubeconfig{}` struct with the given fields + """ + @spec new!(keyword()) :: t() + def new!(fields), do: struct!(__MODULE__, fields) + + @doc """ + Sets the current context. This function sets `current_cluster` and + `current_user` in the given `Kubereq.Kubeconfig.t()` + """ + @spec set_current_context(kubeconfig :: t(), current_context :: String.t()) :: t() + def set_current_context(kubeconfig, current_context) do + context = + get_in(kubeconfig.contexts, [ + access_by_name!(current_context), + "context" + ]) + + current_cluster = + get_in(kubeconfig.clusters, [ + access_by_name!(context["cluster"]), + "cluster" + ]) + + current_user = + get_in(kubeconfig.users, [ + access_by_name!(context["user"]), + "user" + ]) + + struct!(kubeconfig, current_cluster: current_cluster, current_user: current_user) + end + + @doc """ + Loads the Kubernetes config by running the given `pipeline`. Returns the + resulting `%Kubereq.Kubeconfig{}`. + + `pipeline` can be passed in the form of `{pipeline_module, opts}` tuples, + a single `pipeline_module` or a list of either. + + ### Example + + Single pipeline module without opts passed as module: + + Kubereq.Kubeconfig.load(Kubereq.Kubeconfig.Default) + + Single pipeline module with opts: + + Kubereq.Kubeconfig.load({Kubereq.Kubeconfig.File, path: "/path/to/kubeconfig"}) + + List of either: + + Kubereq.Kubeconfig.load([ + Kubereq.Kubeconfig.ENV, + {Kubereq.Kubeconfig.File, path: ".kube/config", relative_to_home?: true}, + Kubereq.Kubeconfig.ServiceAccount + ]) + """ + @spec load(pipeline :: module()) :: t() + def load(pipeline) do + pipeline = + pipeline + |> List.wrap() + |> Enum.map(&ensure_step_opts_tuple/1) + + Pluggable.run(%__MODULE__{}, pipeline) + end + + defp ensure_step_opts_tuple(module) when is_atom(module), do: {module, []} + defp ensure_step_opts_tuple({module, opts}), do: {module, opts} + + defp access_by_name!(name), do: Access.find!(&(&1["name"] == name)) +end diff --git a/lib/kubereq/kubeconfig/access.ex b/lib/kubereq/kubeconfig/access.ex new file mode 100644 index 0000000..4b75d50 --- /dev/null +++ b/lib/kubereq/kubeconfig/access.ex @@ -0,0 +1,154 @@ +defmodule Kubereq.Kubeconfig.Access do + @moduledoc """ + Helper module to access maps in lists. + """ + + @doc ~S""" + Returns a function that accesses the first element for which `fun` returns a truthy value. + + The returned function is typically passed as an accessor to `Kernel.get_in/2`, + `Kernel.get_and_update_in/3`, and friends. + + ## Examples + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> get_in(list, [Kubereq.Kubeconfig.Access.find(&(&1.name == "john")), :salary]) + 10 + iex> get_and_update_in(list, [Kubereq.Kubeconfig.Access.find(&(&1.name == "john")), :salary], fn prev -> + ...> {prev, 15} + ...> end) + {10, [%{name: "john", salary: 15}, %{name: "francine", salary: 30}]} + + `find/1` can also be used to pop elements out of a list or + a key inside of a list: + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> pop_in(list, [Kubereq.Kubeconfig.Access.find(&(&1.name == "francine"))]) + {%{name: "francine", salary: 30}, [%{name: "john", salary: 10}]} + iex> pop_in(list, [Kubereq.Kubeconfig.Access.find(&(&1.name == "francine")), :name]) + {"francine", [%{salary: 30}, %{name: "john", salary: 10}]} + + When no match is found, the given default is used. This can be used to + specify defaults and safely traverse missing items. + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> get_in(list, [Kubereq.Kubeconfig.Access.find(&(&1.name == "adam"), %{name: "adam", salary: 50}), :salary]) + 50 + iex> get_and_update_in(list, [Kubereq.Kubeconfig.Access.find(&(&1.name == "adam"), %{name: "adam"}), :salary], fn prev -> + ...> {prev, 50} + ...> end) + {nil, [%{name: "adam", salary: 50}, %{name: "john", salary: 10}, %{name: "francine", salary: 30}]} + + When multiple items exist for which `fun` return a truthy value, the first one is accessed. + + iex> list = [%{name: "john", salary: 10}, %{name: "john", salary: 30}] + iex> get_in(list, [Kubereq.Kubeconfig.Access.find(&(&1.name == "john")), :salary]) + 10 + + An error is raised if the accessed structure is not a list: + + iex> get_in(%{}, [Kubereq.Kubeconfig.Access.find(&(&1.name == "john"))]) + ** (RuntimeError) Kubereq.Kubeconfig.Access.find/1 expected a list, got: %{} + + """ + @spec find((term -> boolean), term()) :: Access.access_fun(data :: list, current_value :: list) + def find(func, default \\ nil) when is_function(func) do + fn op, data, next -> find(op, data, func, default, next) end + end + + defp find(:get, data, func, default, next) when is_list(data) do + data |> Enum.find(default, func) |> next.() + end + + defp find(:get_and_update, data, func, default, next) when is_list(data) do + get_and_update_find(data, func, next, default, false) + end + + defp find(_op, data, _default, _func, _next) do + raise "Kubereq.Kubeconfig.Access.find/1 expected a list, got: #{inspect(data)}" + end + + @doc ~S""" + Returns a function that accesses the first element for which `fun` returns a truthy value. + + The returned function is typically passed as an accessor to `Kernel.get_in/2`, + `Kernel.get_and_update_in/3`, and friends. + + Similar to find/2, but the returned function raises if the no item is found for which `fun` returns a truthy value. + + ## Examples + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> get_in(list, [Kubereq.Kubeconfig.Access.find!(&(&1.name == "john")), :salary]) + 10 + iex> get_and_update_in(list, [Kubereq.Kubeconfig.Access.find!(&(&1.name == "john")), :salary], fn prev -> + ...> {prev, 15} + ...> end) + {10, [%{name: "john", salary: 15}, %{name: "francine", salary: 30}]} + + `find/1` can also be used to pop elements out of a list or + a key inside of a list: + + iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}] + iex> pop_in(list, [Kubereq.Kubeconfig.Access.find!(&(&1.name == "francine"))]) + {%{name: "francine", salary: 30}, [%{name: "john", salary: 10}]} + iex> pop_in(list, [Kubereq.Kubeconfig.Access.find!(&(&1.name == "francine")), :name]) + {"francine", [%{salary: 30}, %{name: "john", salary: 10}]} + iex> get_in(list, [Kubereq.Kubeconfig.Access.find!(&(&1.name == "adam")), :salary]) + ** (ArgumentError) There is no item in the list for which the given function returns a truthy value. + + When multiple items exist for which `fun` return a truthy value, the first one is accessed. + + iex> list = [%{name: "john", salary: 10}, %{name: "john", salary: 30}] + iex> get_in(list, [Kubereq.Kubeconfig.Access.find!(&(&1.name == "john")), :salary]) + 10 + + An error is raised if the accessed structure is not a list: + + iex> get_in(%{}, [Kubereq.Kubeconfig.Access.find!(&(&1.name == "john"))]) + ** (RuntimeError) Kubereq.Kubeconfig.Access.find!/1 expected a list, got: %{} + """ + @spec find!((term -> boolean)) :: Access.access_fun(data :: list, current_value :: list) + def find!(func) when is_function(func) do + fn op, data, next -> find!(op, data, func, next) end + end + + defp find!(:get, data, func, next) when is_list(data) do + case Enum.find(data, func) do + nil -> + raise ArgumentError, + "There is no item in the list for which the given function returns a truthy value." + + item -> + next.(item) + end + end + + defp find!(:get_and_update, data, func, next) when is_list(data) do + get_and_update_find(data, func, next) + end + + defp find!(_op, data, _func, _next) do + raise "Kubereq.Kubeconfig.Access.find!/1 expected a list, got: #{inspect(data)}" + end + + defp get_and_update_find(data, func, next, default \\ nil, raise? \\ true) do + {value, rest} = + case Enum.find_index(data, func) do + nil when raise? -> + raise ArgumentError, + "There is no item in the list for which the given function returns a truthy value." + + nil -> + {default, data} + + index -> + List.pop_at(data, index) + end + + case next.(value) do + {get, update} -> {get, [update | rest]} + :pop -> {value, rest} + end + end +end diff --git a/lib/kubereq/kubeconfig/default.ex b/lib/kubereq/kubeconfig/default.ex new file mode 100644 index 0000000..9183b8e --- /dev/null +++ b/lib/kubereq/kubeconfig/default.ex @@ -0,0 +1,28 @@ +defmodule Kubereq.Kubeconfig.Default do + @moduledoc """ + Default pipeline of pluggable steps for loading the Kubeconfig. Tries to + build the Kubeconfig from different default settings. + + 1. Checks for the `KUBECONFIG` environment variable. If it is set and pointing + to a Kubeconfig file, that file is imported. + 1. Checks for `$HOME/.kube/config`. + 1. Checks if running inside a Cluster and tries to connect using the Service + Account Token. + + ### Example + + Usage in a pipeline created with `Pluggable.StepBuilder`: + + defmodule MyApp.KubeconfLoader do + use Pluggable.StepBuilder + + step Kubereq.Kubeconfig.Default + end + """ + + use Pluggable.StepBuilder + + step(Kubereq.Kubeconfig.ENV) + step(Kubereq.Kubeconfig.File, path: ".kube/config", relative_to_home?: true) + step(Kubereq.Kubeconfig.ServiceAccount) +end diff --git a/lib/kubereq/kubeconfig/env.ex b/lib/kubereq/kubeconfig/env.ex new file mode 100644 index 0000000..e70842c --- /dev/null +++ b/lib/kubereq/kubeconfig/env.ex @@ -0,0 +1,46 @@ +defmodule Kubereq.Kubeconfig.ENV do + @moduledoc """ + Pluggable step that loads the Kubeconfig from a config file whose location is + defined by an ENV variable. Uses `Kubereq.Kubeconfig.File` under the hood. + + ### Examples + + step Kubereq.Kubeconfig.ENV + + By default, this step assumes the name of the variable to be `KUBECONFIG`. + This can be customized through the `:env_var` option + + step Kubereq.Kubeconfig.ENV, env_var: SPECIAL_KUBECONFIG + + ### Options + + * `env_var` - (optional) The name of the environment variable. Defaults to + `KUBECONFIG` + * `!` - (optional. And yes, that's a valid atom) + """ + + alias Kubereq.Kubeconfig.File + + @behaviour Pluggable + + @impl true + @spec init(keyword()) :: keyword() + def init(opts \\ []) do + Keyword.validate!(opts, [:env_var, :!]) + end + + @impl true + @spec call(Kubereq.Kubeconfig.t(), keyword()) :: Kubereq.Kubeconfig.t() + def call(kubeconfig, opts) do + env_var = opts[:env_var] || "KUBECONFIG" + + case System.get_env(env_var) do + nil -> + kubeconfig + + file_path -> + file_opts = File.init(path: file_path, !: opts[:!]) + File.call(kubeconfig, file_opts) + end + end +end diff --git a/lib/kubereq/kubeconfig/file.ex b/lib/kubereq/kubeconfig/file.ex new file mode 100644 index 0000000..d4e030b --- /dev/null +++ b/lib/kubereq/kubeconfig/file.ex @@ -0,0 +1,80 @@ +defmodule Kubereq.Kubeconfig.File do + @moduledoc """ + Pluggable step that load the Kubeconfig from a file. + + ``` + step Kubereq.Kubeconfig.File, path: "path/to/kubeconfig-integration.yaml" + ``` + + Pass `:relative_to_home?` to interprete the `:path` relative to the current + user's home directory at run time. + + ``` + step Kubereq.Kubeconfig.File, path: ".kube/config", relative_to_home?: true + ``` + + If the config file defined by the `:path` option is not found on disk, by + default, the step gracefully returns the kubeconfig that was passed as + argument. If you want the step to raise an `ArgumentException` instead, you + can set the option `!: true`. + + ### Options + + * `:path` - Path to the config file. + * `:relative_to_home` - (optional) Interprete the `:path` as relative to the + user's home directory at runtime. Defaults to `false`. + * `:!` - (optional. And yes, that's a valid atom) Raise an exception if the + config file is not found. Defaults to `false`. + * `:context` - (optional) Sets the current context in case there's multiple + contexts defined in the config file. Defaults to what's defined in the + "current-context" field in the loaded config. + """ + + alias Kubeconf + + @behaviour Pluggable + + @impl true + @spec init(keyword()) :: keyword() + def init(opts) do + if !opts[:path] do + raise ArgumentError, "Please pass a :path option contatining the path to the config file." + end + + Keyword.validate!(opts, [:path, :relative_to_home?, :context, :!]) + end + + @impl true + @spec call(Kubereq.Kubeconfig.t(), any()) :: Kubereq.Kubeconfig.t() + def call(kubeconf, opts) do + if !opts[:path] do + raise ArgumentError, "Please pass a :path option contatining the path to the config file." + end + + path = + if opts[:relative_to_home?], + do: Path.join(System.user_home!(), opts[:path]), + else: opts[:path] + + raise? = Keyword.get(opts, :!, false) + + case File.exists?(path) do + false when raise? -> + raise ArgumentError, "No Kubernetes config file found at #{path}." + + false -> + kubeconf + + true -> + config = YamlElixir.read_from_file!(path) + + Kubereq.Kubeconfig.new!( + clusters: config["clusters"], + users: config["users"], + contexts: config["contexts"] + ) + |> Kubereq.Kubeconfig.set_current_context(opts[:context] || config["current-context"]) + |> Pluggable.Token.halt() + end + end +end diff --git a/lib/kubereq/kubeconfig/service_account.ex b/lib/kubereq/kubeconfig/service_account.ex new file mode 100644 index 0000000..778bb61 --- /dev/null +++ b/lib/kubereq/kubeconfig/service_account.ex @@ -0,0 +1,88 @@ +defmodule Kubereq.Kubeconfig.ServiceAccount do + @moduledoc """ + Pluggable step that builds the Kubeconfig using a Service Account's token for + authentication. + + When running the app inside a Kubernetes cluster, make sure RBAC is configured + correctly and use this step. It reads the service account's JWC token and + build the Kubeconfig accordingly. + + ### Examples + + step Kubereq.Kubeconfig.ServiceAccount + + If your token is mounted at a different location than the default, pass its + location as `:path_to_folder`. + + step Kubereq.Kubeconfig.ServiceAccount, path_to_folder: "path/to/folder/with/token" + + ### Options + + * `path_to_folder` - (optional) Path to the folder where the `token`, `ca.crt` + and `namespace` files of the service account are located. Defaults to + `"/var/run/secrets/kubernetes.io/serviceaccount"` + * `:!` - (optional. And yes, that's a valid atom) Raise an exception if the + config file is not found. Defaults to `false`. + """ + + alias Kubeconf + + @behaviour Pluggable + + @sa_token_path "/var/run/secrets/kubernetes.io/serviceaccount" + + @impl true + @spec init(keyword()) :: keyword() + def init(opts \\ []) do + Keyword.validate!(opts, [:path_to_folder, :!]) + end + + @impl true + @spec call(Kubereq.Kubeconfig.t(), keyword()) :: Kubereq.Kubeconfig.t() + def call(kubeconf, opts) do + path_to_folder = opts[:path_to_folder] || @sa_token_path + apiserver_host = System.get_env("KUBERNETES_SERVICE_HOST") + apiserver_port = System.get_env("KUBERNETES_SERVICE_PORT_HTTPS") + + files = ["token", "ca.crt", "namespace"] |> Enum.map(&Path.join(path_to_folder, &1)) + + raise? = Keyword.get(opts, :!, false) + + case Enum.all?(files, &File.exists?/1) do + false when raise? -> + raise ArgumentError, "Could not find all required files: #{inspect(files)}." + + false -> + kubeconf + + true -> + [token_file, ca_file, namespace_file] = files + + cluster = %{ + "name" => "default", + "cluster" => %{ + "certificate-authority" => ca_file, + server: "https://#{apiserver_host}:#{apiserver_port}" + } + } + + user = %{ + "name" => "default", + "user" => %{"tokenFile" => token_file} + } + + context = %{ + "name" => "default", + "context" => %{ + "cluster" => cluster["name"], + "user" => user["name"], + "namespace" => File.read!(namespace_file) + } + } + + Kubereq.Kubeconfig.new!(clusters: [cluster], users: [user], contexts: [context]) + |> Kubereq.Kubeconfig.set_current_context(context["name"]) + |> Pluggable.Token.halt() + end + end +end diff --git a/mix.exs b/mix.exs index 014119d..1e8160a 100644 --- a/mix.exs +++ b/mix.exs @@ -39,7 +39,6 @@ defmodule Kubereq.MixProject do {:pluggable, "~> 1.0"}, {:req, "~> 0.4.0"}, {:yaml_elixir, "~> 2.0"}, - {:kubeconf, path: "../kubeconf"}, # Test deps {:excoveralls, "~> 0.18", only: :test}, diff --git a/test/kubereq/kubeconfig/access_test.exs b/test/kubereq/kubeconfig/access_test.exs new file mode 100644 index 0000000..62a29de --- /dev/null +++ b/test/kubereq/kubeconfig/access_test.exs @@ -0,0 +1,4 @@ +defmodule Kubereq.Kubeconfig.AccessTest do + use ExUnit.Case, async: true + doctest Kubereq.Kubeconfig.Access +end diff --git a/test/kubereq/kubeconfig/env_test.exs b/test/kubereq/kubeconfig/env_test.exs new file mode 100644 index 0000000..29a314b --- /dev/null +++ b/test/kubereq/kubeconfig/env_test.exs @@ -0,0 +1,42 @@ +defmodule Kubereq.Kubeconfig.ENVTest do + use ExUnit.Case + + alias Kubereq.Kubeconfig.ENV, as: MUT + + test "imports config from file at location of KUBECONFIG env var" do + System.put_env("KUBECONFIG", "test/support/kubeconfig-unit.yaml") + kubeconfig = MUT.call(%Kubereq.Kubeconfig{}, []) + assert 4 === length(kubeconfig.clusters) + assert 7 === length(kubeconfig.users) + assert 3 === length(kubeconfig.contexts) + assert "https://localhost:6443" === kubeconfig.current_cluster["server"] + assert Map.has_key?(kubeconfig.current_user, "client-certificate-data") + end + + test "imports config from file at location of custom env var" do + env_var_name = "FOOCONFIG" + System.put_env(env_var_name, "test/support/kubeconfig-unit.yaml") + kubeconfig = MUT.call(%Kubereq.Kubeconfig{}, MUT.init(env_var: env_var_name)) + assert 4 === length(kubeconfig.clusters) + assert 7 === length(kubeconfig.users) + assert 3 === length(kubeconfig.contexts) + assert "https://localhost:6443" === kubeconfig.current_cluster["server"] + assert Map.has_key?(kubeconfig.current_user, "client-certificate-data") + end + + test "raises if file not found but only if !: true" do + System.put_env("KUBECONFIG", "/does/not/exist") + + assert_raise( + ArgumentError, + "No Kubernetes config file found at /does/not/exist.", + fn -> MUT.call(%Kubereq.Kubeconfig{}, MUT.init(!: true)) end + ) + + _kubeconfig = MUT.call(%Kubereq.Kubeconfig{}, MUT.init([])) + end + + test "init/1 raises if unsupported options passed" do + assert_raise ArgumentError, fn -> MUT.init(foo: :bar) end + end +end diff --git a/test/kubereq/kubeconfig/file_test.exs b/test/kubereq/kubeconfig/file_test.exs new file mode 100644 index 0000000..9471b1c --- /dev/null +++ b/test/kubereq/kubeconfig/file_test.exs @@ -0,0 +1,46 @@ +defmodule Kubereq.Kubeconfig.FileTest do + use ExUnit.Case, async: true + + alias Kubereq.Kubeconfig.File, as: MUT + + test "Imports config from file" do + kubeconfig = + MUT.call(%Kubereq.Kubeconfig{}, MUT.init(path: "test/support/kubeconfig-unit.yaml")) + + assert 4 === length(kubeconfig.clusters) + assert 7 === length(kubeconfig.users) + assert 3 === length(kubeconfig.contexts) + assert "https://localhost:6443" === kubeconfig.current_cluster["server"] + assert Map.has_key?(kubeconfig.current_user, "client-certificate-data") + end + + test "raises if file not found but only if !: true" do + assert_raise( + ArgumentError, + "No Kubernetes config file found at /does/not/exist.", + fn -> MUT.call(%Kubereq.Kubeconfig{}, MUT.init(path: "/does/not/exist", !: true)) end + ) + + _kubeconf = MUT.call(%Kubereq.Kubeconfig{}, MUT.init(path: "/does/not/exist")) + end + + test "init/1 raises if path not given" do + assert_raise( + ArgumentError, + "Please pass a :path option contatining the path to the config file.", + fn -> MUT.init([]) end + ) + end + + test "aises if path not given" do + assert_raise( + ArgumentError, + "Please pass a :path option contatining the path to the config file.", + fn -> MUT.call(%Kubereq.Kubeconfig{}, []) end + ) + end + + test "init/1 raises if unsupported options passed" do + assert_raise ArgumentError, fn -> MUT.init(foo: :bar) end + end +end diff --git a/test/kubereq/kubeconfig/service_account_test.exs b/test/kubereq/kubeconfig/service_account_test.exs new file mode 100644 index 0000000..dd91ea9 --- /dev/null +++ b/test/kubereq/kubeconfig/service_account_test.exs @@ -0,0 +1,36 @@ +defmodule Kubereq.Kubeconfig.ServiceAccountTest do + use ExUnit.Case, async: true + + alias Kubereq.Kubeconfig.ServiceAccount, as: MUT + + test "Imports config from file" do + path_to_folder = "test/support/kubeconfig/tls" + kubeconfig = MUT.call(%Kubereq.Kubeconfig{}, MUT.init(path_to_folder: path_to_folder)) + + assert 1 === length(kubeconfig.clusters) + cluster = List.first(kubeconfig.clusters) + assert "default" === cluster["name"] + assert "#{path_to_folder}/ca.crt" === cluster["cluster"]["certificate-authority"] + assert 1 === length(kubeconfig.users) + user = List.first(kubeconfig.users) + assert "default" === user["name"] + assert "#{path_to_folder}/token" === user["user"]["tokenFile"] + + assert 1 === length(kubeconfig.contexts) + context = List.first(kubeconfig.contexts) + assert "default" === context["name"] + assert "foo-namespace" === context["context"]["namespace"] + end + + test "raises if file not found but only if !: true" do + assert_raise ArgumentError, fn -> + MUT.call(%Kubereq.Kubeconfig{}, MUT.init(path_to_folder: "/does/not/exist", !: true)) + end + + _kubeconfig = MUT.call(%Kubereq.Kubeconfig{}, MUT.init(path_to_folder: "/does/not/exist")) + end + + test "init/1 raises if unsupported options passed" do + assert_raise ArgumentError, fn -> MUT.init(foo: :bar) end + end +end diff --git a/test/kubereq/step/auth_test.exs b/test/kubereq/step/auth_test.exs index 7719e11..022e6e0 100644 --- a/test/kubereq/step/auth_test.exs +++ b/test/kubereq/step/auth_test.exs @@ -9,7 +9,7 @@ defmodule Kubereq.Step.AuthTest do test "Sets certfile and keyfile transport options" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_user: %{"client-certificate" => "/path/to/cert", "client-key" => "/path/to/key"} ) @@ -21,7 +21,7 @@ defmodule Kubereq.Step.AuthTest do test "Sets cert and key transport options" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_user: %{ "client-certificate-data" => "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5RENDQWR5Z0F3SUJBZ0lJY0FwYS9xZlNVc013RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RFeE1Ua3dNREk1TlRGYUZ3MHhPVEV5TVRFd01qQTNORGhhTURZeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sc3dHUVlEVlFRREV4SmtiMk5yWlhJdFptOXlMV1JsCmMydDBiM0F3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRFJqRzdZK2xSN1VlbnMKVUo1aGkvRWlnem53bnpQdWR4NkJLZjkwaG9zVldpeFlZVmlZQ2FYWXhiUk1RMDZQUUhXV2ZVWGEvcEZqWTdQUwpjMllyZ05JZU1oMm5XS2hKbUFGelc5ZFRsV0QrUkNmS3dRVlFYa3ZBVS8rUVczU3pvRUpieHRpczRIc0Vyc2tvCllkeVcxb2hRSm1yc3MxakYzZE93NTQ5cElUSUM3T3VZd0ZQVmx3TWprUmNKUUpMbjJ4UjBIVCt1UmUxTHp0UEoKK2QwdTkvYmpSTERpbnVJYWZhYjZzN3M3Nk52YmJVYXBsSy82RnVxbzhhNUt4Z0lOYXJPNkVHWlZuRU1XMVVxNAorNFVVb1lrdVRWcXJVTlBvSzJ5Yy9wamxySENna3dpTTU3cXNQS00yWVNXRTlXSFZGMGZyZS81bzZtRGRUOU1TCkg5d3ZqNm8vQWdNQkFBR2pKekFsTUE0R0ExVWREd0VCL3dRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFhOHJKUHVsQlMxYWRnb1J5WGo4ak9ZaVpjV3crNUJZTwpuQW5JS2hBWVEvZHBMYXhhcG4zODNHVS9ZeGhKM3E3azExNnZBSmdRTkdPNXBHS3M2b3k3M2FWMGd6ZU00ZklGCnlFN0dNTG1BQVN6QzRJUlIvc0JOWUlKbTlaZERsbmVicEJxTkhIUWt5dlJpdWNMdjFDVDl4dTI0NTBoOW5RSlAKWTJQSmVMSVRhYUhNWVk5eTBPWnQvOGVoNnFyTks2RlY5VWN4bnZYVHQwUW1qL0k2aXdlZ1BVb0t2Vm5aamF0ZQpLOHJMdlQ3SXJLSDRubGRrOUNtclYyZTMwT3IzRVFHeXlOM0xJTGlBa3R2Z3BHNXlLd2s1M2RPSzJtNkl4QTlyCnBzMFRYUmUxMUhlVVhsWm1PcERKVHMwa3VzTGVjVE05ZW5MN1VhK3RSakRtR08xTFo0RDFidz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K", @@ -38,7 +38,7 @@ defmodule Kubereq.Step.AuthTest do test "Sets bearer token auth option" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{"server" => "https://example.com"}, current_user: %{"token" => "foo-token"} ) @@ -57,7 +57,7 @@ defmodule Kubereq.Step.AuthTest do test "Sets bearer token auth option to token from file" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{"server" => "https://example.com"}, current_user: %{"tokenFile" => "test/support/token"} ) @@ -76,7 +76,7 @@ defmodule Kubereq.Step.AuthTest do test "Sets basic auth option " do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{"server" => "https://example.com"}, current_user: %{"username" => "foo", "password" => "bar"} ) @@ -103,7 +103,7 @@ defmodule Kubereq.Step.AuthTest do } kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{"server" => "https://example.com"}, current_user: %{"exec" => exec_config} ) diff --git a/test/kubereq/step/base_url_test.exs b/test/kubereq/step/base_url_test.exs index b49a909..6c6f96e 100644 --- a/test/kubereq/step/base_url_test.exs +++ b/test/kubereq/step/base_url_test.exs @@ -8,7 +8,7 @@ defmodule Kubereq.Step.BaseUrlTest do end test "sets the base url" do - kubeconfig = Kubeconf.new!(current_cluster: %{"server" => "https://example.com"}) + kubeconfig = Kubereq.Kubeconfig.new!(current_cluster: %{"server" => "https://example.com"}) kubeconfig |> Kubereq.new("unused") diff --git a/test/kubereq/step/compression_test.exs b/test/kubereq/step/compression_test.exs index 981b09f..1460a33 100644 --- a/test/kubereq/step/compression_test.exs +++ b/test/kubereq/step/compression_test.exs @@ -9,7 +9,7 @@ defmodule Kubereq.Step.CompressionTest do test "enables compression by default" do kubeconfig = - Kubeconf.new!(current_cluster: %{"server" => "https://example.com"}) + Kubereq.Kubeconfig.new!(current_cluster: %{"server" => "https://example.com"}) req = kubeconfig @@ -27,7 +27,7 @@ defmodule Kubereq.Step.CompressionTest do test "enables compression upon request" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{"server" => "https://example.com", "disable-compression" => false} ) @@ -45,7 +45,7 @@ defmodule Kubereq.Step.CompressionTest do test "disables compression upon request" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{"server" => "https://example.com", "disable-compression" => true} ) @@ -63,7 +63,7 @@ defmodule Kubereq.Step.CompressionTest do test "disables compression if request body is nil" do kubeconfig = - Kubeconf.new!(current_cluster: %{"server" => "https://example.com"}) + Kubereq.Kubeconfig.new!(current_cluster: %{"server" => "https://example.com"}) kubeconfig |> Kubereq.new("unused") diff --git a/test/kubereq/step/impersonate_test.exs b/test/kubereq/step/impersonate_test.exs index d54c741..3310b62 100644 --- a/test/kubereq/step/impersonate_test.exs +++ b/test/kubereq/step/impersonate_test.exs @@ -10,7 +10,7 @@ defmodule Kubereq.Step.ImpersonateTest do @tag :wip test "Sets impersonation headers" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{"server" => "https://example.com"}, current_user: %{ "as" => "calvin", diff --git a/test/kubereq/step/tls_test.exs b/test/kubereq/step/tls_test.exs index e7506a6..b1a7e34 100644 --- a/test/kubereq/step/tls_test.exs +++ b/test/kubereq/step/tls_test.exs @@ -8,12 +8,12 @@ defmodule Kubereq.Step.TLSTest do end test "sets the verify option" do - kubeconfig = Kubeconf.new!(current_cluster: %{"server" => "https://example.com"}) + kubeconfig = Kubereq.Kubeconfig.new!(current_cluster: %{"server" => "https://example.com"}) req = kubeconfig |> Kubereq.new("unused") |> MUT.call() assert :verify_peer === get_in(req.options, ~w"connect_options transport_opts verify"a) kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{"server" => "https://example.com", "insecure-skip-tls-verify" => true} ) @@ -23,7 +23,7 @@ defmodule Kubereq.Step.TLSTest do test "sets the cacertfile option" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{ "server" => "https://example.com", "certificate-authority" => "/path/to/ca.crt" @@ -38,7 +38,7 @@ defmodule Kubereq.Step.TLSTest do test "sets the cacert option" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{ "server" => "https://example.com", "certificate-authority-data" => @@ -55,7 +55,7 @@ defmodule Kubereq.Step.TLSTest do test "sets the SNI option" do kubeconfig = - Kubeconf.new!( + Kubereq.Kubeconfig.new!( current_cluster: %{ "server" => "https://example.com", "tls-server-name" => "localhost" diff --git a/test/kubereq_integration_test.exs b/test/kubereq_integration_test.exs index fc96691..08a7a0f 100644 --- a/test/kubereq_integration_test.exs +++ b/test/kubereq_integration_test.exs @@ -4,7 +4,7 @@ defmodule KubereqIntegrationTest do import YamlElixir.Sigil @cluster_name "kubereq" - @kubeconfig_path "test/support/kubeconfig.yaml" + @kubeconfig_path "test/support/kubeconfig-integration.yaml" @resource_path_ns "api/v1/namespaces/:name" @resource_path_cm "api/v1/namespaces/:namespace/configmaps/:name" @namespace "integrationtest" @@ -26,7 +26,11 @@ defmodule KubereqIntegrationTest do ) end - kubeconf = Kubeconf.load({Kubeconf.File, path: "test/support/kubeconfig.yaml"}) + kubeconf = + Kubereq.Kubeconfig.load( + {Kubereq.Kubeconfig.File, path: "test/support/kubeconfig-integration.yaml"} + ) + req_ns = Kubereq.new(kubeconf, @resource_path_ns) req_cm = Kubereq.new(kubeconf, @resource_path_cm) diff --git a/test/support/kubeconfig-unit.yaml b/test/support/kubeconfig-unit.yaml new file mode 100644 index 0000000..31314ae --- /dev/null +++ b/test/support/kubeconfig-unit.yaml @@ -0,0 +1,75 @@ +apiVersion: v1 +clusters: + - cluster: + insecure-skip-tls-verify: true + server: https://localhost:6443 + name: insecure-cluster + - cluster: + insecure-skip-tls-verify: true + server: https://localhost:6443 + name: kubeconf-elixir-client-cluster + - cluster: + certificate-authority: tls/certificate.pem + server: https://localhost:6443 + name: cert-cluster + - cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5RENDQWR5Z0F3SUJBZ0lJY0FwYS9xZlNVc013RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RFeE1Ua3dNREk1TlRGYUZ3MHhPVEV5TVRFd01qQTNORGhhTURZeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sc3dHUVlEVlFRREV4SmtiMk5yWlhJdFptOXlMV1JsCmMydDBiM0F3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRFJqRzdZK2xSN1VlbnMKVUo1aGkvRWlnem53bnpQdWR4NkJLZjkwaG9zVldpeFlZVmlZQ2FYWXhiUk1RMDZQUUhXV2ZVWGEvcEZqWTdQUwpjMllyZ05JZU1oMm5XS2hKbUFGelc5ZFRsV0QrUkNmS3dRVlFYa3ZBVS8rUVczU3pvRUpieHRpczRIc0Vyc2tvCllkeVcxb2hRSm1yc3MxakYzZE93NTQ5cElUSUM3T3VZd0ZQVmx3TWprUmNKUUpMbjJ4UjBIVCt1UmUxTHp0UEoKK2QwdTkvYmpSTERpbnVJYWZhYjZzN3M3Nk52YmJVYXBsSy82RnVxbzhhNUt4Z0lOYXJPNkVHWlZuRU1XMVVxNAorNFVVb1lrdVRWcXJVTlBvSzJ5Yy9wamxySENna3dpTTU3cXNQS00yWVNXRTlXSFZGMGZyZS81bzZtRGRUOU1TCkg5d3ZqNm8vQWdNQkFBR2pKekFsTUE0R0ExVWREd0VCL3dRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFhOHJKUHVsQlMxYWRnb1J5WGo4ak9ZaVpjV3crNUJZTwpuQW5JS2hBWVEvZHBMYXhhcG4zODNHVS9ZeGhKM3E3azExNnZBSmdRTkdPNXBHS3M2b3k3M2FWMGd6ZU00ZklGCnlFN0dNTG1BQVN6QzRJUlIvc0JOWUlKbTlaZERsbmVicEJxTkhIUWt5dlJpdWNMdjFDVDl4dTI0NTBoOW5RSlAKWTJQSmVMSVRhYUhNWVk5eTBPWnQvOGVoNnFyTks2RlY5VWN4bnZYVHQwUW1qL0k2aXdlZ1BVb0t2Vm5aamF0ZQpLOHJMdlQ3SXJLSDRubGRrOUNtclYyZTMwT3IzRVFHeXlOM0xJTGlBa3R2Z3BHNXlLd2s1M2RPSzJtNkl4QTlyCnBzMFRYUmUxMUhlVVhsWm1PcERKVHMwa3VzTGVjVE05ZW5MN1VhK3RSakRtR08xTFo0RDFidz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://123.123.123.123 + name: cluster-with-cert-data +contexts: + - context: + cluster: kubeconf-elixir-client-cluster + user: kubeconf-elixir-client + name: kubeconf-elixir-client + - context: + cluster: insecure-cluster + user: kubeconf-elixir-client + name: insecure-context + - context: + cluster: cluster-with-cert-data + user: cluster-with-cert-data + name: cluster-with-cert-data +current-context: kubeconf-elixir-client +kind: Config +preferences: {} +users: + - name: token-user + user: + token: just-a-token-user-pun-intended + - name: basic-auth-user + user: + username: basic-auth-username + password: basic-auth-password + - name: pem-cert-user + user: + client-certificate: tls/certificate.pem + client-key: tls/key.pem + - name: base64-cert-user + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5RENDQWR5Z0F3SUJBZ0lJY0FwYS9xZlNVc013RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RFeE1Ua3dNREk1TlRGYUZ3MHhPVEV5TVRFd01qQTNORGhhTURZeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sc3dHUVlEVlFRREV4SmtiMk5yWlhJdFptOXlMV1JsCmMydDBiM0F3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRFJqRzdZK2xSN1VlbnMKVUo1aGkvRWlnem53bnpQdWR4NkJLZjkwaG9zVldpeFlZVmlZQ2FYWXhiUk1RMDZQUUhXV2ZVWGEvcEZqWTdQUwpjMllyZ05JZU1oMm5XS2hKbUFGelc5ZFRsV0QrUkNmS3dRVlFYa3ZBVS8rUVczU3pvRUpieHRpczRIc0Vyc2tvCllkeVcxb2hRSm1yc3MxakYzZE93NTQ5cElUSUM3T3VZd0ZQVmx3TWprUmNKUUpMbjJ4UjBIVCt1UmUxTHp0UEoKK2QwdTkvYmpSTERpbnVJYWZhYjZzN3M3Nk52YmJVYXBsSy82RnVxbzhhNUt4Z0lOYXJPNkVHWlZuRU1XMVVxNAorNFVVb1lrdVRWcXJVTlBvSzJ5Yy9wamxySENna3dpTTU3cXNQS00yWVNXRTlXSFZGMGZyZS81bzZtRGRUOU1TCkg5d3ZqNm8vQWdNQkFBR2pKekFsTUE0R0ExVWREd0VCL3dRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFhOHJKUHVsQlMxYWRnb1J5WGo4ak9ZaVpjV3crNUJZTwpuQW5JS2hBWVEvZHBMYXhhcG4zODNHVS9ZeGhKM3E3azExNnZBSmdRTkdPNXBHS3M2b3k3M2FWMGd6ZU00ZklGCnlFN0dNTG1BQVN6QzRJUlIvc0JOWUlKbTlaZERsbmVicEJxTkhIUWt5dlJpdWNMdjFDVDl4dTI0NTBoOW5RSlAKWTJQSmVMSVRhYUhNWVk5eTBPWnQvOGVoNnFyTks2RlY5VWN4bnZYVHQwUW1qL0k2aXdlZ1BVb0t2Vm5aamF0ZQpLOHJMdlQ3SXJLSDRubGRrOUNtclYyZTMwT3IzRVFHeXlOM0xJTGlBa3R2Z3BHNXlLd2s1M2RPSzJtNkl4QTlyCnBzMFRYUmUxMUhlVVhsWm1PcERKVHMwa3VzTGVjVE05ZW5MN1VhK3RSakRtR08xTFo0RDFidz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBMFl4dTJQcFVlMUhwN0ZDZVlZdnhJb001OEo4ejduY2VnU24vZElhTEZWb3NXR0ZZCm1BbWwyTVcwVEVOT2owQjFsbjFGMnY2UlkyT3owbk5tSzREU0hqSWRwMWlvU1pnQmMxdlhVNVZnL2tRbnlzRUYKVUY1THdGUC9rRnQwczZCQ1c4YllyT0I3Qks3SktHSGNsdGFJVUNacTdMTll4ZDNUc09lUGFTRXlBdXpybU1CVAoxWmNESTVFWENVQ1M1OXNVZEIwL3JrWHRTODdUeWZuZEx2ZjI0MFN3NHA3aUduMm0rck83TytqYjIyMUdxWlN2CitoYnFxUEd1U3NZQ0RXcXp1aEJtVlp4REZ0Vkt1UHVGRktHSkxrMWFxMURUNkN0c25QNlk1YXh3b0pNSWpPZTYKckR5ak5tRWxoUFZoMVJkSDYzdithT3BnM1UvVEVoL2NMNCtxUHdJREFRQUJBb0lCQUdwUVY0VGFMTGFNYnFRbwptdEplejY1MDZaWjlEem56VVpTeW5CcWdrRHY3RGZpaEd2TzRJVjZEbjkvNVhnZ3I2Znk1L2hFSGl2ZmtBNzNJCk1wUHJ2YTc0T2pkSE1jcDB4bmVpcHZLUEhUQ2puNVNzcldlREQrZTZOalVsVVdZNDdySGxodFRlNTBzTzZwd0UKV29oa3U2LytiYzA5aU5LS292Wmo1VXl2UE5KaU9oL1l4cmpOdU9KdURWSXNraXExRXRkczdwV21Rb01XcXo5eQowYkxwY1Mwc24zb3I0amxaNy9UcGtsM1cvZEhDWjg5SzB5NTVUNjJnOTY5UFhBTXZCQ2FEMUlEcjRJajhSRGg3CmpBNFFwSXkzKzI0SVBKQWQ0RUw4Qlk3QXIvc0E1SE02R3VLNHo2cURNSXk1UFlYZmIwVzMwNUpGWlJTdEx3cEwKdTJCL1BBRUNnWUVBMjF4eXU3cVZ1QURSNWRZb0NFUXVDMm5SVjBMT3kzNzZRck5XbDlOdzlzcHVWSzBySCtXcQpmeWI4Rnd6Y0FSbkg4K2s2d3FDaUhDVUwrMnYwdGw2Uld1a2txTGJReE9aOVRNYS9PL2lRaVRaUDlZdkM2MUpTCnF2dWlZZ2ZWRVlQWUVyODlNbGxTVGFkRDRLVjJmVnFVa3pHanB5TnBrUzU5b0ZDTFZvMFZDejhDZ1lFQTlJeHEKSDRuYS9BUmZzVkEzR3Q0QzRxblBzQ29qY0tGS0taZ2ZpeGx4a0tOUlMva0FxczU0UTNubXpBNFdRLy9XcmNKQgpmSEFLeFgvM05rdGVjWWRBWmhmY3BIQWVmcmlkUHNDelIvRTg0WUFBS21TdVJZY25OWE9BVmNQd2ExTmJnbFJDCmw0QWN2dk9aTEN5c3I0dWNaYkVtNFBMZURkeUpoUURvZ2dlWW9RRUNnWUVBcDhML09CVk5kV2lqSGp4M1owTUYKVjlNNHQ1eXZYTEFpb3lwV21reXB3d1F6OXV4czQ3c1lkcUFSQVd2alFiQSszSXBOVnhYVWhPUE1VeDlRQ3IwdQpPekc4eUk3d0FQWXBjN000QTV4b3BaZDA5VnhLMlArZm00WlF2Tm95bUcrVnExaTRhNjRtSko4OGFTMEIvb0pzCnlGbVpTRFRzQW8xa3BGdVZCTDluRGE4Q2dZQmVoWU5qUzFKa0JJREVOVUFIVjNhQUM2aWw2N09sRGdKdlQwZ3AKNkp5M2poaVhKOWgxTExiWlJkM0tVMHVSM3Vvb1lTUVVwKzNSNXFNenppL2o2NllkaisyTmRYU2tBRkZ1OXVhVQowUTU2RHBLQjBFWjN3MFNKYVdwYVBCREtPdjdzd2dxM0tpSnlRQStkUG10RXNzNnhrNloyWGdrc0RHanZDcW5UCjBJSFRBUUtCZ1FDUjQ2clpzRDJxUm54S0huYzcyaWRlbjJCK25NQnJoODdmUmE4alBaQU1SajEzTG5RbkxoZHgKV2IwUDdRdFlYWEowTDJkK1FKRFJXZFEzNEZyMmlndkZsUFJkREQzTXJjNVBscEM2Y05HM1BlcndVOFV3VS83UwpuRGhWSWdoRXZRUjFQS29vZFdjMEVrYkxnZ01FdFR2WkdCejVHelVudTY4OXpGV1VzZmtKRHc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + - name: kubeconf-elixir-client + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5RENDQWR5Z0F3SUJBZ0lJY0FwYS9xZlNVc013RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RFeE1Ua3dNREk1TlRGYUZ3MHhPVEV5TVRFd01qQTNORGhhTURZeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sc3dHUVlEVlFRREV4SmtiMk5yWlhJdFptOXlMV1JsCmMydDBiM0F3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRFJqRzdZK2xSN1VlbnMKVUo1aGkvRWlnem53bnpQdWR4NkJLZjkwaG9zVldpeFlZVmlZQ2FYWXhiUk1RMDZQUUhXV2ZVWGEvcEZqWTdQUwpjMllyZ05JZU1oMm5XS2hKbUFGelc5ZFRsV0QrUkNmS3dRVlFYa3ZBVS8rUVczU3pvRUpieHRpczRIc0Vyc2tvCllkeVcxb2hRSm1yc3MxakYzZE93NTQ5cElUSUM3T3VZd0ZQVmx3TWprUmNKUUpMbjJ4UjBIVCt1UmUxTHp0UEoKK2QwdTkvYmpSTERpbnVJYWZhYjZzN3M3Nk52YmJVYXBsSy82RnVxbzhhNUt4Z0lOYXJPNkVHWlZuRU1XMVVxNAorNFVVb1lrdVRWcXJVTlBvSzJ5Yy9wamxySENna3dpTTU3cXNQS00yWVNXRTlXSFZGMGZyZS81bzZtRGRUOU1TCkg5d3ZqNm8vQWdNQkFBR2pKekFsTUE0R0ExVWREd0VCL3dRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFhOHJKUHVsQlMxYWRnb1J5WGo4ak9ZaVpjV3crNUJZTwpuQW5JS2hBWVEvZHBMYXhhcG4zODNHVS9ZeGhKM3E3azExNnZBSmdRTkdPNXBHS3M2b3k3M2FWMGd6ZU00ZklGCnlFN0dNTG1BQVN6QzRJUlIvc0JOWUlKbTlaZERsbmVicEJxTkhIUWt5dlJpdWNMdjFDVDl4dTI0NTBoOW5RSlAKWTJQSmVMSVRhYUhNWVk5eTBPWnQvOGVoNnFyTks2RlY5VWN4bnZYVHQwUW1qL0k2aXdlZ1BVb0t2Vm5aamF0ZQpLOHJMdlQ3SXJLSDRubGRrOUNtclYyZTMwT3IzRVFHeXlOM0xJTGlBa3R2Z3BHNXlLd2s1M2RPSzJtNkl4QTlyCnBzMFRYUmUxMUhlVVhsWm1PcERKVHMwa3VzTGVjVE05ZW5MN1VhK3RSakRtR08xTFo0RDFidz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBMFl4dTJQcFVlMUhwN0ZDZVlZdnhJb001OEo4ejduY2VnU24vZElhTEZWb3NXR0ZZCm1BbWwyTVcwVEVOT2owQjFsbjFGMnY2UlkyT3owbk5tSzREU0hqSWRwMWlvU1pnQmMxdlhVNVZnL2tRbnlzRUYKVUY1THdGUC9rRnQwczZCQ1c4YllyT0I3Qks3SktHSGNsdGFJVUNacTdMTll4ZDNUc09lUGFTRXlBdXpybU1CVAoxWmNESTVFWENVQ1M1OXNVZEIwL3JrWHRTODdUeWZuZEx2ZjI0MFN3NHA3aUduMm0rck83TytqYjIyMUdxWlN2CitoYnFxUEd1U3NZQ0RXcXp1aEJtVlp4REZ0Vkt1UHVGRktHSkxrMWFxMURUNkN0c25QNlk1YXh3b0pNSWpPZTYKckR5ak5tRWxoUFZoMVJkSDYzdithT3BnM1UvVEVoL2NMNCtxUHdJREFRQUJBb0lCQUdwUVY0VGFMTGFNYnFRbwptdEplejY1MDZaWjlEem56VVpTeW5CcWdrRHY3RGZpaEd2TzRJVjZEbjkvNVhnZ3I2Znk1L2hFSGl2ZmtBNzNJCk1wUHJ2YTc0T2pkSE1jcDB4bmVpcHZLUEhUQ2puNVNzcldlREQrZTZOalVsVVdZNDdySGxodFRlNTBzTzZwd0UKV29oa3U2LytiYzA5aU5LS292Wmo1VXl2UE5KaU9oL1l4cmpOdU9KdURWSXNraXExRXRkczdwV21Rb01XcXo5eQowYkxwY1Mwc24zb3I0amxaNy9UcGtsM1cvZEhDWjg5SzB5NTVUNjJnOTY5UFhBTXZCQ2FEMUlEcjRJajhSRGg3CmpBNFFwSXkzKzI0SVBKQWQ0RUw4Qlk3QXIvc0E1SE02R3VLNHo2cURNSXk1UFlYZmIwVzMwNUpGWlJTdEx3cEwKdTJCL1BBRUNnWUVBMjF4eXU3cVZ1QURSNWRZb0NFUXVDMm5SVjBMT3kzNzZRck5XbDlOdzlzcHVWSzBySCtXcQpmeWI4Rnd6Y0FSbkg4K2s2d3FDaUhDVUwrMnYwdGw2Uld1a2txTGJReE9aOVRNYS9PL2lRaVRaUDlZdkM2MUpTCnF2dWlZZ2ZWRVlQWUVyODlNbGxTVGFkRDRLVjJmVnFVa3pHanB5TnBrUzU5b0ZDTFZvMFZDejhDZ1lFQTlJeHEKSDRuYS9BUmZzVkEzR3Q0QzRxblBzQ29qY0tGS0taZ2ZpeGx4a0tOUlMva0FxczU0UTNubXpBNFdRLy9XcmNKQgpmSEFLeFgvM05rdGVjWWRBWmhmY3BIQWVmcmlkUHNDelIvRTg0WUFBS21TdVJZY25OWE9BVmNQd2ExTmJnbFJDCmw0QWN2dk9aTEN5c3I0dWNaYkVtNFBMZURkeUpoUURvZ2dlWW9RRUNnWUVBcDhML09CVk5kV2lqSGp4M1owTUYKVjlNNHQ1eXZYTEFpb3lwV21reXB3d1F6OXV4czQ3c1lkcUFSQVd2alFiQSszSXBOVnhYVWhPUE1VeDlRQ3IwdQpPekc4eUk3d0FQWXBjN000QTV4b3BaZDA5VnhLMlArZm00WlF2Tm95bUcrVnExaTRhNjRtSko4OGFTMEIvb0pzCnlGbVpTRFRzQW8xa3BGdVZCTDluRGE4Q2dZQmVoWU5qUzFKa0JJREVOVUFIVjNhQUM2aWw2N09sRGdKdlQwZ3AKNkp5M2poaVhKOWgxTExiWlJkM0tVMHVSM3Vvb1lTUVVwKzNSNXFNenppL2o2NllkaisyTmRYU2tBRkZ1OXVhVQowUTU2RHBLQjBFWjN3MFNKYVdwYVBCREtPdjdzd2dxM0tpSnlRQStkUG10RXNzNnhrNloyWGdrc0RHanZDcW5UCjBJSFRBUUtCZ1FDUjQ2clpzRDJxUm54S0huYzcyaWRlbjJCK25NQnJoODdmUmE4alBaQU1SajEzTG5RbkxoZHgKV2IwUDdRdFlYWEowTDJkK1FKRFJXZFEzNEZyMmlndkZsUFJkREQzTXJjNVBscEM2Y05HM1BlcndVOFV3VS83UwpuRGhWSWdoRXZRUjFQS29vZFdjMEVrYkxnZ01FdFR2WkdCejVHelVudTY4OXpGV1VzZmtKRHc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + - name: auth-provider-user + user: + auth-provider: + config: + access-token: "" + cmd-args: config config-helper --format=json + cmd-path: /Users/user/google-cloud-sdk/bin/gcloud + expiry: 2018-10-29 21:06:53 + expiry-key: "{.credential.token_expiry}" + token-key: "{.credential.access_token}" + name: gcp + - name: exec-user + user: + exec: + apiVersion: client.authentication.kubeconf.io/v1alpha1 + command: echo + args: + - "foo" + env: + - name: "FOO" + value: "bar" diff --git a/test/support/kubeconfig.yaml b/test/support/kubeconfig.yaml deleted file mode 100644 index 52fcef3..0000000 --- a/test/support/kubeconfig.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJWjNzaXd2TXV5Z3N3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRBek1qY3hOREk1TkRkYUZ3MHpOREF6TWpVeE5ETTBORGRhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUM4V3JaRDlPR21Ra2dBbEUzdmNOb2Z2Unl1MVFXZktsdUpGZXhRRDE0MnU3T2dDWmNCNUlPVmJOdlYKU2dmWXloUDdXS0NOVXZ2QmtEcGZGNGNMYm01U2cvc281bmFZTGxSSzdDYmpwWUVOTFZMRDdWU0xmTUkvMlB3dApzOHVYbGtyaWQvd3FVM3ZpVWRsRkhMKzJjTVZMNGJCSUJaZlJFMmFRa2w4OXBMSXc2RnNoeFBNMnQ5eVpUTEM4Ck1DTGJiRG9uUENtUGJEdVZhWGJkd3B6c09ZZ1VmanJJWWNPM0VwYVB2djQ3eVFKSnBnbUd2Nm42cUxmYTltOXgKdFFxaXJTTlRMUjNaS3lJcXZTVUQ4N285WUlPQktGSUhEM1gzaGZzNFA0K1Eya2ozN3dXb0NBOHVuYnJVeXA0aQpIVWYvTkd5UVczTEpxSDN6ZGNBOXpaY0lpUzRUQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUdE9Kb3lLdnE3Sk5LdE5aTzNkekgvSzhBK0NUQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQmhzWSt5OC9pYwo2Q0JhOUpGYW1HTkIrbTVkcG9GTUp2M1BNWFI3UTMyUE9lZkdBYkJhOXdJMC9KdTNKVHVidFhZZXo2S2dNQ080CmN5Vmg2ZFlJUG5QbGR3WjZlMklKWTZ0dDg1eUFYL2pYY1BQYWdPS0tHdHZEODQ2WTc5UDJ1ck5aUWJtdFVqUVYKUDVWR0xWQnVicHNuMThuZE1ta0o4d095dWxxdWFVQU9Gc2hQV29tWVF0OEkvR0kzSnVWNTJ5d2F1UjRMVjNSdApFVU1LVHZCTCtKLzhkcmMxaE03U2V5MGtWcGsyL0JzeEhLVUJkQ0ZOWTlvcy9OSXdlS01ZdS9MdkdPbXprU0p4CkNJbHE0amVVMG1vdXFzTURyMTBlT0NVZzFVdzBsdFlaT29NN1ZSKzNKM2NMQXhDR2d5MS8wRDVzK2ozM2JpR1IKNC9XdUtxdXZHNFppCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K - server: https://127.0.0.1:50264 - name: kind-kubereq -contexts: -- context: - cluster: kind-kubereq - user: kind-kubereq - name: kind-kubereq -current-context: kind-kubereq -kind: Config -preferences: {} -users: -- name: kind-kubereq - user: - client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJS1dRaHNkVW8rb1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRBek1qY3hOREk1TkRkYUZ3MHlOVEF6TWpjeE5ETTBORGxhTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDN2k1UzUKb00yVzVvTkp6WUptenAwY3BSMm9BcG9ISFNraE1VdXhmMXRRQlFYZTFUek0ySXQ2MXUyRlJpRzlBN214T3RndApPMkFWTEREOU9CY2V3SEtVTXIzU3hydGFNY3Z4MzN1YUtMbkNveWdGQTRrS2VHS21qcmJTcUdFd3kxbExBa2ozCkJOc21mVkFad1gxNTY1NnU2MjFuRUZxc1kveVk5K3hrSVN0eWZ3N0Y5K05UZWJvZUtVZ2ZIMXB4cGcvNCs4ZWoKUEhWbUdoS01ZR21YcTdyUHZTa2dtMEtxaFRUOW5ydGNUM0Y3bEVLdWYwTjFJOVRmSnlxSHhDTE1KVHZrMXEvVApEUjdNQllvWXFTdEJkYUpMVTZKcXZHVGcrTkhtWEIrMHU4WDZoTXhMMGdGL1hoTTVZYncyY1FxdUpOc1ZQMTI5CkxVbXpWcG5PcEFJa29majNBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRk8wNG1qSXErcnNrMHEwMQprN2QzTWY4cndENEpNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUFJTDFadVJKWUc0OTRJTk55eSt2UHdnKzdlCmFWOUV5ZFM5SmpQK2pVb2xDdnhhV3dBclE1MHlyd3hyaXpSQ2NWa0tpUUM1T1BmeEozWVBMVGFaZGNmaXl6QWEKbkJzSVlZbHFUNVBteGRSOU4veXNIRU1OeUs4djlhc2ZZZ1VHOGFzcDZ3eUVid2ZHVlhGeml6dlFCVGJEYXBjVgptdWthOXFjSFZqUFZzM0liU203S3d6ZVFxdmNQRW9SRWhmS1FMc1pTTVB4MzQyL0lKUkptNS9zNklmajRKdHRuCnhBaE5ybHJMWitkNGpsQUFzaVhHRkQ5dWZZeTdCcnJteGRlK0lOZzhjMVQ4b3JpVCswRkdyTGowb2NWRGloZGgKOW5XYTk1QjM5d3dlZmJKVGJXTU9QSFhOV2lET1QyeW1rYlYrSnlYRk0vQS8wVENKdGgvVjBQTVBiYzhmCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K - client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBdTR1VXVhRE5sdWFEU2MyQ1pzNmRIS1VkcUFLYUJ4MHBJVEZMc1g5YlVBVUYzdFU4CnpOaUxldGJ0aFVZaHZRTzVzVHJZTFR0Z0ZTd3cvVGdYSHNCeWxESzkwc2E3V2pITDhkOTdtaWk1d3FNb0JRT0oKQ25oaXBvNjIwcWhoTU10WlN3Skk5d1RiSm4xUUdjRjllZXVlcnV0dFp4QmFyR1A4bVBmc1pDRXJjbjhPeGZmagpVM202SGlsSUh4OWFjYVlQK1B2SG96eDFaaG9TakdCcGw2dTZ6NzBwSUp0Q3FvVTAvWjY3WEU5eGU1UkNybjlECmRTUFUzeWNxaDhRaXpDVTc1TmF2MHcwZXpBV0tHS2tyUVhXaVMxT2lhcnhrNFBqUjVsd2Z0THZGK29UTVM5SUIKZjE0VE9XRzhObkVLcmlUYkZUOWR2UzFKczFhWnpxUUNKS0g0OXdJREFRQUJBb0lCQUFjZ0pJUnhBWjZzSDJ6YwpiQW52NjdRODZjV0tZOFBGYmR5ZS9Ocy9sWDZTMGtoSEhjcENwL29BMUlGdHlyWmR2OTVsOS9Tdi90U1BHMFZqCnJpMHk3cWVHemdPR0hYUXQxQ3ZhYTkzTmtsRTgxYlpyRGZmaDN3Mm8rblZDaEg3UUd4Sm9nZEV3MUFyQVZCekcKWnpnTXBiakxwUnAzV3BxV0tmQzk2ck5iTjBWZ1Vid0tlM29ncERCYVZ2NnBScTE5R2pSSFRXak5BRm9TK1IxOQpNbzU2NG4wRFpYNXowU3dEU2hLVmFGWGFseE9tdm13cGNYa1dNeHNIci9DWDZwYVF1WmcxSzBEU3F1Zm94MHJ3CnlrRzIxbkZ2T1dsREFFK3owZzFRMEZzQ2RKT1ZyWFpuOE5DeDNYUUJuSEFhMFVrdzhxYmxOK2RXZG91Zy9CVXAKTHFXMU9HRUNnWUVBOExkSXVGbVhBVWFyZ3VuRzM5Qkk5YkgwVnh5OFRIL29keWU5TzdRVTZFTXNoMUxZYmJQSwpLSUZDQ1RUNWlEWnA0UVpmdDY3MzhqTkN1dFM1R2MzUFROdVlmWlNsM25FY29zS255K3AvanVMbTczV2ZJZGp4CnNacDNHcUZXQkpCZXM5SVdNZ2Rad09pK25UYngzNWg5NU1HeGEyS3JSYnZGZHdqNUlTQnVSd2tDZ1lFQXgzUUkKcWNoUmhTRFlHdzQvMzA2Rk93ajdWd1NCUm1YMGFqT3F5Rmc4Q3hzU3ZTckY5SEVIVTNCSXAvTE9oQlErNzZZVQpyMzk0a0JjbFVEWFZjNmw3OVJ2Z1NEOEhRT21rN0xZRnhlUzZiZHFVVEJpbEJENkJGZVlGdjJOTkg1WHJsMVlFClJRVUJCREVSU2dDWjlqMGJrUGg2SC9ITzdpbGdPaks1Rmk1ZVAvOENnWUJzVjRJaWZ5aEpDYjYrUUd3alprTEEKV3VVSHBFWllCQ1hrSTMyM2Rua0doTjF3MXVmVWtGeEgyTTJaQ1QvYWYrc3R2Y210Q2RKTkw2YzBJZlJoMmRFWQozYW1IdERUbnBtOW1Ea2lwcmhUVmdlakJGR2FZUEdXa00wcXlNTFZmZm44eFhqaGhNZy9DbEw3ZVVkR1JlcmdICkV1ckEwM3NVK3N1SXBRQkVXYTFveVFLQmdCNUZ1MGJoczVYTUxXQzE0ZlhudWdzQ3l2c2pJT2ZwYzVabTd5WG4KVm4ydEIwUVU2T1MzL3crbi9DOFppRVM5dWV1dnZuWUFsL0R1d21nUk02dVJidEgrQUNXWEEyZlR6b2RxL1NkVApGd1N6WjB6QmROSlhGa3FYbGdtOWJhZzhuZnhqUHRTbTlNZUpGd0d2WTY3ZkNYOFFaNkRQbDhQSUhlWFhhd1kxCjFYSkxBb0dBUTd3RXFydWtoaExsaSthMWdsd3V6djhJRy9FSnFkYmZIVFgvSXJ1WWhBSFM0bGl4VjVNR2VLTFEKVCtyeDhSQkV5NWZvaUxrcmU2SVluZHhjWkIveFkvWlR0ZXJXRXM5SUhzMi96aCt2UWt6UXhIazZMeFd3NmNaZAo1MTBWNS9JeTR4NUF2VnA0bnIyK3VVdVpmTXRNT3k0QS9FOXFyWG1PbU1Ccm45NTBsd1U9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== diff --git a/test/support/kubeconfig/tls/ca.crt b/test/support/kubeconfig/tls/ca.crt new file mode 100644 index 0000000..73d5c07 --- /dev/null +++ b/test/support/kubeconfig/tls/ca.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC9jCCAd4CCQCJfWpO8UHSKTANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCFBhc2FkZW5hMQ4wDAYDVQQKDAVib25u +eTAeFw0xODEyMTMyMzI5MjdaFw0xOTEyMTMyMzI5MjdaMD0xCzAJBgNVBAYTAlVT +MQswCQYDVQQIDAJDQTERMA8GA1UEBwwIUGFzYWRlbmExDjAMBgNVBAoMBWJvbm55 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp8tUS+/GW1slTXvQWRU3 +tfs6ULdwODIKCNyiR7++asIK1wQEn6gfhQ92YACN907L3uhKu5tI2Fv3s6f/Z1Fy +qPnOeOWFqDotxQqAPTSamO+MhrYkkMsdmN8z15+EptW7xZCSwazLG6fZh1whk0q1 +JhMlFDKxnK53ykoB18LASnhFt+SYeV267Az52vxHr7SdVJ/wll1bddDikfOP1fKa +2RtQH0mAfrTVgKC2W+Ubl4GLAR+QNWDRbMLRzk/SM/7cK6ZVJwP7M6gYog3aRTqy +aWMdzhXhhf4sto5Ot9EraS3pRv3m4u78mBglt2tN0RnIqWh3oZw1JFxT6ioQjgvP +cwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBTWo0LjuQIe+lvI3YURHuZHs3l5Ya8 +JGy1Nunmmp0bnLNMYD1Q5pJpIcxXwJQX9u68nO0dnBaYTNjm3lu5838oZjvgYlWz +4WN3PrQPZZf3THF5RdRGBPOSXoEHwgaeKWCzLxOsDYD5xH2/sLLHeKCVu/Ln2gw7 +FmU7wp/1hcrJzEbdO0cS3ER1nB+y76yGtkj8PLiNey0Gvh38tcVnA8Mb1ZRJRVLf +b0/howwBNALFrfoYad+u8iLqGWUrhSKNerqCGqQEn+sl1d2JxXn3lPillsR/yvSN +lYey6quMI6kcdSO7Tmcs/u8yRXi8TJfQisqYEuPeuwpohOGpkmQyhkGi +-----END CERTIFICATE----- diff --git a/test/support/kubeconfig/tls/certificate.pem b/test/support/kubeconfig/tls/certificate.pem new file mode 100644 index 0000000..73d5c07 --- /dev/null +++ b/test/support/kubeconfig/tls/certificate.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC9jCCAd4CCQCJfWpO8UHSKTANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCFBhc2FkZW5hMQ4wDAYDVQQKDAVib25u +eTAeFw0xODEyMTMyMzI5MjdaFw0xOTEyMTMyMzI5MjdaMD0xCzAJBgNVBAYTAlVT +MQswCQYDVQQIDAJDQTERMA8GA1UEBwwIUGFzYWRlbmExDjAMBgNVBAoMBWJvbm55 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp8tUS+/GW1slTXvQWRU3 +tfs6ULdwODIKCNyiR7++asIK1wQEn6gfhQ92YACN907L3uhKu5tI2Fv3s6f/Z1Fy +qPnOeOWFqDotxQqAPTSamO+MhrYkkMsdmN8z15+EptW7xZCSwazLG6fZh1whk0q1 +JhMlFDKxnK53ykoB18LASnhFt+SYeV267Az52vxHr7SdVJ/wll1bddDikfOP1fKa +2RtQH0mAfrTVgKC2W+Ubl4GLAR+QNWDRbMLRzk/SM/7cK6ZVJwP7M6gYog3aRTqy +aWMdzhXhhf4sto5Ot9EraS3pRv3m4u78mBglt2tN0RnIqWh3oZw1JFxT6ioQjgvP +cwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBTWo0LjuQIe+lvI3YURHuZHs3l5Ya8 +JGy1Nunmmp0bnLNMYD1Q5pJpIcxXwJQX9u68nO0dnBaYTNjm3lu5838oZjvgYlWz +4WN3PrQPZZf3THF5RdRGBPOSXoEHwgaeKWCzLxOsDYD5xH2/sLLHeKCVu/Ln2gw7 +FmU7wp/1hcrJzEbdO0cS3ER1nB+y76yGtkj8PLiNey0Gvh38tcVnA8Mb1ZRJRVLf +b0/howwBNALFrfoYad+u8iLqGWUrhSKNerqCGqQEn+sl1d2JxXn3lPillsR/yvSN +lYey6quMI6kcdSO7Tmcs/u8yRXi8TJfQisqYEuPeuwpohOGpkmQyhkGi +-----END CERTIFICATE----- diff --git a/test/support/kubeconfig/tls/key.pem b/test/support/kubeconfig/tls/key.pem new file mode 100644 index 0000000..11385d6 --- /dev/null +++ b/test/support/kubeconfig/tls/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAq3u0hSHlB/JkdbN+pH6qW8z4pRIpzPIe0B40Te9z4HM5fToG +27Gp+NSdkWEslqgaF0oOa4/omkQ+7dLS3XrDlQcj9bjJAc5wktDBEeMR30h3HLu6 +5BKpecLov0cR96ibKPn457be0USN9j0jqX/KFSZP9xgFZgsKXEB7xVToRxwpJEyf +HYYcl6+x1leqKch7jhOlVAmmtjVWCgwwTzXIidcmmgCth+GZvdIKz/HXUtpq+DKm +Y8Jy2BLeWWeNwxp+6GEZy/y4eyd5bSPXzVIZIAsiMF9QveCF508BxCdR4Jid+Dov +ChTGT5lfwBN/C8P6HnytDhTF3c/V5KJ+ar/8lwIDAQABAoIBAQCihibsOtiXtIqz +5v+zJX2ObfCGenV1kU/UzZui9bwsUOh+oB1Z4MOIWvJPk/qvevjnc7/A/+5jLyU5 +P6OsV6rANrlFLPINLTwSWZlyu3P32/VQHTie6nUHOsxeZRxJTdpodqoJ6o65ZbDq +XL3M6pl7fAkz1edf9DzIfDESMEvJAAEGEL+n5SU+HmlR9GT2j5sCaFslIayIpbcK +lh1vMOSoa6lI+pX/N66iBZwbRIR6umeG61f+h/cjvAeJTbMjyhujEohMk0kkDkew +VyvZgApKAjQCDsoaPSO1vOIufge2XEh1OZMxIhVXMcamw1hYwKFIMMtGW8L6PfpQ ++rFotgxpAoGBANtjkuojxBpztwFIcRsd5e4Yn9CgdTO9Y+w3/zUUVbNSw62IxXht +paQa3rNHrHfdplSLKlnDLV/rzvguupaP/3gNVcbm6PTFtB18u5W3qzY9qwFjdTzb +U4dn2v9BOglJkG+vEMzAIAd1008zkHBNxmhH+2sbNnI5UN/8Y/ombIgLAoGBAMgZ +keG/yQ0tRm+r/Bowa374usjhAxvz9S1U8xX075ayDoppgrpwR9BJ3OZTtwRAbpdu +qkJKgBrf4zpLNs3OIJbNmWDz8O0rfT5cSePW0M6faDGhtIauen0cidEt7YrYjk/z +0CWt09yweLElVGbKL6mZZiC6hvHqzAGHspKtndklAoGAUbowWqTkQu5keEfiSrXC +mAX4UyHTR2kANllN6xoeKuVkG9tdpNVD5XRVDfhfWW/qrIgDSNkaqCcSPQY7YCdY +hgKBt80Bb13PEUUMJGP9lgE3GRdR70/NOB4KSRilZBxRgCl5Wq0fwWe1RkXxQsTW +araWBHpot/h+FlLRc7ioqa0CgYEAniLfEGqx6FD8lmpz7PqRKaJGc1SoUWIGRFIb +XiHZat3C8g4ae18a0fhdtEU6oQiOneyeb49TGGoRuNMSN6mMRcGsUlvW7ohtDWiA +GKh/EpwWCwtBkSK4uMFiBUdnsFAef/8uY2ixw3pPl6fpwOZOrpkFhh4DYZJusVLd +hF/jHGUCgYEArFMerS2zBUFa+pzDE2IpyC3ewWkfp998YYshYVHPK3gHF6Z1tSDA +f2CwPFG4rgTjvp2NhckIUK6rS7xPzMne85K2MGDE4lRXgwUcGnd27otFgzGxhYV6 +kgE+prjwS6mGLWR1/2ORHTqdb5rlO33ehRLhF02FXuaHnHf8IPLBjCI= +-----END RSA PRIVATE KEY----- diff --git a/test/support/kubeconfig/tls/namespace b/test/support/kubeconfig/tls/namespace new file mode 100644 index 0000000..3f9386a --- /dev/null +++ b/test/support/kubeconfig/tls/namespace @@ -0,0 +1 @@ +foo-namespace \ No newline at end of file diff --git a/test/support/kubeconfig/tls/token b/test/support/kubeconfig/tls/token new file mode 100644 index 0000000..e310d28 --- /dev/null +++ b/test/support/kubeconfig/tls/token @@ -0,0 +1 @@ +foo-token \ No newline at end of file