Skip to content

Commit

Permalink
Add capability to lookup values from kubernetes objects
Browse files Browse the repository at this point in the history
  • Loading branch information
Lewis Marshall authored and lewismarshall committed Oct 15, 2018
1 parent d1e0c16 commit 52d142e
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 5 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ kd specific template functions:

- [file](#file)
- [secret](#secret)
- [k8lookup](#k8lookup)

### split

Expand Down Expand Up @@ -282,6 +283,38 @@ data:
username: {{ "my-username" | b64enc }}
```
### k8lookup
`k8lookup` function allows retrieval of an Kubernetes object value using the parameters:
- `kind` - a Kubernetes object kind e.g. `pv` or `PersistentVolume`
- `name` - an object name e.g. `sysdig-mysql-a`
- `path` - a object path reference e.g. `.spec.capacity.storage`

Example:

With manually provisioned storage (e.g. iSCSI or NFS) a PV is typically managed
using a separate repository. Using lookup, we can discover the appropriate
storage size for a given cluster automatically:

```
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
name: sysdig-galera
name: data-sysdig-galera-0
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ lookup "pv" "sysdig-mysql-a" ".spec.capacity.storage" }}
selector:
matchLabels:
name: sysdig-mysql
storageClassName: manual
```

## Configuration

Configuration can be provided via cli flags and arguments as well as
Expand Down
49 changes: 49 additions & 0 deletions k8api-kubectl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"fmt"
"io/ioutil"
"strings"

"github.com/urfave/cli"
)

// K8ApiKubectl is a kubectl implimentation of K8Api interface
type K8ApiKubectl struct {
K8Api
Cx *cli.Context
}

// NewK8ApiKubectl creates a concrete class bound to use kubectl
func NewK8ApiKubectl(c *cli.Context) K8Api {
api := &K8ApiKubectl{
Cx: c,
}
return api
}

// Lookup will get data from a specified kubernetes object
func (a K8ApiKubectl) Lookup(kind, name, path string) (string, error) {
args := []string{"get", kind + "/" + name, "-o", "custom-columns=:" + path, "--no-headers"}

cmd, err := newKubeCmd(a.Cx, args, false)
if err != nil {
return "", err
}
stderr, _ := cmd.StderrPipe()
stdout, _ := cmd.StdoutPipe()
if err := cmd.Start(); err != nil {
logDebug.Printf("error starting kubectl: %s", err)
return "", err
}
data, _ := ioutil.ReadAll(stdout)
if err := cmd.Wait(); err != nil {
logDebug.Printf("error with kubectl: %s", err)
errData, _ := ioutil.ReadAll(stderr)
if strings.Contains("NotFound", string(errData[:])) {
return "", fmt.Errorf("Error object %s/%s not found", kind, name)
}
return "", err
}
return strings.TrimSpace(string(data[:])), nil
}
16 changes: 16 additions & 0 deletions k8api-noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

// K8ApiNoop is a noop API runner used when not connected to a server
type K8ApiNoop struct {
K8Api
}

// NewK8ApiNoop creats a new K8Api implimentaion based on K8ApiNoop
func NewK8ApiNoop() K8Api {
return &K8ApiNoop{}
}

// Lookup will pretentd to get data from a specified kubernetes object
func (a K8ApiNoop) Lookup(kind, name, path string) (string, error) {
return "noop", nil
}
7 changes: 7 additions & 0 deletions k8api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

// K8Api is an abstraction to allow the migration to the real API not kubectl
type K8Api interface {
// Lookup abstract interface for finding kuberneets api data by kind, name and path
Lookup(kind, name, path string) (string, error)
}
8 changes: 7 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,13 @@ func run(c *cli.Context) error {
return err
}
for _, d := range splitYamlDocs(string(data)) {
rendered, genSecret, err := Render(string(d), EnvToMap())
var k8api K8Api
if dryRun {
k8api = NewK8ApiNoop()
} else {
k8api = NewK8ApiKubectl(c)
}
rendered, genSecret, err := Render(k8api, string(d), EnvToMap())
if err != nil {
return err
}
Expand Down
17 changes: 15 additions & 2 deletions render.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import (

var (
secretUsed = false
k8Api K8Api
)

// Render - the function used for rendering templates (with Sprig support)
func Render(tmpl string, vars map[string]string) (string, bool, error) {
func Render(k K8Api, tmpl string, vars map[string]string) (string, bool, error) {
fm := sprig.TxtFuncMap()
// Preserve old KD functionality (strings param order vs sprig)
fm["contains"] = strings.Contains
Expand All @@ -27,6 +28,9 @@ func Render(tmpl string, vars map[string]string) (string, bool, error) {
fm["secret"] = secret
// Add file function to map
fm["file"] = fileRender
// Required for lookup function
k8Api = k
fm["k8lookup"] = k8lookup
secretUsed = false
defer func() {
if err := recover(); err != nil {
Expand Down Expand Up @@ -88,10 +92,19 @@ func fileRender(key string) string {
if err != nil {
panic(err.Error())
}
render, wasSecret, err := Render(string(data), EnvToMap())
render, wasSecret, err := Render(k8Api, string(data), EnvToMap())
if err != nil {
panic(err.Error())
}
secretUsed = wasSecret
return render
}

// k8lookup find a value from a kubernetes object
func k8lookup(kind, name, path string) string {
data, err := k8Api.Lookup(kind, name, path)
if err != nil {
panic(err.Error())
}
return data
}
5 changes: 3 additions & 2 deletions render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ func TestRender(t *testing.T) {
},
}

api := NewK8ApiNoop()
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got, _, err := Render(c.inputdata, c.inputvars)
got, _, err := Render(api, c.inputdata, c.inputvars)
if err != nil {
fmt.Println("Testing if folder doesnt exist")
}
Expand All @@ -83,7 +84,7 @@ func TestRender(t *testing.T) {
// Test of secret functions:
t.Run("Check secret is parsed and detected", func(t *testing.T) {
c := readfile("test/secret.yaml")
_, isSecret, err := Render(c, testData)
_, isSecret, err := Render(api, c, testData)
if err != nil {
fmt.Printf("unexpected problem rendering:%v\n", err)
}
Expand Down

0 comments on commit 52d142e

Please sign in to comment.