b8c6f167 |
package cmd
import (
"fmt"
"io" |
be69ad81 |
"strings" |
59a42f10 |
"time" |
b8c6f167 |
|
011e8314 |
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" |
83c702b4 |
"k8s.io/kubernetes/pkg/fields" |
95ec120f |
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" |
83c702b4 |
"k8s.io/kubernetes/pkg/watch" |
b8c6f167 |
|
011e8314 |
"github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/cmd/cli/describe"
imageapi "github.com/openshift/origin/pkg/image/api"
"github.com/spf13/cobra"
|
b8c6f167 |
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
)
const ( |
c124965f |
importImageLong = `
Import tag and image information from an external Docker image repository |
b8c6f167 |
|
6b424a62 |
Only image streams that have a value set for spec.dockerImageRepository and/or
spec.Tags may have tag and image information imported.` |
b8c6f167 |
importImageExample = ` $ %[1]s import-image mystream`
)
// NewCmdImportImage implements the OpenShift cli import-image command.
func NewCmdImportImage(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command { |
0db7669d |
opts := &ImportImageOptions{} |
b8c6f167 |
cmd := &cobra.Command{ |
1316869b |
Use: "import-image IMAGESTREAM[:TAG]", |
b3e77d8d |
Short: "Imports images from a Docker registry",
Long: importImageLong,
Example: fmt.Sprintf(importImageExample, fullName),
SuggestFor: []string{"image"}, |
b8c6f167 |
Run: func(cmd *cobra.Command, args []string) { |
0db7669d |
kcmdutil.CheckErr(opts.Complete(f, args, out))
kcmdutil.CheckErr(opts.Validate(cmd))
kcmdutil.CheckErr(opts.Run()) |
b8c6f167 |
},
} |
1b8c4af9 |
cmd.Flags().StringVar(&opts.From, "from", "", "A Docker image repository to import images from") |
0db7669d |
cmd.Flags().BoolVar(&opts.Confirm, "confirm", false, "If true, allow the image stream import location to be set or changed")
cmd.Flags().BoolVar(&opts.All, "all", false, "If true, import all tags from the provided source on creation or if --from is specified")
cmd.Flags().BoolVar(&opts.Insecure, "insecure", false, "If true, allow importing from registries that have invalid HTTPS certificates or are hosted via HTTP") |
b8c6f167 |
return cmd
}
|
0db7669d |
// ImageImportOptions contains all the necessary information to perform an import.
type ImportImageOptions struct {
// user set values
From string
Confirm bool
All bool
Insecure bool
// internal values
Namespace string
Name string
Tag string
Target string
// helpers
out io.Writer
osClient client.Interface
isClient client.ImageStreamInterface
}
// Complete turns a partially defined ImportImageOptions into a solvent structure
// which can be validated and used for aa import.
func (o *ImportImageOptions) Complete(f *clientcmd.Factory, args []string, out io.Writer) error {
if len(args) > 0 {
o.Target = args[0] |
b8c6f167 |
}
|
ab7d732c |
namespace, _, err := f.DefaultNamespace() |
b8c6f167 |
if err != nil {
return err
} |
0db7669d |
o.Namespace = namespace |
b8c6f167 |
|
90c2aef2 |
osClient, _, err := f.Clients() |
b8c6f167 |
if err != nil {
return err
} |
0db7669d |
o.osClient = osClient
o.isClient = osClient.ImageStreams(namespace)
o.out = out |
b8c6f167 |
|
0db7669d |
return nil
}
// Validate ensures that a ImportImageOptions is valid and can be used to execute
// an import.
func (o *ImportImageOptions) Validate(cmd *cobra.Command) error {
if len(o.Target) == 0 {
return kcmdutil.UsageError(cmd, "you must specify the name of an image stream")
} |
1316869b |
|
0db7669d |
targetRef, err := imageapi.ParseDockerImageReference(o.Target) |
730f1f52 |
switch {
case err != nil: |
1316869b |
return fmt.Errorf("the image name must be a valid Docker image pull spec or reference to an image stream (e.g. myregistry/myteam/image:tag)") |
730f1f52 |
case len(targetRef.ID) > 0:
return fmt.Errorf("to import images by ID, use the 'tag' command") |
0db7669d |
case len(targetRef.Tag) != 0 && o.All: |
730f1f52 |
// error out |
0db7669d |
return fmt.Errorf("cannot specify a tag %q as well as --all", o.Target)
case len(targetRef.Tag) == 0 && !o.All: |
730f1f52 |
// apply the default tag
targetRef.Tag = imageapi.DefaultImageTag |
1316869b |
} |
0db7669d |
o.Name = targetRef.Name
o.Tag = targetRef.Tag
return nil
} |
1316869b |
|
0db7669d |
// Run contains all the necessary functionality for the OpenShift cli import-image command.
func (o *ImportImageOptions) Run() error {
stream, isi, err := o.createImageImport() |
b8c6f167 |
if err != nil { |
0db7669d |
return err
}
// TODO: add dry-run
result, err := o.isClient.Import(isi)
switch {
case err == client.ErrImageStreamImportUnsupported:
case err != nil:
return err
default:
fmt.Fprint(o.out, "The import completed successfully.\n\n")
// optimization, use the image stream returned by the call |
90c2aef2 |
d := describe.ImageStreamDescriber{Interface: o.osClient} |
0db7669d |
info, err := d.Describe(o.Namespace, stream.Name)
if err != nil { |
011e8314 |
return err
} |
730f1f52 |
|
0db7669d |
fmt.Fprintln(o.out, info)
if r := result.Status.Repository; r != nil && len(r.AdditionalTags) > 0 {
fmt.Fprintf(o.out, "\ninfo: The remote repository contained %d additional tags which were not imported: %s\n", len(r.AdditionalTags), strings.Join(r.AdditionalTags, ", "))
}
return nil
}
// Legacy path, remove when support for older importers is removed
delete(stream.Annotations, imageapi.DockerImageRepositoryCheckAnnotation)
if o.Insecure {
if stream.Annotations == nil {
stream.Annotations = make(map[string]string)
}
stream.Annotations[imageapi.InsecureRepositoryAnnotation] = "true"
}
if stream.CreationTimestamp.IsZero() {
stream, err = o.isClient.Create(stream)
} else {
stream, err = o.isClient.Update(stream)
}
if err != nil {
return err
}
fmt.Fprintln(o.out, "Importing (ctrl+c to stop waiting) ...")
resourceVersion := stream.ResourceVersion
updatedStream, err := o.waitForImport(resourceVersion)
if err != nil {
if _, ok := err.(importError); ok {
return err
}
return fmt.Errorf("unable to determine if the import completed successfully - please run 'oc describe -n %s imagestream/%s' to see if the tags were updated as expected: %v", stream.Namespace, stream.Name, err)
}
fmt.Fprint(o.out, "The import completed successfully.\n\n")
|
90c2aef2 |
d := describe.ImageStreamDescriber{Interface: o.osClient} |
0db7669d |
info, err := d.Describe(updatedStream.Namespace, updatedStream.Name)
if err != nil {
return err
}
fmt.Fprintln(o.out, info)
return nil
}
// TODO: move to image/api as a helper
type importError struct {
annotation string
}
func (e importError) Error() string {
return fmt.Sprintf("unable to import image: %s", e.annotation)
}
func (o *ImportImageOptions) waitForImport(resourceVersion string) (*imageapi.ImageStream, error) {
streamWatch, err := o.isClient.Watch(kapi.ListOptions{FieldSelector: fields.OneTermEqualSelector("metadata.name", o.Name), ResourceVersion: resourceVersion})
if err != nil {
return nil, err
}
defer streamWatch.Stop()
for {
select {
case event, ok := <-streamWatch.ResultChan():
if !ok {
return nil, fmt.Errorf("image stream watch ended prematurely")
}
switch event.Type {
case watch.Modified:
s, ok := event.Object.(*imageapi.ImageStream)
if !ok {
continue
}
annotation, ok := s.Annotations[imageapi.DockerImageRepositoryCheckAnnotation]
if !ok {
continue
}
if _, err := time.Parse(time.RFC3339, annotation); err == nil {
return s, nil
}
return nil, importError{annotation}
case watch.Deleted:
return nil, fmt.Errorf("the image stream was deleted")
case watch.Error:
return nil, fmt.Errorf("error watching image stream")
}
}
}
}
func (o *ImportImageOptions) createImageImport() (*imageapi.ImageStream, *imageapi.ImageStreamImport, error) {
stream, err := o.isClient.Get(o.Name)
from := o.From
tag := o.Tag
if err != nil {
if !errors.IsNotFound(err) {
return nil, nil, err
}
|
730f1f52 |
// the stream is new |
0db7669d |
if !o.Confirm {
return nil, nil, fmt.Errorf("no image stream named %q exists, pass --confirm to create and import", o.Name) |
011e8314 |
} |
730f1f52 |
if len(from) == 0 { |
0db7669d |
from = o.Target |
730f1f52 |
} |
0db7669d |
if o.All { |
1316869b |
stream = &imageapi.ImageStream{ |
0db7669d |
ObjectMeta: kapi.ObjectMeta{Name: o.Name}, |
1316869b |
Spec: imageapi.ImageStreamSpec{DockerImageRepository: from},
}
} else {
stream = &imageapi.ImageStream{ |
0db7669d |
ObjectMeta: kapi.ObjectMeta{Name: o.Name}, |
1316869b |
Spec: imageapi.ImageStreamSpec{
Tags: map[string]imageapi.TagReference{ |
730f1f52 |
tag: { |
1316869b |
From: &kapi.ObjectReference{
Kind: "DockerImage",
Name: from,
},
},
},
},
} |
011e8314 |
} |
1316869b |
|
011e8314 |
} else { |
730f1f52 |
// the stream already exists |
7257b7c4 |
if len(stream.Spec.DockerImageRepository) == 0 && len(stream.Spec.Tags) == 0 { |
0db7669d |
return nil, nil, fmt.Errorf("image stream has not defined anything to import") |
7257b7c4 |
} |
730f1f52 |
|
0db7669d |
if o.All { |
730f1f52 |
// importing a whole repository |
1b8c4af9 |
// TODO soltysh: write tests to cover all the possible usecases!!! |
730f1f52 |
if len(from) == 0 { |
1b8c4af9 |
if len(stream.Spec.DockerImageRepository) == 0 {
// FIXME soltysh:
return nil, nil, fmt.Errorf("flag --all is applicable only to images with spec.dockerImageRepository defined")
}
from = stream.Spec.DockerImageRepository |
730f1f52 |
}
if from != stream.Spec.DockerImageRepository { |
0db7669d |
if !o.Confirm { |
730f1f52 |
if len(stream.Spec.DockerImageRepository) == 0 { |
0db7669d |
return nil, nil, fmt.Errorf("the image stream does not currently import an entire Docker repository, pass --confirm to update") |
1316869b |
} |
0db7669d |
return nil, nil, fmt.Errorf("the image stream has a different import spec %q, pass --confirm to update", stream.Spec.DockerImageRepository) |
1316869b |
} |
730f1f52 |
stream.Spec.DockerImageRepository = from |
1316869b |
} |
730f1f52 |
|
1316869b |
} else { |
730f1f52 |
// importing a single tag
// follow any referential tags to the destination
finalTag, existing, ok, multiple := imageapi.FollowTagReference(stream, tag)
if !ok && multiple { |
0db7669d |
return nil, nil, fmt.Errorf("tag %q on the image stream is a reference to %q, which does not exist", tag, finalTag) |
730f1f52 |
}
if ok {
// disallow changing an existing tag
if existing.From == nil || existing.From.Kind != "DockerImage" { |
0db7669d |
return nil, nil, fmt.Errorf("tag %q already exists - you must use the 'tag' command if you want to change the source to %q", tag, from) |
730f1f52 |
}
if len(from) != 0 && from != existing.From.Name {
if multiple { |
0db7669d |
return nil, nil, fmt.Errorf("the tag %q points to the tag %q which points to %q - use the 'tag' command if you want to change the source to %q", tag, finalTag, existing.From.Name, from) |
730f1f52 |
} |
0db7669d |
return nil, nil, fmt.Errorf("the tag %q points to %q - use the 'tag' command if you want to change the source to %q", tag, existing.From.Name, from) |
730f1f52 |
}
// set the target item to import
from = existing.From.Name
if multiple {
tag = finalTag
}
// clear the legacy annotation
delete(existing.Annotations, imageapi.DockerImageRepositoryCheckAnnotation)
// reset the generation
zero := int64(0)
existing.Generation = &zero
|
1316869b |
} else { |
730f1f52 |
// create a new tag
if len(from) == 0 { |
1b8c4af9 |
from = stream.Spec.DockerImageRepository |
730f1f52 |
} |
6e84e1ff |
// if the from is still empty this means there's no such tag defined
// nor we can't create any from .spec.dockerImageRepository
if len(from) == 0 {
return nil, nil, fmt.Errorf("the tag %q does not exist on the image stream - choose an existing tag to import or use the 'tag' command to create a new tag", tag)
} |
730f1f52 |
existing = &imageapi.TagReference{ |
1316869b |
From: &kapi.ObjectReference{
Kind: "DockerImage",
Name: from,
}, |
011e8314 |
}
} |
730f1f52 |
stream.Spec.Tags[tag] = *existing |
011e8314 |
} |
b8c6f167 |
}
|
730f1f52 |
if len(from) == 0 {
// catch programmer error |
0db7669d |
return nil, nil, fmt.Errorf("unexpected error, from is empty") |
730f1f52 |
}
// Attempt the new, direct import path
isi := &imageapi.ImageStreamImport{
ObjectMeta: kapi.ObjectMeta{
Name: stream.Name, |
0db7669d |
Namespace: o.Namespace, |
730f1f52 |
ResourceVersion: stream.ResourceVersion,
},
Spec: imageapi.ImageStreamImportSpec{Import: true},
} |
52cfb733 |
insecureAnnotation := stream.Annotations[imageapi.InsecureRepositoryAnnotation]
insecure := o.Insecure || insecureAnnotation == "true" |
0db7669d |
if o.All { |
730f1f52 |
isi.Spec.Repository = &imageapi.RepositoryImportSpec{
From: kapi.ObjectReference{
Kind: "DockerImage",
Name: from,
}, |
52cfb733 |
ImportPolicy: imageapi.TagImportPolicy{Insecure: insecure}, |
730f1f52 |
} |
e6d5e470 |
} else { |
730f1f52 |
isi.Spec.Images = append(isi.Spec.Images, imageapi.ImageImportSpec{
From: kapi.ObjectReference{
Kind: "DockerImage",
Name: from,
},
To: &kapi.LocalObjectReference{Name: tag}, |
52cfb733 |
ImportPolicy: imageapi.TagImportPolicy{Insecure: insecure}, |
730f1f52 |
}) |
b8c6f167 |
}
|
0db7669d |
return stream, isi, nil |
b8c6f167 |
} |