Skip to content

Commit c09f117

Browse files
refactor: provision pt.1 (#544)
1 parent e839477 commit c09f117

File tree

7 files changed

+94
-117
lines changed

7 files changed

+94
-117
lines changed

cmd/arduino-app-cli/internal/servicelocator/servicelocator.go

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,15 @@ var (
4343
})
4444

4545
GetProvisioner = sync.OnceValue(func() *orchestrator.Provision {
46-
pythonImage, _ := getPythonImageAndTag()
46+
pythonImage, usedPythonImageTag := getPythonImageAndTag()
47+
slog.Debug("Using pythonImage", slog.String("image", pythonImage))
48+
4749
return f.Must(orchestrator.NewProvision(
4850
GetDockerClient(),
49-
shouldUseDynamicProvisioning(),
5051
pythonImage,
52+
usedPythonImageTag,
53+
runnerVersion,
54+
globalConfig,
5155
))
5256
})
5357

@@ -81,13 +85,7 @@ var (
8185
})
8286

8387
GetStaticStore = sync.OnceValue(func() *store.StaticStore {
84-
var baseDir string
85-
if GetProvisioner().IsUsingDynamicProvision() {
86-
baseDir = GetProvisioner().DynamicProvisionDir().String()
87-
} else {
88-
baseDir = globalConfig.AssetsDir().Join(runnerVersion).String()
89-
}
90-
return store.NewStaticStore(baseDir)
88+
return store.NewStaticStore(globalConfig.AssetsDir().Join(GetUsedPythonImageTag()).String())
9189
})
9290

9391
GetBrickService = sync.OnceValue(func() *bricks.Service {
@@ -121,9 +119,3 @@ func getPythonImageAndTag() (string, string) {
121119
}
122120
return pythonImage, usedPythonImageTag
123121
}
124-
125-
func shouldUseDynamicProvisioning() bool {
126-
pythonImage, usedPythonImageTag := getPythonImageAndTag()
127-
slog.Debug("Using pythonImage", slog.String("image", pythonImage))
128-
return usedPythonImageTag != runnerVersion
129-
}

internal/orchestrator/config/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ func (c *Configuration) init() error {
9595
if err := c.AppsDir().MkdirAll(); err != nil {
9696
return err
9797
}
98-
if err := c.dataDir.MkdirAll(); err != nil {
98+
if err := c.ExamplesDir().MkdirAll(); err != nil {
9999
return err
100100
}
101-
if err := c.ExamplesDir().MkdirAll(); err != nil {
101+
if err := c.AssetsDir().MkdirAll(); err != nil {
102102
return err
103103
}
104104
return nil

internal/orchestrator/orchestrator.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ func StartApp(
154154
cancel()
155155
return
156156
}
157-
if err := ProvisionApp(ctx, provisioner, bricksIndex, mapped_env, &app, cfg, staticStore); err != nil {
157+
158+
if err := provisioner.App(ctx, bricksIndex, &app, cfg, mapped_env, staticStore); err != nil {
158159
yield(StreamMessage{error: err})
159160
return
160161
}

internal/orchestrator/orchestrator_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ func setTestOrchestratorConfig(t *testing.T) config.Configuration {
341341

342342
tmpDir := paths.New(t.TempDir())
343343
t.Setenv("ARDUINO_APP_CLI__APPS_DIR", tmpDir.Join("apps").String())
344+
t.Setenv("ARDUINO_APP_CLI__CONFIG_DIR", tmpDir.Join("config").String())
344345
t.Setenv("ARDUINO_APP_CLI__DATA_DIR", tmpDir.Join("data").String())
345346
cfg, err := config.NewFromEnv()
346347
require.NoError(t, err)

internal/orchestrator/provision.go

Lines changed: 44 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ import (
77
"log/slog"
88
"maps"
99
"os"
10-
"path/filepath"
1110
"regexp"
1211
"slices"
1312
"strings"
14-
"time"
1513

1614
"github.com/containerd/errdefs"
1715

@@ -46,49 +44,39 @@ type service struct {
4644
Labels map[string]string `yaml:"labels,omitempty"`
4745
}
4846

49-
func ProvisionApp(
50-
ctx context.Context,
51-
provisioner *Provision,
52-
bricksIndex *bricksindex.BricksIndex,
53-
mapped_env map[string]string,
54-
app *app.ArduinoApp,
55-
cfg config.Configuration,
56-
staticStore *store.StaticStore,
57-
) error {
58-
start := time.Now()
59-
defer func() {
60-
slog.Info("Provisioning took", "duration", time.Since(start).String())
61-
}()
62-
return provisioner.App(ctx, bricksIndex, app, cfg, mapped_env, staticStore)
63-
}
64-
6547
type Provision struct {
66-
docker command.Cli
67-
useDynamicProvision bool
68-
pythonImage string
48+
docker command.Cli
49+
pythonImage string
6950
}
7051

7152
func NewProvision(
7253
docker command.Cli,
73-
useDynamicProvision bool,
7454
pythonImage string,
55+
usedPythonImageTag string,
56+
pinnedPythonImagTag string,
57+
cfg config.Configuration,
7558
) (*Provision, error) {
76-
provisioner := &Provision{
77-
docker: docker,
78-
useDynamicProvision: useDynamicProvision,
79-
pythonImage: pythonImage,
80-
}
81-
82-
dyanmicProvisionDir := provisioner.DynamicProvisionDir()
83-
if useDynamicProvision {
84-
_ = dyanmicProvisionDir.RemoveAll()
85-
_ = dyanmicProvisionDir.MkdirAll()
86-
if err := dynamicProvisioning(context.Background(), docker.Client(), pythonImage, dyanmicProvisionDir.String()); err != nil {
59+
isDevelopmentMode := usedPythonImageTag != pinnedPythonImagTag
60+
if isDevelopmentMode {
61+
dynamicProvisionDir := cfg.AssetsDir().Join(usedPythonImageTag)
62+
_ = dynamicProvisionDir.RemoveAll()
63+
tmpProvisionDir, err := cfg.AssetsDir().MkTempDir("dynamic-provisioning")
64+
if err != nil {
65+
return nil, fmt.Errorf("failed to perform creation of dynamic provisioning dir: %w", err)
66+
}
67+
if err := dynamicProvisioning(context.Background(), docker.Client(), pythonImage, tmpProvisionDir.String()); err != nil {
8768
return nil, fmt.Errorf("failed to perform dynamic provisioning: %w", err)
8869
}
70+
if err := tmpProvisionDir.Join(".cache").Rename(dynamicProvisionDir); err != nil {
71+
return nil, fmt.Errorf("failed to rename tmp provisioning folder: %w", err)
72+
}
73+
_ = tmpProvisionDir.RemoveAll()
8974
}
9075

91-
return provisioner, nil
76+
return &Provision{
77+
docker: docker,
78+
pythonImage: pythonImage,
79+
}, nil
9280
}
9381

9482
func (p *Provision) App(
@@ -103,33 +91,13 @@ func (p *Provision) App(
10391
return fmt.Errorf("provisioning failed: arduinoApp is nil")
10492
}
10593

106-
dst := arduinoApp.ProvisioningStateDir().Join("compose")
107-
if err := dst.RemoveAll(); err != nil {
108-
return fmt.Errorf("failed to remove compose directory: %w", err)
109-
}
110-
if p.useDynamicProvision {
111-
composeDir := paths.New(paths.TempDir().String(), ".cache", "compose")
112-
if err := composeDir.CopyDirTo(dst); err != nil {
113-
return fmt.Errorf("failed to copy compose directory: %w", err)
114-
}
115-
} else {
116-
if err := staticStore.SaveComposeFolderTo(dst.String()); err != nil {
117-
return fmt.Errorf("failed to save compose folder: %w", err)
94+
if arduinoApp.ProvisioningStateDir().NotExist() {
95+
if err := arduinoApp.ProvisioningStateDir().MkdirAll(); err != nil {
96+
return fmt.Errorf("provisioning failed: unable to create .cache")
11897
}
11998
}
12099

121-
return generateMainComposeFile(arduinoApp, bricksIndex, p.pythonImage, cfg, mapped_env)
122-
}
123-
124-
func (p *Provision) IsUsingDynamicProvision() bool {
125-
return p.useDynamicProvision
126-
}
127-
128-
func (p *Provision) DynamicProvisionDir() *paths.Path {
129-
if p.useDynamicProvision {
130-
return paths.TempDir().Join("dyanmic-provision", ".cache")
131-
}
132-
return nil
100+
return generateMainComposeFile(arduinoApp, bricksIndex, p.pythonImage, cfg, mapped_env, staticStore)
133101
}
134102

135103
func dynamicProvisioning(
@@ -209,28 +177,33 @@ func generateMainComposeFile(
209177
pythonImage string,
210178
cfg config.Configuration,
211179
mapped_env map[string]string,
180+
staticStore *store.StaticStore,
212181
) error {
213182
slog.Debug("Generating main compose file for the App")
214183

215184
var composeFiles paths.PathList
216185
services := []string{}
217186
brickServices := map[string][]string{}
218187
for _, brick := range app.Descriptor.Bricks {
219-
brickPath := filepath.Join(strings.Split(brick.ID, ":")...)
220-
composeFilePath := app.ProvisioningStateDir().Join("compose", brickPath, "brick_compose.yaml")
221-
if composeFilePath.Exist() {
222-
composeFiles.Add(composeFilePath)
223-
svcs, e := extracServicesFromComposeFile(composeFilePath)
224-
if e != nil {
225-
slog.Error("Failed to extract services from compose file", slog.String("compose_file", composeFilePath.String()), slog.Any("error", e))
226-
continue
227-
}
228-
brickServices[brick.ID] = svcs
229-
services = append(services, svcs...)
230-
slog.Debug("Brick compose file found", slog.String("module", brick.ID), slog.String("path", composeFilePath.String()))
231-
} else {
188+
composeFilePath, err := staticStore.GetBrickComposeFilePathFromID(brick.ID)
189+
if err != nil {
190+
slog.Error("brick compose id not valid", slog.String("error", err.Error()), slog.String("brick_id", brick.ID))
191+
continue
192+
}
193+
if !composeFilePath.Exist() {
232194
slog.Debug("Brick compose file not found", slog.String("module", brick.ID), slog.String("path", composeFilePath.String()))
195+
continue
196+
}
197+
198+
composeFiles.Add(composeFilePath)
199+
svcs, e := extracServicesFromComposeFile(composeFilePath)
200+
if e != nil {
201+
slog.Error("Failed to extract services from compose file", slog.String("compose_file", composeFilePath.String()), slog.Any("error", e))
202+
continue
233203
}
204+
brickServices[brick.ID] = svcs
205+
services = append(services, svcs...)
206+
slog.Debug("Brick compose file found", slog.String("module", brick.ID), slog.String("path", composeFilePath.String()))
234207
}
235208

236209
// Create a single docker-mainCompose that includes all the required services

internal/orchestrator/provision_test.go

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ import (
99

1010
"github.com/bcmi-labs/orchestrator/internal/orchestrator/app"
1111
"github.com/bcmi-labs/orchestrator/internal/orchestrator/bricksindex"
12+
"github.com/bcmi-labs/orchestrator/internal/store"
1213

1314
yaml "github.com/goccy/go-yaml"
1415

15-
"github.com/stretchr/testify/assert"
1616
"github.com/stretchr/testify/require"
1717
)
1818

1919
func TestProvisionAppWithOverrides(t *testing.T) {
2020
cfg := setTestOrchestratorConfig(t)
2121
tempDirectory := t.TempDir()
2222

23+
staticStore := store.NewStaticStore(cfg.AssetsDir().String())
24+
2325
// Define a mock app with bricks that require overrides
2426
app := app.ArduinoApp{
2527
Name: "TestApp",
@@ -36,10 +38,10 @@ func TestProvisionAppWithOverrides(t *testing.T) {
3638
},
3739
FullPath: paths.New(tempDirectory),
3840
}
41+
require.NoError(t, app.ProvisioningStateDir().MkdirAll())
3942
// Add compose files for the bricks - video object detection
40-
videoObjectDetectionComposePath := paths.New(tempDirectory).Join(".cache").Join("compose").Join("arduino").Join("video_object_detection")
41-
err := videoObjectDetectionComposePath.MkdirAll()
42-
assert.Nil(t, err, "Failed to create compose directory for video object detection")
43+
videoObjectDetectionComposePath := cfg.AssetsDir().Join("compose", "arduino", "video_object_detection")
44+
require.NoError(t, videoObjectDetectionComposePath.MkdirAll())
4345
composeForVideoObjectDetection := `
4446
version: '3.8'
4547
services:
@@ -48,8 +50,8 @@ services:
4850
ports:
4951
- "8080:8080"
5052
`
51-
err = videoObjectDetectionComposePath.Join("brick_compose.yaml").WriteFile([]byte(composeForVideoObjectDetection))
52-
assert.Nil(t, err, "Failed to write compose file for video object detection")
53+
err := videoObjectDetectionComposePath.Join("brick_compose.yaml").WriteFile([]byte(composeForVideoObjectDetection))
54+
require.NoError(t, err)
5355

5456
bricksIndexContent := []byte(`
5557
bricks:
@@ -80,40 +82,40 @@ bricks:
8082
- name: EI_OBJ_DETECTION_MODEL
8183
default_value: /models/ootb/ei/yolo-x-nano.eim
8284
description: path to the model file`)
83-
err = paths.New(tempDirectory, "bricks-list.yaml").WriteFile(bricksIndexContent)
85+
err = cfg.AssetsDir().Join("bricks-list.yaml").WriteFile(bricksIndexContent)
8486
require.NoError(t, err)
8587

8688
// Override brick index with custom test content
87-
bricksIndex, err := bricksindex.GenerateBricksIndexFromFile(paths.New(tempDirectory))
88-
assert.Nil(t, err, "Failed to load bricks index with custom content")
89+
bricksIndex, err := bricksindex.GenerateBricksIndexFromFile(cfg.AssetsDir())
90+
require.Nil(t, err, "Failed to load bricks index with custom content")
8991

9092
br, ok := bricksIndex.FindBrickByID("arduino:video_object_detection")
91-
assert.True(t, ok, "Brick arduino:video_object_detection should exist in the index")
92-
assert.NotNil(t, br, "Brick arduino:video_object_detection should not be nil")
93-
assert.Equal(t, "Object Detection", br.Name, "Brick name should match")
93+
require.True(t, ok, "Brick arduino:video_object_detection should exist in the index")
94+
require.NotNil(t, br, "Brick arduino:video_object_detection should not be nil")
95+
require.Equal(t, "Object Detection", br.Name, "Brick name should match")
9496

9597
// Run the provision function to generate the main compose file
9698
env := map[string]string{}
97-
err = generateMainComposeFile(&app, bricksIndex, "arduino:appslab-python-apps-base:dev-latest", cfg, env)
99+
err = generateMainComposeFile(&app, bricksIndex, "arduino:appslab-python-apps-base:dev-latest", cfg, env, staticStore)
98100

99101
// Validate that the main compose file and overrides are created
100-
assert.Nil(t, err, "Failed to generate main compose file")
102+
require.NoError(t, err, "Failed to generate main compose file")
101103
composeFilePath := paths.New(tempDirectory).Join(".cache").Join("app-compose.yaml")
102-
assert.True(t, composeFilePath.Exist(), "Main compose file should exist")
104+
require.True(t, composeFilePath.Exist(), "Main compose file should exist")
103105
overridesFilePath := paths.New(tempDirectory).Join(".cache").Join("app-compose-overrides.yaml")
104-
assert.True(t, overridesFilePath.Exist(), "Override compose file should exist")
106+
require.True(t, overridesFilePath.Exist(), "Override compose file should exist")
105107

106108
// Open override file and check for the expected override
107109
overridesContent, err := overridesFilePath.ReadFile()
108-
assert.Nil(t, err, "Failed to read overrides file")
110+
require.Nil(t, err, "Failed to read overrides file")
109111
type services struct {
110112
Services map[string]map[string]interface{} `yaml:"services"`
111113
}
112114
content := services{}
113115
err = yaml.Unmarshal(overridesContent, &content)
114-
assert.Nil(t, err, "Failed to unmarshal overrides content")
115-
assert.NotNil(t, content.Services["ei-video-obj-detection-runner"], "Override for ei-video-obj-detection-runner should exist")
116-
assert.NotNil(t, content.Services["ei-video-obj-detection-runner"]["devices"], "Override for ei-video-obj-detection-runner devices should exist")
116+
require.Nil(t, err, "Failed to unmarshal overrides content")
117+
require.NotNil(t, content.Services["ei-video-obj-detection-runner"], "Override for ei-video-obj-detection-runner should exist")
118+
require.NotNil(t, content.Services["ei-video-obj-detection-runner"]["devices"], "Override for ei-video-obj-detection-runner devices should exist")
117119
}
118120

119121
func TestVolumeParser(t *testing.T) {
@@ -145,9 +147,9 @@ services:
145147
"CUSTOM_PATH": tempDirectory,
146148
}
147149
volumes, err := extractVolumesFromComposeFile(volumesFromFile.String())
148-
assert.Nil(t, err, "Failed to extract volumes from compose file")
150+
require.Nil(t, err, "Failed to extract volumes from compose file")
149151
provisionComposeVolumes(volumesFromFile.String(), volumes, app, env)
150-
assert.True(t, app.FullPath.Join("data").Join("influx-data").Exist(), "Volume directory should exist")
152+
require.True(t, app.FullPath.Join("data").Join("influx-data").Exist(), "Volume directory should exist")
151153
})
152154

153155
t.Run("TestPreProvsionVolumesCustomEnvUsingDefault", func(t *testing.T) {
@@ -178,9 +180,9 @@ services:
178180
// No env, use macro default value
179181
env := map[string]string{}
180182
volumes, err := extractVolumesFromComposeFile(volumesFromFile.String())
181-
assert.Nil(t, err, "Failed to extract volumes from compose file")
183+
require.Nil(t, err, "Failed to extract volumes from compose file")
182184
provisionComposeVolumes(volumesFromFile.String(), volumes, app, env)
183-
assert.True(t, app.FullPath.Join("customized").Join("data").Join("influx-data").Exist(), "Volume directory should exist")
185+
require.True(t, app.FullPath.Join("customized").Join("data").Join("influx-data").Exist(), "Volume directory should exist")
184186
})
185187

186188
t.Run("TestPreProvsionVolumesAsStructure", func(t *testing.T) {
@@ -210,9 +212,9 @@ services:
210212
}
211213
env := map[string]string{}
212214
volumes, err := extractVolumesFromComposeFile(volumesFromFile.String())
213-
assert.Nil(t, err, "Failed to extract volumes from compose file")
215+
require.Nil(t, err, "Failed to extract volumes from compose file")
214216
provisionComposeVolumes(volumesFromFile.String(), volumes, app, env)
215-
assert.True(t, app.FullPath.Join("data").Join("influx-data").Exist(), "Volume directory should exist")
217+
require.True(t, app.FullPath.Join("data").Join("influx-data").Exist(), "Volume directory should exist")
216218
})
217219

218220
t.Run("TestPreProvsionVolumes", func(t *testing.T) {
@@ -240,9 +242,9 @@ services:
240242
}
241243
env := map[string]string{}
242244
volumes, err := extractVolumesFromComposeFile(volumesFromFile.String())
243-
assert.Nil(t, err, "Failed to extract volumes from compose file")
245+
require.Nil(t, err, "Failed to extract volumes from compose file")
244246
provisionComposeVolumes(volumesFromFile.String(), volumes, app, env)
245-
assert.True(t, app.FullPath.Join("data").Join("influx-data").Exist(), "Volume directory should exist")
247+
require.True(t, app.FullPath.Join("data").Join("influx-data").Exist(), "Volume directory should exist")
246248
})
247249

248250
}

0 commit comments

Comments
 (0)