// Handle processes image change triggers associated with imageRepo. func (c *ImageChangeController) Handle(imageRepo *imageapi.ImageStream) error { configs, err := c.deploymentConfigClient.listDeploymentConfigs() if err != nil { return fmt.Errorf("couldn't get list of DeploymentConfig while handling ImageStream %s: %v", labelForRepo(imageRepo), err) } // Find any configs which should be updated based on the new image state configsToUpdate := map[string]*deployapi.DeploymentConfig{} for _, config := range configs { glog.V(4).Infof("Detecting changed images for DeploymentConfig %s", deployutil.LabelForDeploymentConfig(config)) for _, trigger := range config.Triggers { params := trigger.ImageChangeParams // Only automatic image change triggers should fire if trigger.Type != deployapi.DeploymentTriggerOnImageChange || !params.Automatic { continue } // Check if the image repo matches the trigger if !triggerMatchesImage(config, params, imageRepo) { continue } // Find the latest tag event for the trigger tag latestEvent := imageapi.LatestTaggedImage(imageRepo, params.Tag) if latestEvent == nil { glog.V(2).Infof("Couldn't find latest tag event for tag %s in ImageStream %s", params.Tag, labelForRepo(imageRepo)) continue } // Ensure a change occurred if len(latestEvent.DockerImageReference) > 0 && latestEvent.DockerImageReference != params.LastTriggeredImage { // Mark the config for regeneration configsToUpdate[config.Name] = config } } } // Attempt to regenerate all configs which may contain image updates anyFailed := false for _, config := range configsToUpdate { err := c.regenerate(config) if err != nil { anyFailed = true glog.V(2).Infof("Couldn't regenerate DeploymentConfig %s: %s", deployutil.LabelForDeploymentConfig(config), err) continue } glog.V(4).Infof("Regenerated DeploymentConfig %s in response to image change trigger", deployutil.LabelForDeploymentConfig(config)) } if anyFailed { return fatalError(fmt.Sprintf("couldn't update some DeploymentConfig for trigger on ImageStream %s", labelForRepo(imageRepo))) } glog.V(4).Infof("Updated all DeploymentConfigs for trigger on ImageStream %s", labelForRepo(imageRepo)) return nil }
// regenerate calls the generator to get a new config. If the newly generated // config's version is newer, update the old config to be the new config. // Otherwise do nothing. func (c *ImageChangeController) regenerate(config *deployapi.DeploymentConfig) error { // Get a regenerated config which includes the new image repo references newConfig, err := c.deploymentConfigClient.generateDeploymentConfig(config.Namespace, config.Name) if err != nil { return fmt.Errorf("error generating new version of DeploymentConfig %s: %v", deployutil.LabelForDeploymentConfig(config), err) } // No update occurred if config.LatestVersion == newConfig.LatestVersion { glog.V(4).Infof("No version difference for generated DeploymentConfig %s", deployutil.LabelForDeploymentConfig(config)) return nil } // Persist the new config _, err = c.deploymentConfigClient.updateDeploymentConfig(newConfig.Namespace, newConfig) if err != nil { return err } glog.Infof("Regenerated DeploymentConfig %s for image updates", deployutil.LabelForDeploymentConfig(config)) return nil }
func (g *DeploymentConfigGenerator) findImageStream(config *deployapi.DeploymentConfig, params *deployapi.DeploymentTriggerImageChangeParams) (*imageapi.ImageStream, error) { // Try to find the repo by ObjectReference if len(params.From.Name) > 0 { namespace := params.From.Namespace if len(namespace) == 0 { namespace = config.Namespace } return g.Client.GetImageStream(kapi.WithNamespace(kapi.NewContext(), namespace), params.From.Name) } // Fall back to a list based lookup on RepositoryName repos, err := g.Client.ListImageStreams(kapi.WithNamespace(kapi.NewContext(), config.Namespace)) if err != nil { return nil, err } for _, repo := range repos.Items { if len(repo.Status.DockerImageRepository) > 0 && params.RepositoryName == repo.Status.DockerImageRepository { return &repo, nil } } return nil, fmt.Errorf("couldn't find image stream for config %s trigger params", deployutil.LabelForDeploymentConfig(config)) }
// Handle processes config and creates a new deployment if necessary. func (c *DeploymentConfigController) Handle(config *deployapi.DeploymentConfig) error { // Only deploy when the version has advanced past 0. if config.LatestVersion == 0 { glog.V(5).Infof("Waiting for first version of %s", deployutil.LabelForDeploymentConfig(config)) return nil } // Check if any existing inflight deployments (any non-terminal state). existingDeployments, err := c.deploymentClient.listDeploymentsForConfig(config.Namespace, config.Name) if err != nil { return fmt.Errorf("couldn't list Deployments for DeploymentConfig %s: %v", deployutil.LabelForDeploymentConfig(config), err) } var inflightDeployment *kapi.ReplicationController latestDeploymentExists := false for _, deployment := range existingDeployments.Items { // check if this is the latest deployment // we'll return after we've dealt with the multiple-active-deployments case if deployutil.DeploymentVersionFor(&deployment) == config.LatestVersion { latestDeploymentExists = true } deploymentStatus := deployutil.DeploymentStatusFor(&deployment) switch deploymentStatus { case deployapi.DeploymentStatusFailed, deployapi.DeploymentStatusComplete: // Previous deployment in terminal state - can ignore // Ignoring specific deployment states so that any newly introduced // deployment state will not be ignored default: if inflightDeployment == nil { inflightDeployment = &deployment continue } var deploymentForCancellation *kapi.ReplicationController if deployutil.DeploymentVersionFor(inflightDeployment) < deployutil.DeploymentVersionFor(&deployment) { deploymentForCancellation, inflightDeployment = inflightDeployment, &deployment } else { deploymentForCancellation = &deployment } deploymentForCancellation.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue deploymentForCancellation.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledNewerDeploymentExists if _, err := c.deploymentClient.updateDeployment(deploymentForCancellation.Namespace, deploymentForCancellation); err != nil { util.HandleError(fmt.Errorf("couldn't cancel Deployment %s: %v", deployutil.LabelForDeployment(deploymentForCancellation), err)) } glog.V(4).Infof("Cancelled Deployment %s for DeploymentConfig %s", deployutil.LabelForDeployment(deploymentForCancellation), deployutil.LabelForDeploymentConfig(config)) } } // if the latest deployment exists then nothing else needs to be done if latestDeploymentExists { return nil } // check to see if there are inflight deployments if inflightDeployment != nil { // raise a transientError so that the deployment config can be re-queued glog.V(4).Infof("Found previous inflight Deployment for %s - will requeue", deployutil.LabelForDeploymentConfig(config)) return transientError(fmt.Sprintf("found previous inflight Deployment for %s - requeuing", deployutil.LabelForDeploymentConfig(config))) } // Try and build a deployment for the config. deployment, err := c.makeDeployment(config) if err != nil { return fatalError(fmt.Sprintf("couldn't make Deployment from (potentially invalid) DeploymentConfig %s: %v", deployutil.LabelForDeploymentConfig(config), err)) } // Compute the desired replicas for the deployment. Use the last completed // deployment's current replica count, or the config template if there is no // prior completed deployment available. desiredReplicas := config.Template.ControllerTemplate.Replicas if len(existingDeployments.Items) > 0 { sort.Sort(deployutil.DeploymentsByLatestVersionDesc(existingDeployments.Items)) for _, existing := range existingDeployments.Items { if deployutil.DeploymentStatusFor(&existing) == deployapi.DeploymentStatusComplete { desiredReplicas = existing.Spec.Replicas glog.V(4).Infof("Desired replicas for %s set to %d based on prior completed deployment %s", deployutil.LabelForDeploymentConfig(config), desiredReplicas, existing.Name) break } } } deployment.Annotations[deployapi.DesiredReplicasAnnotation] = strconv.Itoa(desiredReplicas) // Create the deployment. if _, err := c.deploymentClient.createDeployment(config.Namespace, deployment); err == nil { glog.V(4).Infof("Created Deployment for DeploymentConfig %s", deployutil.LabelForDeploymentConfig(config)) return nil } else { // If the deployment was already created, just move on. The cache could be stale, or another // process could have already handled this update. if errors.IsAlreadyExists(err) { glog.V(4).Infof("Deployment already exists for DeploymentConfig %s", deployutil.LabelForDeploymentConfig(config)) return nil } // log an event if the deployment could not be created that the user can discover c.recorder.Eventf(config, "failedCreate", "Error creating: %v", err) return fmt.Errorf("couldn't create Deployment for DeploymentConfig %s: %v", deployutil.LabelForDeploymentConfig(config), err) } }
// Handle processes change triggers for config. func (c *DeploymentConfigChangeController) Handle(config *deployapi.DeploymentConfig) error { hasChangeTrigger := false for _, trigger := range config.Triggers { if trigger.Type == deployapi.DeploymentTriggerOnConfigChange { hasChangeTrigger = true break } } if !hasChangeTrigger { glog.V(4).Infof("Ignoring DeploymentConfig %s; no change triggers detected", deployutil.LabelForDeploymentConfig(config)) return nil } if config.LatestVersion == 0 { _, _, 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)) } c.recorder.Eventf(config, "failedCreate", "Couldn't create initial deployment: %v", err) return fmt.Errorf("couldn't create initial Deployment for DeploymentConfig %s: %v", deployutil.LabelForDeploymentConfig(config), err) } glog.V(4).Infof("Created initial Deployment for DeploymentConfig %s", 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(2).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)) } newSpec, oldSpec := config.Template.ControllerTemplate.Template.Spec, deployedConfig.Template.ControllerTemplate.Template.Spec if kapi.Semantic.DeepEqual(oldSpec, newSpec) { glog.V(2).Infof("Ignoring DeploymentConfig change for %s (latestVersion=%d); same as Deployment %s", deployutil.LabelForDeploymentConfig(config), config.LatestVersion, deployutil.LabelForDeployment(deployment)) return nil } fromVersion, toVersion, 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) } 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 }