Skip to content

Commit 7e44118

Browse files
authored
Merge branch 'main' into feature/docs
2 parents ff318eb + 16a5ea0 commit 7e44118

20 files changed

+415
-61
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ You can find the full documentation on the [getting started](./docs/getting-star
1212

1313
## LICENSE
1414

15-
Apache 2.0 License. Please see [LICENSE](LICENSE) for more information.
15+
Apache 2.0 License. Please see [LICENSE](LICENSE) for more information.

cmd/build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ var buildCmd = &cobra.Command{
5050
func init() {
5151
flags := buildCmd.Flags()
5252
flags.StringVarP(&buildConfig.Target, "target", "t", "", "target model artifact name")
53-
flags.StringVarP(&buildConfig.Modelfile, "modelfile", "f", "", "model file path")
53+
flags.StringVarP(&buildConfig.Modelfile, "modelfile", "f", "Modelfile", "model file path")
5454

5555
if err := viper.BindPFlags(flags); err != nil {
5656
panic(fmt.Errorf("bind cache list flags to viper: %w", err))

cmd/inspect.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2024 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+
"encoding/json"
22+
"fmt"
23+
24+
"github.com/CloudNativeAI/modctl/pkg/backend"
25+
26+
"github.com/spf13/cobra"
27+
"github.com/spf13/viper"
28+
)
29+
30+
// inspectCmd represents the modctl command for inspect.
31+
var inspectCmd = &cobra.Command{
32+
Use: "inspect [flags] <target>",
33+
Short: "A command line tool for modctl inspect",
34+
Args: cobra.ExactArgs(1),
35+
DisableAutoGenTag: true,
36+
SilenceUsage: true,
37+
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
38+
RunE: func(cmd *cobra.Command, args []string) error {
39+
return runInspect(context.Background(), args[0])
40+
},
41+
}
42+
43+
// init initializes inspect command.
44+
func init() {
45+
flags := rmCmd.Flags()
46+
47+
if err := viper.BindPFlags(flags); err != nil {
48+
panic(fmt.Errorf("bind cache inspect flags to viper: %w", err))
49+
}
50+
}
51+
52+
// runInspect runs the inspect modctl.
53+
func runInspect(ctx context.Context, target string) error {
54+
b, err := backend.New()
55+
if err != nil {
56+
return err
57+
}
58+
59+
if target == "" {
60+
return fmt.Errorf("target is required")
61+
}
62+
63+
inspected, err := b.Inspect(ctx, target)
64+
if err != nil {
65+
return err
66+
}
67+
68+
data, err := json.MarshalIndent(inspected, "", " ")
69+
if err != nil {
70+
return err
71+
}
72+
73+
fmt.Println(string(data))
74+
return nil
75+
}

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ func init() {
6565
rootCmd.AddCommand(pushCmd)
6666
rootCmd.AddCommand(rmCmd)
6767
rootCmd.AddCommand(pruneCmd)
68+
rootCmd.AddCommand(inspectCmd)
6869
}

pkg/backend/backend.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ type Backend interface {
4747

4848
// Prune prunes the unused blobs and clean up the storage.
4949
Prune(ctx context.Context) ([]string, error)
50+
51+
// Inspect inspects the model artifact.
52+
Inspect(ctx context.Context, target string) (*InspectedModelArtifact, error)
5053
}
5154

5255
// backend is the implementation of Backend.

pkg/backend/build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func (b *backend) process(ctx context.Context, workDir string, repo string, proc
114114
for _, p := range processors {
115115
// process the file if it can be recognized.
116116
if p.Identify(ctx, path, info) {
117-
desc, err := p.Process(ctx, b.store, repo, path, info)
117+
desc, err := p.Process(ctx, b.store, repo, path, workDir)
118118
if err != nil {
119119
return fmt.Errorf("failed to process file: %w", err)
120120
}

pkg/backend/build/build.go

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"context"
2222
"encoding/json"
2323
"fmt"
24+
"path/filepath"
25+
"time"
2426

2527
modelspec "github.com/CloudNativeAI/modctl/pkg/spec"
2628
"github.com/CloudNativeAI/modctl/pkg/storage"
@@ -30,16 +32,14 @@ import (
3032
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3133
)
3234

33-
// DescriptorEmptyJSON is the descriptor of a blob with content of `{}`.
34-
var DescriptorEmptyJSON = ocispec.Descriptor{
35-
MediaType: ocispec.MediaTypeImageConfig,
36-
Digest: `sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`,
37-
Size: 2,
38-
Data: []byte(`{}`),
35+
// ModelConfig is a configuration that corresponds to the image config in the image spec.
36+
type ModelConfig struct {
37+
// Created is the time when the model image is created.
38+
Created string `json:"Created"`
3939
}
4040

4141
// BuildLayer converts the file to the image blob and push it to the storage.
42-
func BuildLayer(ctx context.Context, store storage.Storage, repo, path string) (ocispec.Descriptor, error) {
42+
func BuildLayer(ctx context.Context, store storage.Storage, repo, path, workDir string) (ocispec.Descriptor, error) {
4343
reader, err := TarFileToStream(path)
4444
if err != nil {
4545
return ocispec.Descriptor{}, fmt.Errorf("failed to tar file: %w", err)
@@ -50,23 +50,48 @@ func BuildLayer(ctx context.Context, store storage.Storage, repo, path string) (
5050
return ocispec.Descriptor{}, fmt.Errorf("failed to push blob to storage: %w", err)
5151
}
5252

53+
absPath, err := filepath.Abs(workDir)
54+
if err != nil {
55+
return ocispec.Descriptor{}, fmt.Errorf("failed to get absolute path of workDir: %w", err)
56+
}
57+
58+
filePath, err := filepath.Rel(absPath, path)
59+
if err != nil {
60+
return ocispec.Descriptor{}, fmt.Errorf("failed to get relative path: %w", err)
61+
}
62+
5363
return ocispec.Descriptor{
5464
ArtifactType: modelspec.ArtifactTypeModelLayer,
5565
MediaType: ocispec.MediaTypeImageLayer,
5666
Digest: godigest.Digest(digest),
5767
Size: size,
68+
Annotations: map[string]string{
69+
modelspec.AnnotationFilepath: filePath,
70+
},
5871
}, nil
5972
}
6073

6174
// BuildConfig builds the image config and push it to the storage.
6275
func BuildConfig(ctx context.Context, store storage.Storage, repo string) (ocispec.Descriptor, error) {
63-
// by default using the empty JSON config.
64-
_, _, err := store.PushBlob(ctx, repo, bytes.NewReader(DescriptorEmptyJSON.Data))
76+
config := &ModelConfig{
77+
Created: time.Now().Format(time.RFC3339Nano),
78+
}
79+
configJSON, err := json.Marshal(config)
80+
if err != nil {
81+
return ocispec.Descriptor{}, fmt.Errorf("failed to marshal config: %w", err)
82+
}
83+
84+
digest, size, err := store.PushBlob(ctx, repo, bytes.NewReader(configJSON))
6585
if err != nil {
6686
return ocispec.Descriptor{}, fmt.Errorf("failed to push config to storage: %w", err)
6787
}
6888

69-
return DescriptorEmptyJSON, nil
89+
return ocispec.Descriptor{
90+
// reuse the image config media type for runtime compatibility.
91+
MediaType: ocispec.MediaTypeImageConfig,
92+
Size: size,
93+
Digest: godigest.Digest(digest),
94+
}, nil
7095
}
7196

7297
// BuildManifest builds the manifest and push it to the storage.

pkg/backend/inspect.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2024 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 backend
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"fmt"
23+
24+
modelspec "github.com/CloudNativeAI/modctl/pkg/spec"
25+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
26+
)
27+
28+
// InspectedModelArtifact is the data structure for model artifact that has been inspected.
29+
type InspectedModelArtifact struct {
30+
// ID is the image id of the model artifact.
31+
ID string `json:"Id"`
32+
// Digest is the digest of the model artifact.
33+
Digest string `json:"Digest"`
34+
// Architecture is the architecture of the model.
35+
Architecture string `json:"Architecture"`
36+
// Created is the creation time of the model artifact.
37+
Created string `json:"Created"`
38+
// Family is the family of the model.
39+
Family string `json:"Family"`
40+
// Format is the format of the model.
41+
Format string `json:"Format"`
42+
// Name is the name of the model.
43+
Name string `json:"Name"`
44+
// ParamSize is the param size of the model.
45+
ParamSize string `json:"ParamSize"`
46+
// Precision is the precision of the model.
47+
Precision string `json:"Precision"`
48+
// Quantization is the quantization of the model.
49+
Quantization string `json:"Quantization"`
50+
// Layers is the layers of the model artifact.
51+
Layers []InspectedModelArtifactLayer `json:"Layers"`
52+
}
53+
54+
// InspectedModelArtifactLayer is the data structure for model artifact layer that has been inspected.
55+
type InspectedModelArtifactLayer struct {
56+
// Digest is the digest of the model artifact layer.
57+
Digest string `json:"Digest"`
58+
// Size is the size of the model artifact layer.
59+
Size int64 `json:"Size"`
60+
// Filepath is the filepath of the model artifact layer.
61+
Filepath string `json:"Filepath"`
62+
}
63+
64+
// Inspect inspects the target from the storage.
65+
func (b *backend) Inspect(ctx context.Context, target string) (*InspectedModelArtifact, error) {
66+
ref, err := ParseReference(target)
67+
if err != nil {
68+
return nil, fmt.Errorf("failed to parse target: %w", err)
69+
}
70+
71+
repo, tag := ref.Repository(), ref.Tag()
72+
manifestRaw, digest, err := b.store.PullManifest(ctx, repo, tag)
73+
if err != nil {
74+
return nil, fmt.Errorf("failed to get manifest: %w", err)
75+
}
76+
77+
var manifest ocispec.Manifest
78+
if err := json.Unmarshal(manifestRaw, &manifest); err != nil {
79+
return nil, fmt.Errorf("failed to unmarshal manifest: %w", err)
80+
}
81+
82+
inspectedModelArtifact := &InspectedModelArtifact{
83+
ID: manifest.Config.Digest.String(),
84+
Digest: digest,
85+
Architecture: manifest.Annotations[modelspec.AnnotationArchitecture],
86+
Created: manifest.Annotations[modelspec.AnnotationCreated],
87+
Family: manifest.Annotations[modelspec.AnnotationFamily],
88+
Format: manifest.Annotations[modelspec.AnnotationFormat],
89+
Name: manifest.Annotations[modelspec.AnnotationName],
90+
ParamSize: manifest.Annotations[modelspec.AnnotationParamSize],
91+
Precision: manifest.Annotations[modelspec.AnnotationPrecision],
92+
Quantization: manifest.Annotations[modelspec.AnnotationQuantization],
93+
}
94+
95+
for _, layer := range manifest.Layers {
96+
inspectedModelArtifact.Layers = append(inspectedModelArtifact.Layers, InspectedModelArtifactLayer{
97+
Digest: layer.Digest.String(),
98+
Size: layer.Size,
99+
Filepath: layer.Annotations[modelspec.AnnotationFilepath],
100+
})
101+
}
102+
103+
return inspectedModelArtifact, nil
104+
}

0 commit comments

Comments
 (0)