Skip to content

Commit c8281f2

Browse files
committed
feat(pack): add optional OCI subject linkage
1 parent cd23d67 commit c8281f2

2 files changed

Lines changed: 66 additions & 0 deletions

File tree

pkg/cmd/pack/pack.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,27 @@ package pack
1818

1919
import (
2020
"context"
21+
"errors"
2122
"fmt"
2223
"io"
2324
"os"
25+
"strings"
2426

2527
"github.com/kitops-ml/kitops/pkg/artifact"
28+
cmdoptions "github.com/kitops-ml/kitops/pkg/cmd/options"
2629
"github.com/kitops-ml/kitops/pkg/lib/constants"
2730
"github.com/kitops-ml/kitops/pkg/lib/constants/mediatype"
2831
"github.com/kitops-ml/kitops/pkg/lib/filesystem"
2932
"github.com/kitops-ml/kitops/pkg/lib/filesystem/ignore"
3033
kfutils "github.com/kitops-ml/kitops/pkg/lib/kitfile"
3134
"github.com/kitops-ml/kitops/pkg/lib/repo/local"
35+
"github.com/kitops-ml/kitops/pkg/lib/repo/remote"
3236
"github.com/kitops-ml/kitops/pkg/lib/repo/util"
3337
"github.com/kitops-ml/kitops/pkg/output"
3438

39+
"github.com/opencontainers/go-digest"
3540
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
41+
"oras.land/oras-go/v2/errdef"
3642
)
3743

3844
// runPack compresses and stores a modelkit based on a Kitfile. Returns an error if packing
@@ -79,13 +85,21 @@ func runPack(ctx context.Context, options *packOptions) error {
7985

8086
func pack(ctx context.Context, opts *packOptions, kitfile *artifact.KitFile, localRepo local.LocalRepo) (*ocispec.Descriptor, error) {
8187
var extraLayerPaths []string
88+
var subject *ocispec.Descriptor
8289
if kitfile.Model != nil && artifact.IsModelKitReference(kitfile.Model.Path) {
8390
baseRef := artifact.FormatRepositoryForDisplay(opts.modelRef.String())
8491
parentKitfile, err := kfutils.ResolveKitfile(ctx, opts.configHome, kitfile.Model.Path, baseRef)
8592
if err != nil {
8693
return nil, fmt.Errorf("Failed to resolve referenced modelkit %s: %w", kitfile.Model.Path, err)
8794
}
8895
extraLayerPaths = util.LayerPathsFromKitfile(parentKitfile)
96+
97+
baseDesc, err := resolveBaseManifestDescriptor(ctx, opts.configHome, kitfile.Model.Path)
98+
if err != nil {
99+
output.Logf(output.LogLevelWarn, "failed to resolve base manifest descriptor for OCI subject: %v", err)
100+
} else if baseDesc != nil {
101+
subject = baseDesc
102+
}
89103
}
90104

91105
ignore, err := ignore.NewFromContext(opts.contextDir, kitfile, extraLayerPaths...)
@@ -105,13 +119,61 @@ func pack(ctx context.Context, opts *packOptions, kitfile *artifact.KitFile, loc
105119
ModelFormat: modelFormat,
106120
Compression: compression,
107121
LayerFormat: mediatype.TarFormat,
122+
Subject: subject,
108123
})
109124
if err != nil {
110125
return nil, err
111126
}
112127
return manifestDesc, nil
113128
}
114129

130+
func resolveBaseManifestDescriptor(ctx context.Context, configHome, baseRef string) (*ocispec.Descriptor, error) {
131+
if strings.HasPrefix(baseRef, "sha256:") {
132+
parsedDigest, err := digest.Parse(baseRef)
133+
if err != nil {
134+
return nil, fmt.Errorf("failed to parse digest-only base reference: %w", err)
135+
}
136+
return &ocispec.Descriptor{MediaType: ocispec.MediaTypeImageManifest, Digest: parsedDigest}, nil
137+
}
138+
139+
ref, _, err := artifact.ParseReference(baseRef)
140+
if err != nil {
141+
return nil, fmt.Errorf("failed to parse base reference: %w", err)
142+
}
143+
144+
localRepo, err := local.NewLocalRepo(constants.StoragePath(configHome), ref)
145+
if err != nil {
146+
return nil, fmt.Errorf("failed to create local repository: %w", err)
147+
}
148+
149+
desc, _, _, err := util.ResolveManifestAndConfig(ctx, localRepo, ref.Reference)
150+
if err == nil {
151+
return &desc, nil
152+
}
153+
if errors.Is(err, util.ErrNoKitfile) || errors.Is(err, util.ErrNotAModelKit) {
154+
return nil, nil
155+
}
156+
if !errors.Is(err, errdef.ErrNotFound) && !errors.Is(err, errdef.ErrMissingReference) {
157+
return nil, fmt.Errorf("failed to resolve base descriptor locally: %w", err)
158+
}
159+
160+
repository, err := remote.NewRepository(ctx, ref.Registry, ref.Repository, cmdoptions.DefaultNetworkOptions(configHome))
161+
if err != nil {
162+
return nil, fmt.Errorf("failed to create remote repository: %w", err)
163+
}
164+
165+
desc, _, _, err = util.ResolveManifestAndConfig(ctx, repository, ref.Reference)
166+
if err != nil {
167+
if errors.Is(err, util.ErrNoKitfile) || errors.Is(err, util.ErrNotAModelKit) {
168+
return nil, nil
169+
}
170+
output.Logf(output.LogLevelWarn,
171+
"failed to resolve base descriptor, skipping subject linkage: %v", err)
172+
return nil, nil
173+
}
174+
return &desc, nil
175+
}
176+
115177
func readKitfile(modelFile string) (*artifact.KitFile, error) {
116178
// 1. Read the model file
117179
kitfile := &artifact.KitFile{}

pkg/lib/filesystem/local-storage.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type SaveModelOptions struct {
4343
ModelFormat mediatype.ModelFormat
4444
Compression mediatype.CompressionType
4545
LayerFormat mediatype.Format
46+
Subject *ocispec.Descriptor
4647
}
4748

4849
// SaveModel saves an *artifact.Model to the provided oras.Target, compressing layers. It attempts to block
@@ -63,6 +64,9 @@ func SaveModel(ctx context.Context, localRepo local.LocalRepo, kitfile *artifact
6364
if err != nil {
6465
return nil, fmt.Errorf("error creating manifest: %w", err)
6566
}
67+
if opts.Subject != nil {
68+
manifest.Subject = opts.Subject
69+
}
6670

6771
// If not storing a Kitfile, save the Kitfile to an annotation since it will be lost otherwise
6872
if opts.ModelFormat == mediatype.ModelPackFormat {

0 commit comments

Comments
 (0)