Skip to content

Commit

Permalink
Use unstructured instead of v1 Deployment (#83)
Browse files Browse the repository at this point in the history
* allow individual overrides and check for null

* updated to support paramaeters

* fix merge

* fix tests
  • Loading branch information
pcriadoperez authored May 5, 2021
1 parent a768df2 commit f57d06d
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 29 deletions.
94 changes: 77 additions & 17 deletions pipeline/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ import (
"github.com/namely/k8s-pipeliner/pipeline/builder/types"
"github.com/namely/k8s-pipeliner/pipeline/config"
"github.com/pkg/errors"
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/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)

const (
Expand Down Expand Up @@ -325,30 +323,47 @@ func (b *Builder) buildDeployEmbeddedManifestStage(index int, s config.Stage) (*
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 {
// if containers in deployment set container overrides
c, _, _ := unstructured.NestedFieldNoCopy(u.Object, "spec", "template", "spec", "containers")
containers := c.([]interface{})

for i, unstructuredContainer := range containers {
container := unstructuredContainer.(map[string]interface{})
for _, overrideContainer := range maniStage.ContainerOverrides {
if specContainer.Name != overrideContainer.Name || overrideContainer.Resources == nil {
if container["name"] != overrideContainer.Name || overrideContainer.Resources == nil {
continue
}
requests, err := overrideResource(specContainer.Resources.Requests, overrideContainer.Resources.Requests)
c := (containers[i]).(map[string]interface{})
// set resources requests
requests, _, _ := unstructured.NestedFieldNoCopy(c, "resources", "requests")
requestsTyped, err := toResourceList(requests)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert resources requests to a resource list for container: %s", overrideContainer.Name)
}
requests, err = overrideResource(requestsTyped, overrideContainer.Resources.Requests)
if err != nil {
return nil, errors.Wrapf(err, errOverrideResource, "requests", overrideContainer.Name)
}
d.Spec.Template.Spec.Containers[ii].Resources.Requests = requests
limits, err := overrideResource(specContainer.Resources.Limits, overrideContainer.Resources.Limits)
if err := setNestedFieldNoCopy(c, requests, "resources", "requests"); err != nil {
return nil, errors.Wrapf(err, "failed to set resources requests for container: %s", overrideContainer.Name)
}

// set resources limits
limits, _, _ := unstructured.NestedFieldNoCopy(c, "resources", "limits")
limitsTyped, err := toResourceList(limits)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert resources limits to a resource list for container: %s", overrideContainer.Name)
}
limits, err = overrideResource(limitsTyped, overrideContainer.Resources.Limits)
if err != nil {
return nil, errors.Wrapf(err, errOverrideResource, "limits", overrideContainer.Name)
}
d.Spec.Template.Spec.Containers[ii].Resources.Limits = limits
if err := setNestedFieldNoCopy(c, limits, "resources", "limits"); err != nil {
return nil, errors.Wrapf(err, "failed to set resources requests for container: %s", overrideContainer.Name)
}
}
}
objs[i] = d.DeepCopyObject()
objs[i] = u
}

ds.Manifests = append(ds.Manifests, objs...)
Expand Down Expand Up @@ -458,17 +473,43 @@ func overrideResource(resourceList corev1.ResourceList, override *config.Resourc
if override.Memory != "" {
memoryQty, err := resource.ParseQuantity(override.Memory)
if err != nil {
return nil, errors.Wrapf(err, "could not parse memory")
return result, errors.Wrapf(err, "could not parse memory")
}
result[corev1.ResourceMemory] = memoryQty
}
if override.CPU != "" {
cpuQty, err := resource.ParseQuantity(override.CPU)
if err != nil {
return nil, errors.Wrapf(err, "could not parse memory")
return result, errors.Wrapf(err, "could not parse memory")
}
result[corev1.ResourceCPU] = cpuQty
}
return result, nil
}
func toResourceList(i interface{}) (corev1.ResourceList, error) {
result := make(corev1.ResourceList)

if i == nil {
return result, nil
}
resourceList, ok := i.(map[string]interface{})
if !ok {
return result, nil
}
if cpu, found := resourceList[corev1.ResourceCPU.String()]; found {
cpuQty, err := resource.ParseQuantity(fmt.Sprint(cpu))
if err != nil {
return result, errors.Wrapf(err, "could not parse cpu")
}
result[corev1.ResourceCPU] = cpuQty
}
if memory, found := resourceList[corev1.ResourceMemory.String()]; found {
memoryQty, err := resource.ParseQuantity(fmt.Sprint(memory))
if err != nil {
return result, errors.Wrapf(err, "could not parse memory")
}
result[corev1.ResourceMemory] = memoryQty
}
return result, nil
}

Expand Down Expand Up @@ -506,6 +547,25 @@ func (b *Builder) defaultManifestStage(index int, s config.Stage) *types.Manifes

return stage
}
func setNestedFieldNoCopy(obj map[string]interface{}, value interface{}, fields ...string) error {
m := obj

for i, field := range fields[:len(fields)-1] {
if val, ok := m[field]; ok {
if valMap, ok := val.(map[string]interface{}); ok {
m = valMap
} else {
return fmt.Errorf("value cannot be set because %v is not a map[string]interface{}", fields[:i+1])
}
} else {
newVal := make(map[string]interface{})
m[field] = newVal
m = newVal
}
}
m[fields[len(fields)-1]] = value
return nil
}

func (b *Builder) buildWebHookStage(index int, s config.Stage) (*types.Webhook, error) {
stage := &types.Webhook{
Expand Down
40 changes: 33 additions & 7 deletions pipeline/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/runtime"
)

func TestBuilderAssignsNotifications(t *testing.T) {
Expand Down Expand Up @@ -289,8 +290,13 @@ func TestBuilderPipelineStages(t *testing.T) {
require.NoError(t, err, "error generating pipeline json")

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]
manifest := spinnaker.Stages[0].(*types.ManifestStage).Manifests[0]
assert.NotNil(t, manifest)
var d v1.Deployment
u, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(manifest)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, &d)
assert.NoError(t, err)
container := d.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())
Expand Down Expand Up @@ -347,7 +353,11 @@ func TestBuilderPipelineStages(t *testing.T) {
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]
var d v1.Deployment
u, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(spinnaker.Stages[0].(*types.ManifestStage).Manifests[0])
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, &d)
require.NoError(t, err)
container := d.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())
Expand Down Expand Up @@ -379,7 +389,11 @@ func TestBuilderPipelineStages(t *testing.T) {
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]
var d v1.Deployment
u, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(spinnaker.Stages[0].(*types.ManifestStage).Manifests[0])
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, &d)
require.NoError(t, err)
container := d.Spec.Template.Spec.Containers[0]
assert.Equal(t, "100", container.Resources.Requests.Memory().String())
assert.Equal(t, "200", container.Resources.Requests.Cpu().String())
})
Expand Down Expand Up @@ -410,7 +424,11 @@ func TestBuilderPipelineStages(t *testing.T) {
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]
var d v1.Deployment
u, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(spinnaker.Stages[0].(*types.ManifestStage).Manifests[0])
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, &d)
require.NoError(t, err)
container := d.Spec.Template.Spec.Containers[0]
assert.Equal(t, "300Mi", container.Resources.Limits.Memory().String())
assert.Equal(t, "400m", container.Resources.Limits.Cpu().String())
})
Expand Down Expand Up @@ -440,7 +458,11 @@ func TestBuilderPipelineStages(t *testing.T) {
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]
var d v1.Deployment
u, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(spinnaker.Stages[0].(*types.ManifestStage).Manifests[0])
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, &d)
require.NoError(t, err)
container := d.Spec.Template.Spec.Containers[0]
assert.Equal(t, "4", container.Resources.Requests.Memory().String())
assert.Equal(t, "400m", container.Resources.Requests.Cpu().String())
})
Expand Down Expand Up @@ -470,7 +492,11 @@ func TestBuilderPipelineStages(t *testing.T) {
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[1]
var d v1.Deployment
u, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(spinnaker.Stages[0].(*types.ManifestStage).Manifests[0])
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u, &d)
require.NoError(t, err)
container := d.Spec.Template.Spec.Containers[1]
assert.Equal(t, "0", container.Resources.Requests.Memory().String())
assert.Equal(t, "400m", container.Resources.Requests.Cpu().String())
})
Expand Down
7 changes: 3 additions & 4 deletions pipeline/builder/embedded_manifests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ 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"
Expand Down Expand Up @@ -57,7 +56,7 @@ func (em *EmbeddedManifestTest) TestFilesAreBuilt() {

em.Require().Len(stg.Manifests, 1)

deploy, ok := stg.Manifests[0].(*v1.Deployment)
deploy, ok := stg.Manifests[0].(*unstructured.Unstructured)
em.Require().True(ok)
em.Equal("nginx-deployment", deploy.GetName())
}
Expand Down Expand Up @@ -248,7 +247,7 @@ func (em *EmbeddedManifestTest) TestMonikerAnnotationsAreIncluded() {
em.Equal("fake-detail", stg.Moniker.Detail)
em.Equal("fake-cluster", stg.Moniker.Cluster)

_, dok := stg.Manifests[0].(*v1.Deployment)
_, dok := stg.Manifests[0].(*unstructured.Unstructured)
em.Require().True(dok)
}

Expand Down Expand Up @@ -276,7 +275,7 @@ func (em *EmbeddedManifestTest) TestDeployEmbeddedManifestDefaultProperties() {

em.Require().Len(stg.Manifests, 1)

deploy, ok := stg.Manifests[0].(*v1.Deployment)
deploy, ok := stg.Manifests[0].(*unstructured.Unstructured)
em.Require().True(ok)
em.Equal("nginx-deployment", deploy.GetName())

Expand Down
2 changes: 1 addition & 1 deletion test-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ metadata:
labels:
app: nginx
spec:
replicas: 3
replicas: '${ #toInt(parameters.random) }'
selector:
matchLabels:
app: nginx
Expand Down

0 comments on commit f57d06d

Please sign in to comment.