Files
fission-src/pkg/executor/util/merge.go
T

220 lines
6.0 KiB
Go

/*
Copyright 2016 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"errors"
"github.com/hashicorp/go-multierror"
"github.com/imdario/mergo"
apiv1 "k8s.io/api/core/v1"
)
// MergeContainerSpecs merges container specs using a predefined order.
//
// The order of the arguments indicates which spec has precedence (lower index takes precedence over higher indexes).
// Slices and maps are merged; other fields are set only if they are a zero value.
func MergeContainerSpecs(specs ...*apiv1.Container) apiv1.Container {
result := &apiv1.Container{}
for _, spec := range specs {
if spec == nil {
continue
}
err := mergo.Merge(result, spec)
if err != nil {
panic(err)
}
}
return *result
}
// mergeContainer is a specialized implementation of MergeContainerSpecs
func mergeContainer(deployContainer *apiv1.Container, containerSpec apiv1.Container) error {
if &containerSpec == nil {
return nil
}
if deployContainer.Name == containerSpec.Name {
volMap := make(map[string]*apiv1.VolumeMount)
for _, vol := range deployContainer.VolumeMounts {
volMap[vol.Name] = &vol
}
for _, specVol := range containerSpec.VolumeMounts {
_, ok := volMap[specVol.Name]
if ok {
return errors.New("Duplicate volume name found in the spec")
} else {
deployContainer.VolumeMounts = append(deployContainer.VolumeMounts, specVol)
}
}
deployContainer.Env = append(deployContainer.Env, containerSpec.Env...)
}
return nil
}
func MergePodSpec(srcPodSpec *apiv1.PodSpec, targetPodSpec *apiv1.PodSpec) error {
if &targetPodSpec == nil {
return nil
}
var multierr *multierror.Error
// Get item from spec, if they exist in deployment - merge, else append
// Same pattern for all lists (Mergo can not handle lists)
// At some point this is better done with generics/reflection?
err := mergeContainerLists(srcPodSpec, targetPodSpec)
if err != nil {
return err
}
err = mergeInitContainerList(srcPodSpec, targetPodSpec)
if err != nil {
return err
}
// For volumes - if duplicate exist, throw error
err = mergeVolumeLists(srcPodSpec, targetPodSpec)
if err != nil {
return err
}
if targetPodSpec.NodeName != "" {
srcPodSpec.NodeName = targetPodSpec.NodeName
}
if targetPodSpec.Subdomain != "" {
srcPodSpec.Subdomain = targetPodSpec.Subdomain
}
if targetPodSpec.SchedulerName != "" {
srcPodSpec.SchedulerName = targetPodSpec.SchedulerName
}
if targetPodSpec.PriorityClassName != "" {
srcPodSpec.PriorityClassName = targetPodSpec.PriorityClassName
}
if targetPodSpec.TerminationGracePeriodSeconds != nil {
srcPodSpec.TerminationGracePeriodSeconds = targetPodSpec.TerminationGracePeriodSeconds
}
//TODO - Security context should be merged instead of overriding.
if targetPodSpec.SecurityContext != nil {
srcPodSpec.SecurityContext = targetPodSpec.SecurityContext
}
//TODO - Affinity should be merged instead of overriding.
if targetPodSpec.Affinity != nil {
srcPodSpec.Affinity = targetPodSpec.Affinity
}
if targetPodSpec.Hostname != "" {
srcPodSpec.Hostname = targetPodSpec.Hostname
}
for _, obj := range targetPodSpec.ImagePullSecrets {
srcPodSpec.ImagePullSecrets = append(srcPodSpec.ImagePullSecrets, obj)
}
for _, obj := range targetPodSpec.Tolerations {
srcPodSpec.Tolerations = append(srcPodSpec.Tolerations, obj)
}
for _, obj := range targetPodSpec.HostAliases {
srcPodSpec.HostAliases = append(srcPodSpec.HostAliases, obj)
}
err = mergo.Merge(&srcPodSpec.NodeSelector, targetPodSpec.NodeSelector)
if err != nil {
multierr = multierror.Append(multierr, err)
}
return multierr.ErrorOrNil()
}
func mergeContainerLists(srcPodSpec *apiv1.PodSpec, targetPodSpec *apiv1.PodSpec) error {
targetSpecContainers := targetPodSpec.Containers
targetContainers := make(map[string]apiv1.Container)
for _, c := range targetSpecContainers {
targetContainers[c.Name] = c
}
var multierr *multierror.Error
for _, c := range srcPodSpec.Containers {
container, ok := targetContainers[c.Name]
if ok {
err := mergeContainer(&c, container)
multierr = multierror.Append(multierr, err)
delete(targetContainers, c.Name)
}
}
for _, container := range targetContainers {
srcPodSpec.Containers = append(srcPodSpec.Containers, container)
}
return multierr.ErrorOrNil()
}
func mergeInitContainerList(srcPodSpec *apiv1.PodSpec, targetPodSpec *apiv1.PodSpec) error {
targetSpecContainers := targetPodSpec.InitContainers
targetContainers := make(map[string]apiv1.Container)
for _, c := range targetSpecContainers {
targetContainers[c.Name] = c
}
var multierr *multierror.Error
for _, c := range srcPodSpec.InitContainers {
container, ok := targetContainers[c.Name]
if ok {
err := mergeContainer(&c, container)
multierr = multierror.Append(multierr, err)
delete(targetContainers, c.Name)
}
}
for _, container := range targetContainers {
srcPodSpec.InitContainers = append(srcPodSpec.InitContainers, container)
}
return multierr.ErrorOrNil()
}
func mergeVolumeLists(srcPodSpec *apiv1.PodSpec, targetPodSpec *apiv1.PodSpec) error {
volumeList := targetPodSpec.Volumes
specVolumes := make(map[string]apiv1.Volume)
for _, vol := range volumeList {
specVolumes[vol.Name] = vol
}
var multierr *multierror.Error
for _, vol := range srcPodSpec.Volumes {
_, ok := specVolumes[vol.Name]
if ok {
multierr = multierror.Append(multierr, errors.New("Duplicate volume name found in the spec"))
} else {
delete(specVolumes, vol.Name)
}
}
for _, volume := range specVolumes {
srcPodSpec.Volumes = append(srcPodSpec.Volumes, volume)
}
return multierr.ErrorOrNil()
}