diff --git a/dependencies/config/config.go b/dependencies/config/config.go new file mode 100644 index 0000000..5cb87ca --- /dev/null +++ b/dependencies/config/config.go @@ -0,0 +1,95 @@ +package config + +import ( + "fmt" + "net" + "os" + "path/filepath" + "strings" + "text/template" + + entry "github.com/stackanetes/docker-entrypoint/dependencies" + "github.com/stackanetes/docker-entrypoint/logger" + "github.com/stackanetes/docker-entrypoint/util/env" +) + +type Config struct { + name string + params struct { + iface string + HOSTNAME string + IP string + IP_ERLANG string + } +} + +func init() { + configEnv := fmt.Sprintf("%sCONFIG", entry.DependencyPrefix) + if configDeps := env.SplitEnvToList(configEnv); len(configDeps) > 0 { + for _, dep := range configDeps { + entry.Register(NewConfig(dep)) + } + } +} + +func NewConfig(name string) Config { + var config Config + config.name = name + iface := os.Getenv("INTERFACE_NAME") + if iface == "" { + logger.Error.Print("Environment variable INTERFACE_NAME not set") + os.Exit(1) + } + hostname := os.Getenv("HOSTNAME") + if hostname == "" { + logger.Error.Print("Environment variable HOSTNAME not set") + } + config.params.HOSTNAME = hostname + config.params.iface = iface + i, err := net.InterfaceByName(iface) + if err != nil { + logger.Error.Printf("Cannot get iface: %v", err) + os.Exit(1) + } + + address, err := i.Addrs() + if err != nil || len(address) == 0 { + logger.Error.Printf("Cannot get ip: %v", err) + os.Exit(1) + } + config.params.IP = strings.Split(address[0].String(), "/")[0] + config.params.IP_ERLANG = strings.Replace(config.params.IP, ".", ",", -1) + + return config +} + +func (c Config) IsResolved(entrypoint *entry.Entrypoint) (bool, error) { + logger.Info.Print(c.GetName()) + err := CreateDirectory(c.GetName()) + if err != nil { + return false, fmt.Errorf("Couldn't create directory: %v", err) + } + config, err := os.Create(c.GetName()) + if err != nil { + return false, fmt.Errorf("Couldn't touch file %v: %v", c.GetName(), err) + } + file := filepath.Base(c.GetName()) + temp := template.Must(template.New(file).ParseFiles(fmt.Sprintf("/configmaps/%s/%s", file, file))) + if err = temp.Execute(config, c.params); err != nil { + return false, err + } + return true, nil + +} + +func (c Config) GetName() string { + return c.name +} + +func CreateDirectory(file string) error { + err := os.MkdirAll(filepath.Dir(file), 0644) + if err != nil { + return err + } + return nil +} diff --git a/dependencies/container/container.go b/dependencies/container/container.go new file mode 100644 index 0000000..faf3e16 --- /dev/null +++ b/dependencies/container/container.go @@ -0,0 +1,48 @@ +package container + +import ( + "fmt" + entry "github.com/stackanetes/docker-entrypoint/dependencies" + "github.com/stackanetes/docker-entrypoint/util/env" + "os" +) + +type Container struct { + name string +} + +func init() { + containerEnv := fmt.Sprintf("%sCONTAINER", entry.DependencyPrefix) + if containerDeps := env.SplitEnvToList(containerEnv); len(containerDeps) > 0 { + for _, dep := range containerDeps { + entry.Register(NewContainer(dep)) + } + } +} + +func NewContainer(name string) Container { + return Container{name: name} + +} + +func (c Container) IsResolved(entrypoint *entry.Entrypoint) (bool, error) { + myPodName := os.Getenv("POD_NAME") + if myPodName == "" { + return false, fmt.Errorf("Environment variable POD_NAME not set") + } + pod, err := entrypoint.Client.Pods(entrypoint.Namespace).Get(myPodName) + if err != nil { + return false, err + } + containers := pod.Status.ContainerStatuses + for _, container := range containers { + if container.Name == c.GetName() && container.State.Running != nil { + return true, nil + } + } + return false, nil +} + +func (c Container) GetName() string { + return c.name +} diff --git a/dependencies/daemonset/daemonset.go b/dependencies/daemonset/daemonset.go new file mode 100644 index 0000000..f80f4b3 --- /dev/null +++ b/dependencies/daemonset/daemonset.go @@ -0,0 +1,87 @@ +package daemonset + +import ( + "fmt" + "os" + + entry "github.com/stackanetes/docker-entrypoint/dependencies" + "github.com/stackanetes/docker-entrypoint/logger" + "github.com/stackanetes/docker-entrypoint/util/env" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/labels" +) + +type Daemonset struct { + name string +} + +func init() { + daemonsetEnv := fmt.Sprintf("%sDAEMONSET", entry.DependencyPrefix) + if daemonsetsDeps := env.SplitEnvToList(daemonsetEnv); daemonsetsDeps != nil { + for _, dep := range daemonsetsDeps { + entry.Register(NewDaemonset(dep)) + } + } +} + +func NewDaemonset(name string) Daemonset { + return Daemonset{name: name} +} + +func (d Daemonset) IsResolved(entrypoint *entry.Entrypoint) (bool, error) { + daemonset, err := entrypoint.Client.ExtensionsClient.DaemonSets(entrypoint.Namespace).Get(d.name) + if err != nil { + return false, err + } + label := labels.SelectorFromSet(daemonset.Spec.Selector.MatchLabels) + opts := api.ListOptions{LabelSelector: label} + pods, err := entrypoint.Client.Pods(entrypoint.Namespace).List(opts) + if err != nil { + return false, err + } + myPodName := os.Getenv("POD_NAME") + if myPodName == "" { + logger.Error.Print("Environment variable POD_NAME not set") + os.Exit(1) + + } + myPod, err := entrypoint.Client.Pods(entrypoint.Namespace).Get(myPodName) + if err != nil { + logger.Error.Printf("Getting POD: %v failed : %v", myPodName, err) + os.Exit(1) + } + myHost := myPod.Status.HostIP + + for _, pod := range pods.Items { + if !podReady(&pod) { + return false, fmt.Errorf("Pod %v of daemonset %v is not ready", pod.Name, d.GetName()) + } + + } + if !isPodOnHost(pods.Items, myHost) { + return false, fmt.Errorf("Hostname mismatch: Daemonset %v is not on the same host as Pod %v", d.GetName(), myPodName) + } + return true, nil +} + +func (d Daemonset) GetName() string { + return d.name +} + +func podReady(pod *api.Pod) bool { + for _, cond := range pod.Status.Conditions { + if cond.Type == api.PodReady && cond.Status == api.ConditionTrue { + return true + } + } + return false +} + +func isPodOnHost(podList []api.Pod, hostIP string) bool { + for _, pod := range podList { + if pod.Status.HostIP == hostIP { + return true + } + } + return false +} diff --git a/dependencies/entrypoint.go b/dependencies/entrypoint.go new file mode 100644 index 0000000..33c2e7f --- /dev/null +++ b/dependencies/entrypoint.go @@ -0,0 +1,75 @@ +package entrypoint + +import ( + "github.com/stackanetes/docker-entrypoint/logger" + // "k8s.io/kubernetes/pkg/client/restclient" + "fmt" + cl "k8s.io/kubernetes/pkg/client/unversioned" + "os" + "sync" + "time" +) + +var dependencies []Resolver // List containing all dependencies to be resolved +const ( + DependencyPrefix = "DEPENDENCY_" //Prefix for env variables + interval = 2 +) + +// Object containing k8s client +type Entrypoint struct { + Client *cl.Client + Namespace string +} + +//Constructor for entrypoint +func NewEntrypoint(client *cl.Client) (entry *Entrypoint, err error) { + entry = new(Entrypoint) + if entry.Client = client; client == nil { + if entry.Client, err = cl.NewInCluster(); err != nil { + err = fmt.Errorf("Error while creating k8s client: %s", err) + return entry, err + } + } + if entry.Namespace = os.Getenv("NAMESPACE"); entry.Namespace == "" { + logger.Warning.Print("NAMESPACE env not set, using default") + entry.Namespace = "default" + } + return entry, err +} + +func (e *Entrypoint) Resolve() { + var wg sync.WaitGroup + for _, dep := range dependencies { + wg.Add(1) + go func(dep Resolver) { + defer wg.Done() + logger.Info.Printf("Resolving %s", dep.GetName()) + var err error + status := false + for status == false { + if status, err = dep.IsResolved(e); err != nil { + logger.Warning.Printf("Resolving dependency for %v failed: %v", dep.GetName(), err) + } + time.Sleep(interval * time.Second) + } + logger.Info.Printf("Dependency %v is resolved", dep.GetName()) + + }(dep) + } + wg.Wait() + +} + +type Resolver interface { + // GetType() string + IsResolved(entrypoint *Entrypoint) (bool, error) + GetName() string +} + +func Register(res Resolver) { + if res == nil { + panic("Entrypoint: could not register nil Resolver") + } + dependencies = append(dependencies, res) +} diff --git a/dependencies/entrypoint_test.go b/dependencies/entrypoint_test.go new file mode 100644 index 0000000..3bfcb0e --- /dev/null +++ b/dependencies/entrypoint_test.go @@ -0,0 +1,17 @@ +package entrypoint + +import "testing" + +type dummyResolver struct { +} + +func (d *dummyResolver) IsResolved(name string) (bool, error) { + return true, nil +} +func TestRegisterNewDependency(t *testing.T) { + dummy := new(dummyResolver) + Register(dummy) + if len(Dependencies) != 1 { + t.Errorf("Expecting dependencies len to be 1 got %v", len(Dependencies)) + } +} diff --git a/dependencies/job/job.go b/dependencies/job/job.go new file mode 100644 index 0000000..259b2b4 --- /dev/null +++ b/dependencies/job/job.go @@ -0,0 +1,41 @@ +package job + +import ( + "fmt" + + entry "github.com/stackanetes/docker-entrypoint/dependencies" + "github.com/stackanetes/docker-entrypoint/util/env" +) + +type Job struct { + name string +} + +func init() { + jobsEnv := fmt.Sprintf("%sJOBS", entry.DependencyPrefix) + if jobsDeps := env.SplitEnvToList(jobsEnv); len(jobsDeps) > 0 { + for _, dep := range jobsDeps { + entry.Register(NewJob(dep)) + } + } +} + +func NewJob(name string) Job { + return Job{name: name} + +} + +func (j Job) IsResolved(entrypoint *entry.Entrypoint) (bool, error) { + job, err := entrypoint.Client.ExtensionsClient.Jobs(entrypoint.Namespace).Get(j.name) + if err != nil { + return false, err + } + if job.Status.Succeeded == 0 { + return false, fmt.Errorf("Job %v is not completed yet", j.GetName()) + } + return true, nil +} + +func (j Job) GetName() string { + return j.name +} diff --git a/dependencies/job/job_test.go b/dependencies/job/job_test.go new file mode 100644 index 0000000..c8f5376 --- /dev/null +++ b/dependencies/job/job_test.go @@ -0,0 +1,25 @@ +package job + +import ( + "fmt" + "os" + "testing" + + entry "github.com/stackanetes/docker-entrypoint/dependencies" + "github.com/stackanetes/docker-entrypoint/logger" +) + +func init() { + os.Setenv(fmt.Sprintf("%sJOBS", entry.DependencyPrefix), "test") + +} +func TestRegisterNewJob(t *testing.T) { + logger.Info.Printf("%v", os.Getenv(fmt.Sprintf("%sJOBS", entry.DependencyPrefix))) + if len(entry.Dependencies) != 1 { + t.Errorf("Expecting len of dependencies to be 1 not %v", len(entry.Dependencies)) + } + + if entry.Dependencies[0].GetName() != "test" { + t.Errorf("Expecting name to be test not %s", entry.Dependencies[0].GetName()) + } +} diff --git a/dependencies/service/service.go b/dependencies/service/service.go new file mode 100644 index 0000000..39e7c6a --- /dev/null +++ b/dependencies/service/service.go @@ -0,0 +1,40 @@ +package service + +import ( + "fmt" + entry "github.com/stackanetes/docker-entrypoint/dependencies" + "github.com/stackanetes/docker-entrypoint/util/env" +) + +type Service struct { + name string +} + +func init() { + serviceEnv := fmt.Sprintf("%sSERVICE", entry.DependencyPrefix) + if serviceDeps := env.SplitEnvToList(serviceEnv); len(serviceDeps) > 0 { + for _, dep := range serviceDeps { + entry.Register(NewService(dep)) + } + } +} + +func NewService(name string) Service { + return Service{name: name} + +} + +func (s Service) IsResolved(entrypoint *entry.Entrypoint) (bool, error) { + e, err := entrypoint.Client.Endpoints(entrypoint.Namespace).Get(s.GetName()) + if err != nil { + return false, err + } + if len(e.Subsets) > 0 { + return true, nil + } + return false, fmt.Errorf("Service %v has no endpoints", s.GetName()) +} + +func (s Service) GetName() string { + return s.name +} diff --git a/dependencies/service/service_test.go b/dependencies/service/service_test.go new file mode 100644 index 0000000..9f4ad52 --- /dev/null +++ b/dependencies/service/service_test.go @@ -0,0 +1,25 @@ +package service + +import ( + "fmt" + "os" + "testing" + + entry "github.com/stackanetes/docker-entrypoint/dependencies" + "github.com/stackanetes/docker-entrypoint/logger" +) + +func init() { + os.Setenv(fmt.Sprintf("%sSERVICE", entry.DependencyPrefix), "test") + +} +func TestRegisterNewService(t *testing.T) { + logger.Info.Printf("%v", os.Getenv(fmt.Sprintf("%sSERVICE", entry.DependencyPrefix))) + if len(entry.Dependencies) != 1 { + t.Errorf("Expecting len of dependencies to be 1 not %v", len(entry.Dependencies)) + } + + if entry.Dependencies[0].GetName() != "test" { + t.Errorf("Expecting name to be test not %s", entry.Dependencies[0].GetName()) + } +} diff --git a/dependencies/socket/socket.go b/dependencies/socket/socket.go new file mode 100644 index 0000000..0dab05d --- /dev/null +++ b/dependencies/socket/socket.go @@ -0,0 +1,43 @@ +package socket + +import ( + "fmt" + entry "github.com/stackanetes/docker-entrypoint/dependencies" + "github.com/stackanetes/docker-entrypoint/util/env" + "os" +) + +type Socket struct { + name string +} + +func init() { + socketEnv := fmt.Sprintf("%sSOCKET", entry.DependencyPrefix) + if socketDeps := env.SplitEnvToList(socketEnv); len(socketDeps) > 0 { + for _, dep := range socketDeps { + entry.Register(NewSocket(dep)) + } + } +} + +func NewSocket(name string) Socket { + return Socket{name: name} +} + +func (s Socket) GetName() string { + return s.name +} + +func (s Socket) IsResolved(entrypoint *entry.Entrypoint) (bool, error) { + _, err := os.Stat(s.GetName()) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, fmt.Errorf("Socket %v doesn't exists", s.GetName()) + } + if os.IsPermission(err) { + return false, fmt.Errorf("I have no permission to %v", s.GetName()) + } + return false, err +} diff --git a/kubernetes-entrypoint.go b/kubernetes-entrypoint.go new file mode 100644 index 0000000..f8ee25b --- /dev/null +++ b/kubernetes-entrypoint.go @@ -0,0 +1,40 @@ +package main + +import ( + "os" + + entry "github.com/stackanetes/docker-entrypoint/dependencies" + + "github.com/stackanetes/docker-entrypoint/logger" + comm "github.com/stackanetes/docker-entrypoint/util/command" + "github.com/stackanetes/docker-entrypoint/util/env" + cl "k8s.io/kubernetes/pkg/client/unversioned" + //Register resolvers + _ "github.com/stackanetes/docker-entrypoint/dependencies/config" + _ "github.com/stackanetes/docker-entrypoint/dependencies/container" + _ "github.com/stackanetes/docker-entrypoint/dependencies/daemonset" + _ "github.com/stackanetes/docker-entrypoint/dependencies/job" + _ "github.com/stackanetes/docker-entrypoint/dependencies/service" + _ "github.com/stackanetes/docker-entrypoint/dependencies/socket" +) + +func main() { + var client *cl.Client + var command []string + var entrypoint *entry.Entrypoint + var err error + + if entrypoint, err = entry.NewEntrypoint(client); err != nil { + logger.Error.Printf("Creating entrypoint failed: %v", err) + os.Exit(1) + } + entrypoint.Resolve() + + if command = env.SplitEnvToList("COMMAND", " "); len(command) == 0 { + logger.Error.Printf("COMMAND env is empty") + os.Exit(1) + } + if err = comm.ExecuteCommand(command); err != nil { + logger.Error.Printf("Executing command failed: %v", err) + } +} diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000..9d7e073 --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,21 @@ +package logger + +import ( + "log" + "os" +) + +var ( + //"Info logger"" + Info *log.Logger + //"Error logger" + Error *log.Logger + //Warning logger + Warning *log.Logger +) + +func init() { + Info = log.New(os.Stdout, "Entrypoint INFO: ", log.Ldate|log.Ltime|log.Lshortfile) + Warning = log.New(os.Stdout, "Entrypoint WARNING: ", log.Ldate|log.Ltime|log.Lshortfile) + Error = log.New(os.Stderr, "Entrypoint Error: ", log.Ldate|log.Ltime|log.Lshortfile) +} diff --git a/util/command/command.go b/util/command/command.go new file mode 100644 index 0000000..ebc5c0e --- /dev/null +++ b/util/command/command.go @@ -0,0 +1,26 @@ +package command + +import ( + "os" + "os/exec" +) + +func ExecuteCommand(command []string) error { + path, err := exec.LookPath(command[0]) + if err != nil { + return err + } + cmd := exec.Cmd{ + Path: path, + Args: command, + Stdout: os.Stdout, + Stderr: os.Stderr, + } + + err = cmd.Run() + if err != nil { + return err + } + + return nil +} diff --git a/util/command/command_test.go b/util/command/command_test.go new file mode 100644 index 0000000..449ea9d --- /dev/null +++ b/util/command/command_test.go @@ -0,0 +1,19 @@ +package command + +import "testing" + +func TestExecuteCommandSuccess(t *testing.T) { + successCommand := []string{"echo", "test"} + err := ExecuteCommand(successCommand) + if err != nil { + t.Errorf("Expecting: command to success not %v", err) + } +} + +func TestExecuteCommandFail(t *testing.T) { + errorCommand := []string{"false"} + err := ExecuteCommand(errorCommand) + if err == nil { + t.Errorf("Expecting command to fail") + } +} diff --git a/util/env/env.go b/util/env/env.go new file mode 100644 index 0000000..521781b --- /dev/null +++ b/util/env/env.go @@ -0,0 +1,20 @@ +package env + +import ( + "os" + "strings" +) + +func SplitEnvToList(env string, s ...string) (envList []string) { + separator := "," + if len(s) > 0 { + separator = s[0] + } + e := os.Getenv(env) + if e == "" { + return envList + } + + envList = strings.Split(e, separator) + return envList +} diff --git a/util/env/env_test.go b/util/env/env_test.go new file mode 100644 index 0000000..892a6f2 --- /dev/null +++ b/util/env/env_test.go @@ -0,0 +1,77 @@ +package env + +import ( + "os" + "testing" +) + +func TestSplitEnvToListWithColon(t *testing.T) { + os.Setenv("TEST_LIST", "foo,bar") + list := SplitEnvToList("TEST_LIST") + if list == nil { + t.Errorf("Expected: not nil") + } + if list[0] != "foo" { + t.Errorf("Expected: foo got %s", list[0]) + } + if list[1] != "bar" { + t.Errorf("Expected: bar got %s", list[1]) + } + + os.Setenv("TEST_LIST", "foo1") + list1 := SplitEnvToList("TEST_LIST") + if list1 == nil { + t.Errorf("Expected: not nil") + } + if len(list1) != 1 { + t.Errorf("Expected len to be 1 not %i", len(list1)) + } + if list1[0] != "foo1" { + t.Errorf("Expected: foo1 got %s", list1[0]) + } + +} + +func TestSplitEnvToListWithSpace(t *testing.T) { + os.Setenv("TEST_LIST", "foo bar") + list := SplitEnvToList("TEST_LIST", " ") + if list == nil { + t.Errorf("Expected: not nil") + } + if list[0] != "foo" { + t.Errorf("Expected: foo got %s", list[0]) + } + if list[1] != "bar" { + t.Errorf("Expected: bar got %s", list[1]) + } + + os.Setenv("TEST_LIST", "foo1") + list1 := SplitEnvToList("TEST_LIST", " ") + if list1 == nil { + t.Errorf("Expected: not nil") + } + if len(list1) != 1 { + t.Errorf("Expected len to be 1 not %i", len(list1)) + } + if list1[0] != "foo1" { + t.Errorf("Expected: foo1 got %s", list1[0]) + } + +} + +func TestSplitEmptyEnvWithColon(t *testing.T) { + os.Setenv("TEST_LIST", "") + list := SplitEnvToList("TEST_LIST") + if list != nil { + t.Errorf("Expected nil got %v", list) + } +} + +func TestSplitEmptyEnvWithSpace(t *testing.T) { + os.Setenv("TEST_LIST", "") + list := SplitEnvToList("TEST_LIST", " ") + if list != nil { + t.Errorf("Expected nil got %v", list) + } + +}