@@ -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"
@@ -24,53 +27,63 @@ const (
2427)
2528
2629// PushBundleToOciRepository pushes an ORC bundle to the given remote OCI repository.
27- func PushBundleToOciRepository (bundleFn , dst , tag string ) error {
30+ //
31+ // Returns the manifest digest.
32+ func PushBundleToOciRepository (bundleFn , dst string ) (string , error ) {
2833 ctx := context .Background ()
2934
35+ atoms := strings .Split (dst , ":" )
36+ if len (atoms ) != 2 {
37+ return "" , fmt .Errorf ("malformed OCI repository reference (repo:tag required)" )
38+ }
39+ dst = atoms [0 ]
40+ tag := atoms [1 ]
41+
3042 // Open the bundle.
3143 bnd , err := bundle .Open (bundleFn )
3244 if err != nil {
33- return fmt .Errorf ("failed to open bundle: %w" , err )
45+ return "" , fmt .Errorf ("failed to open bundle: %w" , err )
3446 }
3547 defer bnd .Close ()
3648
3749 // Create a temporary file store to build the OCI layers.
3850 tmpDir , err := os .MkdirTemp ("" , "oasis-orc2oci" )
3951 if err != nil {
40- return fmt .Errorf ("failed to create temporary directory: %w" , err )
52+ return "" , fmt .Errorf ("failed to create temporary directory: %w" , err )
4153 }
4254 defer os .RemoveAll (tmpDir )
4355
4456 storeDir := filepath .Join (tmpDir , "oci" )
4557 store , err := file .New (storeDir )
4658 if err != nil {
47- return fmt .Errorf ("failed to create temporary OCI store: %w" , err )
59+ return "" , fmt .Errorf ("failed to create temporary OCI store: %w" , err )
4860 }
4961 defer store .Close ()
5062
5163 bundleDir := filepath .Join (tmpDir , "bundle" )
5264 if err = bnd .WriteExploded (bundleDir ); err != nil {
53- return fmt .Errorf ("failed to explode bundle: %w" , err )
65+ return "" , fmt .Errorf ("failed to explode bundle: %w" , err )
5466 }
5567
5668 // Generate the config object from the manifest.
5769 const manifestName = "META-INF/MANIFEST.MF"
5870 configDsc , err := store .Add (ctx , manifestName , ociTypeOrcConfig , filepath .Join (bundleDir , manifestName ))
5971 if err != nil {
60- return fmt .Errorf ("failed to add config object from manifest: %w" , err )
72+ return "" , fmt .Errorf ("failed to add config object from manifest: %w" , err )
6173 }
6274
6375 // Add other files as layers.
6476 layers := make ([]v1.Descriptor , 0 , len (bnd .Data )- 1 )
65- for fn := range bnd .Data {
77+ fns := slices .Sorted (maps .Keys (bnd .Data )) // Ensure deterministic order.
78+ for _ , fn := range fns {
6679 if fn == manifestName {
6780 continue
6881 }
6982
7083 var layerDsc v1.Descriptor
7184 layerDsc , err = store .Add (ctx , fn , ociTypeOrcLayer , filepath .Join (bundleDir , fn ))
7285 if err != nil {
73- return fmt .Errorf ("failed to add OCI layer: %w" , err )
86+ return "" , fmt .Errorf ("failed to add OCI layer: %w" , err )
7487 }
7588
7689 layers = append (layers , layerDsc )
@@ -80,25 +93,29 @@ func PushBundleToOciRepository(bundleFn, dst, tag string) error {
8093 opts := oras.PackManifestOptions {
8194 Layers : layers ,
8295 ConfigDescriptor : & configDsc ,
96+ ManifestAnnotations : map [string ]string {
97+ // Use a fixed crated timestamp to avoid changing the manifest digest for no reason.
98+ v1 .AnnotationCreated : "2025-03-31T00:00:00Z" ,
99+ },
83100 }
84101 manifestDescriptor , err := oras .PackManifest (ctx , store , oras .PackManifestVersion1_1 , ociTypeOrcArtifact , opts )
85102 if err != nil {
86- return fmt .Errorf ("failed to pack OCI manifest: %w" , err )
103+ return "" , fmt .Errorf ("failed to pack OCI manifest: %w" , err )
87104 }
88105
89106 // Tag the manifest.
90107 if err = store .Tag (ctx , manifestDescriptor , tag ); err != nil {
91- return fmt .Errorf ("failed to tag OCI manifest: %w" , err )
108+ return "" , fmt .Errorf ("failed to tag OCI manifest: %w" , err )
92109 }
93110
94111 // Connect to remote repository.
95112 repo , err := remote .NewRepository (dst )
96113 if err != nil {
97- return fmt .Errorf ("failed to init remote OCI repository: %w" , err )
114+ return "" , fmt .Errorf ("failed to init remote OCI repository: %w" , err )
98115 }
99116 creds , err := credentials .NewStoreFromDocker (credentials.StoreOptions {})
100117 if err != nil {
101- return fmt .Errorf ("failed to init OCI credential store: %w" , err )
118+ return "" , fmt .Errorf ("failed to init OCI credential store: %w" , err )
102119 }
103120 repo .Client = & auth.Client {
104121 Client : retry .DefaultClient ,
@@ -108,8 +125,8 @@ func PushBundleToOciRepository(bundleFn, dst, tag string) error {
108125
109126 // Push to remote repository.
110127 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 )
128+ return "" , fmt .Errorf ("failed to push to remote OCI repository: %w" , err )
112129 }
113130
114- return nil
131+ return manifestDescriptor . Digest . String (), nil
115132}
0 commit comments