Skip to content

Commit

Permalink
Add Container Overrides (#81)
Browse files Browse the repository at this point in the history
* added config, override, lint, added tests, removed unused code

* update tests

* added to readme

* update to apps/v1 and updated errors

* update error message
  • Loading branch information
pcriadoperez authored Apr 28, 2021
1 parent a089088 commit ab18c6b
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 211 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
bin/
.vscode
.idea
vendor/**
out
dist/
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 0 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand All @@ -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=
Expand Down
257 changes: 62 additions & 195 deletions pipeline/builder/builder.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Package builder implements functions used to build the JSON output

package builder

import (
Expand All @@ -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 (
Expand Down Expand Up @@ -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...)
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit ab18c6b

Please sign in to comment.