Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions pkg/utils/kubeutils/kubectl/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,10 @@ func (c *Cli) Describe(ctx context.Context, namespace string, name string) (stri
}

// GetContainerLogs retrieves the logs for the specified container
func (c *Cli) GetContainerLogs(ctx context.Context, namespace string, name string) (string, error) {
stdout, stderr, err := c.Execute(ctx, "-n", namespace, "logs", name)
func (c *Cli) GetContainerLogs(ctx context.Context, namespace string, name string, options ...LogOption) (string, error) {
args := []string{"-n", namespace, "logs", name}
args = append(args, BuildLogArgs(options...)...)
stdout, stderr, err := c.Execute(ctx, args...)
return stdout + stderr, err
}

Expand Down
89 changes: 89 additions & 0 deletions pkg/utils/kubeutils/kubectl/logs_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package kubectl

import (
"strconv"
"time"
)

// LogOption represents an option for a kubectl logs request.
type LogOption func(config *logConfig)

type logConfig struct {
container string
since time.Duration
sinceTime string
tail int
}

// WithContainer sets the container name to get logs from (-c, --container)
func WithContainer(container string) LogOption {
return func(config *logConfig) {
config.container = container
}
}

// WithSince sets the relative time to return logs from (--since)
// Example: 5m, 1h, 2h30m
func WithSince(since time.Duration) LogOption {
return func(config *logConfig) {
config.since = since
}
}

// WithSinceTime sets the absolute time to return logs from (--since-time)
// Should be in RFC3339 format, e.g., "2024-01-01T00:00:00Z"
func WithSinceTime(sinceTime string) LogOption {
return func(config *logConfig) {
config.sinceTime = sinceTime
}
}

// WithTail sets the number of lines from the end of the logs to show (--tail)
// Use -1 to show all lines
func WithTail(lines int) LogOption {
return func(config *logConfig) {
config.tail = lines
}
}

// BuildLogArgs constructs the kubectl logs arguments from the provided options
func BuildLogArgs(options ...LogOption) []string {
// Default config
cfg := &logConfig{
container: "",
since: 0,
sinceTime: "",
tail: -1,
}

// Apply options
for _, opt := range options {
opt(cfg)
}

var args []string

if cfg.container != "" {
args = append(args, "-c", cfg.container)
}

if cfg.since > 0 {
args = append(args, "--since", cfg.since.String())
}

if cfg.sinceTime != "" {
args = append(args, "--since-time", cfg.sinceTime)
}

if cfg.tail >= 0 {
args = append(args, "--tail", strconv.Itoa(cfg.tail))
}

return args
}

// FormatSinceTime is a helper function to format a time.Time into RFC3339 format
// suitable for use with WithSinceTime
func FormatSinceTime(t time.Time) string {
return t.Format(time.RFC3339)
}
107 changes: 107 additions & 0 deletions pkg/utils/kubeutils/kubectl/logs_options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package kubectl

import (
"slices"
"testing"
"time"
)

func TestBuildLogArgs_NoOptions(t *testing.T) {
args := BuildLogArgs()
if len(args) != 0 {
t.Errorf("expected empty args, got %v", args)
}
}

func TestBuildLogArgs_IndividualOptions(t *testing.T) {
tests := []struct {
name string
option LogOption
expectedArgs []string
}{
{
name: "WithContainer",
option: WithContainer("my-container"),
expectedArgs: []string{"-c", "my-container"},
},
{
name: "WithSince",
option: WithSince(5 * time.Minute),
expectedArgs: []string{"--since", "5m0s"},
},
{
name: "WithSinceTime",
option: WithSinceTime("2024-01-01T00:00:00Z"),
expectedArgs: []string{"--since-time", "2024-01-01T00:00:00Z"},
},
{
name: "WithTail",
option: WithTail(100),
expectedArgs: []string{"--tail", "100"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
args := BuildLogArgs(tt.option)
for _, expected := range tt.expectedArgs {
if !slices.Contains(args, expected) {
t.Errorf("expected args to contain %q, got %v", expected, args)
}
}
})
}
}

func TestBuildLogArgs_MultipleOptions(t *testing.T) {
args := BuildLogArgs(
WithContainer("app"),
WithTail(50),
WithSince(10*time.Minute),
)

expectedPairs := [][]string{
{"-c", "app"},
{"--tail", "50"},
{"--since", "10m0s"},
}

for _, pair := range expectedPairs {
for _, expected := range pair {
if !slices.Contains(args, expected) {
t.Errorf("expected args to contain %q, got %v", expected, args)
}
}
}
}

func TestBuildLogArgs_DoesNotIncludeTailWhenNegative(t *testing.T) {
args := BuildLogArgs(WithContainer("app"))
if slices.Contains(args, "--tail") {
t.Errorf("expected args not to contain --tail, got %v", args)
}
}

func TestBuildLogArgs_DoesNotIncludeSinceWhenZero(t *testing.T) {
args := BuildLogArgs(WithContainer("app"))
if slices.Contains(args, "--since") {
t.Errorf("expected args not to contain --since, got %v", args)
}
}

func TestBuildLogArgs_DoesNotIncludeSinceTimeWhenEmpty(t *testing.T) {
args := BuildLogArgs(WithContainer("app"))
if slices.Contains(args, "--since-time") {
t.Errorf("expected args not to contain --since-time, got %v", args)
}
}

func TestFormatSinceTime(t *testing.T) {
testTime := time.Date(2024, 1, 1, 12, 30, 45, 0, time.UTC)
formatted := FormatSinceTime(testTime)
expected := "2024-01-01T12:30:45Z"

if formatted != expected {
t.Errorf("expected %q, got %q", expected, formatted)
}
}