Skip to content

Commit 5285b78

Browse files
committed
Implement basic 'jmm models' command
Add basic implementation of `jmm models` command that prints artifacts in the local store. Prints digest, maintainer, format, and size for now. Signed-off-by: Angel Misevski <[email protected]>
1 parent 87dbb07 commit 5285b78

File tree

4 files changed

+212
-76
lines changed

4 files changed

+212
-76
lines changed

cmd/root.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ var (
3535
// rootCmd represents the base command when called without any subcommands
3636
var rootCmd = newRootCmd()
3737

38+
func init() {
39+
rootCmd.AddCommand(build.NewCmdBuild())
40+
rootCmd.AddCommand(login.NewCmdLogin())
41+
rootCmd.AddCommand(pull.NewCmdPull())
42+
rootCmd.AddCommand(push.NewCmdPush())
43+
rootCmd.AddCommand(models.ModelsCommand())
44+
}
45+
3846
func newRootCmd() *cobra.Command {
3947
flags := &RootFlags{}
4048
cmd := &cobra.Command{
@@ -66,6 +74,7 @@ func (f *RootFlags) ToOptions() (*RootOptions, error) {
6674
ConfigHome: f.ConfigHome,
6775
}, nil
6876
}
77+
6978
func (o *RootOptions) Complete() error {
7079
if o.ConfigHome == "" {
7180
currentUser, err := user.Current()
@@ -87,11 +96,3 @@ func Execute() {
8796
os.Exit(1)
8897
}
8998
}
90-
91-
func init() {
92-
rootCmd.AddCommand(build.NewCmdBuild())
93-
rootCmd.AddCommand(login.NewCmdLogin())
94-
rootCmd.AddCommand(pull.NewCmdPull())
95-
rootCmd.AddCommand(push.NewCmdPush())
96-
rootCmd.AddCommand(models.NewCmdModels())
97-
}

pkg/artifact/store.go

+41-22
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"encoding/json"
77
"fmt"
8+
"os"
89
"path/filepath"
910

1011
_ "crypto/sha256"
@@ -17,21 +18,58 @@ import (
1718
)
1819

1920
type Store struct {
20-
Storage *oci.Store
21+
Storage *oci.Store
22+
indexPath string
2123
}
2224

2325
func NewArtifactStore(jozuhome string) *Store {
26+
storeHome := filepath.Join(jozuhome, ".jozuStore")
27+
indexPath := filepath.Join(storeHome, "index.json")
2428

25-
store, err := oci.New(filepath.Join(jozuhome, ".jozuStore"))
29+
store, err := oci.New(storeHome)
2630
if err != nil {
2731
panic(err)
2832
}
2933

3034
return &Store{
31-
Storage: store,
35+
Storage: store,
36+
indexPath: indexPath,
3237
}
3338
}
3439

40+
func (store *Store) SaveModel(model *Model) (*ocispec.Manifest, error) {
41+
config, err := store.saveConfigFile(model.Config)
42+
if err != nil {
43+
return nil, err
44+
}
45+
for _, layer := range model.Layers {
46+
_, err = store.saveContentLayer(layer)
47+
if err != nil {
48+
return nil, err
49+
}
50+
}
51+
52+
manifest, err := store.saveModelManifest(model, config)
53+
if err != nil {
54+
return nil, err
55+
}
56+
return manifest, nil
57+
}
58+
59+
func (store *Store) ParseIndexJson() (*ocispec.Index, error) {
60+
indexBytes, err := os.ReadFile(store.indexPath)
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to read index: %w", err)
63+
}
64+
65+
index := &ocispec.Index{}
66+
if err := json.Unmarshal(indexBytes, index); err != nil {
67+
return nil, fmt.Errorf("failed to parse index: %w", err)
68+
}
69+
70+
return index, nil
71+
}
72+
3573
func (store *Store) saveContentLayer(layer *ModelLayer) (*ocispec.Descriptor, error) {
3674
ctx := context.Background()
3775

@@ -102,22 +140,3 @@ func (store *Store) saveModelManifest(model *Model, config *ocispec.Descriptor)
102140
}
103141
return &manifest, nil
104142
}
105-
106-
func (store *Store) SaveModel(model *Model) (*ocispec.Manifest, error) {
107-
config, err := store.saveConfigFile(model.Config)
108-
if err != nil {
109-
return nil, err
110-
}
111-
for _, layer := range model.Layers {
112-
_, err = store.saveContentLayer(layer)
113-
if err != nil {
114-
return nil, err
115-
}
116-
}
117-
118-
manifest, err := store.saveModelManifest(model, config)
119-
if err != nil {
120-
return nil, err
121-
}
122-
return manifest, nil
123-
}

pkg/cmd/models/cmd.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package models
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/spf13/viper"
8+
)
9+
10+
const (
11+
shortDesc = `List models`
12+
longDesc = `List models TODO`
13+
)
14+
15+
var (
16+
opts *ModelsOptions
17+
)
18+
19+
type ModelsOptions struct {
20+
configHome string
21+
}
22+
23+
func (opts *ModelsOptions) readGlobalConfig() {
24+
opts.configHome = viper.GetString("config")
25+
}
26+
27+
func (opts *ModelsOptions) validate() error {
28+
return nil
29+
}
30+
31+
// ModelsCommand represents the models command
32+
func ModelsCommand() *cobra.Command {
33+
opts = &ModelsOptions{}
34+
35+
cmd := &cobra.Command{
36+
Use: "models",
37+
Short: shortDesc,
38+
Long: longDesc,
39+
Run: RunCommand(opts),
40+
}
41+
42+
return cmd
43+
}
44+
45+
func RunCommand(options *ModelsOptions) func(*cobra.Command, []string) {
46+
return func(cmd *cobra.Command, args []string) {
47+
options.readGlobalConfig()
48+
err := options.validate()
49+
if err != nil {
50+
fmt.Println(err)
51+
return
52+
}
53+
err = listModels(options)
54+
if err != nil {
55+
fmt.Println(err)
56+
return
57+
}
58+
}
59+
}

pkg/cmd/models/models.go

+103-46
Original file line numberDiff line numberDiff line change
@@ -4,67 +4,124 @@ Copyright © 2024 Jozu.com
44
package models
55

66
import (
7+
"context"
8+
"encoding/json"
79
"fmt"
10+
"io"
11+
"jmm/pkg/artifact"
12+
"os"
13+
"text/tabwriter"
814

9-
"github.com/spf13/cobra"
15+
"github.com/opencontainers/go-digest"
16+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
1017
)
1118

12-
type ModelsFlags struct {
19+
const (
20+
ModelsTableHeader = "DIGEST\tMAINTAINER\tMODEL FORMAT\tSIZE"
21+
ModelsTableFmt = "%s\t%s\t%s\t%s\t"
22+
)
23+
24+
func listModels(opts *ModelsOptions) error {
25+
store := artifact.NewArtifactStore(opts.configHome)
26+
index, err := store.ParseIndexJson()
27+
if err != nil {
28+
return err
29+
}
30+
31+
manifests, err := manifestsFromIndex(index, store)
32+
if err != nil {
33+
return err
34+
}
35+
36+
if err := printManifestsSummary(manifests, store); err != nil {
37+
return err
38+
}
39+
40+
return nil
1341
}
14-
type ModelsOptions struct {
42+
43+
func manifestsFromIndex(index *ocispec.Index, store *artifact.Store) (map[digest.Digest]ocispec.Manifest, error) {
44+
manifests := map[digest.Digest]ocispec.Manifest{}
45+
for _, manifestDesc := range index.Manifests {
46+
manifestReader, err := store.Storage.Fetch(context.Background(), manifestDesc)
47+
if err != nil {
48+
return nil, fmt.Errorf("failed to get manifest %s: %w", manifestDesc.Digest, err)
49+
}
50+
manifestBytes, err := io.ReadAll(manifestReader)
51+
if err != nil {
52+
return nil, fmt.Errorf("failed to read manifest %s: %w", manifestDesc.Digest, err)
53+
}
54+
manifest := ocispec.Manifest{}
55+
if err := json.Unmarshal(manifestBytes, &manifest); err != nil {
56+
return nil, fmt.Errorf("failed to parse manifest %s: %w", manifestDesc.Digest, err)
57+
}
58+
manifests[manifestDesc.Digest] = manifest
59+
}
60+
return manifests, nil
1561
}
1662

17-
// modelsCmd represents the models command
18-
func NewCmdModels() *cobra.Command {
19-
modelsFlags := NewModelsFlags()
20-
21-
cmd := &cobra.Command{
22-
Use: "models",
23-
Short: "A brief description of your command",
24-
Long: `A longer description that spans multiple lines and likely contains examples
25-
and usage of using your command. For example:
26-
27-
Cobra is a CLI library for Go that empowers applications.
28-
This application is a tool to generate the needed files
29-
to quickly create a Cobra application.`,
30-
Run: func(cmd *cobra.Command, args []string) {
31-
options, err := modelsFlags.ToOptions()
32-
if err != nil {
33-
fmt.Println(err)
34-
return
35-
}
36-
err = options.Validate()
37-
if err != nil {
38-
fmt.Println(err)
39-
return
40-
}
41-
options.RunModels()
42-
if err != nil {
43-
fmt.Println(err)
44-
return
45-
}
46-
},
63+
func readManifestConfig(manifest *ocispec.Manifest, store *artifact.Store) (*artifact.JozuFile, error) {
64+
configReader, err := store.Storage.Fetch(context.Background(), manifest.Config)
65+
if err != nil {
66+
return nil, fmt.Errorf("failed to get config: %w", err)
67+
}
68+
configBytes, err := io.ReadAll(configReader)
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to read config: %w", err)
4771
}
48-
modelsFlags.AddFlags(cmd)
49-
return cmd
72+
config := &artifact.JozuFile{}
73+
if err := json.Unmarshal(configBytes, config); err != nil {
74+
return nil, fmt.Errorf("failed to parse config: %w", err)
75+
}
76+
return config, nil
5077
}
5178

52-
func NewModelsFlags() *ModelsFlags {
53-
return &ModelsFlags{}
79+
func printManifestsSummary(manifests map[digest.Digest]ocispec.Manifest, store *artifact.Store) error {
80+
tw := tabwriter.NewWriter(os.Stdout, 0, 2, 3, ' ', 0)
81+
fmt.Fprintln(tw, ModelsTableHeader)
82+
for digest, manifest := range manifests {
83+
// TODO: filter this list for manifests we're interested in (build needs to set a manifest mediaType/artifactType)
84+
line, err := getManifestInfoLine(digest, &manifest, store)
85+
if err != nil {
86+
return err
87+
}
88+
fmt.Fprintln(tw, line)
89+
}
90+
tw.Flush()
91+
return nil
5492
}
5593

56-
func (f *ModelsFlags) AddFlags(cmd *cobra.Command) {
94+
func getManifestInfoLine(digest digest.Digest, manifest *ocispec.Manifest, store *artifact.Store) (string, error) {
95+
config, err := readManifestConfig(manifest, store)
96+
if err != nil {
97+
return "", err
98+
}
99+
var size int64
100+
for _, layer := range manifest.Layers {
101+
size += layer.Size
102+
}
103+
sizeStr := formatBytes(size)
57104

105+
info := fmt.Sprintf(ModelsTableFmt, digest, config.Maintainer, config.ModelFormat, sizeStr)
106+
return info, nil
58107
}
59108

60-
func (f *ModelsFlags) ToOptions() (*ModelsOptions, error) {
61-
return &ModelsOptions{}, nil
62-
}
109+
func formatBytes(i int64) string {
110+
if i == 0 {
111+
return "0 B"
112+
}
63113

64-
func (o *ModelsOptions) Validate() error {
65-
return nil
66-
}
114+
suffixes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB"}
115+
unit := float64(1024)
67116

68-
func (o *ModelsOptions) RunModels() error {
69-
return nil
117+
size := float64(i)
118+
for _, suffix := range suffixes {
119+
if size < unit {
120+
return fmt.Sprintf("%.1f %s", size, suffix)
121+
}
122+
size = size / unit
123+
}
124+
125+
// Fall back to printing 1000's of PiB
126+
return fmt.Sprintf("%.1f PiB", size)
70127
}

0 commit comments

Comments
 (0)