Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func runAttach(ctx context.Context, filepath string) error {
sp.Start()
defer sp.Stop()

nydusName, err := b.Nydusify(ctx, attachConfig.Target)
nydusName, err := b.Nydusify(ctx, attachConfig.Target, rootConfig)
if err != nil {
err = fmt.Errorf("failed to nydusify %s: %w", attachConfig.Target, err)
sp.FinalMSG = err.Error()
Expand Down
8 changes: 4 additions & 4 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import (
"fmt"
"time"

"github.com/CloudNativeAI/modctl/pkg/backend"
"github.com/CloudNativeAI/modctl/pkg/config"
"github.com/briandowns/spinner"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/CloudNativeAI/modctl/pkg/backend"
"github.com/CloudNativeAI/modctl/pkg/config"
)

var buildConfig = config.NewBuild()
Expand Down Expand Up @@ -86,7 +86,7 @@ func runBuild(ctx context.Context, workDir string) error {
sp.Start()
defer sp.Stop()

nydusName, err := b.Nydusify(ctx, buildConfig.Target)
nydusName, err := b.Nydusify(ctx, buildConfig.Target, rootConfig)
if err != nil {
err = fmt.Errorf("failed to nydusify %s: %w", buildConfig.Target, err)
sp.FinalMSG = err.Error()
Expand Down
2 changes: 1 addition & 1 deletion cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func runPush(ctx context.Context, target string) error {
sp.Start()
defer sp.Stop()

nydusName, err := b.Nydusify(ctx, target)
nydusName, err := b.Nydusify(ctx, target, rootConfig)
if err != nil {
err = fmt.Errorf("failed to nydusify %s: %w", target, err)
sp.FinalMSG = err.Error()
Expand Down
34 changes: 31 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ import (
"os/signal"
"syscall"

"github.com/CloudNativeAI/modctl/cmd/modelfile"
"github.com/CloudNativeAI/modctl/pkg/config"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/CloudNativeAI/modctl/cmd/modelfile"
"github.com/CloudNativeAI/modctl/pkg/config"
)

var rootConfig *config.Root
var logFile *os.File

// rootCmd represents the modctl command.
var rootCmd = &cobra.Command{
Expand All @@ -51,6 +53,30 @@ var rootCmd = &cobra.Command{
}
}()
}

var err error
// Initialize logger.
logFile, err = os.OpenFile(rootConfig.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logrus.Fatalf("Failed to open log file: %v", err)
}

logLevel, err := logrus.ParseLevel(rootConfig.LogLevel)
if err != nil {
logrus.Fatalf("Failed to parse log level: %v", err)
}

logrus.SetOutput(logFile)
logrus.SetLevel(logLevel)
logrus.SetFormatter(&logrus.TextFormatter{})

return nil
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
if logFile != nil {
return logFile.Close()
}

return nil
},
}
Expand Down Expand Up @@ -83,6 +109,8 @@ func init() {
flags.StringVar(&rootConfig.StoargeDir, "storage-dir", rootConfig.StoargeDir, "specify the storage directory for modctl")
flags.BoolVar(&rootConfig.Pprof, "pprof", rootConfig.Pprof, "enable pprof")
flags.StringVar(&rootConfig.PprofAddr, "pprof-addr", rootConfig.PprofAddr, "specify the address for pprof")
flags.StringVar(&rootConfig.LogLevel, "log-level", rootConfig.LogLevel, "specify the log level")
flags.StringVar(&rootConfig.LogFile, "log-file", rootConfig.LogFile, "specify the log file")

// Bind common flags.
if err := viper.BindPFlags(flags); err != nil {
Expand Down
25 changes: 25 additions & 0 deletions pkg/archiver/archiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ import (
"os"
"path/filepath"
"strings"

"github.com/sirupsen/logrus"
)

// Tar creates a tar archive of the specified path (file or directory)
// and returns the content as a stream. For individual files, it preserves
// the directory structure relative to the working directory.
func Tar(srcPath string, workDir string) (io.Reader, error) {
logrus.Infof("Taring %s, work dir: %s", srcPath, workDir)
pr, pw := io.Pipe()

go func() {
Expand All @@ -38,6 +41,7 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {

info, err := os.Stat(srcPath)
if err != nil {
logrus.Errorf("failed to stat source path: %v", err)
pw.CloseWithError(fmt.Errorf("failed to stat source path: %w", err))
return
}
Expand All @@ -53,28 +57,33 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
// Create a relative path for the tar file header.
relPath, err := filepath.Rel(workDir, path)
if err != nil {
logrus.Errorf("failed to get relative path: %v", err)
return fmt.Errorf("failed to get relative path: %w", err)
}

header, err := tar.FileInfoHeader(info, "")
if err != nil {
logrus.Errorf("failed to create tar header: %v", err)
return fmt.Errorf("failed to create tar header: %w", err)
}

// Set the header name to preserve directory structure.
header.Name = relPath
if err := tw.WriteHeader(header); err != nil {
logrus.Errorf("failed to write header: %v", err)
return fmt.Errorf("failed to write header: %w", err)
}

if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
logrus.Errorf("failed to open file %s: %v", path, err)
return fmt.Errorf("failed to open file %s: %w", path, err)
}
defer file.Close()

if _, err := io.Copy(tw, file); err != nil {
logrus.Errorf("failed to write file %s to tar: %v", path, err)
return fmt.Errorf("failed to write file %s to tar: %w", path, err)
}
}
Expand All @@ -83,20 +92,23 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
})

if err != nil {
logrus.Errorf("failed to walk directory: %v", err)
pw.CloseWithError(fmt.Errorf("failed to walk directory: %w", err))
return
}
} else {
// For a single file, include the directory structure.
file, err := os.Open(srcPath)
if err != nil {
logrus.Errorf("failed to open file %s: %v", srcPath, err)
pw.CloseWithError(fmt.Errorf("failed to open file: %w", err))
return
}
defer file.Close()

header, err := tar.FileInfoHeader(info, "")
if err != nil {
logrus.Errorf("failed to create tar header for file %s: %v", srcPath, err)
pw.CloseWithError(fmt.Errorf("failed to create tar header: %w", err))
return
}
Expand All @@ -105,18 +117,21 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
// This keeps the directory structure as part of the file path in the tar.
relPath, err := filepath.Rel(workDir, srcPath)
if err != nil {
logrus.Errorf("failed to get relative path for file %s: %v", srcPath, err)
pw.CloseWithError(fmt.Errorf("failed to get relative path: %w", err))
return
}

// Use the relative path (including directories) as the header name.
header.Name = relPath
if err := tw.WriteHeader(header); err != nil {
logrus.Errorf("failed to write header for file %s: %v", srcPath, err)
pw.CloseWithError(fmt.Errorf("failed to write header: %w", err))
return
}

if _, err := io.Copy(tw, file); err != nil {
logrus.Errorf("failed to copy file to tar for file %s: %v", srcPath, err)
pw.CloseWithError(fmt.Errorf("failed to copy file to tar: %w", err))
return
}
Expand All @@ -129,10 +144,12 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
// Untar extracts the contents of a tar archive from the provided reader
// to the specified destination path.
func Untar(reader io.Reader, destPath string) error {
logrus.Infof("Untaring archive to %s", destPath)
tarReader := tar.NewReader(reader)

// Ensure destination directory exists.
if err := os.MkdirAll(destPath, 0755); err != nil {
logrus.Errorf("failed to create destination directory: %v", err)
return fmt.Errorf("failed to create destination directory: %w", err)
}

Expand All @@ -142,12 +159,14 @@ func Untar(reader io.Reader, destPath string) error {
break
}
if err != nil {
logrus.Errorf("error reading tar: %v", err)
return fmt.Errorf("error reading tar: %w", err)
}

// Sanitize file paths to prevent directory traversal.
cleanPath := filepath.Clean(header.Name)
if strings.Contains(cleanPath, "..") || strings.HasPrefix(cleanPath, "/") || strings.HasPrefix(cleanPath, ":\\") {
logrus.Errorf("tar file contains invalid path: %s", cleanPath)
return fmt.Errorf("tar file contains invalid path: %s", cleanPath)
}

Expand All @@ -156,12 +175,14 @@ func Untar(reader io.Reader, destPath string) error {
// Create directories for all path components.
dirPath := filepath.Dir(targetPath)
if err := os.MkdirAll(dirPath, 0755); err != nil {
logrus.Errorf("failed to create directory %s: %v", dirPath, err)
return fmt.Errorf("failed to create directory %s: %w", dirPath, err)
}

switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
logrus.Errorf("failed to create directory %s: %v", targetPath, err)
return fmt.Errorf("failed to create directory %s: %w", targetPath, err)
}

Expand All @@ -172,21 +193,25 @@ func Untar(reader io.Reader, destPath string) error {
os.FileMode(header.Mode),
)
if err != nil {
logrus.Errorf("failed to create file %s: %v", targetPath, err)
return fmt.Errorf("failed to create file %s: %w", targetPath, err)
}

if _, err := io.Copy(file, tarReader); err != nil {
file.Close()
logrus.Errorf("failed to write to file %s: %v", targetPath, err)
return fmt.Errorf("failed to write to file %s: %w", targetPath, err)
}
file.Close()

case tar.TypeSymlink:
if isRel(header.Linkname, destPath) && isRel(header.Name, destPath) {
if err := os.Symlink(header.Linkname, targetPath); err != nil {
logrus.Errorf("failed to create symlink %s -> %s: %v", targetPath, header.Linkname, err)
return fmt.Errorf("failed to create symlink %s -> %s: %w", targetPath, header.Linkname, err)
}
} else {
logrus.Errorf("symlink %s -> %s points outside of destination directory", targetPath, header.Linkname)
return fmt.Errorf("symlink %s -> %s points outside of destination directory", targetPath, header.Linkname)
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/backend/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
modelspec "github.com/CloudNativeAI/model-spec/specs-go/v1"
godigest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"

internalpb "github.com/CloudNativeAI/modctl/internal/pb"
"github.com/CloudNativeAI/modctl/pkg/backend/build"
Expand Down Expand Up @@ -58,13 +59,17 @@ var (

// Attach attaches user materials into the model artifact which follows the Model Spec.
func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attach) error {
logrus.Infof("Attaching file %s to %s", filepath, cfg.Source)

srcManifest, err := b.getManifest(ctx, cfg.Source, cfg)
if err != nil {
logrus.Errorf("failed to get source manifest: %v", err)
return fmt.Errorf("failed to get source manifest: %w", err)
}

srcModelConfig, err := b.getModelConfig(ctx, cfg.Source, srcManifest.Config, cfg)
if err != nil {
logrus.Errorf("failed to get source model config: %v", err)
return fmt.Errorf("failed to get source model config: %w", err)
}

Expand Down Expand Up @@ -95,11 +100,13 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac

proc := b.getProcessor(filepath)
if proc == nil {
logrus.Errorf("failed to get processor for file %s", filepath)
return fmt.Errorf("failed to get processor for file %s", filepath)
}

builder, err := b.getBuilder(cfg.Target, cfg)
if err != nil {
logrus.Errorf("failed to create builder: %v", err)
return fmt.Errorf("failed to create builder: %w", err)
}

Expand All @@ -109,6 +116,7 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac

newLayers, err := proc.Process(ctx, builder, ".", processor.WithProgressTracker(pb))
if err != nil {
logrus.Errorf("failed to process layers: %v", err)
return fmt.Errorf("failed to process layers: %w", err)
}

Expand All @@ -122,6 +130,7 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac
}
// Return earlier if the diffID has no changed, which means the artifact has not changed.
if reflect.DeepEqual(diffIDs, srcModelConfig.ModelFS.DiffIDs) {
logrus.Info("The artifact has not changed after attaching layer, skip to attach")
return nil
}

Expand All @@ -147,6 +156,7 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac
}),
))
if err != nil {
logrus.Errorf("failed to build model config: %v", err)
return fmt.Errorf("failed to build model config: %w", err)
}

Expand All @@ -163,9 +173,12 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac
}),
))
if err != nil {
logrus.Errorf("failed to build model manifest: %v", err)
return fmt.Errorf("failed to build model manifest: %w", err)
}

logrus.Infof("Attched %s to %s successfully", filepath, cfg.Source)

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type Backend interface {
Tag(ctx context.Context, source, target string) error

// Nydusify converts the model artifact to nydus format.
Nydusify(ctx context.Context, target string) (string, error)
Nydusify(ctx context.Context, target string, cfg *config.Root) (string, error)
}

// backend is the implementation of Backend.
Expand Down
Loading