Example #1
0
func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
	isi, ok := obj.(*api.ImageStreamImport)
	if !ok {
		return nil, kapierrors.NewBadRequest(fmt.Sprintf("obj is not an ImageStreamImport: %#v", obj))
	}

	inputMeta := isi.ObjectMeta

	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")
	}

	secrets, err := r.secrets.ImageStreamSecrets(namespace).Secrets(isi.Name, kapi.ListOptions{})
	if err != nil {
		util.HandleError(fmt.Errorf("unable to load secrets for namespace %q: %v", namespace, err))
		secrets = &kapi.SecretList{}
	}

	if r.clientFn != nil {
		if client := r.clientFn(); client != nil {
			ctx = kapi.WithValue(ctx, importer.ContextKeyV1RegistryClient, client)
		}
	}
	credentials := importer.NewCredentialsForSecrets(secrets.Items)
	importCtx := importer.NewContext(r.transport).WithCredentials(credentials)

	imports := r.importFn(importCtx)
	if err := imports.Import(ctx.(gocontext.Context), isi); err != nil {
		return nil, kapierrors.NewInternalError(err)
	}

	// TODO: perform the transformation of the image stream and return it with the ISI if import is false
	//   so that clients can see what the resulting object would look like.
	if !isi.Spec.Import {
		clearManifests(isi)
		return isi, nil
	}

	create := false
	stream, err := r.streams.GetImageStream(ctx, isi.Name)
	if err != nil {
		if !kapierrors.IsNotFound(err) {
			return nil, err
		}
		// consistency check, stream must exist
		if len(inputMeta.ResourceVersion) > 0 || len(inputMeta.UID) > 0 {
			return nil, err
		}
		create = true
		stream = &api.ImageStream{
			ObjectMeta: kapi.ObjectMeta{
				Name:       isi.Name,
				Namespace:  namespace,
				Generation: 0,
			},
		}
	} else {
		if len(inputMeta.ResourceVersion) > 0 && inputMeta.ResourceVersion != stream.ResourceVersion {
			glog.V(4).Infof("DEBUG: mismatch between requested UID %s and located UID %s", inputMeta.UID, stream.UID)
			return nil, kapierrors.NewConflict("imageStream", inputMeta.Name, fmt.Errorf("the image stream was updated from %q to %q", inputMeta.ResourceVersion, stream.ResourceVersion))
		}
		if len(inputMeta.UID) > 0 && inputMeta.UID != stream.UID {
			glog.V(4).Infof("DEBUG: mismatch between requested UID %s and located UID %s", inputMeta.UID, stream.UID)
			return nil, kapierrors.NewNotFound("imageStream", inputMeta.Name)
		}
	}

	if stream.Annotations == nil {
		stream.Annotations = make(map[string]string)
	}
	now := unversioned.Now()
	stream.Annotations[api.DockerImageRepositoryCheckAnnotation] = now.UTC().Format(time.RFC3339)
	gen := stream.Generation + 1
	zero := int64(0)

	importedImages := make(map[string]error)
	updatedImages := make(map[string]*api.Image)
	if spec := isi.Spec.Repository; spec != nil {
		for i, imageStatus := range isi.Status.Repository.Images {
			image := imageStatus.Image
			if image == nil {
				continue
			}

			// update the spec tag
			ref, err := api.ParseDockerImageReference(image.DockerImageReference)
			if err != nil {
				// ???
				continue
			}
			tag := ref.Tag
			if len(imageStatus.Tag) > 0 {
				tag = imageStatus.Tag
			}
			if _, ok := stream.Spec.Tags[tag]; !ok {
				if stream.Spec.Tags == nil {
					stream.Spec.Tags = make(map[string]api.TagReference)
				}
				stream.Spec.Tags[tag] = api.TagReference{
					From: &kapi.ObjectReference{
						Kind: "DockerImage",
						Name: image.DockerImageReference,
					},
					Generation:   &gen,
					ImportPolicy: api.TagImportPolicy{Insecure: spec.ImportPolicy.Insecure},
				}
			}

			// import or reuse the image
			importErr, imported := importedImages[image.Name]
			if importErr != nil {
				api.SetTagConditions(stream, tag, newImportFailedCondition(err, gen, now))
			}

			pullSpec, _ := api.MostAccuratePullSpec(image.DockerImageReference, image.Name, "")
			api.AddTagEventToImageStream(stream, tag, api.TagEvent{
				Created:              now,
				DockerImageReference: pullSpec,
				Image:                image.Name,
				Generation:           gen,
			})

			if imported {
				if updatedImage, ok := updatedImages[image.Name]; ok {
					isi.Status.Repository.Images[i].Image = updatedImage
				}
				continue
			}

			// establish the image into the store
			updated, err := r.images.Create(ctx, image)
			switch {
			case kapierrors.IsAlreadyExists(err):
				if err := api.ImageWithMetadata(image); err != nil {
					glog.V(4).Infof("Unable to update image metadata during image import when image already exists %q: err", image.Name, err)
				}
				updated = image
				fallthrough
			case err == nil:
				updatedImage := updated.(*api.Image)
				updatedImages[image.Name] = updatedImage
				isi.Status.Repository.Images[i].Image = updatedImage
				importedImages[image.Name] = nil
			default:
				importedImages[image.Name] = err
			}
		}
	}

	for i, spec := range isi.Spec.Images {
		if spec.To == nil {
			continue
		}
		tag := spec.To.Name

		if stream.Spec.Tags == nil {
			stream.Spec.Tags = make(map[string]api.TagReference)
		}
		specTag := stream.Spec.Tags[tag]
		from := spec.From
		specTag.From = &from
		specTag.Generation = &zero
		specTag.ImportPolicy.Insecure = spec.ImportPolicy.Insecure
		stream.Spec.Tags[tag] = specTag

		status := isi.Status.Images[i]
		if status.Image == nil || status.Status.Status == unversioned.StatusFailure {
			message := status.Status.Message
			if len(message) == 0 {
				message = "unknown error prevented import"
			}
			api.SetTagConditions(stream, tag, api.TagEventCondition{
				Type:       api.ImportSuccess,
				Status:     kapi.ConditionFalse,
				Message:    message,
				Reason:     string(status.Status.Reason),
				Generation: gen,

				LastTransitionTime: now,
			})
			continue
		}
		image := status.Image

		importErr, imported := importedImages[image.Name]
		if importErr != nil {
			api.SetTagConditions(stream, tag, newImportFailedCondition(err, gen, now))
		}

		pullSpec, _ := api.MostAccuratePullSpec(image.DockerImageReference, image.Name, "")
		api.AddTagEventToImageStream(stream, tag, api.TagEvent{
			Created:              now,
			DockerImageReference: pullSpec,
			Image:                image.Name,
			Generation:           gen,
		})

		if imported {
			continue
		}
		_, err = r.images.Create(ctx, image)
		if kapierrors.IsAlreadyExists(err) {
			err = nil
		}
		importedImages[image.Name] = err
	}

	// TODO: should we allow partial failure?
	for _, err := range importedImages {
		if err != nil {
			return nil, err
		}
	}

	clearManifests(isi)

	if create {
		obj, err = r.internalStreams.Create(ctx, stream)
	} else {
		obj, _, err = r.internalStreams.Update(ctx, stream)
	}
	if err != nil {
		return nil, err
	}
	isi.Status.Import = obj.(*api.ImageStream)
	return isi, nil
}
Example #2
0
func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
	isi, ok := obj.(*api.ImageStreamImport)
	if !ok {
		return nil, kapierrors.NewBadRequest(fmt.Sprintf("obj is not an ImageStreamImport: %#v", obj))
	}

	inputMeta := isi.ObjectMeta

	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")
	}

	if r.clientFn != nil {
		if client := r.clientFn(); client != nil {
			ctx = kapi.WithValue(ctx, importer.ContextKeyV1RegistryClient, client)
		}
	}

	// only load secrets if we need them
	credentials := importer.NewLazyCredentialsForSecrets(func() ([]kapi.Secret, error) {
		secrets, err := r.secrets.ImageStreamSecrets(namespace).Secrets(isi.Name, kapi.ListOptions{})
		if err != nil {
			return nil, err
		}
		return secrets.Items, nil
	})
	importCtx := importer.NewContext(r.transport, r.insecureTransport).WithCredentials(credentials)
	imports := r.importFn(importCtx)
	if err := imports.Import(ctx.(gocontext.Context), isi); err != nil {
		return nil, kapierrors.NewInternalError(err)
	}

	// if we encountered an error loading credentials and any images could not be retrieved with an access
	// related error, modify the message.
	// TODO: set a status cause
	if err := credentials.Err(); err != nil {
		for i, image := range isi.Status.Images {
			switch image.Status.Reason {
			case unversioned.StatusReasonUnauthorized, unversioned.StatusReasonForbidden:
				isi.Status.Images[i].Status.Message = fmt.Sprintf("Unable to load secrets for this image: %v; (%s)", err, image.Status.Message)
			}
		}
		if r := isi.Status.Repository; r != nil {
			switch r.Status.Reason {
			case unversioned.StatusReasonUnauthorized, unversioned.StatusReasonForbidden:
				r.Status.Message = fmt.Sprintf("Unable to load secrets for this repository: %v; (%s)", err, r.Status.Message)
			}
		}
	}

	// TODO: perform the transformation of the image stream and return it with the ISI if import is false
	//   so that clients can see what the resulting object would look like.
	if !isi.Spec.Import {
		clearManifests(isi)
		return isi, nil
	}

	create := false
	stream, err := r.streams.GetImageStream(ctx, isi.Name)
	if err != nil {
		if !kapierrors.IsNotFound(err) {
			return nil, err
		}
		// consistency check, stream must exist
		if len(inputMeta.ResourceVersion) > 0 || len(inputMeta.UID) > 0 {
			return nil, err
		}
		create = true
		stream = &api.ImageStream{
			ObjectMeta: kapi.ObjectMeta{
				Name:       isi.Name,
				Namespace:  namespace,
				Generation: 0,
			},
		}
	} else {
		if len(inputMeta.ResourceVersion) > 0 && inputMeta.ResourceVersion != stream.ResourceVersion {
			glog.V(4).Infof("DEBUG: mismatch between requested ResourceVersion %s and located ResourceVersion %s", inputMeta.ResourceVersion, stream.ResourceVersion)
			return nil, kapierrors.NewConflict(api.Resource("imagestream"), inputMeta.Name, fmt.Errorf("the image stream was updated from %q to %q", inputMeta.ResourceVersion, stream.ResourceVersion))
		}
		if len(inputMeta.UID) > 0 && inputMeta.UID != stream.UID {
			glog.V(4).Infof("DEBUG: mismatch between requested UID %s and located UID %s", inputMeta.UID, stream.UID)
			return nil, kapierrors.NewNotFound(api.Resource("imagestream"), inputMeta.Name)
		}
	}

	if stream.Annotations == nil {
		stream.Annotations = make(map[string]string)
	}
	now := unversioned.Now()
	_, hasAnnotation := stream.Annotations[api.DockerImageRepositoryCheckAnnotation]
	nextGeneration := stream.Generation + 1

	original, err := kapi.Scheme.DeepCopy(stream)
	if err != nil {
		return nil, err
	}

	// walk the retrieved images, ensuring each one exists in etcd
	importedImages := make(map[string]error)
	updatedImages := make(map[string]*api.Image)

	if spec := isi.Spec.Repository; spec != nil {
		for i, status := range isi.Status.Repository.Images {
			if checkImportFailure(status, stream, status.Tag, nextGeneration, now) {
				continue
			}

			image := status.Image
			ref, err := api.ParseDockerImageReference(image.DockerImageReference)
			if err != nil {
				utilruntime.HandleError(fmt.Errorf("unable to parse image reference during import: %v", err))
				continue
			}
			from, err := api.ParseDockerImageReference(spec.From.Name)
			if err != nil {
				utilruntime.HandleError(fmt.Errorf("unable to parse from reference during import: %v", err))
				continue
			}

			tag := ref.Tag
			if len(status.Tag) > 0 {
				tag = status.Tag
			}
			// we've imported a set of tags, ensure spec tag will point to this for later imports
			from.ID, from.Tag = "", tag

			if updated, ok := r.importSuccessful(ctx, image, stream, tag, from.Exact(), nextGeneration, now, spec.ImportPolicy, importedImages, updatedImages); ok {
				isi.Status.Repository.Images[i].Image = updated
			}
		}
	}

	for i, spec := range isi.Spec.Images {
		if spec.To == nil {
			continue
		}
		tag := spec.To.Name

		// record a failure condition
		status := isi.Status.Images[i]
		if checkImportFailure(status, stream, tag, nextGeneration, now) {
			// ensure that we have a spec tag set
			ensureSpecTag(stream, tag, spec.From.Name, spec.ImportPolicy, false)
			continue
		}

		// record success
		image := status.Image
		if updated, ok := r.importSuccessful(ctx, image, stream, tag, spec.From.Name, nextGeneration, now, spec.ImportPolicy, importedImages, updatedImages); ok {
			isi.Status.Images[i].Image = updated
		}
	}

	// TODO: should we allow partial failure?
	for _, err := range importedImages {
		if err != nil {
			return nil, err
		}
	}

	clearManifests(isi)

	hasChanges := !kapi.Semantic.DeepEqual(original, stream)
	if create {
		stream.Annotations[api.DockerImageRepositoryCheckAnnotation] = now.UTC().Format(time.RFC3339)
		glog.V(4).Infof("create new stream: %#v", stream)
		obj, err = r.internalStreams.Create(ctx, stream)
	} else {
		if hasAnnotation && !hasChanges {
			glog.V(4).Infof("stream did not change: %#v", stream)
			obj, err = original.(*api.ImageStream), nil
		} else {
			if glog.V(4) {
				glog.V(4).Infof("updated stream %s", diff.ObjectDiff(original, stream))
			}
			stream.Annotations[api.DockerImageRepositoryCheckAnnotation] = now.UTC().Format(time.RFC3339)
			obj, _, err = r.internalStreams.Update(ctx, stream.Name, rest.DefaultUpdatedObjectInfo(stream, kapi.Scheme))
		}
	}

	if err != nil {
		return nil, err
	}
	isi.Status.Import = obj.(*api.ImageStream)
	return isi, nil
}
Example #3
0
// WithRequestInfo returns a copy of parent in which the request info value is set
func WithRequestInfo(parent api.Context, info *RequestInfo) api.Context {
	return api.WithValue(parent, requestInfoKey, info)
}