@ -30,8 +30,10 @@ import (
"os"
"sort"
"strings"
"sync"
"github.com/Masterminds/semver/v3"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2"
@ -667,33 +669,53 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
}
}
ctx := context . Background ( )
memoryStore := memory . New ( )
chartDescriptor , err := oras . PushBytes ( ctx , memoryStore , ChartLayerMediaType , data )
repository , err := remote . NewRepository ( parsedRef . String ( ) )
if err != nil {
return nil , err
}
repository . PlainHTTP = c . plainHTTP
repository . Client = c . authorizer
configData , err := json . Marshal ( meta )
ctx := context . Background ( )
ctx = auth . AppendRepositoryScope ( ctx , repository . Reference , auth . ActionPull , auth . ActionPush )
chartBlob := newBlob ( repository , ChartLayerMediaType , data )
exists , err := chartBlob . exists ( ctx )
if err != nil {
return nil , err
}
configDescriptor , err := oras . PushBytes ( ctx , memoryStore , ConfigMediaType , configData )
layers := [ ] ocispec . Descriptor { chartBlob . descriptor }
var wg sync . WaitGroup
if ! exists {
wg . Go ( func ( ) { chartBlob . push ( ctx ) } )
}
configData , err := json . Marshal ( meta )
if err != nil {
return nil , err
}
configBlob := newBlob ( repository , ConfigMediaType , configData )
wg . Go ( func ( ) { configBlob . pushNew ( ctx ) } )
layers := [ ] ocispec . Descriptor { chartDescriptor }
var provDescriptor ocispec . Descriptor
var provBlob blob
if operation . provData != nil {
prov Descriptor, err = oras . PushBytes ( ctx , memoryStore , ProvLayerMediaType , operation . provData )
if err != nil {
return nil , err
}
prov Blob = newBlob ( repository , ProvLayerMediaType , operation . provData )
wg . Go ( func ( ) { provBlob . pushNew ( ctx ) } )
}
wg . Wait ( )
layers = append ( layers , provDescriptor )
if chartBlob . err != nil {
return nil , chartBlob . err
}
if configBlob . err != nil {
return nil , configBlob . err
}
if provBlob . err != nil {
return nil , provBlob . err
}
if operation . provData != nil {
layers = append ( layers , provBlob . descriptor )
}
// sort layers for determinism, similar to how ORAS v1 does it
@ -703,22 +725,20 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
ociAnnotations := generateOCIAnnotations ( meta , operation . creationTime )
manifestDescriptor , err := c . tagManifest ( ctx , memoryStore , configDescriptor ,
layers , ociAnnotations , parsedRef )
if err != nil {
return nil , err
manifest := ocispec . Manifest {
Versioned : specs . Versioned { SchemaVersion : 2 } ,
Config : configBlob . descriptor ,
Layers : layers ,
Annotations : ociAnnotations ,
}
repository, err := remote . NewRepository ( parsedRef . String ( ) )
manifestData, err := json . Marshal ( manifest )
if err != nil {
return nil , err
}
repository . PlainHTTP = c . plainHTTP
repository . Client = c . authorizer
ctx = withScopeHint ( ctx , repository , auth . ActionPull , auth . ActionPush )
manifestDescriptor , err = oras . ExtendedCopy ( ctx , memoryStore , parsedRef . String ( ) , repository , parsedRef . String ( ) , oras . DefaultExtendedCopyOptions )
manifestDescriptor , err := oras . TagBytes ( ctx , repository , ocispec . MediaTypeImageManifest ,
manifestData , parsedRef . String ( ) )
if err != nil {
return nil , err
}
@ -726,16 +746,16 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
chartSummary := & descriptorPushSummaryWithMeta {
Meta : meta ,
}
chartSummary . Digest = chart D escriptor. Digest . String ( )
chartSummary . Size = chart D escriptor. Size
chartSummary . Digest = chart Blob. d escriptor. Digest . String ( )
chartSummary . Size = chart Blob. d escriptor. Size
result := & PushResult {
Manifest : & descriptorPushSummary {
Digest : manifestDescriptor . Digest . String ( ) ,
Size : manifestDescriptor . Size ,
} ,
Config : & descriptorPushSummary {
Digest : config D escriptor. Digest . String ( ) ,
Size : config D escriptor. Size ,
Digest : config Blob. d escriptor. Digest . String ( ) ,
Size : config Blob. d escriptor. Size ,
} ,
Chart : chartSummary ,
Prov : & descriptorPushSummary { } , // prevent nil references
@ -743,8 +763,8 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
}
if operation . provData != nil {
result . Prov = & descriptorPushSummary {
Digest : prov D escriptor. Digest . String ( ) ,
Size : prov D escriptor. Size ,
Digest : prov Blob. d escriptor. Digest . String ( ) ,
Size : prov Blob. d escriptor. Size ,
}
}
_ , _ = fmt . Fprintf ( c . out , "Pushed: %s\n" , result . Ref )
@ -929,13 +949,53 @@ func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store,
manifestData , parsedRef . String ( ) )
}
// add actions when request a registry authentication token(jwt)
// example1. when we want to pull 'testrepo/local-subchart' we can send below url, and 'pull' is the action
// auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice
// example2. when we want to push 'testrepo/local-subchart' we can send below url, and 'pull%2Cpush' are the actions
// auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice
// we can set the actions like below
// example) ctx = withScopeHint(ctx, repository, auth.ActionPush, auth.ActionPull)
func withScopeHint ( ctx context . Context , repo * remote . Repository , actions ... string ) context . Context {
return auth . AppendRepositoryScope ( ctx , repo . Reference , actions ... )
// blob represents a content-addressable blob to be pushed to an OCI registry.
// It encapsulates the data, media type, and destination repository, and tracks
// the resulting descriptor and any error from push operations.
type blob struct {
dst * remote . Repository
mediaType string
data [ ] byte
descriptor ocispec . Descriptor
err error
}
// newBlob creates a new blob with the given repository, media type, and data.
func newBlob ( dst * remote . Repository , mediaType string , data [ ] byte ) blob {
return blob {
dst : dst ,
mediaType : mediaType ,
data : data ,
}
}
// exists checks if the blob already exists in the registry by computing its
// digest and querying the repository. It also populates the blob's descriptor
// with size, media type, and digest information.
func ( b * blob ) exists ( ctx context . Context ) ( bool , error ) {
b . descriptor . Size = int64 ( len ( b . data ) )
b . descriptor . MediaType = b . mediaType
b . descriptor . Digest = digest . FromBytes ( b . data )
return b . dst . Exists ( ctx , b . descriptor )
}
// pushNew checks if the blob exists in the registry first, and only pushes
// if it doesn't exist. This avoids redundant uploads for blobs that are
// already present. Any error is stored in b.err.
func ( b * blob ) pushNew ( ctx context . Context ) {
var exists bool
exists , b . err = b . exists ( ctx )
if b . err != nil {
return
}
if exists {
return
}
b . descriptor , b . err = oras . PushBytes ( ctx , b . dst , b . mediaType , b . data )
}
// push unconditionally pushes the blob to the registry without checking
// for existence first. Any error is stored in b.err.
func ( b * blob ) push ( ctx context . Context ) {
b . descriptor , b . err = oras . PushBytes ( ctx , b . dst , b . mediaType , b . data )
}