Skip to content

Commit

Permalink
Adds kube_get starlark built-in
Browse files Browse the repository at this point in the history
This patch includes the kube_get() built-in which can query and
present the k8s objects as starlark values which can be usd to use in
the starlark based crashd configuration file
This also includes:
- Moving test code from a test file to the test suite file
- Convert the ListItems from SearchResult to Starlark values
- Fixes kube_get & kube_capture built-ins for kube_cfg option
  • Loading branch information
srm09 committed Jun 30, 2020
1 parent 6a65e6d commit 0f489be
Show file tree
Hide file tree
Showing 13 changed files with 1,092 additions and 138 deletions.
9 changes: 9 additions & 0 deletions k8s/k8s_suite_test.go
@@ -1,3 +1,6 @@
// Copyright (c) 2020 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package k8s

import (
Expand All @@ -11,3 +14,9 @@ func TestK8s(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "K8s Suite")
}

var searchResults []SearchResult

var _ = BeforeSuite(func() {
searchResults = populateSearchResults()
})
75 changes: 72 additions & 3 deletions k8s/search_result.go
@@ -1,11 +1,17 @@
// Copyright (c) 2020 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package k8s

import (
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// SearchResult is the object representation of the kubernetes objects
// returned by querying the API server
type SearchResult struct {
ListKind string
ResourceName string
Expand All @@ -16,7 +22,70 @@ type SearchResult struct {
Namespace string
}

func (sr SearchResult) ToStarlarkValue() starlark.Value {
var val starlark.Value
return val
// ToStarlarkValue converts the SearchResult object to a starlark dictionary
func (sr SearchResult) ToStarlarkValue() *starlarkstruct.Struct {
var values []starlark.Value
listDict := starlark.StringDict{}

if sr.List != nil {
for _, item := range sr.List.Items {
values = append(values, convertToStruct(item))
}
listDict = starlark.StringDict{
"Object": convertToStarlarkPrimitive(sr.List.Object),
"Items": starlark.NewList(values),
}
}
listStruct := starlarkstruct.FromStringDict(starlarkstruct.Default, listDict)

grValDict := starlark.StringDict{
"Group": starlark.String(sr.GroupVersionResource.Group),
"Version": starlark.String(sr.GroupVersionResource.Version),
"Resource": starlark.String(sr.GroupVersionResource.Resource),
}

dict := starlark.StringDict{
"ListKind": starlark.String(sr.ListKind),
"ResourceName": starlark.String(sr.ResourceName),
"ResourceKind": starlark.String(sr.ResourceKind),
"Namespaced": starlark.Bool(sr.Namespaced),
"Namespace": starlark.String(sr.Namespace),
"GroupVersionResource": starlarkstruct.FromStringDict(starlarkstruct.Default, grValDict),
"List": listStruct,
}

return starlarkstruct.FromStringDict(starlarkstruct.Default, dict)
}

// convertToStruct returns a starlark struct constructed from the contents of the input.
func convertToStruct(obj unstructured.Unstructured) starlark.Value {
return convertToStarlarkPrimitive(obj.Object)
}

func convertToStarlarkPrimitive(input interface{}) starlark.Value {
var value starlark.Value
switch input.(type) {
case string:
value = starlark.String(input.(string))
case int, int32, int64:
value = starlark.MakeInt64(input.(int64))
case bool:
value = starlark.Bool(input.(bool))
case []interface{}:
interfaceArr, _ := input.([]interface{})
var structs []starlark.Value
for _, i := range interfaceArr {
structs = append(structs, convertToStarlarkPrimitive(i))
}
value = starlark.NewList(structs)
case map[string]interface{}:
dict := starlark.StringDict{}
for k, v := range input.(map[string]interface{}) {
dict[k] = convertToStarlarkPrimitive(v)
}
value = starlarkstruct.FromStringDict(starlarkstruct.Default, dict)
default:
value = starlark.None
}
return value
}
171 changes: 161 additions & 10 deletions k8s/search_result_test.go
@@ -1,35 +1,186 @@
// Copyright (c) 2020 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package k8s

import (
"encoding/json"
"fmt"
"io/ioutil"

"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)

var populateSearchResults = func() []SearchResult {
content, err := ioutil.ReadFile("../testing/search_results.json")
Expect(err).NotTo(HaveOccurred())
Expect(len(content)).NotTo(Equal(0))

var lists []unstructured.UnstructuredList
err = json.Unmarshal(content, &lists)
Expect(err).NotTo(HaveOccurred())

var results []SearchResult
for index, list := range lists {
Expect(list.Items).To(HaveLen(index + 1))
results = append(results, SearchResult{
List: list.DeepCopy(),
})
}
return results
}

var _ = Describe("SearchResult", func() {

Context("ToStarlarkValue", func() {

Context("ListKind", func() {
It("returns a dictionary of size equal to number of struct elements", func() {
sr := SearchResult{ListKind: "PodList"}
val := sr.ToStarlarkValue()
Expect(val).To(BeAssignableToTypeOf(&starlarkstruct.Struct{}))
Expect(val.AttrNames()).To(HaveLen(7))
})

It("creates value object with ListKind value", func() {
_ = sr.ToStarlarkValue()
sr := SearchResult{
ListKind: "NodeList",
ResourceName: "nodes",
ResourceKind: "Node",
Namespace: "",
Namespaced: false,
}

DescribeTable("String types", func(typeDescription, stringVal string) {
structVal := sr.ToStarlarkValue()
val, err := structVal.Attr(typeDescription)
Expect(err).NotTo(HaveOccurred())

strVal, _ := val.(starlark.String)
Expect(strVal.GoString()).To(Equal(stringVal))
},
Entry("", "ListKind", "NodeList"),
Entry("", "ResourceName", "nodes"),
Entry("", "ResourceKind", "Node"),
Entry("", "Namespace", ""),
)

Context("For Namespaced", func() {
It(fmt.Sprintf("creates a dictionary with Namespaced value"), func() {
dict := sr.ToStarlarkValue()
val, err := dict.Attr("Namespaced")
Expect(err).NotTo(HaveOccurred())

boolVal, _ := val.(starlark.Bool)
Expect(boolVal.Truth()).To(Equal(starlark.False))
})
})

Context("For ResourceName", func() {
Context("For List", func() {

})
It("returns a starlark struct", func() {
sr = searchResults[0]
structVal := sr.ToStarlarkValue()
Expect(structVal).To(BeAssignableToTypeOf(&starlarkstruct.Struct{}))

Context("For ResourceKind", func() {
val, err := structVal.Attr("List")
Expect(err).NotTo(HaveOccurred())

})
_, ok := val.(*starlarkstruct.Struct)
Expect(ok).To(BeTrue())
})

Context("For Namespaced", func() {
It("contains a starlark struct with the Object key", func() {
sr = searchResults[0]
structVal := sr.ToStarlarkValue()
val, _ := structVal.Attr("List")
listVal, _ := val.(*starlarkstruct.Struct)

})
objVal, err := listVal.Attr("Object")
Expect(err).NotTo(HaveOccurred())
objStructVal, ok := objVal.(*starlarkstruct.Struct)
Expect(ok).To(BeTrue())
Expect(objStructVal).To(BeAssignableToTypeOf(&starlarkstruct.Struct{}))
})

It("contains a starlark list with the Items key", func() {
sr = searchResults[0]
structVal := sr.ToStarlarkValue()
val, _ := structVal.Attr("List")
listVal, _ := val.(*starlarkstruct.Struct)

itemsVal, err := listVal.Attr("Items")
Expect(err).NotTo(HaveOccurred())
itemsListVal, ok := itemsVal.(*starlark.List)
Expect(ok).To(BeTrue())
Expect(itemsListVal).To(BeAssignableToTypeOf(&starlark.List{}))

Expect(itemsListVal.Len()).To(Equal(1))
})

Context("For each list entry", func() {

Context("For Namespace", func() {
var listStructVal *starlarkstruct.Struct

BeforeEach(func() {
sr := searchResults[0]
structVal := sr.ToStarlarkValue()
val, _ := structVal.Attr("List")
listVal, _ := val.(*starlarkstruct.Struct)
itemsVal, _ := listVal.Attr("Items")
itemsListVal, _ := itemsVal.(*starlark.List)
listStructVal, _ = itemsListVal.Index(0).(*starlarkstruct.Struct)
})

It("returns a starlark string for a string value", func() {
kindAttrVal, err := listStructVal.Attr("kind")
Expect(err).NotTo(HaveOccurred())
if kind, ok := kindAttrVal.(starlark.String); !ok {
Expect(kind.GoString()).To(Equal("Service"))
} else {
Expect(ok).To(BeTrue())
}

apiVersionVal, err := listStructVal.Attr("apiVersion")
Expect(err).NotTo(HaveOccurred())
if version, ok := apiVersionVal.(starlark.String); ok {
Expect(version.GoString()).To(Equal("v1"))
} else {
Expect(ok).To(BeTrue())
}
})

It("returns a starlark struct for a map value", func() {
metadataAttrVal, err := listStructVal.Attr("metadata")
Expect(err).NotTo(HaveOccurred())
metadata, ok := metadataAttrVal.(*starlarkstruct.Struct)
Expect(ok).To(BeTrue())

labelVal, err := metadata.Attr("labels")
Expect(err).NotTo(HaveOccurred())
Expect(labelVal).To(BeAssignableToTypeOf(&starlarkstruct.Struct{}))
})

It("returns a starlark list for an array value", func() {
specAttrVal, err := listStructVal.Attr("spec")
Expect(err).NotTo(HaveOccurred())
spec, ok := specAttrVal.(*starlarkstruct.Struct)
Expect(ok).To(BeTrue())

portsVal, err := spec.Attr("ports")
Expect(err).NotTo(HaveOccurred())
Expect(portsVal).To(BeAssignableToTypeOf(&starlark.List{}))

ports, ok := portsVal.(*starlark.List)
Expect(ok).To(BeTrue())
Expect(ports.Len()).To(Equal(3))
Expect(ports.Index(0)).To(BeAssignableToTypeOf(&starlarkstruct.Struct{}))
})
})
})
})
})
60 changes: 31 additions & 29 deletions starlark/kube_capture.go
Expand Up @@ -26,29 +26,31 @@ func KubeCaptureFn(thread *starlark.Thread, b *starlark.Builtin, args starlark.T
}
structVal := starlarkstruct.FromStringDict(starlarkstruct.Default, argDict)

kubeconfig, err := kubeconfigPath(thread, structVal)
kubeconfig, err := getKubeConfigPath(thread, structVal)
if err != nil {
return nil, errors.Wrap(err, "failed to kubeconfig")
return starlark.None, errors.Wrap(err, "failed to kubeconfig")
}
client, err := k8s.New(kubeconfig)
if err != nil {
return nil, errors.Wrap(err, "could not initialize search client")
return starlark.None, errors.Wrap(err, "could not initialize search client")
}

data := thread.Local(identifiers.crashdCfg)
cfg, _ := data.(*starlarkstruct.Struct)
workDirVal, _ := cfg.Attr("workdir")
resultDir, err := write(trimQuotes(workDirVal.String()), client, structVal)

dict := starlark.StringDict{
"error": starlark.String(""),
}
if err != nil {
dict["error"] = starlark.String(err.Error())
} else {
dict["file"] = starlark.String(resultDir)
}
return starlarkstruct.FromStringDict(starlarkstruct.Default, dict), nil
return starlarkstruct.FromStringDict(
starlarkstruct.Default,
starlark.StringDict{
"file": starlark.String(resultDir),
"error": func() starlark.String {
if err != nil {
return starlark.String(err.Error())
}
return ""
}(),
}), nil
}

func write(workdir string, client *k8s.Client, structVal *starlarkstruct.Struct) (string, error) {
Expand Down Expand Up @@ -90,29 +92,29 @@ func write(workdir string, client *k8s.Client, structVal *starlarkstruct.Struct)
return resultWriter.GetResultDir(), nil
}

// kubeconfigPath is responsible to obtain the path to the kubeconfig
// getKubeConfigPath is responsible to obtain the path to the kubeconfig
// It checks for the `path` key in the input args for the directive otherwise
// falls back to the default kube_config from the thread context
func kubeconfigPath(thread *starlark.Thread, structVal *starlarkstruct.Struct) (string, error) {
var kubeConfigPath string
func getKubeConfigPath(thread *starlark.Thread, structVal *starlarkstruct.Struct) (string, error) {
var (
kubeConfigPath string
err error
kcVal starlark.Value
)

if v, err := structVal.Attr("path"); err == nil {
kubeConfigPath = v.String()
} else {
if kcVal, err = structVal.Attr("kube_config"); err != nil {
kubeConfigData := thread.Local(identifiers.kubeCfg)
if kubeConfigData == nil {
return kubeConfigPath, errors.New("unable to find kubeconfig data")
}
cfg, ok := kubeConfigData.(*starlarkstruct.Struct)
if !ok {
return kubeConfigPath, errors.New("unable to process kubeconfig data")
}
path, err := cfg.Attr("path")
kcVal = kubeConfigData.(starlark.Value)
}

if kubeConfigVal, ok := kcVal.(*starlarkstruct.Struct); ok {
kvPathVal, err := kubeConfigVal.Attr("path")
if err != nil {
return kubeConfigPath, errors.New("unable to find path to kubeconfig")
return kubeConfigPath, errors.Wrap(err, "unable to extract kubeconfig path")
}
if kvPathStrVal, ok := kvPathVal.(starlark.String); ok {
kubeConfigPath = kvPathStrVal.GoString()
}
kubeConfigPath = path.String()
}

return trimQuotes(kubeConfigPath), nil
}

0 comments on commit 0f489be

Please sign in to comment.