// Handle processes change triggers for config. func (c *DeploymentConfigChangeController) Handle(config *deployapi.DeploymentConfig) error { if !deployutil.HasChangeTrigger(config) { glog.V(5).Infof("Ignoring deployment config %q; no change triggers detected", deployutil.LabelForDeploymentConfig(config)) return nil } // Try to decode this deployment config from the encoded annotation found in // its latest deployment. decoded, err := c.decodeFromLatest(config) if err != nil { return err } // If this is the initial deployment, then wait for any images that need to be resolved, otherwise // automatically start a new deployment. if config.Status.LatestVersion == 0 { canTrigger, causes := canTrigger(config, decoded) if !canTrigger { // If we cannot trigger then we need to wait for the image change controller. glog.V(5).Infof("Ignoring deployment config %q; template image needs to be resolved by the image change controller", deployutil.LabelForDeploymentConfig(config)) return nil } return c.updateStatus(config, causes) } // If this is not the initial deployment, check if there is any template difference between // this and the decoded deploymentconfig. if kapi.Semantic.DeepEqual(config.Spec.Template, decoded.Spec.Template) { return nil } _, causes := canTrigger(config, decoded) return c.updateStatus(config, causes) }
// Handle processes change triggers for config. func (c *DeploymentConfigChangeController) Handle(config *deployapi.DeploymentConfig) error { if !deployutil.HasChangeTrigger(config) { glog.V(5).Infof("Ignoring DeploymentConfig %s; no change triggers detected", deployutil.LabelForDeploymentConfig(config)) return nil } if config.Status.LatestVersion == 0 { _, _, abort, err := c.generateDeployment(config) if err != nil { if kerrors.IsConflict(err) { return fatalError(fmt.Sprintf("DeploymentConfig %s updated since retrieval; aborting trigger: %v", deployutil.LabelForDeploymentConfig(config), err)) } glog.V(4).Infof("Couldn't create initial deployment for deploymentConfig %q: %v", deployutil.LabelForDeploymentConfig(config), err) return nil } if !abort { glog.V(4).Infof("Created initial deployment for deploymentConfig %q", deployutil.LabelForDeploymentConfig(config)) } return nil } latestDeploymentName := deployutil.LatestDeploymentNameForConfig(config) deployment, err := c.changeStrategy.getDeployment(config.Namespace, latestDeploymentName) if err != nil { // If there's no deployment for the latest config, we have no basis of // comparison. It's the responsibility of the deployment config controller // to make the deployment for the config, so return early. if kerrors.IsNotFound(err) { glog.V(5).Infof("Ignoring change for DeploymentConfig %s; no existing Deployment found", deployutil.LabelForDeploymentConfig(config)) return nil } return fmt.Errorf("couldn't retrieve Deployment for DeploymentConfig %s: %v", deployutil.LabelForDeploymentConfig(config), err) } deployedConfig, err := c.decodeConfig(deployment) if err != nil { return fatalError(fmt.Sprintf("error decoding DeploymentConfig from Deployment %s for DeploymentConfig %s: %v", deployutil.LabelForDeployment(deployment), deployutil.LabelForDeploymentConfig(config), err)) } // Detect template diffs, and return early if there aren't any changes. if kapi.Semantic.DeepEqual(config.Spec.Template, deployedConfig.Spec.Template) { glog.V(5).Infof("Ignoring DeploymentConfig change for %s (latestVersion=%d); same as Deployment %s", deployutil.LabelForDeploymentConfig(config), config.Status.LatestVersion, deployutil.LabelForDeployment(deployment)) return nil } // There was a template diff, so generate a new config version. fromVersion, toVersion, abort, err := c.generateDeployment(config) if err != nil { if kerrors.IsConflict(err) { return fatalError(fmt.Sprintf("DeploymentConfig %s updated since retrieval; aborting trigger: %v", deployutil.LabelForDeploymentConfig(config), err)) } return fmt.Errorf("couldn't generate deployment for DeploymentConfig %s: %v", deployutil.LabelForDeploymentConfig(config), err) } if !abort { glog.V(4).Infof("Updated DeploymentConfig %s from version %d to %d for existing deployment %s", deployutil.LabelForDeploymentConfig(config), fromVersion, toVersion, deployutil.LabelForDeployment(deployment)) } return nil }
// canTrigger determines if we can trigger a new deployment for config based on the various deployment triggers. func canTrigger( config *deployapi.DeploymentConfig, rn kclient.ReplicationControllersNamespacer, decoder runtime.Decoder, force bool, ) (bool, []deployapi.DeploymentCause, error) { decoded, err := decodeFromLatestDeployment(config, rn, decoder) if err != nil { return false, nil, err } ictCount, resolved, canTriggerByImageChange := 0, 0, false var causes []deployapi.DeploymentCause for _, t := range config.Spec.Triggers { if t.Type != deployapi.DeploymentTriggerOnImageChange { continue } ictCount++ // If the image is yet to be resolved then we cannot process this trigger. lastTriggered := t.ImageChangeParams.LastTriggeredImage if len(lastTriggered) == 0 { continue } resolved++ // Non-automatic triggers should not be able to trigger deployments. if !t.ImageChangeParams.Automatic { continue } // We need stronger checks in order to validate that this template // change is an image change. Look at the deserialized config's // triggers and compare with the present trigger. Initial deployments // should always trigger - there is no previous config to use for the // comparison. if config.Status.LatestVersion > 0 && !triggeredByDifferentImage(*t.ImageChangeParams, *decoded) { continue } canTriggerByImageChange = true causes = append(causes, deployapi.DeploymentCause{ Type: deployapi.DeploymentTriggerOnImageChange, ImageTrigger: &deployapi.DeploymentCauseImageTrigger{ From: kapi.ObjectReference{ Name: t.ImageChangeParams.From.Name, Namespace: t.ImageChangeParams.From.Namespace, Kind: "ImageStreamTag", }, }, }) } if ictCount != resolved { err = errors.NewBadRequest(fmt.Sprintf("cannot trigger a deployment for %q because it contains unresolved images", config.Name)) return false, nil, err } if force { return true, []deployapi.DeploymentCause{{Type: deployapi.DeploymentTriggerManual}}, nil } canTriggerByConfigChange := false if deployutil.HasChangeTrigger(config) && // Our deployment config has a config change trigger len(causes) == 0 && // and no other trigger has triggered. (config.Status.LatestVersion == 0 || // Either it's the initial deployment !kapi.Semantic.DeepEqual(config.Spec.Template, decoded.Spec.Template)) /* or a config change happened so we need to trigger */ { canTriggerByConfigChange = true causes = []deployapi.DeploymentCause{{Type: deployapi.DeploymentTriggerOnConfigChange}} } return canTriggerByConfigChange || canTriggerByImageChange, causes, nil }
// canTrigger is used by the trigger controller to determine if the provided config can trigger // a deployment. // // Image change triggers are processed first. It is required for all of them to point to images // that exist. Otherwise, this controller will wait for the images to land and be updated in the // triggers that point to them by the image change controller. // // Config change triggers are processed last. If all images are resolved and an automatic trigger // was updated, then it should be possible to trigger a new deployment without a config change // trigger. Otherwise, if a config change trigger exists and the config is not deployed yet or it // has a podtemplate change, then the controller should trigger a new deployment (assuming all // image change triggers can trigger). func canTrigger(config, decoded *deployapi.DeploymentConfig) (bool, []deployapi.DeploymentCause) { if decoded == nil { // The decoded deployment config will never be nil here but a sanity check // never hurts. return false, nil } ictCount, resolved, canTriggerByImageChange := 0, 0, false var causes []deployapi.DeploymentCause // IMAGE CHANGE TRIGGERS for _, t := range config.Spec.Triggers { if t.Type != deployapi.DeploymentTriggerOnImageChange { continue } ictCount++ // If this is the initial deployment then we need to wait for the image change controller // to resolve the image inside the pod template. lastTriggered := t.ImageChangeParams.LastTriggeredImage if len(lastTriggered) == 0 { continue } resolved++ // Non-automatic triggers should not be able to trigger deployments. if !t.ImageChangeParams.Automatic { continue } // We need stronger checks in order to validate that this template // change is an image change. Look at the deserialized config's // triggers and compare with the present trigger. Initial deployments // should always trigger since there is no previous config to compare to. if config.Status.LatestVersion > 0 { if !triggeredByDifferentImage(*t.ImageChangeParams, *decoded) { continue } } canTriggerByImageChange = true causes = append(causes, deployapi.DeploymentCause{ Type: deployapi.DeploymentTriggerOnImageChange, ImageTrigger: &deployapi.DeploymentCauseImageTrigger{ From: kapi.ObjectReference{ Name: t.ImageChangeParams.From.Name, Namespace: t.ImageChangeParams.From.Namespace, Kind: "ImageStreamTag", }, }, }) } // We need to wait for all images to resolve before triggering a new deployment. if ictCount != resolved { return false, nil } // CONFIG CHANGE TRIGGERS canTriggerByConfigChange := false // Our deployment config has a config change trigger and no image change has triggered. // If an image change had happened, it would be enough to start a new deployment without // caring about the config change trigger. if deployutil.HasChangeTrigger(config) && !canTriggerByImageChange { // This is the initial deployment or the config has a template change. We need to // kick a new deployment. if config.Status.LatestVersion == 0 || !kapi.Semantic.DeepEqual(config.Spec.Template, decoded.Spec.Template) { canTriggerByConfigChange = true causes = []deployapi.DeploymentCause{{Type: deployapi.DeploymentTriggerOnConfigChange}} } } return canTriggerByConfigChange || canTriggerByImageChange, causes }