From 68c70739ef5581c0fc5075adbe0030ab0623a192 Mon Sep 17 00:00:00 2001 From: Michail Kargakis Date: Wed, 27 Apr 2016 18:47:16 +0200 Subject: [PATCH] Bug 1329138: stop emitting events on update conflicts Update conflicts for deployments are pretty common since they are handled by three different controllers (kube: rc manager, origin: deployer pod controller, deployment controller) and their events stay attached on deploymentconfigs which may confuse users ("My deployment is running but I have this event over there talking about an update conflict"). Since those errors are retried by the controller there is no reason to emit events for them. --- pkg/deploy/api/validation/validation.go | 4 ++ .../controller/deployment/controller.go | 71 +++++++------------ .../controller/deployment/controller_test.go | 66 ++++++++--------- pkg/deploy/controller/deployment/factory.go | 31 ++++---- pkg/deploy/util/util.go | 9 ++- 5 files changed, 83 insertions(+), 98 deletions(-) diff --git a/pkg/deploy/api/validation/validation.go b/pkg/deploy/api/validation/validation.go index 4d9d1a65cd12..62cd62f3f0e0 100644 --- a/pkg/deploy/api/validation/validation.go +++ b/pkg/deploy/api/validation/validation.go @@ -127,6 +127,10 @@ func validateDeploymentStrategy(strategy *deployapi.DeploymentStrategy, pod *kap } else { errs = append(errs, validateCustomParams(strategy.CustomParams, fldPath.Child("customParams"))...) } + case "": + errs = append(errs, field.Required(fldPath.Child("type"), "strategy type is required")) + default: + errs = append(errs, field.Invalid(fldPath.Child("type"), strategy.Type, "unsupported strategy type, use \"Custom\" instead and specify your own strategy")) } if strategy.Labels != nil { diff --git a/pkg/deploy/controller/deployment/controller.go b/pkg/deploy/controller/deployment/controller.go index 2eed5a5cc2a5..c1ea342c4c16 100644 --- a/pkg/deploy/controller/deployment/controller.go +++ b/pkg/deploy/controller/deployment/controller.go @@ -34,7 +34,7 @@ type DeploymentController struct { // podClient provides access to pods. podClient podClient // makeContainer knows how to make a container appropriate to execute a deployment strategy. - makeContainer func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) + makeContainer func(strategy *deployapi.DeploymentStrategy) *kapi.Container // decodeConfig knows how to decode the deploymentConfig from a deployment's annotations. decodeConfig func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) // recorder is used to record events. @@ -46,6 +46,11 @@ type fatalError string func (e fatalError) Error() string { return "fatal error handling deployment: " + string(e) } +// actionableError is an error on which users can act +type actionableError string + +func (e actionableError) Error() string { return string(e) } + // Handle processes deployment and either creates a deployer pod or responds // to a terminal deployment status. func (c *DeploymentController) Handle(deployment *kapi.ReplicationController) error { @@ -73,11 +78,6 @@ func (c *DeploymentController) Handle(deployment *kapi.ReplicationController) er // annotation on it, and throw a retryable error. existingPod, err := c.podClient.getPod(deployment.Namespace, deployutil.DeployerPodNameForDeployment(deployment.Name)) if err != nil && !kerrors.IsNotFound(err) { - if config, decodeErr := c.decodeConfig(deployment); decodeErr == nil { - c.recorder.Eventf(config, kapi.EventTypeWarning, "FailedCreate", "Error getting existing deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) - } else { - c.recorder.Eventf(deployment, kapi.EventTypeWarning, "FailedCreate", "Error getting existing deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) - } return fmt.Errorf("couldn't fetch existing deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) } if err == nil && existingPod != nil { @@ -92,11 +92,7 @@ func (c *DeploymentController) Handle(deployment *kapi.ReplicationController) er if deployutil.DeploymentNameFor(existingPod) != deployment.Name { nextStatus = deployapi.DeploymentStatusFailed deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentFailedUnrelatedDeploymentExists - if config, decodeErr := c.decodeConfig(deployment); decodeErr == nil { - c.recorder.Eventf(config, kapi.EventTypeWarning, "FailedCreate", "Error creating deployer pod for %s since another pod with the same name (%q) exists", deployutil.LabelForDeployment(deployment), existingPod.Name) - } else { - c.recorder.Eventf(deployment, kapi.EventTypeWarning, "FailedCreate", "Error creating deployer pod for %s since another pod with the same name (%q) exists", deployutil.LabelForDeployment(deployment), existingPod.Name) - } + c.emitDeploymentEvent(deployment, kapi.EventTypeWarning, "FailedCreate", fmt.Sprintf("Error creating deployer pod since another pod with the same name (%q) exists", existingPod.Name)) glog.V(2).Infof("Couldn't create deployer pod for %s since an unrelated pod with the same name (%q) exists", deployutil.LabelForDeployment(deployment), existingPod.Name) } else { // Update to pending relative to the existing validated deployer pod. @@ -110,22 +106,18 @@ func (c *DeploymentController) Handle(deployment *kapi.ReplicationController) er // Generate a deployer pod spec. podTemplate, err := c.makeDeployerPod(deployment) if err != nil { + // TODO: Make this an oc status error return fatalError(fmt.Sprintf("couldn't make deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err)) } // Create the deployer pod. deploymentPod, err := c.podClient.createPod(deployment.Namespace, podTemplate) // Retry on error. if err != nil { - if config, decodeErr := c.decodeConfig(deployment); decodeErr == nil { - c.recorder.Eventf(config, kapi.EventTypeWarning, "FailedCreate", "Error creating deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) - } else { - c.recorder.Eventf(deployment, kapi.EventTypeWarning, "FailedCreate", "Error creating deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) - } return fmt.Errorf("couldn't create deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) } deployment.Annotations[deployapi.DeploymentPodAnnotation] = deploymentPod.Name nextStatus = deployapi.DeploymentStatusPending - glog.V(4).Infof("Created pod %s for deployment %s", deploymentPod.Name, deployutil.LabelForDeployment(deployment)) + glog.V(4).Infof("Created deployer pod %s for deployment %s", deploymentPod.Name, deployutil.LabelForDeployment(deployment)) case deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning: // If the deployer pod has vanished, consider the deployment a failure. deployerPodName := deployutil.DeployerPodNameForDeployment(deployment.Name) @@ -134,11 +126,7 @@ func (c *DeploymentController) Handle(deployment *kapi.ReplicationController) er nextStatus = deployapi.DeploymentStatusFailed deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentFailedDeployerPodNoLongerExists - if config, decodeErr := c.decodeConfig(deployment); decodeErr == nil { - c.recorder.Eventf(config, kapi.EventTypeWarning, "Failed", "Deployer pod %q has gone missing", deployerPodName) - } else { - c.recorder.Eventf(deployment, kapi.EventTypeWarning, "Failed", "Deployer pod %q has gone missing", deployerPodName) - } + c.emitDeploymentEvent(deployment, kapi.EventTypeWarning, "Failed", fmt.Sprintf("Deployer pod %q has gone missing", deployerPodName)) glog.V(4).Infof("Failing deployment %q because its deployer pod %q disappeared", deployutil.LabelForDeployment(deployment), deployerPodName) break } else { @@ -195,18 +183,13 @@ func (c *DeploymentController) Handle(deployment *kapi.ReplicationController) er } if !cleanedAll { - return fmt.Errorf("couldn't clean up all deployer pods for %s", deployutil.LabelForDeployment(deployment)) + return actionableError(fmt.Sprintf("couldn't clean up all deployer pods for %s", deployment.Name)) } } if deployutil.CanTransitionPhase(currentStatus, nextStatus) || deploymentScaled { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) if _, err := c.deploymentClient.updateDeployment(deployment.Namespace, deployment); err != nil { - if config, decodeErr := c.decodeConfig(deployment); decodeErr == nil { - c.recorder.Eventf(config, kapi.EventTypeWarning, "FailedUpdate", "Cannot update deployment %s status to %s: %v", deployutil.LabelForDeployment(deployment), nextStatus, err) - } else { - c.recorder.Eventf(deployment, kapi.EventTypeWarning, "FailedUpdate", "Cannot update deployment %s status to %s: %v", deployutil.LabelForDeployment(deployment), nextStatus, err) - } return fmt.Errorf("couldn't update deployment %s to status %s: %v", deployutil.LabelForDeployment(deployment), nextStatus, err) } glog.V(4).Infof("Updated deployment %s status from %s to %s (scale: %d)", deployutil.LabelForDeployment(deployment), currentStatus, nextStatus, deployment.Spec.Replicas) @@ -222,10 +205,7 @@ func (c *DeploymentController) makeDeployerPod(deployment *kapi.ReplicationContr return nil, err } - container, err := c.makeContainer(&deploymentConfig.Spec.Strategy) - if err != nil { - return nil, err - } + container := c.makeContainer(&deploymentConfig.Spec.Strategy) // Add deployment environment variables to the container. envVars := []kapi.EnvVar{} @@ -284,33 +264,32 @@ func (c *DeploymentController) cancelDeployerPods(deployment *kapi.ReplicationCo } glog.V(4).Infof("Cancelling %d deployer pods for deployment %s", len(deployerPods), deployutil.LabelForDeployment(deployment)) zeroDelay := int64(1) - anyCancelled := false + cleanedAll := len(deployerPods) > 0 for _, deployerPod := range deployerPods { // Set the ActiveDeadlineSeconds on the pod so it's terminated very soon. if deployerPod.Spec.ActiveDeadlineSeconds == nil || *deployerPod.Spec.ActiveDeadlineSeconds != zeroDelay { deployerPod.Spec.ActiveDeadlineSeconds = &zeroDelay if _, err := c.podClient.updatePod(deployerPod.Namespace, &deployerPod); err != nil { - if config, decodeErr := c.decodeConfig(deployment); decodeErr == nil { - c.recorder.Eventf(config, kapi.EventTypeWarning, "FailedCancellation", "Error cancelling deployer pod %s for deployment %s: %v", deployerPod.Name, deployutil.LabelForDeployment(deployment), err) - } else { - c.recorder.Eventf(deployment, kapi.EventTypeWarning, "FailedCancellation", "Error cancelling deployer pod %s for deployment %s: %v", deployerPod.Name, deployutil.LabelForDeployment(deployment), err) - } - return fmt.Errorf("couldn't cancel deployer pod %s for deployment %s: %v", deployerPod.Name, deployutil.LabelForDeployment(deployment), err) + cleanedAll = false + utilruntime.HandleError(fmt.Errorf("couldn't cancel deployer pod %s for deployment %s: %v", deployerPod.Name, deployutil.LabelForDeployment(deployment), err)) } - anyCancelled = true glog.V(4).Infof("Cancelled deployer pod %s for deployment %s", deployerPod.Name, deployutil.LabelForDeployment(deployment)) } } - if anyCancelled { - if config, decodeErr := c.decodeConfig(deployment); decodeErr == nil && len(deployerPods) > 0 { - c.recorder.Eventf(config, kapi.EventTypeNormal, "Cancelled", "Cancelled deployer pods for deployment %s", deployutil.LabelForDeployment(deployment)) - } else if len(deployerPods) > 0 { - c.recorder.Eventf(deployment, kapi.EventTypeNormal, "Cancelled", "Cancelled deployer pods") - } + if cleanedAll { + c.emitDeploymentEvent(deployment, kapi.EventTypeNormal, "Cancelled", "Cancelled all deployer pods") } return nil } +func (c *DeploymentController) emitDeploymentEvent(deployment *kapi.ReplicationController, eventType, title, message string) { + if config, _ := c.decodeConfig(deployment); config != nil { + c.recorder.Eventf(config, eventType, title, fmt.Sprintf("%s: %s", deployment.Name, message)) + } else { + c.recorder.Eventf(deployment, eventType, title, message) + } +} + // deploymentClient abstracts access to deployments. type deploymentClient interface { getDeployment(namespace, name string) (*kapi.ReplicationController, error) diff --git a/pkg/deploy/controller/deployment/controller_test.go b/pkg/deploy/controller/deployment/controller_test.go index c791e6573b3b..aa1a1a10c058 100644 --- a/pkg/deploy/controller/deployment/controller_test.go +++ b/pkg/deploy/controller/deployment/controller_test.go @@ -45,8 +45,8 @@ func TestHandle_createPodOk(t *testing.T) { return pod, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return expectedContainer, nil + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return expectedContainer }, recorder: &record.FakeRecorder{}, } @@ -132,7 +132,7 @@ func TestHandle_makeContainerFail(t *testing.T) { controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { - return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) + return nil, fmt.Errorf("invalid serialized object reference") }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { @@ -149,8 +149,8 @@ func TestHandle_makeContainerFail(t *testing.T) { return nil, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return nil, fmt.Errorf("couldn't make container") + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return nil }, recorder: &record.FakeRecorder{}, } @@ -192,8 +192,8 @@ func TestHandle_createPodFail(t *testing.T) { return nil, fmt.Errorf("Failed to create pod %s", pod.Name) }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return okContainer(), nil + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return okContainer() }, recorder: &record.FakeRecorder{}, } @@ -241,8 +241,8 @@ func TestHandle_deployerPodAlreadyExists(t *testing.T) { return nil, kerrors.NewAlreadyExists(kapi.Resource("Pod"), pod.Name) }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return okContainer(), nil + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return okContainer() }, recorder: &record.FakeRecorder{}, } @@ -291,8 +291,8 @@ func TestHandle_unrelatedPodAlreadyExists(t *testing.T) { return nil, kerrors.NewAlreadyExists(kapi.Resource("Pod"), pod.Name) }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return okContainer(), nil + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return okContainer() }, recorder: &record.FakeRecorder{}, } @@ -338,9 +338,9 @@ func TestHandle_noop(t *testing.T) { return &kapi.Pod{}, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to make container") - return nil, nil + return nil }, recorder: &record.FakeRecorder{}, } @@ -392,9 +392,9 @@ func TestHandle_failedTest(t *testing.T) { return nil, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to make container") - return nil, nil + return nil }, recorder: &record.FakeRecorder{}, } @@ -452,9 +452,9 @@ func TestHandle_cleanupPodOk(t *testing.T) { return pods, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to make container") - return nil, nil + return nil }, recorder: &record.FakeRecorder{}, } @@ -513,9 +513,9 @@ func TestHandle_cleanupPodOkTest(t *testing.T) { return pods, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to make container") - return nil, nil + return nil }, recorder: &record.FakeRecorder{}, } @@ -570,9 +570,9 @@ func TestHandle_cleanupPodNoop(t *testing.T) { return []kapi.Pod{}, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to make container") - return nil, nil + return nil }, recorder: &record.FakeRecorder{}, } @@ -613,9 +613,9 @@ func TestHandle_cleanupPodFail(t *testing.T) { return []kapi.Pod{{}}, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to make container") - return nil, nil + return nil }, recorder: &record.FakeRecorder{}, } @@ -653,8 +653,8 @@ func TestHandle_cancelNew(t *testing.T) { return []kapi.Pod{}, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return okContainer(), nil + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return okContainer() }, recorder: &record.FakeRecorder{}, } @@ -704,8 +704,8 @@ func TestHandle_cancelNewWithDeployers(t *testing.T) { return []kapi.Pod{*relatedPod(deployment)}, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return okContainer(), nil + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return okContainer() }, recorder: &record.FakeRecorder{}, } @@ -756,8 +756,8 @@ func TestHandle_cancelPendingRunning(t *testing.T) { return pods, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return okContainer(), nil + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return okContainer() }, recorder: &record.FakeRecorder{}, } @@ -810,8 +810,8 @@ func TestHandle_deployerPodDisappeared(t *testing.T) { return nil, kerrors.NewNotFound(kapi.Resource("Pod"), name) }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return okContainer(), nil + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return okContainer() }, recorder: &record.FakeRecorder{}, } @@ -857,8 +857,8 @@ func TestDeployerCustomLabelsAndAnnotations(t *testing.T) { return pod, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { - return okContainer(), nil + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { + return okContainer() }, } diff --git a/pkg/deploy/controller/deployment/factory.go b/pkg/deploy/controller/deployment/factory.go index b566c306d00d..96cad479f3ca 100644 --- a/pkg/deploy/controller/deployment/factory.go +++ b/pkg/deploy/controller/deployment/factory.go @@ -51,8 +51,9 @@ func (factory *DeploymentControllerFactory) Create() controller.RunnableControll eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartRecordingToSink(factory.KubeClient.Events("")) + eventRecorder := eventBroadcaster.NewRecorder(kapi.EventSource{Component: "deployments-controller"}) - deployController := &DeploymentController{ + c := &DeploymentController{ serviceAccount: factory.ServiceAccount, deploymentClient: &deploymentClientImpl{ getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { @@ -90,13 +91,13 @@ func (factory *DeploymentControllerFactory) Create() controller.RunnableControll return pods.Items, nil }, }, - makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { + makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { return factory.makeContainer(strategy) }, decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, factory.Codec) }, - recorder: eventBroadcaster.NewRecorder(kapi.EventSource{Component: "deployment-controller"}), + recorder: eventRecorder, } return &controller.RetryController{ @@ -110,6 +111,12 @@ func (factory *DeploymentControllerFactory) Create() controller.RunnableControll return false } if retries.Count > 1 { + _, isActionableErr := err.(actionableError) + deployment, noReasonToPanic := obj.(*kapi.ReplicationController) + + if isActionableErr && noReasonToPanic { + c.emitDeploymentEvent(deployment, kapi.EventTypeWarning, "FailedRetry", fmt.Sprintf("About to stop retrying %s: %v", deployment.Name, err)) + } return false } return true @@ -118,7 +125,7 @@ func (factory *DeploymentControllerFactory) Create() controller.RunnableControll ), Handle: func(obj interface{}) error { deployment := obj.(*kapi.ReplicationController) - return deployController.Handle(deployment) + return c.Handle(deployment) }, } } @@ -131,9 +138,7 @@ func (factory *DeploymentControllerFactory) Create() controller.RunnableControll // 2. For all Custom strategy, use the strategy's image for the container // image, and use the combination of the factory's Environment and the // strategy's environment as the container environment. -// -// An error is returned if the deployment strategy type is not supported. -func (factory *DeploymentControllerFactory) makeContainer(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { +func (factory *DeploymentControllerFactory) makeContainer(strategy *deployapi.DeploymentStrategy) *kapi.Container { // Set default environment values environment := []kapi.EnvVar{} for _, env := range factory.Environment { @@ -144,10 +149,6 @@ func (factory *DeploymentControllerFactory) makeContainer(strategy *deployapi.De switch strategy.Type { case deployapi.DeploymentStrategyTypeRecreate, deployapi.DeploymentStrategyTypeRolling: // Use the factory-configured image. - return &kapi.Container{ - Image: factory.DeployerImage, - Env: environment, - }, nil case deployapi.DeploymentStrategyTypeCustom: // Use user-defined values from the strategy input. for _, env := range strategy.CustomParams.Environment { @@ -156,8 +157,10 @@ func (factory *DeploymentControllerFactory) makeContainer(strategy *deployapi.De return &kapi.Container{ Image: strategy.CustomParams.Image, Env: environment, - }, nil - default: - return nil, fmt.Errorf("unsupported deployment strategy type: %s", strategy.Type) + } + } + return &kapi.Container{ + Image: factory.DeployerImage, + Env: environment, } } diff --git a/pkg/deploy/util/util.go b/pkg/deploy/util/util.go index c021c7a7d898..4a5d37f85b54 100644 --- a/pkg/deploy/util/util.go +++ b/pkg/deploy/util/util.go @@ -132,15 +132,14 @@ func IsImageChangeControllerChange(newDc, oldDc deployapi.DeploymentConfig) bool // if the controller doesn't contain an encoded config. func DecodeDeploymentConfig(controller *api.ReplicationController, decoder runtime.Decoder) (*deployapi.DeploymentConfig, error) { encodedConfig := []byte(EncodedDeploymentConfigFor(controller)) - if decoded, err := runtime.Decode(decoder, encodedConfig); err == nil { + decoded, err := runtime.Decode(decoder, encodedConfig) + if err == nil { if config, ok := decoded.(*deployapi.DeploymentConfig); ok { return config, nil - } else { - return nil, fmt.Errorf("decoded DeploymentConfig from controller is not a DeploymentConfig: %v", err) } - } else { - return nil, fmt.Errorf("failed to decode DeploymentConfig from controller: %v", err) + return nil, fmt.Errorf("decoded object from controller is not a DeploymentConfig") } + return nil, fmt.Errorf("failed to decode DeploymentConfig from controller: %v", err) } // EncodeDeploymentConfig encodes config as a string using codec.