diff --git a/.golangci.yml b/.golangci.yml index 72ba47ad..edfb65a6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -75,6 +75,8 @@ linters-settings: - gopkg.in/yaml.v3 - github.com/compose-spec/compose-go/v2 - github.com/github/go-spdx/v2 + - github.com/opencontainers/image-spec/specs-go/v1 + - oras.land/oras-go/v2 exhaustive: # Switch statements are to be considered exhaustive if a 'default' case is # present, even if all enum members aren't listed in the switch. diff --git a/build/rofl/oci.go b/build/rofl/oci.go new file mode 100644 index 00000000..b32c7eba --- /dev/null +++ b/build/rofl/oci.go @@ -0,0 +1,114 @@ +package rofl + +import ( + "context" + "fmt" + "os" + "path/filepath" + + v1 "github.com/opencontainers/image-spec/specs-go/v1" + oras "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content/file" + "oras.land/oras-go/v2/registry/remote" + "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras-go/v2/registry/remote/credentials" + "oras.land/oras-go/v2/registry/remote/retry" + + "github.com/oasisprotocol/oasis-core/go/runtime/bundle" +) + +const ( + ociTypeOrcConfig = "application/vnd.oasis.orc.config.v1+json" + ociTypeOrcLayer = "application/vnd.oasis.orc.layer.v1" + ociTypeOrcArtifact = "application/vnd.oasis.orc" +) + +// PushBundleToOciRepository pushes an ORC bundle to the given remote OCI repository. +func PushBundleToOciRepository(bundleFn, dst, tag string) error { + ctx := context.Background() + + // Open the bundle. + bnd, err := bundle.Open(bundleFn) + if err != nil { + return fmt.Errorf("failed to open bundle: %w", err) + } + defer bnd.Close() + + // Create a temporary file store to build the OCI layers. + tmpDir, err := os.MkdirTemp("", "oasis-orc2oci") + if err != nil { + return fmt.Errorf("failed to create temporary directory: %w", err) + } + defer os.RemoveAll(tmpDir) + + storeDir := filepath.Join(tmpDir, "oci") + store, err := file.New(storeDir) + if err != nil { + return fmt.Errorf("failed to create temporary OCI store: %w", err) + } + defer store.Close() + + bundleDir := filepath.Join(tmpDir, "bundle") + if err = bnd.WriteExploded(bundleDir); err != nil { + return fmt.Errorf("failed to explode bundle: %w", err) + } + + // Generate the config object from the manifest. + const manifestName = "META-INF/MANIFEST.MF" + configDsc, err := store.Add(ctx, manifestName, ociTypeOrcConfig, filepath.Join(bundleDir, manifestName)) + if err != nil { + return fmt.Errorf("failed to add config object from manifest: %w", err) + } + + // Add other files as layers. + layers := make([]v1.Descriptor, 0, len(bnd.Data)-1) + for fn := range bnd.Data { + if fn == manifestName { + continue + } + + layerDsc, err := store.Add(ctx, fn, ociTypeOrcLayer, filepath.Join(bundleDir, fn)) + if err != nil { + return fmt.Errorf("failed to add OCI layer: %w", err) + } + + layers = append(layers, layerDsc) + } + + // Pack the OCI manifest. + opts := oras.PackManifestOptions{ + Layers: layers, + ConfigDescriptor: &configDsc, + } + manifestDescriptor, err := oras.PackManifest(ctx, store, oras.PackManifestVersion1_1, ociTypeOrcArtifact, opts) + if err != nil { + return fmt.Errorf("failed to pack OCI manifest: %w", err) + } + + // Tag the manifest. + if err = store.Tag(ctx, manifestDescriptor, tag); err != nil { + return fmt.Errorf("failed to tag OCI manifest: %w", err) + } + + // Connect to remote repository. + repo, err := remote.NewRepository(dst) + if err != nil { + return fmt.Errorf("failed to init remote OCI repository: %w", err) + } + creds, err := credentials.NewStoreFromDocker(credentials.StoreOptions{}) + if err != nil { + return fmt.Errorf("failed to init OCI credential store: %w", err) + } + repo.Client = &auth.Client{ + Client: retry.DefaultClient, + Cache: auth.NewCache(), + Credential: credentials.Credential(creds), + } + + // Push to remote repository. + if _, err = oras.Copy(ctx, store, tag, repo, tag, oras.DefaultCopyOptions); err != nil { + return fmt.Errorf("failed to push to remote OCI repository: %w", err) + } + + return nil +} diff --git a/go.mod b/go.mod index 105d20c6..7f5fa38d 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/oasisprotocol/oasis-core/go v0.2500.0 github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.12.2 github.com/olekukonko/tablewriter v0.0.5 + github.com/opencontainers/image-spec v1.1.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.19.0 @@ -36,6 +37,7 @@ require ( golang.org/x/sys v0.31.0 golang.org/x/text v0.23.0 gopkg.in/yaml.v3 v3.0.1 + oras.land/oras-go/v2 v2.5.0 ) require ( diff --git a/go.sum b/go.sum index 3f37b0ba..8b517b69 100644 --- a/go.sum +++ b/go.sum @@ -445,6 +445,8 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 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 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= +oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=