Skip to content

Commit

Permalink
Merge pull request #37 from mruoss/websockets
Browse files Browse the repository at this point in the history
Basic websocket implementation with exec and log helper functions
  • Loading branch information
mruoss authored Nov 22, 2024
2 parents aaacb42 + 9d9429a commit 8c562ea
Show file tree
Hide file tree
Showing 15 changed files with 930 additions and 21 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!--------------------- Don't add new entries after this line --------------------->

## 0.3.2 - 2024-11-22

### Added

- Support for websocket connections to `pods/log` and `pods/exec` subresources [#37](https://github.com/mruoss/kubereq/pull/37)

## 0.3.1 - 2024-10-24

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The package can be installed by adding `kubereq` to your list of dependencies in
```elixir
def deps do
[
{:kubereq, "~> 0.3.0"}
{:kubereq, "~> 0.3.2"}
]
end
```
Expand Down
161 changes: 153 additions & 8 deletions lib/kubereq.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
defmodule Kubereq do
@moduledoc ~S"""
Kubereq processes requests to your Kubernetes API Server.
A Kubernetes client for Elixir based on `Req`.
## Usage
This library can used with plan `Req` but the function in this module
provide an easier API to people used to `kubectl` and friends.
First, attach `kubereq` to your `Req` request (see `attach/2` for options):
### Plain Req
Req.new() |> Kubereq.attach()
Now you can use plain Req functionality. However, the functions defined in
this module make it much easier to perform the most common operation.
### Usage with plain Req functionality
Use `Kubereq.Kubeconfig.Default` to create connection to cluster and
plain `Req.request()` to make the request
Expand Down Expand Up @@ -45,7 +49,7 @@ defmodule Kubereq do
Req.request!(sa_req, operation: :list, path_params: [namespace: "default"])
```
### Kubectl API
### Kubereq API
While this library can attach to any `Req` struct, it is sometimes easier
to prepare `Req` for a specific resource and then use the functions
Expand Down Expand Up @@ -119,15 +123,20 @@ defmodule Kubereq do
end

@doc """
Attaches `kubereq` to a `Req.Request` struct for making HTTP requests to a Kubernetes
cluster. You can optionally pass a Kubernetes configuration or pipeline via
`kubeconfig` option. If it is omitted, the default config
Attaches `kubereq` to a `Req.Request` struct for making HTTP requests to a
Kubernetes cluster. You can optionally pass a Kubernetes configuration or
pipeline via `kubeconfig` option. If it is omitted, the default config
`Kubereq.Kubeconfig.Default` is loaded.
### Examples
iex> Req.new() |> Kubereq.attach()
%Request.Req{...}
### Options
All options (see Options section in module doc) are accepted and merged with
the given req.
"""
@spec attach(req :: Req.Request.t(), opts :: Keyword.t()) :: Req.Request.t()
def attach(req, opts \\ []) do
Expand Down Expand Up @@ -639,4 +648,140 @@ defmodule Kubereq do
{:ok, stream}
end
end

@doc """
Opens a websocket to the given container and streams logs from it.
> #### Info {: .tip}
>
> This function blocks the process. It should be used to retrieve a finite
> set of logs from a container. If you want to follow logs, use
> `Kubereq.PodLogs` combined with the `:follow` options instead.
## Examples
req = Req.new() |> Kubereq.attach()
{:ok, resp} =
Kubereq.logs(req, "default", "my-pod",
container: "main-container",
tailLines: 5
)
Enum.each(resp.body, &IO.inspect/1)
## Options
* `:container` - The container for which to stream logs. Defaults to only
container if there is one container in the pod. Fails if not defined for
pods with multiple pods.
* `:follow` - Follow the log stream of the pod. If this is set to `true`,
the connection is kept alive which blocks current the process. If you need
this, you probably want to use `Kubereq.PodLogs` instead. Defaults to
`false`.
* `:insecureSkipTLSVerifyBackend` - insecureSkipTLSVerifyBackend indicates
that the apiserver should not confirm the validity of the serving
certificate of the backend it is connecting to. This will make the HTTPS
connection between the apiserver and the backend insecure. This means the
apiserver cannot verify the log data it is receiving came from the real
kubelet. If the kubelet is configured to verify the apiserver's TLS
credentials, it does not mean the connection to the real kubelet is
vulnerable to a man in the middle attack (e.g. an attacker could not
intercept the actual log data coming from the real kubelet).
* `:limitBytes` - If set, the number of bytes to read from the server before
terminating the log output. This may not display a complete final line of
logging, and may return slightly more or slightly less than the specified
limit.
* `:pretty` - If 'true', then the output is pretty printed.
* `:previous` - Return previous t erminated container logs. Defaults to
`false`.
* `:sinceSeconds` - A relative time in seconds before the current time from
which to show logs. If this value precedes the time a pod was started,
only logs since the pod start will be returned. If this value is in the
future, no logs will be returned. Only one of sinceSeconds or sinceTime
may be specified.
* `:tailLines` - If set, the number of lines from the end of the logs to
show. If not specified, logs are shown from the creation of the container
or sinceSeconds or sinceTime
* `:timestamps` - If true, add an RFC3339 or RFC3339Nano timestamp at the
beginning of every line of log output. Defaults to `false`.
"""
@spec logs(
Req.Request.t(),
namespace :: namespace(),
name :: String.t(),
opts :: Keyword.t() | nil
) ::
response()
def logs(req, namespace, name, opts \\ []) do
opts =
opts
|> Keyword.merge(
namespace: namespace,
name: name,
operation: :connect,
subresource: "log",
adapter: &Kubereq.Connect.run(&1)
)
|> Kubereq.Connect.args_to_opts()

Req.request(req, opts)
end

@doc ~S"""
Opens a websocket to the given Pod and executes a command on it.
> #### Info {: .tip}
>
> This function blocks the process. It should be used to execute commands
> which terminate eventually. To implement a shell with a long running
> connection, use `Kubereq.PodExec` with `tty: true` instead.
## Examples
{:ok, resp} =
Kubereq.exec(req, "defaault", "my-pod",
container: "main-container",
command: "/bin/sh",
command: "-c",
command: "echo foobar",
stdout: true,
stderr: true
)
Enum.each(resp.body, &IO.inspect/1)
# {:stdout, ""}
# {:stdout, "foobar\n"}
## Options
* `:container` (optional) - The container to connect to. Defaults to only
container if there is one container in the pod. Fails if not defined for
pods with multiple pods.
* `:command` - Command is the remote command to execute. Not executed within a shell.
* `:stdin` (optional) - Redirect the standard input stream of the pod for this call. Defaults to `true`.
* `:stdin` (optional) - Redirect the standard output stream of the pod for this call. Defaults to `true`.
* `:stderr` (optional) - Redirect the standard error stream of the pod for this call. Defaults to `true`.
* `:tty` (optional) - If `true` indicates that a tty will be allocated for the exec call. Defaults to `false`.
"""
@spec exec(
req :: Req.Request.t(),
namespace :: namespace(),
name :: String.t(),
opts :: Keyword.t() | nil
) ::
response()
def exec(req, namespace, name, opts \\ []) do
opts =
opts
|> Keyword.merge(
namespace: namespace,
name: name,
operation: :connect,
subresource: "exec",
adapter: &Kubereq.Connect.run(&1)
)
|> Kubereq.Connect.args_to_opts()

Req.request(req, opts)
end
end
2 changes: 1 addition & 1 deletion lib/kubereq/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Kubereq.Application do
@impl true
def start(_start_type, _start_args) do
children = [
{Registry, keys: :unique, name: Kubereq.Exec}
{Registry, keys: :unique, name: Kubereq.Auth.Exec}
]

opts = [strategy: :one_for_one, name: Kubereq.Supervisor]
Expand Down
2 changes: 1 addition & 1 deletion lib/kubereq/exec.ex → lib/kubereq/auth/exec.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Kubereq.Exec do
defmodule Kubereq.Auth.Exec do
@moduledoc false

alias Kubereq.Error.KubeconfError
Expand Down
Loading

0 comments on commit 8c562ea

Please sign in to comment.