diff --git a/cmd/root.go b/cmd/root.go index 12291d17..9b19cd8d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,6 +17,7 @@ import ( "kitops/pkg/cmd/logout" "kitops/pkg/cmd/pull" "kitops/pkg/cmd/push" + "kitops/pkg/cmd/tag" "kitops/pkg/cmd/version" "kitops/pkg/lib/constants" "kitops/pkg/output" @@ -73,6 +74,7 @@ func addSubcommands(rootCmd *cobra.Command) { rootCmd.AddCommand(list.ListCommand()) rootCmd.AddCommand(export.ExportCommand()) rootCmd.AddCommand(version.VersionCommand()) + rootCmd.AddCommand(tag.TagCommand()) } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/pkg/cmd/tag/cmd.go b/pkg/cmd/tag/cmd.go new file mode 100644 index 00000000..047d70dc --- /dev/null +++ b/pkg/cmd/tag/cmd.go @@ -0,0 +1,71 @@ +package tag + +import ( + "context" + "fmt" + "kitops/pkg/lib/constants" + "kitops/pkg/lib/repo" + "kitops/pkg/output" + + "github.com/spf13/cobra" + "oras.land/oras-go/v2/registry" +) + +var ( + shortDesc = "Tag a modelkit" + longDesc = `Tag a modelkit with a new tag.` +) + +type tagOptions struct { + configHome string + sourceRef *registry.Reference + targetRef *registry.Reference +} + +func (opts *tagOptions) complete(ctx context.Context, args []string) error { + + configHome, ok := ctx.Value(constants.ConfigKey{}).(string) + if !ok { + return fmt.Errorf("default config path not set on command context") + } + opts.configHome = configHome + modelRef, _, err := repo.ParseReference(args[0]) + if err != nil { + return fmt.Errorf("failed to parse reference %s: %w", opts.sourceRef, err) + } + opts.sourceRef = modelRef + modelRef, _, err = repo.ParseReference(args[1]) + if err != nil { + return fmt.Errorf("failed to parse reference %s: %w", opts.targetRef, err) + } + opts.targetRef = modelRef + return nil +} + +func TagCommand() *cobra.Command { + + cmd := &cobra.Command{ + Use: "tag SOURCE_MODELKIT[:TAG] TARGET_MODELKIT[:TAG]", + Short: shortDesc, + Long: longDesc, + Example: `kit tag myregistry.com/myrepo/mykit:latest myregistry.com/myrepo/mykit:v1.0.0`, + Run: runCommand(&tagOptions{}), + } + + cmd.Args = cobra.ExactArgs(2) + return cmd +} + +func runCommand(opts *tagOptions) func(cmd *cobra.Command, args []string) { + return func(cmd *cobra.Command, args []string) { + if err := opts.complete(cmd.Context(), args); err != nil { + output.Fatalf("Failed to parse argument: %s", err) + } + + err := RunTag(cmd.Context(), opts) + if err != nil { + output.Fatalf("Failed to tag modelkit: %s", err) + } + output.Infof("Modelkit %s tagged as %s", opts.sourceRef, opts.targetRef) + } +} diff --git a/pkg/cmd/tag/tag.go b/pkg/cmd/tag/tag.go new file mode 100644 index 00000000..049b5781 --- /dev/null +++ b/pkg/cmd/tag/tag.go @@ -0,0 +1,43 @@ +package tag + +import ( + "context" + "fmt" + "kitops/pkg/lib/constants" + "kitops/pkg/lib/repo" + + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/errdef" +) + +func RunTag(ctx context.Context, options *tagOptions) error { + storageHome := constants.StoragePath(options.configHome) + sourceStore, err := repo.NewLocalStore(storageHome, options.sourceRef) + if err != nil { + return fmt.Errorf("failed to open local storage: %w", err) + } + descriptor, err := oras.Resolve(ctx, sourceStore, options.sourceRef.Reference, oras.ResolveOptions{}) + if err != nil { + if err == errdef.ErrNotFound { + return fmt.Errorf("model %s not found", options.sourceRef.String()) + } + return fmt.Errorf("error resolving model: %s", err) + } + if options.sourceRef.Registry == options.targetRef.Registry && options.sourceRef.Repository == options.targetRef.Repository { + err = sourceStore.Tag(ctx, descriptor, options.targetRef.Reference) + if err != nil { + return fmt.Errorf("failed to tag reference %s: %w", options.targetRef, err) + } + return nil + } + // model kit is on a different registry and/or repo, copy the model to the target store + targetStore, err := repo.NewLocalStore(storageHome, options.targetRef) + if err != nil { + return fmt.Errorf("failed to open local storage: %w", err) + } + _, err = oras.Copy(ctx, sourceStore, options.sourceRef.Reference, targetStore, options.targetRef.Reference, oras.CopyOptions{}) + if err != nil { + return fmt.Errorf("failed to tag model: %w", err) + } + return nil +}