From fd335f76c7c42ad6a56d3a4a9af1daf82692fc11 Mon Sep 17 00:00:00 2001 From: Robert Ross Date: Thu, 1 Feb 2018 14:13:46 -0500 Subject: [PATCH] Adds the ability to modify containers using overrides (#6) * Adds the ability to modify containers using overrides * Update version --- cmd/k8s-pipeliner/main.go | 2 +- pipeline/builder/builder.go | 94 +++++++++++++-------- pipeline/config/config.go | 22 +++++ pipeline/config/config_test.go | 3 +- pipeline/config/testdata/pipeline.full.yaml | 3 +- test-pipeline.yml | 14 +-- 6 files changed, 93 insertions(+), 45 deletions(-) diff --git a/cmd/k8s-pipeliner/main.go b/cmd/k8s-pipeliner/main.go index 73fab92..87bc556 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.4" + Version = "0.0.5" ) func main() { diff --git a/pipeline/builder/builder.go b/pipeline/builder/builder.go index 4e54499..3262d93 100644 --- a/pipeline/builder/builder.go +++ b/pipeline/builder/builder.go @@ -13,6 +13,9 @@ import ( var ( // ErrNoContainers is returned when a manifest has defined containers in it ErrNoContainers = errors.New("builder: no containers were found in given manifest file") + + // ErrOverrideContention is returned when a manifest defines multiple containers and overrides were provided + ErrOverrideContention = errors.New("builder: overrides were provided to a group that has multiple containers defined") ) // Builder constructs a spinnaker pipeline JSON from a pipeliner config @@ -128,45 +131,64 @@ func (b *Builder) buildDeployStage(index int, s config.Stage) (*types.DeployStag StageMetadata: buildStageMetadata(s, "deploy", index, b.isLinear), } - mg, err := ContainersFromManifest(s.Deploy.ManifestFile) - if err != nil { - return nil, err - } - if len(mg.Containers) == 0 { - return nil, ErrNoContainers - } + for _, group := range s.Deploy.Groups { + mg, err := ContainersFromManifest(group.ManifestFile) + if err != nil { + return nil, err + } + if len(mg.Containers) == 0 { + return nil, ErrNoContainers + } - // grab the load balancers for the deployment - var lbs []string - if l, ok := mg.Annotations[SpinnakerLoadBalancersAnnotations]; ok { - lbs = strings.Split(l, ",") - } + // check for overrides defined on the group so we can replace the containers + // values before rendering our spinnaker json. + if overrides := group.ContainerOverrides; overrides != nil { + if len(mg.Containers) > 1 { + return nil, ErrOverrideContention + } + + container := mg.Containers[0] + if overrides.Args != nil { + container.Args = overrides.Args + } + + if overrides.Command != nil { + container.Command = overrides.Command + } + } - cluster := types.Cluster{ - Account: s.Account, - Application: b.pipeline.Application, - Containers: mg.Containers, - LoadBalancers: lbs, - Region: mg.Namespace, - Namespace: mg.Namespace, - MaxRemainingAsgs: s.Deploy.MaxRemainingASGS, - ReplicaSetAnnotations: mg.Annotations, - ScaleDown: s.Deploy.ScaleDown, - Stack: s.Deploy.Stack, - Strategy: s.Deploy.Strategy, - TargetSize: s.Deploy.TargetSize, - VolumeSources: mg.VolumeSources, - - // TODO(bobbytables): allow these to be configurable - Events: []interface{}{}, - InterestingHealthProviderNames: []string{"KubernetesContainer", "KubernetesPod"}, - Provider: "kubernetes", - CloudProvider: "kubernetes", - DNSPolicy: "ClusterFirst", - TerminationGracePeriodSeconds: 30, - } + // grab the load balancers for the deployment + var lbs []string + if l, ok := mg.Annotations[SpinnakerLoadBalancersAnnotations]; ok { + lbs = strings.Split(l, ",") + } + + cluster := types.Cluster{ + Account: s.Account, + Application: b.pipeline.Application, + Containers: mg.Containers, + LoadBalancers: lbs, + Region: mg.Namespace, + Namespace: mg.Namespace, + MaxRemainingAsgs: group.MaxRemainingASGS, + ReplicaSetAnnotations: mg.Annotations, + ScaleDown: group.ScaleDown, + Stack: group.Stack, + Strategy: group.Strategy, + TargetSize: group.TargetSize, + VolumeSources: mg.VolumeSources, + + // TODO(bobbytables): allow these to be configurable + Events: []interface{}{}, + InterestingHealthProviderNames: []string{"KubernetesContainer", "KubernetesPod"}, + Provider: "kubernetes", + CloudProvider: "kubernetes", + DNSPolicy: "ClusterFirst", + TerminationGracePeriodSeconds: 30, + } - ds.Clusters = []types.Cluster{cluster} + ds.Clusters = append(ds.Clusters, cluster) + } return ds, nil } diff --git a/pipeline/config/config.go b/pipeline/config/config.go index 1d9a12d..82e7c05 100644 --- a/pipeline/config/config.go +++ b/pipeline/config/config.go @@ -84,12 +84,27 @@ type RunJobStage struct { // DeployStage is the configuration for deploying a cluster of servers (pods) type DeployStage struct { + Groups []Group `yaml:"groups"` +} + +// Group represents a group to be deployed (Think: Kubernetes Pods). Most of the configuration +// of a group is filled out by the defined manifest file. This means things like commands, env vars, +// etc, are all pulled into the group spec for you. +type Group struct { ManifestFile string `yaml:"manifestFile"` MaxRemainingASGS int `yaml:"maxRemainingASGS"` ScaleDown bool `yaml:"scaleDown"` Stack string `yaml:"stack"` Strategy string `yaml:"strategy"` TargetSize int `yaml:"targetSize"` + + // If overrides are provided, the group will run a check to make sure + // the given manifest only defines one container. If it does, the given + // overrides will be written into the spinnaker json output. + // This is useful for using the same container image, env, etc to run in a + // different mode like a queue consumer process that needs the same config, + // image, but different command. + ContainerOverrides *ContainerOverrides `yaml:"containerOverrides"` } // ManualJudgementStage is the configuration for pausing a pipeline awaiting @@ -99,3 +114,10 @@ type ManualJudgementStage struct { Instructions string `yaml:"instructions"` Inputs []string `yaml:"inputs"` } + +// ContainerOverrides are used to override a containers values for simple +// values like the command and arguments +type ContainerOverrides struct { + Args []string `yaml:"args,omitempty"` + Command []string `yaml:"command,omitempty"` +} diff --git a/pipeline/config/config_test.go b/pipeline/config/config_test.go index 3a3ff23..110e208 100644 --- a/pipeline/config/config_test.go +++ b/pipeline/config/config_test.go @@ -27,5 +27,6 @@ func TestNewConfig(t *testing.T) { assert.Equal(t, cfg.Stages[0].RunJob.ManifestFile, "manifests/deploy/connect.yml") assert.NotNil(t, cfg.Stages[0].RunJob.Container) - assert.Equal(t, cfg.Stages[1].Deploy.ManifestFile, "manifests/deploy/connect.yml") + require.Len(t, cfg.Stages[1].Deploy.Groups, 1, "no groups on deploy stage") + assert.Equal(t, cfg.Stages[1].Deploy.Groups[0].ManifestFile, "manifests/deploy/connect.yml") } diff --git a/pipeline/config/testdata/pipeline.full.yaml b/pipeline/config/testdata/pipeline.full.yaml index e59a2e7..6f6e445 100644 --- a/pipeline/config/testdata/pipeline.full.yaml +++ b/pipeline/config/testdata/pipeline.full.yaml @@ -19,4 +19,5 @@ stages: - account: int-k8s name: "Deploy INT" deploy: - manifestFile: manifests/deploy/connect.yml + groups: + - manifestFile: manifests/deploy/connect.yml diff --git a/test-pipeline.yml b/test-pipeline.yml index d155460..dcf3c7f 100644 --- a/test-pipeline.yml +++ b/test-pipeline.yml @@ -34,12 +34,14 @@ stages: reliesOn: - "1" deploy: - manifestFile: test-deployment.yml - maxRemainingASGS: 2 # total amount of replica sets to keep around afterwards before deleting - scaleDown: true - stack: web # primarily for labeling purposes on the created resources - strategy: redblack - targetSize: 2 # this is the total amount of replicas for the deployment + groups: + - manifestFile: test-deployment.yml + maxRemainingASGS: 2 # total amount of replica sets to keep around afterwards before deleting + scaleDown: true + stack: web # primarily for labeling purposes on the created resources + strategy: redblack + targetSize: 2 # this is the total amount of replicas for the deployment + containerOverrides: {} - account: int-k8s name: "Proceed to Staging?" refId: "3"