From 9ecd25ed0bde620216bb73cbeadadd22d64b08f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9E=D0=BB=D0=B5=D0=B3=20=D0=91=D0=BE=D1=80=D0=BE=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Wed, 4 Mar 2026 20:27:35 +0200 Subject: [PATCH] working commit --- cmd/mstorectl/command/util.go | 86 +++++++++++++ cmd/mstorectl/filecmd/delcoll.go | 75 +++++++++++ cmd/mstorectl/filecmd/delfile.go | 51 ++++++++ cmd/mstorectl/filecmd/expfiles.go | 121 ++++++++++++++++++ cmd/mstorectl/filecmd/filecmd.go | 193 +++++++++++++++++++++++++++++ cmd/mstorectl/filecmd/fileinfo.go | 63 ++++++++++ cmd/mstorectl/filecmd/getfile.go | 66 ++++++++++ cmd/mstorectl/filecmd/impfiles.go | 116 +++++++++++++++++ cmd/mstorectl/filecmd/listcolls.go | 66 ++++++++++ cmd/mstorectl/filecmd/listfiles.go | 84 +++++++++++++ cmd/mstorectl/filecmd/putfile.go | 64 ++++++++++ cmd/mstored/starter/starter.go | 80 ++++++++++++ 12 files changed, 1065 insertions(+) create mode 100644 cmd/mstorectl/command/util.go create mode 100644 cmd/mstorectl/filecmd/delcoll.go create mode 100644 cmd/mstorectl/filecmd/delfile.go create mode 100644 cmd/mstorectl/filecmd/expfiles.go create mode 100644 cmd/mstorectl/filecmd/filecmd.go create mode 100644 cmd/mstorectl/filecmd/fileinfo.go create mode 100644 cmd/mstorectl/filecmd/getfile.go create mode 100644 cmd/mstorectl/filecmd/impfiles.go create mode 100644 cmd/mstorectl/filecmd/listcolls.go create mode 100644 cmd/mstorectl/filecmd/listfiles.go create mode 100644 cmd/mstorectl/filecmd/putfile.go create mode 100644 cmd/mstored/starter/starter.go diff --git a/cmd/mstorectl/command/util.go b/cmd/mstorectl/command/util.go new file mode 100644 index 0000000..9d691af --- /dev/null +++ b/cmd/mstorectl/command/util.go @@ -0,0 +1,86 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package command + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "sigs.k8s.io/yaml" + + "mstore/cmd/mstorectl/filecmd" +) + +type Util struct { + filecmd.FileUtil + ImageUtil + AccountUtil + GrantUtil + rootCmd *cobra.Command +} + +func NewUtil() *Util { + return &Util{} +} + +func (util *Util) GetRooCmd() *cobra.Command { + return util.rootCmd +} + +func (util *Util) Build() error { + var err error + execName := filepath.Base(os.Args[0]) + rootCmd := &cobra.Command{ + Use: execName, + Short: "\nOperation with artefacts: files, images, service accounts and grants", + SilenceUsage: true, + } + rootCmd.CompletionOptions.DisableDefaultCmd = true + + rootCmd.AddCommand(util.MakeFileCmds()) + rootCmd.AddCommand(util.MakeCollectionCmds()) + rootCmd.AddCommand(util.CreateImageCmds()) + rootCmd.AddCommand(util.CreateAccountCmds()) + rootCmd.AddCommand(util.CreateGrantCmds()) + + util.rootCmd = rootCmd + + return err +} + +func (util *Util) Exec(args []string) error { + var err error + util.rootCmd.SetArgs(args) + err = util.rootCmd.Execute() + return err +} + +func (util *Util) Hello(cmd *cobra.Command, args []string) { + fmt.Println("hello, world!") +} + +func printResponse(res any, err error) { + type Response struct { + Error bool `json:"error" yaml:"error"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Result any `json:"result,omitempty" yaml:"result,omitempty"` + } + resp := Response{} + if err != nil { + resp.Error = true + resp.Message = err.Error() + } else { + resp.Result = res + } + respBytes, _ := yaml.Marshal(resp) + fmt.Printf("---\n%s\n", string(respBytes)) +} diff --git a/cmd/mstorectl/filecmd/delcoll.go b/cmd/mstorectl/filecmd/delcoll.go new file mode 100644 index 0000000..51f81ad --- /dev/null +++ b/cmd/mstorectl/filecmd/delcoll.go @@ -0,0 +1,75 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "context" + "encoding/json" + "time" + + "github.com/spf13/cobra" + + "mstore/pkg/descr" + "mstore/pkg/filecli" +) + +// DeleteCollection +type DeleteCollectionParams struct { + Path string + Detail bool + Prefix bool + Regexp bool + DryRun bool +} + +type DeleteCollectionResult struct { + Files []descr.File `json:"files,omitempty"` + Filenames []string `json:"filenames,omitempty"` +} + +func (util *FileUtil) DeleteCollection(cmd *cobra.Command, args []string) { + util.deleteCollectionParams.Path = args[0] + res, err := util.deleteCollection(&util.commonFileParams, &util.deleteCollectionParams) + printResponse(res, err) +} + +func (util *FileUtil) deleteCollection(common *CommonFileParams, params *DeleteCollectionParams) (*DeleteCollectionResult, error) { + var err error + res := &DeleteCollectionResult{ + Filenames: make([]string, 0), + } + + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref, err := filecli.ParsePath(params.Path) + if err != nil { + return res, err + } + ref.SetUserinfo(common.Username, common.Password) + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + ref.PathType(pathType(params.Regexp, params.Prefix)) + ref.DryRun(params.DryRun) + cli := filecli.NewClient(nil, mw) + list, err := cli.DeleteCollection(ctx, ref.Raw()) + if err != nil { + return res, err + } + files := descr.NewFiles() + err = json.Unmarshal(list, files.ArrayPtr()) + if err != nil { + return res, err + } + if !params.Detail { + res.Filenames = files.List() + } else { + res.Files = files.Array() + } + return res, err +} diff --git a/cmd/mstorectl/filecmd/delfile.go b/cmd/mstorectl/filecmd/delfile.go new file mode 100644 index 0000000..a0f0294 --- /dev/null +++ b/cmd/mstorectl/filecmd/delfile.go @@ -0,0 +1,51 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "context" + "time" + + "github.com/spf13/cobra" + + "mstore/pkg/filecli" +) + +// DeleteFile +type DeleteFileParams struct { + Filepath string +} + +type DeleteFileResult struct{} + +func (util *FileUtil) DeleteFile(cmd *cobra.Command, args []string) { + util.deleteFileParams.Filepath = args[0] + res, err := util.deleteFile(&util.commonFileParams, &util.deleteFileParams) + printResponse(res, err) +} +func (util *FileUtil) deleteFile(common *CommonFileParams, params *DeleteFileParams) (*DeleteFileResult, error) { + var err error + res := &DeleteFileResult{} + ref, err := filecli.ParsePath(params.Filepath) + if err != nil { + return res, err + } + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref.SetUserinfo(common.Username, common.Password) + + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + cli := filecli.NewClient(nil, mw) + _, err = cli.DeleteFile(ctx, ref.Raw()) + if err != nil { + return res, err + } + return res, err +} diff --git a/cmd/mstorectl/filecmd/expfiles.go b/cmd/mstorectl/filecmd/expfiles.go new file mode 100644 index 0000000..493d7ec --- /dev/null +++ b/cmd/mstorectl/filecmd/expfiles.go @@ -0,0 +1,121 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "time" + + "github.com/spf13/cobra" + + "mstore/pkg/descr" + "mstore/pkg/filecli" +) + +// ExportFiles +type ExportFilesParams struct { + Filepath string + Detail bool + Prefix bool + Regexp bool + Dest string +} + +type ExportFilesResult struct { + Files []descr.File `yaml:"files,omitempty"` + Filenames []string `yaml:"filenames,omitempty"` +} + +func (util *FileUtil) ExportFiles(cmd *cobra.Command, args []string) { + util.exportFilesParams.Filepath = args[0] + util.exportFilesParams.Dest = args[1] + res, err := util.exportFiles(&util.commonFileParams, &util.exportFilesParams) + printResponse(res, err) +} + +func (util *FileUtil) exportFiles(common *CommonFileParams, params *ExportFilesParams) (*ExportFilesResult, error) { + var err error + res := &ExportFilesResult{} + + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref, err := filecli.ParsePath(params.Filepath) + if err != nil { + return res, err + } + ref.SetUserinfo(common.Username, common.Password) + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + cli := filecli.NewClient(nil, mw) + list, err := cli.ListFiles(ctx, ref.Raw()) + if err != nil { + return res, err + } + files := make([]descr.File, 0) + err = json.Unmarshal(list, &files) + if err != nil { + return res, err + } + + exported := make([]descr.File, 0) + for _, descr := range files { + destdir := filepath.Join(params.Dest, descr.Collection) + err = os.MkdirAll(destdir, 0750) + if err != nil { + return res, err + } + destpath := filepath.Join(params.Dest, descr.Collection, descr.Name) + destfile, err := os.OpenFile(destpath, os.O_WRONLY|os.O_CREATE, 0640) + if err != nil { + return res, err + } + defer destfile.Close() + + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref.SetResource(filepath.Join(descr.Collection, descr.Name)) + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + cli := filecli.NewClient(nil, mw) + _, err = cli.GetFile(ctx, ref.Raw(), destfile) + if err != nil { + + fmt.Printf("- %s: error %v\n", ref.Raw(), err) + err = nil + } else { + fmt.Printf("- %s: ok\n", ref.Raw()) + exported = append(exported, descr) + } + } + if params.Detail { + res.Files = exported + } else { + files := descr.NewFiles() + files.Set(exported) + res.Filenames = files.List() + } + return res, err +} + +func makeFileURI(hosturi, collection, name string) (string, error) { + var err error + var res string + uri, err := url.Parse(hosturi) + if err != nil { + return res, err + } + uri.Path = path.Join(collection, name) + res = uri.String() + return res, err +} diff --git a/cmd/mstorectl/filecmd/filecmd.go b/cmd/mstorectl/filecmd/filecmd.go new file mode 100644 index 0000000..9ad60b5 --- /dev/null +++ b/cmd/mstorectl/filecmd/filecmd.go @@ -0,0 +1,193 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "sigs.k8s.io/yaml" +) + +func (util *FileUtil) MakeFileCmds() *cobra.Command { + var subCmd = &cobra.Command{ + Use: "files", + Short: "File operations", + Aliases: []string{"file"}, + } + const defaultTimeout uint64 = 10 + + subCmd.PersistentFlags().StringVarP(&util.commonFileParams.Username, "user", "u", "", "Username") + subCmd.PersistentFlags().StringVarP(&util.commonFileParams.Password, "pass", "p", "", "Password") + subCmd.PersistentFlags().Uint64VarP(&util.commonFileParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") + subCmd.PersistentFlags().BoolVarP(&util.commonFileParams.SkipTLSVerify, "skipVerify", "S", true, "Skip server certificate verify") + subCmd.MarkPersistentFlagRequired("host") + subCmd.MarkFlagsRequiredTogether("user", "pass") + + vi := viper.New() + vi.SetEnvPrefix("mstore") + vi.BindEnv("user") + vi.BindEnv("pass") + util.commonFileParams.Username = vi.GetString("user") + util.commonFileParams.Password = vi.GetString("pass") + + // PutFile + var putFileCmd = &cobra.Command{ + Use: "put filepath [user:pass@]hostname[:port]/collection/name", + Args: cobra.ExactArgs(2), + Aliases: []string{"push"}, + Short: "Put file to storage", + Run: util.PutFile, + } + subCmd.AddCommand(putFileCmd) + + // GetFile + var getFileCmd = &cobra.Command{ + Use: "get [user:pass@]hostname[:port]/collection/name filepath", + Aliases: []string{"pull"}, + Args: cobra.ExactArgs(2), + Short: "Get file from storage", + Run: util.GetFile, + } + subCmd.AddCommand(getFileCmd) + + // FileInfo + var fileInfoCmd = &cobra.Command{ + Use: "info [user:pass@]hostname[:port]/collection/name", + Args: cobra.ExactArgs(1), + Short: "Show file information", + Run: util.FileInfo, + } + subCmd.AddCommand(fileInfoCmd) + + // DeleteFile + var deleteFileCmd = &cobra.Command{ + Use: "delete [user:pass@]hostname[:port]/collection/name", + Args: cobra.ExactArgs(1), + Aliases: []string{"remove"}, + Short: "Delete file in storage", + Run: util.DeleteFile, + } + subCmd.AddCommand(deleteFileCmd) + + // ListFiles + var listFilesCmd = &cobra.Command{ + Use: "list [user:pass@]hostname[:port]/catalog", + Args: cobra.ExactArgs(1), + Short: "List files in storage", + Run: util.ListFiles, + } + listFilesCmd.Flags().BoolVarP(&util.listFilesParams.Detail, "detail", "D", false, "Show detail file information") + listFilesCmd.Flags().BoolVarP(&util.listFilesParams.Prefix, "prefix", "P", true, "Use path as collection path prefix") + listFilesCmd.Flags().BoolVarP(&util.listFilesParams.Regexp, "regex", "R", false, "Use path as collection path prefix") + subCmd.AddCommand(listFilesCmd) + + // ImportFiles + var importFilesCmd = &cobra.Command{ + Use: "import directory [user:pass@]hostname[:port]/collection", + Args: cobra.ExactArgs(2), + Short: "Send file tree to storage as is", + Run: util.ImportFiles, + } + subCmd.AddCommand(importFilesCmd) + + // ExportFiles + var exportFilesCmd = &cobra.Command{ + Use: "export directory [user:pass@]hostname[:port]/collection dir", + Args: cobra.ExactArgs(2), + Short: "Download file tree to storage as is", + Run: util.ExportFiles, + } + exportFilesCmd.Flags().BoolVarP(&util.exportFilesParams.Detail, "detail", "D", false, "Show detail file information") + exportFilesCmd.Flags().BoolVarP(&util.exportFilesParams.Prefix, "prefix", "P", true, "Use path as collection path prefix") + exportFilesCmd.Flags().BoolVarP(&util.exportFilesParams.Regexp, "regex", "R", false, "Use path as collection path prefix") + subCmd.AddCommand(exportFilesCmd) + + return subCmd +} + +func (util *FileUtil) MakeCollectionCmds() *cobra.Command { + var subCmd = &cobra.Command{ + Use: "collections", + Short: "Colletion operations", + Aliases: []string{"col", "cols", "dir", "dirs"}, + } + const defaultTimeout uint64 = 10 + + subCmd.PersistentFlags().StringVarP(&util.commonFileParams.Username, "user", "u", "", "Username") + subCmd.PersistentFlags().StringVarP(&util.commonFileParams.Password, "pass", "p", "", "Password") + subCmd.PersistentFlags().Uint64VarP(&util.commonFileParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") + subCmd.MarkPersistentFlagRequired("host") + subCmd.MarkFlagsRequiredTogether("user", "pass") + + // ListCollections + var listCollectionsCmd = &cobra.Command{ + Use: "list [user:pass@]hostname[:port]/catalog", + Args: cobra.ExactArgs(1), + Short: "List collections in storage", + Run: util.ListCollections, + } + listCollectionsCmd.Flags().BoolVarP(&util.listCollectionsParams.Prefix, "prefix", "P", true, "Use path as collection path prefix") + subCmd.AddCommand(listCollectionsCmd) + + // DeleteCollection + var deleteCollectionCmd = &cobra.Command{ + Use: "delete [user:pass@]hostname[:port]/catalog", + Args: cobra.ExactArgs(1), + Short: "Delete all files in collection", + Run: util.DeleteCollection, + } + deleteCollectionCmd.Flags().BoolVarP(&util.deleteCollectionParams.Detail, "detail", "D", false, "Show detail file information") + deleteCollectionCmd.Flags().BoolVarP(&util.deleteCollectionParams.Prefix, "prefix", "P", false, "Use path as collection path prefix") + deleteCollectionCmd.Flags().BoolVarP(&util.deleteCollectionParams.Regexp, "regex", "R", false, "Use path as collection path prefix") + deleteCollectionCmd.Flags().BoolVarP(&util.deleteCollectionParams.DryRun, "dryrun", "Y", false, "Simulate process, don't delete files") + + subCmd.AddCommand(deleteCollectionCmd) + + return subCmd +} + +type FileUtil struct { + fileInfoParams FileInfoParams + putFileParams PutFileParams + getFileParams GetFileParams + deleteFileParams DeleteFileParams + listFilesParams ListFilesParams + importFilesParams ImportFilesParams + exportFilesParams ExportFilesParams + deleteCollectionParams DeleteCollectionParams + listCollectionsParams ListCollectionsParams + commonFileParams CommonFileParams +} + +type CommonFileParams struct { + Username string + Password string + Timeout uint64 + SkipTLSVerify bool +} + +func printResponse(res any, err error) { + type Response struct { + Error bool `json:"error" yaml:"error"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Result any `json:"result,omitempty" yaml:"result,omitempty"` + } + resp := Response{} + if err != nil { + resp.Error = true + resp.Message = err.Error() + } else { + resp.Result = res + } + respBytes, _ := yaml.Marshal(resp) + fmt.Printf("---\n%s\n", string(respBytes)) +} diff --git a/cmd/mstorectl/filecmd/fileinfo.go b/cmd/mstorectl/filecmd/fileinfo.go new file mode 100644 index 0000000..7282b00 --- /dev/null +++ b/cmd/mstorectl/filecmd/fileinfo.go @@ -0,0 +1,63 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "context" + "fmt" + "time" + + "github.com/spf13/cobra" + + "mstore/pkg/descr" + "mstore/pkg/filecli" +) + +// FileInfo +type FileInfoParams struct { + Filepath string +} +type FileInfoResult struct { + File *descr.File `yaml:"file,omitempty"` + Size int64 `yaml:"size,omitempty"` + Digest string `yaml:"digest,omitempty"` +} + +func (util *FileUtil) FileInfo(cmd *cobra.Command, args []string) { + util.fileInfoParams.Filepath = args[0] + res, err := util.fileInfo(&util.commonFileParams, &util.fileInfoParams) + printResponse(res, err) +} + +func (util *FileUtil) fileInfo(common *CommonFileParams, params *FileInfoParams) (*FileInfoResult, error) { + var err error + res := &FileInfoResult{} + ref, err := filecli.ParsePath(params.Filepath) + if err != nil { + return res, err + } + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref.SetUserinfo(common.Username, common.Password) + + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + cli := filecli.NewClient(nil, mw) + exists, size, digest, err := cli.FileInfo(ctx, ref.Raw()) + if err != nil { + return res, err + } + if !exists { + err = fmt.Errorf("File %s not exists", params.Filepath) + return res, err + } + res.Size = size + res.Digest = digest + return res, err +} diff --git a/cmd/mstorectl/filecmd/getfile.go b/cmd/mstorectl/filecmd/getfile.go new file mode 100644 index 0000000..9758f88 --- /dev/null +++ b/cmd/mstorectl/filecmd/getfile.go @@ -0,0 +1,66 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "context" + "os" + "path/filepath" + "time" + + "github.com/spf13/cobra" + + "mstore/pkg/filecli" +) + +// GetFile +type GetFileParams struct { + Source string + Dest string +} + +func (util *FileUtil) GetFile(cmd *cobra.Command, args []string) { + util.getFileParams.Source = args[0] + util.getFileParams.Dest = args[1] + res, err := util.getFile(&util.commonFileParams, &util.getFileParams) + printResponse(res, err) +} + +type GetFileResult struct{} + +func (util *FileUtil) getFile(common *CommonFileParams, params *GetFileParams) (*GetFileResult, error) { + var err error + res := &GetFileResult{} + + err = os.MkdirAll(filepath.Dir(params.Dest), 0750) + if err != nil { + return res, err + } + file, err := os.OpenFile(params.Dest, os.O_WRONLY|os.O_CREATE, 0640) + if err != nil { + return res, err + } + defer file.Close() + + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref, err := filecli.ParsePath(params.Source) + if err != nil { + return res, err + } + ref.SetUserinfo(common.Username, common.Password) + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + cli := filecli.NewClient(nil, mw) + _, err = cli.GetFile(ctx, ref.Raw(), file) + if err != nil { + return res, err + } + return res, err +} diff --git a/cmd/mstorectl/filecmd/impfiles.go b/cmd/mstorectl/filecmd/impfiles.go new file mode 100644 index 0000000..38900a1 --- /dev/null +++ b/cmd/mstorectl/filecmd/impfiles.go @@ -0,0 +1,116 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "context" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + "time" + + "github.com/spf13/cobra" + + "mstore/pkg/filecli" +) + +// ImportFiles +type ImportFilesParams struct { + Source string + Dest string + Progress bool +} +type ImportFilesResult struct { + Files []string `json:"files,omitempty"` +} + +func (util *FileUtil) ImportFiles(cmd *cobra.Command, args []string) { + util.importFilesParams.Source = args[0] + util.importFilesParams.Dest = args[1] + + res, err := util.importFiles(&util.commonFileParams, &util.importFilesParams) + printResponse(res, err) +} + +func (util *FileUtil) importFiles(common *CommonFileParams, params *ImportFilesParams) (*ImportFilesResult, error) { + var err error + res := &ImportFilesResult{ + Files: make([]string, 0), + } + + putErrors := make([]error, 0) + walcFunc := func(walkpath string, infoItem fs.FileInfo, err error) error { + if err != nil { + return err + } + mode := infoItem.Mode() + skip := mode == fs.ModeDevice + skip = skip || mode == fs.ModeNamedPipe + skip = skip || mode == fs.ModeCharDevice + if skip { + return nil + } + + if !infoItem.IsDir() { + relpath, _ := strings.CutPrefix(walkpath, params.Source) + ref, err := filecli.ParsePath(params.Dest) + if err != nil { + putErrors = append(putErrors, err) + return nil + } + ref.JoinResource(relpath) + //fmt.Printf("====%s %s\n", relpath, ref.Raw()) + //return nil + file, err := os.OpenFile(walkpath, os.O_RDONLY, 0) + if err != nil { + putErrors = append(putErrors, err) + return nil + } + defer file.Close() + stat, err := file.Stat() + if err != nil { + putErrors = append(putErrors, err) + return nil + } + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + + ref.SetUserinfo(common.Username, common.Password) + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + + cli := filecli.NewClient(nil, mw) + err = cli.PutFile(ctx, ref.Raw(), file, stat.Size()) + if err != nil { + putErrors = append(putErrors, err) + fmt.Printf("- %s: error: %v \n", ref.Raw(), err) + } else { + res.Files = append(res.Files, relpath) + fmt.Printf("- %s: ok\n", ref.Raw()) + } + } + return nil + } + err = filepath.Walk(params.Source, walcFunc) + if err != nil { + return res, err + } + + for _, item := range putErrors { + err = errors.Join(err, item) + } + err = filepath.Walk(params.Source, walcFunc) + if err != nil { + return res, err + } + return res, err +} diff --git a/cmd/mstorectl/filecmd/listcolls.go b/cmd/mstorectl/filecmd/listcolls.go new file mode 100644 index 0000000..91230f0 --- /dev/null +++ b/cmd/mstorectl/filecmd/listcolls.go @@ -0,0 +1,66 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "context" + "encoding/json" + "time" + + "github.com/spf13/cobra" + + "mstore/pkg/filecli" +) + +// ListCollections +type ListCollectionsParams struct { + Path string + Prefix bool + Regexp bool +} + +type ListCollectionsResult struct { + Collections []string `json:"collections,omitempty"` +} + +func (util *FileUtil) ListCollections(cmd *cobra.Command, args []string) { + util.listCollectionsParams.Path = args[0] + res, err := util.listCollections(&util.commonFileParams, &util.listCollectionsParams) + printResponse(res, err) +} + +func (util *FileUtil) listCollections(common *CommonFileParams, params *ListCollectionsParams) (*ListCollectionsResult, error) { + var err error + res := &ListCollectionsResult{ + Collections: make([]string, 0), + } + + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref, err := filecli.ParsePath(params.Path) + if err != nil { + return res, err + } + ref.SetUserinfo(common.Username, common.Password) + ref.PathType(pathType(params.Regexp, params.Prefix)) + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + cli := filecli.NewClient(nil, mw) + list, err := cli.ListCollections(ctx, ref.Raw()) + if err != nil { + return res, err + } + collections := make([]string, 0) + err = json.Unmarshal(list, &collections) + if err != nil { + return res, err + } + res.Collections = collections + return res, err +} diff --git a/cmd/mstorectl/filecmd/listfiles.go b/cmd/mstorectl/filecmd/listfiles.go new file mode 100644 index 0000000..4f399e4 --- /dev/null +++ b/cmd/mstorectl/filecmd/listfiles.go @@ -0,0 +1,84 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "context" + "encoding/json" + "time" + "fmt" + + "github.com/spf13/cobra" + + "mstore/pkg/descr" + "mstore/pkg/filecli" +) + +// ListFiles +type ListFilesParams struct { + Filepath string + Detail bool + Prefix bool + Regexp bool +} + +type ListFilesResult struct { + Files []descr.File `json:"files,omitempty"` + Filenames []string `json:"filenames,omitempty"` +} + +func (util *FileUtil) ListFiles(cmd *cobra.Command, args []string) { + util.listFilesParams.Filepath = args[0] + res, err := util.listFiles(&util.commonFileParams, &util.listFilesParams) + printResponse(res, err) +} + +func (util *FileUtil) listFiles(common *CommonFileParams, params *ListFilesParams) (*ListFilesResult, error) { + var err error + res := &ListFilesResult{} + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref, err := filecli.ParsePath(params.Filepath) + if err != nil { + return res, err + } + ref.SetUserinfo(common.Username, common.Password) + ref.PathType(pathType(params.Regexp, params.Prefix)) + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + + cli := filecli.NewClient(nil, mw) + list, err := cli.ListFiles(ctx, ref.Raw()) + if err != nil { + return res, err + } + fmt.Printf("====%s\n", list) + files := descr.NewFiles() + err = json.Unmarshal(list, files.ArrayPtr()) + if err != nil { + return res, err + } + if !params.Detail { + res.Filenames = files.List() + } else { + res.Files = files.Array() + } + return res, err +} + +func pathType(regex, prefix bool) string { + switch { + case regex: + return filecli.PathTypeRegexp + case prefix: + return filecli.PathTypePrefix + default: + } + return filecli.PathTypeIdentic +} diff --git a/cmd/mstorectl/filecmd/putfile.go b/cmd/mstorectl/filecmd/putfile.go new file mode 100644 index 0000000..31ee171 --- /dev/null +++ b/cmd/mstorectl/filecmd/putfile.go @@ -0,0 +1,64 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package filecmd + +import ( + "context" + "os" + "time" + + "github.com/spf13/cobra" + + "mstore/pkg/filecli" +) + +// PutFile +type PutFileParams struct { + Source string + Dest string +} +type PutFileResult struct{} + +func (util *FileUtil) PutFile(cmd *cobra.Command, args []string) { + util.putFileParams.Source = args[0] + util.putFileParams.Dest = args[1] + res, err := util.putFile(&util.commonFileParams, &util.putFileParams) + printResponse(res, err) +} + +func (util *FileUtil) putFile(common *CommonFileParams, params *PutFileParams) (*PutFileResult, error) { + var err error + res := &PutFileResult{} + + file, err := os.OpenFile(params.Source, os.O_RDONLY, 0) + if err != nil { + return res, err + } + defer file.Close() + stat, err := file.Stat() + if err != nil { + return res, err + } + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref, err := filecli.ParsePath(params.Dest) + if err != nil { + return res, err + } + ref.SetUserinfo(common.Username, common.Password) + + mw := filecli.NewBasicAuthMiddleware(ref.Userinfo()) + cli := filecli.NewClient(nil, mw) + err = cli.PutFile(ctx, ref.Raw(), file, stat.Size()) + if err != nil { + return res, err + } + return res, err +} diff --git a/cmd/mstored/starter/starter.go b/cmd/mstored/starter/starter.go new file mode 100644 index 0000000..f6b0732 --- /dev/null +++ b/cmd/mstored/starter/starter.go @@ -0,0 +1,80 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package starter + +import ( + "os" + "path/filepath" + + "mstore/app/server" + + "github.com/spf13/cobra" +) + +type Starter struct { + runAsDaemon bool + port int64 + cmd *cobra.Command + srv *server.Server +} + +func NewStarter() *Starter { + execName := filepath.Base(os.Args[0]) + sta := &Starter{} + cmd := &cobra.Command{ + Use: execName, + Short: "\nArtifact storage service", + SilenceUsage: true, + RunE: sta.run, + } + cmd.CompletionOptions.DisableDefaultCmd = true + cmd.Flags().BoolVarP(&sta.runAsDaemon, "asDaemon", "D", true, "Run service as daemon") + cmd.Flags().Int64VarP(&sta.port, "port", "P", 1025, "Service port") + + sta.cmd = cmd + return sta +} + +func (sta *Starter) GetCmd() *cobra.Command { + return sta.cmd +} + +func (sta *Starter) run(cmd *cobra.Command, args []string) error { + var err error + srv, err := server.NewServer() + if err != nil { + return err + } + err = srv.Configure() + if err != nil { + return err + } + srv.SetAsDaemon(sta.runAsDaemon) + srv.SetPort(sta.port) + + err = srv.Daemonize() + if err != nil { + return err + } + err = srv.Build() + if err != nil { + return err + } + err = srv.Run() + if err != nil { + return err + } + sta.srv = srv + return err +} + +func (sta *Starter) Exec() error { + return sta.cmd.Execute() +}