166190a4 |
package cmd
import ( |
477300a4 |
"errors" |
166190a4 |
"fmt"
"io"
"strings"
|
011e8314 |
"github.com/golang/glog" |
166190a4 |
"github.com/spf13/cobra" |
b9776fd4 |
|
83c702b4 |
kapi "k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors" |
b9776fd4 |
"k8s.io/kubernetes/pkg/api/unversioned" |
867e6e44 |
kclient "k8s.io/kubernetes/pkg/client/unversioned" |
95ec120f |
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" |
3dd75654 |
"k8s.io/kubernetes/pkg/util/sets" |
166190a4 |
|
477300a4 |
"github.com/openshift/origin/pkg/client" |
166190a4 |
"github.com/openshift/origin/pkg/cmd/util/clientcmd" |
011e8314 |
imageapi "github.com/openshift/origin/pkg/image/api" |
166190a4 |
)
|
477300a4 |
// TagOptions contains all the necessary options for the cli tag command.
type TagOptions struct {
out io.Writer
osClient client.Interface
|
56d56293 |
deleteTag bool
aliasTag bool
scheduleTag bool
insecureTag bool
namespace string |
477300a4 |
ref imageapi.DockerImageReference
sourceKind string
destNamespace []string
destNameAndTag []string
}
|
166190a4 |
const ( |
c124965f |
tagLong = `
Tag existing images into image streams |
166190a4 |
The tag command allows you to take an existing tag or image from an image
stream, or a Docker image pull spec, and set it as the most recent image for a
tag in 1 or more other image streams. It is similar to the 'docker tag' |
1d958f08 |
command, but it operates on image streams instead.
Pass the --insecure flag if your external registry does not have a valid HTTPS
certificate, or is only served over HTTP. Pass --scheduled to have the server
regularly check the tag for updates and import the latest version (which can
then trigger builds and deployments). Note that --scheduled is only allowed for
Docker images.
` |
166190a4 |
|
477300a4 |
tagExample = ` # Tag the current image for the image stream 'openshift/ruby' and tag '2.0' into the image stream 'yourproject/ruby with tag 'tip'. |
af2094c4 |
%[1]s tag openshift/ruby:2.0 yourproject/ruby:tip |
166190a4 |
|
477300a4 |
# Tag a specific image. |
af2094c4 |
%[1]s tag openshift/ruby@sha256:6b646fa6bf5e5e4c7fa41056c27910e679c03ebe7f93e361e6515a9da7e258cc yourproject/ruby:tip |
166190a4 |
|
477300a4 |
# Tag an external Docker image. |
af2094c4 |
%[1]s tag --source=docker openshift/origin:latest yourproject/ruby:tip |
477300a4 |
# Remove the specified spec tag from an image stream. |
af2094c4 |
%[1]s tag openshift/origin:latest -d` |
166190a4 |
)
// NewCmdTag implements the OpenShift cli tag command.
func NewCmdTag(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command { |
477300a4 |
opts := &TagOptions{} |
166190a4 |
cmd := &cobra.Command{
Use: "tag [--source=SOURCETYPE] SOURCE DEST [DEST ...]",
Short: "Tag existing images into image streams",
Long: tagLong,
Example: fmt.Sprintf(tagExample, fullName),
Run: func(cmd *cobra.Command, args []string) { |
95ec120f |
kcmdutil.CheckErr(opts.Complete(f, cmd, args, out))
kcmdutil.CheckErr(opts.Validate())
kcmdutil.CheckErr(opts.RunTag()) |
166190a4 |
},
}
|
477300a4 |
cmd.Flags().StringVar(&opts.sourceKind, "source", opts.sourceKind, "Optional hint for the source type; valid values are 'imagestreamtag', 'istag', 'imagestreamimage', 'isimage', and 'docker'") |
eb97291e |
cmd.Flags().BoolVarP(&opts.deleteTag, "delete", "d", opts.deleteTag, "Delete the provided spec tags")
cmd.Flags().BoolVar(&opts.aliasTag, "alias", false, "Should the destination tag be updated whenever the source tag changes. Defaults to false.") |
1d958f08 |
cmd.Flags().BoolVar(&opts.scheduleTag, "scheduled", false, "Set a Docker image to be periodically imported from a remote repository.") |
56d56293 |
cmd.Flags().BoolVar(&opts.insecureTag, "insecure", false, "Set to true if importing the specified Docker image requires HTTP or has a self-signed certificate.") |
166190a4 |
return cmd
}
|
477300a4 |
func parseStreamName(defaultNamespace, name string) (string, string, error) { |
166190a4 |
if !strings.Contains(name, "/") {
return defaultNamespace, name, nil
}
parts := strings.Split(name, "/")
if len(parts) != 2 {
return "", "", fmt.Errorf("invalid image stream %q", name)
}
namespace := parts[0]
if len(namespace) == 0 {
return "", "", fmt.Errorf("invalid namespace %q for image stream %q", namespace, name)
}
streamName := parts[1]
if len(streamName) == 0 {
return "", "", fmt.Errorf("invalid name %q for image stream %q", streamName, name)
}
return namespace, streamName, nil
}
func determineSourceKind(f *clientcmd.Factory, input string) string { |
59e6f9d2 |
mapper, _ := f.Object(false) |
b9776fd4 |
gvks, err := mapper.KindsFor(unversioned.GroupVersionResource{Group: imageapi.GroupName, Resource: input}) |
166190a4 |
if err == nil { |
b9776fd4 |
return gvks[0].Kind |
166190a4 |
}
// DockerImage isn't in RESTMapper
switch strings.ToLower(input) {
case "docker", "dockerimage":
return "DockerImage"
}
return input
}
|
477300a4 |
// Complete completes all the required options for the tag command.
func (o *TagOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string, out io.Writer) error {
if len(args) < 2 && (len(args) < 1 && !o.deleteTag) { |
95ec120f |
return kcmdutil.UsageError(cmd, "you must specify a source and at least one destination or one or more tags to delete") |
166190a4 |
}
|
477300a4 |
// Setup writer.
o.out = out |
166190a4 |
|
477300a4 |
// Setup client.
var err error
o.osClient, _, err = f.Clients() |
166190a4 |
if err != nil {
return err
}
|
477300a4 |
// Setup namespace. |
867e6e44 |
if len(o.namespace) == 0 {
o.namespace, _, err = f.DefaultNamespace()
if err != nil {
return err
} |
166190a4 |
} |
477300a4 |
// Populate source.
if !o.deleteTag {
source := args[0]
glog.V(3).Infof("Using %q as a source tag", source)
|
eb97291e |
sourceKind := o.sourceKind
if len(sourceKind) > 0 {
sourceKind = determineSourceKind(f, sourceKind) |
011e8314 |
} |
eb97291e |
if len(sourceKind) > 0 { |
477300a4 |
validSources := sets.NewString("imagestreamtag", "istag", "imagestreamimage", "isimage", "docker", "dockerimage") |
eb97291e |
if !validSources.Has(strings.ToLower(sourceKind)) { |
95ec120f |
kcmdutil.CheckErr(kcmdutil.UsageError(cmd, "invalid source %q; valid values are %v", o.sourceKind, strings.Join(validSources.List(), ", "))) |
011e8314 |
}
} |
477300a4 |
|
eb97291e |
ref, err := imageapi.ParseDockerImageReference(source) |
477300a4 |
if err != nil {
return fmt.Errorf("invalid SOURCE: %v", err) |
011e8314 |
} |
eb97291e |
switch sourceKind { |
477300a4 |
case "ImageStreamTag", "ImageStreamImage": |
eb97291e |
if len(ref.Registry) > 0 { |
477300a4 |
return fmt.Errorf("server in SOURCE is only allowed when providing a Docker image")
} |
eb97291e |
if ref.Namespace == imageapi.DockerDefaultNamespace { |
867e6e44 |
ref.Namespace = o.namespace |
477300a4 |
} |
eb97291e |
if sourceKind == "ImageStreamTag" {
if len(ref.Tag) == 0 { |
477300a4 |
return fmt.Errorf("--source=ImageStreamTag requires a valid <name>:<tag> in SOURCE")
}
} else { |
eb97291e |
if len(ref.ID) == 0 { |
477300a4 |
return fmt.Errorf("--source=ImageStreamImage requires a valid <name>@<id> in SOURCE")
}
}
case "": |
eb97291e |
if len(ref.Registry) > 0 {
sourceKind = "DockerImage" |
477300a4 |
break
} |
eb97291e |
if len(ref.ID) > 0 {
sourceKind = "ImageStreamImage" |
477300a4 |
break
} |
eb97291e |
if len(ref.Tag) > 0 {
sourceKind = "ImageStreamTag"
break
}
sourceKind = "DockerImage" |
166190a4 |
}
|
eb97291e |
// if we are not aliasing the tag, specify the exact value to copy
if sourceKind == "ImageStreamTag" && !o.aliasTag {
srcNamespace := ref.Namespace
if len(srcNamespace) == 0 { |
867e6e44 |
srcNamespace = o.namespace |
eb97291e |
}
is, err := o.osClient.ImageStreams(srcNamespace).Get(ref.Name)
if err != nil {
return err
}
event := imageapi.LatestTaggedImage(is, ref.Tag)
if event == nil {
return fmt.Errorf("%q is not currently pointing to an image, cannot use it as the source of a tag", args[0])
}
if len(event.Image) == 0 {
imageRef, err := imageapi.ParseDockerImageReference(event.DockerImageReference)
if err != nil {
return fmt.Errorf("the image stream tag %q has an invalid pull spec and cannot be used to tag: %v", args[0], err)
}
ref = imageRef
sourceKind = "DockerImage"
} else {
ref.ID = event.Image
ref.Tag = ""
sourceKind = "ImageStreamImage"
}
} |
166190a4 |
|
eb97291e |
args = args[1:]
o.sourceKind = sourceKind
o.ref = ref |
477300a4 |
glog.V(3).Infof("Source tag %s %#v", o.sourceKind, o.ref)
} |
011e8314 |
|
477300a4 |
// Populate destinations.
for _, arg := range args { |
867e6e44 |
destNamespace, destNameAndTag, err := parseStreamName(o.namespace, arg) |
166190a4 |
if err != nil {
return err
} |
477300a4 |
o.destNamespace = append(o.destNamespace, destNamespace)
o.destNameAndTag = append(o.destNameAndTag, destNameAndTag)
glog.V(3).Infof("Using \"%s/%s\" as a destination tag", destNamespace, destNameAndTag)
} |
166190a4 |
|
477300a4 |
return nil
}
// Validate validates all the required options for the tag command.
func (o TagOptions) Validate() error {
// Validate client and writer
if o.osClient == nil {
return errors.New("a client is required")
}
if o.out == nil {
return errors.New("a writer interface is required")
}
|
eb97291e |
if o.deleteTag && o.aliasTag { |
0e6d8de2 |
return errors.New("--alias and --delete may not be both specified") |
eb97291e |
}
|
477300a4 |
// Validate source tag based on --delete usage.
if o.deleteTag {
if len(o.sourceKind) > 0 {
return errors.New("cannot specify a source kind when deleting")
}
if len(o.ref.String()) > 0 {
return errors.New("cannot specify a source when deleting")
} |
56d56293 |
if o.scheduleTag || o.insecureTag {
return errors.New("cannot set flags for importing images when deleting a tag")
} |
477300a4 |
} else {
if len(o.sourceKind) == 0 {
return errors.New("a source kind is required")
}
if len(o.ref.String()) == 0 {
return errors.New("a source is required")
}
}
// Validate destination tags.
if len(o.destNamespace) == 0 || len(o.destNameAndTag) == 0 {
return errors.New("at least a destination is required")
}
if len(o.destNamespace) != len(o.destNameAndTag) {
return errors.New("destination namespaces don't match with destination tags")
} |
56d56293 |
if o.sourceKind != "DockerImage" && (o.scheduleTag || o.insecureTag) {
return errors.New("only Docker images can have importing flags set")
}
if o.aliasTag && (o.scheduleTag || o.insecureTag) {
return errors.New("cannot set a Docker image tag as an alias and also set import flags")
} |
477300a4 |
return nil
}
// RunTag contains all the necessary functionality for the OpenShift cli tag command.
func (o TagOptions) RunTag() error {
for i, destNameAndTag := range o.destNameAndTag { |
166190a4 |
destName, destTag, ok := imageapi.SplitImageStreamTag(destNameAndTag)
if !ok { |
0e6d8de2 |
return fmt.Errorf("%q must be of the form <stream_name>:<tag>", destNameAndTag) |
166190a4 |
}
|
867e6e44 |
err := kclient.RetryOnConflict(kclient.DefaultRetry, func() error {
isc := o.osClient.ImageStreams(o.destNamespace[i])
target, err := isc.Get(destName)
if err != nil {
if !kerrors.IsNotFound(err) {
return err
} |
97b2b5c6 |
|
867e6e44 |
if o.deleteTag {
// Nothing to do here, continue to the next dest tag
// if there is any. |
0e6d8de2 |
fmt.Fprintf(o.out, "Image stream %q does not exist.\n", destName) |
867e6e44 |
return nil
}
// try to create the target if it doesn't exist
target = &imageapi.ImageStream{
ObjectMeta: kapi.ObjectMeta{
Name: destName,
},
} |
477300a4 |
}
|
867e6e44 |
if target.Spec.Tags == nil {
target.Spec.Tags = make(map[string]imageapi.TagReference) |
97b2b5c6 |
} |
166190a4 |
|
867e6e44 |
msg := ""
if o.deleteTag {
// The user wants to delete a spec tag.
if _, ok := target.Spec.Tags[destTag]; !ok {
return fmt.Errorf("destination tag %s/%s does not exist.\n", o.destNamespace[i], destNameAndTag)
}
delete(target.Spec.Tags, destTag)
msg = fmt.Sprintf("Deleted tag %s/%s.", o.destNamespace[i], destNameAndTag)
} else {
// The user wants to symlink a tag.
targetRef, ok := target.Spec.Tags[destTag]
if !ok {
targetRef = imageapi.TagReference{}
} |
166190a4 |
|
56d56293 |
targetRef.ImportPolicy.Insecure = o.insecureTag
targetRef.ImportPolicy.Scheduled = o.scheduleTag |
867e6e44 |
targetRef.From = &kapi.ObjectReference{
Kind: o.sourceKind,
}
localRef := o.ref
switch o.sourceKind {
case "DockerImage": |
730f1f52 |
targetRef.From.Name = localRef.Exact()
if targetRef.Generation == nil {
// for servers that do not support tag generations, we need to force re-import to fetch its metadata
delete(target.Annotations, imageapi.DockerImageRepositoryCheckAnnotation)
} else {
// for newer servers we do not need to force re-import
gen := int64(0)
targetRef.Generation = &gen
} |
56d56293 |
|
867e6e44 |
default:
targetRef.From.Name = localRef.NameString()
targetRef.From.Namespace = o.ref.Namespace
} |
166190a4 |
|
867e6e44 |
sameNamespace := o.namespace == o.destNamespace[i]
target.Spec.Tags[destTag] = targetRef
if o.aliasTag {
if sameNamespace {
msg = fmt.Sprintf("Tag %s set up to track %s.", destNameAndTag, o.ref.Exact())
} else {
msg = fmt.Sprintf("Tag %s/%s set up to track %s.", o.destNamespace[i], destNameAndTag, o.ref.Exact())
}
} else { |
56d56293 |
if targetRef.ImportPolicy.Scheduled {
if sameNamespace {
msg = fmt.Sprintf("Tag %s set to import %s periodically.", destNameAndTag, o.ref.Exact())
} else {
msg = fmt.Sprintf("Tag %s/%s set to %s periodically.", o.destNamespace[i], destNameAndTag, o.ref.Exact())
} |
867e6e44 |
} else { |
56d56293 |
if sameNamespace {
msg = fmt.Sprintf("Tag %s set to %s.", destNameAndTag, o.ref.Exact())
} else {
msg = fmt.Sprintf("Tag %s/%s set to %s.", o.destNamespace[i], destNameAndTag, o.ref.Exact())
} |
867e6e44 |
}
} |
477300a4 |
} |
166190a4 |
|
867e6e44 |
// Check the stream creation timestamp and make sure we will not
// create a new image stream while deleting.
if target.CreationTimestamp.IsZero() && !o.deleteTag {
_, err = isc.Create(target)
} else {
_, err = isc.Update(target)
}
if err != nil {
return err |
477300a4 |
} |
166190a4 |
|
867e6e44 |
fmt.Fprintln(o.out, msg)
return nil
}) |
011e8314 |
if err != nil { |
166190a4 |
return err
}
}
return nil
} |