diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..345e61a --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/README.md b/README.md index 91b9326..4d885db 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,17 @@ There is only one required environment variable "COMMAND" which specifies a comm Kubernetes-entrypoint introduces a wide variety of dependencies which can be used to better orchestrate once deployment. +## Latest features + +Extending functionality of kubernetes-entrypoint by adding an ability to specify dependencies in different namespaces. The new format for writing dependencies is `namespace:name`. To ensure backward compatibility if the dependency name is without colon, it behaves just like in previous versions so it esumes that dependecies is running at the same namespace as kubernetes-entrypoint. This feature is not implemented for container, config and socket dependency because in such cases the different namespace is irrelevant. + +For instance: +` +DEPENDENCY_SERVICE=mysql:mariadb,keystone-api +` + +The new entrypoint will resolve mariadb in mysql namespace and keystone-api in the same namespace as entrypoint was deployed in. + ## Supported types of dependencies All dependencies are passed as environement variables in format of `DEPENDENCY_` delimited by colon. For dependencies to be effective please use [readiness probes](http://kubernetes.io/docs/user-guide/production-pods/#liveness-and-readiness-probes-aka-health-checks) for all containers. diff --git a/dependencies/config/config.go b/dependencies/config/config.go index 937211e..16d9b04 100644 --- a/dependencies/config/config.go +++ b/dependencies/config/config.go @@ -13,7 +13,10 @@ import ( "github.com/stackanetes/kubernetes-entrypoint/util/env" ) -const configmapDirPrefix = "/configmaps" +const ( + configmapDirPrefix = "/configmaps" + NamespaceNotSupported = "Config doesn't accept namespace" +) type configParams struct { HOSTNAME string @@ -29,9 +32,13 @@ type Config struct { func init() { configEnv := fmt.Sprintf("%sCONFIG", entry.DependencyPrefix) - if configDeps := env.SplitEnvToList(configEnv); len(configDeps) > 0 { + if util.ContainsSeparator(configEnv, "Config") { + logger.Error.Printf(NamespaceNotSupported) + os.Exit(1) + } + if configDeps := env.SplitEnvToDeps(configEnv); len(configDeps) > 0 { for _, dep := range configDeps { - config, err := NewConfig(dep, configmapDirPrefix) + config, err := NewConfig(dep.Name, configmapDirPrefix) if err != nil { logger.Error.Printf("Cannot initialize config dep: %v", err) } @@ -63,11 +70,11 @@ func NewConfig(name string, prefix string) (*Config, error) { func (c Config) IsResolved(entrypoint entry.EntrypointInterface) (bool, error) { //Create directory to ensure it exists - if err := createDirectory(c.GetName()); err != nil { + if err := createDirectory(c.name); err != nil { return false, fmt.Errorf("Couldn't create directory: %v", err) } if err := c.createAndTemplateConfig(); err != nil { - return false, fmt.Errorf("Cannot template %s: %v", c.GetName(), err) + return false, fmt.Errorf("Cannot template %s: %v", c.name, err) } return true, nil @@ -87,10 +94,6 @@ func (c Config) createAndTemplateConfig() (err error) { return } -func (c Config) GetName() string { - return c.name -} - func getSrcConfig(prefix string, config string) (srcConfig string) { return fmt.Sprintf("%s/%s/%s", prefix, config, config) } @@ -98,3 +101,7 @@ func getSrcConfig(prefix string, config string) (srcConfig string) { func createDirectory(file string) error { return os.MkdirAll(filepath.Dir(file), 0755) } + +func (c Config) String() string { + return fmt.Sprintf("Config %s", c.name) +} diff --git a/dependencies/config/config_test.go b/dependencies/config/config_test.go index e6e83a7..3f39b02 100644 --- a/dependencies/config/config_test.go +++ b/dependencies/config/config_test.go @@ -1,4 +1,4 @@ -package config_test +package config import ( "fmt" @@ -7,7 +7,6 @@ import ( "os" "path/filepath" - . "github.com/stackanetes/kubernetes-entrypoint/dependencies/config" "github.com/stackanetes/kubernetes-entrypoint/entrypoint" "github.com/stackanetes/kubernetes-entrypoint/mocks" @@ -22,7 +21,6 @@ const ( testConfigContentsFormat = "TEST_CONFIG %s\n" - // configPath = "/tmp/lgtm" templatePrefix = "/tmp/templates" ) @@ -111,9 +109,11 @@ var _ = Describe("Config", func() { }) It("checks the name of a newly created config file", func() { - config, _ := NewConfig(testConfigPath, templatePrefix) + config, err := NewConfig(testConfigPath, templatePrefix) + Expect(config.name).To(Equal(testConfigPath)) - Expect(config.GetName()).To(Equal(testConfigPath)) + Expect(config).NotTo(Equal(nil)) + Expect(err).NotTo(HaveOccurred()) }) It("checks the format of a newly created config file", func() { @@ -137,5 +137,4 @@ var _ = Describe("Config", func() { Expect(isResolved).To(Equal(true)) Expect(err).NotTo(HaveOccurred()) }) - }) diff --git a/dependencies/container/container.go b/dependencies/container/container.go index b6fde1b..bd1387c 100644 --- a/dependencies/container/container.go +++ b/dependencies/container/container.go @@ -4,11 +4,18 @@ import ( "fmt" "os" + "strings" + entry "github.com/stackanetes/kubernetes-entrypoint/entrypoint" + "github.com/stackanetes/kubernetes-entrypoint/logger" + "github.com/stackanetes/kubernetes-entrypoint/util" "github.com/stackanetes/kubernetes-entrypoint/util/env" ) -const PodNameNotSetError = "Environment variable POD_NAME not set" +const ( + PodNameNotSetError = "Environment variable POD_NAME not set" + NamespaceNotSupported = "Container doesn't accept namespace" +) type Container struct { name string @@ -16,9 +23,16 @@ type Container struct { func init() { containerEnv := fmt.Sprintf("%sCONTAINER", entry.DependencyPrefix) - if containerDeps := env.SplitEnvToList(containerEnv); len(containerDeps) > 0 { - for _, dep := range containerDeps { - entry.Register(NewContainer(dep)) + if util.ContainsSeparator(containerEnv, "Container") { + logger.Error.Printf(NamespaceNotSupported) + os.Exit(1) + } + if containerDeps := env.SplitEnvToDeps(containerEnv); containerDeps != nil { + + if len(containerDeps) > 0 { + for _, dep := range containerDeps { + entry.Register(NewContainer(dep.Name)) + } } } } @@ -33,19 +47,23 @@ func (c Container) IsResolved(entrypoint entry.EntrypointInterface) (bool, error if myPodName == "" { return false, fmt.Errorf(PodNameNotSetError) } - pod, err := entrypoint.Client().Pods(entrypoint.GetNamespace()).Get(myPodName) + pod, err := entrypoint.Client().Pods(env.GetBaseNamespace()).Get(myPodName) if err != nil { return false, err } + + if strings.Contains(c.name, env.Separator) { + return false, fmt.Errorf("Specifing namespace is not permitted") + } containers := pod.Status.ContainerStatuses for _, container := range containers { - if container.Name == c.GetName() && container.Ready { + if container.Name == c.name && container.Ready { return true, nil } } return false, nil } -func (c Container) GetName() string { - return c.name +func (c Container) String() string { + return fmt.Sprintf("Container %s", c.name) } diff --git a/dependencies/container/container_test.go b/dependencies/container/container_test.go index 5fb1e73..9cd78bd 100644 --- a/dependencies/container/container_test.go +++ b/dependencies/container/container_test.go @@ -1,10 +1,9 @@ -package container_test +package container import ( "fmt" "os" - . "github.com/stackanetes/kubernetes-entrypoint/dependencies/container" "github.com/stackanetes/kubernetes-entrypoint/entrypoint" "github.com/stackanetes/kubernetes-entrypoint/mocks" @@ -30,7 +29,7 @@ var _ = Describe("Container", func() { It("checks the name of a newly created container", func() { container := NewContainer(mocks.MockContainerName) - Expect(container.GetName()).To(Equal(mocks.MockContainerName)) + Expect(container.name).To(Equal(mocks.MockContainerName)) }) It(fmt.Sprintf("checks container resolution failure with %s not set", podEnvVariableName), func() { diff --git a/dependencies/daemonset/daemonset.go b/dependencies/daemonset/daemonset.go index 25401ed..18d0f5d 100644 --- a/dependencies/daemonset/daemonset.go +++ b/dependencies/daemonset/daemonset.go @@ -14,19 +14,20 @@ import ( const ( PodNameEnvVar = "POD_NAME" - PodNameNotSetErrorFormat = "Env POD_NAME not set. Daemonset dependency %s will be ignored!" + PodNameNotSetErrorFormat = "Env POD_NAME not set. Daemonset dependency %s in namespace %s will be ignored!" ) type Daemonset struct { - name string - podName string + name string + namespace string + podName string } func init() { daemonsetEnv := fmt.Sprintf("%sDAEMONSET", entry.DependencyPrefix) - if daemonsetsDeps := env.SplitEnvToList(daemonsetEnv); daemonsetsDeps != nil { + if daemonsetsDeps := env.SplitEnvToDeps(daemonsetEnv); daemonsetsDeps != nil { for _, dep := range daemonsetsDeps { - daemonset, err := NewDaemonset(dep) + daemonset, err := NewDaemonset(dep.Name, dep.Namespace) if err != nil { logger.Error.Printf("Cannot initialize daemonset: %v", err) continue @@ -36,19 +37,20 @@ func init() { } } -func NewDaemonset(name string) (*Daemonset, error) { +func NewDaemonset(name string, namespace string) (*Daemonset, error) { if os.Getenv(PodNameEnvVar) == "" { - return nil, fmt.Errorf(PodNameNotSetErrorFormat, name) + return nil, fmt.Errorf(PodNameNotSetErrorFormat, name, namespace) } return &Daemonset{ - name: name, - podName: os.Getenv(PodNameEnvVar), + name: name, + namespace: namespace, + podName: os.Getenv(PodNameEnvVar), }, nil } func (d Daemonset) IsResolved(entrypoint entry.EntrypointInterface) (bool, error) { var myPodName string - daemonset, err := entrypoint.Client().DaemonSets(entrypoint.GetNamespace()).Get(d.GetName()) + daemonset, err := entrypoint.Client().DaemonSets(d.namespace).Get(d.name) if err != nil { return false, err @@ -56,12 +58,12 @@ func (d Daemonset) IsResolved(entrypoint entry.EntrypointInterface) (bool, error label := labels.SelectorFromSet(daemonset.Spec.Selector.MatchLabels) opts := api.ListOptions{LabelSelector: label} - pods, err := entrypoint.Client().Pods(entrypoint.GetNamespace()).List(opts) + pods, err := entrypoint.Client().Pods(d.namespace).List(opts) if err != nil { return false, err } - myPod, err := entrypoint.Client().Pods(entrypoint.GetNamespace()).Get(d.podName) + myPod, err := entrypoint.Client().Pods(d.namespace).Get(d.podName) if err != nil { return false, fmt.Errorf("Getting POD: %v failed : %v", myPodName, err) } @@ -75,16 +77,12 @@ func (d Daemonset) IsResolved(entrypoint entry.EntrypointInterface) (bool, error if isPodReady(pod) { return true, nil } - return false, fmt.Errorf("Pod %v of daemonset %v is not ready", pod.Name, d.GetName()) + return false, fmt.Errorf("Pod %v of daemonset %s is not ready", pod.Name, d) } return true, nil } -func (d Daemonset) GetName() string { - return d.name -} - func isPodOnHost(pod *v1.Pod, hostIP string) bool { if pod.Status.HostIP == hostIP { return true @@ -100,3 +98,7 @@ func isPodReady(pod v1.Pod) bool { } return false } + +func (d Daemonset) String() string { + return fmt.Sprintf("Daemonset %s in namespace %s", d.name, d.namespace) +} diff --git a/dependencies/daemonset/daemonset_test.go b/dependencies/daemonset/daemonset_test.go index e862b10..c79c756 100644 --- a/dependencies/daemonset/daemonset_test.go +++ b/dependencies/daemonset/daemonset_test.go @@ -1,10 +1,9 @@ -package daemonset_test +package daemonset import ( "fmt" "os" - . "github.com/stackanetes/kubernetes-entrypoint/dependencies/daemonset" "github.com/stackanetes/kubernetes-entrypoint/entrypoint" "github.com/stackanetes/kubernetes-entrypoint/mocks" @@ -14,6 +13,7 @@ import ( const ( podEnvVariableValue = "podlist" + daemonsetNamespace = "namespace1" ) var testEntrypoint entrypoint.EntrypointInterface @@ -29,22 +29,22 @@ var _ = Describe("Daemonset", func() { It(fmt.Sprintf("checks failure of new daemonset creation without %s set", PodNameEnvVar), func() { os.Unsetenv(PodNameEnvVar) - daemonset, err := NewDaemonset(mocks.SucceedingDaemonsetName) + daemonset, err := NewDaemonset(mocks.SucceedingDaemonsetName, daemonsetNamespace) Expect(daemonset).To(BeNil()) - Expect(err.Error()).To(Equal(fmt.Sprintf(PodNameNotSetErrorFormat, mocks.SucceedingDaemonsetName))) + Expect(err.Error()).To(Equal(fmt.Sprintf(PodNameNotSetErrorFormat, mocks.SucceedingDaemonsetName, daemonsetNamespace))) }) It(fmt.Sprintf("creates new daemonset with %s set and checks its name", PodNameEnvVar), func() { - daemonset, err := NewDaemonset(mocks.SucceedingDaemonsetName) + daemonset, err := NewDaemonset(mocks.SucceedingDaemonsetName, daemonsetNamespace) Expect(daemonset).NotTo(Equal(nil)) Expect(err).NotTo(HaveOccurred()) - Expect(daemonset.GetName()).To(Equal(mocks.SucceedingDaemonsetName)) + Expect(daemonset.name).To(Equal(mocks.SucceedingDaemonsetName)) }) It("checks resolution of a succeeding daemonset", func() { - daemonset, _ := NewDaemonset(mocks.SucceedingDaemonsetName) + daemonset, _ := NewDaemonset(mocks.SucceedingDaemonsetName, daemonsetNamespace) isResolved, err := daemonset.IsResolved(testEntrypoint) @@ -53,7 +53,7 @@ var _ = Describe("Daemonset", func() { }) It("checks resolution failure of a daemonset with incorrect name", func() { - daemonset, _ := NewDaemonset(mocks.FailingDaemonsetName) + daemonset, _ := NewDaemonset(mocks.FailingDaemonsetName, daemonsetNamespace) isResolved, err := daemonset.IsResolved(testEntrypoint) @@ -62,7 +62,7 @@ var _ = Describe("Daemonset", func() { }) It("checks resolution failure of a daemonset with incorrect match labels", func() { - daemonset, _ := NewDaemonset(mocks.IncorrectMatchLabelsDaemonsetName) + daemonset, _ := NewDaemonset(mocks.IncorrectMatchLabelsDaemonsetName, daemonsetNamespace) isResolved, err := daemonset.IsResolved(testEntrypoint) @@ -73,7 +73,7 @@ var _ = Describe("Daemonset", func() { It(fmt.Sprintf("checks resolution failure of a daemonset with incorrect %s value", PodNameEnvVar), func() { // Set POD_NAME to value not present in the mocks os.Setenv(PodNameEnvVar, mocks.PodNotPresent) - daemonset, _ := NewDaemonset(mocks.IncorrectMatchLabelsDaemonsetName) + daemonset, _ := NewDaemonset(mocks.IncorrectMatchLabelsDaemonsetName, daemonsetNamespace) isResolved, err := daemonset.IsResolved(testEntrypoint) @@ -82,7 +82,32 @@ var _ = Describe("Daemonset", func() { }) It("checks resolution failure of a daemonset with none of the pods with Ready status", func() { - daemonset, _ := NewDaemonset(mocks.NotReadyMatchLabelsDaemonsetName) + daemonset, _ := NewDaemonset(mocks.NotReadyMatchLabelsDaemonsetName, daemonsetNamespace) + + isResolved, err := daemonset.IsResolved(testEntrypoint) + + Expect(isResolved).To(Equal(false)) + Expect(err).To(HaveOccurred()) + }) + + It("checks resolution of a correct daemonset namespace", func() { + daemonset, err := NewDaemonset(mocks.CorrectDaemonsetNamespace, daemonsetNamespace) + + Expect(daemonset).NotTo(Equal(nil)) + Expect(err).NotTo(HaveOccurred()) + + isResolved, err := daemonset.IsResolved(testEntrypoint) + + Expect(isResolved).To(Equal(true)) + Expect(err).NotTo(HaveOccurred()) + + }) + + It("checks resolution of an incorrect daemonset namespace", func() { + daemonset, err := NewDaemonset(mocks.IncorrectDaemonsetNamespace, daemonsetNamespace) + + Expect(daemonset).NotTo(Equal(nil)) + Expect(err).NotTo(HaveOccurred()) isResolved, err := daemonset.IsResolved(testEntrypoint) diff --git a/dependencies/job/job.go b/dependencies/job/job.go index 00c6331..006f41a 100644 --- a/dependencies/job/job.go +++ b/dependencies/job/job.go @@ -7,37 +7,43 @@ import ( "github.com/stackanetes/kubernetes-entrypoint/util/env" ) -const FailingStatusFormat = "Job %v is not completed yet" +const FailingStatusFormat = "Job %s is not completed yet" type Job struct { - name string + name string + namespace 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)) + if jobsDeps := env.SplitEnvToDeps(jobsEnv); jobsDeps != nil { + if len(jobsDeps) > 0 { + for _, dep := range jobsDeps { + entry.Register(NewJob(dep.Name, dep.Namespace)) + } } } } -func NewJob(name string) Job { - return Job{name: name} +func NewJob(name string, namespace string) Job { + return Job{ + name: name, + namespace: namespace, + } } func (j Job) IsResolved(entrypoint entry.EntrypointInterface) (bool, error) { - job, err := entrypoint.Client().Jobs(entrypoint.GetNamespace()).Get(j.GetName()) + job, err := entrypoint.Client().Jobs(j.namespace).Get(j.name) if err != nil { return false, err } if job.Status.Succeeded == 0 { - return false, fmt.Errorf(FailingStatusFormat, j.GetName()) + return false, fmt.Errorf(FailingStatusFormat, j) } return true, nil } -func (j Job) GetName() string { - return j.name +func (j Job) String() string { + return fmt.Sprintf("Job %s in namespace %s", j.name, j.namespace) } diff --git a/dependencies/job/job_test.go b/dependencies/job/job_test.go index 86db058..7efc2c7 100644 --- a/dependencies/job/job_test.go +++ b/dependencies/job/job_test.go @@ -1,9 +1,8 @@ -package job_test +package job import ( "fmt" - . "github.com/stackanetes/kubernetes-entrypoint/dependencies/job" "github.com/stackanetes/kubernetes-entrypoint/entrypoint" "github.com/stackanetes/kubernetes-entrypoint/mocks" @@ -11,7 +10,8 @@ import ( . "github.com/onsi/gomega" ) -const testJobName = "TEST_JOB" +const testJobName = "TEST_JOB_NAME" +const testJobNamespace = "TEST_JOB_NAMESPACE" var testEntrypoint entrypoint.EntrypointInterface @@ -22,13 +22,14 @@ var _ = Describe("Job", func() { }) It("checks the name of a newly created job", func() { - job := NewJob(testJobName) + job := NewJob(testJobName, testJobNamespace) - Expect(job.GetName()).To(Equal(testJobName)) + Expect(job.name).To(Equal(testJobName)) + Expect(job.namespace).To(Equal(testJobNamespace)) }) It("checks resolution of a succeeding job", func() { - job := NewJob(mocks.SucceedingJobName) + job := NewJob(mocks.SucceedingJobName, mocks.SucceedingJobName) isResolved, err := job.IsResolved(testEntrypoint) @@ -37,11 +38,12 @@ var _ = Describe("Job", func() { }) It("checks resolution failure of a failing job", func() { - job := NewJob(mocks.FailingJobName) + job := NewJob(mocks.FailingJobName, mocks.FailingJobName) isResolved, err := job.IsResolved(testEntrypoint) Expect(isResolved).To(Equal(false)) - Expect(err.Error()).To(Equal(fmt.Sprintf(FailingStatusFormat, job.GetName()))) + Expect(err.Error()).To(Equal(fmt.Sprintf(FailingStatusFormat, job))) + Expect(err.Error()).To(Equal(fmt.Sprintf(FailingStatusFormat, job))) }) }) diff --git a/dependencies/service/service.go b/dependencies/service/service.go index c5412ae..c3ffa4a 100644 --- a/dependencies/service/service.go +++ b/dependencies/service/service.go @@ -10,25 +10,31 @@ import ( const FailingStatusFormat = "Service %v has no endpoints" type Service struct { - name string + name string + namespace 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)) + if serviceDeps := env.SplitEnvToDeps(serviceEnv); serviceDeps != nil { + if len(serviceDeps) > 0 { + for _, dep := range serviceDeps { + entry.Register(NewService(dep.Name, dep.Namespace)) + } } } } -func NewService(name string) Service { - return Service{name: name} +func NewService(name string, namespace string) Service { + return Service{ + name: name, + namespace: namespace, + } } func (s Service) IsResolved(entrypoint entry.EntrypointInterface) (bool, error) { - e, err := entrypoint.Client().Endpoints(entrypoint.GetNamespace()).Get(s.GetName()) + e, err := entrypoint.Client().Endpoints(s.namespace).Get(s.name) if err != nil { return false, err } @@ -38,9 +44,9 @@ func (s Service) IsResolved(entrypoint entry.EntrypointInterface) (bool, error) return true, nil } } - return false, fmt.Errorf(FailingStatusFormat, s.GetName()) + return false, fmt.Errorf(FailingStatusFormat, s.name) } -func (s Service) GetName() string { - return s.name +func (s Service) String() string { + return fmt.Sprintf("Service %s in namespace %s", s.name, s.namespace) } diff --git a/dependencies/service/service_test.go b/dependencies/service/service_test.go index 3639f5b..5430ea6 100644 --- a/dependencies/service/service_test.go +++ b/dependencies/service/service_test.go @@ -1,9 +1,8 @@ -package service_test +package service import ( "fmt" - . "github.com/stackanetes/kubernetes-entrypoint/dependencies/service" "github.com/stackanetes/kubernetes-entrypoint/entrypoint" "github.com/stackanetes/kubernetes-entrypoint/mocks" @@ -11,7 +10,8 @@ import ( . "github.com/onsi/gomega" ) -const testServiceName = "TEST_SERVICE" +const testServiceName = "TEST_SERVICE_NAME" +const testServiceNamespace = "TEST_SERVICE_NAMESPACE" var testEntrypoint entrypoint.EntrypointInterface @@ -22,13 +22,14 @@ var _ = Describe("Service", func() { }) It("checks the name of a newly created service", func() { - service := NewService(testServiceName) + service := NewService(testServiceName, testServiceNamespace) - Expect(service.GetName()).To(Equal(testServiceName)) + Expect(service.name).To(Equal(testServiceName)) + Expect(service.namespace).To(Equal(testServiceNamespace)) }) It("checks resolution of a succeeding service", func() { - service := NewService(mocks.SucceedingServiceName) + service := NewService(mocks.SucceedingServiceName, mocks.SucceedingServiceName) isResolved, err := service.IsResolved(testEntrypoint) @@ -37,7 +38,7 @@ var _ = Describe("Service", func() { }) It("checks resolution failure of a failing service", func() { - service := NewService(mocks.FailingServiceName) + service := NewService(mocks.FailingServiceName, mocks.FailingServiceName) isResolved, err := service.IsResolved(testEntrypoint) @@ -46,10 +47,10 @@ var _ = Describe("Service", func() { }) It("checks resolution failure of a succeeding service with removed subsets", func() { - service := NewService(mocks.EmptySubsetsServiceName) + service := NewService(mocks.EmptySubsetsServiceName, mocks.EmptySubsetsServiceName) isResolved, err := service.IsResolved(testEntrypoint) Expect(isResolved).To(Equal(false)) - Expect(err.Error()).To(Equal(fmt.Sprintf(FailingStatusFormat, service.GetName()))) + Expect(err.Error()).To(Equal(fmt.Sprintf(FailingStatusFormat, service.name))) }) }) diff --git a/dependencies/socket/socket.go b/dependencies/socket/socket.go index b251be1..da47f89 100644 --- a/dependencies/socket/socket.go +++ b/dependencies/socket/socket.go @@ -5,12 +5,15 @@ import ( "os" entry "github.com/stackanetes/kubernetes-entrypoint/entrypoint" + "github.com/stackanetes/kubernetes-entrypoint/logger" + "github.com/stackanetes/kubernetes-entrypoint/util" "github.com/stackanetes/kubernetes-entrypoint/util/env" ) const ( - NonExistingErrorFormat = "Socket %v doesn't exists" - NoPermsErrorFormat = "I have no permission to %v" + NonExistingErrorFormat = "%s doesn't exists" + NoPermsErrorFormat = "I have no permission to %s" + NamespaceNotSupported = "Socket doesn't accept namespace" ) type Socket struct { @@ -19,9 +22,15 @@ type Socket struct { func init() { socketEnv := fmt.Sprintf("%sSOCKET", entry.DependencyPrefix) - if socketDeps := env.SplitEnvToList(socketEnv); len(socketDeps) > 0 { - for _, dep := range socketDeps { - entry.Register(NewSocket(dep)) + if util.ContainsSeparator(socketEnv, "Socket") { + logger.Error.Printf(NamespaceNotSupported) + os.Exit(1) + } + if socketDeps := env.SplitEnvToDeps(socketEnv); socketDeps != nil { + if len(socketDeps) > 0 { + for _, dep := range socketDeps { + entry.Register(NewSocket(dep.Name)) + } } } } @@ -30,20 +39,20 @@ func NewSocket(name string) Socket { return Socket{name: name} } -func (s Socket) GetName() string { - return s.name -} - func (s Socket) IsResolved(entrypoint entry.EntrypointInterface) (bool, error) { - _, err := os.Stat(s.GetName()) + _, err := os.Stat(s.name) if err == nil { return true, nil } if os.IsNotExist(err) { - return false, fmt.Errorf(NonExistingErrorFormat, s.GetName()) + return false, fmt.Errorf(NonExistingErrorFormat, s) } if os.IsPermission(err) { - return false, fmt.Errorf(NoPermsErrorFormat, s.GetName()) + return false, fmt.Errorf(NoPermsErrorFormat, s) } return false, err } + +func (s Socket) String() string { + return fmt.Sprintf("Socket %s", s.name) +} diff --git a/dependencies/socket/socket_test.go b/dependencies/socket/socket_test.go index 3dd4e3e..ea8978d 100644 --- a/dependencies/socket/socket_test.go +++ b/dependencies/socket/socket_test.go @@ -1,10 +1,9 @@ -package socket_test +package socket import ( "fmt" "os" - . "github.com/stackanetes/kubernetes-entrypoint/dependencies/socket" "github.com/stackanetes/kubernetes-entrypoint/entrypoint" "github.com/stackanetes/kubernetes-entrypoint/mocks" @@ -33,7 +32,7 @@ var _ = Describe("Socket", func() { It("checks the name of a newly created socket", func() { socket := NewSocket(existingSocketPath) - Expect(socket.GetName()).To(Equal(existingSocketPath)) + Expect(socket.name).To(Equal(existingSocketPath)) }) It("resolves an existing socket socket", func() { @@ -52,7 +51,7 @@ var _ = Describe("Socket", func() { Expect(isResolved).To(Equal(false)) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal(fmt.Sprintf(NonExistingErrorFormat, socket.GetName()))) + Expect(err.Error()).To(Equal(fmt.Sprintf(NonExistingErrorFormat, socket))) }) It("fails on trying to resolve a socket without permissions", func() { @@ -62,7 +61,6 @@ var _ = Describe("Socket", func() { Expect(isResolved).To(Equal(false)) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal(fmt.Sprintf(NoPermsErrorFormat, socket.GetName()))) + Expect(err.Error()).To(Equal(fmt.Sprintf(NoPermsErrorFormat, socket))) }) - }) diff --git a/entrypoint/entrypoint.go b/entrypoint/entrypoint.go index 21a46f1..5ddf734 100644 --- a/entrypoint/entrypoint.go +++ b/entrypoint/entrypoint.go @@ -1,7 +1,6 @@ package entrypoint import ( - "os" "sync" "time" @@ -20,13 +19,11 @@ const ( //Resolver is an interface which all dependencies should implement type Resolver interface { IsResolved(entrypoint EntrypointInterface) (bool, error) - GetName() string } type EntrypointInterface interface { Resolve() Client() cli.ClientInterface - GetNamespace() string } // Entrypoint is a main struct which checks dependencies @@ -51,10 +48,6 @@ func New(config *rest.Config) (entry *Entrypoint, err error) { return nil, err } entry.client = client - if entry.namespace = os.Getenv("NAMESPACE"); entry.namespace == "" { - logger.Warning.Print("NAMESPACE env not set, using default") - entry.namespace = "default" - } return entry, err } @@ -62,10 +55,6 @@ func (e Entrypoint) Client() (client cli.ClientInterface) { return e.client } -func (e Entrypoint) GetNamespace() string { - return e.namespace -} - //Resolve is a main loop which iterates through all dependencies and resolves them func (e Entrypoint) Resolve() { var wg sync.WaitGroup @@ -73,16 +62,16 @@ func (e Entrypoint) Resolve() { wg.Add(1) go func(dep Resolver) { defer wg.Done() - logger.Info.Printf("Resolving %s", dep.GetName()) + logger.Info.Printf("Resolving %v", dep) 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) + logger.Warning.Printf("Resolving dependency %s failed: %v .", dep, err) } time.Sleep(resolverSleepInterval * time.Second) } - logger.Info.Printf("Dependency %v is resolved", dep.GetName()) + logger.Info.Printf("Dependency %v is resolved.", dep) }(dep) } diff --git a/entrypoint/entrypoint_test.go b/entrypoint/entrypoint_test.go index a2bf9d8..b843522 100644 --- a/entrypoint/entrypoint_test.go +++ b/entrypoint/entrypoint_test.go @@ -34,6 +34,10 @@ func (d dummyResolver) GetName() (name string) { return d.name } +func (d dummyResolver) String() string { + return fmt.Sprintf("Dummy %s in namespace %s", d.name, d.namespace) +} + func init() { testClient = mocks.NewClient() testEntrypoint = mocks.NewEntrypointInNamespace(testNamespace) @@ -75,11 +79,6 @@ var _ = Describe("Entrypoint", func() { Expect(client).To(Equal(testClient)) }) - It("checks Namespace() method", func() { - ns := testEntrypoint.GetNamespace() - Expect(ns).To(Equal(testNamespace)) - }) - It("resolves main entrypoint with a dummy dependency", func() { defer GinkgoRecover() @@ -104,6 +103,6 @@ var _ = Describe("Entrypoint", func() { time.Sleep(5 * time.Second) stdout, _ := ioutil.ReadAll(r) - Expect(string(stdout)).To(Equal(fmt.Sprintf("%sResolving %s\n%sDependency %s is resolved\n", loggerInfoText, dummyResolverName, loggerInfoText, dummyResolverName))) + Expect(string(stdout)).To(Equal(fmt.Sprintf("%sResolving %v\n%sDependency %v is resolved.\n", loggerInfoText, dummy, loggerInfoText, dummy))) }) }) diff --git a/kubernetes-entrypoint b/kubernetes-entrypoint new file mode 100755 index 0000000..87d9f03 Binary files /dev/null and b/kubernetes-entrypoint differ diff --git a/kubernetes-entrypoint.go b/kubernetes-entrypoint.go index c9a617a..c3d4728 100644 --- a/kubernetes-entrypoint.go +++ b/kubernetes-entrypoint.go @@ -27,7 +27,7 @@ func main() { entrypoint.Resolve() - if comm = env.SplitEnvToList("COMMAND", " "); len(comm) == 0 { + if comm = env.SplitCommand(); len(comm) == 0 { // TODO(DTadrzak): we should consider other options to handle whether pod // is an init-container logger.Warning.Printf("COMMAND env is empty") diff --git a/mocks/daemonset.go b/mocks/daemonset.go index df3be41..9b7c833 100644 --- a/mocks/daemonset.go +++ b/mocks/daemonset.go @@ -41,6 +41,12 @@ func (d dClient) Get(name string) (*extensions.DaemonSet, error) { }, } + if name == CorrectDaemonsetNamespace { + ds.ObjectMeta.Namespace = CorrectDaemonset + } else if name == IncorrectDaemonsetNamespace { + return nil, fmt.Errorf("Mock daemonset didnt work") + } + return ds, nil } func (d dClient) Create(ds *extensions.DaemonSet) (*extensions.DaemonSet, error) { diff --git a/mocks/pod.go b/mocks/pod.go index b50aab9..4dc1f34 100644 --- a/mocks/pod.go +++ b/mocks/pod.go @@ -17,12 +17,15 @@ type pClient struct { } const ( - PodNotPresent = "NOT_PRESENT" - PodEmptyContainerStatuses = "EMPTY_CONTAINTER_STATUSES" - PodEnvVariableValue = "podlist" + PodNotPresent = "NOT_PRESENT" + PodEnvVariableValue = "podlist" IncorrectMatchLabel = "INCORRECT" NotReadyMatchLabel = "INCORRECT" + + CorrectDaemonsetNamespace = "CORRECT_DAEMONSET_NAMESPACE" + IncorrectDaemonsetNamespace = "INCORRECT_DAEMONSET_NAMESPACE" + CorrectDaemonset = "CORRECT_DAEMONSET" ) func (p pClient) Get(name string) (*v1.Pod, error) { diff --git a/util/env/env.go b/util/env/env.go index 521781b..2c9e28c 100644 --- a/util/env/env.go +++ b/util/env/env.go @@ -3,18 +3,70 @@ package env import ( "os" "strings" + + "github.com/stackanetes/kubernetes-entrypoint/logger" ) -func SplitEnvToList(env string, s ...string) (envList []string) { - separator := "," - if len(s) > 0 { - separator = s[0] +const ( + Separator = ":" +) + +type Dependency struct { + Name string + Namespace string +} + +func SplitCommand() []string { + command := os.Getenv("COMMAND") + if command == "" { + return []string{} } + commandList := strings.Split(command, " ") + return commandList +} + +//SplitEnvToDeps returns list of namespaces and names pairs +func SplitEnvToDeps(env string) (envList []Dependency) { + separator := "," + e := os.Getenv(env) if e == "" { return envList } - envList = strings.Split(e, separator) + envVars := strings.Split(e, separator) + namespace := GetBaseNamespace() + dep := Dependency{} + for _, envVar := range envVars { + if strings.Contains(envVar, Separator) { + nameAfterSplit := strings.Split(envVar, Separator) + if len(nameAfterSplit) != 2 { + logger.Warning.Printf("Invalid format got %s, expected namespace:name", envVar) + continue + } + if nameAfterSplit[0] == "" { + logger.Warning.Printf("Invalid format, missing namespace", envVar) + continue + } + + dep = Dependency{Name: nameAfterSplit[1], Namespace: nameAfterSplit[0]} + + } else { + dep = Dependency{Name: envVar, Namespace: namespace} + } + + envList = append(envList, dep) + + } + return envList } + +//GetBaseNamespace returns default namespace when user set empty one +func GetBaseNamespace() string { + namespace := os.Getenv("NAMESPACE") + if namespace == "" { + namespace = "default" + } + return namespace +} diff --git a/util/env/env_test.go b/util/env/env_test.go index 892a6f2..a656a82 100644 --- a/util/env/env_test.go +++ b/util/env/env_test.go @@ -6,72 +6,136 @@ import ( ) func TestSplitEnvToListWithColon(t *testing.T) { + + defer os.Unsetenv("TEST_LIST") + os.Setenv("TEST_LIST", "foo,bar") - list := SplitEnvToList("TEST_LIST") + list := SplitEnvToDeps("TEST_LIST") if list == nil { t.Errorf("Expected: not nil") } - if list[0] != "foo" { + if list[0].Name != "foo" { t.Errorf("Expected: foo got %s", list[0]) } - if list[1] != "bar" { + if list[1].Name != "bar" { t.Errorf("Expected: bar got %s", list[1]) } os.Setenv("TEST_LIST", "foo1") - list1 := SplitEnvToList("TEST_LIST") + list1 := SplitEnvToDeps("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" { + if list1[0].Name != "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") + os.Setenv("TEST_LIST", "foo:foo") + list2 := SplitEnvToDeps("TEST_LIST") + if list2[0].Name != "foo" { + t.Errorf("Expected: foo got %s", list2[0].Name) } - if list[0] != "foo" { - t.Errorf("Expected: foo got %s", list[0]) - } - if list[1] != "bar" { - t.Errorf("Expected: bar got %s", list[1]) + if list2[0].Namespace != "foo" { + t.Errorf("Expected: foo got %s", list2[0].Namespace) } - os.Setenv("TEST_LIST", "foo1") - list1 := SplitEnvToList("TEST_LIST", " ") - if list1 == nil { - t.Errorf("Expected: not nil") + os.Setenv("TEST_LIST", "bar") + list3 := SplitEnvToDeps("TEST_LIST") + if list3[0].Name != "bar" { + t.Errorf("Expected: bar got %s", list3[0].Name) } - 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]) + if list3[0].Namespace != "default" { + t.Errorf("Expected: default got %s", list3[0].Namespace) } + os.Setenv("TEST_LIST", "foo:foo1:foo2") + list4 := SplitEnvToDeps("TEST_LIST") + if len(list4) != 0 { + t.Errorf("Expected list to be empty") + } + + os.Setenv("TEST_LIST", "foo:foo1:foo2,bar") + list5 := SplitEnvToDeps("TEST_LIST") + if list5[0].Namespace != "default" { + t.Errorf("Expected: default got %s", list5[0].Namespace) + } + if list5[0].Name != "bar" { + t.Errorf("Expected: bar got %s", list5[0].Name) + } + + os.Setenv("TEST_LIST", "foo:foo1:foo2,bar:foo") + list6 := SplitEnvToDeps("TEST_LIST") + if list6[0].Namespace != "bar" { + t.Errorf("Expected: bar got %s", list6[0].Namespace) + } + if list6[0].Name != "foo" { + t.Errorf("Expected: foo got %s", list6[0].Name) + } + + os.Setenv("TEST_LIST", ":foo") + list7 := SplitEnvToDeps("TEST_LIST") + if len(list7) != 0 { + t.Errorf("Invalid format, missing namespace in pod") + } } func TestSplitEmptyEnvWithColon(t *testing.T) { + defer os.Unsetenv("TEST_LIST") os.Setenv("TEST_LIST", "") - list := SplitEnvToList("TEST_LIST") + list := SplitEnvToDeps("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) +func TestSplitCommand(t *testing.T) { + defer os.Unsetenv("COMMAND") + list2 := SplitCommand() + if len(list2) > 0 { + t.Errorf("Expected len to be 0, got %v", len(list2)) + } + os.Setenv("COMMAND", "echo test") + list := SplitCommand() + if list == nil { + t.Errorf("Expected slice, got nil") + return + } + if len(list) != 2 { + t.Errorf("Expected two elements, got %v", len(list)) + } + if list[0] != "echo" { + t.Errorf("Expected echo, got %s", list[0]) + } + if list[1] != "test" { + t.Errorf("Expected test, got %s", list[1]) + } + + os.Setenv("COMMAND", "") + list1 := SplitCommand() + if len(list1) > 0 { + t.Errorf("Expected len to be 0, got %v", len(list1)) } } + +func TestGetBaseNamespace(t *testing.T) { + defer os.Unsetenv("NAMESPACE") + os.Setenv("NAMESPACE", "") + getBaseNamespace := GetBaseNamespace() + if getBaseNamespace != "default" { + t.Errorf("Expected namespace to be default, got %v", getBaseNamespace) + } + os.Setenv("NAMESPACE", "foo") + getBaseNamespace = GetBaseNamespace() + if getBaseNamespace != "foo" { + t.Errorf("Expected namespace to be foo, got %v", getBaseNamespace) + } + os.Setenv("NAMESPACE", "default") + getBaseNamespace = GetBaseNamespace() + if getBaseNamespace != "default" { + t.Errorf("Expected namespace to be default, got %v", getBaseNamespace) + } +} diff --git a/util/util.go b/util/util.go index ba44732..291242e 100644 --- a/util/util.go +++ b/util/util.go @@ -2,6 +2,7 @@ package util import ( "fmt" + "github.com/stackanetes/kubernetes-entrypoint/util/env" "net" "os" "strings" @@ -25,3 +26,11 @@ func GetIp() (ip string, err error) { ip = strings.Split(address[0].String(), "/")[0] return } + +func ContainsSeparator(envString string, kind string) bool { + if strings.Contains(envString, env.Separator) { + fmt.Errorf("%s doesn't accept namespace: %s", kind, envString) + return true + } + return false +} diff --git a/util/util_suite_test.go b/util/util_suite_test.go new file mode 100644 index 0000000..d700067 --- /dev/null +++ b/util/util_suite_test.go @@ -0,0 +1,13 @@ +package util_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestUtil(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Util Suite") +} diff --git a/util/util_test.go b/util/util_test.go new file mode 100644 index 0000000..c42b996 --- /dev/null +++ b/util/util_test.go @@ -0,0 +1,16 @@ +package util + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const failingNamespaceUtil = "foo:util" + +var _ = Describe("Util", func() { + + It("fails on trying to resolve a socket with namespace", func() { + contains := ContainsSeparator(failingNamespaceUtil, "Util") + Expect(contains).To(Equal(true)) + }) +})