Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions contrib/completions/bash/oc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions contrib/completions/zsh/oc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 67 additions & 14 deletions pkg/oc/cli/admin/release/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,16 @@ func NewInfo(f kcmdutil.Factory, parentName string, streams genericclioptions.IO
`),
Run: func(cmd *cobra.Command, args []string) {
kcmdutil.CheckErr(o.Complete(f, cmd, args))
kcmdutil.CheckErr(o.Validate())
kcmdutil.CheckErr(o.Run())
},
}
flags := cmd.Flags()
flags.StringVar(&o.From, "changes-from", o.From, "Show changes from this image to the requested image.")
flags.BoolVar(&o.ShowCommit, "commits", o.ShowCommit, "Display information about the source an image was created with.")
flags.BoolVar(&o.ShowPullSpec, "pullspecs", o.ShowCommit, "Display the pull spec of the image instead of the digest.")
// flags.BoolVar(&o.Verify, "verify", o.Verify, "Verify the payload is consistent by checking the referenced images.")
flags.BoolVar(&o.ShowPullSpec, "pullspecs", o.ShowCommit, "Display the pull spec of each image instead of the digest.")
flags.StringVar(&o.ImageFor, "image-for", o.ImageFor, "Print the pull spec of the specified image or an error if it does not exist.")
flags.StringVarP(&o.Output, "output", "o", o.Output, "Display the release info in an alternative format: json")
return cmd
}

Expand All @@ -69,6 +71,8 @@ type InfoOptions struct {
Images []string
From string

Output string
ImageFor string
ShowCommit bool
ShowPullSpec bool
Verify bool
Expand Down Expand Up @@ -107,6 +111,18 @@ func (o *InfoOptions) Complete(f kcmdutil.Factory, cmd *cobra.Command, args []st
return nil
}

func (o *InfoOptions) Validate() error {
if len(o.ImageFor) > 0 && len(o.Output) > 0 {
return fmt.Errorf("--output and --image-for may not both be specified")
}
switch o.Output {
case "", "json":
default:
return fmt.Errorf("--output only supports 'json'")
}
return nil
}

func (o *InfoOptions) Run() error {
if len(o.Images) == 0 {
return fmt.Errorf("must specify a release image as an argument")
Expand Down Expand Up @@ -140,16 +156,50 @@ func (o *InfoOptions) Run() error {
return describeReleaseDiff(o.Out, diff, o.ShowCommit)
}

var exitErr error
for _, image := range o.Images {
release, err := o.LoadReleaseInfo(image)
if err := o.describeImage(image); err != nil {
exitErr = kcmdutil.ErrExit
fmt.Fprintf(o.ErrOut, "error: %v\n", err)
continue
}
}
return exitErr
}

func (o *InfoOptions) describeImage(image string) error {
release, err := o.LoadReleaseInfo(image)
if err != nil {
return err
}
if len(o.Output) > 0 {
data, err := json.MarshalIndent(release, "", " ")
if err != nil {
return err
}
if err := describeReleaseInfo(o.Out, release, o.ShowCommit, o.ShowPullSpec); err != nil {
fmt.Fprintln(o.Out, string(data))
return nil
}
if len(o.ImageFor) > 0 {
spec, err := findImageSpec(release.References, o.ImageFor, image)
if err != nil {
return err
}
fmt.Fprintln(o.Out, spec)
return nil
}
return nil
return describeReleaseInfo(o.Out, release, o.ShowCommit, o.ShowPullSpec)
}

func findImageSpec(image *imageapi.ImageStream, tagName, imageName string) (string, error) {
for _, tag := range image.Spec.Tags {
if tag.Name == tagName {
if tag.From != nil && tag.From.Kind == "DockerImage" && len(tag.From.Name) > 0 {
return tag.From.Name, nil
}
}
}
return "", fmt.Errorf("no image tag %q exists in the release image %s", tagName, imageName)
}

func calculateDiff(from, to *ReleaseInfo) (*ReleaseDiff, error) {
Expand Down Expand Up @@ -233,14 +283,15 @@ type ReleaseManifestDiff struct {
}

type ReleaseInfo struct {
Image imagereference.DockerImageReference
Digest digest.Digest
Config *docker10.DockerImageConfig
Metadata *CincinnatiMetadata
References *imageapi.ImageStream

ManifestFiles map[string][]byte
UnknownFiles []string
Image string `json:"image"`
ImageRef imagereference.DockerImageReference `json:"-"`
Digest digest.Digest `json:"digest"`
Config *docker10.DockerImageConfig `json:"config"`
Metadata *CincinnatiMetadata `json:"metadata"`
References *imageapi.ImageStream `json:"references"`

ManifestFiles map[string][]byte `json:"-"`
UnknownFiles []string `json:"-"`
}

func (i *ReleaseInfo) PreferredName() string {
Expand Down Expand Up @@ -269,7 +320,9 @@ func (o *InfoOptions) LoadReleaseInfo(image string) (*ReleaseInfo, error) {
}
opts := extract.NewOptions(genericclioptions.IOStreams{Out: o.Out, ErrOut: o.ErrOut})

release := &ReleaseInfo{}
release := &ReleaseInfo{
Image: image,
}

opts.ImageMetadataCallback = func(m *extract.Mapping, dgst digest.Digest, config *docker10.DockerImageConfig) {
release.Digest = dgst
Expand Down
5 changes: 5 additions & 0 deletions pkg/oc/cli/image/append/append.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ func NewCmdAppendImage(name string, streams genericclioptions.IOStreams) *cobra.
Example: fmt.Sprintf(example, name+" append"),
Run: func(c *cobra.Command, args []string) {
kcmdutil.CheckErr(o.Complete(c, args))
kcmdutil.CheckErr(o.Validate())
kcmdutil.CheckErr(o.Run())
},
}
Expand Down Expand Up @@ -164,6 +165,10 @@ func (o *AppendImageOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}

func (o *AppendImageOptions) Validate() error {
return o.FilterOptions.Validate()
}

func (o *AppendImageOptions) Run() error {
var createdAt *time.Time
if len(o.CreatedAt) > 0 {
Expand Down
84 changes: 64 additions & 20 deletions pkg/oc/cli/image/extract/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ type TarEntryFunc func(*tar.Header, LayerInfo, io.Reader) (cont bool, err error)
type Options struct {
Mappings []Mapping

Files []string
Paths []string

OnlyFiles bool
Expand Down Expand Up @@ -125,7 +126,7 @@ type Options struct {

func NewOptions(streams genericclioptions.IOStreams) *Options {
return &Options{
Paths: []string{"/:."},
Paths: []string{},

IOStreams: streams,
MaxPerRegistry: 1,
Expand All @@ -143,6 +144,7 @@ func New(name string, streams genericclioptions.IOStreams) *cobra.Command {
Example: fmt.Sprintf(example, name+" extract"),
Run: func(c *cobra.Command, args []string) {
kcmdutil.CheckErr(o.Complete(c, args))
kcmdutil.CheckErr(o.Validate())
kcmdutil.CheckErr(o.Run())
},
}
Expand All @@ -154,6 +156,7 @@ func New(name string, streams genericclioptions.IOStreams) *cobra.Command {
flag.BoolVar(&o.DryRun, "dry-run", o.DryRun, "Print the actions that would be taken and exit without writing any contents.")
flag.BoolVar(&o.Insecure, "insecure", o.Insecure, "Allow pull operations to registries to be made over HTTP")

flag.StringSliceVar(&o.Files, "file", o.Files, "Extract the specified files to the current directory.")
flag.StringSliceVar(&o.Paths, "path", o.Paths, "Extract only part of an image. Must be SRC:DST where SRC is the path within the image and DST a local directory. If not specified the default is to extract everything to the current directory.")
flag.BoolVarP(&o.PreservePermissions, "preserve-ownership", "p", o.PreservePermissions, "Preserve the permissions of extracted files.")
flag.BoolVar(&o.OnlyFiles, "only-files", o.OnlyFiles, "Only extract regular files and directories from the image.")
Expand Down Expand Up @@ -183,11 +186,24 @@ type Mapping struct {
ConditionFn func(m *Mapping, dgst digest.Digest, imageConfig *docker10.DockerImageConfig) (bool, error)
}

func parseMappings(images, paths []string, requireEmpty bool) ([]Mapping, error) {
func parseMappings(images, paths, files []string, requireEmpty bool) ([]Mapping, error) {
layerFilter := regexp.MustCompile(`^(.*)\[([^\]]*)\](.*)$`)

var mappings []Mapping

// convert paths and files to mappings for each image
for _, image := range images {
for _, arg := range files {
if strings.HasSuffix(arg, "/") {
return nil, fmt.Errorf("invalid file: %s must not end with a slash", arg)
}
mappings = append(mappings, Mapping{
Image: image,
From: strings.TrimPrefix(arg, "/"),
To: ".",
})
}

for _, arg := range paths {
parts := strings.SplitN(arg, ":", 2)
var mapping Mapping
Expand All @@ -197,17 +213,6 @@ func parseMappings(images, paths []string, requireEmpty bool) ([]Mapping, error)
default:
return nil, fmt.Errorf("--paths must be of the form SRC:DST")
}
if matches := layerFilter.FindStringSubmatch(mapping.Image); len(matches) > 0 {
if len(matches[1]) == 0 || len(matches[2]) == 0 || len(matches[3]) != 0 {
return nil, fmt.Errorf("layer selectors must be of the form IMAGE[\\d:\\d]")
}
mapping.Image = matches[1]
var err error
mapping.LayerFilter, err = parseLayerFilter(matches[2])
if err != nil {
return nil, err
}
}
if len(mapping.From) > 0 {
mapping.From = strings.TrimPrefix(mapping.From, "/")
}
Expand Down Expand Up @@ -237,17 +242,36 @@ func parseMappings(images, paths []string, requireEmpty bool) ([]Mapping, error)
}
}
}
src, err := imagereference.Parse(mapping.Image)
mappings = append(mappings, mapping)
}
}

// extract layer filter and set the ref
for i := range mappings {
mapping := &mappings[i]

if matches := layerFilter.FindStringSubmatch(mapping.Image); len(matches) > 0 {
if len(matches[1]) == 0 || len(matches[2]) == 0 || len(matches[3]) != 0 {
return nil, fmt.Errorf("layer selectors must be of the form IMAGE[\\d:\\d]")
}
mapping.Image = matches[1]
var err error
mapping.LayerFilter, err = parseLayerFilter(matches[2])
if err != nil {
return nil, err
}
if len(src.Tag) == 0 && len(src.ID) == 0 {
return nil, fmt.Errorf("source image must point to an image ID or image tag")
}
mapping.ImageRef = src
mappings = append(mappings, mapping)
}

src, err := imagereference.Parse(mapping.Image)
if err != nil {
return nil, err
}
if len(src.Tag) == 0 && len(src.ID) == 0 {
return nil, fmt.Errorf("source image must point to an image ID or image tag")
}
mapping.ImageRef = src
}

return mappings, nil
}

Expand All @@ -260,14 +284,25 @@ func (o *Options) Complete(cmd *cobra.Command, args []string) error {
return fmt.Errorf("you must specify at least one image to extract as an argument")
}

if len(o.Paths) == 0 && len(o.Files) == 0 {
o.Paths = append(o.Paths, "/:.")
}

var err error
o.Mappings, err = parseMappings(args, o.Paths, !o.Confirm && !o.DryRun)
o.Mappings, err = parseMappings(args, o.Paths, o.Files, !o.Confirm && !o.DryRun)
if err != nil {
return err
}
return nil
}

func (o *Options) Validate() error {
if len(o.Mappings) == 0 {
return fmt.Errorf("you must specify one or more paths or files")
}
return o.FilterOptions.Validate()
}

func (o *Options) Run() error {
rt, err := rest.TransportFor(&rest.Config{})
if err != nil {
Expand Down Expand Up @@ -296,6 +331,15 @@ func (o *Options) Run() error {

srcManifest, srcDigest, location, err := imagemanifest.FirstManifest(ctx, from, repo, o.FilterOptions.Include)
if err != nil {
if imagemanifest.IsImageForbidden(err) {
var msg string
if len(o.Mappings) == 1 {
msg = "image does not exist or you don't have permission to access the repository"
} else {
msg = fmt.Sprintf("image %q does not exist or you don't have permission to access the repository", from)
}
return imagemanifest.NewImageForbidden(msg, err)
}
if imagemanifest.IsImageNotFound(err) {
var msg string
if len(o.Mappings) == 1 {
Expand Down
8 changes: 5 additions & 3 deletions pkg/oc/cli/image/info/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func NewInfo(parentName string, streams genericclioptions.IOStreams) *cobra.Comm
`),
Run: func(cmd *cobra.Command, args []string) {
kcmdutil.CheckErr(o.Complete(cmd, args))
kcmdutil.CheckErr(o.Validate())
kcmdutil.CheckErr(o.Run())
},
}
Expand All @@ -74,13 +75,14 @@ func (o *InfoOptions) Complete(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("info expects at least one argument, an image pull spec")
}
if err := o.FilterOptions.Validate(); err != nil {
return err
}
o.Images = args
return nil
}

func (o *InfoOptions) Validate() error {
return o.FilterOptions.Validate()
}

func (o *InfoOptions) Run() error {
if len(o.Images) == 0 {
return fmt.Errorf("must specify one or more images as arguments")
Expand Down
Loading