impr: modular sandbox configs
This commit is contained in:
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"sh": {
|
|
||||||
"run": {
|
|
||||||
"engine": "docker",
|
|
||||||
"entry": "main.sh",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"box": "alpine",
|
|
||||||
"command": ["sh", "main.sh"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
configs/commands/sh.json
Normal file
12
configs/commands/sh.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"run": {
|
||||||
|
"engine": "docker",
|
||||||
|
"entry": "main.sh",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"box": "alpine",
|
||||||
|
"command": ["sh", "main.sh"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nalgeon/codapi/internal/fileio"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configFilename = "config.json"
|
configFilename = "config.json"
|
||||||
boxesFilename = "boxes.json"
|
boxesFilename = "boxes.json"
|
||||||
commandsFilename = "commands.json"
|
commandsDirname = "commands"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Read reads application config from JSON files.
|
// Read reads application config from JSON files.
|
||||||
@@ -24,7 +27,7 @@ func Read(path string) (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err = ReadCommands(cfg, filepath.Join(path, commandsFilename))
|
cfg, err = ReadCommands(cfg, filepath.Join(path, commandsDirname))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -71,31 +74,36 @@ func ReadBoxes(cfg *Config, path string) (*Config, error) {
|
|||||||
|
|
||||||
// ReadCommands reads commands config from a JSON file.
|
// ReadCommands reads commands config from a JSON file.
|
||||||
func ReadCommands(cfg *Config, path string) (*Config, error) {
|
func ReadCommands(cfg *Config, path string) (*Config, error) {
|
||||||
data, err := os.ReadFile(path)
|
fnames, err := filepath.Glob(filepath.Join(path, "*.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
commands := make(map[string]SandboxCommands)
|
cfg.Commands = make(map[string]SandboxCommands, len(fnames))
|
||||||
err = json.Unmarshal(data, &commands)
|
for _, fname := range fnames {
|
||||||
if err != nil {
|
sandbox := strings.TrimSuffix(filepath.Base(fname), ".json")
|
||||||
return nil, err
|
commands, err := fileio.ReadJson[SandboxCommands](fname)
|
||||||
}
|
if err != nil {
|
||||||
|
break
|
||||||
for _, playCmds := range commands {
|
|
||||||
for _, cmd := range playCmds {
|
|
||||||
if cmd.Before != nil {
|
|
||||||
setStepDefaults(cmd.Before, cfg.Step)
|
|
||||||
}
|
|
||||||
for _, step := range cmd.Steps {
|
|
||||||
setStepDefaults(step, cfg.Step)
|
|
||||||
}
|
|
||||||
if cmd.After != nil {
|
|
||||||
setStepDefaults(cmd.After, cfg.Step)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
setCommandDefaults(commands, cfg)
|
||||||
|
cfg.Commands[sandbox] = commands
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Commands = commands
|
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setCommandDefaults applies global defaults to sandbox commands.
|
||||||
|
func setCommandDefaults(commands SandboxCommands, cfg *Config) {
|
||||||
|
for _, cmd := range commands {
|
||||||
|
if cmd.Before != nil {
|
||||||
|
setStepDefaults(cmd.Before, cfg.Step)
|
||||||
|
}
|
||||||
|
for _, step := range cmd.Steps {
|
||||||
|
setStepDefaults(step, cfg.Step)
|
||||||
|
}
|
||||||
|
if cmd.After != nil {
|
||||||
|
setStepDefaults(cmd.After, cfg.Step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
25
internal/config/testdata/commands.json
vendored
25
internal/config/testdata/commands.json
vendored
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"python": {
|
|
||||||
"run": {
|
|
||||||
"engine": "docker",
|
|
||||||
"entry": "main.py",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"box": "python",
|
|
||||||
"command": ["python", "main.py"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"engine": "docker",
|
|
||||||
"entry": "test_main.py",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"box": "python",
|
|
||||||
"command": ["python", "-m", "unittest"],
|
|
||||||
"noutput": 8192
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
internal/config/testdata/commands/python.json
vendored
Normal file
23
internal/config/testdata/commands/python.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"run": {
|
||||||
|
"engine": "docker",
|
||||||
|
"entry": "main.py",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"box": "python",
|
||||||
|
"command": ["python", "main.py"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"engine": "docker",
|
||||||
|
"entry": "test_main.py",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"box": "python",
|
||||||
|
"command": ["python", "-m", "unittest"],
|
||||||
|
"noutput": 8192
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
package fileio
|
package fileio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -37,3 +38,17 @@ func CopyFiles(pattern string, dstDir string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadJson reads the file and decodes it from JSON.
|
||||||
|
func ReadJson[T any](path string) (T, error) {
|
||||||
|
var obj T
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return obj, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(data, &obj)
|
||||||
|
if err != nil {
|
||||||
|
return obj, err
|
||||||
|
}
|
||||||
|
return obj, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package fileio
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,3 +55,30 @@ func TestCopyFiles(t *testing.T) {
|
|||||||
t.Errorf("unexpected file content: got %q, want %q", data, expected)
|
t.Errorf("unexpected file content: got %q, want %q", data, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadJson(t *testing.T) {
|
||||||
|
type Person struct{ Name string }
|
||||||
|
|
||||||
|
t.Run("valid", func(t *testing.T) {
|
||||||
|
got, err := ReadJson[Person](filepath.Join("testdata", "valid.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
want := Person{"alice"}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("expected %v, got %v", want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("invalid", func(t *testing.T) {
|
||||||
|
_, err := ReadJson[Person](filepath.Join("testdata", "invalid.json"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("does not exist", func(t *testing.T) {
|
||||||
|
_, err := ReadJson[Person](filepath.Join("testdata", "missing.json"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
1
internal/fileio/testdata/invalid.json
vendored
Normal file
1
internal/fileio/testdata/invalid.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: alice
|
||||||
3
internal/fileio/testdata/valid.json
vendored
Normal file
3
internal/fileio/testdata/valid.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"name": "alice"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user