Skip to content
This repository has been archived by the owner on Aug 20, 2021. It is now read-only.

Commit

Permalink
Merge pull request #68 from dweomer/cli/images/tabular-output
Browse files Browse the repository at this point in the history
cli: images tabular listing
  • Loading branch information
dweomer authored Jan 22, 2021
2 parents 3893fb8 + 0d22479 commit cfd2a8a
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 10 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/containerd/containerd v1.4.3
github.com/containerd/cri v1.11.1-0.20200810101850-4e6644c8cf7f
github.com/containerd/typeurl v1.0.1
github.com/docker/go-units v0.4.0
github.com/gogo/googleapis v1.3.2
github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.4.3
Expand Down
85 changes: 85 additions & 0 deletions pkg/apis/services/images/images.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// adapted from https://github.com/kubernetes-sigs/cri-tools/blob/1bcad62d514c1166c9fd49557d2c5de2b05368aa/cmd/crictl/image.go

package images

import (
"sort"
"strings"

criv1 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)

type byId []*criv1.Image

func (a byId) Len() int { return len(a) }
func (a byId) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byId) Less(i, j int) bool {
return a[i].Id < a[j].Id
}

type byDigest []*criv1.Image

func (a byDigest) Len() int { return len(a) }
func (a byDigest) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byDigest) Less(i, j int) bool {
return strings.Join(a[i].RepoDigests, `_`) < strings.Join(a[j].RepoDigests, `_`)
}

func Sort(refs []*criv1.Image) {
sort.Sort(byId(refs))
sort.Sort(byDigest(refs))
}

func TruncateID(id, prefix string, n int) string {
id = strings.TrimPrefix(id, prefix)
if len(id) > n {
id = id[:n]
}
return id
}

func NormalizeRepoDigest(repoDigests []string) (string, string) {
if len(repoDigests) == 0 {
return "<none>", "<none>"
}
repoDigestPair := strings.Split(repoDigests[0], "@")
if len(repoDigestPair) != 2 {
return "errorName", "errorRepoDigest"
}
return repoDigestPair[0], repoDigestPair[1]
}

func NormalizeRepoTagPair(repoTags []string, imageName string) (repoTagPairs [][]string) {
const none = "<none>"
if len(repoTags) == 0 {
repoTagPairs = append(repoTagPairs, []string{imageName, none})
return
}
for _, repoTag := range repoTags {
idx := strings.LastIndex(repoTag, ":")
if idx == -1 {
repoTagPairs = append(repoTagPairs, []string{"errorRepoTag", "errorRepoTag"})
continue
}
name := repoTag[:idx]
if name == none {
name = imageName
}
repoTagPairs = append(repoTagPairs, []string{name, repoTag[idx+1:]})
}
return
}
104 changes: 96 additions & 8 deletions pkg/client/action/action-images.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,28 @@ package action
import (
"context"
"fmt"
"os"
"strings"
"text/tabwriter"

"github.com/docker/go-units"
"github.com/rancher/k3c/pkg/apis/services/images"
imagesv1 "github.com/rancher/k3c/pkg/apis/services/images/v1alpha1"
"github.com/rancher/k3c/pkg/client"
criv1 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)

type ListImages struct {
All bool `usage:"Ignored (compatibility)" short:"a"`
Digests bool `usage:"Show digests"`
Filter string `usage:"Filter output based on conditions provided" short:"f"`
Format string `usage:"Pretty-print images using a Go template"`
NoTrunc bool `usage:"Don't truncate output"`
Quiet bool `usage:"Only show image IDs" short:"q"`
All bool `usage:"Show all images (default hides tag-less images)" short:"a"`
Digests bool `usage:"Show digests"`
//Filter string `usage:"Filter output based on conditions provided" short:"f"`
//Format string `usage:"Pretty-print images using a Go template"`
NoTrunc bool `usage:"Don't truncate output"`
Quiet bool `usage:"Only show image IDs" short:"q"`
}

func (s *ListImages) Invoke(ctx context.Context, k8s *client.Interface, names []string) error {
return DoImages(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error {
// TODO tabular listing
req := &imagesv1.ImageListRequest{}
// TODO filtering not working as expected
if len(names) > 0 {
Expand All @@ -34,11 +38,95 @@ func (s *ListImages) Invoke(ctx context.Context, k8s *client.Interface, names []
if err != nil {
return err
}
images.Sort(res.Images)

// output in table format by default.
display := newTableDisplay(20, 1, 3, ' ', 0)
if !s.Quiet {
if s.Digests {
display.AddRow([]string{columnImage, columnTag, columnDigest, columnImageID, columnSize})
} else {
display.AddRow([]string{columnImage, columnTag, columnImageID, columnSize})
}
}
for _, image := range res.Images {
if s.Quiet {
fmt.Printf("%s\n", image.Id)
continue
}
imageName, repoDigest := images.NormalizeRepoDigest(image.RepoDigests)
repoTagPairs := images.NormalizeRepoTagPair(image.RepoTags, imageName)
size := units.HumanSizeWithPrecision(float64(image.GetSize_()), 3)
id := image.Id
if !s.NoTrunc {
id = images.TruncateID(id, "sha256:", 13)
repoDigest = images.TruncateID(repoDigest, "sha256:", 13)
}
for _, repoTagPair := range repoTagPairs {
if !s.All && repoDigest == "<none>" {
continue
}
if s.Digests {
display.AddRow([]string{repoTagPair[0], repoTagPair[1], repoDigest, id, size})
} else {
display.AddRow([]string{repoTagPair[0], repoTagPair[1], id, size})
}
}
continue
fmt.Printf("ID: %s\n", image.Id)
for _, tag := range image.RepoTags {
fmt.Println(tag)
fmt.Printf("RepoTags: %s\n", tag)
}
for _, digest := range image.RepoDigests {
fmt.Printf("RepoDigests: %s\n", digest)
}
if image.Size_ != 0 {
fmt.Printf("Size: %d\n", image.Size_)
}
if image.Uid != nil {
fmt.Printf("Uid: %v\n", image.Uid)
}
if image.Username != "" {
fmt.Printf("Username: %v\n", image.Username)
}
fmt.Printf("\n")
}
display.Flush()
return nil
})
}

const (
columnImage = "IMAGE"
columnImageID = "IMAGE ID"
columnSize = "SIZE"
columnTag = "TAG"
columnDigest = "DIGEST"
)

// display use to output something on screen with table format.
type display struct {
w *tabwriter.Writer
}

// newTableDisplay creates a display instance, and uses to format output with table.
func newTableDisplay(minwidth, tabwidth, padding int, padchar byte, flags uint) *display {
w := tabwriter.NewWriter(os.Stdout, minwidth, tabwidth, padding, padchar, 0)
return &display{w}
}

// AddRow add a row of data.
func (d *display) AddRow(row []string) {
fmt.Fprintln(d.w, strings.Join(row, "\t"))
}

// Flush output all rows on screen.
func (d *display) Flush() error {
return d.w.Flush()
}

// ClearScreen clear all output on screen.
func (d *display) ClearScreen() {
fmt.Fprint(os.Stdout, "\033[2J")
fmt.Fprint(os.Stdout, "\033[H")
}
4 changes: 2 additions & 2 deletions pkg/server/images-push.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ func (i *Interface) PushProgress(req *imagesv1.ImageProgressRequest, srv imagesv
return err
}
}
logrus.Debugf("push-progress-done: %s ", req.Image)
logrus.Debugf("push-progress-done: %s", req.Image)
return nil
}
select {
case <-timeout:
logrus.Debugf("push-progress-timeout: %s ", req.Image)
logrus.Debugf("push-progress-timeout: not tracking %s", req.Image)
return nil
case <-ctx.Done():
return ctx.Err()
Expand Down

0 comments on commit cfd2a8a

Please sign in to comment.