Skip to content

Commit 1739b86

Browse files
committed
chore: move pkg from deis/deis to deis/pkg
0 parents  commit 1739b86

18 files changed

Lines changed: 1724 additions & 0 deletions

Makefile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
include ../includes.mk
2+
3+
repo_path = github.com/deis/deis/pkg
4+
5+
GO_PACKAGES = prettyprint time
6+
GO_PACKAGES_REPO_PATH = $(addprefix $(repo_path)/,$(GO_PACKAGES))
7+
8+
test: test-style test-unit
9+
10+
test-style:
11+
# display output, then check
12+
$(GOFMT) $(GO_PACKAGES)
13+
@$(GOFMT) $(GO_PACKAGES) | read; if [ $$? == 0 ]; then echo "gofmt check failed."; exit 1; fi
14+
$(GOVET) $(GO_PACKAGES_REPO_PATH)
15+
$(GOLINT) ./...
16+
17+
test-unit:
18+
$(GOTEST) ./...

aboutme/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# aboutme: Self-discovery for Kubernetes containers
2+
3+
This library provides hooks for containers to learn about themselves and
4+
their pods.
5+
6+
Essentially, this uses environmental data to connect to a k8s API server
7+
and find out about the current pod's configuration.

aboutme/aboutme.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Package aboutme provides information to a pod about itself.
2+
//
3+
// Typical usage is to let the Pod auto-detect information about itself:
4+
//
5+
// my, err := aboutme.FromEnv()
6+
// if err != nil {
7+
// // Error connecting to tke k8s API server
8+
// }
9+
//
10+
// fmt.Printf("My Pod Name is %s", my.Name)
11+
package aboutme
12+
13+
import (
14+
"errors"
15+
"net"
16+
"os"
17+
"strings"
18+
19+
"github.com/deis/deis/pkg/k8s"
20+
21+
"k8s.io/kubernetes/pkg/api"
22+
"k8s.io/kubernetes/pkg/client/unversioned"
23+
"k8s.io/kubernetes/pkg/labels"
24+
)
25+
26+
type Me struct {
27+
ApiServer, Name string
28+
IP, NodeIP, Namespace, SelfLink, UID string
29+
Labels map[string]string
30+
Annotations map[string]string
31+
32+
c *unversioned.Client
33+
}
34+
35+
// FromEnv uses the environment to create a new Me.
36+
//
37+
// To use this, a client MUST be running inside of a Pod environment. It uses
38+
// a combination of environment variables and file paths to determine
39+
// information about the cluster.
40+
func FromEnv() (*Me, error) {
41+
42+
host := os.Getenv("KUBERNETES_SERVICE_HOST")
43+
port := os.Getenv("KUBERNETES_SERVICE_PORT")
44+
name := os.Getenv("HOSTNAME")
45+
46+
// FIXME: Better way? Probably scanning secrets for
47+
// an SSL cert would help?
48+
proto := "https"
49+
50+
url := proto + "://" + host + ":" + port
51+
52+
me := &Me{
53+
ApiServer: url,
54+
Name: name,
55+
56+
// FIXME: This is a chicken-and-egg problem. We need the namespace
57+
// to get pod info, and we can only get info from the pod.
58+
Namespace: "default",
59+
}
60+
61+
client, err := k8s.PodClient()
62+
if err != nil {
63+
return me, err
64+
}
65+
me.c = client
66+
67+
me.init()
68+
69+
return me, nil
70+
}
71+
72+
// Client returns an initialized Kubernetes API client.
73+
func (me *Me) Client() *unversioned.Client {
74+
return me.c
75+
}
76+
77+
// ShuntEnv puts the Me object into the environment.
78+
//
79+
// The properties of Me are placed into the environment according to the
80+
// following rules:
81+
//
82+
// - In general, all variables are prefaced with MY_ (MY_IP, MY_NAMESPACE)
83+
// - Labels become MY_LABEL_[NAME]=[value]
84+
// - Annotations become MY_ANNOTATION_[NAME] = [value]
85+
func (me *Me) ShuntEnv() {
86+
env := map[string]string{
87+
"MY_APISERVER": me.ApiServer,
88+
"MY_NAME": me.Name,
89+
"MY_IP": me.IP,
90+
"MY_NODEIP": me.NodeIP,
91+
"MY_NAMESPACE": me.Namespace,
92+
"MY_SELFLINK": me.SelfLink,
93+
"MY_UID": me.UID,
94+
}
95+
for k, v := range env {
96+
os.Setenv(k, v)
97+
}
98+
var name string
99+
for k, v := range me.Labels {
100+
name = "MY_LABEL_" + strings.ToUpper(k)
101+
os.Setenv(name, v)
102+
}
103+
for k, v := range me.Annotations {
104+
name = "MY_ANNOTATION_" + strings.ToUpper(k)
105+
os.Setenv(name, v)
106+
}
107+
}
108+
109+
func (me *Me) init() error {
110+
p, n, err := me.findPodInNamespaces()
111+
if err != nil {
112+
return err
113+
}
114+
115+
me.Namespace = n
116+
me.IP = p.Status.PodIP
117+
me.NodeIP = p.Status.HostIP
118+
me.SelfLink = p.SelfLink
119+
me.UID = string(p.UID)
120+
me.Labels = p.Labels
121+
me.Annotations = me.Annotations
122+
123+
return nil
124+
}
125+
126+
// findPodInNamespaces searches relevant namespaces for this pod.
127+
//
128+
// It returns a PodInterface for working with the pod, a namespace name as a
129+
// string, and an error if something goes wrong.
130+
//
131+
// The search pattern is to look for namespaces that have the "deis" name in
132+
// their labels, and then to fall back to default. We don't look at all
133+
// namespaces.
134+
func (me *Me) findPodInNamespaces() (*api.Pod, string, error) {
135+
// Get the deis namespace. If it does not exist, get the default namespce.
136+
s, err := labels.Parse("name=deis")
137+
if err == nil {
138+
ns, err := me.c.Namespaces().List(s, nil)
139+
if err != nil {
140+
return nil, "default", err
141+
}
142+
for _, n := range ns.Items {
143+
p, err := me.c.Pods(n.Name).Get(me.Name)
144+
145+
// If there is no error, we got a matching pod.
146+
if err == nil {
147+
return p, n.Name, nil
148+
}
149+
}
150+
}
151+
152+
// If we get here, it's really the last ditch.
153+
p, err := me.c.Pods("default").Get(me.Name)
154+
return p, "default", err
155+
}
156+
157+
// MyIP examines the local interfaces and guesses which is its IP.
158+
//
159+
// Containers tend to put the IP address in eth0, so this attempts to look up
160+
// that interface and retrieve its IP. It is fairly naive. To get more
161+
// thorough IP information, you may prefer to use the `net` package and
162+
// look up the desired information.
163+
//
164+
// Because this queries the interfaces, not the Kube API server, this could,
165+
// in theory, return an IP address different from Me.IP.
166+
func MyIP() (string, error) {
167+
iface, err := net.InterfaceByName("eth0")
168+
if err != nil {
169+
return "", err
170+
}
171+
addrs, err := iface.Addrs()
172+
if err != nil {
173+
return "", err
174+
}
175+
var ip string
176+
for _, a := range addrs {
177+
if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
178+
if ipnet.IP.To4() != nil {
179+
ip = ipnet.IP.String()
180+
}
181+
}
182+
}
183+
if len(ip) == 0 {
184+
return ip, errors.New("Found no IPv4 addresses.")
185+
}
186+
return ip, nil
187+
}

aboutme/aboutme_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package aboutme
2+
3+
import (
4+
"net"
5+
"os"
6+
"testing"
7+
8+
"k8s.io/kubernetes/pkg/client/unversioned"
9+
)
10+
11+
func TestFromEnv(t *testing.T) {
12+
if _, err := unversioned.InClusterConfig(); err != nil {
13+
t.Skip("This can only be run inside Kubernetes. Skipping.")
14+
}
15+
16+
me, err := FromEnv()
17+
if err != nil {
18+
t.Errorf("Could not get an environment: %s", err)
19+
}
20+
if len(me.Name) == 0 {
21+
t.Error("Could not get a pod name.")
22+
}
23+
}
24+
25+
func TestShuntEnv(t *testing.T) {
26+
e := &Me{
27+
Annotations: map[string]string{"a": "a"},
28+
Labels: map[string]string{"b": "b"},
29+
Name: "c",
30+
}
31+
32+
e.ShuntEnv()
33+
34+
if "a" != os.Getenv("MY_ANNOTATION_A") {
35+
t.Errorf("Expected 'a', got '%s'", os.Getenv("MY_ANNOTATION_A"))
36+
}
37+
if "b" != os.Getenv("MY_LABEL_B") {
38+
t.Errorf("Expected 'b', got '%s'", os.Getenv("MY_LABEL_B"))
39+
}
40+
41+
if "c" != os.Getenv("MY_NAME") {
42+
t.Errorf("Expected 'c', got '%s'", os.Getenv("MY_NAME"))
43+
}
44+
}
45+
46+
func TestMyIP(t *testing.T) {
47+
if _, err := net.InterfaceByName("eth0"); err != nil {
48+
t.Skip("Host operating system does not have an eth0 device to test.")
49+
}
50+
51+
ip, err := MyIP()
52+
if err != nil {
53+
t.Errorf("Could not get IP address: %s", err)
54+
}
55+
56+
if len(ip) == 0 {
57+
t.Errorf("Expected a valid IP address. Got nuthin.")
58+
}
59+
}

env/envvar.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package env
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/Masterminds/cookoo"
8+
"github.com/Masterminds/cookoo/log"
9+
)
10+
11+
// Get gets one or more environment variables and puts them into the context.
12+
//
13+
// Parameters passed in are of the form varname => defaultValue.
14+
//
15+
// r.Route("foo", "example").Does(envvar.Get).Using("HOME").WithDefault(".")
16+
//
17+
// As with all environment variables, the default value must be a string.
18+
//
19+
// WARNING: Since parameters are a map, order of processing is not
20+
// guaranteed. If order is important, you'll need to call this command
21+
// multiple times.
22+
//
23+
// For each parameter (`Using` clause), this command will look into the
24+
// environment for a matching variable. If it finds one, it will add that
25+
// variable to the context. If it does not find one, it will expand the
26+
// default value (so you can set a default to something like "$HOST:$PORT")
27+
// and also put the (unexpanded) default value back into the context in case
28+
// any subsequent call to `os.Getenv` occurs.
29+
func Get(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
30+
for name, def := range params.AsMap() {
31+
var val string
32+
if val = os.Getenv(name); len(val) == 0 {
33+
if def == nil {
34+
def = ""
35+
}
36+
def, ok := def.(string)
37+
if !ok {
38+
log.Warnf(c, "Could not convert %s. Type is %T", name, def)
39+
}
40+
val = os.ExpandEnv(def)
41+
// We want to make sure that any subsequent calls to Getenv
42+
// return the same default.
43+
os.Setenv(name, val)
44+
45+
}
46+
c.Put(name, val)
47+
log.Debugf(c, "Name: %s, Val: %s", name, val)
48+
}
49+
return true, nil
50+
}
51+
52+
// Expand expands the environment variables in the given string and returns the result.
53+
//
54+
// Params:
55+
// - content (string): The given string to expand.
56+
//
57+
// Returns:
58+
// - The expanded string. This expands against the os environemnt (os.ExpandEnv).
59+
func Expand(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
60+
s := p.Get("content", "").(string)
61+
62+
// TODO: We could easily add support here for Expand().
63+
64+
return os.ExpandEnv(s), nil
65+
}
66+
67+
// Set takes the given names and values and puts them into both the context
68+
// and the environment.
69+
//
70+
// Unlike Get, it does not try to retrieve the values from the environment
71+
// first.
72+
//
73+
// Values are passed through os.ExpandEnv()
74+
//
75+
// There is no guarantee of insertion order. If multiple name/value pairs
76+
// are given, they will be put into the context in whatever order they
77+
// are retrieved from the underlying map.
78+
//
79+
// Params:
80+
// accessed as map[string]string
81+
// Returns:
82+
// nothing, but inserts all name/value pairs into the context and the
83+
// environment.
84+
func Set(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
85+
for name, def := range p.AsMap() {
86+
// Assume Nil means unset the value.
87+
if def == nil {
88+
def = ""
89+
}
90+
91+
val := fmt.Sprintf("%v", def)
92+
val = os.ExpandEnv(val)
93+
log.Debugf(c, "Name: %s, Val: %s", name, val)
94+
95+
os.Setenv(name, val)
96+
c.Put(name, val)
97+
}
98+
return true, nil
99+
100+
}

0 commit comments

Comments
 (0)