@@ -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,52 +27,62 @@ 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 layerDsc , err := store .Add (ctx , fn , ociTypeOrcLayer , filepath .Join (bundleDir , fn ))
7184 if err != nil {
72- return fmt .Errorf ("failed to add OCI layer: %w" , err )
85+ return "" , fmt .Errorf ("failed to add OCI layer: %w" , err )
7386 }
7487
7588 layers = append (layers , layerDsc )
@@ -79,25 +92,29 @@ func PushBundleToOciRepository(bundleFn, dst, tag string) error {
7992 opts := oras.PackManifestOptions {
8093 Layers : layers ,
8194 ConfigDescriptor : & configDsc ,
95+ ManifestAnnotations : map [string ]string {
96+ // Use a fixed crated timestamp to avoid changing the manifest digest for no reason.
97+ v1 .AnnotationCreated : "2025-03-31T00:00:00Z" ,
98+ },
8299 }
83100 manifestDescriptor , err := oras .PackManifest (ctx , store , oras .PackManifestVersion1_1 , ociTypeOrcArtifact , opts )
84101 if err != nil {
85- return fmt .Errorf ("failed to pack OCI manifest: %w" , err )
102+ return "" , fmt .Errorf ("failed to pack OCI manifest: %w" , err )
86103 }
87104
88105 // Tag the manifest.
89106 if err = store .Tag (ctx , manifestDescriptor , tag ); err != nil {
90- return fmt .Errorf ("failed to tag OCI manifest: %w" , err )
107+ return "" , fmt .Errorf ("failed to tag OCI manifest: %w" , err )
91108 }
92109
93110 // Connect to remote repository.
94111 repo , err := remote .NewRepository (dst )
95112 if err != nil {
96- return fmt .Errorf ("failed to init remote OCI repository: %w" , err )
113+ return "" , fmt .Errorf ("failed to init remote OCI repository: %w" , err )
97114 }
98115 creds , err := credentials .NewStoreFromDocker (credentials.StoreOptions {})
99116 if err != nil {
100- return fmt .Errorf ("failed to init OCI credential store: %w" , err )
117+ return "" , fmt .Errorf ("failed to init OCI credential store: %w" , err )
101118 }
102119 repo .Client = & auth.Client {
103120 Client : retry .DefaultClient ,
@@ -107,8 +124,8 @@ func PushBundleToOciRepository(bundleFn, dst, tag string) error {
107124
108125 // Push to remote repository.
109126 if _ , err = oras .Copy (ctx , store , tag , repo , tag , oras .DefaultCopyOptions ); err != nil {
110- return fmt .Errorf ("failed to push to remote OCI repository: %w" , err )
127+ return "" , fmt .Errorf ("failed to push to remote OCI repository: %w" , err )
111128 }
112129
113- return nil
130+ return manifestDescriptor . Digest . String (), nil
114131}
0 commit comments