From 4197fa5a5dc6176816d46fb44a24159b3ec71602 Mon Sep 17 00:00:00 2001 From: Robert Ross Date: Thu, 1 Feb 2018 12:04:54 -0500 Subject: [PATCH 1/2] Add volume source JSON targeting --- pipeline/builder/builder.go | 4 +- pipeline/builder/kubernetes.go | 59 ++++++++++++++++++- pipeline/builder/kubernetes_test.go | 34 +++++++++++ pipeline/builder/testdata/deployment.full.yml | 20 +++++++ pipeline/builder/types/types.go | 45 +++++++++++++- test-deployment.yml | 11 ++++ 6 files changed, 166 insertions(+), 7 deletions(-) diff --git a/pipeline/builder/builder.go b/pipeline/builder/builder.go index 578cc87..4e54499 100644 --- a/pipeline/builder/builder.go +++ b/pipeline/builder/builder.go @@ -155,10 +155,10 @@ func (b *Builder) buildDeployStage(index int, s config.Stage) (*types.DeployStag Stack: s.Deploy.Stack, Strategy: s.Deploy.Strategy, TargetSize: s.Deploy.TargetSize, + VolumeSources: mg.VolumeSources, // TODO(bobbytables): allow these to be configurable - VolumeSources: []interface{}{}, - Events: []interface{}{}, + Events: []interface{}{}, InterestingHealthProviderNames: []string{"KubernetesContainer", "KubernetesPod"}, Provider: "kubernetes", CloudProvider: "kubernetes", diff --git a/pipeline/builder/kubernetes.go b/pipeline/builder/kubernetes.go index 0494118..911ca42 100644 --- a/pipeline/builder/kubernetes.go +++ b/pipeline/builder/kubernetes.go @@ -9,6 +9,7 @@ import ( "github.com/namely/k8s-pipeliner/pipeline/builder/types" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" ) @@ -54,9 +55,10 @@ const ( // ManifestGroup keeps a collection of containers from a deployment // and metadata associated with them type ManifestGroup struct { - Namespace string - Annotations map[string]string - Containers []*types.Container + Namespace string + Annotations map[string]string + Containers []*types.Container + VolumeSources []*types.VolumeSource } // ContainersFromManifest loads a kubernetes manifest file and generates @@ -96,6 +98,7 @@ func ContainersFromManifest(file string) (*ManifestGroup, error) { mg.Containers = deploymentContainers(t) mg.Annotations = t.Annotations mg.Namespace = t.Namespace + mg.VolumeSources = volumeSources(t.Spec.Template.Spec.Volumes) default: return nil, ErrUnsupportedManifest } @@ -103,8 +106,49 @@ func ContainersFromManifest(file string) (*ManifestGroup, error) { return &mg, nil } +// converts kubernetes volume sources into builder types +func volumeSources(vols []corev1.Volume) []*types.VolumeSource { + var vs []*types.VolumeSource + + for _, vol := range vols { + spinVol := &types.VolumeSource{ + Name: vol.Name, + } + + if cm := vol.ConfigMap; cm != nil { + spinVol.ConfigMap = &types.ConfigMapVolumeSource{ + ConfigMapName: cm.Name, + Items: cm.Items, + DefaultMode: cm.DefaultMode, + } + spinVol.Type = "CONFIGMAP" + } + + if sec := vol.Secret; sec != nil { + spinVol.Secret = &types.SecretVolumeSource{ + SecretName: sec.SecretName, + Items: sec.Items, + } + spinVol.Type = "SECRET" + } + + if ed := vol.EmptyDir; ed != nil { + spinVol.EmptyDir = &types.EmptyDirVolumeSource{ + // Spinnaker requires this to be uppercased for some reason + Medium: strings.ToUpper(string(ed.Medium)), + } + spinVol.Type = "EMPTYDIR" + } + + vs = append(vs, spinVol) + } + + return vs +} + func deploymentContainers(dep *appsv1.Deployment) []*types.Container { var c []*types.Container + for _, container := range dep.Spec.Template.Spec.Containers { spinContainer := &types.Container{} @@ -170,6 +214,15 @@ func deploymentContainers(dep *appsv1.Deployment) []*types.Container { spinContainer.EnvVars = append(spinContainer.EnvVars, e) } + // add all of the volume mounts + for _, vm := range container.VolumeMounts { + spinContainer.VolumeMounts = append(spinContainer.VolumeMounts, types.VolumeMount{ + Name: vm.Name, + ReadOnly: vm.ReadOnly, + MountPath: vm.MountPath, + }) + } + c = append(c, spinContainer) } diff --git a/pipeline/builder/kubernetes_test.go b/pipeline/builder/kubernetes_test.go index ca4784f..cb31485 100644 --- a/pipeline/builder/kubernetes_test.go +++ b/pipeline/builder/kubernetes_test.go @@ -22,6 +22,15 @@ func TestContainersFromManifests(t *testing.T) { assert.Len(t, group.Containers, 1) assert.Len(t, group.Annotations, 2) assert.Equal(t, "fake-namespace", group.Namespace) + + t.Run("Container VolumeMounts are copied in", func(t *testing.T) { + c := group.Containers[0] + + require.Len(t, c.VolumeMounts, 1) + assert.Equal(t, "configmap-volume", c.VolumeMounts[0].Name) + assert.Equal(t, "/thisisthemount", c.VolumeMounts[0].MountPath) + assert.Equal(t, true, c.VolumeMounts[0].ReadOnly) + }) }) t.Run("Deployments schemes are converted to latest", func(t *testing.T) { @@ -34,4 +43,29 @@ func TestContainersFromManifests(t *testing.T) { assert.Len(t, group.Annotations, 2) assert.Equal(t, "fake-namespace", group.Namespace) }) + + t.Run("Volume sources are copied", func(t *testing.T) { + file := filepath.Join(wd, "testdata", "deployment.full.yml") + group, err := builder.ContainersFromManifest(file) + require.NoError(t, err) + require.Len(t, group.VolumeSources, 3) + + t.Run("ConfigMaps are copied", func(t *testing.T) { + cms := group.VolumeSources[0] + require.NotNil(t, cms.ConfigMap) + assert.Equal(t, cms.Type, "CONFIGMAP") + }) + + t.Run("Secrets are copied", func(t *testing.T) { + sec := group.VolumeSources[1] + require.NotNil(t, sec.Secret) + assert.Equal(t, sec.Type, "SECRET") + }) + + t.Run("EmptyDirs are copied", func(t *testing.T) { + ed := group.VolumeSources[2] + require.NotNil(t, ed.EmptyDir) + assert.Equal(t, ed.Type, "EMPTYDIR") + }) + }) } diff --git a/pipeline/builder/testdata/deployment.full.yml b/pipeline/builder/testdata/deployment.full.yml index b7b7b82..d7858c9 100644 --- a/pipeline/builder/testdata/deployment.full.yml +++ b/pipeline/builder/testdata/deployment.full.yml @@ -20,3 +20,23 @@ spec: - name: WHATS_THE_WORD value: "bird is the word" image: bird.word/latest + volumeMounts: + - name: configmap-volume + mountPath: "/thisisthemount" + readOnly: true + volumes: + - name: configmap-volume + configMap: + name: "my-configmap-name" + items: + - key: "hello" + path: "/my/file/path" + - name: secret-volume + secret: + secretName: "my-secret" + items: + - key: "hello" + path: "/my/file/path" + - name: empty-volume + emptyDir: + medium: "Memory" diff --git a/pipeline/builder/types/types.go b/pipeline/builder/types/types.go index ac5ad5f..1defb60 100644 --- a/pipeline/builder/types/types.go +++ b/pipeline/builder/types/types.go @@ -1,5 +1,9 @@ package types +import ( + corev1 "k8s.io/api/core/v1" +) + // SpinnakerPipeline defines the fields for the top leve object of a spinnaker // pipeline. Mostly used for constructing JSON type SpinnakerPipeline struct { @@ -115,7 +119,7 @@ type Cluster struct { Strategy string `json:"strategy"` TargetSize int `json:"targetSize"` TerminationGracePeriodSeconds int `json:"terminationGracePeriodSeconds"` - VolumeSources []interface{} `json:"volumeSources"` + VolumeSources []*VolumeSource `json:"volumeSources"` DelayBeforeDisableSec int `json:"delayBeforeDisableSec,omitempty"` } @@ -133,7 +137,15 @@ type Container struct { Name string `json:"name"` Ports []Port `json:"ports"` - VolumeMounts []interface{} `json:"volumeMounts"` + VolumeMounts []VolumeMount `json:"volumeMounts"` +} + +// VolumeMount describes a mount that should be mounted in to the container +// by referencing a volume source in the pod spec +type VolumeMount struct { + MountPath string `json:"mountPath"` + Name string `json:"name"` + ReadOnly bool `json:"readOnly"` } // Resources for the container either as a limit or request @@ -201,3 +213,32 @@ type Notification struct { type NotificationMessage struct { Text string `json:"text"` } + +// VolumeSource defines a pod volume source that can be referenced by containers +type VolumeSource struct { + Name string `json:"name"` + Type string `json:"type"` + + EmptyDir *EmptyDirVolumeSource `json:"emptyDir,omitempty"` + ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"` + Secret *SecretVolumeSource `json:"secret,omitempty"` +} + +// EmptyDirVolumeSource defines a empty directory volume source for a pod: +// https://kubernetes.io/docs/api-reference/v1.9/#emptydirvolumesource-v1-core +type EmptyDirVolumeSource struct { + Medium string `json:"medium"` +} + +// ConfigMapVolumeSource type for referencing configmaps in volumes +type ConfigMapVolumeSource struct { + ConfigMapName string `json:"configMapName"` + DefaultMode *int32 `json:"defaultMode,omitempty"` + Items []corev1.KeyToPath `json:"items"` +} + +// SecretVolumeSource for referencing secret types in volumes +type SecretVolumeSource struct { + SecretName string `json:"secretName"` + Items []corev1.KeyToPath `json:"items"` +} diff --git a/test-deployment.yml b/test-deployment.yml index 6cc8b5e..a574abc 100644 --- a/test-deployment.yml +++ b/test-deployment.yml @@ -16,6 +16,13 @@ spec: labels: app: example spec: + volumes: + - name: configmap-volume + configMap: + name: "my-configmap-name" + items: + - key: "hello" + path: "/my/file/path" containers: - command: - bundle @@ -25,6 +32,10 @@ spec: - "80" - -c - ./config/unicorn.rb + volumeMounts: + - name: configmap-volume + mountPath: "/thisisthemount" + readOnly: true env: - name: ADMIN_PASSWORD valueFrom: From 8ed825b1e94f42309ad62f9030f206476a26a92d Mon Sep 17 00:00:00 2001 From: Robert Ross Date: Thu, 1 Feb 2018 12:06:45 -0500 Subject: [PATCH 2/2] Bump version to 0.0.4 --- cmd/k8s-pipeliner/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/k8s-pipeliner/main.go b/cmd/k8s-pipeliner/main.go index 09e76df..73fab92 100644 --- a/cmd/k8s-pipeliner/main.go +++ b/cmd/k8s-pipeliner/main.go @@ -14,7 +14,7 @@ import ( const ( // Version defines the current version of k8s-pipeliner - Version = "0.0.3" + Version = "0.0.4" ) func main() {