Skip to content

Commit 0a84a63

Browse files
authored
feat: support attach command to attach a file to existed model artifact (#141)
Signed-off-by: chlins <[email protected]>
1 parent b674617 commit 0a84a63

File tree

24 files changed

+1170
-270
lines changed

24 files changed

+1170
-270
lines changed

cmd/attach.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2025 The CNAI Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
"github.com/briandowns/spinner"
25+
"github.com/spf13/cobra"
26+
"github.com/spf13/viper"
27+
28+
"github.com/CloudNativeAI/modctl/pkg/backend"
29+
"github.com/CloudNativeAI/modctl/pkg/config"
30+
)
31+
32+
var attachConfig = config.NewAttach()
33+
34+
// attachCmd represents the modctl command for attach.
35+
var attachCmd = &cobra.Command{
36+
Use: "attach [flags] <file>",
37+
Short: "A command line tool for modctl attach",
38+
Args: cobra.ExactArgs(1),
39+
DisableAutoGenTag: true,
40+
SilenceUsage: true,
41+
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
42+
RunE: func(cmd *cobra.Command, args []string) error {
43+
if err := attachConfig.Validate(); err != nil {
44+
return err
45+
}
46+
47+
return runAttach(context.Background(), args[0])
48+
},
49+
}
50+
51+
// init initializes build command.
52+
func init() {
53+
flags := attachCmd.Flags()
54+
flags.StringVarP(&attachConfig.Source, "source", "s", "", "source model artifact name")
55+
flags.StringVarP(&attachConfig.Target, "target", "t", "", "target model artifact name")
56+
flags.BoolVarP(&attachConfig.OutputRemote, "output-remote", "", false, "turning on this flag will output model artifact to remote registry directly")
57+
flags.BoolVarP(&attachConfig.PlainHTTP, "plain-http", "", false, "turning on this flag will use plain HTTP instead of HTTPS")
58+
flags.BoolVarP(&attachConfig.Insecure, "insecure", "", false, "turning on this flag will disable TLS verification")
59+
flags.BoolVarP(&attachConfig.Force, "force", "f", false, "turning on this flag will force the attach, which will overwrite the layer if it already exists with same filepath")
60+
flags.BoolVar(&attachConfig.Nydusify, "nydusify", false, "[EXPERIMENTAL] nydusify the model artifact")
61+
flags.MarkHidden("nydusify")
62+
63+
if err := viper.BindPFlags(flags); err != nil {
64+
panic(fmt.Errorf("bind cache list flags to viper: %w", err))
65+
}
66+
}
67+
68+
// runAttach runs the attach modctl.
69+
func runAttach(ctx context.Context, filepath string) error {
70+
b, err := backend.New(rootConfig.StoargeDir)
71+
if err != nil {
72+
return err
73+
}
74+
75+
if err := b.Attach(ctx, filepath, attachConfig); err != nil {
76+
return err
77+
}
78+
79+
fmt.Printf("Successfully attached model artifact: %s\n", attachConfig.Target)
80+
81+
// nydusify the model artifact if needed.
82+
if attachConfig.Nydusify {
83+
sp := spinner.New(spinner.CharSets[39], 100*time.Millisecond, spinner.WithSuffix("Nydusifying..."))
84+
sp.Start()
85+
defer sp.Stop()
86+
87+
nydusName, err := b.Nydusify(ctx, attachConfig.Target)
88+
if err != nil {
89+
err = fmt.Errorf("failed to nydusify %s: %w", attachConfig.Target, err)
90+
sp.FinalMSG = err.Error()
91+
return err
92+
}
93+
94+
sp.FinalMSG = fmt.Sprintf("Successfully nydusify model artifact: %s", nydusName)
95+
}
96+
97+
return nil
98+
}

cmd/build.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ package cmd
1919
import (
2020
"context"
2121
"fmt"
22+
"time"
2223

2324
"github.com/CloudNativeAI/modctl/pkg/backend"
2425
"github.com/CloudNativeAI/modctl/pkg/config"
26+
"github.com/briandowns/spinner"
2527

2628
"github.com/spf13/cobra"
2729
"github.com/spf13/viper"
@@ -78,12 +80,18 @@ func runBuild(ctx context.Context, workDir string) error {
7880

7981
// nydusify the model artifact if needed.
8082
if buildConfig.Nydusify {
83+
sp := spinner.New(spinner.CharSets[39], 100*time.Millisecond, spinner.WithSuffix("Nydusifying..."))
84+
sp.Start()
85+
defer sp.Stop()
86+
8187
nydusName, err := b.Nydusify(ctx, buildConfig.Target)
8288
if err != nil {
83-
return fmt.Errorf("failed to nydusify %s: %w", buildConfig.Target, err)
89+
err = fmt.Errorf("failed to nydusify %s: %w", buildConfig.Target, err)
90+
sp.FinalMSG = err.Error()
91+
return err
8492
}
8593

86-
fmt.Printf("Successfully nydusify model artifact: %s\n", nydusName)
94+
sp.FinalMSG = fmt.Sprintf("Successfully nydusify model artifact: %s", nydusName)
8795
}
8896

8997
return nil

cmd/push.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ package cmd
1919
import (
2020
"context"
2121
"fmt"
22+
"time"
2223

2324
"github.com/CloudNativeAI/modctl/pkg/backend"
2425
"github.com/CloudNativeAI/modctl/pkg/config"
2526

27+
"github.com/briandowns/spinner"
2628
"github.com/spf13/cobra"
2729
"github.com/spf13/viper"
2830
)
@@ -75,12 +77,18 @@ func runPush(ctx context.Context, target string) error {
7577

7678
// nydusify the model artifact if needed.
7779
if pushConfig.Nydusify {
80+
sp := spinner.New(spinner.CharSets[39], 100*time.Millisecond, spinner.WithSuffix("Nydusifying..."))
81+
sp.Start()
82+
defer sp.Stop()
83+
7884
nydusName, err := b.Nydusify(ctx, target)
7985
if err != nil {
80-
return fmt.Errorf("failed to nydusify %s: %w", target, err)
86+
err = fmt.Errorf("failed to nydusify %s: %w", target, err)
87+
sp.FinalMSG = err.Error()
88+
return err
8189
}
8290

83-
fmt.Printf("Successfully nydusify model artifact: %s\n", nydusName)
91+
sp.FinalMSG = fmt.Sprintf("Successfully nydusify model artifact: %s", nydusName)
8492
}
8593

8694
return nil

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,6 @@ func init() {
8686
rootCmd.AddCommand(extractCmd)
8787
rootCmd.AddCommand(tagCmd)
8888
rootCmd.AddCommand(fetchCmd)
89+
rootCmd.AddCommand(attachCmd)
8990
rootCmd.AddCommand(modelfile.RootCmd)
9091
}

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.24.1
44

55
require (
66
github.com/CloudNativeAI/model-spec v0.0.3
7+
github.com/briandowns/spinner v1.23.2
78
github.com/distribution/distribution/v3 v3.0.0-rc.3
89
github.com/distribution/reference v0.6.0
910
github.com/dustin/go-humanize v1.0.1
@@ -29,6 +30,7 @@ require (
2930
github.com/cespare/xxhash/v2 v2.3.0 // indirect
3031
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
3132
github.com/docker/go-metrics v0.0.1 // indirect
33+
github.com/fatih/color v1.7.0 // indirect
3234
github.com/fsnotify/fsnotify v1.8.0 // indirect
3335
github.com/go-logr/logr v1.4.2 // indirect
3436
github.com/go-logr/stdr v1.2.2 // indirect
@@ -40,6 +42,8 @@ require (
4042
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4143
github.com/klauspost/compress v1.17.11 // indirect
4244
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
45+
github.com/mattn/go-colorable v0.1.2 // indirect
46+
github.com/mattn/go-isatty v0.0.8 // indirect
4347
github.com/mattn/go-runewidth v0.0.16 // indirect
4448
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
4549
github.com/pelletier/go-toml/v2 v2.2.3 // indirect

go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
1010
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
1111
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
1212
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
13+
github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w=
14+
github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
1315
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
1416
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
1517
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -29,6 +31,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
2931
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
3032
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
3133
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
34+
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
35+
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
3236
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
3337
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
3438
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
@@ -81,6 +85,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
8185
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
8286
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
8387
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
88+
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
89+
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
90+
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
91+
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
8492
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
8593
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
8694
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -218,6 +226,7 @@ golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
218226
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
219227
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
220228
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
229+
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
221230
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
222231
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
223232
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

0 commit comments

Comments
 (0)