Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ jobs:
include:
- goos: darwin
goarch: amd64
archive: kubectl-multi_darwin_amd64.tar.gz
archive: kubectl-multi-get_darwin_amd64.tar.gz
- goos: darwin
goarch: arm64
archive: kubectl-multi_darwin_arm64.tar.gz
archive: kubectl-multi-get_darwin_arm64.tar.gz
- goos: linux
goarch: amd64
archive: kubectl-multi_linux_amd64.tar.gz
archive: kubectl-multi-get_linux_amd64.tar.gz
- goos: linux
goarch: arm64
archive: kubectl-multi_linux_arm64.tar.gz
archive: kubectl-multi-get_linux_arm64.tar.gz
steps:
- uses: actions/checkout@v4

Expand All @@ -43,9 +43,9 @@ jobs:
CGO_ENABLED: '0'
run: |
mkdir -p dist
go build -trimpath -ldflags='-s -w' -o dist/kubectl-multi ./cmd/kubectl-multi
tar -czf "dist/${{ matrix.archive }}" -C dist kubectl-multi
rm dist/kubectl-multi
go build -trimpath -ldflags='-s -w' -o dist/kubectl-multi-get ./cmd/kubectl-multi-get
tar -czf "dist/${{ matrix.archive }}" -C dist kubectl-multi-get -C "$GITHUB_WORKSPACE" LICENSE
rm dist/kubectl-multi-get

- name: Upload release archive
uses: actions/upload-artifact@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ jobs:
check-latest: true
- run: go test ./...
- run: go vet ./...
- run: go build ./cmd/kubectl-multi
- run: go build ./cmd/kubectl-multi-get
57 changes: 57 additions & 0 deletions .krew.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
apiVersion: krew.googlecontainertools.github.com/v1alpha2
kind: Plugin
metadata:
name: multi-get
spec:
version: {{ .TagName }}
homepage: https://github.com/squatboy/multi-get
shortDescription: Query resources across namespaces
description: |
Query the same Kubernetes resource across multiple namespaces with one
kubectl command. Select namespaces explicitly, find them by name, or
query every namespace.
platforms:
- selector:
matchLabels:
os: darwin
arch: amd64
{{addURIAndSha "https://github.com/squatboy/multi-get/releases/download/{{ .TagName }}/kubectl-multi-get_darwin_amd64.tar.gz" .TagName }}
bin: kubectl-multi-get
files:
- from: kubectl-multi-get
to: .
- from: LICENSE
to: .
- selector:
matchLabels:
os: darwin
arch: arm64
{{addURIAndSha "https://github.com/squatboy/multi-get/releases/download/{{ .TagName }}/kubectl-multi-get_darwin_arm64.tar.gz" .TagName }}
bin: kubectl-multi-get
files:
- from: kubectl-multi-get
to: .
- from: LICENSE
to: .
- selector:
matchLabels:
os: linux
arch: amd64
{{addURIAndSha "https://github.com/squatboy/multi-get/releases/download/{{ .TagName }}/kubectl-multi-get_linux_amd64.tar.gz" .TagName }}
bin: kubectl-multi-get
files:
- from: kubectl-multi-get
to: .
- from: LICENSE
to: .
- selector:
matchLabels:
os: linux
arch: arm64
{{addURIAndSha "https://github.com/squatboy/multi-get/releases/download/{{ .TagName }}/kubectl-multi-get_linux_arm64.tar.gz" .TagName }}
bin: kubectl-multi-get
files:
- from: kubectl-multi-get
to: .
- from: LICENSE
to: .
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
GO ?= go
BINARY ?= bin/kubectl-multi
BINARY ?= bin/kubectl-multi-get

.PHONY: build test vet e2e-local

build:
mkdir -p bin
$(GO) build -o $(BINARY) ./cmd/kubectl-multi
$(GO) build -o $(BINARY) ./cmd/kubectl-multi-get

test:
$(GO) test ./...
Expand Down
60 changes: 39 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ kubectl [plugin](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins
Choose the namespaces explicitly, find them by name, or query every namespace:

```bash
kubectl multi get pods -n dev,stage,prod
kubectl multi get deploy --find-ns dev
kubectl multi get pods -A
kubectl multi-get get pods -n dev,stage,prod
kubectl multi-get get deploy --find-ns dev
kubectl multi-get get pods -A
```

This multi-namespace lookup is the core feature. The plugin sends the resource query to each selected Namespace and combines the results with the Namespace shown in human-readable output.
Expand All @@ -21,10 +21,10 @@ Regular kubectl already supports multiple resource kinds in one namespace:
kubectl get pod,svc,pvc -n dev
```

It also supports all namespaces for many normal list commands. `kubectl-multi` keeps those familiar resource expressions as a secondary composition feature, so the following applies each resource query to both namespaces and prints separate blocks:
It also supports all namespaces for many normal list commands. `kubectl-multi-get` keeps those familiar resource expressions as a secondary composition feature, so the following applies each resource query to both namespaces and prints separate blocks:

```bash
kubectl multi get pods,services -n dev,stage
kubectl multi-get get pods,services -n dev,stage
```

## Install
Expand All @@ -38,7 +38,7 @@ curl -fsSL https://github.com/squatboy/multi-get/releases/latest/download/instal
```

The installer detects the operating system and CPU architecture, verifies the
downloaded archive, and installs `kubectl-multi` into `$HOME/.local/bin`.
downloaded archive, and installs `kubectl-multi-get` into `$HOME/.local/bin`.

The installer adds `$HOME/.local/bin` to the detected shell configuration.
Open a new terminal, or reload the shell configuration using the command shown
Expand All @@ -47,7 +47,23 @@ by the installer.
Verify the installation:

```bash
kubectl multi --help
kubectl multi-get --help
```

### Krew

Install from the official Krew index:

```bash
kubectl krew update
kubectl krew install multi-get
```

Verify the installation:

```bash
kubectl multi-get --help
kubectl multi-get get pods -n dev,stage
```

### Local Build
Expand All @@ -57,7 +73,7 @@ Build the plugin locally:
```bash
make build
export PATH="$PWD/bin:$PATH"
kubectl multi get pods -n dev,stage
kubectl multi-get get pods -n dev,stage
```

This repository targets Go 1.25.x and Kubernetes client modules v0.35.x.
Expand All @@ -70,10 +86,10 @@ Exactly one namespace mode is used:

| Command | Behavior |
| --- | --- |
| `kubectl multi get pods` | Current context namespace, or `default` |
| `kubectl multi get pods -n dev,stage` | Explicit namespaces, in input order |
| `kubectl multi get pods --find-ns dev` | Namespace names containing `dev`, sorted by name |
| `kubectl multi get pods -A` | All namespaces, sorted by name |
| `kubectl multi-get get pods` | Current context namespace, or `default` |
| `kubectl multi-get get pods -n dev,stage` | Explicit namespaces, in input order |
| `kubectl multi-get get pods --find-ns dev` | Namespace names containing `dev`, sorted by name |
| `kubectl multi-get get pods -A` | All namespaces, sorted by name |

Explicit lists trim whitespace and remove duplicate names while preserving the first occurrence. Empty entries and mixed namespace modes are errors. `--find-ns` trims the search text and matches it literally anywhere in each Namespace name. Empty search text and a search with no matches are errors and do not start resource requests.

Expand All @@ -82,24 +98,24 @@ Explicit lists trim whitespace and remove duplicate names while preserving the f
Selectors are sent to every namespaced List request:

```bash
kubectl multi get pods -n dev,stage -l app=backend -o wide
kubectl multi get pods -n dev,stage --field-selector status.phase=Running
kubectl multi get pods -n dev,stage --sort-by=.metadata.creationTimestamp
kubectl multi-get get pods -n dev,stage -l app=backend -o wide
kubectl multi-get get pods -n dev,stage --field-selector status.phase=Running
kubectl multi-get get pods -n dev,stage --sort-by=.metadata.creationTimestamp
```

Supported output formats are `wide`, `json`, `yaml`, `name`, `custom-columns`, `jsonpath`, and `go-template`. Human tables always put `NAMESPACE` first. JSON and YAML for one resource are emitted as one Kubernetes `List` object.

```bash
kubectl multi get pods -n dev,stage -o json
kubectl multi get pods -n dev,stage -o name
kubectl multi get pods -n dev,stage -o 'custom-columns=NAME:.metadata.name,PHASE:.status.phase'
kubectl multi-get get pods -n dev,stage -o json
kubectl multi-get get pods -n dev,stage -o name
kubectl multi-get get pods -n dev,stage -o 'custom-columns=NAME:.metadata.name,PHASE:.status.phase'
```

Named lookup supports multiple object names for one resource type:

```bash
kubectl multi get pod api web -n dev,stage
kubectl multi get pod missing -n dev,stage --ignore-not-found
kubectl multi-get get pod api web -n dev,stage
kubectl multi-get get pod missing -n dev,stage --ignore-not-found
```

Namespace requests and resource requests are sequential. Successful results are printed even when another namespace or resource request fails; any real API error still gives exit code 1.
Expand All @@ -116,7 +132,9 @@ Namespace requests and resource requests are sequential. Successful results are
| Table, wide, JSON, YAML, name, and template output | Multi-resource with object names |
| Namespaced built-in resources and namespaced CRDs | Namespace exclude and parallel queries |

The plugin does not register with Krew or provide shell-completion.
The repository includes the Krew manifest template used for the
[official index](https://github.com/kubernetes-sigs/krew-index/blob/master/plugins/multi-get.yaml).
The plugin does not provide shell-completion.

## Verification

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"

"github.com/squatboy/multi-get/internal/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)

func main() {
Expand Down
34 changes: 17 additions & 17 deletions hack/e2e-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
kubectl_bin="$(command -v kubectl)"
go_bin="${GO:-go}"

"$go_bin" build -o "$repo_root/bin/kubectl-multi" ./cmd/kubectl-multi
"$go_bin" build -o "$repo_root/bin/kubectl-multi-get" ./cmd/kubectl-multi-get
export PATH="$repo_root/bin:$PATH"

context_name="$(kubectl config current-context)"
Expand Down Expand Up @@ -171,35 +171,35 @@ spec: {}
EOF
done

kubectl multi get pods -n "${namespace_a},${namespace_b}" >/dev/null
kubectl multi get pods -n "${namespace_b},${namespace_a}" >/dev/null
kubectl multi get pods --find-ns "${prefix}-dev" >/dev/null
kubectl multi get pods -A >/dev/null
kubectl multi get pods,services -n "${namespace_a},${namespace_b}" >/dev/null
kubectl multi get pods -n "${namespace_a},${namespace_b}" -l app=multi-get-e2e >/dev/null
kubectl multi get pods -n "${namespace_a},${namespace_b}" --field-selector "metadata.namespace=${namespace_a}" >/dev/null
kubectl multi get pods -n "${namespace_a},${namespace_b}" -o wide >/dev/null
kubectl multi-get get pods -n "${namespace_a},${namespace_b}" >/dev/null
kubectl multi-get get pods -n "${namespace_b},${namespace_a}" >/dev/null
kubectl multi-get get pods --find-ns "${prefix}-dev" >/dev/null
kubectl multi-get get pods -A >/dev/null
kubectl multi-get get pods,services -n "${namespace_a},${namespace_b}" >/dev/null
kubectl multi-get get pods -n "${namespace_a},${namespace_b}" -l app=multi-get-e2e >/dev/null
kubectl multi-get get pods -n "${namespace_a},${namespace_b}" --field-selector "metadata.namespace=${namespace_a}" >/dev/null
kubectl multi-get get pods -n "${namespace_a},${namespace_b}" -o wide >/dev/null

json_output="$(kubectl multi get pods -n "${namespace_a},${namespace_b}" -o json)"
json_output="$(kubectl multi-get get pods -n "${namespace_a},${namespace_b}" -o json)"
grep -q '"kind": "List"' <<<"$json_output"
grep -q "${namespace_a}" <<<"$json_output"
kubectl multi get pods -n "${namespace_a},${namespace_b}" -o yaml >/dev/null
kubectl multi get pods -n "${namespace_a},${namespace_b}" -o name >/dev/null
kubectl multi get widgets -n "${namespace_a},${namespace_b}" >/dev/null
kubectl multi-get get pods -n "${namespace_a},${namespace_b}" -o yaml >/dev/null
kubectl multi-get get pods -n "${namespace_a},${namespace_b}" -o name >/dev/null
kubectl multi-get get widgets -n "${namespace_a},${namespace_b}" >/dev/null

if kubectl multi get pods --find-ns "${prefix}-missing" >/dev/null 2>&1; then
if kubectl multi-get get pods --find-ns "${prefix}-missing" >/dev/null 2>&1; then
echo "expected no-match to fail" >&2
exit 1
fi
if kubectl multi get pods,services "${prefix}-pod" -n "$namespace_a" >/dev/null 2>&1; then
if kubectl multi-get get pods,services "${prefix}-pod" -n "$namespace_a" >/dev/null 2>&1; then
echo "expected multi-resource named lookup to fail" >&2
exit 1
fi
if kubectl multi get pods,services -o json -n "$namespace_a" >/dev/null 2>&1; then
if kubectl multi-get get pods,services -o json -n "$namespace_a" >/dev/null 2>&1; then
echo "expected multi-resource structured output to fail" >&2
exit 1
fi
if kubectl multi get nodes -A >/dev/null 2>&1; then
if kubectl multi-get get nodes -A >/dev/null 2>&1; then
echo "expected cluster-scoped lookup to fail" >&2
exit 1
fi
Expand Down
28 changes: 14 additions & 14 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
set -eu

die() {
echo "kubectl-multi installer: $*" >&2
echo "kubectl-multi-get installer: $*" >&2
exit 1
}

Expand Down Expand Up @@ -51,11 +51,11 @@ esac

base_url=${MULTI_GET_RELEASE_BASE_URL:-https://github.com/squatboy/multi-get/releases/latest/download}
base_url=${base_url%/}
archive_name="kubectl-multi_${release_os}_${release_arch}.tar.gz"
archive_name="kubectl-multi-get_${release_os}_${release_arch}.tar.gz"
tmp_dir=
staged_target=

if ! tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/kubectl-multi-install.XXXXXX"); then
if ! tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/kubectl-multi-get-install.XXXXXX"); then
die "could not create a temporary directory"
fi

Expand All @@ -79,7 +79,7 @@ download() {
archive_path="$tmp_dir/$archive_name"
checksums_path="$tmp_dir/checksums.txt"

echo "Downloading kubectl-multi for $release_os/$release_arch..."
echo "Downloading kubectl-multi-get for $release_os/$release_arch..."
download "$base_url/$archive_name" "$archive_path"
download "$base_url/checksums.txt" "$checksums_path"

Expand Down Expand Up @@ -115,24 +115,24 @@ if ! tar -xzf "$archive_path" -C "$extract_dir"; then
die "could not extract $archive_name"
fi

binary_path="$extract_dir/kubectl-multi"
[ -f "$binary_path" ] || die "archive does not contain kubectl-multi"
binary_path="$extract_dir/kubectl-multi-get"
[ -f "$binary_path" ] || die "archive does not contain kubectl-multi-get"

install_dir="$home/.local/bin"
target_path="$install_dir/kubectl-multi"
target_path="$install_dir/kubectl-multi-get"
if ! mkdir -p "$install_dir"; then
die "could not create $install_dir"
fi

staged_target="$install_dir/.kubectl-multi.$$"
staged_target="$install_dir/.kubectl-multi-get.$$"
if ! cp "$binary_path" "$staged_target"; then
die "could not stage kubectl-multi"
die "could not stage kubectl-multi-get"
fi
if ! chmod 0755 "$staged_target"; then
die "could not make kubectl-multi executable"
die "could not make kubectl-multi-get executable"
fi
if ! mv -f "$staged_target" "$target_path"; then
die "could not install kubectl-multi into $install_dir"
die "could not install kubectl-multi-get into $install_dir"
fi
staged_target=

Expand All @@ -157,17 +157,17 @@ path_status=
if [ -n "$shell_config" ]; then
if [ -f "$shell_config" ] && grep -F '$HOME/.local/bin' "$shell_config" >/dev/null 2>&1; then
path_status="already present"
elif printf '\n# kubectl-multi\n%s\n' "$path_line" >> "$shell_config"; then
elif printf '\n# kubectl-multi-get\n%s\n' "$path_line" >> "$shell_config"; then
path_status="added to $shell_config"
else
path_status="could not update $shell_config"
fi
fi

echo "Installed kubectl-multi to $target_path."
echo "Installed kubectl-multi-get to $target_path."
if [ -n "$shell_config" ]; then
echo "PATH entry: $path_status."
echo "Reload your shell with: . \"$shell_config\""
else
echo "Add $install_dir to PATH before running kubectl multi."
echo "Add $install_dir to PATH before running kubectl multi-get."
fi
Loading
Loading