From 9e1a689fe11c0dcc7969fae36c4af1bb81182fa6 Mon Sep 17 00:00:00 2001 From: Michael Ruoss Date: Sun, 14 May 2023 10:08:47 +0200 Subject: [PATCH 1/7] add failing test for pods/log connect op --- .../client/runner/base_integration_test.exs | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/test/k8s/client/runner/base_integration_test.exs b/test/k8s/client/runner/base_integration_test.exs index cfad71e7..449ed5e5 100644 --- a/test/k8s/client/runner/base_integration_test.exs +++ b/test/k8s/client/runner/base_integration_test.exs @@ -271,7 +271,7 @@ defmodule K8s.Client.Runner.BaseIntegrationTest do @tag :integration @tag :websocket - test "runs :connect operations and returns stdout", %{ + test "runs :connect operations for pods/exec and returns stdout", %{ conn: conn, labels: labels, test_id: test_id @@ -303,6 +303,43 @@ defmodule K8s.Client.Runner.BaseIntegrationTest do assert response.stdout =~ "ok" end + @tag :integration + @tag :websocket + @tag :wip + @tailLines 5 + test "runs :connect operations for pods/log and returns stdout", %{ + conn: conn, + labels: labels, + test_id: test_id + } do + {:ok, created_pod} = + build_pod("k8s-ex-#{test_id}", labels) + |> K8s.Client.create() + |> K8s.Client.put_conn(conn) + |> K8s.Client.run() + + {:ok, _} = + K8s.Client.wait_until(conn, K8s.Client.get(created_pod), + find: ["status", "containerStatuses", Access.filter(&(&1["ready"] == true))], + eval: &match?([_ | _], &1), + timeout: 60 + ) + + {:ok, response} = + K8s.Client.connect( + created_pod["apiVersion"], + "pods/log", + [namespace: K8s.Resource.namespace(created_pod), name: K8s.Resource.name(created_pod)], + tailLines: @tailLines + ) + |> K8s.Client.put_conn(conn) + |> K8s.Client.run() + + assert String.printable?(response.stdout) + lines = String.split(response.stdout) + assert @tailLines == length(lines) + end + @tag :integration @tag :websocket test "runs :connect operations and returns errors", %{ From b4b1b2f084b4d863504a2477efec4ffd9f48feed Mon Sep 17 00:00:00 2001 From: Michael Ruoss Date: Sun, 14 May 2023 10:12:17 +0200 Subject: [PATCH 2/7] handle binary frames with no leading byte --- lib/k8s/client/mint/request.ex | 1 + test/k8s/client/runner/base_integration_test.exs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/k8s/client/mint/request.ex b/lib/k8s/client/mint/request.ex index 83d29b8a..08dfdc42 100644 --- a/lib/k8s/client/mint/request.ex +++ b/lib/k8s/client/mint/request.ex @@ -132,6 +132,7 @@ defmodule K8s.Client.Mint.Request do def map_frame({:binary, <<1, msg::binary>>}), do: {:stdout, msg} def map_frame({:binary, <<2, msg::binary>>}), do: {:stderr, msg} def map_frame({:binary, <<3, msg::binary>>}), do: {:error, msg} + def map_frame({:binary, <>}), do: {:stdout, msg} @spec map_outgoing_frame({:stdin, binary()} | {:close, integer(), binary()} | :close | :exit) :: {:ok, :close | {:text, binary} | {:close, integer(), binary()}} diff --git a/test/k8s/client/runner/base_integration_test.exs b/test/k8s/client/runner/base_integration_test.exs index 449ed5e5..48dd48f2 100644 --- a/test/k8s/client/runner/base_integration_test.exs +++ b/test/k8s/client/runner/base_integration_test.exs @@ -336,7 +336,7 @@ defmodule K8s.Client.Runner.BaseIntegrationTest do |> K8s.Client.run() assert String.printable?(response.stdout) - lines = String.split(response.stdout) + lines = String.split(response.stdout, "\n", trim: true) assert @tailLines == length(lines) end From 2959f1f985e4f5ef9dc17f5270997cf5666f2faa Mon Sep 17 00:00:00 2001 From: Michael Ruoss Date: Sun, 14 May 2023 10:19:32 +0200 Subject: [PATCH 3/7] fix: allow params for pod/logs --- lib/k8s/operation.ex | 20 ++++++++++++++++--- .../client/runner/base_integration_test.exs | 6 +++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/k8s/operation.ex b/lib/k8s/operation.ex index a7782531..8a1c391d 100644 --- a/lib/k8s/operation.ex +++ b/lib/k8s/operation.ex @@ -30,6 +30,22 @@ defmodule K8s.Operation do apply: "application/apply-patch+yaml" } + @allowed_connect_params [ + :command, + :container, + :follow, + :limitBytes, + :pretty, + :previous, + :sinceSeconds, + :stderr, + :stdin, + :stdout, + :tailLines, + :timestamps, + :tty + ] + defstruct method: nil, verb: nil, api_version: nil, @@ -212,9 +228,7 @@ defmodule K8s.Operation do verb === :connect -> [stdin: true, stdout: true, stderr: true, tty: false] - |> Keyword.merge( - Keyword.take(opts, [:stdin, :stdout, :stderr, :tty, :command, :container]) - ) + |> Keyword.merge(Keyword.take(opts, @allowed_connect_params)) true -> [] diff --git a/test/k8s/client/runner/base_integration_test.exs b/test/k8s/client/runner/base_integration_test.exs index 48dd48f2..c092f34b 100644 --- a/test/k8s/client/runner/base_integration_test.exs +++ b/test/k8s/client/runner/base_integration_test.exs @@ -306,7 +306,7 @@ defmodule K8s.Client.Runner.BaseIntegrationTest do @tag :integration @tag :websocket @tag :wip - @tailLines 5 + @tail_lines 5 test "runs :connect operations for pods/log and returns stdout", %{ conn: conn, labels: labels, @@ -330,14 +330,14 @@ defmodule K8s.Client.Runner.BaseIntegrationTest do created_pod["apiVersion"], "pods/log", [namespace: K8s.Resource.namespace(created_pod), name: K8s.Resource.name(created_pod)], - tailLines: @tailLines + tailLines: @tail_lines ) |> K8s.Client.put_conn(conn) |> K8s.Client.run() assert String.printable?(response.stdout) lines = String.split(response.stdout, "\n", trim: true) - assert @tailLines == length(lines) + assert @tail_lines == length(lines) end @tag :integration From 703542e806bf3d601f944fc2f1890732f0c0b336 Mon Sep 17 00:00:00 2001 From: Michael Ruoss Date: Sun, 14 May 2023 20:54:22 +0200 Subject: [PATCH 4/7] separate lists of allowed params for exec and logs --- lib/k8s/operation.ex | 23 +++++++++++-------- .../client/runner/base_integration_test.exs | 3 +-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/k8s/operation.ex b/lib/k8s/operation.ex index 8a1c391d..6543b2e4 100644 --- a/lib/k8s/operation.ex +++ b/lib/k8s/operation.ex @@ -30,20 +30,19 @@ defmodule K8s.Operation do apply: "application/apply-patch+yaml" } - @allowed_connect_params [ - :command, + @exec_default_params [stdin: true, stdout: true, stderr: true, tty: false] + @exec_allowed_connect_params [:stdin, :stdout, :stderr, :tty, :command, :container] + + @log_allowed_connect_params [ :container, :follow, + :insecureSkipTLSVerifyBackend, :limitBytes, :pretty, :previous, :sinceSeconds, - :stderr, - :stdin, - :stdout, :tailLines, - :timestamps, - :tty + :timestamps ] defstruct method: nil, @@ -226,9 +225,13 @@ defmodule K8s.Operation do force: Keyword.get(opts, :force, true) ] - verb === :connect -> - [stdin: true, stdout: true, stderr: true, tty: false] - |> Keyword.merge(Keyword.take(opts, @allowed_connect_params)) + verb === :connect and name_or_kind == "pods/exec" -> + @exec_default_params + |> Keyword.merge(opts) + |> Keyword.take(@exec_allowed_connect_params) + + verb === :connect and name_or_kind == "pods/log" -> + Keyword.take(opts, @log_allowed_connect_params) true -> [] diff --git a/test/k8s/client/runner/base_integration_test.exs b/test/k8s/client/runner/base_integration_test.exs index c092f34b..1d1cb2d6 100644 --- a/test/k8s/client/runner/base_integration_test.exs +++ b/test/k8s/client/runner/base_integration_test.exs @@ -257,7 +257,7 @@ defmodule K8s.Client.Runner.BaseIntegrationTest do end @tag :integration - test "creating a operation without correct kind `pod/exec` should return an error", %{ + test "creating a operation without correct kind should return an error", %{ conn: conn } do operation = @@ -305,7 +305,6 @@ defmodule K8s.Client.Runner.BaseIntegrationTest do @tag :integration @tag :websocket - @tag :wip @tail_lines 5 test "runs :connect operations for pods/log and returns stdout", %{ conn: conn, From 450df4d140ed73285fbed34b893b0ad919222ef5 Mon Sep 17 00:00:00 2001 From: Michael Ruoss Date: Sun, 14 May 2023 20:58:06 +0200 Subject: [PATCH 5/7] disable credo check for this function --- lib/k8s/operation.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/k8s/operation.ex b/lib/k8s/operation.ex index 6543b2e4..7152a703 100644 --- a/lib/k8s/operation.ex +++ b/lib/k8s/operation.ex @@ -207,6 +207,7 @@ defmodule K8s.Operation do ) end + # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity def build(verb, api_version, name_or_kind, path_params, data, opts) do http_method = @verb_map[verb] || verb patch_type = Keyword.get(opts, :patch_type, :not_set) @@ -219,18 +220,18 @@ defmodule K8s.Operation do query_params = cond do - verb === :patch && patch_type === :apply -> + verb === :patch and patch_type === :apply -> [ fieldManager: Keyword.get(opts, :field_manager, "elixir"), force: Keyword.get(opts, :force, true) ] - verb === :connect and name_or_kind == "pods/exec" -> + verb === :connect and name_or_kind === "pods/exec" -> @exec_default_params |> Keyword.merge(opts) |> Keyword.take(@exec_allowed_connect_params) - verb === :connect and name_or_kind == "pods/log" -> + verb === :connect and name_or_kind === "pods/log" -> Keyword.take(opts, @log_allowed_connect_params) true -> From 51ab01d6a5a4f82cdb99d86d2cb0c045e4183cff Mon Sep 17 00:00:00 2001 From: Michael Ruoss Date: Sun, 14 May 2023 21:16:42 +0200 Subject: [PATCH 6/7] guide and changelog --- CHANGELOG.md | 1 + guides/operations.md | 45 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd80499f..a4555501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `K8s.Client.connect/4` - Support connecting to `pods/log` subresource. - [#254](https://github.com/coryodaniel/k8s/issues/254), [#255](https://github.com/coryodaniel/k8s/issues/255) - `K8s.Conn.from_env/2` - Generates configuration from a file defined by an env variable. - [#251](https://github.com/coryodaniel/k8s/pull/251) - `K8s.Conn` - Better hexdocs diff --git a/guides/operations.md b/guides/operations.md index e8d432e8..b5c251e7 100644 --- a/guides/operations.md +++ b/guides/operations.md @@ -177,11 +177,12 @@ conn |> Enum.into([]) ``` -## Connect to pods and execute commands +## Connect to `pods/exec` subresource and execute commands -The `:connect` operation is used to connect to pods and execute commands. -A `:connect` operation is created with `K8s.Client.connect/N`. Be sure to pass -the command you want to run in the options. +Use the `:connect` operation to connect to the `pods/exec` subresource and +execute commands. A `:connect` operation is created with `K8s.Client.connect/N`. +When connecting to `pods/exec`, be sure to pass the command you want to run in +the options. ### Waiting for command termination @@ -263,3 +264,39 @@ commands). See the example below. {:ok, response} = K8s.Client.run(conn, op) ``` + +## Connect to `pods/log` subresource to read logs from Pods + +Use the `:connect` operation to connect to the `pods/log` subresource. A +`:connect` operation is created with `K8s.Client.connect/N`. + +### Options + +Refer to the [Kubernetes +documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#read-log-pod-v1-core) +for documentation on these options. + +- `container` +- `follow` - Use with `K8s.Client.stream/N` or `K8s.Client.stream_to/N`. +- `insecureSkipTLSVerifyBackend` +- `limitBytes` +- `pretty` +- `previous` +- `sinceSeconds` +- `tailLines` +- `timestamps` + +```elixir + {:ok, conn} = K8s.Conn.from_file("~/.kube/config") + + {:ok, stream} = K8s.Client.connect( + "v1", + "pods/log", + [namespace: "default", name: "nginx-8f458dc5b-zwmkb"], + command: ["/bin/sh", "-c", "nginx -t"], + container: "main", + follow: true + ) + |> K8s.Client.put_conn(conn) + |> K8s.Client.stream() +``` From e54a8fa6c4eb265acc6d067532723812b8ce0f15 Mon Sep 17 00:00:00 2001 From: Michael Ruoss Date: Sun, 14 May 2023 21:36:17 +0200 Subject: [PATCH 7/7] no need for binary pattern matching --- lib/k8s/client/mint/request.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/k8s/client/mint/request.ex b/lib/k8s/client/mint/request.ex index 08dfdc42..067e46fb 100644 --- a/lib/k8s/client/mint/request.ex +++ b/lib/k8s/client/mint/request.ex @@ -132,7 +132,7 @@ defmodule K8s.Client.Mint.Request do def map_frame({:binary, <<1, msg::binary>>}), do: {:stdout, msg} def map_frame({:binary, <<2, msg::binary>>}), do: {:stderr, msg} def map_frame({:binary, <<3, msg::binary>>}), do: {:error, msg} - def map_frame({:binary, <>}), do: {:stdout, msg} + def map_frame({:binary, msg}), do: {:stdout, msg} @spec map_outgoing_frame({:stdin, binary()} | {:close, integer(), binary()} | :close | :exit) :: {:ok, :close | {:text, binary} | {:close, integer(), binary()}}