From d35e13c2d2547f43c9a0f251b76d00f858e038a9 Mon Sep 17 00:00:00 2001 From: Jun Chen Date: Tue, 26 Jan 2021 11:00:41 +0800 Subject: [PATCH] feat(cmd): impl ormb models command (#172) * feat(cmd): impl ormb models command * chore(*): update vendor --- cmd/ormb/cmd/model.go | 36 +++ go.mod | 1 + go.sum | 2 + pkg/oras/client.go | 39 ++- pkg/oras/interface.go | 1 + pkg/oras/mock/mock.go | 14 + pkg/ormb/mock/mock_ormb.go | 14 + pkg/ormb/ormb.go | 5 + pkg/util/bytes/byte.go | 4 +- vendor/github.com/xeonx/timeago/.travis.yml | 1 + vendor/github.com/xeonx/timeago/LICENSE | 20 ++ vendor/github.com/xeonx/timeago/README.md | 31 ++ vendor/github.com/xeonx/timeago/timeago.go | 308 ++++++++++++++++++++ vendor/modules.txt | 2 + 14 files changed, 475 insertions(+), 3 deletions(-) create mode 100644 cmd/ormb/cmd/model.go create mode 100644 vendor/github.com/xeonx/timeago/.travis.yml create mode 100644 vendor/github.com/xeonx/timeago/LICENSE create mode 100644 vendor/github.com/xeonx/timeago/README.md create mode 100644 vendor/github.com/xeonx/timeago/timeago.go diff --git a/cmd/ormb/cmd/model.go b/cmd/ormb/cmd/model.go new file mode 100644 index 00000000..3da0e8a7 --- /dev/null +++ b/cmd/ormb/cmd/model.go @@ -0,0 +1,36 @@ +/* +Copyright © 2020 NAME HERE + +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. +*/ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// modelsCmd represents the pull command +var modelsCmd = &cobra.Command{ + Use: "models", + Short: "List localhost models", + Long: ``, + PreRunE: preRunE, + RunE: func(cmd *cobra.Command, args []string) error { + + return ormbClient.Models() + }, +} + +func init() { + rootCmd.AddCommand(modelsCmd) +} diff --git a/go.mod b/go.mod index 6ce4d334..376d3c02 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.5.1 // indirect + github.com/xeonx/timeago v1.0.0-rc4 github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 // indirect github.com/yvasiyarov/gorelic v0.0.7 // indirect github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect diff --git a/go.sum b/go.sum index 13e6ea43..b045d8b8 100644 --- a/go.sum +++ b/go.sum @@ -388,6 +388,8 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeonx/timeago v1.0.0-rc4 h1:9rRzv48GlJC0vm+iBpLcWAr8YbETyN9Vij+7h2ammz4= +github.com/xeonx/timeago v1.0.0-rc4/go.mod h1:qDLrYEFynLO7y5Ho7w3GwgtYgpy5UfhcXIIQvMKVDkA= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/pkg/oras/client.go b/pkg/oras/client.go index bce0722e..12e4e1af 100644 --- a/pkg/oras/client.go +++ b/pkg/oras/client.go @@ -8,8 +8,8 @@ import ( "io/ioutil" "net/http" "path" + "strconv" - auth "github.com/deislabs/oras/pkg/auth/docker" "github.com/deislabs/oras/pkg/oras" "github.com/kleveross/ormb/pkg/consts" "github.com/kleveross/ormb/pkg/model" @@ -19,7 +19,10 @@ import ( bts "github.com/kleveross/ormb/pkg/util/bytes" "github.com/kleveross/ormb/pkg/util/ctx" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + + auth "github.com/deislabs/oras/pkg/auth/docker" "github.com/pkg/errors" + "github.com/xeonx/timeago" ) const ( @@ -235,6 +238,40 @@ func (c *Client) LoadModel(ref *oci.Reference) (*model.Model, error) { return r.Model, nil } +func (c *Client) Models() error { + refs, err := c.cache.ListReferences() + if err != nil { + return err + } + + maxLen := []int{0, 0, 12, 20, 0} + for _, ref := range refs { + if len(ref.Repo) > maxLen[0] { + maxLen[0] = len(ref.Repo) + } + if len(ref.Tag) > maxLen[1] { + maxLen[1] = len(ref.Tag) + } + + size := bts.ByteCountBinary(ref.Size) + if len(size) > maxLen[4] { + maxLen[4] = len(size) + } + } + + format := "%-" + strconv.Itoa(maxLen[0]) + "s " // repo name + format += "%-" + strconv.Itoa(maxLen[1]) + "s " // tag + format += "%-" + strconv.Itoa(maxLen[2]) + "s " // digest + format += "%-" + strconv.Itoa(maxLen[3]) + "s " // create at + format += "%-" + strconv.Itoa(maxLen[4]) + "s\n" // size + fmt.Fprintf(c.out, format, "REPOSITORY", "TAG", "MODEL ID", "CREATED", "SIZE") + + for _, ref := range refs { + fmt.Fprintf(c.out, format, ref.Repo, ref.Tag, ref.Digest.Hex()[0:12], timeago.English.Format(ref.CreatedAt), bts.ByteCountBinary(ref.Size)) + } + return nil +} + // printCacheRefSummary prints out model ref summary func (c *Client) printCacheRefSummary(r *cache.CacheRefSummary) { fmt.Fprintf(c.out, "ref: %s\n", r.Name) diff --git a/pkg/oras/interface.go b/pkg/oras/interface.go index f427f31d..89239854 100644 --- a/pkg/oras/interface.go +++ b/pkg/oras/interface.go @@ -15,4 +15,5 @@ type Interface interface { PullModel(ref *oci.Reference) error LoadModel(ref *oci.Reference) (*model.Model, error) TagModel(ref *oci.Reference, target *oci.Reference) error + Models() error } diff --git a/pkg/oras/mock/mock.go b/pkg/oras/mock/mock.go index 9ed51f80..91c752ba 100644 --- a/pkg/oras/mock/mock.go +++ b/pkg/oras/mock/mock.go @@ -146,3 +146,17 @@ func (mr *MockInterfaceMockRecorder) TagModel(ref, target interface{}) *gomock.C mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagModel", reflect.TypeOf((*MockInterface)(nil).TagModel), ref, target) } + +// Models mocks base method +func (m *MockInterface) Models() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Models") + ret0, _ := ret[0].(error) + return ret0 +} + +// Models indicates an expected call of Models +func (mr *MockInterfaceMockRecorder) Models() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Models", reflect.TypeOf((*MockInterface)(nil).Models)) +} diff --git a/pkg/ormb/mock/mock_ormb.go b/pkg/ormb/mock/mock_ormb.go index cb5ef95a..a17832c2 100644 --- a/pkg/ormb/mock/mock_ormb.go +++ b/pkg/ormb/mock/mock_ormb.go @@ -129,3 +129,17 @@ func (mr *MockInterfaceMockRecorder) Remove(refStr interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockInterface)(nil).Remove), refStr) } + +// Models mocks base method +func (m *MockInterface) Models() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Models") + ret0, _ := ret[0].(error) + return ret0 +} + +// Models indicates an expected call of Models +func (mr *MockInterfaceMockRecorder) Models() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Models", reflect.TypeOf((*MockInterface)(nil).Models)) +} diff --git a/pkg/ormb/ormb.go b/pkg/ormb/ormb.go index db6863da..0aa6b415 100644 --- a/pkg/ormb/ormb.go +++ b/pkg/ormb/ormb.go @@ -20,6 +20,7 @@ type Interface interface { Save(src, refStr string) error Tag(refStr, targetStr string) error Remove(refStr string) error + Models() error } type ORMB struct { @@ -128,3 +129,7 @@ func (o ORMB) Remove(refStr string) error { return o.client.RemoveModel(ref) } + +func (o ORMB) Models() error { + return o.client.Models() +} diff --git a/pkg/util/bytes/byte.go b/pkg/util/bytes/byte.go index f8e22d05..26f95a68 100644 --- a/pkg/util/bytes/byte.go +++ b/pkg/util/bytes/byte.go @@ -6,12 +6,12 @@ import "fmt" func ByteCountBinary(b int64) string { const unit = 1024 if b < unit { - return fmt.Sprintf("%d B", b) + return fmt.Sprintf("%dB", b) } div, exp := int64(unit), 0 for n := b / unit; n >= unit; n /= unit { div *= unit exp++ } - return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) + return fmt.Sprintf("%.1f%cB", float64(b)/float64(div), "KMGTPE"[exp]) } diff --git a/vendor/github.com/xeonx/timeago/.travis.yml b/vendor/github.com/xeonx/timeago/.travis.yml new file mode 100644 index 00000000..eb93f278 --- /dev/null +++ b/vendor/github.com/xeonx/timeago/.travis.yml @@ -0,0 +1 @@ +language: go \ No newline at end of file diff --git a/vendor/github.com/xeonx/timeago/LICENSE b/vendor/github.com/xeonx/timeago/LICENSE new file mode 100644 index 00000000..9cacc745 --- /dev/null +++ b/vendor/github.com/xeonx/timeago/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Simon HEGE + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/xeonx/timeago/README.md b/vendor/github.com/xeonx/timeago/README.md new file mode 100644 index 00000000..616e7517 --- /dev/null +++ b/vendor/github.com/xeonx/timeago/README.md @@ -0,0 +1,31 @@ +# timeago - A time formatting package + +## Install + + go get github.com/xeonx/timeago + +## Docs + + + +## Use + + package main + + import ( + "time" + "github.com/xeonx/timeago" + ) + + func main() { + t := time.Now().Add(42 * time.Second) + + s := timeago.English.Format(t) + //s will contains "less than a minute ago" + + //... + } + +## Tests + +`go test` is used for testing. \ No newline at end of file diff --git a/vendor/github.com/xeonx/timeago/timeago.go b/vendor/github.com/xeonx/timeago/timeago.go new file mode 100644 index 00000000..7ebdead6 --- /dev/null +++ b/vendor/github.com/xeonx/timeago/timeago.go @@ -0,0 +1,308 @@ +// Copyright 2013 Simon HEGE. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//timeago allows the formatting of time in terms of fuzzy timestamps. +//For example: +// one minute ago +// 3 years ago +// in 2 minutes +package timeago + +import ( + "fmt" + "strings" + "time" +) + +const ( + Day time.Duration = time.Hour * 24 + Month time.Duration = Day * 30 + Year time.Duration = Day * 365 +) + +type FormatPeriod struct { + D time.Duration + One string + Many string +} + +//Config allows the customization of timeago. +//You may configure string items (language, plurals, ...) and +//maximum allowed duration value for fuzzy formatting. +type Config struct { + PastPrefix string + PastSuffix string + FuturePrefix string + FutureSuffix string + + Periods []FormatPeriod + + Zero string + Max time.Duration //Maximum duration for using the special formatting. + //DefaultLayout is used if delta is greater than the minimum of last period + //in Periods and Max. It is the desired representation of the date 2nd of + // January 2006. + DefaultLayout string +} + +//Predefined english configuration +var English = Config{ + PastPrefix: "", + PastSuffix: " ago", + FuturePrefix: "in ", + FutureSuffix: "", + + Periods: []FormatPeriod{ + FormatPeriod{time.Second, "about a second", "%d seconds"}, + FormatPeriod{time.Minute, "about a minute", "%d minutes"}, + FormatPeriod{time.Hour, "about an hour", "%d hours"}, + FormatPeriod{Day, "one day", "%d days"}, + FormatPeriod{Month, "one month", "%d months"}, + FormatPeriod{Year, "one year", "%d years"}, + }, + + Zero: "about a second", + + Max: 73 * time.Hour, + DefaultLayout: "2006-01-02", +} + +var Portuguese = Config{ + PastPrefix: "há ", + PastSuffix: "", + FuturePrefix: "daqui a ", + FutureSuffix: "", + + Periods: []FormatPeriod{ + FormatPeriod{time.Second, "um segundo", "%d segundos"}, + FormatPeriod{time.Minute, "um minuto", "%d minutos"}, + FormatPeriod{time.Hour, "uma hora", "%d horas"}, + FormatPeriod{Day, "um dia", "%d dias"}, + FormatPeriod{Month, "um mês", "%d meses"}, + FormatPeriod{Year, "um ano", "%d anos"}, + }, + + Zero: "menos de um segundo", + + Max: 73 * time.Hour, + DefaultLayout: "02-01-2006", +} + +var Chinese = Config{ + PastPrefix: "", + PastSuffix: "前", + FuturePrefix: "于 ", + FutureSuffix: "", + + Periods: []FormatPeriod{ + FormatPeriod{time.Second, "1 秒", "%d 秒"}, + FormatPeriod{time.Minute, "1 分钟", "%d 分钟"}, + FormatPeriod{time.Hour, "1 小时", "%d 小时"}, + FormatPeriod{Day, "1 天", "%d 天"}, + FormatPeriod{Month, "1 月", "%d 月"}, + FormatPeriod{Year, "1 年", "%d 年"}, + }, + + Zero: "1 秒", + + Max: 73 * time.Hour, + DefaultLayout: "2006-01-02", +} + +//Predefined french configuration +var French = Config{ + PastPrefix: "il y a ", + PastSuffix: "", + FuturePrefix: "dans ", + FutureSuffix: "", + + Periods: []FormatPeriod{ + FormatPeriod{time.Second, "environ une seconde", "moins d'une minute"}, + FormatPeriod{time.Minute, "environ une minute", "%d minutes"}, + FormatPeriod{time.Hour, "environ une heure", "%d heures"}, + FormatPeriod{Day, "un jour", "%d jours"}, + FormatPeriod{Month, "un mois", "%d mois"}, + FormatPeriod{Year, "un an", "%d ans"}, + }, + + Zero: "environ une seconde", + + Max: 73 * time.Hour, + DefaultLayout: "02/01/2006", +} + +//Predefined german configuration +var German = Config{ + PastPrefix: "vor ", + PastSuffix: "", + FuturePrefix: "in ", + FutureSuffix: "", + + Periods: []FormatPeriod{ + FormatPeriod{time.Second, "einer Sekunde", "%d Sekunden"}, + FormatPeriod{time.Minute, "einer Minute", "%d Minuten"}, + FormatPeriod{time.Hour, "einer Stunde", "%d Stunden"}, + FormatPeriod{Day, "einem Tag", "%d Tagen"}, + FormatPeriod{Month, "einem Monat", "%d Monaten"}, + FormatPeriod{Year, "einem Jahr", "%d Jahren"}, + }, + + Zero: "einer Sekunde", + + Max: 73 * time.Hour, + DefaultLayout: "02.01.2006", +} + +//Predefined turkish configuration +var Turkish = Config{ + PastPrefix: "", + PastSuffix: " önce", + FuturePrefix: "", + FutureSuffix: " içinde", + + Periods: []FormatPeriod{ + FormatPeriod{time.Second, "yaklaşık bir saniye", "%d saniye"}, + FormatPeriod{time.Minute, "yaklaşık bir dakika", "%d dakika"}, + FormatPeriod{time.Hour, "yaklaşık bir saat", "%d saat"}, + FormatPeriod{Day, "bir gün", "%d gün"}, + FormatPeriod{Month, "bir ay", "%d ay"}, + FormatPeriod{Year, "bir yıl", "%d yıl"}, + }, + + Zero: "yaklaşık bir saniye", + + Max: 73 * time.Hour, + DefaultLayout: "02/01/2006", +} + +// Korean support +var Korean = Config{ + PastPrefix: "", + PastSuffix: " 전", + FuturePrefix: "", + FutureSuffix: " 후", + + Periods: []FormatPeriod{ + FormatPeriod{time.Second, "약 1초", "%d초"}, + FormatPeriod{time.Minute, "약 1분", "%d분"}, + FormatPeriod{time.Hour, "약 한시간", "%d시간"}, + FormatPeriod{Day, "하루", "%d일"}, + FormatPeriod{Month, "1개월", "%d개월"}, + FormatPeriod{Year, "1년", "%d년"}, + }, + + Zero: "방금", + + Max: 10 * 365 * 24 * time.Hour, + DefaultLayout: "2006-01-02", +} + + +//Format returns a textual representation of the time value formatted according to the layout +//defined in the Config. The time is compared to time.Now() and is then formatted as a fuzzy +//timestamp (eg. "4 days ago") +func (cfg Config) Format(t time.Time) string { + return cfg.FormatReference(t, time.Now()) +} + +//FormatReference is the same as Format, but the reference has to be defined by the caller +func (cfg Config) FormatReference(t time.Time, reference time.Time) string { + + d := reference.Sub(t) + + if (d >= 0 && d >= cfg.Max) || (d < 0 && -d >= cfg.Max) { + return t.Format(cfg.DefaultLayout) + } + + return cfg.FormatRelativeDuration(d) +} + +//FormatRelativeDuration is the same as Format, but for time.Duration. +//Config.Max is not used in this function, as there is no other alternative. +func (cfg Config) FormatRelativeDuration(d time.Duration) string { + + isPast := d >= 0 + + if d < 0 { + d = -d + } + + s, _ := cfg.getTimeText(d, true) + + if isPast { + return strings.Join([]string{cfg.PastPrefix, s, cfg.PastSuffix}, "") + } else { + return strings.Join([]string{cfg.FuturePrefix, s, cfg.FutureSuffix}, "") + } +} + +//Round the duration d in terms of step. +func round(d time.Duration, step time.Duration, roundCloser bool) time.Duration { + + if roundCloser { + return time.Duration(float64(d)/float64(step) + 0.5) + } + + return time.Duration(float64(d) / float64(step)) +} + +//Count the number of parameters in a format string +func nbParamInFormat(f string) int { + return strings.Count(f, "%") - 2*strings.Count(f, "%%") +} + +//Convert a duration to a text, based on the current config +func (cfg Config) getTimeText(d time.Duration, roundCloser bool) (string, time.Duration) { + if len(cfg.Periods) == 0 || d < cfg.Periods[0].D { + return cfg.Zero, 0 + } + + for i, p := range cfg.Periods { + + next := p.D + if i+1 < len(cfg.Periods) { + next = cfg.Periods[i+1].D + } + + if i+1 == len(cfg.Periods) || d < next { + + r := round(d, p.D, roundCloser) + + if next != p.D && r == round(next, p.D, roundCloser) { + continue + } + + if r == 0 { + return "", d + } + + layout := p.Many + if r == 1 { + layout = p.One + } + + if nbParamInFormat(layout) == 0 { + return layout, d - r*p.D + } + + return fmt.Sprintf(layout, r), d - r*p.D + } + } + + return d.String(), 0 +} + +//NoMax creates an new config without a maximum +func NoMax(cfg Config) Config { + return WithMax(cfg, 9223372036854775807, time.RFC3339) +} + +//WithMax creates an new config with special formatting limited to durations less than max. +//Values greater than max will be formatted by the standard time package using the defaultLayout. +func WithMax(cfg Config, max time.Duration, defaultLayout string) Config { + n := cfg + n.Max = max + n.DefaultLayout = defaultLayout + return n +} diff --git a/vendor/modules.txt b/vendor/modules.txt index cdc0ca5c..4e6047be 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -226,6 +226,8 @@ github.com/spf13/pflag github.com/spf13/viper # github.com/subosito/gotenv v1.2.0 github.com/subosito/gotenv +# github.com/xeonx/timeago v1.0.0-rc4 +github.com/xeonx/timeago # go.opencensus.io v0.22.0 go.opencensus.io go.opencensus.io/internal