func validateToImageReference(reference *kapi.ObjectReference, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} kind, name, namespace := reference.Kind, reference.Name, reference.Namespace switch kind { case "ImageStreamTag": if len(name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } else if _, _, ok := imageapi.SplitImageStreamTag(name); !ok { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), name, "ImageStreamTag object references must be in the form <name>:<tag>")) } else if name, _, _ := imageapi.SplitImageStreamTag(name); len(imageapivalidation.ValidateImageStreamName(name, false)) != 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), name, "ImageStreamTag name contains invalid syntax")) } if len(namespace) != 0 && len(kvalidation.IsDNS1123Subdomain(namespace)) != 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), namespace, "namespace must be a valid subdomain")) } case "DockerImage": if len(namespace) != 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), namespace, "namespace is not valid when used with a 'DockerImage'")) } if _, err := imageapi.ParseDockerImageReference(name); err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), name, fmt.Sprintf("name is not a valid Docker pull specification: %v", err))) } case "": allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) default: allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), kind, "the target of build output must be an 'ImageStreamTag' or 'DockerImage'")) } return allErrs }
// ImageStreamReferenceIndexFunc is a default index function that indexes based on image stream references. func ImageStreamReferenceIndexFunc(obj interface{}) ([]string, error) { switch t := obj.(type) { case *buildapi.BuildConfig: var keys []string for _, trigger := range t.Spec.Triggers { if trigger.Type != buildapi.ImageChangeBuildTriggerType { continue } from := trigger.ImageChange.From // We're indexing on the imagestream referenced by the imagechangetrigger, // however buildconfigs allow one "default" imagechangetrigger in which // the ICT does not explicitly indicate the image it is triggering on, // instead it triggers on the image being used as the builder/base image // as referenced in the build strategy, so if this is an ICT w/ no // explicit image reference, use the image referenced by the strategy // because this is the default ICT. if from == nil { from = buildutil.GetInputReference(t.Spec.Strategy) if from == nil || from.Kind != "ImageStreamTag" { continue } } name, _, _ := imageapi.SplitImageStreamTag(from.Name) namespace := from.Namespace // if the imagestream reference has no namespace, use the // namespace of the buildconfig. if len(namespace) == 0 { namespace = t.Namespace } keys = append(keys, namespace+"/"+name) } if len(keys) == 0 { // Return an empty key for configs that don't hold object references. keys = append(keys, "") } return keys, nil case *deployapi.DeploymentConfig: var keys []string for _, trigger := range t.Spec.Triggers { if trigger.Type != deployapi.DeploymentTriggerOnImageChange { continue } params := trigger.ImageChangeParams name, _, _ := imageapi.SplitImageStreamTag(params.From.Name) keys = append(keys, params.From.Namespace+"/"+name) } if len(keys) == 0 { // Return an empty key for configs that don't hold object references. keys = append(keys, "") } return keys, nil } return nil, fmt.Errorf("image stream reference index not implemented for %#v", obj) }
func convert_v1_DeploymentTriggerImageChangeParams_To_api_DeploymentTriggerImageChangeParams(in *DeploymentTriggerImageChangeParams, out *newer.DeploymentTriggerImageChangeParams, s conversion.Scope) error { out.Automatic = in.Automatic out.ContainerNames = make([]string, len(in.ContainerNames)) copy(out.ContainerNames, in.ContainerNames) out.LastTriggeredImage = in.LastTriggeredImage if err := s.Convert(&in.From, &out.From, 0); err != nil { return err } switch in.From.Kind { case "DockerImage": ref, err := imageapi.ParseDockerImageReference(in.From.Name) if err != nil { return err } out.Tag = ref.Tag ref.Tag, ref.ID = "", "" out.RepositoryName = ref.String() case "ImageStreamTag": name, tag, ok := imageapi.SplitImageStreamTag(in.From.Name) if !ok { return fmt.Errorf("ImageStreamTag object references must be in the form <name>:<tag>: %s", in.From.Name) } out.From.Kind = "ImageStream" out.From.Name = name out.Tag = tag } return nil }
func convert_v1_BuildOutput_To_api_BuildOutput(in *BuildOutput, out *newer.BuildOutput, s conversion.Scope) error { if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } if in.To != nil && in.To.Kind == "ImageStreamTag" { name, tag, ok := imageapi.SplitImageStreamTag(in.To.Name) if !ok { return fmt.Errorf("ImageStreamTag object references must be in the form <name>:<tag>: %s", in.To.Name) } out.To.Kind = "ImageStream" out.To.Name = name out.Tag = tag return nil } if in.To != nil && in.To.Kind == "DockerImage" { out.To = nil if ref, err := imageapi.ParseDockerImageReference(in.To.Name); err == nil { out.Tag = ref.Tag ref.Tag = "" out.DockerImageReference = ref.String() } else { out.DockerImageReference = in.To.Name } } return nil }
func validateToImageReference(reference *kapi.ObjectReference) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} kind, name, namespace := reference.Kind, reference.Name, reference.Namespace switch kind { case "ImageStreamTag": if len(name) == 0 { allErrs = append(allErrs, fielderrors.NewFieldRequired("name")) } else if _, _, ok := imageapi.SplitImageStreamTag(name); !ok { allErrs = append(allErrs, fielderrors.NewFieldInvalid("name", name, "ImageStreamTag object references must be in the form <name>:<tag>")) } if len(namespace) != 0 && !util.IsDNS1123Subdomain(namespace) { allErrs = append(allErrs, fielderrors.NewFieldInvalid("namespace", namespace, "namespace must be a valid subdomain")) } case "DockerImage": if len(namespace) != 0 { allErrs = append(allErrs, fielderrors.NewFieldInvalid("namespace", namespace, "namespace is not valid when used with a 'DockerImage'")) } if _, err := imageapi.ParseDockerImageReference(name); err != nil { allErrs = append(allErrs, fielderrors.NewFieldInvalid("name", name, fmt.Sprintf("name is not a valid Docker pull specification: %v", err))) } case "": allErrs = append(allErrs, fielderrors.NewFieldRequired("kind")) default: allErrs = append(allErrs, fielderrors.NewFieldInvalid("kind", kind, "the target of build output must be an 'ImageStreamTag' or 'DockerImage'")) } return allErrs }
// ResolveObjectReference converts a reference into an image API or returns an error. If the kind is not recognized // this method will return an error to prevent references that may be images from being ignored. func (c *imageResolutionCache) ResolveObjectReference(ref *kapi.ObjectReference, defaultNamespace string) (*rules.ImagePolicyAttributes, error) { switch ref.Kind { case "ImageStreamTag": ns := ref.Namespace if len(ns) == 0 { ns = defaultNamespace } name, tag, ok := imageapi.SplitImageStreamTag(ref.Name) if !ok { return &rules.ImagePolicyAttributes{IntegratedRegistry: true}, fmt.Errorf("references of kind ImageStreamTag must be of the form NAME:TAG") } return c.resolveImageStreamTag(ns, name, tag) case "ImageStreamImage": ns := ref.Namespace if len(ns) == 0 { ns = defaultNamespace } name, id, ok := imageapi.SplitImageStreamImage(ref.Name) if !ok { return &rules.ImagePolicyAttributes{IntegratedRegistry: true}, fmt.Errorf("references of kind ImageStreamImage must be of the form NAME@DIGEST") } return c.resolveImageStreamImage(ns, name, id) case "DockerImage": ref, err := imageapi.ParseDockerImageReference(ref.Name) if err != nil { return nil, err } return c.resolveImageReference(ref) default: return nil, fmt.Errorf("image policy does not allow image references of kind %q", ref.Kind) } }
// AddTriggerEdges creates edges that point to named Docker image repositories for each image used in the deployment. func AddTriggerEdges(g osgraph.MutableUniqueGraph, node *deploygraph.DeploymentConfigNode) *deploygraph.DeploymentConfigNode { podTemplate := node.DeploymentConfig.Spec.Template if podTemplate == nil { return node } deployapi.EachTemplateImage( &podTemplate.Spec, deployapi.DeploymentConfigHasTrigger(node.DeploymentConfig), func(image deployapi.TemplateImage, err error) { if err != nil { return } if image.From != nil { if len(image.From.Name) == 0 { return } name, tag, _ := imageapi.SplitImageStreamTag(image.From.Name) in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(image.From.Namespace, name, tag)) g.AddEdge(in, node, TriggersDeploymentEdgeKind) return } tag := image.Ref.Tag image.Ref.Tag = "" in := imagegraph.EnsureDockerRepositoryNode(g, image.Ref.String(), tag) g.AddEdge(in, node, UsedInDeploymentEdgeKind) }) return node }
// triggerMatchesImage decides whether a given trigger for config matches the provided image stream. func triggerMatchesImage(config *deployapi.DeploymentConfig, params *deployapi.DeploymentTriggerImageChangeParams, stream *imageapi.ImageStream) bool { namespace := params.From.Namespace if len(namespace) == 0 { namespace = config.Namespace } name, _, ok := imageapi.SplitImageStreamTag(params.From.Name) return stream.Namespace == namespace && stream.Name == name && ok }
func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { istag, ok := obj.(*imageapi.ImageStreamTag) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("obj is not an ImageStreamTag: %#v", obj)) } if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { return nil, err } namespace, ok := kapi.NamespaceFrom(ctx) if !ok { return nil, kapierrors.NewBadRequest("a namespace must be specified to import images") } imageStreamName, imageTag, ok := imageapi.SplitImageStreamTag(istag.Name) if !ok { return nil, fmt.Errorf("%q must be of the form <stream_name>:<tag>", istag.Name) } target, err := r.imageStreamRegistry.GetImageStream(ctx, imageStreamName) if err != nil { if !kapierrors.IsNotFound(err) { return nil, err } // try to create the target if it doesn't exist target = &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: imageStreamName, Namespace: namespace, }, } } if target.Spec.Tags == nil { target.Spec.Tags = make(map[string]imageapi.TagReference) } // The user wants to symlink a tag. _, exists := target.Spec.Tags[imageTag] if exists { return nil, kapierrors.NewAlreadyExists(imageapi.Resource("imagestreamtag"), istag.Name) } target.Spec.Tags[imageTag] = *istag.Tag // Check the stream creation timestamp and make sure we will not // create a new image stream while deleting. if target.CreationTimestamp.IsZero() { _, err = r.imageStreamRegistry.CreateImageStream(ctx, target) } else { _, err = r.imageStreamRegistry.UpdateImageStream(ctx, target) } if err != nil { return nil, err } return istag, nil }
// AddImageStreamRefEdge ensures that a directed edge exists between an IST Node and the IS it references func AddImageStreamRefEdge(g osgraph.MutableUniqueGraph, node *imagegraph.ImageStreamTagNode) { isName, _, _ := imageapi.SplitImageStreamTag(node.Name) imageStream := &imageapi.ImageStream{} imageStream.Namespace = node.Namespace imageStream.Name = isName imageStreamNode := imagegraph.FindOrCreateSyntheticImageStreamNode(g, imageStream) g.AddEdge(node, imageStreamNode, ReferencedImageStreamGraphEdgeKind) }
func validateImageStreamTagName(istag string) error { name, _, ok := imageapi.SplitImageStreamTag(istag) if !ok { return fmt.Errorf("must be in the form of <name>:<tag>") } if reasons := imageval.ValidateImageStreamName(name, false); len(reasons) != 0 { return errors.New(strings.Join(reasons, ", ")) } return nil }
func validateImageStreamTagName(istag string) error { name, _, ok := imageapi.SplitImageStreamTag(istag) if !ok { return fmt.Errorf("invalid ImageStreamTag: %s", istag) } if reasons := imageval.ValidateImageStreamName(name, false); len(reasons) != 0 { return errors.New(strings.Join(reasons, ", ")) } return nil }
func validateImageStreamTagName(istag string) error { name, _, ok := imageapi.SplitImageStreamTag(istag) if !ok { return fmt.Errorf("invalid ImageStreamTag: %s", istag) } ok, reason := imageval.ValidateImageStreamName(name, false) if !ok { return errors.New(reason) } return nil }
func validateDockerStrategy(strategy *buildapi.DockerBuildStrategy) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} if strategy.From != nil && strategy.From.Kind == "ImageStreamTag" { if _, _, ok := imageapi.SplitImageStreamTag(strategy.From.Name); !ok { allErrs = append(allErrs, fielderrors.NewFieldInvalid("from.name", strategy.From.Name, "ImageStreamTag object references must be in the form <name>:<tag>")) } } allErrs = append(allErrs, validateSecretRef(strategy.PullSecret).Prefix("pullSecret")...) return allErrs }
func TestGenerate_fromConfigWithUpdatedImageRef(t *testing.T) { newRepoName := "registry:8080/openshift/test-image@sha256:0000000000000000000000000000000000000000000000000000000000000002" streamName := "test-image-stream" newImageID := "sha256:0000000000000000000000000000000000000000000000000000000000000002" generator := &DeploymentConfigGenerator{ Client: Client{ DCFn: func(ctx kapi.Context, id string) (*deployapi.DeploymentConfig, error) { return deploytest.OkDeploymentConfig(1), nil }, ISFn: func(ctx kapi.Context, name string) (*imageapi.ImageStream, error) { stream := makeStream( streamName, imageapi.DefaultImageTag, newRepoName, newImageID, ) return stream, nil }, }, } config, err := generator.Generate(kapi.NewDefaultContext(), "deploy1") if err != nil { t.Fatalf("Unexpected error: %v", err) } if config == nil { t.Fatalf("Expected non-nil config") } if config.Status.LatestVersion != 2 { t.Fatalf("Expected config LatestVersion=2, got %d", config.Status.LatestVersion) } if expected, actual := newRepoName, config.Spec.Template.Spec.Containers[0].Image; actual != expected { t.Fatalf("Expected container image %q, got %q", expected, actual) } if expected, actual := newRepoName, config.Spec.Triggers[0].ImageChangeParams.LastTriggeredImage; actual != expected { t.Fatalf("Expected LastTriggeredImage %q, got %q", expected, actual) } name, tag, _ := imageapi.SplitImageStreamTag(config.Status.Details.Causes[0].ImageTrigger.From.Name) if actual, expected := tag, imageapi.DefaultImageTag; actual != expected { t.Fatalf("Expected cause tag %q, got %q", expected, actual) } if actual, expected := name, streamName; actual != expected { t.Fatalf("Expected cause stream %q, got %q", expected, actual) } }
// BuildConfig adds a graph node for the specific build config if it does not exist, // and will link the build config to other nodes for the images and source repositories // it depends on. func BuildConfig(g MutableUniqueGraph, config *build.BuildConfig) graph.Node { node, found := g.FindOrCreate( UniqueName(fmt.Sprintf("%d|%s/%s", BuildConfigGraphKind, config.Namespace, config.Name)), func(node Node) graph.Node { return &BuildConfigNode{ Node: node, BuildConfig: config, } }, ) if found { return node } output := config.Parameters.Output to := output.To switch { case to != nil && len(to.Name) > 0: out := ImageStreamTag(g, defaultNamespace(to.Namespace, config.Namespace), to.Name, output.Tag) g.AddEdge(node, out, BuildOutputGraphEdgeKind) case len(output.DockerImageReference) > 0: out := DockerRepository(g, output.DockerImageReference, output.Tag) g.AddEdge(node, out, BuildOutputGraphEdgeKind) } if in, ok := SourceRepository(g, config.Parameters.Source); ok { g.AddEdge(in, node, BuildInputGraphEdgeKind) } from := buildutil.GetImageStreamForStrategy(config.Parameters.Strategy) if from != nil { switch from.Kind { case "DockerImage": if ref, err := image.ParseDockerImageReference(from.Name); err == nil { tag := ref.Tag ref.Tag = "" in := DockerRepository(g, ref.String(), tag) g.AddEdge(in, node, BuildInputImageGraphEdgeKind) } case "ImageStream": tag := image.DefaultImageTag in := ImageStreamTag(g, defaultNamespace(from.Namespace, config.Namespace), from.Name, tag) g.AddEdge(in, node, BuildInputImageGraphEdgeKind) case "ImageStreamTag": name, tag, _ := image.SplitImageStreamTag(from.Name) in := ImageStreamTag(g, defaultNamespace(from.Namespace, config.Namespace), name, tag) g.AddEdge(in, node, BuildInputImageGraphEdgeKind) case "ImageStreamImage": glog.V(4).Infof("Ignoring ImageStreamImage reference in BuildConfig %s/%s", config.Namespace, config.Name) } } return node }
func (g *DeploymentConfigGenerator) findImageStream(config *deployapi.DeploymentConfig, params *deployapi.DeploymentTriggerImageChangeParams) (*imageapi.ImageStream, error) { if len(params.From.Name) > 0 { namespace := params.From.Namespace if len(namespace) == 0 { namespace = config.Namespace } name, _, ok := imageapi.SplitImageStreamTag(params.From.Name) if !ok { return nil, fmt.Errorf("invalid ImageStreamTag: %s", params.From.Name) } return g.Client.GetImageStream(kapi.WithNamespace(kapi.NewContext(), namespace), name) } return nil, fmt.Errorf("couldn't find image stream for config %s trigger params", deployutil.LabelForDeploymentConfig(config)) }
// ResolveImagePullSpec resolves the provided source which can be "docker", "istag" or // "isimage" and returns the full Docker pull spec. func ResolveImagePullSpec(images client.ImageStreamImagesNamespacer, tags client.ImageStreamTagsNamespacer, source, name, defaultNamespace string) (string, error) { // for Docker source, just passtrough the image name if IsDocker(source) { return name, nil } // parse the namespace from the provided image namespace, image := splitNamespaceAndImage(name) if len(namespace) == 0 { namespace = defaultNamespace } dockerImageReference := "" if IsImageStreamTag(source) { name, tag, ok := imageapi.SplitImageStreamTag(image) if !ok { return "", fmt.Errorf("invalid image stream tag %q, must be of the form [NAMESPACE/]NAME:TAG", name) } if resolved, err := tags.ImageStreamTags(namespace).Get(name, tag); err != nil { return "", fmt.Errorf("failed to get image stream tag %q: %v", name, err) } else { dockerImageReference = resolved.Image.DockerImageReference } } if IsImageStreamImage(source) { name, digest, ok := imageapi.SplitImageStreamImage(image) if !ok { return "", fmt.Errorf("invalid image stream image %q, must be of the form [NAMESPACE/]NAME@DIGEST", name) } if resolved, err := images.ImageStreamImages(namespace).Get(name, digest); err != nil { return "", fmt.Errorf("failed to get image stream image %q: %v", name, err) } else { dockerImageReference = resolved.Image.DockerImageReference } } if len(dockerImageReference) == 0 { return "", fmt.Errorf("unable to resolve %s %q", source, name) } reference, err := imageapi.ParseDockerImageReference(dockerImageReference) if err != nil { return "", err } return reference.String(), nil }
func convert_v1_CustomBuildStrategy_To_api_CustomBuildStrategy(in *CustomBuildStrategy, out *newer.CustomBuildStrategy, s conversion.Scope) error { if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } if in.From != nil { switch in.From.Kind { case "ImageStream": out.From.Kind = "ImageStreamTag" out.From.Name = imageapi.JoinImageStreamTag(in.From.Name, "") case "ImageStreamTag": _, _, ok := imageapi.SplitImageStreamTag(in.From.Name) if !ok { return fmt.Errorf("ImageStreamTag object references must be in the form <name>:<tag>: %s", in.From.Name) } } } return nil }
// resolveOutputDockerImageReference returns a reference to a Docker image // computed from the buid.Spec.Output.To reference. func (bc *BuildController) resolveOutputDockerImageReference(build *buildapi.Build) (string, error) { outputTo := build.Spec.Output.To if outputTo == nil || outputTo.Name == "" { return "", nil } var ref string switch outputTo.Kind { case "DockerImage": ref = outputTo.Name case "ImageStream", "ImageStreamTag": // TODO(smarterclayton): security, ensure that the reference image stream is actually visible namespace := outputTo.Namespace if len(namespace) == 0 { namespace = build.Namespace } var tag string streamName := outputTo.Name if outputTo.Kind == "ImageStreamTag" { var ok bool streamName, tag, ok = imageapi.SplitImageStreamTag(streamName) if !ok { return "", fmt.Errorf("the referenced image stream tag is invalid: %s", outputTo.Name) } tag = ":" + tag } stream, err := bc.ImageStreamClient.GetImageStream(namespace, streamName) if err != nil { if errors.IsNotFound(err) { return "", fmt.Errorf("the referenced output image stream %s/%s does not exist", namespace, streamName) } return "", fmt.Errorf("the referenced output image stream %s/%s could not be found by build %s/%s: %v", namespace, streamName, build.Namespace, build.Name, err) } if len(stream.Status.DockerImageRepository) == 0 { e := fmt.Errorf("the image stream %s/%s cannot be used as the output for build %s/%s because the integrated Docker registry is not configured and no external registry was defined", namespace, outputTo.Name, build.Namespace, build.Name) bc.Recorder.Eventf(build, kapi.EventTypeWarning, "invalidOutput", "Error starting build: %v", e) return "", e } ref = fmt.Sprintf("%s%s", stream.Status.DockerImageRepository, tag) } return ref, nil }
// GetStreamsForConfig returns all the image streams that the provided deployment config points to. func (s *StoreToImageStreamLister) GetStreamsForConfig(config *deployapi.DeploymentConfig) []*imageapi.ImageStream { var streams []*imageapi.ImageStream for _, t := range config.Spec.Triggers { if t.Type != deployapi.DeploymentTriggerOnImageChange { continue } from := t.ImageChangeParams.From name, _, _ := imageapi.SplitImageStreamTag(from.Name) stream, err := s.ImageStreams(from.Namespace).Get(name) if err != nil { glog.Infof("Cannot retrieve image stream %s/%s: %v", from.Namespace, name, err) continue } streams = append(streams, stream) } return streams }
// buildChainInput parses user input and returns a stream name, a tag // and an error if any func buildChainInput(input string) (string, string, error) { // Split name and tag name, tag, _ := imageapi.SplitImageStreamTag(input) // Support resource type/name syntax // TODO: Use the RESTMapper to resolve this resource := strings.Split(name, "/") switch len(resource) { case 1: case 2: resourceType := resource[0] if resourceType != "ist" && resourceType != "imagestreamtag" { return "", "", fmt.Errorf("invalid resource type %q", resourceType) } default: return "", "", fmt.Errorf("invalid image stream name %q", name) } return name, tag, nil }
// RunBuildChain contains all the necessary functionality for the OpenShift // experimental build-chain command func (o *BuildChainOptions) RunBuildChain() error { ist := imagegraph.MakeImageStreamTagObjectMeta2(o.defaultNamespace, o.name) desc, err := describe.NewChainDescriber(o.c, o.namespaces, o.output).Describe(ist, !o.triggerOnly, o.reverse) if err != nil { if _, isNotFoundErr := err.(describe.NotFoundErr); isNotFoundErr { name, tag, _ := imageapi.SplitImageStreamTag(o.name) // Try to get the imageStreamTag via a direct GET if _, getErr := o.t.ImageStreamTags(o.defaultNamespace).Get(name, tag); getErr != nil { return getErr } fmt.Printf("Image stream tag %q in %q doesn't have any dependencies.\n", o.name, o.defaultNamespace) return nil } return err } fmt.Println(desc) return nil }
// AddInputOutputEdges links the build config to other nodes for the images and source repositories it depends on. func AddInputOutputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) *buildgraph.BuildConfigNode { output := node.BuildConfig.Parameters.Output to := output.To switch { case to != nil && len(to.Name) > 0: out := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(to.Namespace, node.BuildConfig.Namespace), to.Name, output.Tag)) g.AddEdge(node, out, BuildOutputEdgeKind) case len(output.DockerImageReference) > 0: out := imagegraph.EnsureDockerRepositoryNode(g, output.DockerImageReference, output.Tag) g.AddEdge(node, out, BuildOutputEdgeKind) } if in := buildgraph.EnsureSourceRepositoryNode(g, node.BuildConfig.Parameters.Source); in != nil { g.AddEdge(in, node, BuildInputEdgeKind) } from := buildutil.GetImageStreamForStrategy(node.BuildConfig.Parameters.Strategy) if from != nil { switch from.Kind { case "DockerImage": if ref, err := imageapi.ParseDockerImageReference(from.Name); err == nil { tag := ref.Tag ref.Tag = "" in := imagegraph.EnsureDockerRepositoryNode(g, ref.String(), tag) g.AddEdge(in, node, BuildInputImageEdgeKind) } case "ImageStream": tag := imageapi.DefaultImageTag in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), from.Name, tag)) g.AddEdge(in, node, BuildInputImageEdgeKind) case "ImageStreamTag": name, tag, _ := imageapi.SplitImageStreamTag(from.Name) in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), name, tag)) g.AddEdge(in, node, BuildInputImageEdgeKind) case "ImageStreamImage": glog.V(4).Infof("Ignoring ImageStreamImage reference in BuildConfig %s/%s", node.BuildConfig.Namespace, node.BuildConfig.Name) } } return node }
func OkStreamForConfig(config *deployapi.DeploymentConfig) *imageapi.ImageStream { for _, t := range config.Spec.Triggers { if t.Type != deployapi.DeploymentTriggerOnImageChange { continue } ref := t.ImageChangeParams.From name, tag, _ := imageapi.SplitImageStreamTag(ref.Name) return &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: name, Namespace: ref.Namespace, }, Status: imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ tag: { Items: []imageapi.TagEvent{{DockerImageReference: t.ImageChangeParams.LastTriggeredImage}}}}}, } } return nil }
func printTriggers(triggers []deployapi.DeploymentTriggerPolicy, w *tabwriter.Writer) { if len(triggers) == 0 { formatString(w, "Triggers", "<none>") return } labels := []string{} for _, t := range triggers { switch t.Type { case deployapi.DeploymentTriggerOnConfigChange: labels = append(labels, "Config") case deployapi.DeploymentTriggerOnImageChange: if len(t.ImageChangeParams.From.Name) > 0 { name, tag, _ := imageapi.SplitImageStreamTag(t.ImageChangeParams.From.Name) labels = append(labels, fmt.Sprintf("Image(%s@%s, auto=%v)", name, tag, t.ImageChangeParams.Automatic)) } } } desc := strings.Join(labels, ", ") formatString(w, "Triggers", desc) }
// ImageStreamReferenceIndexFunc is a default index function that indexes based on image stream references. func ImageStreamReferenceIndexFunc(obj interface{}) ([]string, error) { switch t := obj.(type) { // TODO: Add support for build configs case *deployapi.DeploymentConfig: var keys []string for _, trigger := range t.Spec.Triggers { if trigger.Type != deployapi.DeploymentTriggerOnImageChange { continue } params := trigger.ImageChangeParams name, _, _ := imageapi.SplitImageStreamTag(params.From.Name) keys = append(keys, params.From.Namespace+"/"+name) } if len(keys) == 0 { // Return an empty key for configs that don't hold object references. keys = append(keys, "") } return keys, nil } return nil, fmt.Errorf("image stream reference index not implemented for %#v", obj) }
func makeImageStreamKey(ref kapi.ObjectReference) string { name, _, _ := imageapi.SplitImageStreamTag(ref.Name) return types.NamespacedName{ref.Namespace, name}.String() }
// HandleImageStream processes the next ImageStream event. func (c *ImageChangeController) HandleImageStream(stream *imageapi.ImageStream) error { glog.V(4).Infof("Build image change controller detected ImageStream change %s", stream.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 bcs, err := c.BuildConfigIndex.GetConfigsForImageStreamTrigger(stream.Namespace, stream.Name) if err != nil { return err } for _, config := range bcs { var ( from *kapi.ObjectReference shouldBuild = false triggeredImage = "" latest *imageapi.TagEvent ) // For every ImageChange trigger find the latest tagged image from the image stream and // invoke a build using that image id. 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 that trigger for _, trigger := range config.Spec.Triggers { if trigger.Type != buildapi.ImageChangeBuildTriggerType { continue } if trigger.ImageChange.From != nil { from = trigger.ImageChange.From } else { from = buildutil.GetInputReference(config.Spec.Strategy) } if from == nil || from.Kind != "ImageStreamTag" { continue } fromStreamName, tag, ok := imageapi.SplitImageStreamTag(from.Name) if !ok { glog.Errorf("Invalid image stream tag: %s in build config %s/%s", from.Name, config.Name, config.Namespace) continue } fromNamespace := from.Namespace if len(fromNamespace) == 0 { fromNamespace = config.Namespace } // only trigger a build if this image stream matches the name and namespace of the stream 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(stream.Status.DockerImageRepository) == 0 || fromStreamName != stream.Name || fromNamespace != stream.Namespace { continue } // This split is safe because ImageStreamTag names always have the form // name:tag. latest = imageapi.LatestTaggedImage(stream, tag) if latest == nil { glog.V(4).Infof("unable to find tagged image: no image recorded for %s/%s:%s", stream.Namespace, stream.Name, tag) continue } glog.V(4).Infof("Found ImageStream %s/%s with tag %s", stream.Namespace, stream.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) buildCauses := []buildapi.BuildTriggerCause{} // instantiate new build request := &buildapi.BuildRequest{ ObjectMeta: kapi.ObjectMeta{ Name: config.Name, Namespace: config.Namespace, }, TriggeredBy: append(buildCauses, buildapi.BuildTriggerCause{ Message: "Image change", ImageChangeBuild: &buildapi.ImageChangeCause{ ImageID: latest.DockerImageReference, FromRef: from, }, }, ), TriggeredByImage: &kapi.ObjectReference{ Kind: "DockerImage", Name: triggeredImage, }, From: from, } if _, err := c.BuildConfigInstantiator.Instantiate(config.Namespace, request); err != nil { if kerrors.IsConflict(err) { utilruntime.HandleError(fmt.Errorf("unable to instantiate Build for BuildConfig %s/%s due to a conflicting update: %v", config.Namespace, config.Name, err)) } else { utilruntime.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", stream.Status.DockerImageRepository) } return nil }
// followRefToDockerImage follows a buildconfig...To/From reference until it // terminates in docker image information. This can include dereferencing chains // of ImageStreamTag references that already exist or which are being created. // ref is the reference to To/From to follow. If ref is an ImageStreamTag // that is following another ImageStreamTag, isContext should be set to the // parent IS. Finally, objects is the list of objects that new-app is creating // to support the buildconfig. It returns a reference to a terminal DockerImage // or nil if one could not be determined (a valid, non-error outcome). err // is only used to indicate that the follow encountered a severe error // (e.g malformed data). func (c *AppConfig) followRefToDockerImage(ref *kapi.ObjectReference, isContext *imageapi.ImageStream, objects app.Objects) (*kapi.ObjectReference, error) { if ref == nil { return nil, errors.New("Unable to follow nil") } if ref.Kind == "DockerImage" { // Make a shallow copy so we don't modify the ObjectReference properties that // new-app/build created. copy := *ref // Namespace should not matter here. The DockerImage URL will include project // information if it is relevant. copy.Namespace = "" // DockerImage names may or may not have a tag suffix. Add :latest if there // is no tag so that string comparison will behave as expected. if !strings.Contains(copy.Name, ":") { copy.Name += ":" + imageapi.DefaultImageTag } return ©, nil } if ref.Kind != "ImageStreamTag" { return nil, fmt.Errorf("Unable to follow reference type: %q", ref.Kind) } isNS := ref.Namespace if len(isNS) == 0 { isNS = c.OriginNamespace } // Otherwise, we are tracing an IST reference isName, isTag, ok := imageapi.SplitImageStreamTag(ref.Name) if !ok { if isContext == nil { return nil, fmt.Errorf("Unable to parse ImageStreamTag reference: %q", ref.Name) } // Otherwise, we are following a tag that references another tag in the same ImageStream. isName = isContext.Name isTag = ref.Name } else { // The imagestream is usually being created alongside the buildconfig // when new-build is being used, so scan objects being created for it. for _, check := range objects { if is2, ok := check.(*imageapi.ImageStream); ok { if is2.Name == isName { isContext = is2 break } } } if isContext == nil { var err error isContext, err = c.OSClient.ImageStreams(isNS).Get(isName) if err != nil { return nil, fmt.Errorf("Unable to check for circular build input/outputs: %v", err) } } } // Dereference ImageStreamTag to see what it is pointing to target := isContext.Spec.Tags[isTag].From if target == nil { if isContext.Spec.DockerImageRepository == "" { // Otherwise, this appears to be a new IS, created by new-app, with very little information // populated. We cannot resolve a DockerImage. return nil, nil } // Legacy InputStream without tag support? Spoof what we need. imageName := isContext.Spec.DockerImageRepository + ":" + isTag return &kapi.ObjectReference{ Kind: "DockerImage", Name: imageName, }, nil } return c.followRefToDockerImage(target, isContext, objects) }