github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/cmd/umoci/tag.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016-2020 SUSE LLC
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package main
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  
    24  	"github.com/apex/log"
    25  	"github.com/opencontainers/umoci/oci/cas/dir"
    26  	"github.com/opencontainers/umoci/oci/casext"
    27  	"github.com/pkg/errors"
    28  	"github.com/urfave/cli"
    29  )
    30  
    31  var tagAddCommand = cli.Command{
    32  	Name:  "tag",
    33  	Usage: "creates a new tag in an OCI image",
    34  	ArgsUsage: `--image <image-path>[:<tag>] <new-tag>
    35  
    36  Where "<image-path>" is the path to the OCI image, "<tag>" is the old name of
    37  the tag and "<new-tag>" is the new name of the tag.`,
    38  
    39  	// tag modifies an image layout.
    40  	Category: "image",
    41  
    42  	Action: tagAdd,
    43  
    44  	Before: func(ctx *cli.Context) error {
    45  		if ctx.NArg() != 1 {
    46  			return errors.Errorf("invalid number of positional arguments: expected <new-tag>")
    47  		}
    48  		if ctx.Args().First() == "" {
    49  			return errors.Errorf("new tag cannot be empty")
    50  		}
    51  		if !casext.IsValidReferenceName(ctx.Args().First()) {
    52  			return errors.Errorf("new tag is an invalid reference")
    53  		}
    54  		ctx.App.Metadata["new-tag"] = ctx.Args().First()
    55  		return nil
    56  	},
    57  }
    58  
    59  func tagAdd(ctx *cli.Context) error {
    60  	imagePath := ctx.App.Metadata["--image-path"].(string)
    61  	fromName := ctx.App.Metadata["--image-tag"].(string)
    62  	tagName := ctx.App.Metadata["new-tag"].(string)
    63  
    64  	// Get a reference to the CAS.
    65  	engine, err := dir.Open(imagePath)
    66  	if err != nil {
    67  		return errors.Wrap(err, "open CAS")
    68  	}
    69  	engineExt := casext.NewEngine(engine)
    70  	defer engine.Close()
    71  
    72  	// Get original descriptor.
    73  	descriptorPaths, err := engineExt.ResolveReference(context.Background(), fromName)
    74  	if err != nil {
    75  		return errors.Wrap(err, "get descriptor")
    76  	}
    77  	if len(descriptorPaths) == 0 {
    78  		return errors.Errorf("tag not found: %s", fromName)
    79  	}
    80  	if len(descriptorPaths) != 1 {
    81  		// TODO: Handle this more nicely.
    82  		return errors.Errorf("tag is ambiguous: %s", fromName)
    83  	}
    84  	descriptor := descriptorPaths[0].Descriptor()
    85  
    86  	// Add it.
    87  	if err := engineExt.UpdateReference(context.Background(), tagName, descriptor); err != nil {
    88  		return errors.Wrap(err, "put reference")
    89  	}
    90  
    91  	log.Infof("created new tag: %q -> %q", tagName, fromName)
    92  	return nil
    93  }
    94  
    95  var tagRemoveCommand = cli.Command{
    96  	Name:    "remove",
    97  	Aliases: []string{"rm"},
    98  	Usage:   "removes a tag from an OCI image",
    99  	ArgsUsage: `--image <image-path>[:<tag>]
   100  
   101  
   102  Where "<image-path>" is the path to the OCI image, "<tag>" is the name of the
   103  tag to remove.`,
   104  
   105  	// tag modifies an image layout.
   106  	Category: "image",
   107  
   108  	Before: func(ctx *cli.Context) error {
   109  		if ctx.NArg() != 0 {
   110  			return errors.Errorf("invalid number of positional arguments: expected none")
   111  		}
   112  		return nil
   113  	},
   114  
   115  	Action: tagRemove,
   116  }
   117  
   118  func tagRemove(ctx *cli.Context) error {
   119  	imagePath := ctx.App.Metadata["--image-path"].(string)
   120  	tagName := ctx.App.Metadata["--image-tag"].(string)
   121  
   122  	// Get a reference to the CAS.
   123  	engine, err := dir.Open(imagePath)
   124  	if err != nil {
   125  		return errors.Wrap(err, "open CAS")
   126  	}
   127  	engineExt := casext.NewEngine(engine)
   128  	defer engine.Close()
   129  
   130  	// Remove it.
   131  	if err := engineExt.DeleteReference(context.Background(), tagName); err != nil {
   132  		return errors.Wrap(err, "delete reference")
   133  	}
   134  
   135  	log.Infof("removed tag: %s", tagName)
   136  	return nil
   137  }
   138  
   139  var tagListCommand = cli.Command{
   140  	Name:    "list",
   141  	Aliases: []string{"ls"},
   142  	Usage:   "lists the set of tags in an OCI layout",
   143  	ArgsUsage: `--layout <image-path>
   144  
   145  Where "<image-path>" is the path to the OCI layout.
   146  
   147  Gives the full list of tags in an OCI layout, with each tag name on a single
   148  line. See umoci-stat(1) to get more information about each tagged image.`,
   149  
   150  	// tag modifies an image layout.
   151  	Category: "layout",
   152  
   153  	Before: func(ctx *cli.Context) error {
   154  		if ctx.NArg() != 0 {
   155  			return errors.Errorf("invalid number of positional arguments: expected none")
   156  		}
   157  		return nil
   158  	},
   159  
   160  	Action: tagList,
   161  }
   162  
   163  func tagList(ctx *cli.Context) error {
   164  	imagePath := ctx.App.Metadata["--image-path"].(string)
   165  
   166  	// Get a reference to the CAS.
   167  	engine, err := dir.Open(imagePath)
   168  	if err != nil {
   169  		return errors.Wrap(err, "open CAS")
   170  	}
   171  	engineExt := casext.NewEngine(engine)
   172  	defer engine.Close()
   173  
   174  	names, err := engineExt.ListReferences(context.Background())
   175  	if err != nil {
   176  		return errors.Wrap(err, "list references")
   177  	}
   178  
   179  	for _, name := range names {
   180  		fmt.Println(name)
   181  	}
   182  	return nil
   183  }