Skip to content

Commit 4594a95

Browse files
committed
feat(cmd/rofl): Implement deploy via ROFL market
1 parent 697861d commit 4594a95

15 files changed

Lines changed: 1529 additions & 76 deletions

File tree

build/rofl/manifest.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@ func (m *Manifest) Save() error {
243243
// used in case no deployment is passed.
244244
const DefaultDeploymentName = "default"
245245

246+
// DefaultInstanceName is the name of the default instance into which the app is deployed when no
247+
// specific instance is passed.
248+
const DefaultInstanceName = "default"
249+
246250
// Deployment describes a single ROFL app deployment.
247251
type Deployment struct {
248252
// AppID is the Bech32-encoded ROFL app ID.
@@ -255,6 +259,8 @@ type Deployment struct {
255259
Admin string `yaml:"admin,omitempty" json:"admin,omitempty"`
256260
// Debug is a flag denoting whether this is a debuggable deployment.
257261
Debug bool `yaml:"debug,omitempty" json:"debug,omitempty"`
262+
// OCIRepository is the optional OCI repository where one can push the ORC to.
263+
OCIRepository string `yaml:"oci_repository,omitempty" json:"oci_repository,omitempty"`
258264
// TrustRoot is the optional trust root configuration.
259265
TrustRoot *TrustRootConfig `yaml:"trust_root,omitempty" json:"trust_root,omitempty"`
260266
// Policy is the ROFL app policy.
@@ -263,9 +269,12 @@ type Deployment struct {
263269
Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty"`
264270
// Secrets contains encrypted secrets.
265271
Secrets []*SecretConfig `yaml:"secrets,omitempty" json:"secrets,omitempty"`
272+
273+
// Instances are the deployed instances.
274+
Instances map[string]*Instance `yaml:"instances,omitempty" json:"instances,omitempty"`
266275
}
267276

268-
// Validate validates the manifest for correctness.
277+
// Validate validates the deployment for correctness.
269278
func (d *Deployment) Validate() error {
270279
if len(d.AppID) > 0 {
271280
var appID rofl.AppID
@@ -284,6 +293,12 @@ func (d *Deployment) Validate() error {
284293
return fmt.Errorf("bad secret: %w", err)
285294
}
286295
}
296+
297+
for name, instance := range d.Instances {
298+
if err := instance.Validate(); err != nil {
299+
return fmt.Errorf("bad instance '%s': %w", name, err)
300+
}
301+
}
287302
return nil
288303
}
289304

@@ -292,6 +307,27 @@ func (d *Deployment) HasAppID() bool {
292307
return len(d.AppID) > 0
293308
}
294309

310+
// Instance is a hosted instance where a ROLF app is deployed.
311+
type Instance struct {
312+
// Provider is the address of the ROFL market provider to deploy to.
313+
Provider string `yaml:"provider,omitempty" json:"provider,omitempty"`
314+
// Offer is the provider's offer identifier to provision.
315+
Offer string `yaml:"offer,omitempty" json:"provider,omitempty"`
316+
// Instance is the identifier of the instance to deploy into.
317+
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
318+
}
319+
320+
// Validate validates the instance for correctness.
321+
func (i *Instance) Validate() error {
322+
if i.Offer != "" && i.Provider == "" {
323+
return fmt.Errorf("offer identifier cannot be specified without a provider")
324+
}
325+
if i.Instance != "" && i.Provider == "" {
326+
return fmt.Errorf("instance identifier cannot be specified without a provider")
327+
}
328+
return nil
329+
}
330+
295331
// TrustRootConfig is the trust root configuration.
296332
type TrustRootConfig struct {
297333
// Height is the consensus layer block height where to take the trust root.

build/rofl/oci.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package rofl
33
import (
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
}

build/rofl/provider/defaults.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package provider
2+
3+
// DefaultSchedulerApp contains the default scheduler app IDs for each network/paratime.
4+
var DefaultSchedulerApp = map[string]map[string]string{
5+
"testnet": {
6+
"sapphire": "rofl1qrqw99h0f7az3hwt2cl7yeew3wtz0fxunu7luyfg",
7+
},
8+
}

0 commit comments

Comments
 (0)