Skip to content

Commit 32d875e

Browse files
committed
config: prefer boxes dir to boxes file
1 parent c77d483 commit 32d875e

File tree

10 files changed

+81
-21
lines changed

10 files changed

+81
-21
lines changed

configs/boxes.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

configs/boxes/alpine.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"image": "codapi/alpine"
3+
}

docs/add-sandbox.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,11 @@ Build the image:
4545
docker build --file images/python/Dockerfile --tag codapi/python:latest images/python/
4646
```
4747

48-
And register the image as a Codapi _box_ in `configs/boxes.json`:
48+
Then register the image as a Codapi _box_. To do this, we create `configs/boxes/python.json`:
4949

5050
```js
5151
{
52-
// ...
53-
"python": {
54-
"image": "codapi/python"
55-
}
52+
"image": "codapi/python"
5653
}
5754
```
5855

docs/install.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Make sure you install Codapi on a separate machine — this is a must for security reasons. Do not store any sensitive data or credentials on this machine. This way, even if someone runs malicious code that somehow escapes the isolated environment, they won't have access to your other machines and data.
44

5-
Steps for Debian (11/12) or Ubuntu (20.04/22.04).
5+
Steps for Debian (11+) or Ubuntu (20.04+).
66

77
1. Install necessary packages (as root):
88

images/alpine/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM alpine:3.18
1+
FROM alpine:3.20
22

33
RUN adduser --home /sandbox --disabled-password sandbox
44

internal/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func (cfg *Config) ToJSON() string {
5555
// A sandbox command can contain multiple steps, each of which runs in a separate box.
5656
// So the relation sandbox -> box is 1 -> 1+.
5757
type Box struct {
58+
Name string `json:"name"`
5859
Image string `json:"image"`
5960
Runtime string `json:"runtime"`
6061
Host

internal/config/load.go

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
const (
1313
configFilename = "config.json"
14-
boxesFilename = "boxes.json"
14+
boxesDirname = "boxes"
1515
commandsDirname = "commands"
1616
)
1717

@@ -22,7 +22,7 @@ func Read(path string) (*Config, error) {
2222
return nil, err
2323
}
2424

25-
cfg, err = ReadBoxes(cfg, filepath.Join(path, boxesFilename))
25+
cfg, err = ReadBoxes(cfg, filepath.Join(path, boxesDirname))
2626
if err != nil {
2727
return nil, err
2828
}
@@ -51,8 +51,57 @@ func ReadConfig(path string) (*Config, error) {
5151
return cfg, err
5252
}
5353

54-
// ReadBoxes reads boxes config from a JSON file.
54+
// ReadBoxes reads boxes config from the boxes dir
55+
// or from the boxes.json file if the boxes dir does not exist.
5556
func ReadBoxes(cfg *Config, path string) (*Config, error) {
57+
var boxes map[string]*Box
58+
var err error
59+
60+
if fileio.Exists(path) {
61+
// prefer the boxes dir
62+
boxes, err = readBoxesDir(path)
63+
} else {
64+
// fallback to boxes.json
65+
boxes, err = readBoxesFile(path + ".json")
66+
}
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
for _, box := range boxes {
72+
setBoxDefaults(box, cfg.Box)
73+
}
74+
75+
cfg.Boxes = boxes
76+
return cfg, nil
77+
78+
}
79+
80+
// readBoxesDir reads boxes config from the boxes dir.
81+
func readBoxesDir(path string) (map[string]*Box, error) {
82+
fnames, err := filepath.Glob(filepath.Join(path, "*.json"))
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
boxes := make(map[string]*Box, len(fnames))
88+
for _, fname := range fnames {
89+
box, err := fileio.ReadJson[Box](fname)
90+
if err != nil {
91+
return nil, err
92+
}
93+
if box.Name == "" {
94+
// use the filename as the box name if it's not set
95+
box.Name = strings.TrimSuffix(filepath.Base(fname), ".json")
96+
}
97+
boxes[box.Name] = &box
98+
}
99+
100+
return boxes, err
101+
}
102+
103+
// readBoxesFile reads boxes config from the boxes.json file.
104+
func readBoxesFile(path string) (map[string]*Box, error) {
56105
data, err := os.ReadFile(path)
57106
if err != nil {
58107
return nil, err
@@ -64,12 +113,7 @@ func ReadBoxes(cfg *Config, path string) (*Config, error) {
64113
return nil, err
65114
}
66115

67-
for _, box := range boxes {
68-
setBoxDefaults(box, cfg.Box)
69-
}
70-
71-
cfg.Boxes = boxes
72-
return cfg, err
116+
return boxes, err
73117
}
74118

75119
// ReadCommands reads commands config from a JSON file.

internal/config/load_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ func TestRead(t *testing.T) {
2222
if cfg.Step.User != "sandbox" {
2323
t.Errorf("Step.User: expected sandbox, got %s", cfg.Step.User)
2424
}
25+
26+
// alpine box
27+
if _, ok := cfg.Boxes["custom-alpine"]; !ok {
28+
t.Error("Boxes: missing my/alpine box")
29+
}
30+
if cfg.Boxes["custom-alpine"].Image != "custom/alpine" {
31+
t.Errorf(
32+
"Boxes[custom-alpine]: expected custom/alpine image, got %s",
33+
cfg.Boxes["custom-alpine"].Image,
34+
)
35+
}
36+
37+
// python box
2538
if _, ok := cfg.Boxes["python"]; !ok {
2639
t.Error("Boxes: missing python box")
2740
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "custom-alpine",
3+
"image": "custom/alpine"
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"image": "codapi/python"
3+
}

0 commit comments

Comments
 (0)