@@ -3,8 +3,11 @@ package rofl
33import (
44 "context"
55 "fmt"
6+ "maps"
67 "os"
78 "path/filepath"
9+ "slices"
10+ "strings"
811
912 v1 "github.com/opencontainers/image-spec/specs-go/v1"
1013 oras "oras.land/oras-go/v2"
@@ -14,6 +17,7 @@ import (
1417 "oras.land/oras-go/v2/registry/remote/credentials"
1518 "oras.land/oras-go/v2/registry/remote/retry"
1619
20+ "github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
1721 "github.com/oasisprotocol/oasis-core/go/runtime/bundle"
1822)
1923
@@ -24,53 +28,63 @@ const (
2428)
2529
2630// PushBundleToOciRepository pushes an ORC bundle to the given remote OCI repository.
27- func PushBundleToOciRepository (bundleFn , dst , tag string ) error {
31+ //
32+ // Returns the OCI manifest digest and the ORC manifest hash.
33+ func PushBundleToOciRepository (bundleFn , dst string ) (string , hash.Hash , error ) {
2834 ctx := context .Background ()
2935
36+ atoms := strings .Split (dst , ":" )
37+ if len (atoms ) != 2 {
38+ return "" , hash.Hash {}, fmt .Errorf ("malformed OCI repository reference (repo:tag required)" )
39+ }
40+ dst = atoms [0 ]
41+ tag := atoms [1 ]
42+
3043 // Open the bundle.
3144 bnd , err := bundle .Open (bundleFn )
3245 if err != nil {
33- return fmt .Errorf ("failed to open bundle: %w" , err )
46+ return "" , hash. Hash {}, fmt .Errorf ("failed to open bundle: %w" , err )
3447 }
3548 defer bnd .Close ()
3649
3750 // Create a temporary file store to build the OCI layers.
3851 tmpDir , err := os .MkdirTemp ("" , "oasis-orc2oci" )
3952 if err != nil {
40- return fmt .Errorf ("failed to create temporary directory: %w" , err )
53+ return "" , hash. Hash {}, fmt .Errorf ("failed to create temporary directory: %w" , err )
4154 }
4255 defer os .RemoveAll (tmpDir )
4356
4457 storeDir := filepath .Join (tmpDir , "oci" )
4558 store , err := file .New (storeDir )
4659 if err != nil {
47- return fmt .Errorf ("failed to create temporary OCI store: %w" , err )
60+ return "" , hash. Hash {}, fmt .Errorf ("failed to create temporary OCI store: %w" , err )
4861 }
4962 defer store .Close ()
5063
5164 bundleDir := filepath .Join (tmpDir , "bundle" )
5265 if err = bnd .WriteExploded (bundleDir ); err != nil {
53- return fmt .Errorf ("failed to explode bundle: %w" , err )
66+ return "" , hash. Hash {}, fmt .Errorf ("failed to explode bundle: %w" , err )
5467 }
5568
5669 // Generate the config object from the manifest.
5770 const manifestName = "META-INF/MANIFEST.MF"
5871 configDsc , err := store .Add (ctx , manifestName , ociTypeOrcConfig , filepath .Join (bundleDir , manifestName ))
5972 if err != nil {
60- return fmt .Errorf ("failed to add config object from manifest: %w" , err )
73+ return "" , hash. Hash {}, fmt .Errorf ("failed to add config object from manifest: %w" , err )
6174 }
6275
6376 // Add other files as layers.
6477 layers := make ([]v1.Descriptor , 0 , len (bnd .Data )- 1 )
65- for fn := range bnd .Data {
78+ fns := slices .Sorted (maps .Keys (bnd .Data )) // Ensure deterministic order.
79+ for _ , fn := range fns {
6680 if fn == manifestName {
6781 continue
6882 }
6983
7084 var layerDsc v1.Descriptor
7185 layerDsc , err = store .Add (ctx , fn , ociTypeOrcLayer , filepath .Join (bundleDir , fn ))
7286 if err != nil {
73- return fmt .Errorf ("failed to add OCI layer: %w" , err )
87+ return "" , hash. Hash {}, fmt .Errorf ("failed to add OCI layer: %w" , err )
7488 }
7589
7690 layers = append (layers , layerDsc )
@@ -80,25 +94,29 @@ func PushBundleToOciRepository(bundleFn, dst, tag string) error {
8094 opts := oras.PackManifestOptions {
8195 Layers : layers ,
8296 ConfigDescriptor : & configDsc ,
97+ ManifestAnnotations : map [string ]string {
98+ // Use a fixed crated timestamp to avoid changing the manifest digest for no reason.
99+ v1 .AnnotationCreated : "2025-03-31T00:00:00Z" ,
100+ },
83101 }
84102 manifestDescriptor , err := oras .PackManifest (ctx , store , oras .PackManifestVersion1_1 , ociTypeOrcArtifact , opts )
85103 if err != nil {
86- return fmt .Errorf ("failed to pack OCI manifest: %w" , err )
104+ return "" , hash. Hash {}, fmt .Errorf ("failed to pack OCI manifest: %w" , err )
87105 }
88106
89107 // Tag the manifest.
90108 if err = store .Tag (ctx , manifestDescriptor , tag ); err != nil {
91- return fmt .Errorf ("failed to tag OCI manifest: %w" , err )
109+ return "" , hash. Hash {}, fmt .Errorf ("failed to tag OCI manifest: %w" , err )
92110 }
93111
94112 // Connect to remote repository.
95113 repo , err := remote .NewRepository (dst )
96114 if err != nil {
97- return fmt .Errorf ("failed to init remote OCI repository: %w" , err )
115+ return "" , hash. Hash {}, fmt .Errorf ("failed to init remote OCI repository: %w" , err )
98116 }
99117 creds , err := credentials .NewStoreFromDocker (credentials.StoreOptions {})
100118 if err != nil {
101- return fmt .Errorf ("failed to init OCI credential store: %w" , err )
119+ return "" , hash. Hash {}, fmt .Errorf ("failed to init OCI credential store: %w" , err )
102120 }
103121 repo .Client = & auth.Client {
104122 Client : retry .DefaultClient ,
@@ -108,8 +126,8 @@ func PushBundleToOciRepository(bundleFn, dst, tag string) error {
108126
109127 // Push to remote repository.
110128 if _ , err = oras .Copy (ctx , store , tag , repo , tag , oras .DefaultCopyOptions ); err != nil {
111- return fmt .Errorf ("failed to push to remote OCI repository: %w" , err )
129+ return "" , hash. Hash {}, fmt .Errorf ("failed to push to remote OCI repository: %w" , err )
112130 }
113131
114- return nil
132+ return manifestDescriptor . Digest . String (), bnd . Manifest . Hash (), nil
115133}
0 commit comments