Skip to content
This repository has been archived by the owner on Nov 17, 2021. It is now read-only.

Commit

Permalink
Expose basic cluster information to jsonnet
Browse files Browse the repository at this point in the history
This change adds two new jsonnet native functions:

- `kubernetesVersion` returns (eg) `[1, 7]` for a K8s 1.7 server.
- `kubernetesGroupVersionSupported` returns a bool indicating whether
  a given GroupVersion is supported by the current server

Note the latter does not provide a way to iterate over all supported
GroupVersions, for privacy.
  • Loading branch information
anguslees committed Sep 11, 2017
1 parent 4a554ab commit f0a87ad
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 20 deletions.
2 changes: 1 addition & 1 deletion cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ var applyCmd = &cobra.Command{
return err
}

objs, err := expandEnvCmdObjs(cmd, args)
objs, err := expandEnvCmdObjs(cmd, args, c.Discovery)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ var deleteCmd = &cobra.Command{
return err
}

objs, err := expandEnvCmdObjs(cmd, args)
objs, err := expandEnvCmdObjs(cmd, args, c.Discovery)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var diffCmd = &cobra.Command{
return err
}

objs, err := expandEnvCmdObjs(cmd, args)
objs, err := expandEnvCmdObjs(cmd, args, c.Discovery)
if err != nil {
return err
}
Expand Down
11 changes: 6 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@ import (
"path/filepath"
"strings"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
Expand Down Expand Up @@ -170,7 +169,7 @@ func (f *logFormatter) Format(e *log.Entry) ([]byte, error) {
return buf.Bytes(), nil
}

func newExpander(cmd *cobra.Command) (*template.Expander, error) {
func newExpander(cmd *cobra.Command, disco discovery.DiscoveryInterface) (*template.Expander, error) {
flags := cmd.Flags()
spec := template.Expander{}
var err error
Expand Down Expand Up @@ -211,6 +210,8 @@ func newExpander(cmd *cobra.Command) (*template.Expander, error) {
return nil, err
}

spec.Discovery = disco

return &spec, nil
}

Expand Down Expand Up @@ -273,7 +274,7 @@ func parseEnvCmd(cmd *cobra.Command, args []string) (*string, []string, error) {
// the user passes a list of files, we will expand all templates in those files,
// while if a user passes an environment name, we will expand all component
// files using that environment.
func expandEnvCmdObjs(cmd *cobra.Command, args []string) ([]*unstructured.Unstructured, error) {
func expandEnvCmdObjs(cmd *cobra.Command, args []string, disco discovery.DiscoveryInterface) ([]*unstructured.Unstructured, error) {
env, fileNames, err := parseEnvCmd(cmd, args)
if err != nil {
return nil, err
Expand All @@ -284,7 +285,7 @@ func expandEnvCmdObjs(cmd *cobra.Command, args []string) ([]*unstructured.Unstru
return nil, err
}

expander, err := newExpander(cmd)
expander, err := newExpander(cmd, disco)
if err != nil {
return nil, err
}
Expand Down
7 changes: 6 additions & 1 deletion cmd/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ var showCmd = &cobra.Command{
return err
}

objs, err := expandEnvCmdObjs(cmd, args)
_, disco, err := restClientPool(cmd)
if err != nil {
return err
}

objs, err := expandEnvCmdObjs(cmd, args, disco)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ local configuration. Accepts JSON, YAML, or Jsonnet.`,
return err
}

objs, err := expandEnvCmdObjs(cmd, args)
objs, err := expandEnvCmdObjs(cmd, args, c.Discovery)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var validateCmd = &cobra.Command{
return err
}

objs, err := expandEnvCmdObjs(cmd, args)
objs, err := expandEnvCmdObjs(cmd, args, c.Discovery)
if err != nil {
return err
}
Expand Down
10 changes: 10 additions & 0 deletions lib/kubecfg.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,14 @@
// to refer to submatches. Regex is as implemented in golang regexp
// package (python-ish).
regexSubst:: std.native("regexSubst"),

// kubernetesVersion(): Return [major, minor] representing the
// Kubernetes server version.
kubernetesVersion:: std.native("kubernetesVersion"),

// kubernetesGroupVersionSupported(gv): Return true iff the
// Kubernetes server advertises support for the specified API
// "group/version" (eg: "storage.k8s.io/v1beta1"). Legacy core is
// "v1" (and hopefully always supported).
kubernetesGroupVersionSupported:: std.native("kubernetesGroupVersionSupported"),
}
3 changes: 3 additions & 0 deletions lib/kubecfg_test.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ assert r == "f\\[o" : "got " + r;
local r = kubecfg.regexSubst("e", "tree", "oll");
assert r == "trolloll" : "got " + r;

// NB: kubernetes* functions are tested via integration tests, since
// they require access to a k8s server.

// Kubecfg wants to see something that looks like a k8s object
{
apiVersion: "test",
Expand Down
9 changes: 6 additions & 3 deletions template/expander.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import (
"os"
"strings"

log "github.com/sirupsen/logrus"
jsonnet "github.com/strickyak/jsonnet_cgo"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/discovery"

"github.com/ksonnet/kubecfg/utils"
log "github.com/sirupsen/logrus"
jsonnet "github.com/strickyak/jsonnet_cgo"
)

type Expander struct {
Expand All @@ -23,6 +24,8 @@ type Expander struct {

Resolver string
FailAction string

Discovery discovery.DiscoveryInterface
}

func (spec *Expander) Expand(paths []string) ([]*unstructured.Unstructured, error) {
Expand Down Expand Up @@ -116,7 +119,7 @@ func (spec *Expander) jsonnetVM() (*jsonnet.VM, error) {
if err != nil {
return nil, err
}
utils.RegisterNativeFuncs(vm, resolver)
utils.RegisterNativeFuncs(vm, resolver, spec.Discovery)

return vm, nil
}
38 changes: 36 additions & 2 deletions utils/nativefuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import (
"encoding/json"
"io"
"regexp"
"strconv"
"strings"

goyaml "github.com/ghodss/yaml"

jsonnet "github.com/strickyak/jsonnet_cgo"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/discovery"
)

func resolveImage(resolver Resolver, image string) (string, error) {
Expand All @@ -42,7 +43,7 @@ func resolveImage(resolver Resolver, image string) (string, error) {
}

// RegisterNativeFuncs adds kubecfg's native jsonnet functions to provided VM
func RegisterNativeFuncs(vm *jsonnet.VM, resolver Resolver) {
func RegisterNativeFuncs(vm *jsonnet.VM, resolver Resolver, disco discovery.DiscoveryInterface) {
// NB: libjsonnet native functions can only pass primitive
// types, so some functions json-encode the arg. These
// "*FromJson" functions will be replaced by regular native
Expand Down Expand Up @@ -105,4 +106,37 @@ func RegisterNativeFuncs(vm *jsonnet.VM, resolver Resolver) {
}
return r.ReplaceAllString(src, repl), nil
})

vm.NativeCallback("kubernetesVersion", []string{}, func() ([]interface{}, error) {
info, err := disco.ServerVersion()
if err != nil {
return nil, err
}

major, err := strconv.Atoi(info.Major)
if err != nil {
return nil, err
}
minor, err := strconv.Atoi(info.Minor)
if err != nil {
return nil, err
}
return []interface{}{major, minor}, nil
})

vm.NativeCallback("kubernetesGroupVersionSupported", []string{"gv"}, func(gv string) (bool, error) {
groups, err := disco.ServerGroups()
if err != nil {
return false, err
}

for _, g := range groups.Groups {
for _, v := range g.Versions {
if v.GroupVersion == gv {
return true, nil
}
}
}
return false, nil
})
}
30 changes: 26 additions & 4 deletions utils/nativefuncs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import (
"testing"

jsonnet "github.com/strickyak/jsonnet_cgo"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fakedisco "k8s.io/client-go/discovery/fake"
ktesting "k8s.io/client-go/testing"
)

// check there is no err, and a == b.
Expand All @@ -30,10 +33,29 @@ func check(t *testing.T, err error, actual, expected string) {
}
}

func registerNativeFuncs(vm *jsonnet.VM) {
fake := &ktesting.Fake{
Resources: []*metav1.APIResourceList{
{
GroupVersion: "tests/v1alpha1",
APIResources: []metav1.APIResource{
{
Name: "tests",
Kind: "Test",
},
},
},
},
}
disco := &fakedisco.FakeDiscovery{Fake: fake}

RegisterNativeFuncs(vm, NewIdentityResolver(), disco)
}

func TestParseJson(t *testing.T) {
vm := jsonnet.Make()
defer vm.Destroy()
RegisterNativeFuncs(vm, NewIdentityResolver())
registerNativeFuncs(vm)

_, err := vm.EvaluateSnippet("failtest", `std.native("parseJson")("barf{")`)
if err == nil {
Expand All @@ -52,7 +74,7 @@ func TestParseJson(t *testing.T) {
func TestParseYaml(t *testing.T) {
vm := jsonnet.Make()
defer vm.Destroy()
RegisterNativeFuncs(vm, NewIdentityResolver())
registerNativeFuncs(vm)

_, err := vm.EvaluateSnippet("failtest", `std.native("parseYaml")("[barf")`)
if err == nil {
Expand All @@ -76,7 +98,7 @@ func TestParseYaml(t *testing.T) {
func TestRegexMatch(t *testing.T) {
vm := jsonnet.Make()
defer vm.Destroy()
RegisterNativeFuncs(vm, NewIdentityResolver())
registerNativeFuncs(vm)

_, err := vm.EvaluateSnippet("failtest", `std.native("regexMatch")("[f", "foo")`)
if err == nil {
Expand All @@ -93,7 +115,7 @@ func TestRegexMatch(t *testing.T) {
func TestRegexSubst(t *testing.T) {
vm := jsonnet.Make()
defer vm.Destroy()
RegisterNativeFuncs(vm, NewIdentityResolver())
registerNativeFuncs(vm)

_, err := vm.EvaluateSnippet("failtest", `std.native("regexSubst")("[f",s "foo", "bar")`)
if err == nil {
Expand Down

0 comments on commit f0a87ad

Please sign in to comment.