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
3 changes: 3 additions & 0 deletions .changes/unreleased/Changed-20260628-133837.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Changed
body: Improve CLI help output.
time: 2026-06-28T13:38:37.410920814+01:00
3 changes: 3 additions & 0 deletions .changes/unreleased/Fixed-20260627-150100.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Fixed
body: 'The json output was not valid, items would have an extra }. '
time: 2026-06-27T15:01:00.473603684+01:00
44 changes: 13 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,19 @@ kubectlgetall -n <namespace>

There are some flags that can be passed.
```shell
kubectlgetall --help
-h, --help
Display this help and exit.

-n, --namespace <STR>
Namespace to get resources from.

-A, --all-namespaces
If present, list all objects across all namespaces. Specifing --namespace will be ignored.

-s, --sort
Prints the resources in order.

-e, --exclude <STR>...
Exclude crd types. Multiple can be excluded eg: "-e <CRD> -e <CRD>"

-o, --output <OUTPUT>
Changes the output format of the results.

-d, --database <PATH>
Path to the sqlite file to save the results. If the files does not exist it will be created.

-l, --label <STR>
Set the label that will be saved with entries when using the --database option.

--log-level <LEVEL>
Set the log level. All logs are saved to file. Possible values are (debug, info, warn, error). Defualt level is warn.

--version
Display verson, and exit.

USAGE:
kubectlgetall <COMMAND> [FLAGS]

COMMANDS:
get Get list of resource on cluster.
diff Show the resources Added, Updated and Removed
from a cluster by comparing two labels defined in the database.

GLOBAL FLAGS:
-h, --help Display this help and exit.
--version Display version, and exit.
--log-level <LEVEL> Set the log level. All logs are saved to file.
Possible values are (debug, info, warn, error). Default level is warn.
```

## Dev
Expand Down
8 changes: 5 additions & 3 deletions src/diff.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ const logging = @import("log.zig");
const db = @import("database.zig");
const table = @import("table.zig");
const types = @import("types.zig");
const help = @import("help.zig");

pub fn diffMain(io: std.Io, gpa: std.mem.Allocator, iter: *std.process.Args.Iterator) !void {

const params = comptime clap.parseParamsComptime(
\\-h, --help Display this help and exit.
\\-d, --database <PATH> Path to SQLite database to load data from.
Expand Down Expand Up @@ -37,8 +37,10 @@ pub fn diffMain(io: std.Io, gpa: std.mem.Allocator, iter: *std.process.Args.Iter
};
defer res.deinit();

if (res.args.help != 0)
return clap.helpToFile(io, .stdout(), clap.Help, &params, .{});
if (res.args.help != 0) {
try help.diff(io, .stdout());
std.process.exit(0);
}

if (res.args.@"log-level") |l| {
logging.setLogLevel(l);
Expand Down
28 changes: 16 additions & 12 deletions src/get.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ const logging = @import("log.zig");
const types = @import("types.zig");
const table = @import("table.zig");
const utils = @import("utils.zig");
const help = @import("help.zig");

pub fn getMain(io: std.Io, gpa: std.mem.Allocator, iter: *std.process.Args.Iterator) !void {

var stdout_buf: [4096]u8 = undefined;
var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buf);
const stdout = &stdout_writer.interface;
Expand Down Expand Up @@ -57,8 +57,10 @@ pub fn getMain(io: std.Io, gpa: std.mem.Allocator, iter: *std.process.Args.Itera
var label: []const u8 = &[_]u8{};
var exclude: ?[]const []const u8 = null;

if (res.args.help != 0)
return clap.helpToFile(io, .stdout(), clap.Help, &params, .{});
if (res.args.help != 0) {
try help.get(io, .stdout());
std.process.exit(0);
}

if (res.args.namespace) |n| {
namespace = n;
Expand Down Expand Up @@ -256,16 +258,18 @@ fn compareStrings(_: void, lhs: []const u8, rhs: []const u8) bool {
return std.mem.lessThan(u8, lhs, rhs);
}

fn getCrdList(io: std.Io, allocator: std.mem.Allocator) !std.ArrayList([]const u8) {
fn getCrdList(io: std.Io, gpa: std.mem.Allocator) !std.ArrayList([]const u8) {
const cmd = [_][]const u8{ "kubectl", "api-resources", "--verbs=list", "--namespaced", "-o", "name" };
const result = std.process.run(allocator, io, .{
const result = std.process.run(gpa, io, .{
.argv = &cmd,
}) catch |err| {
std.log.err("Failed to run kubectl: {}", .{err});
return err;
};
defer allocator.free(result.stdout);
defer allocator.free(result.stderr);
defer {
gpa.free(result.stdout);
gpa.free(result.stderr);
}

if (result.term.exited != 0) {
std.log.debug("stdout: {s}. stderr: {s}", .{ result.stdout, result.stderr });
Expand All @@ -274,17 +278,17 @@ fn getCrdList(io: std.Io, allocator: std.mem.Allocator) !std.ArrayList([]const u
var lines: std.ArrayList([]const u8) = .empty;
errdefer {
for (lines.items) |item| {
allocator.free(item);
gpa.free(item);
}
lines.deinit(allocator);
lines.deinit(gpa);
}

var iter = std.mem.splitScalar(u8, result.stdout, '\n');
while (iter.next()) |line| {
if (line.len > 0) {
const owned = try allocator.dupe(u8, line);
errdefer allocator.free(owned);
try lines.append(allocator, owned);
const owned = try gpa.dupe(u8, line);
errdefer gpa.free(owned);
try lines.append(gpa, owned);
}
}

Expand Down
94 changes: 94 additions & 0 deletions src/help.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! Provides help strings for the commands defined in the application.
//! The help documentation is manually formatted.

const std = @import("std");

const main_msg =
\\USAGE:
\\ kubectlgetall <COMMAND> [FLAGS]
\\
\\COMMANDS:
\\get Get list of resource on cluster.
\\diff Show the resources Added, Updated and Removed
\\ from a cluster by comparing two labels defined in the database.
\\
\\GLOBAL FLAGS:
\\-h, --help Display this help and exit.
\\--version Display version, and exit.
\\--log-level <LEVEL> Set the log level. All logs are saved to file.
\\ Possible values are (debug, info, warn, error). Default level is warn.
\\
;

const get_msg =
\\USAGE:
\\ kubectlgetall get [FLAGS]
\\
\\Get list of resource on a cluster.
\\
\\REQUIREMENTS:
\\kubectl must be installed and available on PATH.
\\An active connection to a Kubernetes cluster is required.
\\
\\FLAGS:
\\-h, --help Display this help and exit.
\\-n, --namespace <STR> Namespace to get resources from.
\\-A, --all-namespaces If present, list all objects across all namespaces.
\\ Specifying --namespace will be ignored.
\\-s, --sort Prints the resources in order.
\\-e, --exclude <STR>... Exclude crd types. Multiple can be excluded eg: "-e <CRD> -e <CRD>"
\\-o, --output <OUTPUT> Changes the output format of the results. [default: tty, tty|json|sqlite]
\\-d, --database <PATH> Path to the sqlite file to save the results.
\\ If the files does not exist it will be created.
\\-l, --label <STR> Set the label that will be saved with entries when using the --database option.
\\
;

const diff_msg =
\\USAGE:
\\ kubectlgetall diff <BASE> <HEAD> [FLAGS]
\\
\\Show the resources Added, Updated and Removed
\\from a cluster by comparing two labels defined in the database.
\\
\\REQUIREMENTS:
\\A SQLite database populated by the get command. See: kubectlgetall get --help
\\
\\ARGUMENTS:
\\BASE The older label. Serves as the baseline for comparison.
\\HEAD The newer label. Compared against BASE to identify what has changed.
\\
\\Items in HEAD but not BASE are reported as new.
\\Items in BASE but not HEAD are reported as removed.
\\Items in both but differing in value are reported as updated.
\\
\\FLAGS:
\\-h, --help Display this help and exit.
\\-d, --database <PATH> Path to SQLite database to load data from.
\\-e, --exclude <STR>... Exclude resource types. Multiple can be excluded eg: "-e <KIND> -e <KIND>"
\\-o, --output <OUTPUT> Changes the output format of the results. [default: tty, tty|json]
\\
;

fn print(
io: std.Io,
file: std.Io.File,
msg: []const u8,
) !void {
var buf: [1024]u8 = undefined;
var writer = file.writer(io, &buf);
try writer.interface.writeAll(msg);
return writer.interface.flush();
}

pub fn main(io: std.Io, file: std.Io.File) !void {
return print(io, file, main_msg);
}

pub fn get(io: std.Io, file: std.Io.File) !void {
return print(io, file, get_msg);
}

pub fn diff(io: std.Io, file: std.Io.File) !void {
return print(io, file, diff_msg);
}
16 changes: 12 additions & 4 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const arg = @import("args.zig");
const diff = @import("diff.zig");
const get = @import("get.zig");
const logging = @import("log.zig");
const help = @import("help.zig");

pub const std_options: std.Options = .{
.log_level = .debug,
Expand All @@ -32,11 +33,18 @@ pub fn main(init: std.process.Init) !void {
};
defer res.deinit();

if (res.args.help != 0)
return clap.helpToFile(init.io, .stdout(), clap.Help, &arg.main_params, .{});
if (res.args.help != 0) {
try help.main(init.io, .stdout());
std.process.exit(0);
}

if (res.args.version != 0) {
std.log.info("{s}, {s}", .{ build_options.name, build_options.version });
const version = try std.fmt.allocPrint(init.gpa, "{s}, {s}\n", .{ build_options.name, build_options.version });
defer init.gpa.free(version);
var buf: [1024]u8 = undefined;
var writer = std.Io.File.stdout().writer(init.io, &buf);
try writer.interface.writeAll(version);
try writer.interface.flush();
std.process.exit(0);
}

Expand All @@ -45,7 +53,7 @@ pub fn main(init: std.process.Init) !void {
}

const command = res.positionals[0] orelse {
try clap.helpToFile(init.io, .stdout(), clap.Help, &arg.main_params, .{});
try help.main(init.io, .stdout());
return error.MissingCommand;
};
switch (command) {
Expand Down
Loading
Loading