Skip to content

Commit a588a85

Browse files
authored
Merge pull request #422 from oasisprotocol/kostko/feature/rofl-market
feat(cmd/rofl): Implement deploy via ROFL market
2 parents c6de32e + 15b9e2f commit a588a85

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2358
-125
lines changed

.golangci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ 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
80+
- github.com/wI2L/jsondiff
7881
exhaustive:
7982
# Switch statements are to be considered exhaustive if a 'default' case is
8083
# present, even if all enum members aren't listed in the switch.

build/cargo/cargo.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"os/exec"
99
"slices"
1010
"strings"
11+
12+
"github.com/oasisprotocol/cli/build/env"
1113
)
1214

1315
// Metadata is the cargo package metadata.
@@ -42,12 +44,15 @@ func (d *Dependency) HasFeature(feature string) bool {
4244
}
4345

4446
// GetMetadata queries `cargo` for metadata of the package in the current working directory.
45-
func GetMetadata() (*Metadata, error) {
47+
func GetMetadata(env env.ExecEnv) (*Metadata, error) {
4648
cmd := exec.Command("cargo", "metadata", "--no-deps", "--format-version", "1")
4749
stdout, err := cmd.StdoutPipe()
4850
if err != nil {
4951
return nil, fmt.Errorf("failed to initialize metadata process: %w", err)
5052
}
53+
if err = env.WrapCommand(cmd); err != nil {
54+
return nil, err
55+
}
5156
if err = cmd.Start(); err != nil {
5257
return nil, fmt.Errorf("failed to start metadata process: %w", err)
5358
}
@@ -105,8 +110,8 @@ func GetMetadata() (*Metadata, error) {
105110
}
106111

107112
// Build builds a Rust program using `cargo` in the current working directory.
108-
func Build(release bool, target string, features []string) (string, error) {
109-
args := []string{"build"}
113+
func Build(env env.ExecEnv, release bool, target string, features []string) (string, error) {
114+
args := []string{"build", "--locked"}
110115
if release {
111116
args = append(args, "--release")
112117
}
@@ -128,6 +133,9 @@ func Build(release bool, target string, features []string) (string, error) {
128133
var stderr bytes.Buffer
129134
cmd.Stderr = &stderr
130135

136+
if err = env.WrapCommand(cmd); err != nil {
137+
return "", err
138+
}
131139
if err = cmd.Start(); err != nil {
132140
return "", fmt.Errorf("failed to start build process: %w", err)
133141
}
@@ -171,5 +179,9 @@ func Build(release bool, target string, features []string) (string, error) {
171179
if executable == "" {
172180
return "", fmt.Errorf("no executable generated")
173181
}
182+
executable, err = env.PathFromEnv(executable)
183+
if err != nil {
184+
return "", fmt.Errorf("failed to map executable path: %w", err)
185+
}
174186
return executable, nil
175187
}

build/env/base.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package env
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
)
7+
8+
// GetBasedir finds the Git repository root directory and use that as base. If one cannot be found,
9+
// uses the current working directory.
10+
func GetBasedir() (string, error) {
11+
wd, err := os.Getwd()
12+
if err != nil {
13+
return "", err
14+
}
15+
16+
dir := getGitRoot(wd)
17+
if dir != "" {
18+
return dir, nil
19+
}
20+
return wd, nil
21+
}
22+
23+
func getGitRoot(dir string) string {
24+
currentDir := dir
25+
26+
for {
27+
entries, err := os.ReadDir(currentDir)
28+
if err != nil {
29+
return ""
30+
}
31+
32+
for _, de := range entries {
33+
if !de.IsDir() {
34+
continue
35+
}
36+
if de.Name() == ".git" {
37+
return currentDir
38+
}
39+
}
40+
41+
parentDir := filepath.Dir(currentDir)
42+
if parentDir == currentDir {
43+
return ""
44+
}
45+
currentDir = parentDir
46+
}
47+
}

build/env/env.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package env
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
)
10+
11+
// ExecEnv is an execution environment.
12+
type ExecEnv interface {
13+
// WrapCommand modifies an existing `exec.Cmd` such that it runs in this environment.
14+
WrapCommand(cmd *exec.Cmd) error
15+
16+
// PathFromEnv converts the given path from inside the environment into a path outside the
17+
// environment.
18+
PathFromEnv(path string) (string, error)
19+
20+
// PathToEnv converts the given path from outside the environment into a path inside the
21+
// environment.
22+
PathToEnv(path string) (string, error)
23+
24+
// FixPermissions ensures that the user executing this process owns the file at the given path
25+
// outside the environment.
26+
FixPermissions(path string) error
27+
28+
// HasBinary returns true iff the given binary name is available in this environment.
29+
HasBinary(name string) bool
30+
31+
// IsAvailable returns true iff the given execution environment is available.
32+
IsAvailable() bool
33+
}
34+
35+
// NativeEnv is the native execution environment that executes all commands directly.
36+
type NativeEnv struct{}
37+
38+
// NewNativeEnv creates a new native execution environment.
39+
func NewNativeEnv() *NativeEnv {
40+
return &NativeEnv{}
41+
}
42+
43+
// WrapCommand implements ExecEnv.
44+
func (ne *NativeEnv) WrapCommand(*exec.Cmd) error {
45+
return nil
46+
}
47+
48+
// PathFromEnv implements ExecEnv.
49+
func (ne *NativeEnv) PathFromEnv(path string) (string, error) {
50+
return path, nil
51+
}
52+
53+
// PathToEnv implements ExecEnv.
54+
func (ne *NativeEnv) PathToEnv(path string) (string, error) {
55+
return path, nil
56+
}
57+
58+
// FixPermissions implements ExecEnv.
59+
func (ne *NativeEnv) FixPermissions(string) error {
60+
return nil
61+
}
62+
63+
// HasBinary implements ExecEnv.
64+
func (ne *NativeEnv) HasBinary(name string) bool {
65+
path, err := exec.LookPath(name)
66+
return err == nil && path != ""
67+
}
68+
69+
// IsAvailable implements ExecEnv.
70+
func (ne *NativeEnv) IsAvailable() bool {
71+
return true
72+
}
73+
74+
// String returns a string representation of the execution environment.
75+
func (ne *NativeEnv) String() string {
76+
return "native environment"
77+
}
78+
79+
// DockerEnv is a Docker-based execution environment that executes all commands inside a Docker
80+
// container using the configured image.
81+
type DockerEnv struct {
82+
image string
83+
volumes map[string]string
84+
}
85+
86+
// NewDockerEnv creates a new Docker-based execution environment.
87+
func NewDockerEnv(image, baseDir, dirMount string) *DockerEnv {
88+
return &DockerEnv{
89+
image: image,
90+
volumes: map[string]string{
91+
baseDir: dirMount,
92+
},
93+
}
94+
}
95+
96+
// AddDirectory exposes a host directory to the container under the same path.
97+
func (de *DockerEnv) AddDirectory(path string) {
98+
de.volumes[path] = path
99+
}
100+
101+
// WrapCommand implements ExecEnv.
102+
func (de *DockerEnv) WrapCommand(cmd *exec.Cmd) error {
103+
origArgs := cmd.Args
104+
105+
var err error
106+
wd := cmd.Dir
107+
if wd == "" {
108+
wd, err = os.Getwd()
109+
if err != nil {
110+
return err
111+
}
112+
}
113+
workDir, err := de.PathToEnv(wd)
114+
if err != nil {
115+
return fmt.Errorf("bad working directory: %w", err)
116+
}
117+
118+
var envArgs []string //nolint: prealloc
119+
for _, envKV := range cmd.Env {
120+
envArgs = append(envArgs, "--env", envKV)
121+
}
122+
// When no environment is set, copy over any OASIS_ and ROFL_ variables.
123+
if len(cmd.Env) == 0 {
124+
for _, envKV := range os.Environ() {
125+
if !strings.HasPrefix(envKV, "OASIS_") && !strings.HasPrefix(envKV, "ROFL_") {
126+
continue
127+
}
128+
envArgs = append(envArgs, "--env", envKV)
129+
}
130+
}
131+
132+
cmd.Path, err = exec.LookPath("docker")
133+
if err != nil {
134+
return fmt.Errorf("failed to find 'docker': %w", err)
135+
}
136+
137+
cmd.Args = []string{
138+
"docker", "run",
139+
"--rm",
140+
"--platform", "linux/amd64",
141+
"--workdir", workDir,
142+
}
143+
for hostDir, bindDir := range de.volumes {
144+
cmd.Args = append(cmd.Args, "--volume", hostDir+":"+bindDir)
145+
}
146+
cmd.Args = append(cmd.Args, envArgs...)
147+
cmd.Args = append(cmd.Args, de.image)
148+
cmd.Args = append(cmd.Args, origArgs...)
149+
150+
return nil
151+
}
152+
153+
// PathFromEnv implements ExecEnv.
154+
func (de *DockerEnv) PathFromEnv(path string) (string, error) {
155+
for hostDir, bindDir := range de.volumes {
156+
if !strings.HasPrefix(path, bindDir) {
157+
continue
158+
}
159+
relPath, err := filepath.Rel(bindDir, path)
160+
if err != nil {
161+
return "", fmt.Errorf("bad path: %w", err)
162+
}
163+
return filepath.Join(hostDir, relPath), nil
164+
}
165+
return "", fmt.Errorf("bad path '%s'", path)
166+
}
167+
168+
// PathToEnv implements ExecEnv.
169+
func (de *DockerEnv) PathToEnv(path string) (string, error) {
170+
for hostDir, bindDir := range de.volumes {
171+
if !strings.HasPrefix(path, hostDir) {
172+
continue
173+
}
174+
relPath, err := filepath.Rel(hostDir, path)
175+
if err != nil {
176+
return "", fmt.Errorf("bad path: %w", err)
177+
}
178+
return filepath.Join(bindDir, relPath), nil
179+
}
180+
return "", fmt.Errorf("bad path '%s'", path)
181+
}
182+
183+
// FixPermissions implements ExecEnv.
184+
func (de *DockerEnv) FixPermissions(path string) error {
185+
path, err := de.PathToEnv(path)
186+
if err != nil {
187+
return err
188+
}
189+
190+
cmd := exec.Command("chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), path) //nolint: gosec
191+
if err = de.WrapCommand(cmd); err != nil {
192+
return err
193+
}
194+
return cmd.Run()
195+
}
196+
197+
// HasBinary implements ExecEnv.
198+
func (de *DockerEnv) HasBinary(string) bool {
199+
return true
200+
}
201+
202+
// IsAvailable implements ExecEnv.
203+
func (de *DockerEnv) IsAvailable() bool {
204+
path, err := exec.LookPath("docker")
205+
return err == nil && path != ""
206+
}
207+
208+
// String returns a string representation of the execution environment.
209+
func (de *DockerEnv) String() string {
210+
return "Docker"
211+
}

0 commit comments

Comments
 (0)