Exemplo n.º 1
0
// 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
}
Exemplo n.º 2
0
func tagReferenceToTagEvent(stream *api.ImageStream, tagRef api.TagReference, tagOrID string) (*api.TagEvent, error) {
	switch tagRef.From.Kind {
	case "DockerImage":
		return &api.TagEvent{
			Created:              util.Now(),
			DockerImageReference: tagRef.From.Name,
		}, nil

	case "ImageStreamImage":
		ref, err := api.DockerImageReferenceForStream(stream)
		if err != nil {
			return nil, err
		}

		resolvedIDs := api.ResolveImageID(stream, tagOrID)
		switch len(resolvedIDs) {
		case 1:
			ref.ID = resolvedIDs.List()[0]
			return &api.TagEvent{
				Created:              util.Now(),
				DockerImageReference: ref.String(),
				Image:                ref.ID,
			}, nil
		case 0:
			return nil, fmt.Errorf("no images match the prefix %q", tagOrID)
		default:
			return nil, fmt.Errorf("multiple images match the prefix %q: %s", tagOrID, strings.Join(resolvedIDs.List(), ", "))
		}
	case "ImageStreamTag":
		return api.LatestTaggedImage(stream, tagOrID), nil
	default:
		return nil, fmt.Errorf("invalid from.kind %q: it must be ImageStreamImage or ImageStreamTag", tagRef.From.Kind)
	}
}
Exemplo n.º 3
0
// Resolve will attempt to find an imagestream with a name that matches the passed in value
func (r ImageStreamResolver) Resolve(value string) (*ComponentMatch, error) {
	ref, err := imageapi.ParseDockerImageReference(value)
	if err != nil || len(ref.Registry) != 0 {
		return nil, fmt.Errorf("image repositories must be of the form [<namespace>/]<name>[:<tag>|@<digest>]")
	}
	namespaces := r.Namespaces
	if len(ref.Namespace) != 0 {
		namespaces = []string{ref.Namespace}
	}
	searchTag := ref.Tag
	if len(searchTag) == 0 {
		searchTag = imageapi.DefaultImageTag
	}
	for _, namespace := range namespaces {
		glog.V(4).Infof("checking ImageStream %s/%s with ref %q", namespace, ref.Name, searchTag)
		repo, err := r.Client.ImageStreams(namespace).Get(ref.Name)
		if err != nil {
			if errors.IsNotFound(err) || errors.IsForbidden(err) {
				continue
			}
			return nil, err
		}
		ref.Namespace = namespace
		latest := imageapi.LatestTaggedImage(repo, searchTag)
		if latest == nil {
			// continue searching in the next namespace
			glog.V(2).Infof("no image recorded for %s/%s:%s", repo.Namespace, repo.Name, searchTag)
			continue
		}
		imageStreamImage, err := r.ImageStreamImages.ImageStreamImages(namespace).Get(ref.Name, latest.Image)
		if err != nil {
			if errors.IsNotFound(err) {
				// continue searching in the next namespace
				glog.V(2).Infof("tag %q is set, but image %q has been removed", searchTag, latest.Image)
				continue
			}
			return nil, err
		}
		imageData := imageStreamImage.Image

		ref.Registry = ""
		return &ComponentMatch{
			Value:       ref.String(),
			Argument:    fmt.Sprintf("--image=%q", ref.String()),
			Name:        ref.Name,
			Description: fmt.Sprintf("Image repository %s (tag %q) in project %s, tracks %q", repo.Name, searchTag, repo.Namespace, repo.Status.DockerImageRepository),
			Builder:     IsBuilderImage(&imageData.DockerImageMetadata),
			Score:       0,

			ImageStream: repo,
			Image:       &imageData.DockerImageMetadata,
			ImageTag:    searchTag,
		}, nil
	}
	return nil, ErrNoMatch{value: value}
}
Exemplo n.º 4
0
// Get retrieves an image that has been tagged by stream and tag. `id` is of the format
// <stream name>:<tag>.
func (r *REST) Get(ctx kapi.Context, id string) (runtime.Object, error) {
	name, tag, err := nameAndTag(id)
	if err != nil {
		return nil, err
	}

	stream, err := r.imageStreamRegistry.GetImageStream(ctx, name)
	if err != nil {
		return nil, err
	}

	event := api.LatestTaggedImage(stream, tag)
	if event == nil || len(event.Image) == 0 {
		return nil, errors.NewNotFound("imageStreamTag", id)
	}

	image, err := r.imageRegistry.GetImage(ctx, event.Image)
	if err != nil {
		return nil, err
	}

	// if the stream has Spec.Tags[tag].Annotations[k] = v, copy it to the image's annotations
	if stream.Spec.Tags != nil {
		if tagRef, ok := stream.Spec.Tags[tag]; ok {
			if image.Annotations == nil {
				image.Annotations = make(map[string]string)
			}
			for k, v := range tagRef.Annotations {
				image.Annotations[k] = v
			}
		}
	}

	imageWithMetadata, err := api.ImageWithMetadata(*image)
	if err != nil {
		return nil, err
	}

	ist := api.ImageStreamTag{
		ObjectMeta: kapi.ObjectMeta{
			Namespace: kapi.NamespaceValue(ctx),
			Name:      id,
		},
		Image: *imageWithMetadata,
	}
	return &ist, nil
}
Exemplo n.º 5
0
func (r *ImageStreamByAnnotationResolver) annotationMatches(stream *imageapi.ImageStream, value string) []*ComponentMatch {
	if stream.Spec.Tags == nil {
		glog.Infof("No tags found on image, returning nil")
		return nil
	}
	matches := []*ComponentMatch{}
	for tag, tagref := range stream.Spec.Tags {
		if tagref.Annotations == nil {
			continue
		}
		supports, ok := tagref.Annotations[supportsAnnotationKey]
		if !ok {
			continue
		}
		score, ok := matchSupportsAnnotation(value, supports)
		if !ok {
			continue
		}
		latest := imageapi.LatestTaggedImage(stream, tag)
		if latest == nil {
			continue
		}
		imageStream, err := r.ImageStreamImages.ImageStreamImages(stream.Namespace).Get(stream.Name, latest.Image)
		if err != nil {
			glog.V(2).Infof("Could not retrieve image stream image for stream %q, tag %q: %v", stream.Name, tag, err)
			continue
		}
		if imageStream == nil {
			continue
		}
		imageData := imageStream.Image
		match := &ComponentMatch{
			Value:       value,
			Name:        stream.Name,
			Argument:    fmt.Sprintf("--image=%q", value),
			Description: fmt.Sprintf("Image stream %s in project %s, tracks %q", stream.Name, stream.Namespace, stream.Status.DockerImageRepository),
			Builder:     IsBuilderImage(&imageData.DockerImageMetadata),
			Score:       score,

			ImageStream: stream,
			Image:       &imageData.DockerImageMetadata,
			ImageTag:    imageapi.DefaultImageTag,
		}
		matches = append(matches, match)
	}
	return matches
}
// HandleImageRepo processes the next ImageStream event.
func (c *ImageChangeController) HandleImageRepo(repo *imageapi.ImageStream) error {
	glog.V(4).Infof("Build image change controller detected ImageStream change %s", repo.Status.DockerImageRepository)

	// Loop through all build configurations and record if there was an error
	// instead of breaking the loop. The error will be returned in the end, so the
	// retry controller can retry. Any BuildConfigs that were processed successfully
	// should have had their LastTriggeredImageID updated, so the retry should result
	// in a no-op for them.
	hasError := false

	// TODO: this is inefficient
	for _, bc := range c.BuildConfigStore.List() {
		config := bc.(*buildapi.BuildConfig)

		from := buildutil.GetImageStreamForStrategy(config.Parameters.Strategy)
		if from == nil || from.Kind != "ImageStreamTag" {
			continue
		}

		shouldBuild := false
		triggeredImage := ""
		// For every ImageChange trigger find the latest tagged image from the image repository and replace that value
		// throughout the build strategies. A new build is triggered only if the latest tagged image id or pull spec
		// differs from the last triggered build recorded on the build config.
		for _, trigger := range config.Triggers {
			if trigger.Type != buildapi.ImageChangeBuildTriggerType {
				continue
			}
			fromStreamName := getImageStreamNameFromReference(from)

			fromNamespace := from.Namespace
			if len(fromNamespace) == 0 {
				fromNamespace = config.Namespace
			}

			// only trigger a build if this image repo matches the name and namespace of the ref in the build trigger
			// also do not trigger if the imagerepo does not have a valid DockerImageRepository value for us to pull
			// the image from
			if len(repo.Status.DockerImageRepository) == 0 || fromStreamName != repo.Name || fromNamespace != repo.Namespace {
				continue
			}

			// This split is safe because ImageStreamTag names always have the form
			// name:tag.
			tag := strings.Split(from.Name, ":")[1]
			latest := imageapi.LatestTaggedImage(repo, tag)
			if latest == nil {
				glog.V(4).Infof("unable to find tagged image: no image recorded for %s/%s:%s", repo.Namespace, repo.Name, tag)
				continue
			}
			glog.V(4).Infof("Found ImageStream %s/%s with tag %s", repo.Namespace, repo.Name, tag)

			// (must be different) to trigger a build
			last := trigger.ImageChange.LastTriggeredImageID
			next := latest.DockerImageReference

			if len(last) == 0 || (len(next) > 0 && next != last) {
				triggeredImage = next
				shouldBuild = true
				// it doesn't really make sense to have multiple image change triggers any more,
				// so just exit the loop now
				break
			}
		}

		if shouldBuild {
			glog.V(4).Infof("Running build for BuildConfig %s/%s", config.Namespace, config.Name)
			// instantiate new build
			request := &buildapi.BuildRequest{
				ObjectMeta: kapi.ObjectMeta{
					Name:      config.Name,
					Namespace: config.Namespace,
				},
				TriggeredByImage: &kapi.ObjectReference{
					Kind: "DockerImage",
					Name: triggeredImage,
				},
			}
			if _, err := c.BuildConfigInstantiator.Instantiate(config.Namespace, request); err != nil {
				if kerrors.IsConflict(err) {
					util.HandleError(fmt.Errorf("unable to instantiate Build for BuildConfig %s/%s due to a conflicting update: %v", config.Namespace, config.Name, err))
				} else {
					util.HandleError(fmt.Errorf("error instantiating Build from BuildConfig %s/%s: %v", config.Namespace, config.Name, err))
				}
				hasError = true
				continue
			}
		}
	}
	if hasError {
		return fmt.Errorf("an error occurred processing 1 or more build configurations; the image change trigger for image stream %s will be retried", repo.Status.DockerImageRepository)
	}
	return nil
}
// Generate returns a potential future DeploymentConfig based on the DeploymentConfig specified
// by namespace and name. Returns a RESTful error.
func (g *DeploymentConfigGenerator) Generate(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
	config, err := g.Client.GetDeploymentConfig(ctx, name)
	if err != nil {
		return nil, err
	}

	// Update the containers with new images based on defined triggers
	configChanged := false
	errs := fielderrors.ValidationErrorList{}
	causes := []*deployapi.DeploymentCause{}
	for i, trigger := range config.Triggers {
		params := trigger.ImageChangeParams

		// Only process image change triggers
		if trigger.Type != deployapi.DeploymentTriggerOnImageChange {
			continue
		}

		// Find the image repo referred to by the trigger params
		imageStream, err := g.findImageStream(config, params)
		if err != nil {
			f := fmt.Sprintf("triggers[%d].imageChange.from", i)
			v := params.From.Name
			if len(params.RepositoryName) > 0 {
				f = fmt.Sprintf("triggers[%d].imageChange.repositoryName", i)
				v = params.RepositoryName
			}
			errs = append(errs, fielderrors.NewFieldInvalid(f, v, err.Error()))
			continue
		}

		// Find the latest tag event for the trigger tag
		latestEvent := imageapi.LatestTaggedImage(imageStream, params.Tag)
		if latestEvent == nil {
			f := fmt.Sprintf("triggers[%d].imageChange.tag", i)
			errs = append(errs, fielderrors.NewFieldInvalid(f, params.Tag, fmt.Sprintf("no image recorded for %s/%s:%s", imageStream.Namespace, imageStream.Name, params.Tag)))
			continue
		}

		// Update containers
		template := config.Template.ControllerTemplate.Template
		names := util.NewStringSet(params.ContainerNames...)
		containerChanged := false
		for i := range template.Spec.Containers {
			container := &template.Spec.Containers[i]
			if !names.Has(container.Name) {
				continue
			}
			if len(latestEvent.DockerImageReference) > 0 &&
				container.Image != latestEvent.DockerImageReference {
				// Update the image
				container.Image = latestEvent.DockerImageReference
				// Log the last triggered image ID
				params.LastTriggeredImage = latestEvent.DockerImageReference
				containerChanged = true
			}
		}

		// If any container was updated, create a cause for the change
		if containerChanged {
			configChanged = true
			causes = append(causes,
				&deployapi.DeploymentCause{
					Type: deployapi.DeploymentTriggerOnImageChange,
					ImageTrigger: &deployapi.DeploymentCauseImageTrigger{
						RepositoryName: latestEvent.DockerImageReference,
						Tag:            params.Tag,
					},
				})
		}
	}

	if len(errs) > 0 {
		return nil, errors.NewInvalid("DeploymentConfig", config.Name, errs)
	}

	// Bump the version if we updated containers or if this is an initial
	// deployment
	if configChanged || config.LatestVersion == 0 {
		config.Details = &deployapi.DeploymentDetails{
			Causes: causes,
		}
		config.LatestVersion++
	}

	return config, nil
}