Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

envsvc: added pod spec env lookup #9

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion env.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,5 +505,10 @@ func Visit(fn func(*Var)) {

// Parse parses variables from the process environment.
func Parse() error {
return CmdVar.Parse(osLookup{})
return ParseWithGetter(osLookup{})
}

// ParseWithGetter parses variables via the given Getter.
func ParseWithGetter(g Getter) error {
return CmdVar.Parse(g)
}
22 changes: 19 additions & 3 deletions envsvc/envsvc.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,24 @@ func ParseWithExitFn(exitFn func(int)) {
envDumpJSON := flag.Bool("env-dump-json", false, "dump env variables in JSON format")
envDumpCUE := flag.Bool("env-dump-cue", false, "dump env variables as CUE schema")

envPodYAML := flag.String("env-pod-spec", "", "path to pod YAML to read env from")
envPodYAMLContainerName := flag.String("env-pod-spec-container-name", "", "extract env from container `name` in the pod spec (required if more than one container is in the pod)")

flag.Parse()

var g Getter = osLookup{}

if *envPodYAML != "" {
f, err := os.Open(*envPodYAML)
if err != nil {
fmt.Fprintf(os.Stderr, "could not open pod spec %q: %v", *envPodYAML, err)
}
g, err = PodENVLookup(f, *envPodYAMLContainerName)
if err != nil {
fmt.Fprintf(os.Stderr, "could not read pod spec: %v", err)
}
}

var outWriter io.Writer = os.Stderr

if *envDumpJSON {
Expand All @@ -45,15 +61,15 @@ func ParseWithExitFn(exitFn func(int)) {
fmt.Fprintf(outWriter, ",\n")
}
first = false
fmt.Fprintf(outWriter, " %q: %q", v.Name, os.Getenv(v.Name))
fmt.Fprintf(outWriter, " %q: %q", v.Name, get(g, v.Name))
})
fmt.Fprintf(outWriter, "\n}\n")
exitFn(0)
}

if *envDumpYAML {
env.Visit(func(v *env.Var) {
fmt.Fprintf(outWriter, "- name: %v\n value: %q\n", v.Name, os.Getenv(v.Name))
fmt.Fprintf(outWriter, "- name: %v\n value: %q\n", v.Name, get(g, v.Name))
})
exitFn(0)
}
Expand All @@ -76,7 +92,7 @@ func ParseWithExitFn(exitFn func(int)) {
fmt.Fprintf(outWriter, "\n")
}
first = false
fmt.Fprintf(outWriter, "# %v\nexport %v=%q\n", v.Usage, v.Name, os.Getenv(v.Name))
fmt.Fprintf(outWriter, "# %v\nexport %v=%q\n", v.Usage, v.Name, get(g, v.Name))
})
exitFn(0)
}
Expand Down
4 changes: 0 additions & 4 deletions envsvc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,3 @@ func detailHandler(w io.Writer) {
})
fmt.Fprintf(w, "\n ]\n}\n")
}

func init() {
http.HandleFunc("/debug/env", envHandler)
}
89 changes: 89 additions & 0 deletions envsvc/podspec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package envsvc

import (
"errors"
"fmt"
"io"
"os"

"gopkg.in/yaml.v3"
)

type pod struct {
Spec struct {
Containers []struct {
Name string `yaml:"name"`
Env []struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
} `yaml:"env"`
} `yaml:"containers"`
} `yaml:"spec"`
}

type Getter interface {
Get(string) (string, bool)
}

// PodENVLookup creates a lookup Getter from the given Pod YAML.
// Set name if there is more than one container in the pod.
func PodENVLookup(r io.Reader, name string) (Getter, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ENV instead of Env?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was in a shouty mood, presumably in anticipation of the YAML shenanigans. Will change it!

dec := yaml.NewDecoder(r)
p := &pod{}
if err := dec.Decode(p); err != nil {
return nil, err
}

if len(p.Spec.Containers) == 0 {
return nil, errors.New("no containers in pod spec")
}

if name == "" {
if len(p.Spec.Containers) != 1 {
return nil, fmt.Errorf("name empty but %d containers in pod spec, must set name", len(p.Spec.Containers))
}
return lookupFromContainerEnv(p, 0), nil
}

for i, c := range p.Spec.Containers {
if c.Name == name {
return lookupFromContainerEnv(p, i), nil
}
}
return nil, fmt.Errorf("no container for name %q", name)
}

func lookupFromContainerEnv(p *pod, n int) *lookup {
m := make(map[string]string)
for _, x := range p.Spec.Containers[n].Env {
m[x.Name] = x.Value
}
return &lookup{
g: osLookup{},
m: m,
}
}

type lookup struct {
g Getter
m map[string]string
}

func (l lookup) Get(x string) (string, bool) {
z, ok := l.g.Get(x)
if ok {
return z, ok
}

z, ok = l.m[x]
return z, ok
}

type osLookup struct{}

func (osLookup) Get(x string) (string, bool) { return os.LookupEnv(x) }

func get(g Getter, x string) string {
z, _ := g.Get(x)
return z
}
129 changes: 129 additions & 0 deletions envsvc/podspec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package envsvc_test

import (
"strings"
"testing"

"code.sajari.com/env/envsvc"
)

const singleInput = `apiVersion: v1
kind: Pod
metadata:
name: podname
labels:
name: podname
spec:
containers:
- name: one
image: one
ports:
- name: grpc
protocol: TCP
containerPort: 50551
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: "1"
memory: 50Mi
env:
- name: ONE_BOOLEAN
value: "false"
- name: ONE_STRING
value: "string value"
- name: ONE_INTEGER
value: "1"`

const multipleInput = `apiVersion: v1
kind: Pod
metadata:
name: podname
labels:
name: podname
spec:
containers:
- name: one
image: one
ports:
- name: grpc
protocol: TCP
containerPort: 50551
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: "1"
memory: 50Mi
env:
- name: ONE_BOOLEAN
value: "false"
- name: ONE_STRING
value: "string value"
- name: ONE_INTEGER
value: "1"
- name: two
image: two
ports:
- name: grpc
protocol: TCP
containerPort: 50552
env:
- name: TWO_BOOLEAN
value: "true"
- name: TWO_STRING
value: "string value two"
- name: TWO_INTEGER
value: "2"`

func TestPodENVLookup(t *testing.T) {
t.Run("single_container", func(t *testing.T) {
x, err := envsvc.PodENVLookup(strings.NewReader(singleInput), "")
must(t, err)

expectValue(t, x, "ONE_BOOLEAN", "false")
expectValue(t, x, "ONE_STRING", "string value")
expectValue(t, x, "ONE_INTEGER", "1")

// Using the name should be fine (for 1 container also).
x, err = envsvc.PodENVLookup(strings.NewReader(singleInput), "one")
must(t, err)

expectValue(t, x, "ONE_BOOLEAN", "false")
expectValue(t, x, "ONE_STRING", "string value")
expectValue(t, x, "ONE_INTEGER", "1")
})

t.Run("multiple_containers", func(t *testing.T) {
x, err := envsvc.PodENVLookup(strings.NewReader(multipleInput), "two")
must(t, err)

expectValue(t, x, "TWO_BOOLEAN", "true")
expectValue(t, x, "TWO_STRING", "string value two")
expectValue(t, x, "TWO_INTEGER", "2")
})

}

func expectValue(t *testing.T, g envsvc.Getter, name, value string) {
t.Helper()

x, ok := g.Get(name)
if !ok {
t.Errorf("expected value for %q", name)
return
}
if x != value {
t.Errorf("g.Get(%q) = %q, want %q", name, x, value)
}
}

func must(t *testing.T, err error) {
t.Helper()

if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module code.sajari.com/env

go 1.15

require gopkg.in/yaml.v3 v3.0.1
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=