From ab18c6b844b4ef888f0bdd1c38886487e6ed5345 Mon Sep 17 00:00:00 2001 From: Pablo Criado-Perez Date: Wed, 28 Apr 2021 09:30:22 -0600 Subject: [PATCH] Add Container Overrides (#81) * added config, override, lint, added tests, removed unused code * update tests * added to readme * update to apps/v1 and updated errors * update error message --- .gitignore | 1 + Makefile | 5 + README.md | 7 + go.sum | 3 - pipeline/builder/builder.go | 257 +++++------------- pipeline/builder/builder_test.go | 42 ++- pipeline/builder/embedded_manifests_test.go | 9 +- pipeline/builder/testdata/deployment.full.yml | 7 + pipeline/config/config.go | 26 +- pipeline/config/config_test.go | 5 + pipeline/config/testdata/pipeline.full.yml | 9 + 11 files changed, 160 insertions(+), 211 deletions(-) diff --git a/.gitignore b/.gitignore index 357f67a..e7de08c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ bin/ .vscode +.idea vendor/** out dist/ diff --git a/Makefile b/Makefile index 778c8ae..c7939b8 100644 --- a/Makefile +++ b/Makefile @@ -18,3 +18,8 @@ deps: coveralls: overalls -project=github.com/namely/k8s-pipeliner -covermode=count goveralls -coverprofile=overalls.coverprofile -service=travis-ci + +.PHONY: lint +lint: + golangci-lint run --skip-dirs=vendor --skip-dirs=gen --skip-dirs=mocks --deadline=5m --tests=true -E golint \ + -E gosec -E unconvert -E goconst -E gocyclo -E goimports diff --git a/README.md b/README.md index 6ad5441..883a94f 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,13 @@ stages: configuratorFiles: - file: test-configurator.yml env: superOps + containerOverrides: + name: "container-name" + resources: + requests: + memory: "100" + cpu: "200" + ``` All of these files will be composed into a single stage deployment into the given account. This means you can deploy services and deployments in tandem together. diff --git a/go.sum b/go.sum index 1fabaee..8f77865 100644 --- a/go.sum +++ b/go.sum @@ -89,7 +89,6 @@ github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -120,7 +119,6 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= -k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.17.5 h1:QAjfgeTtSGksdkgyaPrIb4lhU16FWMIzxKejYD5S0gc= k8s.io/apimachinery v0.17.5/go.mod h1:ioIo1G/a+uONV7Tv+ZmCbMG1/a3kVw5YcDdncd8ugQ0= @@ -132,7 +130,6 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200316234421-82d701f24f9d/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= diff --git a/pipeline/builder/builder.go b/pipeline/builder/builder.go index a92d1ef..35f72b1 100644 --- a/pipeline/builder/builder.go +++ b/pipeline/builder/builder.go @@ -1,5 +1,4 @@ // Package builder implements functions used to build the JSON output - package builder import ( @@ -8,16 +7,21 @@ import ( "io/ioutil" "os" "path" - "strings" cnfgrtr "github.com/namely/k8s-configurator" "github.com/namely/k8s-pipeliner/pipeline/builder/types" "github.com/namely/k8s-pipeliner/pipeline/config" "github.com/pkg/errors" - appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/scheme" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +const ( + errParseResourceList = "unable to parse resource limits for container: %s" ) var ( @@ -312,6 +316,45 @@ func (b *Builder) buildDeployEmbeddedManifestStage(index int, s config.Stage) (* return nil, errors.Wrapf(err, "could not parse manifest file: %s", file.File) } + for i, obj := range objs { + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil, errors.New("manifest parser returned an unexpected object type") + } + if u.GetKind() != "Deployment" { + continue + } + + // if deployment set container overrides + var d v1.Deployment + err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &d) + if err != nil { + return nil, errors.Wrapf(err, "could not parse Deployment: %s", u.GetName()) + } + for ii, specContainer := range d.Spec.Template.Spec.Containers { + for _, overrideContainer := range maniStage.ContainerOverrides { + if specContainer.Name != overrideContainer.Name { + continue + } + if overrideContainer.Resources.Requests.Memory != "" || overrideContainer.Resources.Requests.CPU != "" { + requestsList, err := parseResourceList(overrideContainer.Resources.Requests.Memory, overrideContainer.Resources.Requests.CPU) + if err != nil { + return nil, errors.Wrapf(err, fmt.Sprintf(errParseResourceList, overrideContainer.Name)) + } + d.Spec.Template.Spec.Containers[ii].Resources.Requests = requestsList + } + if overrideContainer.Resources.Limits.Memory != "" || overrideContainer.Resources.Limits.CPU != "" { + limitsList, err := parseResourceList(overrideContainer.Resources.Limits.Memory, overrideContainer.Resources.Limits.CPU) + if err != nil { + return nil, errors.Wrapf(err, fmt.Sprintf(errParseResourceList, overrideContainer.Name)) + } + d.Spec.Template.Spec.Containers[ii].Resources.Limits = limitsList + } + } + } + objs[i] = d.DeepCopyObject() + } + ds.Manifests = append(ds.Manifests, objs...) } @@ -335,6 +378,9 @@ func (b *Builder) buildDeployEmbeddedManifestStage(index int, s config.Stage) (* destFileName := configuratorFile.File + "." + env destFilePath := path.Join(b.basePath, destFileName) configuredConfigMap, err := os.Create(destFilePath) + if err != nil { + return nil, errors.Wrapf(err, "k8s-configurator could not create destination file path: %s", destFilePath) + } err = cnfgrtr.Generate(file, env, configuredConfigMap) if err != nil { @@ -405,47 +451,20 @@ func (b *Builder) buildDeleteEmbeddedManifestStage(index int, s config.Stage) (* return stage, nil } -func (b *Builder) buildV2ManifestStageFromDeploy(index int, s config.Stage) (*types.ManifestStage, error) { - ds := b.defaultManifestStage(index, s) - - if len(s.Deploy.Groups) == 0 { - return nil, ErrNoDeployGroups +// parseResourceList converts memory and cpu string to a resourceList +func parseResourceList(memory string, cpu string) (corev1.ResourceList, error) { + memoryQty, err := resource.ParseQuantity(memory) + if err != nil { + return nil, errors.Wrapf(err, "could not parse memory") } - - cluster := []string{s.Account, b.pipeline.Application, s.Deploy.Groups[0].Details, s.Deploy.Groups[0].Stack} - - ds.Moniker.Cluster = strings.Join(cluster, "-") - ds.Moniker.Detail = s.Deploy.Groups[0].Details - ds.Moniker.Stack = s.Deploy.Groups[0].Stack - - parser := NewManfifestParser(b.pipeline, b.basePath) - - for _, group := range s.Deploy.Groups { - manifest, err := parser.ManifestFromScaffold(group) - - if err != nil { - return nil, err - } - - resource := &appsv1.Deployment{} - switch manifest.GetObjectKind().GroupVersionKind().Kind { - case "Deployment": - if err := scheme.Scheme.Convert(manifest, resource, nil); err != nil { - return nil, err - } - default: - return nil, ErrKubernetesAPI - } - - _, err = buildDeployment(resource, group) - if err != nil { - return nil, err - } - - ds.Manifests = append(ds.Manifests, manifest) + cpuQty, err := resource.ParseQuantity(cpu) + if err != nil { + return nil, errors.Wrapf(err, "could not parse cpu") } - - return ds, nil + return map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: memoryQty, + corev1.ResourceCPU: cpuQty, + }, nil } func (b *Builder) defaultManifestStage(index int, s config.Stage) *types.ManifestStage { @@ -483,100 +502,6 @@ func (b *Builder) defaultManifestStage(index int, s config.Stage) *types.Manifes return stage } -func (b *Builder) buildV2RunJobStage(index int, s config.Stage) (*types.ManifestStage, error) { - ds := &types.ManifestStage{ - StageMetadata: buildStageMetadata(s, "deployManifest", index, b.isLinear), - Account: s.Account, - CloudProvider: "kubernetes", - Location: "", - ManifestArtifactAccount: "embedded-artifact", - ManifestName: "", - Moniker: types.Moniker{ - App: b.pipeline.Application, - Cluster: fmt.Sprintf("%s-%s", b.pipeline.Application, s.Account), - Detail: "", - Stack: "", - }, - Relationships: types.Relationships{LoadBalancers: []interface{}{}, SecurityGroups: []interface{}{}}, - Source: "text", - } - - parser := NewManfifestParser(b.pipeline, b.basePath) - obj, err := parser.ManifestFromScaffold(s.RunJob) - - if err != nil { - return nil, err - } - - switch t := obj.(type) { - case *corev1.Pod: - if s.RunJob.Container != nil { - t.Spec.Containers[0].Command = s.RunJob.Container.Command - t.Spec.Containers[0].Args = s.RunJob.Container.Args - } - default: - return nil, ErrDeploymentJob - } - - ds.Manifests = append(ds.Manifests, obj) - - return ds, nil - -} - -func (b *Builder) buildV2DeleteManifestStage(index int, s config.Stage) (*types.DeleteManifestStage, error) { - // Set default values - completeOtherBranchesThenFail := setDefaultIfNil(s.DeleteEmbeddedManifest.CompleteOtherBranchesThenFail, false) - continuePipeline := setDefaultIfNil(s.DeleteEmbeddedManifest.ContinuePipeline, false) - failPipeline := setDefaultIfNil(s.DeleteEmbeddedManifest.FailPipeline, true) - markUnstableAsSuccessful := setDefaultIfNil(s.DeleteEmbeddedManifest.MarkUnstableAsSuccessful, false) - waitForCompletion := setDefaultIfNil(s.DeleteEmbeddedManifest.WaitForCompletion, true) - - s.Name = "Delete " + s.Name - dms := &types.DeleteManifestStage{ - StageMetadata: buildStageMetadata(s, "deleteManifest", index, b.isLinear), - Account: s.Account, - CloudProvider: "kubernetes", - Kinds: []string{"Job"}, - Location: "", - Options: types.Options{ - Cascading: true, - }, - CompleteOtherBranchesThenFail: &completeOtherBranchesThenFail, - ContinuePipeline: &continuePipeline, - FailPipeline: &failPipeline, - MarkUnstableAsSuccessful: &markUnstableAsSuccessful, - WaitForCompletion: &waitForCompletion, - } - - parser := NewManfifestParser(b.pipeline, b.basePath) - obj, err := parser.ManifestFromScaffold(s.RunJob) - - if err != nil { - return nil, err - } - - var labels map[string]string - - switch t := obj.(type) { - case metav1.Object: - labels = t.GetLabels() - namespace := t.GetNamespace() - if namespace == "" { - return nil, ErrNoNamespace - } - dms.Location = namespace - default: - return nil, ErrNoKubernetesMetadata - } - - for key, value := range labels { - dms.LabelSelectors.Selectors = append(dms.LabelSelectors.Selectors, types.Selector{Key: key, Values: []string{value}, Kind: "EQUALS"}) - } - - return dms, nil -} - func (b *Builder) buildWebHookStage(index int, s config.Stage) (*types.Webhook, error) { stage := &types.Webhook{ StageMetadata: buildStageMetadata(s, "webhook", index, b.isLinear), @@ -840,64 +765,6 @@ func buildNotifications(notifications []config.Notification) []types.Notificatio return nots } -func buildDeployment(deploy *appsv1.Deployment, group config.Group) (*appsv1.Deployment, error) { - - if len(deploy.Spec.Template.Spec.Containers) == 0 { - return nil, ErrNoContainers - } - - if overrides := group.ContainerOverrides; overrides != nil { - if len(deploy.Spec.Template.Spec.Containers) > 1 { - return nil, ErrOverrideContention - } - if overrides.Args != nil { - deploy.Spec.Template.Spec.Containers[0].Args = overrides.Args - } - if overrides.Command != nil { - deploy.Spec.Template.Spec.Containers[0].Command = overrides.Command - } - } - - if lb := group.LoadBalancers; lb != nil { - labels := make(map[string]string) - - for _, l := range lb { - labels[fmt.Sprintf(LoadBalancerFormat, l)] = "true" - } - - if deploy.Spec.Selector != nil { - for key, val := range labels { - deploy.Spec.Selector.MatchLabels[key] = val - } - } else { - deploy.Spec.Selector = &metav1.LabelSelector{ - MatchLabels: labels, - } - } - - l := deploy.ObjectMeta.GetLabels() - - if l == nil { - l = make(map[string]string) - } - - for key, val := range labels { - l[key] = val - } - - deploy.ObjectMeta.SetLabels(l) - - l = deploy.Spec.Template.GetLabels() - - for key, val := range labels { - l[key] = val - } - - deploy.Spec.Template.SetLabels(l) - } - return deploy, nil -} - func newDefaultTrue(original *bool) bool { if original == nil { return true diff --git a/pipeline/builder/builder_test.go b/pipeline/builder/builder_test.go index a36230e..7d7f631 100644 --- a/pipeline/builder/builder_test.go +++ b/pipeline/builder/builder_test.go @@ -10,6 +10,7 @@ import ( "github.com/namely/k8s-pipeliner/pipeline/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + v1 "k8s.io/api/apps/v1" ) func TestBuilderAssignsNotifications(t *testing.T) { @@ -93,8 +94,7 @@ func TestBuilderPipelineStages(t *testing.T) { assert.Equal(t, "jenkins", spinnaker.Triggers[0].(*types.JenkinsTrigger).Type) }) t.Run("JenkinsTrigger is disabled", func(t *testing.T) { - var enabled *bool - enabled = newFalse() + enabled := newFalse() pipeline := &config.Pipeline{ Triggers: []config.Trigger{ { @@ -290,6 +290,11 @@ func TestBuilderPipelineStages(t *testing.T) { assert.Equal(t, "Test DeployEmbeddedManifests Stage", spinnaker.Stages[0].(*types.ManifestStage).Name) assert.NotNil(t, spinnaker.Stages[0].(*types.ManifestStage).Manifests[0]) + container := spinnaker.Stages[0].(*types.ManifestStage).Manifests[0].(*v1.Deployment).Spec.Template.Spec.Containers[0] + assert.Equal(t, "2", container.Resources.Limits.Memory().String()) + assert.Equal(t, "1", container.Resources.Limits.Cpu().String()) + assert.Equal(t, "4", container.Resources.Requests.Memory().String()) + assert.Equal(t, "3", container.Resources.Requests.Cpu().String()) }) t.Run("Overrides default timeout", func(t *testing.T) { @@ -315,6 +320,39 @@ func TestBuilderPipelineStages(t *testing.T) { assert.Equal(t, int64(360000), spinnaker.Stages[0].(*types.ManifestStage).StageTimeoutMS) assert.Equal(t, true, spinnaker.Stages[0].(*types.ManifestStage).OverrideTimeout) }) + t.Run("Overrides container overrides", func(t *testing.T) { + pipeline := &config.Pipeline{ + Stages: []config.Stage{ + { + Name: "Test DeployEmbeddedManifests Stage", + DeployEmbeddedManifests: &config.DeployEmbeddedManifests{ + Files: []config.ManifestFile{ + { + File: file, + }, + }, + ContainerOverrides: []*config.ContainerOverrides{ + { + Name: "test-container", + Resources: &config.Resources{ + Requests: &config.Resource{Memory: "100", CPU: "200"}, + Limits: &config.Resource{Memory: "300", CPU: "400"}, + }, + }, + }, + }, + }, + }, + } + builder := builder.New(pipeline) + spinnaker, err := builder.Pipeline() + require.NoError(t, err, "error generating pipeline json") + container := spinnaker.Stages[0].(*types.ManifestStage).Manifests[0].(*v1.Deployment).Spec.Template.Spec.Containers[0] + assert.Equal(t, "300", container.Resources.Limits.Memory().String()) + assert.Equal(t, "400", container.Resources.Limits.Cpu().String()) + assert.Equal(t, "100", container.Resources.Requests.Memory().String()) + assert.Equal(t, "200", container.Resources.Requests.Cpu().String()) + }) }) t.Run("Deploy stage is parsed correctly", func(t *testing.T) { diff --git a/pipeline/builder/embedded_manifests_test.go b/pipeline/builder/embedded_manifests_test.go index 5b64f54..8b59044 100644 --- a/pipeline/builder/embedded_manifests_test.go +++ b/pipeline/builder/embedded_manifests_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/suite" + v1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/namely/k8s-pipeliner/pipeline/builder" @@ -56,10 +57,9 @@ func (em *EmbeddedManifestTest) TestFilesAreBuilt() { em.Require().Len(stg.Manifests, 1) - deploy, ok := stg.Manifests[0].(*unstructured.Unstructured) + deploy, ok := stg.Manifests[0].(*v1.Deployment) em.Require().True(ok) em.Equal("nginx-deployment", deploy.GetName()) - em.Equal("Deployment", deploy.GetKind()) } func (em *EmbeddedManifestTest) TestConfiguratorFilesNoEnv() { @@ -248,7 +248,7 @@ func (em *EmbeddedManifestTest) TestMonikerAnnotationsAreIncluded() { em.Equal("fake-detail", stg.Moniker.Detail) em.Equal("fake-cluster", stg.Moniker.Cluster) - _, dok := stg.Manifests[0].(*unstructured.Unstructured) + _, dok := stg.Manifests[0].(*v1.Deployment) em.Require().True(dok) } @@ -276,10 +276,9 @@ func (em *EmbeddedManifestTest) TestDeployEmbeddedManifestDefaultProperties() { em.Require().Len(stg.Manifests, 1) - deploy, ok := stg.Manifests[0].(*unstructured.Unstructured) + deploy, ok := stg.Manifests[0].(*v1.Deployment) em.Require().True(ok) em.Equal("nginx-deployment", deploy.GetName()) - em.Equal("Deployment", deploy.GetKind()) em.Equal(&boolf, stg.CompleteOtherBranchesThenFail) em.Equal(&boolf, stg.ContinuePipeline) diff --git a/pipeline/builder/testdata/deployment.full.yml b/pipeline/builder/testdata/deployment.full.yml index 623be73..83e139f 100644 --- a/pipeline/builder/testdata/deployment.full.yml +++ b/pipeline/builder/testdata/deployment.full.yml @@ -43,6 +43,13 @@ spec: - name: configmap-volume mountPath: "/thisisthemount" readOnly: true + resources: + limits: + cpu: "1" + memory: "2" + requests: + cpu: "3" + memory: "4" volumes: - name: configmap-volume configMap: diff --git a/pipeline/config/config.go b/pipeline/config/config.go index 83fee04..86207b0 100644 --- a/pipeline/config/config.go +++ b/pipeline/config/config.go @@ -1,5 +1,4 @@ // Package config implements YAML configuration for the k8s-pipeliner input files - package config import ( @@ -247,9 +246,10 @@ type ManifestFile struct { // DeployEmbeddedManifests is a Kubernetes V2 provider stage configuration // for deploying YAML manifest files type DeployEmbeddedManifests struct { - DefaultMoniker *Moniker `yaml:"defaultMoniker,omitempty"` - ConfiguratorFiles []ManifestFile `yaml:"configuratorFiles,omitempty"` - Files []ManifestFile `yaml:"files"` + DefaultMoniker *Moniker `yaml:"defaultMoniker,omitempty"` + ConfiguratorFiles []ManifestFile `yaml:"configuratorFiles,omitempty"` + Files []ManifestFile `yaml:"files"` + ContainerOverrides []*ContainerOverrides `yaml:"containerOverrides,omitempty"` CompleteOtherBranchesThenFail *bool `yaml:"completeOtherBranchesThenFail,omitempty"` ContinuePipeline *bool `yaml:"continuePipeline,omitempty"` @@ -296,11 +296,25 @@ type ScaleManifest struct { WaitForCompletion *bool `yaml:"waitForCompletion,omitempty"` } +// Resources represents a set of resources to use for each container +type Resources struct { + Requests *Resource `yaml:"requests,omitempty"` + Limits *Resource `yaml:"limits,omitempty"` +} + +// Resource represent the cpu and memory of a resource +type Resource struct { + Memory string `yaml:"memory,omitempty"` + CPU string `yaml:"cpu,omitempty"` +} + // 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"` + Name string `yaml:"name"` + Args []string `yaml:"args,omitempty"` + Command []string `yaml:"command,omitempty"` + Resources *Resources `yaml:"resources,omitempty"` } // PodOverrides are used to override certain attributes about a pod spec diff --git a/pipeline/config/config_test.go b/pipeline/config/config_test.go index 83eb3ef..5b0f6e3 100644 --- a/pipeline/config/config_test.go +++ b/pipeline/config/config_test.go @@ -31,6 +31,11 @@ func TestNewConfig(t *testing.T) { assert.Equal(t, stage.DeployEmbeddedManifests.Files[0].File, "manifests/nginx-deployment.yml") assert.Equal(t, stage.Name, "Deploy nginx") assert.Equal(t, stage.Account, "int-k8s") + assert.Equal(t, stage.DeployEmbeddedManifests.ContainerOverrides[0].Name, "nginx") + assert.Equal(t, stage.DeployEmbeddedManifests.ContainerOverrides[0].Resources.Requests.CPU, "100") + assert.Equal(t, stage.DeployEmbeddedManifests.ContainerOverrides[0].Resources.Requests.Memory, "200") + assert.Equal(t, stage.DeployEmbeddedManifests.ContainerOverrides[0].Resources.Limits.CPU, "300") + assert.Equal(t, stage.DeployEmbeddedManifests.ContainerOverrides[0].Resources.Limits.Memory, "400") stage2 := cfg.Stages[1] require.NotNil(t, stage2.ManualJudgement) assert.Equal(t, stage2.ManualJudgement.Timeout, 100) diff --git a/pipeline/config/testdata/pipeline.full.yml b/pipeline/config/testdata/pipeline.full.yml index 2171c86..f8697b6 100644 --- a/pipeline/config/testdata/pipeline.full.yml +++ b/pipeline/config/testdata/pipeline.full.yml @@ -11,6 +11,15 @@ stages: deployEmbeddedManifests: files: - file: manifests/nginx-deployment.yml + containerOverrides: + - name: nginx + resources: + requests: + cpu: "100" + memory: "200" + limits: + cpu: "300" + memory: "400" - account: int-k8s name: Deploy to staging-k8s? manualJudgement: