Skip to content

Commit f587bf7

Browse files
mirkoCrobumirkoCrobu
andauthored
feat: add auto pull image at boot time (#506)
Co-authored-by: mirkoCrobu <mirkocrobu@NB-0531.localdomain>
1 parent 15bcdd7 commit f587bf7

File tree

4 files changed

+111
-85
lines changed

4 files changed

+111
-85
lines changed

cmd/arduino-app-cli/daemon/daemon.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,29 @@ import (
2020
)
2121

2222
func NewDaemonCmd(version string) *cobra.Command {
23+
var autoPull bool
2324
daemonCmd := &cobra.Command{
2425
Use: "daemon",
2526
Short: "Run an HTTP server to expose arduino-app-cli functionality thorough REST API",
2627
Run: func(cmd *cobra.Command, args []string) {
2728
daemonPort, _ := cmd.Flags().GetString("port")
2829

30+
if autoPull {
31+
go func() {
32+
slog.Info("Auto-pull enabled, starting background process...")
33+
err := orchestrator.SystemInit(
34+
cmd.Context(),
35+
servicelocator.GetUsedPythonImageTag(),
36+
servicelocator.GetStaticStore(),
37+
)
38+
if err != nil {
39+
slog.Error("Auto-pull process failed", slog.String("error", err.Error()))
40+
} else {
41+
slog.Info("Auto-pull process completed.")
42+
}
43+
}()
44+
}
45+
2946
// start the default app in the background
3047
go func() {
3148
slog.Info("Starting default app")
@@ -46,6 +63,7 @@ func NewDaemonCmd(version string) *cobra.Command {
4663
},
4764
}
4865
daemonCmd.Flags().String("port", "8080", "The TCP port the daemon will listen to")
66+
daemonCmd.Flags().BoolVarP(&autoPull, "auto-pull", "p", false, "Enable auto-pull of all app images on startup")
4967
return daemonCmd
5068
}
5169

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// The servicelocator pkg should be used only under cmd/arduino-app-cli as a convenience to build our DI.
2+
13
package servicelocator
24

35
import (
Lines changed: 2 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
11
package system
22

33
import (
4-
"context"
5-
"io/fs"
6-
"os"
7-
"path"
8-
"strings"
9-
10-
"github.com/arduino/go-paths-helper"
11-
"github.com/compose-spec/compose-go/v2/loader"
12-
"github.com/compose-spec/compose-go/v2/types"
134
"github.com/spf13/cobra"
14-
"go.bug.st/f"
155

166
"github.com/bcmi-labs/orchestrator/cmd/arduino-app-cli/internal/servicelocator"
17-
"github.com/bcmi-labs/orchestrator/cmd/feedback"
7+
"github.com/bcmi-labs/orchestrator/internal/orchestrator"
188
)
199

2010
func NewSystemCmd() *cobra.Command {
@@ -33,82 +23,9 @@ func newDownloadImage() *cobra.Command {
3323
Use: "init",
3424
Args: cobra.ExactArgs(0),
3525
RunE: func(cmd *cobra.Command, _ []string) error {
36-
return SystemInit(cmd.Context())
26+
return orchestrator.SystemInit(cmd.Context(), servicelocator.GetUsedPythonImageTag(), servicelocator.GetStaticStore())
3727
},
3828
}
3929

4030
return cmd
4131
}
42-
43-
// SystemInit pulls necessary Docker images.
44-
func SystemInit(ctx context.Context) error {
45-
preInstallContainer := []string{
46-
"ghcr.io/bcmi-labs/arduino/appslab-python-apps-base:" + servicelocator.GetUsedPythonImageTag(),
47-
}
48-
additionalContainers, err := parseAllModelsRunnerImageTag()
49-
if err != nil {
50-
return err
51-
}
52-
preInstallContainer = append(preInstallContainer, additionalContainers...)
53-
54-
stdout, _, err := feedback.DirectStreams()
55-
if err != nil {
56-
feedback.Fatal(err.Error(), feedback.ErrBadArgument)
57-
return nil
58-
}
59-
60-
for _, container := range preInstallContainer {
61-
cmd, err := paths.NewProcess(nil, "docker", "pull", container)
62-
if err != nil {
63-
return err
64-
}
65-
cmd.RedirectStderrTo(stdout)
66-
cmd.RedirectStdoutTo(stdout)
67-
if err := cmd.RunWithinContext(ctx); err != nil {
68-
return err
69-
}
70-
}
71-
return nil
72-
}
73-
74-
func parseAllModelsRunnerImageTag() ([]string, error) {
75-
assets, err := servicelocator.GetStaticStore().GetAssetsFolder()
76-
if err != nil {
77-
return nil, err
78-
}
79-
baseDir := path.Join("compose", "arduino")
80-
bricks, err := fs.ReadDir(assets, baseDir)
81-
if err != nil {
82-
return nil, err
83-
}
84-
85-
result := make([]string, 0, len(bricks))
86-
for _, brick := range bricks {
87-
composeFile := path.Join(baseDir, brick.Name(), "brick_compose.yaml")
88-
content, err := fs.ReadFile(assets, composeFile)
89-
if err != nil {
90-
return nil, err
91-
}
92-
prj, err := loader.LoadWithContext(
93-
context.Background(),
94-
types.ConfigDetails{
95-
ConfigFiles: []types.ConfigFile{{Content: content}},
96-
Environment: types.NewMapping(os.Environ()),
97-
},
98-
func(o *loader.Options) { o.SetProjectName("test", false) },
99-
)
100-
if err != nil {
101-
return nil, err
102-
}
103-
for _, v := range prj.Services {
104-
// Add only if the image comes from arduino
105-
if strings.HasPrefix(v.Image, "ghcr.io/bcmi-labs/arduino/") ||
106-
// TODO: add the correct ecr prefix as soon as we have it in production
107-
strings.HasPrefix(v.Image, "public.ecr.aws/") {
108-
result = append(result, v.Image)
109-
}
110-
}
111-
}
112-
113-
return f.Uniq(result), nil
114-
}

internal/orchestrator/system.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,90 @@
11
package orchestrator
2+
3+
import (
4+
"context"
5+
"io/fs"
6+
"os"
7+
"path"
8+
"strings"
9+
10+
"github.com/arduino/go-paths-helper"
11+
"github.com/compose-spec/compose-go/v2/loader"
12+
"github.com/compose-spec/compose-go/v2/types"
13+
"go.bug.st/f"
14+
15+
"github.com/bcmi-labs/orchestrator/cmd/feedback"
16+
"github.com/bcmi-labs/orchestrator/internal/store"
17+
)
18+
19+
// SystemInit pulls necessary Docker images.
20+
func SystemInit(ctx context.Context, pythonImageTag string, staticStore *store.StaticStore) error {
21+
preInstallContainer := []string{
22+
"ghcr.io/bcmi-labs/arduino/appslab-python-apps-base:" + pythonImageTag,
23+
}
24+
additionalContainers, err := parseAllModelsRunnerImageTag(staticStore)
25+
if err != nil {
26+
return err
27+
}
28+
preInstallContainer = append(preInstallContainer, additionalContainers...)
29+
30+
stdout, _, err := feedback.DirectStreams()
31+
if err != nil {
32+
feedback.Fatal(err.Error(), feedback.ErrBadArgument)
33+
return nil
34+
}
35+
36+
for _, container := range preInstallContainer {
37+
cmd, err := paths.NewProcess(nil, "docker", "pull", container)
38+
if err != nil {
39+
return err
40+
}
41+
cmd.RedirectStderrTo(stdout)
42+
cmd.RedirectStdoutTo(stdout)
43+
if err := cmd.RunWithinContext(ctx); err != nil {
44+
return err
45+
}
46+
}
47+
return nil
48+
}
49+
50+
func parseAllModelsRunnerImageTag(staticStore *store.StaticStore) ([]string, error) {
51+
assets, err := staticStore.GetAssetsFolder()
52+
if err != nil {
53+
return nil, err
54+
}
55+
baseDir := path.Join("compose", "arduino")
56+
bricks, err := fs.ReadDir(assets, baseDir)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
result := make([]string, 0, len(bricks))
62+
for _, brick := range bricks {
63+
composeFile := path.Join(baseDir, brick.Name(), "brick_compose.yaml")
64+
content, err := fs.ReadFile(assets, composeFile)
65+
if err != nil {
66+
return nil, err
67+
}
68+
prj, err := loader.LoadWithContext(
69+
context.Background(),
70+
types.ConfigDetails{
71+
ConfigFiles: []types.ConfigFile{{Content: content}},
72+
Environment: types.NewMapping(os.Environ()),
73+
},
74+
func(o *loader.Options) { o.SetProjectName("test", false) },
75+
)
76+
if err != nil {
77+
return nil, err
78+
}
79+
for _, v := range prj.Services {
80+
// Add only if the image comes from arduino
81+
if strings.HasPrefix(v.Image, "ghcr.io/bcmi-labs/arduino/") ||
82+
// TODO: add the correct ecr prefix as soon as we have it in production
83+
strings.HasPrefix(v.Image, "public.ecr.aws/") {
84+
result = append(result, v.Image)
85+
}
86+
}
87+
}
88+
89+
return f.Uniq(result), nil
90+
}

0 commit comments

Comments
 (0)