Skip to content

Commit 697861d

Browse files
committed
feat(cmd/rofl): Add support for pushing ORCs into OCI repositories
1 parent 6571c66 commit 697861d

4 files changed

Lines changed: 122 additions & 0 deletions

File tree

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ linters-settings:
7575
- gopkg.in/yaml.v3
7676
- github.com/compose-spec/compose-go/v2
7777
- github.com/github/go-spdx/v2
78+
- github.com/opencontainers/image-spec/specs-go/v1
79+
- oras.land/oras-go/v2
7880
exhaustive:
7981
# Switch statements are to be considered exhaustive if a 'default' case is
8082
# present, even if all enum members aren't listed in the switch.

build/rofl/oci.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package rofl
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
9+
v1 "github.com/opencontainers/image-spec/specs-go/v1"
10+
oras "oras.land/oras-go/v2"
11+
"oras.land/oras-go/v2/content/file"
12+
"oras.land/oras-go/v2/registry/remote"
13+
"oras.land/oras-go/v2/registry/remote/auth"
14+
"oras.land/oras-go/v2/registry/remote/credentials"
15+
"oras.land/oras-go/v2/registry/remote/retry"
16+
17+
"github.com/oasisprotocol/oasis-core/go/runtime/bundle"
18+
)
19+
20+
const (
21+
ociTypeOrcConfig = "application/vnd.oasis.orc.config.v1+json"
22+
ociTypeOrcLayer = "application/vnd.oasis.orc.layer.v1"
23+
ociTypeOrcArtifact = "application/vnd.oasis.orc"
24+
)
25+
26+
// PushBundleToOciRepository pushes an ORC bundle to the given remote OCI repository.
27+
func PushBundleToOciRepository(bundleFn, dst, tag string) error {
28+
ctx := context.Background()
29+
30+
// Open the bundle.
31+
bnd, err := bundle.Open(bundleFn)
32+
if err != nil {
33+
return fmt.Errorf("failed to open bundle: %w", err)
34+
}
35+
defer bnd.Close()
36+
37+
// Create a temporary file store to build the OCI layers.
38+
tmpDir, err := os.MkdirTemp("", "oasis-orc2oci")
39+
if err != nil {
40+
return fmt.Errorf("failed to create temporary directory: %w", err)
41+
}
42+
defer os.RemoveAll(tmpDir)
43+
44+
storeDir := filepath.Join(tmpDir, "oci")
45+
store, err := file.New(storeDir)
46+
if err != nil {
47+
return fmt.Errorf("failed to create temporary OCI store: %w", err)
48+
}
49+
defer store.Close()
50+
51+
bundleDir := filepath.Join(tmpDir, "bundle")
52+
if err = bnd.WriteExploded(bundleDir); err != nil {
53+
return fmt.Errorf("failed to explode bundle: %w", err)
54+
}
55+
56+
// Generate the config object from the manifest.
57+
const manifestName = "META-INF/MANIFEST.MF"
58+
configDsc, err := store.Add(ctx, manifestName, ociTypeOrcConfig, filepath.Join(bundleDir, manifestName))
59+
if err != nil {
60+
return fmt.Errorf("failed to add config object from manifest: %w", err)
61+
}
62+
63+
// Add other files as layers.
64+
layers := make([]v1.Descriptor, 0, len(bnd.Data)-1)
65+
for fn := range bnd.Data {
66+
if fn == manifestName {
67+
continue
68+
}
69+
70+
layerDsc, err := store.Add(ctx, fn, ociTypeOrcLayer, filepath.Join(bundleDir, fn))
71+
if err != nil {
72+
return fmt.Errorf("failed to add OCI layer: %w", err)
73+
}
74+
75+
layers = append(layers, layerDsc)
76+
}
77+
78+
// Pack the OCI manifest.
79+
opts := oras.PackManifestOptions{
80+
Layers: layers,
81+
ConfigDescriptor: &configDsc,
82+
}
83+
manifestDescriptor, err := oras.PackManifest(ctx, store, oras.PackManifestVersion1_1, ociTypeOrcArtifact, opts)
84+
if err != nil {
85+
return fmt.Errorf("failed to pack OCI manifest: %w", err)
86+
}
87+
88+
// Tag the manifest.
89+
if err = store.Tag(ctx, manifestDescriptor, tag); err != nil {
90+
return fmt.Errorf("failed to tag OCI manifest: %w", err)
91+
}
92+
93+
// Connect to remote repository.
94+
repo, err := remote.NewRepository(dst)
95+
if err != nil {
96+
return fmt.Errorf("failed to init remote OCI repository: %w", err)
97+
}
98+
creds, err := credentials.NewStoreFromDocker(credentials.StoreOptions{})
99+
if err != nil {
100+
return fmt.Errorf("failed to init OCI credential store: %w", err)
101+
}
102+
repo.Client = &auth.Client{
103+
Client: retry.DefaultClient,
104+
Cache: auth.NewCache(),
105+
Credential: credentials.Credential(creds),
106+
}
107+
108+
// Push to remote repository.
109+
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)
111+
}
112+
113+
return nil
114+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ require (
2626
github.com/oasisprotocol/oasis-core/go v0.2500.0
2727
github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.12.2
2828
github.com/olekukonko/tablewriter v0.0.5
29+
github.com/opencontainers/image-spec v1.1.0
2930
github.com/spf13/cobra v1.9.1
3031
github.com/spf13/pflag v1.0.6
3132
github.com/spf13/viper v1.19.0
@@ -36,6 +37,7 @@ require (
3637
golang.org/x/sys v0.31.0
3738
golang.org/x/text v0.23.0
3839
gopkg.in/yaml.v3 v3.0.1
40+
oras.land/oras-go/v2 v2.5.0
3941
)
4042

4143
require (

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
445445
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
446446
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
447447
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
448+
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
449+
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
448450
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
449451
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
450452
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
@@ -975,6 +977,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
975977
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
976978
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
977979
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
980+
oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=
981+
oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg=
978982
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
979983
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
980984
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

0 commit comments

Comments
 (0)