Extend functionality of kubernetes-entrypoint with adding namespace per service option.

This commit is contained in:
Dominika Krzyszczyk 2017-09-11 00:19:50 +02:00
parent ff51e34598
commit 2590d0c013
25 changed files with 454 additions and 171 deletions

49
.gitignore vendored Normal file
View File

@ -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

View File

@ -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_<NAME>` 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.

View File

@ -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)
}

View File

@ -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())
})
})

View File

@ -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)
}

View File

@ -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() {

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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)))
})
})

View File

@ -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)
}

View File

@ -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)))
})
})

View File

@ -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)
}

View File

@ -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)))
})
})

View File

@ -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)
}

View File

@ -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)))
})
})

BIN
kubernetes-entrypoint Executable file

Binary file not shown.

View File

@ -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")

View File

@ -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) {

View File

@ -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) {

62
util/env/env.go vendored
View File

@ -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
}

128
util/env/env_test.go vendored
View File

@ -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)
}
}

View File

@ -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
}

13
util/util_suite_test.go Normal file
View File

@ -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")
}

16
util/util_test.go Normal file
View File

@ -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))
})
})