Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementation of kube_capture starlark function
This patch implements the Go code for starlark builtin function kube_capture(). The function allows Crashd script to capture and write information about various kubernetes objects and logs
- Loading branch information
Showing
18 changed files
with
912 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package k8s | ||
|
||
import ( | ||
"fmt" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
) | ||
|
||
func GetContainers(podItem unstructured.Unstructured) ([]Container, error) { | ||
var containers []Container | ||
coreContainers, err := _getPodContainers(podItem) | ||
if err != nil { | ||
return containers, err | ||
} | ||
|
||
for _, c := range coreContainers { | ||
containers = append(containers, NewContainerLogger(podItem.GetNamespace(), podItem.GetName(), c)) | ||
} | ||
return containers, nil | ||
} | ||
|
||
func _getPodContainers(podItem unstructured.Unstructured) ([]corev1.Container, error) { | ||
var containers []corev1.Container | ||
|
||
pod := new(corev1.Pod) | ||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(podItem.Object, &pod); err != nil { | ||
return nil, fmt.Errorf("error converting container objects: %s", err) | ||
} | ||
|
||
for _, c := range pod.Spec.InitContainers { | ||
containers = append(containers, c) | ||
} | ||
|
||
for _, c := range pod.Spec.Containers { | ||
containers = append(containers, c) | ||
} | ||
containers = append(containers, _getPodEphemeralContainers(pod)...) | ||
return containers, nil | ||
} | ||
|
||
func _getPodEphemeralContainers(pod *corev1.Pod) []corev1.Container { | ||
var containers []corev1.Container | ||
for _, ec := range pod.Spec.EphemeralContainers { | ||
containers = append(containers, corev1.Container(ec.EphemeralContainerCommon)) | ||
} | ||
return containers | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package k8s | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/client-go/kubernetes/scheme" | ||
"k8s.io/client-go/rest" | ||
) | ||
|
||
type ContainerLogsImpl struct { | ||
namespace string | ||
podName string | ||
container corev1.Container | ||
} | ||
|
||
func NewContainerLogger(namespace, podName string, container corev1.Container) ContainerLogsImpl { | ||
return ContainerLogsImpl{ | ||
namespace: namespace, | ||
podName: podName, | ||
container: container, | ||
} | ||
} | ||
|
||
func (c ContainerLogsImpl) Fetch(restApi rest.Interface) (io.ReadCloser, error) { | ||
opts := &corev1.PodLogOptions{Container: c.container.Name} | ||
req := restApi.Get().Namespace(c.namespace).Name(c.podName).Resource("pods").SubResource("log").VersionedParams(opts, scheme.ParameterCodec) | ||
stream, err := req.Stream() | ||
if err != nil { | ||
err = errors.Wrap(err, "failed to create container log stream") | ||
} | ||
return stream, err | ||
} | ||
|
||
func (c ContainerLogsImpl) Write(reader io.ReadCloser, rootDir string) error { | ||
containerLogDir := filepath.Join(rootDir, c.container.Name) | ||
if err := os.MkdirAll(containerLogDir, 0744); err != nil && !os.IsExist(err) { | ||
return fmt.Errorf("error creating container log dir: %s", err) | ||
} | ||
|
||
path := filepath.Join(containerLogDir, fmt.Sprintf("%s.log", c.container.Name)) | ||
logrus.Debugf("Writing pod container log %s", path) | ||
|
||
file, err := os.Create(path) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
defer reader.Close() | ||
if _, err := io.Copy(file, reader); err != nil { | ||
cpErr := fmt.Errorf("failed to copy container log:\n%s", err) | ||
if wErr := writeError(cpErr, file); wErr != nil { | ||
return fmt.Errorf("failed to write previous err [%s] to file: %s", err, wErr) | ||
} | ||
return err | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package k8s | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
|
||
"k8s.io/client-go/rest" | ||
) | ||
|
||
const BaseDirname = "kubecapture" | ||
|
||
type Container interface { | ||
Fetch(rest.Interface) (io.ReadCloser, error) | ||
Write(io.ReadCloser, string) error | ||
} | ||
|
||
func writeError(errStr error, w io.Writer) error { | ||
_, err := fmt.Fprintln(w, errStr.Error()) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package k8s | ||
|
||
import ( | ||
"testing" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
func TestK8s(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "K8s Suite") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package k8s | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/sirupsen/logrus" | ||
"k8s.io/cli-runtime/pkg/printers" | ||
) | ||
|
||
type ObjectWriter struct { | ||
writeDir string | ||
} | ||
|
||
func (w ObjectWriter) Write(result SearchResult) (string, error) { | ||
resultDir := w.writeDir | ||
if result.Namespaced { | ||
resultDir = filepath.Join(w.writeDir, result.Namespace) | ||
} | ||
if err := os.MkdirAll(resultDir, 0744); err != nil && !os.IsExist(err) { | ||
return "", fmt.Errorf("failed to create search result dir: %s", err) | ||
} | ||
|
||
path := filepath.Join(resultDir, fmt.Sprintf("%s.json", result.ResourceName)) | ||
file, err := os.Create(path) | ||
if err != nil { | ||
return "", err | ||
} | ||
defer file.Close() | ||
|
||
logrus.Debugf("kube_capture(): saving %s search results to: %s", result.ResourceName, path) | ||
|
||
printer := new(printers.JSONPrinter) | ||
if err := printer.PrintObj(result.List, file); err != nil { | ||
if wErr := writeError(err, file); wErr != nil { | ||
return "", fmt.Errorf("failed to write previous err [%s] to file: %s", err, wErr) | ||
} | ||
return "", err | ||
} | ||
return resultDir, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package k8s | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"k8s.io/client-go/rest" | ||
) | ||
|
||
type ResultWriter struct { | ||
workdir string | ||
writeLogs bool | ||
restApi rest.Interface | ||
} | ||
|
||
func NewResultWriter(workdir, what string, restApi rest.Interface) (*ResultWriter, error) { | ||
var err error | ||
workdir = filepath.Join(workdir, BaseDirname) | ||
if err := os.MkdirAll(workdir, 0744); err != nil && !os.IsExist(err) { | ||
return nil, err | ||
} | ||
|
||
writeLogs := what == "logs" || what == "all" | ||
return &ResultWriter{ | ||
workdir: workdir, | ||
writeLogs: writeLogs, | ||
restApi: restApi, | ||
}, err | ||
} | ||
|
||
func (w *ResultWriter) GetResultDir() string { | ||
return w.workdir | ||
} | ||
|
||
func (w *ResultWriter) Write(searchResults []SearchResult) error { | ||
if searchResults == nil || len(searchResults) == 0 { | ||
return fmt.Errorf("cannot write empty (or nil) search result") | ||
} | ||
|
||
// each result represents a list of searched item | ||
// write each list in a namespaced location in working dir | ||
for _, result := range searchResults { | ||
objWriter := ObjectWriter{ | ||
writeDir: w.workdir, | ||
} | ||
writeDir, err := objWriter.Write(result) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if w.writeLogs && result.ListKind == "PodList" { | ||
if len(result.List.Items) == 0 { | ||
continue | ||
} | ||
for _, podItem := range result.List.Items { | ||
logDir := filepath.Join(writeDir, podItem.GetName()) | ||
if err := os.MkdirAll(logDir, 0744); err != nil && !os.IsExist(err) { | ||
return fmt.Errorf("failed to create pod log dir: %s", err) | ||
} | ||
|
||
containers, err := GetContainers(podItem) | ||
if err != nil { | ||
return err | ||
} | ||
for _, containerLogger := range containers { | ||
reader, err := containerLogger.Fetch(w.restApi) | ||
if err != nil { | ||
return err | ||
} | ||
err = containerLogger.Write(reader, logDir) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.