Skip to content

Commit

Permalink
UPSTREAM: 96120: kubelet: Expose a simple Get-WinEvent shim on the ku…
Browse files Browse the repository at this point in the history
…belet logs endpoint

Provide an administrator a streaming view of event logs on Windows
machines without them having to implement a client side reader.

The kubelet API for querying the Linux journal is re-used for invoking
the Get-WinEvent cmdlet in a PowerShell.
Parameters that have no functional equivalence in Get-WinEvent are
ignored when assembling the command.

Only available to cluster admins.

openshift-rebase(v1.24):source=6a457760045
  • Loading branch information
LorbusChris authored and soltysh committed Aug 22, 2022
1 parent 0a58df6 commit fa28c1e
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 39 deletions.
35 changes: 2 additions & 33 deletions pkg/kubelet/kubelet_server_journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,36 +112,6 @@ func newJournalArgsFromURL(query url.Values) (*journalArgs, error) {
}, nil
}

// Args returns the journalctl arguments for the given args.
func (a *journalArgs) Args() []string {
args := []string{
"--utc",
"--no-pager",
}
if len(a.Since) > 0 {
args = append(args, "--since="+a.Since)
}
if len(a.Until) > 0 {
args = append(args, "--until="+a.Until)
}
if a.Tail > 0 {
args = append(args, "--pager-end", fmt.Sprintf("--lines=%d", a.Tail))
}
if len(a.Format) > 0 {
args = append(args, "--output="+a.Format)
}
for _, unit := range a.Units {
if len(unit) > 0 {
args = append(args, "--unit="+unit)
}
}
if len(a.Pattern) > 0 {
args = append(args, "--grep="+a.Pattern)
args = append(args, fmt.Sprintf("--case-sensitive=%t", a.CaseSensitive))
}
return args
}

// Copy streams the contents of the journalctl command executed with the current
// args to the provided writer, timing out at a.Timeout. If an error occurs a line
// is written to the output.
Expand All @@ -166,9 +136,8 @@ func (a *journalArgs) copyForBoot(ctx context.Context, w io.Writer, previousBoot
return
}

args := a.Args()
args = append(args, "--boot", fmt.Sprintf("%d", previousBoot))
cmd := exec.Command("journalctl", args...)
cmdStr, args := getLoggingCmd(a, previousBoot)
cmd := exec.Command(cmdStr, args...)
cmd.Stdout = w
cmd.Stderr = w

Expand Down
40 changes: 40 additions & 0 deletions pkg/kubelet/kubelet_server_journal_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// +build linux

package kubelet

import (
"fmt"
)

// getLoggingCmd returns the journalctl cmd and arguments for the given journalArgs and boot
func getLoggingCmd(a *journalArgs, boot int) (string, []string) {
args := []string{
"--utc",
"--no-pager",
}
if len(a.Since) > 0 {
args = append(args, "--since="+a.Since)
}
if len(a.Until) > 0 {
args = append(args, "--until="+a.Until)
}
if a.Tail > 0 {
args = append(args, "--pager-end", fmt.Sprintf("--lines=%d", a.Tail))
}
if len(a.Format) > 0 {
args = append(args, "--output="+a.Format)
}
for _, unit := range a.Units {
if len(unit) > 0 {
args = append(args, "--unit="+unit)
}
}
if len(a.Pattern) > 0 {
args = append(args, "--grep="+a.Pattern)
args = append(args, fmt.Sprintf("--case-sensitive=%t", a.CaseSensitive))
}

args = append(args, "--boot", fmt.Sprintf("%d", boot))

return "journalctl", args
}
8 changes: 8 additions & 0 deletions pkg/kubelet/kubelet_server_journal_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// +build !linux,!windows

package kubelet

// getLoggingCmd on unsupported operating systems returns the echo command and a warning message (as strings)
func getLoggingCmd(a *journalArgs, boot int) (string, []string) {
return "echo", []string{"Operating System Not Supported"}
}
32 changes: 26 additions & 6 deletions pkg/kubelet/kubelet_server_journal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kubelet
import (
"net/url"
"reflect"
"runtime"
"strings"
"testing"

Expand All @@ -11,16 +12,35 @@ import (

func Test_journalArgs_Args(t *testing.T) {
tests := []struct {
name string
args journalArgs
want []string
name string
args journalArgs
wantLinux []string
wantWindows []string
wantOtherOS []string
}{
{args: journalArgs{}, want: []string{"--utc", "--no-pager"}},
{
args: journalArgs{},
wantLinux: []string{"--utc", "--no-pager", "--boot", "0"},
wantWindows: []string{"-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "Get-WinEvent -FilterHashtable @{LogName='Application'} | Sort-Object TimeCreated | Format-Table -AutoSize -Wrap"},
wantOtherOS: []string{"Operating System Not Supported"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.args.Args(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("journalArgs.Args() = %v, want %v", got, tt.want)
_, got := getLoggingCmd(&tt.args, 0)
switch os := runtime.GOOS; os {
case "linux":
if !reflect.DeepEqual(got, tt.wantLinux) {
t.Errorf("getLoggingCmd(journalArgs{}, 0) = %v, want %v", got, tt.wantLinux)
}
case "windows":
if !reflect.DeepEqual(got, tt.wantWindows) {
t.Errorf("getLoggingCmd(journalArgs{}, 0) = %v, want %v", got, tt.wantWindows)
}
default:
if !reflect.DeepEqual(got, tt.wantOtherOS) {
t.Errorf("getLoggingCmd(journalArgs{}, 0) = %v, want %v", got, tt.wantWindows)
}
}
})
}
Expand Down
53 changes: 53 additions & 0 deletions pkg/kubelet/kubelet_server_journal_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// +build windows

package kubelet

import (
"fmt"
"strings"
)

// getLoggingCmd returns the powershell cmd and arguments for the given journalArgs and boot
func getLoggingCmd(a *journalArgs, boot int) (string, []string) {
// The WinEvent log does not support querying by boot
// Set the cmd to return true on windows in case boot is not 0
if boot != 0 {
return "cd.", []string{}
}

args := []string{
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command",
}

psCmd := "Get-WinEvent -FilterHashtable @{LogName='Application'"
if len(a.Since) > 0 {
psCmd += fmt.Sprintf("; StartTime='%s'", a.Since)
}
if len(a.Until) > 0 {
psCmd += fmt.Sprintf("; EndTime='%s'", a.Until)
}
var units []string
for _, unit := range a.Units {
if len(unit) > 0 {
units = append(units, "'"+unit+"'")
}
}
if len(units) > 0 {
psCmd += fmt.Sprintf("; ProviderName=%s", strings.Join(units, ","))
}
psCmd += "}"
if a.Tail > 0 {
psCmd += fmt.Sprintf(" -MaxEvents %d", a.Tail)
}
psCmd += " | Sort-Object TimeCreated"
if len(a.Pattern) > 0 {
psCmd += fmt.Sprintf(" | Where-Object -Property Message -Match %s", a.Pattern)
}
psCmd += " | Format-Table -AutoSize -Wrap"

args = append(args, psCmd)

return "PowerShell.exe", args
}

0 comments on commit fa28c1e

Please sign in to comment.