pkg/canary: add daemonset target controller

This commit is contained in:
mathetake
2020-02-23 12:25:38 +09:00
parent bf1089b204
commit a828b43463
17 changed files with 1882 additions and 323 deletions
+27 -7
View File
@@ -93,16 +93,36 @@ func (ct *ConfigTracker) getRefFromSecret(name string, namespace string) (*Confi
func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]ConfigRef, error) {
res := make(map[string]ConfigRef)
targetName := cd.Spec.TargetRef.Name
targetDep, err := ct.KubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return res, fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
var vs []corev1.Volume
var cs []corev1.Container
switch cd.Spec.TargetRef.Kind {
case "Deployment":
targetDep, err := ct.KubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return res, fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
}
return res, fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
}
return res, fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
vs = targetDep.Spec.Template.Spec.Volumes
cs = targetDep.Spec.Template.Spec.Containers
case "DaemonSet":
targetDae, err := ct.KubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return res, fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
}
return res, fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
}
vs = targetDae.Spec.Template.Spec.Volumes
cs = targetDae.Spec.Template.Spec.Containers
default:
return nil, fmt.Errorf("TargetRef.Kind invalid: %s", cd.Spec.TargetRef.Kind)
}
// scan volumes
for _, volume := range targetDep.Spec.Template.Spec.Volumes {
for _, volume := range vs {
if cmv := volume.ConfigMap; cmv != nil {
config, err := ct.getRefFromConfigMap(cmv.Name, cd.Namespace)
if err != nil {
@@ -152,7 +172,7 @@ func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]Conf
}
}
// scan containers
for _, container := range targetDep.Spec.Template.Spec.Containers {
for _, container := range cs {
// scan env
for _, env := range container.Env {
if env.ValueFrom != nil {
+222 -94
View File
@@ -7,125 +7,253 @@ import (
)
func TestConfigTracker_ConfigMaps(t *testing.T) {
mocks := newFixture()
configMap := newTestConfigMap()
configMapProjected := newTestConfigProjected()
t.Run("deployment", func(t *testing.T) {
mocks := newDeploymentFixture()
configMap := newDeploymentControllerTestConfigMap()
configMapProjected := newDeploymentControllerTestConfigProjected()
err := mocks.deployer.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
configPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name
if configPrimaryVolName != "podinfo-config-vol-primary" {
t.Errorf("Got config name %v wanted %v", configPrimaryVolName, "podinfo-config-vol-primary")
}
configPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name
if configPrimaryVolName != "podinfo-config-vol-primary" {
t.Errorf("Got config name %v wanted %v", configPrimaryVolName, "podinfo-config-vol-primary")
}
configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if configPrimary.Data["color"] != configMap.Data["color"] {
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
}
if configPrimary.Data["color"] != configMap.Data["color"] {
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
}
configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-all-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-all-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if configPrimaryEnv.Data["color"] != configMap.Data["color"] {
t.Errorf("Got ConfigMap %s wanted %s", configPrimaryEnv.Data["a"], configMap.Data["color"])
}
if configPrimaryEnv.Data["color"] != configMap.Data["color"] {
t.Errorf("Got ConfigMap %s wanted %s", configPrimaryEnv.Data["a"], configMap.Data["color"])
}
configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if configPrimaryVol.Data["color"] != configMap.Data["color"] {
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
}
if configPrimaryVol.Data["color"] != configMap.Data["color"] {
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
}
configProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name
if configProjectedName != "podinfo-config-projected-primary" {
t.Errorf("Got config name %v wanted %v", configProjectedName, "podinfo-config-projected-primary")
}
configProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name
if configProjectedName != "podinfo-config-projected-primary" {
t.Errorf("Got config name %v wanted %v", configProjectedName, "podinfo-config-projected-primary")
}
configPrimaryProjected, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
configPrimaryProjected, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if configPrimaryProjected.Data["color"] != configMapProjected.Data["color"] {
t.Errorf("Got ConfigMap color %s wanted %s", configPrimaryProjected.Data["color"], configMapProjected.Data["color"])
}
if configPrimaryProjected.Data["color"] != configMapProjected.Data["color"] {
t.Errorf("Got ConfigMap color %s wanted %s", configPrimaryProjected.Data["color"], configMapProjected.Data["color"])
}
})
t.Run("daemonset", func(t *testing.T) {
mocks := newDaemonSetFixture()
configMap := newDaemonSetControllerTestConfigMap()
configMapProjected := newDaemonSetControllerTestConfigProjected()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
configPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name
if configPrimaryVolName != "podinfo-config-vol-primary" {
t.Errorf("Got config name %v wanted %v", configPrimaryVolName, "podinfo-config-vol-primary")
}
configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if configPrimary.Data["color"] != configMap.Data["color"] {
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
}
configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-all-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if configPrimaryEnv.Data["color"] != configMap.Data["color"] {
t.Errorf("Got ConfigMap %s wanted %s", configPrimaryEnv.Data["a"], configMap.Data["color"])
}
configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if configPrimaryVol.Data["color"] != configMap.Data["color"] {
t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"])
}
configProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name
if configProjectedName != "podinfo-config-projected-primary" {
t.Errorf("Got config name %v wanted %v", configProjectedName, "podinfo-config-projected-primary")
}
configPrimaryProjected, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if configPrimaryProjected.Data["color"] != configMapProjected.Data["color"] {
t.Errorf("Got ConfigMap color %s wanted %s", configPrimaryProjected.Data["color"], configMapProjected.Data["color"])
}
})
}
func TestConfigTracker_Secrets(t *testing.T) {
mocks := newFixture()
secret := newTestSecret()
secretProjected := newTestSecretProjected()
t.Run("deployment", func(t *testing.T) {
mocks := newDeploymentFixture()
secret := newDeploymentControllerTestSecret()
secretProjected := newDeploymentControllerTestSecretProjected()
err := mocks.deployer.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
secretPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[1].VolumeSource.Secret.SecretName
if secretPrimaryVolName != "podinfo-secret-vol-primary" {
t.Errorf("Got config name %v wanted %v", secretPrimaryVolName, "podinfo-secret-vol-primary")
}
secretPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[1].VolumeSource.Secret.SecretName
if secretPrimaryVolName != "podinfo-secret-vol-primary" {
t.Errorf("Got config name %v wanted %v", secretPrimaryVolName, "podinfo-secret-vol-primary")
}
secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if string(secretPrimary.Data["apiKey"]) != string(secret.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
}
if string(secretPrimary.Data["apiKey"]) != string(secret.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
}
secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-all-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-all-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if string(secretPrimaryEnv.Data["apiKey"]) != string(secret.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
}
if string(secretPrimaryEnv.Data["apiKey"]) != string(secret.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
}
secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-vol-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-vol-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if string(secretPrimaryVol.Data["apiKey"]) != string(secret.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
}
if string(secretPrimaryVol.Data["apiKey"]) != string(secret.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
}
secretProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[1].Secret.Name
if secretProjectedName != "podinfo-secret-projected-primary" {
t.Errorf("Got config name %v wanted %v", secretProjectedName, "podinfo-secret-projected-primary")
}
secretProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[1].Secret.Name
if secretProjectedName != "podinfo-secret-projected-primary" {
t.Errorf("Got config name %v wanted %v", secretProjectedName, "podinfo-secret-projected-primary")
}
secretPrimaryProjected, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-projected-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
secretPrimaryProjected, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-projected-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if string(secretPrimaryProjected.Data["apiKey"]) != string(secretProjected.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimaryProjected.Data["apiKey"], secretProjected.Data["apiKey"])
}
if string(secretPrimaryProjected.Data["apiKey"]) != string(secretProjected.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimaryProjected.Data["apiKey"], secretProjected.Data["apiKey"])
}
})
t.Run("daemonset", func(t *testing.T) {
mocks := newDaemonSetFixture()
secret := newDaemonSetControllerTestSecret()
secretProjected := newDaemonSetControllerTestSecretProjected()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
secretPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[1].VolumeSource.Secret.SecretName
if secretPrimaryVolName != "podinfo-secret-vol-primary" {
t.Errorf("Got config name %v wanted %v", secretPrimaryVolName, "podinfo-secret-vol-primary")
}
secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if string(secretPrimary.Data["apiKey"]) != string(secret.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
}
secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-all-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if string(secretPrimaryEnv.Data["apiKey"]) != string(secret.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
}
secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-vol-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if string(secretPrimaryVol.Data["apiKey"]) != string(secret.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"])
}
secretProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[1].Secret.Name
if secretProjectedName != "podinfo-secret-projected-primary" {
t.Errorf("Got config name %v wanted %v", secretProjectedName, "podinfo-secret-projected-primary")
}
secretPrimaryProjected, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-projected-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if string(secretPrimaryProjected.Data["apiKey"]) != string(secretProjected.Data["apiKey"]) {
t.Errorf("Got primary secret %s wanted %s", secretPrimaryProjected.Data["apiKey"], secretProjected.Data["apiKey"])
}
})
}
+319
View File
@@ -0,0 +1,319 @@
package canary
import (
"fmt"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
)
var (
daemonSetScaleDownNodeSelector = map[string]string{"flux.weave.works/non-exist": "true"}
)
// DaemonSetController is managing the operations for Kubernetes DaemonSet kind
type DaemonSetController struct {
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
configTracker Tracker
labels []string
}
func (c *DaemonSetController) Scale(cd *flaggerv1.Canary, v int32) error {
// there's no concept `replicas` for DaemonSet
if v == 0 {
targetName := cd.Spec.TargetRef.Name
dae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
}
return fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
}
daeCopy := dae.DeepCopy()
daeCopy.Spec.Template.Spec.NodeSelector = make(map[string]string,
len(dae.Spec.Template.Spec.NodeSelector)+len(daemonSetScaleDownNodeSelector))
for k, v := range dae.Spec.Template.Spec.NodeSelector {
daeCopy.Spec.Template.Spec.NodeSelector[k] = v
}
for k, v := range daemonSetScaleDownNodeSelector {
daeCopy.Spec.Template.Spec.NodeSelector[k] = v
}
_, err = c.kubeClient.AppsV1().DaemonSets(dae.Namespace).Update(daeCopy)
if err != nil {
return fmt.Errorf("scaling down daemonset %s.%s failed: %v", daeCopy.GetName(), daeCopy.Namespace, err)
}
}
return nil
}
func (c *DaemonSetController) ScaleFromZero(cd *flaggerv1.Canary) error {
targetName := cd.Spec.TargetRef.Name
dep, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
}
return fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
}
depCopy := dep.DeepCopy()
for k := range daemonSetScaleDownNodeSelector {
delete(depCopy.Spec.Template.Spec.NodeSelector, k)
}
_, err = c.kubeClient.AppsV1().DaemonSets(dep.Namespace).Update(depCopy)
if err != nil {
return fmt.Errorf("scaling up daemonset %s.%s failed: %v", depCopy.GetName(), depCopy.Namespace, err)
}
return nil
}
// Initialize creates the primary DaemonSet and
// delete the canary DaemonSet and returns the pod selector label and container ports
func (c *DaemonSetController) Initialize(cd *flaggerv1.Canary, skipLivenessChecks bool) (err error) {
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
err = c.createPrimaryDaemonSet(cd)
if err != nil {
return fmt.Errorf("creating daemonset %s.%s failed: %v", primaryName, cd.Namespace, err)
}
if cd.Status.Phase == "" || cd.Status.Phase == flaggerv1.CanaryPhaseInitializing {
if !skipLivenessChecks && !cd.Spec.SkipAnalysis {
_, readyErr := c.IsPrimaryReady(cd)
if readyErr != nil {
return readyErr
}
}
// delete canary daemonset
c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("Deleting %s.%s", cd.Spec.TargetRef.Name, cd.Namespace)
if err := c.Scale(cd, 0); err != nil {
return err
}
}
return nil
}
// Promote copies the pod spec, secrets and config maps from canary to primary
func (c *DaemonSetController) Promote(cd *flaggerv1.Canary) error {
targetName := cd.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-primary", targetName)
canary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("damonset %s.%s not found", targetName, cd.Namespace)
}
return fmt.Errorf("damonset %s.%s query error %v", targetName, cd.Namespace, err)
}
label, err := c.getSelectorLabel(canary)
if err != nil {
return fmt.Errorf("invalid label selector! DaemonSet %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
targetName, cd.Namespace, targetName)
}
primary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("daemonset %s.%s not found", primaryName, cd.Namespace)
}
return fmt.Errorf("daemonset %s.%s query error %v", primaryName, cd.Namespace, err)
}
// promote secrets and config maps
configRefs, err := c.configTracker.GetTargetConfigs(cd)
if err != nil {
return err
}
if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
return err
}
primaryCopy := primary.DeepCopy()
primaryCopy.Spec.MinReadySeconds = canary.Spec.MinReadySeconds
primaryCopy.Spec.RevisionHistoryLimit = canary.Spec.RevisionHistoryLimit
primaryCopy.Spec.UpdateStrategy = canary.Spec.UpdateStrategy
// update spec with primary secrets and config maps
primaryCopy.Spec.Template.Spec = c.configTracker.ApplyPrimaryConfigs(canary.Spec.Template.Spec, configRefs)
// update pod annotations to ensure a rolling update
annotations, err := makeAnnotations(canary.Spec.Template.Annotations)
if err != nil {
return err
}
primaryCopy.Spec.Template.Annotations = annotations
primaryCopy.Spec.Template.Labels = makePrimaryLabels(canary.Spec.Template.Labels, primaryName, label)
// apply update
_, err = c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Update(primaryCopy)
if err != nil {
return fmt.Errorf("updating deployment %s.%s template spec failed: %v",
primaryCopy.GetName(), primaryCopy.Namespace, err)
}
return nil
}
// HasTargetChanged returns true if the canary DaemonSet pod spec has changed
func (c *DaemonSetController) HasTargetChanged(cd *flaggerv1.Canary) (bool, error) {
targetName := cd.Spec.TargetRef.Name
canary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return false, fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
}
return false, fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
}
return hasSpecChanged(cd, canary.Spec.Template)
}
// GetMetadata returns the pod label selector and svc ports
func (c *DaemonSetController) GetMetadata(cd *flaggerv1.Canary) (string, map[string]int32, error) {
targetName := cd.Spec.TargetRef.Name
canaryDae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return "", nil, fmt.Errorf("daemonset %s.%s not found, retrying", targetName, cd.Namespace)
}
return "", nil, err
}
label, err := c.getSelectorLabel(canaryDae)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector! DaemonSet %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
targetName, cd.Namespace, targetName)
}
var ports map[string]int32
if cd.Spec.Service.PortDiscovery {
p, err := getPorts(cd, canaryDae.Spec.Template.Spec.Containers)
if err != nil {
return "", nil, fmt.Errorf("port discovery failed with error: %v", err)
}
ports = p
}
return label, ports, nil
}
func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error {
targetName := cd.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
canaryDae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("daemonset %s.%s not found, retrying", targetName, cd.Namespace)
}
return err
}
if canaryDae.Spec.UpdateStrategy.Type != "" &&
canaryDae.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType {
return fmt.Errorf("daemonset %s.%s must have RollingUpdate strategy but have %s",
targetName, cd.Namespace, canaryDae.Spec.UpdateStrategy.Type)
}
if cd.GetProgressDeadlineSeconds() > 0 {
// (@mathetake): should we?
c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).
Infof("progressDeadlineSeconds is ignored for DaemonSet", cd.Spec.TargetRef.Name, cd.Namespace)
}
label, err := c.getSelectorLabel(canaryDae)
if err != nil {
return fmt.Errorf("invalid label selector! DaemonSet %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
targetName, cd.Namespace, targetName)
}
primaryDep, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
if errors.IsNotFound(err) {
// create primary secrets and config maps
configRefs, err := c.configTracker.GetTargetConfigs(cd)
if err != nil {
return err
}
if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
return err
}
annotations, err := makeAnnotations(canaryDae.Spec.Template.Annotations)
if err != nil {
return err
}
// create primary deployment
primaryDep = &appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Name: primaryName,
Namespace: cd.Namespace,
Labels: map[string]string{
label: primaryName,
},
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(cd, schema.GroupVersionKind{
Group: flaggerv1.SchemeGroupVersion.Group,
Version: flaggerv1.SchemeGroupVersion.Version,
Kind: flaggerv1.CanaryKind,
}),
},
},
Spec: appsv1.DaemonSetSpec{
MinReadySeconds: canaryDae.Spec.MinReadySeconds,
RevisionHistoryLimit: canaryDae.Spec.RevisionHistoryLimit,
UpdateStrategy: canaryDae.Spec.UpdateStrategy,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
label: primaryName,
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: makePrimaryLabels(canaryDae.Spec.Template.Labels, primaryName, label),
Annotations: annotations,
},
// update spec with the primary secrets and config maps
Spec: c.configTracker.ApplyPrimaryConfigs(canaryDae.Spec.Template.Spec, configRefs),
},
},
}
_, err = c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Create(primaryDep)
if err != nil {
return err
}
c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("DaemonSet %s.%s created", primaryDep.GetName(), cd.Namespace)
}
return nil
}
// getSelectorLabel returns the selector match label
func (c *DaemonSetController) getSelectorLabel(daemonSet *appsv1.DaemonSet) (string, error) {
for _, l := range c.labels {
if _, ok := daemonSet.Spec.Selector.MatchLabels[l]; ok {
return l, nil
}
}
return "", fmt.Errorf("selector not found")
}
func (c *DaemonSetController) HaveDependenciesChanged(cd *flaggerv1.Canary) (bool, error) {
return c.configTracker.HasConfigChanged(cd)
}
+267
View File
@@ -0,0 +1,267 @@
package canary
import (
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
)
func TestDaemonSetController_Sync(t *testing.T) {
mocks := newDaemonSetFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
dep := newDaemonSetControllerTestPodInfo()
primaryImage := depPrimary.Spec.Template.Spec.Containers[0].Image
sourceImage := dep.Spec.Template.Spec.Containers[0].Image
if primaryImage != sourceImage {
t.Errorf("Got image %s wanted %s", primaryImage, sourceImage)
}
}
func TestDaemonSetController_Promote(t *testing.T) {
mocks := newDaemonSetFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
dep2 := newDaemonSetControllerTestPodInfoV2()
_, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(dep2)
if err != nil {
t.Fatal(err.Error())
}
config2 := newDaemonSetControllerTestConfigMapV2()
_, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Update(config2)
if err != nil {
t.Fatal(err.Error())
}
err = mocks.controller.Promote(mocks.canary)
if err != nil {
t.Fatal(err.Error())
}
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
primaryImage := depPrimary.Spec.Template.Spec.Containers[0].Image
sourceImage := dep2.Spec.Template.Spec.Containers[0].Image
if primaryImage != sourceImage {
t.Errorf("Got image %s wanted %s", primaryImage, sourceImage)
}
configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if configPrimary.Data["color"] != config2.Data["color"] {
t.Errorf("Got primary ConfigMap color %s wanted %s", configPrimary.Data["color"], config2.Data["color"])
}
}
func TestDaemonSetController_NoConfigTracking(t *testing.T) {
mocks := newDaemonSetFixture()
mocks.controller.configTracker = &NopTracker{}
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
depPrimary, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
_, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Fatalf("Primary ConfigMap shouldn't have been created")
}
configName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name
if configName != "podinfo-config-vol" {
t.Errorf("Got config name %v wanted %v", configName, "podinfo-config-vol")
}
}
func TestDaemonSetController_HasTargetChanged(t *testing.T) {
mocks := newDaemonSetFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
// save last applied hash
canary, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
err = mocks.controller.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseInitializing})
if err != nil {
t.Fatal(err.Error())
}
// save last promoted hash
canary, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
err = mocks.controller.SetStatusPhase(canary, flaggerv1.CanaryPhaseInitialized)
if err != nil {
t.Fatal(err.Error())
}
dep, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
depClone := dep.DeepCopy()
depClone.Spec.Template.Spec.Containers[0].Resources = corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(100, resource.DecimalExponent),
},
}
// update pod spec
_, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(depClone)
if err != nil {
t.Fatal(err.Error())
}
canary, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
// detect change in last applied spec
isNew, err := mocks.controller.HasTargetChanged(canary)
if err != nil {
t.Fatal(err.Error())
}
if !isNew {
t.Errorf("Got %v wanted %v", isNew, true)
}
// save hash
err = mocks.controller.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseProgressing})
if err != nil {
t.Fatal(err.Error())
}
dep, err = mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
depClone = dep.DeepCopy()
depClone.Spec.Template.Spec.Containers[0].Resources = corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(1000, resource.DecimalExponent),
},
}
// update pod spec
_, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(depClone)
if err != nil {
t.Fatal(err.Error())
}
canary, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
// ignore change as hash should be the same with last promoted
isNew, err = mocks.controller.HasTargetChanged(canary)
if err != nil {
t.Fatal(err.Error())
}
if isNew {
t.Errorf("Got %v wanted %v", isNew, false)
}
depClone = dep.DeepCopy()
depClone.Spec.Template.Spec.Containers[0].Resources = corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(600, resource.DecimalExponent),
},
}
// update pod spec
_, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(depClone)
if err != nil {
t.Fatal(err.Error())
}
canary, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
// detect change
isNew, err = mocks.controller.HasTargetChanged(canary)
if err != nil {
t.Fatal(err.Error())
}
if !isNew {
t.Errorf("Got %v wanted %v", isNew, true)
}
}
func TestDaemonSetController_Scale(t *testing.T) {
t.Run("Scale", func(t *testing.T) {
mocks := newDaemonSetFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
err = mocks.controller.Scale(mocks.canary, 0)
c, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
for k := range daemonSetScaleDownNodeSelector {
if _, ok := c.Spec.Template.Spec.NodeSelector[k]; !ok {
t.Errorf("%s should exist in node selector", k)
}
}
})
t.Run("ScaleFromZeo", func(t *testing.T) {
mocks := newDaemonSetFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
err = mocks.controller.ScaleFromZero(mocks.canary)
c, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
for k := range daemonSetScaleDownNodeSelector {
if _, ok := c.Spec.Template.Spec.NodeSelector[k]; ok {
t.Errorf("%s should not exist in node selector", k)
}
}
})
}
+541
View File
@@ -0,0 +1,541 @@
package canary
import (
"github.com/weaveworks/flagger/pkg/logger"
"go.uber.org/zap"
appsv1 "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"
"k8s.io/client-go/kubernetes/fake"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
fakeFlagger "github.com/weaveworks/flagger/pkg/client/clientset/versioned/fake"
)
type daemonSetControllerFixture struct {
canary *flaggerv1.Canary
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
controller DaemonSetController
logger *zap.SugaredLogger
}
func newDaemonSetFixture() daemonSetControllerFixture {
// init canary
canary := newDaemonSetControllerTestCanary()
flaggerClient := fakeFlagger.NewSimpleClientset(canary)
// init kube clientset and register mock objects
kubeClient := fake.NewSimpleClientset(
newDaemonSetControllerTestPodInfo(),
newDaemonSetControllerTestConfigMap(),
newDaemonSetControllerTestConfigMapEnv(),
newDaemonSetControllerTestConfigMapVol(),
newDaemonSetControllerTestConfigProjected(),
newDaemonSetControllerTestSecret(),
newDaemonSetControllerTestSecretEnv(),
newDaemonSetControllerTestSecretVol(),
newDaemonSetControllerTestSecretProjected(),
)
logger, _ := logger.NewLogger("debug")
ctrl := DaemonSetController{
flaggerClient: flaggerClient,
kubeClient: kubeClient,
logger: logger,
labels: []string{"app", "name"},
configTracker: &ConfigTracker{
Logger: logger,
KubeClient: kubeClient,
FlaggerClient: flaggerClient,
},
}
return daemonSetControllerFixture{
canary: canary,
controller: ctrl,
logger: logger,
flaggerClient: flaggerClient,
kubeClient: kubeClient,
}
}
func newDaemonSetControllerTestConfigMap() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-config-env",
},
Data: map[string]string{
"color": "red",
},
}
}
func newDaemonSetControllerTestConfigMapV2() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-config-env",
},
Data: map[string]string{
"color": "blue",
"output": "console",
},
}
}
func newDaemonSetControllerTestConfigProjected() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-config-projected",
},
Data: map[string]string{
"color": "red",
},
}
}
func newDaemonSetControllerTestConfigMapEnv() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-config-all-env",
},
Data: map[string]string{
"color": "red",
},
}
}
func newDaemonSetControllerTestConfigMapVol() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-config-vol",
},
Data: map[string]string{
"color": "red",
},
}
}
func newDaemonSetControllerTestSecret() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-secret-env",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"apiKey": []byte("test"),
},
}
}
func newDaemonSetControllerTestSecretProjected() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-secret-projected",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"apiKey": []byte("test"),
},
}
}
func newDaemonSetControllerTestSecretEnv() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-secret-all-env",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"apiKey": []byte("test"),
},
}
}
func newDaemonSetControllerTestSecretVol() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-secret-vol",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"apiKey": []byte("test"),
},
}
}
func newDaemonSetControllerTestCanary() *flaggerv1.Canary {
cd := &flaggerv1.Canary{
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: flaggerv1.CanarySpec{
TargetRef: flaggerv1.CrossNamespaceObjectReference{
Name: "podinfo",
APIVersion: "apps/v1",
Kind: "DaemonSet",
},
},
}
return cd
}
/*func newDaemonSetControllerWithout() *appsv1.DaemonSet {
n := "nginx-without-node-selector"
d := &appsv1.DaemonSet{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: n,
},
Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"name": n,
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"name": n,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: n,
Image: "nginx",
},
},
},
},
},
}
return d
}
*/
func newDaemonSetControllerTestPodInfo() *appsv1.DaemonSet {
d := &appsv1.DaemonSet{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"name": "podinfo",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"name": "podinfo",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "podinfo",
Image: "quay.io/stefanprodan/podinfo:1.2.0",
Command: []string{
"./podinfo",
"--port=9898",
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(1000, resource.DecimalExponent),
},
},
Args: nil,
WorkingDir: "",
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 9898,
Protocol: corev1.ProtocolTCP,
},
},
Env: []corev1.EnvVar{
{
Name: "PODINFO_UI_COLOR",
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-env",
},
Key: "color",
},
},
},
{
Name: "API_KEY",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-secret-env",
},
Key: "apiKey",
},
},
},
},
EnvFrom: []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-all-env",
},
},
},
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-secret-all-env",
},
},
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "config",
MountPath: "/etc/podinfo/config",
ReadOnly: true,
},
{
Name: "secret",
MountPath: "/etc/podinfo/secret",
ReadOnly: true,
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "config",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-vol",
},
},
},
},
{
Name: "secret",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "podinfo-secret-vol",
},
},
},
{
Name: "projected",
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
Sources: []corev1.VolumeProjection{
{
ConfigMap: &corev1.ConfigMapProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-projected",
},
Items: []corev1.KeyToPath{
{
Key: "color",
Path: "my-group/my-color",
},
},
},
},
{
Secret: &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-secret-projected",
},
Items: []corev1.KeyToPath{
{
Key: "apiKey",
Path: "my-group/my-api-key",
},
},
},
},
},
},
},
},
},
},
},
},
}
return d
}
func newDaemonSetControllerTestPodInfoV2() *appsv1.DaemonSet {
d := &appsv1.DaemonSet{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"name": "podinfo",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"name": "podinfo",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "podinfo",
Image: "quay.io/stefanprodan/podinfo:1.2.1",
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 9898,
Protocol: corev1.ProtocolTCP,
},
},
Command: []string{
"./podinfo",
"--port=9898",
},
Env: []corev1.EnvVar{
{
Name: "PODINFO_UI_COLOR",
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-env",
},
Key: "color",
},
},
},
{
Name: "API_KEY",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-secret-env",
},
Key: "apiKey",
},
},
},
},
EnvFrom: []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-all-env",
},
},
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "config",
MountPath: "/etc/podinfo/config",
ReadOnly: true,
},
{
Name: "secret",
MountPath: "/etc/podinfo/secret",
ReadOnly: true,
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "config",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-vol",
},
},
},
},
{
Name: "secret",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "podinfo-secret-vol",
},
},
},
{
Name: "projected",
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
Sources: []corev1.VolumeProjection{
{
ConfigMap: &corev1.ConfigMapProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-projected",
},
Items: []corev1.KeyToPath{
{
Key: "color",
Path: "my-group/my-color",
},
},
},
},
{
Secret: &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-secret-projected",
},
Items: []corev1.KeyToPath{
{
Key: "apiKey",
Path: "my-group/my-api-key",
},
},
},
},
},
},
},
},
},
},
},
},
}
return d
}
+61
View File
@@ -0,0 +1,61 @@
package canary
import (
"fmt"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
)
// IsPrimaryReady checks the primary daemonset status and returns an error if
// the daemonset is in the middle of a rolling update
func (c *DaemonSetController) IsPrimaryReady(cd *flaggerv1.Canary) (bool, error) {
primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
primary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return true, fmt.Errorf("deployment %s.%s not found", primaryName, cd.Namespace)
}
return true, fmt.Errorf("deployment %s.%s query error %v", primaryName, cd.Namespace, err)
}
retriable, err := c.isDaemonSetReady(primary)
if err != nil {
return retriable, fmt.Errorf("halt advancement %s.%s %s", primaryName, cd.Namespace, err.Error())
}
return true, nil
}
// IsCanaryReady checks the primary daemonset and returns an error if
// the daemonset is in the middle of a rolling update
func (c *DaemonSetController) IsCanaryReady(cd *flaggerv1.Canary) (bool, error) {
targetName := cd.Spec.TargetRef.Name
canary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return true, fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
}
return true, fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
}
retriable, err := c.isDaemonSetReady(canary)
if err != nil {
return retriable, fmt.Errorf("halt advancement %s.%s %s", targetName, cd.Namespace, err.Error())
}
return true, nil
}
// isDaemonSetReady determines if a daemonset is ready by checking the number of old version daemons
func (c *DaemonSetController) isDaemonSetReady(daemonSet *appsv1.DaemonSet) (bool, error) {
if daemonSet.Generation <= daemonSet.Status.ObservedGeneration {
if diff := daemonSet.Status.DesiredNumberScheduled - daemonSet.Status.UpdatedNumberScheduled; diff > 0 {
return true, fmt.Errorf("waiting for rollout to finish: %d old daemons not replaced yet", diff)
}
} else {
return true, fmt.Errorf("waiting for rollout to finish: observed daemonset generation less then desired generation")
}
return true, nil
}
+57
View File
@@ -0,0 +1,57 @@
package canary
import (
"testing"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestDaemonSetController_IsReady(t *testing.T) {
mocks := newDaemonSetFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Error("Expected primary readiness check to fail")
}
_, err = mocks.controller.IsPrimaryReady(mocks.canary)
if err != nil {
t.Fatal(err.Error())
}
_, err = mocks.controller.IsCanaryReady(mocks.canary)
if err != nil {
t.Fatal(err.Error())
}
}
func TestDaemonSetController_isDaemonSetReady(t *testing.T) {
mocks := newDaemonSetFixture()
_, err := mocks.controller.isDaemonSetReady(&appsv1.DaemonSet{
ObjectMeta: v1.ObjectMeta{
Generation: 10,
},
Status: appsv1.DaemonSetStatus{
ObservedGeneration: 10,
DesiredNumberScheduled: 1,
UpdatedNumberScheduled: 1,
},
})
if err != nil {
t.Fatal(err.Error())
}
_, err = mocks.controller.isDaemonSetReady(&appsv1.DaemonSet{
ObjectMeta: v1.ObjectMeta{
Generation: 9,
},
Status: appsv1.DaemonSetStatus{
ObservedGeneration: 10,
DesiredNumberScheduled: 2,
UpdatedNumberScheduled: 1,
},
})
if err == nil {
t.Fatal("expected error")
}
}
+50
View File
@@ -0,0 +1,50 @@
package canary
import (
"fmt"
ex "github.com/pkg/errors"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// SyncStatus encodes the canary pod spec and updates the canary status
func (c *DaemonSetController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
dep, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(cd.Spec.TargetRef.Name, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("daemonset %s.%s not found", cd.Spec.TargetRef.Name, cd.Namespace)
}
return ex.Wrap(err, "SyncStatus daemonset query error")
}
configs, err := c.configTracker.GetConfigRefs(cd)
if err != nil {
return ex.Wrap(err, "SyncStatus configs query error")
}
return syncCanaryStatus(c.flaggerClient, cd, status, dep.Spec.Template, func(cdCopy *flaggerv1.Canary) {
cdCopy.Status.TrackedConfigs = configs
})
}
// SetStatusFailedChecks updates the canary failed checks counter
func (c *DaemonSetController) SetStatusFailedChecks(cd *flaggerv1.Canary, val int) error {
return setStatusFailedChecks(c.flaggerClient, cd, val)
}
// SetStatusWeight updates the canary status weight value
func (c *DaemonSetController) SetStatusWeight(cd *flaggerv1.Canary, val int) error {
return setStatusWeight(c.flaggerClient, cd, val)
}
// SetStatusIterations updates the canary status iterations value
func (c *DaemonSetController) SetStatusIterations(cd *flaggerv1.Canary, val int) error {
return setStatusIterations(c.flaggerClient, cd, val)
}
// SetStatusPhase updates the canary status phase
func (c *DaemonSetController) SetStatusPhase(cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
return setStatusPhase(c.flaggerClient, cd, phase)
}
+91
View File
@@ -0,0 +1,91 @@
package canary
import (
"testing"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestDaemonSetController_SyncStatus(t *testing.T) {
mocks := newDaemonSetFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
status := flaggerv1.CanaryStatus{
Phase: flaggerv1.CanaryPhaseProgressing,
FailedChecks: 2,
}
err = mocks.controller.SyncStatus(mocks.canary, status)
if err != nil {
t.Fatal(err.Error())
}
res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if res.Status.Phase != status.Phase {
t.Errorf("Got state %v wanted %v", res.Status.Phase, status.Phase)
}
if res.Status.FailedChecks != status.FailedChecks {
t.Errorf("Got failed checks %v wanted %v", res.Status.FailedChecks, status.FailedChecks)
}
if res.Status.TrackedConfigs == nil {
t.Fatalf("Status tracking configs are empty")
}
configs := *res.Status.TrackedConfigs
secret := newDaemonSetControllerTestSecret()
if _, exists := configs["secret/"+secret.GetName()]; !exists {
t.Errorf("Secret %s not found in status", secret.GetName())
}
}
func TestDaemonSetController_SetFailedChecks(t *testing.T) {
mocks := newDaemonSetFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
err = mocks.controller.SetStatusFailedChecks(mocks.canary, 1)
if err != nil {
t.Fatal(err.Error())
}
res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if res.Status.FailedChecks != 1 {
t.Errorf("Got %v wanted %v", res.Status.FailedChecks, 1)
}
}
func TestDaemonSetController_SetState(t *testing.T) {
mocks := newDaemonSetFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
err = mocks.controller.SetStatusPhase(mocks.canary, flaggerv1.CanaryPhaseProgressing)
if err != nil {
t.Fatal(err.Error())
}
res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if res.Status.Phase != flaggerv1.CanaryPhaseProgressing {
t.Errorf("Got %v wanted %v", res.Status.Phase, flaggerv1.CanaryPhaseProgressing)
}
}
+3 -97
View File
@@ -1,9 +1,7 @@
package canary
import (
"crypto/rand"
"fmt"
"io"
"github.com/google/go-cmp/cmp"
"go.uber.org/zap"
@@ -13,7 +11,6 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -107,7 +104,7 @@ func (c *DeploymentController) Promote(cd *flaggerv1.Canary) error {
primaryCopy.Spec.Template.Spec = c.configTracker.ApplyPrimaryConfigs(canary.Spec.Template.Spec, configRefs)
// update pod annotations to ensure a rolling update
annotations, err := c.makeAnnotations(canary.Spec.Template.Annotations)
annotations, err := makeAnnotations(canary.Spec.Template.Annotations)
if err != nil {
return err
}
@@ -211,7 +208,7 @@ func (c *DeploymentController) GetMetadata(cd *flaggerv1.Canary) (string, map[st
var ports map[string]int32
if cd.Spec.Service.PortDiscovery {
p, err := c.getPorts(cd, canaryDep)
p, err := getPorts(cd, canaryDep.Spec.Template.Spec.Containers)
if err != nil {
return "", nil, fmt.Errorf("port discovery failed with error: %v", err)
}
@@ -248,7 +245,7 @@ func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) err
if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
return err
}
annotations, err := c.makeAnnotations(canaryDep.Spec.Template.Annotations)
annotations, err := makeAnnotations(canaryDep.Spec.Template.Annotations)
if err != nil {
return err
}
@@ -383,29 +380,6 @@ func (c *DeploymentController) reconcilePrimaryHpa(cd *flaggerv1.Canary, init bo
return nil
}
// makeAnnotations appends an unique ID to annotations map
func (c *DeploymentController) makeAnnotations(annotations map[string]string) (map[string]string, error) {
idKey := "flagger-id"
res := make(map[string]string)
uuid := make([]byte, 16)
n, err := io.ReadFull(rand.Reader, uuid)
if n != len(uuid) || err != nil {
return res, err
}
uuid[8] = uuid[8]&^0xc0 | 0x80
uuid[6] = uuid[6]&^0xf0 | 0x40
id := fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
for k, v := range annotations {
if k != idKey {
res[k] = v
}
}
res[idKey] = id
return res, nil
}
// getSelectorLabel returns the selector match label
func (c *DeploymentController) getSelectorLabel(deployment *appsv1.Deployment) (string, error) {
for _, l := range c.labels {
@@ -417,74 +391,6 @@ func (c *DeploymentController) getSelectorLabel(deployment *appsv1.Deployment) (
return "", fmt.Errorf("selector not found")
}
var sidecars = map[string]bool{
"istio-proxy": true,
"envoy": true,
}
func (c *DeploymentController) HaveDependenciesChanged(cd *flaggerv1.Canary) (bool, error) {
return c.configTracker.HasConfigChanged(cd)
}
// getPorts returns a list of all container ports
func (c *DeploymentController) getPorts(cd *flaggerv1.Canary, deployment *appsv1.Deployment) (map[string]int32, error) {
ports := make(map[string]int32)
for _, container := range deployment.Spec.Template.Spec.Containers {
// exclude service mesh proxies based on container name
if _, ok := sidecars[container.Name]; ok {
continue
}
for i, p := range container.Ports {
// exclude canary.service.port or canary.service.targetPort
if cd.Spec.Service.TargetPort.String() == "0" {
if p.ContainerPort == cd.Spec.Service.Port {
continue
}
} else {
if cd.Spec.Service.TargetPort.Type == intstr.Int {
if p.ContainerPort == cd.Spec.Service.TargetPort.IntVal {
continue
}
}
if cd.Spec.Service.TargetPort.Type == intstr.String {
if p.Name == cd.Spec.Service.TargetPort.StrVal {
continue
}
}
}
name := fmt.Sprintf("tcp-%s-%v", container.Name, i)
if p.Name != "" {
name = p.Name
}
ports[name] = p.ContainerPort
}
}
return ports, nil
}
func makePrimaryLabels(labels map[string]string, primaryName string, label string) map[string]string {
res := make(map[string]string)
for k, v := range labels {
if k != label {
res[k] = v
}
}
res[label] = primaryName
return res
}
func int32p(i int32) *int32 {
return &i
}
func int32Default(i *int32) int32 {
if i == nil {
return 1
}
return *i
}
+45 -45
View File
@@ -11,9 +11,9 @@ import (
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
)
func TestCanaryDeployer_Sync(t *testing.T) {
mocks := newFixture()
err := mocks.deployer.Initialize(mocks.canary, true)
func TestDeploymentController_Sync(t *testing.T) {
mocks := newDeploymentFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
@@ -23,7 +23,7 @@ func TestCanaryDeployer_Sync(t *testing.T) {
t.Fatal(err.Error())
}
dep := newTestDeployment()
dep := newDeploymentControllerTest()
primaryImage := depPrimary.Spec.Template.Spec.Containers[0].Image
sourceImage := dep.Spec.Template.Spec.Containers[0].Image
@@ -41,20 +41,20 @@ func TestCanaryDeployer_Sync(t *testing.T) {
}
}
func TestCanaryDeployer_Promote(t *testing.T) {
mocks := newFixture()
err := mocks.deployer.Initialize(mocks.canary, true)
func TestDeploymentController_Promote(t *testing.T) {
mocks := newDeploymentFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
dep2 := newTestDeploymentV2()
dep2 := newDeploymentControllerTestV2()
_, err = mocks.kubeClient.AppsV1().Deployments("default").Update(dep2)
if err != nil {
t.Fatal(err.Error())
}
config2 := NewTestConfigMapV2()
config2 := newDeploymentControllerTestConfigMapV2()
_, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Update(config2)
if err != nil {
t.Fatal(err.Error())
@@ -72,7 +72,7 @@ func TestCanaryDeployer_Promote(t *testing.T) {
t.Fatal(err.Error())
}
err = mocks.deployer.Promote(mocks.canary)
err = mocks.controller.Promote(mocks.canary)
if err != nil {
t.Fatal(err.Error())
}
@@ -107,32 +107,32 @@ func TestCanaryDeployer_Promote(t *testing.T) {
}
}
func TestCanaryDeployer_IsReady(t *testing.T) {
mocks := newFixture()
err := mocks.deployer.Initialize(mocks.canary, true)
func TestDeploymentController_IsReady(t *testing.T) {
mocks := newDeploymentFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Error("Expected primary readiness check to fail")
}
_, err = mocks.deployer.IsPrimaryReady(mocks.canary)
_, err = mocks.controller.IsPrimaryReady(mocks.canary)
if err == nil {
t.Fatal(err.Error())
}
_, err = mocks.deployer.IsCanaryReady(mocks.canary)
_, err = mocks.controller.IsCanaryReady(mocks.canary)
if err != nil {
t.Fatal(err.Error())
}
}
func TestCanaryDeployer_SetFailedChecks(t *testing.T) {
mocks := newFixture()
err := mocks.deployer.Initialize(mocks.canary, true)
func TestDeploymentController_SetFailedChecks(t *testing.T) {
mocks := newDeploymentFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
err = mocks.deployer.SetStatusFailedChecks(mocks.canary, 1)
err = mocks.controller.SetStatusFailedChecks(mocks.canary, 1)
if err != nil {
t.Fatal(err.Error())
}
@@ -147,14 +147,14 @@ func TestCanaryDeployer_SetFailedChecks(t *testing.T) {
}
}
func TestCanaryDeployer_SetState(t *testing.T) {
mocks := newFixture()
err := mocks.deployer.Initialize(mocks.canary, true)
func TestDeploymentController_SetState(t *testing.T) {
mocks := newDeploymentFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
err = mocks.deployer.SetStatusPhase(mocks.canary, flaggerv1.CanaryPhaseProgressing)
err = mocks.controller.SetStatusPhase(mocks.canary, flaggerv1.CanaryPhaseProgressing)
if err != nil {
t.Fatal(err.Error())
}
@@ -169,9 +169,9 @@ func TestCanaryDeployer_SetState(t *testing.T) {
}
}
func TestCanaryDeployer_SyncStatus(t *testing.T) {
mocks := newFixture()
err := mocks.deployer.Initialize(mocks.canary, true)
func TestDeploymentController_SyncStatus(t *testing.T) {
mocks := newDeploymentFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
@@ -180,7 +180,7 @@ func TestCanaryDeployer_SyncStatus(t *testing.T) {
Phase: flaggerv1.CanaryPhaseProgressing,
FailedChecks: 2,
}
err = mocks.deployer.SyncStatus(mocks.canary, status)
err = mocks.controller.SyncStatus(mocks.canary, status)
if err != nil {
t.Fatal(err.Error())
}
@@ -202,20 +202,20 @@ func TestCanaryDeployer_SyncStatus(t *testing.T) {
t.Fatalf("Status tracking configs are empty")
}
configs := *res.Status.TrackedConfigs
secret := newTestSecret()
secret := newDeploymentControllerTestSecret()
if _, exists := configs["secret/"+secret.GetName()]; !exists {
t.Errorf("Secret %s not found in status", secret.GetName())
}
}
func TestCanaryDeployer_Scale(t *testing.T) {
mocks := newFixture()
err := mocks.deployer.Initialize(mocks.canary, true)
func TestDeploymentController_Scale(t *testing.T) {
mocks := newDeploymentFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
err = mocks.deployer.Scale(mocks.canary, 2)
err = mocks.controller.Scale(mocks.canary, 2)
c, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
@@ -227,11 +227,11 @@ func TestCanaryDeployer_Scale(t *testing.T) {
}
}
func TestCanaryDeployer_NoConfigTracking(t *testing.T) {
mocks := newFixture()
mocks.deployer.configTracker = &NopTracker{}
func TestDeploymentController_NoConfigTracking(t *testing.T) {
mocks := newDeploymentFixture()
mocks.controller.configTracker = &NopTracker{}
err := mocks.deployer.Initialize(mocks.canary, true)
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
@@ -252,9 +252,9 @@ func TestCanaryDeployer_NoConfigTracking(t *testing.T) {
}
}
func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
mocks := newFixture()
err := mocks.deployer.Initialize(mocks.canary, true)
func TestDeploymentController_HasTargetChanged(t *testing.T) {
mocks := newDeploymentFixture()
err := mocks.controller.Initialize(mocks.canary, true)
if err != nil {
t.Fatal(err.Error())
}
@@ -264,7 +264,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
if err != nil {
t.Fatal(err.Error())
}
err = mocks.deployer.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseInitializing})
err = mocks.controller.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseInitializing})
if err != nil {
t.Fatal(err.Error())
}
@@ -274,7 +274,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
if err != nil {
t.Fatal(err.Error())
}
err = mocks.deployer.SetStatusPhase(canary, flaggerv1.CanaryPhaseInitialized)
err = mocks.controller.SetStatusPhase(canary, flaggerv1.CanaryPhaseInitialized)
if err != nil {
t.Fatal(err.Error())
}
@@ -303,7 +303,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
}
// detect change in last applied spec
isNew, err := mocks.deployer.HasTargetChanged(canary)
isNew, err := mocks.controller.HasTargetChanged(canary)
if err != nil {
t.Fatal(err.Error())
}
@@ -312,7 +312,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
}
// save hash
err = mocks.deployer.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseProgressing})
err = mocks.controller.SyncStatus(canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseProgressing})
if err != nil {
t.Fatal(err.Error())
}
@@ -341,7 +341,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
}
// ignore change as hash should be the same with last promoted
isNew, err = mocks.deployer.HasTargetChanged(canary)
isNew, err = mocks.controller.HasTargetChanged(canary)
if err != nil {
t.Fatal(err.Error())
}
@@ -368,7 +368,7 @@ func TestCanaryDeployer_HasTargetChanged(t *testing.T) {
}
// detect change
isNew, err = mocks.deployer.HasTargetChanged(canary)
isNew, err = mocks.controller.HasTargetChanged(canary)
if err != nil {
t.Fatal(err.Error())
}
@@ -16,36 +16,36 @@ import (
fakeFlagger "github.com/weaveworks/flagger/pkg/client/clientset/versioned/fake"
)
type fixture struct {
type deploymentControllerFixture struct {
canary *flaggerv1.Canary
kubeClient kubernetes.Interface
flaggerClient clientset.Interface
deployer DeploymentController
controller DeploymentController
logger *zap.SugaredLogger
}
func newFixture() fixture {
func newDeploymentFixture() deploymentControllerFixture {
// init canary
canary := newTestCanary()
canary := newDeploymentControllerTestCanary()
flaggerClient := fakeFlagger.NewSimpleClientset(canary)
// init kube clientset and register mock objects
kubeClient := fake.NewSimpleClientset(
newTestDeployment(),
newTestHPA(),
newTestConfigMap(),
newTestConfigMapEnv(),
newTestConfigMapVol(),
newTestConfigProjected(),
newTestSecret(),
newTestSecretEnv(),
newTestSecretVol(),
newTestSecretProjected(),
newDeploymentControllerTest(),
newDeploymentControllerTestHPA(),
newDeploymentControllerTestConfigMap(),
newDeploymentControllerTestConfigMapEnv(),
newDeploymentControllerTestConfigMapVol(),
newDeploymentControllerTestConfigProjected(),
newDeploymentControllerTestSecret(),
newDeploymentControllerTestSecretEnv(),
newDeploymentControllerTestSecretVol(),
newDeploymentControllerTestSecretProjected(),
)
logger, _ := logger.NewLogger("debug")
deployer := DeploymentController{
ctrl := DeploymentController{
flaggerClient: flaggerClient,
kubeClient: kubeClient,
logger: logger,
@@ -57,16 +57,16 @@ func newFixture() fixture {
},
}
return fixture{
return deploymentControllerFixture{
canary: canary,
deployer: deployer,
controller: ctrl,
logger: logger,
flaggerClient: flaggerClient,
kubeClient: kubeClient,
}
}
func newTestConfigMap() *corev1.ConfigMap {
func newDeploymentControllerTestConfigMap() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -79,7 +79,7 @@ func newTestConfigMap() *corev1.ConfigMap {
}
}
func NewTestConfigMapV2() *corev1.ConfigMap {
func newDeploymentControllerTestConfigMapV2() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -93,7 +93,7 @@ func NewTestConfigMapV2() *corev1.ConfigMap {
}
}
func newTestConfigProjected() *corev1.ConfigMap {
func newDeploymentControllerTestConfigProjected() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -106,7 +106,7 @@ func newTestConfigProjected() *corev1.ConfigMap {
}
}
func newTestConfigMapEnv() *corev1.ConfigMap {
func newDeploymentControllerTestConfigMapEnv() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -119,7 +119,7 @@ func newTestConfigMapEnv() *corev1.ConfigMap {
}
}
func newTestConfigMapVol() *corev1.ConfigMap {
func newDeploymentControllerTestConfigMapVol() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -132,7 +132,7 @@ func newTestConfigMapVol() *corev1.ConfigMap {
}
}
func newTestSecret() *corev1.Secret {
func newDeploymentControllerTestSecret() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -146,7 +146,7 @@ func newTestSecret() *corev1.Secret {
}
}
func newTestSecretProjected() *corev1.Secret {
func newDeploymentControllerTestSecretProjected() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -160,7 +160,7 @@ func newTestSecretProjected() *corev1.Secret {
}
}
func newTestSecretEnv() *corev1.Secret {
func newDeploymentControllerTestSecretEnv() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -174,7 +174,7 @@ func newTestSecretEnv() *corev1.Secret {
}
}
func newTestSecretVol() *corev1.Secret {
func newDeploymentControllerTestSecretVol() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -188,7 +188,7 @@ func newTestSecretVol() *corev1.Secret {
}
}
func newTestCanary() *flaggerv1.Canary {
func newDeploymentControllerTestCanary() *flaggerv1.Canary {
cd := &flaggerv1.Canary{
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -217,7 +217,7 @@ func newTestCanary() *flaggerv1.Canary {
return cd
}
func newTestDeployment() *appsv1.Deployment {
func newDeploymentControllerTest() *appsv1.Deployment {
d := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -376,7 +376,7 @@ func newTestDeployment() *appsv1.Deployment {
return d
}
func newTestDeploymentV2() *appsv1.Deployment {
func newDeploymentControllerTestV2() *appsv1.Deployment {
d := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -521,7 +521,7 @@ func newTestDeploymentV2() *appsv1.Deployment {
return d
}
func newTestHPA() *hpav2.HorizontalPodAutoscaler {
func newDeploymentControllerTestHPA() *hpav2.HorizontalPodAutoscaler {
h := &hpav2.HorizontalPodAutoscaler{
TypeMeta: metav1.TypeMeta{APIVersion: hpav2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
@@ -36,7 +36,7 @@ func (c *DeploymentController) IsPrimaryReady(cd *flaggerv1.Canary) (bool, error
return true, nil
}
// IsCanaryReady checks the primary deployment status and returns an error if
// IsCanaryReady checks the canary deployment status and returns an error if
// the deployment is in the middle of a rolling update or if the pods are unhealthy
// it will return a non retriable error if the rolling update is stuck
func (c *DeploymentController) IsCanaryReady(cd *flaggerv1.Canary) (bool, error) {
+51
View File
@@ -0,0 +1,51 @@
package canary
import (
"fmt"
ex "github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
)
// SyncStatus encodes the canary pod spec and updates the canary status
func (c *DeploymentController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
dep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(cd.Spec.TargetRef.Name, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("deployment %s.%s not found", cd.Spec.TargetRef.Name, cd.Namespace)
}
return ex.Wrap(err, "SyncStatus deployment query error")
}
configs, err := c.configTracker.GetConfigRefs(cd)
if err != nil {
return ex.Wrap(err, "SyncStatus configs query error")
}
return syncCanaryStatus(c.flaggerClient, cd, status, dep.Spec.Template, func(cdCopy *flaggerv1.Canary) {
cdCopy.Status.TrackedConfigs = configs
})
}
// SetStatusFailedChecks updates the canary failed checks counter
func (c *DeploymentController) SetStatusFailedChecks(cd *flaggerv1.Canary, val int) error {
return setStatusFailedChecks(c.flaggerClient, cd, val)
}
// SetStatusWeight updates the canary status weight value
func (c *DeploymentController) SetStatusWeight(cd *flaggerv1.Canary, val int) error {
return setStatusWeight(c.flaggerClient, cd, val)
}
// SetStatusIterations updates the canary status iterations value
func (c *DeploymentController) SetStatusIterations(cd *flaggerv1.Canary, val int) error {
return setStatusIterations(c.flaggerClient, cd, val)
}
// SetStatusPhase updates the canary status phase
func (c *DeploymentController) SetStatusPhase(cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
return setStatusPhase(c.flaggerClient, cd, phase)
}
+9
View File
@@ -37,6 +37,13 @@ func (factory *Factory) Controller(kind string) Controller {
labels: factory.labels,
configTracker: factory.configTracker,
}
daemonSetCtrl := &DaemonSetController{
logger: factory.logger,
kubeClient: factory.kubeClient,
flaggerClient: factory.flaggerClient,
labels: factory.labels,
configTracker: factory.configTracker,
}
serviceCtrl := &ServiceController{
logger: factory.logger,
kubeClient: factory.kubeClient,
@@ -44,6 +51,8 @@ func (factory *Factory) Controller(kind string) Controller {
}
switch {
case kind == "DaemonSet":
return daemonSetCtrl
case kind == "Deployment":
return deploymentCtrl
case kind == "Service":
+8 -49
View File
@@ -6,7 +6,6 @@ import (
ex "github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/retry"
@@ -14,26 +13,6 @@ import (
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
)
// SyncStatus encodes the canary pod spec and updates the canary status
func (c *DeploymentController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
dep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(cd.Spec.TargetRef.Name, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("deployment %s.%s not found", cd.Spec.TargetRef.Name, cd.Namespace)
}
return ex.Wrap(err, "SyncStatus deployment query error")
}
configs, err := c.configTracker.GetConfigRefs(cd)
if err != nil {
return ex.Wrap(err, "SyncStatus configs query error")
}
return syncCanaryStatus(c.flaggerClient, cd, status, dep.Spec.Template, func(cdCopy *flaggerv1.Canary) {
cdCopy.Status.TrackedConfigs = configs
})
}
func syncCanaryStatus(flaggerClient clientset.Interface, cd *flaggerv1.Canary, status flaggerv1.CanaryStatus, canaryResource interface{}, setAll func(cdCopy *flaggerv1.Canary)) error {
hash := computeHash(canaryResource)
@@ -56,7 +35,7 @@ func syncCanaryStatus(flaggerClient clientset.Interface, cd *flaggerv1.Canary, s
cdCopy.Status.LastTransitionTime = metav1.Now()
setAll(cdCopy)
if ok, conditions := MakeStatusConditions(cd.Status, status.Phase); ok {
if ok, conditions := MakeStatusConditions(cd, status.Phase); ok {
cdCopy.Status.Conditions = conditions
}
@@ -70,11 +49,6 @@ func syncCanaryStatus(flaggerClient clientset.Interface, cd *flaggerv1.Canary, s
return nil
}
// SetStatusFailedChecks updates the canary failed checks counter
func (c *DeploymentController) SetStatusFailedChecks(cd *flaggerv1.Canary, val int) error {
return setStatusFailedChecks(c.flaggerClient, cd, val)
}
func setStatusFailedChecks(flaggerClient clientset.Interface, cd *flaggerv1.Canary, val int) error {
firstTry := true
err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
@@ -99,11 +73,6 @@ func setStatusFailedChecks(flaggerClient clientset.Interface, cd *flaggerv1.Cana
return nil
}
// SetStatusWeight updates the canary status weight value
func (c *DeploymentController) SetStatusWeight(cd *flaggerv1.Canary, val int) error {
return setStatusWeight(c.flaggerClient, cd, val)
}
func setStatusWeight(flaggerClient clientset.Interface, cd *flaggerv1.Canary, val int) error {
firstTry := true
err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
@@ -128,11 +97,6 @@ func setStatusWeight(flaggerClient clientset.Interface, cd *flaggerv1.Canary, va
return nil
}
// SetStatusIterations updates the canary status iterations value
func (c *DeploymentController) SetStatusIterations(cd *flaggerv1.Canary, val int) error {
return setStatusIterations(c.flaggerClient, cd, val)
}
func setStatusIterations(flaggerClient clientset.Interface, cd *flaggerv1.Canary, val int) error {
firstTry := true
err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
@@ -159,11 +123,6 @@ func setStatusIterations(flaggerClient clientset.Interface, cd *flaggerv1.Canary
return nil
}
// SetStatusPhase updates the canary status phase
func (c *DeploymentController) SetStatusPhase(cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
return setStatusPhase(c.flaggerClient, cd, phase)
}
func setStatusPhase(flaggerClient clientset.Interface, cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
firstTry := true
err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
@@ -189,7 +148,7 @@ func setStatusPhase(flaggerClient clientset.Interface, cd *flaggerv1.Canary, pha
cdCopy.Status.LastPromotedSpec = cd.Status.LastAppliedSpec
}
if ok, conditions := MakeStatusConditions(cdCopy.Status, phase); ok {
if ok, conditions := MakeStatusConditions(cdCopy, phase); ok {
cdCopy.Status.Conditions = conditions
}
@@ -215,19 +174,19 @@ func getStatusCondition(status flaggerv1.CanaryStatus, conditionType flaggerv1.C
}
// MakeStatusCondition updates the canary status conditions based on canary phase
func MakeStatusConditions(canaryStatus flaggerv1.CanaryStatus,
func MakeStatusConditions(cd *flaggerv1.Canary,
phase flaggerv1.CanaryPhase) (bool, []flaggerv1.CanaryCondition) {
currentCondition := getStatusCondition(canaryStatus, flaggerv1.PromotedType)
currentCondition := getStatusCondition(cd.Status, flaggerv1.PromotedType)
message := "New deployment detected, starting initialization."
message := fmt.Sprintf("New %s detected, starting initialization.", cd.Spec.TargetRef.Kind)
status := corev1.ConditionUnknown
switch phase {
case flaggerv1.CanaryPhaseInitializing:
status = corev1.ConditionUnknown
message = "New deployment detected, starting initialization."
message = fmt.Sprintf("New %s detected, starting initialization.", cd.Spec.TargetRef.Kind)
case flaggerv1.CanaryPhaseInitialized:
status = corev1.ConditionTrue
message = "Deployment initialization completed."
message = fmt.Sprintf("%s initialization completed.", cd.Spec.TargetRef.Kind)
case flaggerv1.CanaryPhaseWaiting:
status = corev1.ConditionUnknown
message = "Waiting for approval."
@@ -245,7 +204,7 @@ func MakeStatusConditions(canaryStatus flaggerv1.CanaryStatus,
message = "Canary analysis completed successfully, promotion finished."
case flaggerv1.CanaryPhaseFailed:
status = corev1.ConditionFalse
message = "Canary analysis failed, deployment scaled to zero."
message = fmt.Sprintf("Canary analysis failed, %s scaled to zero.", cd.Spec.TargetRef.Kind)
}
newCondition := &flaggerv1.CanaryCondition{
+100
View File
@@ -0,0 +1,100 @@
package canary
import (
"crypto/rand"
"fmt"
"io"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
)
var sidecars = map[string]bool{
"istio-proxy": true,
"envoy": true,
}
func getPorts(cd *flaggerv1.Canary, cs []corev1.Container) (map[string]int32, error) {
ports := make(map[string]int32, len(cs))
for _, container := range cs {
// exclude service mesh proxies based on container name
if _, ok := sidecars[container.Name]; ok {
continue
}
for i, p := range container.Ports {
// exclude canary.service.port or canary.service.targetPort
if cd.Spec.Service.TargetPort.String() == "0" {
if p.ContainerPort == cd.Spec.Service.Port {
continue
}
} else {
if cd.Spec.Service.TargetPort.Type == intstr.Int {
if p.ContainerPort == cd.Spec.Service.TargetPort.IntVal {
continue
}
}
if cd.Spec.Service.TargetPort.Type == intstr.String {
if p.Name == cd.Spec.Service.TargetPort.StrVal {
continue
}
}
}
name := fmt.Sprintf("tcp-%s-%v", container.Name, i)
if p.Name != "" {
name = p.Name
}
ports[name] = p.ContainerPort
}
}
return ports, nil
}
// makeAnnotations appends an unique ID to annotations map
func makeAnnotations(annotations map[string]string) (map[string]string, error) {
idKey := "flagger-id"
res := make(map[string]string)
uuid := make([]byte, 16)
n, err := io.ReadFull(rand.Reader, uuid)
if n != len(uuid) || err != nil {
return res, err
}
uuid[8] = uuid[8]&^0xc0 | 0x80
uuid[6] = uuid[6]&^0xf0 | 0x40
id := fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
for k, v := range annotations {
if k != idKey {
res[k] = v
}
}
res[idKey] = id
return res, nil
}
func makePrimaryLabels(labels map[string]string, primaryName string, label string) map[string]string {
res := make(map[string]string)
for k, v := range labels {
if k != label {
res[k] = v
}
}
res[label] = primaryName
return res
}
func int32p(i int32) *int32 {
return &i
}
func int32Default(i *int32) int32 {
if i == nil {
return 1
}
return *i
}