package controller import ( "strings" "github.com/golang/glog" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" deployapi "github.com/openshift/origin/pkg/deploy/api" imageapi "github.com/openshift/origin/pkg/image/api" ) // ImageChangeController watches for changes to ImageRepositories and regenerates // DeploymentConfigs when a new version of a tag referenced by a DeploymentConfig // is available. type ImageChangeController struct { DeploymentConfigInterface icDeploymentConfigInterface NextImageRepository func() *imageapi.ImageRepository DeploymentConfigStore cache.Store // Stop is an optional channel that controls when the controller exits Stop <-chan struct{} } type icDeploymentConfigInterface interface { UpdateDeploymentConfig(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) GenerateDeploymentConfig(namespace, name string) (*deployapi.DeploymentConfig, error) } // Run processes ImageRepository events one by one. func (c *ImageChangeController) Run() { go util.Until(c.HandleImageRepo, 0, c.Stop) } // HandleImageRepo processes the next ImageRepository event. func (c *ImageChangeController) HandleImageRepo() { imageRepo := c.NextImageRepository() configNames := []string{} firedTriggersForConfig := make(map[string][]deployapi.DeploymentTriggerImageChangeParams) for _, c := range c.DeploymentConfigStore.List() { config := c.(*deployapi.DeploymentConfig) glog.V(4).Infof("Detecting changed images for deploymentConfig %s", config.Name) // Extract relevant triggers for this imageRepo for this config triggersForConfig := []deployapi.DeploymentTriggerImageChangeParams{} for _, trigger := range config.Triggers { if trigger.Type == deployapi.DeploymentTriggerOnImageChange && trigger.ImageChangeParams.Automatic && trigger.ImageChangeParams.RepositoryName == imageRepo.DockerImageRepository { triggersForConfig = append(triggersForConfig, *trigger.ImageChangeParams) } } for _, params := range triggersForConfig { glog.V(4).Infof("Processing image triggers for deploymentConfig %s", config.Name) containerNames := util.NewStringSet(params.ContainerNames...) for _, container := range config.Template.ControllerTemplate.Template.Spec.Containers { if !containerNames.Has(container.Name) { continue } // The container image's tag name is by convention the same as the image ID it references _, containerImageID := parseImage(container.Image) if repoImageID, repoHasTag := imageRepo.Tags[params.Tag]; repoHasTag && repoImageID != containerImageID { configNames = append(configNames, config.Name) firedTriggersForConfig[config.Name] = append(firedTriggersForConfig[config.Name], params) } } } } for _, configName := range configNames { glog.V(4).Infof("Regenerating deploymentConfig %s", configName) err := c.regenerate(imageRepo.Namespace, configName, firedTriggersForConfig[configName]) if err != nil { glog.V(2).Infof("Error regenerating deploymentConfig %v: %v", configName, err) } } } func (c *ImageChangeController) regenerate(namespace, configName string, triggers []deployapi.DeploymentTriggerImageChangeParams) error { newConfig, err := c.DeploymentConfigInterface.GenerateDeploymentConfig(namespace, configName) if err != nil { glog.V(2).Infof("Error generating new version of deploymentConfig %v", configName) return err } // update the deployment config with the trigger that resulted in the new config being generated newConfig.Details = generateTriggerDetails(triggers) _, err = c.DeploymentConfigInterface.UpdateDeploymentConfig(newConfig.Namespace, newConfig) if err != nil { glog.V(2).Infof("Error updating deploymentConfig %v", configName) return err } return nil } func parseImage(name string) (string, string) { index := strings.LastIndex(name, ":") if index == -1 { return "", "" } return name[:index], name[index+1:] } func generateTriggerDetails(triggers []deployapi.DeploymentTriggerImageChangeParams) *deployapi.DeploymentDetails { // Generate the DeploymentCause objects from each DeploymentTriggerImageChangeParams object // Using separate structs to ensure flexibility in the future if these structs need to diverge causes := []*deployapi.DeploymentCause{} for _, trigger := range triggers { causes = append(causes, &deployapi.DeploymentCause{ Type: deployapi.DeploymentTriggerOnImageChange, ImageTrigger: &deployapi.DeploymentCauseImageTrigger{ RepositoryName: trigger.RepositoryName, Tag: trigger.Tag, }, }) } return &deployapi.DeploymentDetails{ Causes: causes, } }