detached 2846d20

Major refactor on configuration init and add new push command to CLI.

- Refactor configuration loading and saving, move to common package
- Remove unused files and methods related to flags and configurations
- Extend CLI with new 'Push' and 'Serve' commands
- Add flag support for 'Push' and 'Serve' commands
- Move docker client initialization to common package
- Update server start logic to include custom port with flag
- Add new payload types for push operation
- Implement push endpoint in server
- Update OAuth logic to use new configuration
- Simplify image storage and Docker import flow in HTTP handlers
- Add Docker image export functionality
- Use YAML parsing and writing utilities in the parser package instead of 2
  different implementations
commit
2846d206f2f759376dd2d3207d316f07573fd676
author
bnema · · 2023-10-25 14:45 UTC
parent
331079c
signature
unsigned
25 files · +446 -595
M .gitignore +2 -2
@@ -2,8 +2,8 @@
22 node_modules/
33 *tmp/
44 tmp/*
5-gordon.yml
5+config.yml
66 bun.lockb
77 *.log
88 *.txt
9-*sync-conflict*⏎̸
9+*sync-conflict*
@@ -2,8 +2,8 @@
2 node_modules/2 node_modules/
3 *tmp/3 *tmp/
4 tmp/*4 tmp/*
5-gordon.yml5+config.yml
6 bun.lockb6 bun.lockb
7 *.log7 *.log
8 *.txt8 *.txt
9-*sync-conflict*⏎̸9+*sync-conflict*
D changes.tx +0 -232
@@ -1,232 +0,0 @@
1-diff --git a/cmd/cli/main.go b/cmd/cli/main.go
2-index 8cf8d6c..8ba374c 100644
3---- a/cmd/cli/main.go
4-+++ b/cmd/cli/main.go
5-@@ -1,11 +1,16 @@
6- package main
7-
8- import (
9-+ "fmt"
10-+
11- "github.com/bnema/gordon/internal/app"
12- "github.com/bnema/gordon/internal/cli"
13- )
14-
15- func main() {
16-- a := app.NewClientApp()
17-+ a, err := app.NewClientApp()
18-+ if err != nil {
19-+ fmt.Println("Error initializing app:", err)
20-+ }
21- cli.Execute(a)
22- }
23-diff --git a/internal/app/newclient.go b/internal/app/newclient.go
24-index ec6bb4b..c51c1bf 100644
25---- a/internal/app/newclient.go
26-+++ b/internal/app/newclient.go
27-@@ -11,118 +11,105 @@ import (
28- "gopkg.in/yaml.v3"
29- )
30-
31--func NewClientApp() *App {
32-- // Initialize AppConfig
33-+// NewClientApp initializes a new App with configuration.
34-+func NewClientApp() (*App, error) {
35- config := &Config{}
36- config, err := config.LoadClientConfig()
37- if err != nil {
38-- log.Fatalf("Failed to load configuration: %v", err)
39-+ return nil, fmt.Errorf("failed to load configuration: %w", err)
40- }
41--
42-- // Initialize App
43-- a := &App{
44-- Config: *config,
45-- }
46-- return a
47-+ return &App{Config: *config}, nil
48- }
49-
50--func (config *Config) LoadClientConfig() (*Config, error) {
51-- configFileName := "config.yml"
52-+// getConfigDir returns the configuration directory based on the operating system.
53-+func getConfigDir() (string, error) {
54- usr, err := user.Current()
55- if err != nil {
56-- return nil, fmt.Errorf("error getting user's home directory: %w", err)
57-+ return "", fmt.Errorf("error getting user's home directory: %w", err)
58- }
59-
60- var configDir string
61--
62- if runtime.GOOS == "windows" {
63- configDir = filepath.Join(usr.HomeDir, "AppData", "Roaming", "Gordon")
64- } else {
65- configDir = filepath.Join(usr.HomeDir, ".config", "Gordon")
66- }
67-+ return configDir, nil
68-+}
69-
70-- if err := os.MkdirAll(configDir, 0755); err != nil {
71-- return nil, fmt.Errorf("error creating configuration directory: %w", err)
72-+// configPath returns the path of the configuration directory and file.
73-+func configPath() (string, string) {
74-+ configDir, err := getConfigDir()
75-+ if err != nil {
76-+ log.Fatalf("error getting configuration directory: %v", err)
77- }
78-+ configFilePath := filepath.Join(configDir, "config.yml")
79-+ return configDir, configFilePath
80-+}
81-
82-- configFilePath := filepath.Join(configDir, configFileName)
83--
84-- if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
85-- fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
86-- defaultConfig := []byte("some: default\ndata: here\n")
87-- if err := os.WriteFile(configFilePath, defaultConfig, 0666); err != nil {
88-- return nil, fmt.Errorf("error writing default configuration: %w", err)
89-- }
90-- } else if err != nil {
91-- return nil, fmt.Errorf("error checking configuration file: %w", err)
92-+// SaveConfig saves the current configuration to a file.
93-+func (config *Config) SaveConfig() error {
94-+ configDir, err := getConfigDir()
95-+ if err != nil {
96-+ return err
97- }
98--
99-- // Read and unmarshal the config file
100-- configData, err := os.ReadFile(configFilePath)
101-+ configFilePath := filepath.Join(configDir, "config.yml")
102-+ data, err := yaml.Marshal(config)
103- if err != nil {
104-- return nil, fmt.Errorf("error reading configuration file: %w", err)
105-+ return fmt.Errorf("failed to marshal config: %v", err)
106-+ }
107-+ if err := os.WriteFile(configFilePath, data, 0644); err != nil {
108-+ return fmt.Errorf("failed to write to config file: %v", err)
109- }
110-+ return nil
111-+}
112-
113-- err = yaml.Unmarshal(configData, &config)
114-+// readAndUnmarshalConfig reads the config file and unmarshals it into the given config.
115-+func readAndUnmarshalConfig(filePath string, config *Config) error {
116-+ configData, err := os.ReadFile(filePath)
117- if err != nil {
118-- return nil, fmt.Errorf("error unmarshalling configuration file: %w", err)
119-+ return fmt.Errorf("error reading configuration file: %w", err)
120- }
121-+ return yaml.Unmarshal(configData, config)
122-+}
123-
124-- // New logic for backend URL and token
125-- needsSave := false
126-+// LoadClientConfig loads the client configuration.
127-+func (config *Config) LoadClientConfig() (*Config, error) {
128-+ configDir, configFilePath := configPath()
129-
130-- if config.Http.BackendURL == "" {
131-- fmt.Println("Please enter the backend URL:")
132-- var backendURL string
133-- fmt.Scanln(&backendURL)
134-- config.Http.BackendURL = backendURL
135-- needsSave = true
136-+ if err := os.MkdirAll(configDir, 0755); err != nil {
137-+ return nil, fmt.Errorf("error creating configuration directory: %w", err)
138- }
139-
140-- if config.General.GordonToken == "" {
141-- fmt.Println("Please enter the token:")
142-- var token string
143-- fmt.Scanln(&token)
144-- config.General.GordonToken = token
145-- needsSave = true
146-- }
147-+ // Check if the file exists
148-+ if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
149-+ fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
150-
151-- if needsSave {
152-- err := config.SaveConfig() // Assuming SaveConfig is a method that saves the config to file
153-- if err != nil {
154-- return nil, fmt.Errorf("error saving configuration file: %w", err)
155-+ // Prompt user for initial settings
156-+ config.Http.BackendURL = readUserInput("Enter the backend URL:")
157-+ config.General.GordonToken = readUserInput("Enter the Gordon token:")
158-+
159-+ // Save the new configuration
160-+ if err := config.SaveConfig(); err != nil {
161-+ return nil, fmt.Errorf("error saving new configuration: %w", err)
162-+ }
163-+ } else if err != nil {
164-+ // Some other error occurred while checking the file
165-+ return nil, fmt.Errorf("error checking configuration file: %w", err)
166-+ } else {
167-+ // File exists, read and unmarshal it
168-+ if err := readAndUnmarshalConfig(configFilePath, config); err != nil {
169-+ return nil, err
170- }
171- }
172-
173- return config, nil
174- }
175-
176--func (config *Config) SaveConfig() error {
177-- usr, err := user.Current()
178-- if err != nil {
179-- return fmt.Errorf("error getting user's home directory: %w", err)
180-- }
181--
182-- var configDir string
183-- if runtime.GOOS == "windows" {
184-- configDir = filepath.Join(usr.HomeDir, "AppData", "Roaming", "Gordon")
185-- } else {
186-- configDir = filepath.Join(usr.HomeDir, ".config", "Gordon")
187-- }
188--
189-- configFilePath := filepath.Join(configDir, "config.yml")
190--
191-- // Marshal the config struct into YAML
192-- data, err := yaml.Marshal(config)
193-- if err != nil {
194-- return fmt.Errorf("failed to marshal config: %v", err)
195-- }
196--
197-- // Write the data to the config file
198-- err = os.WriteFile(configFilePath, data, 0644)
199-- if err != nil {
200-- return fmt.Errorf("failed to write to config file: %v", err)
201-- }
202--
203-- return nil
204-+// readUserInput reads a string input from the user with a prompt.
205-+func readUserInput(prompt string) string {
206-+ fmt.Println(prompt)
207-+ var input string
208-+ fmt.Scanln(&input)
209-+ return input
210- }
211-diff --git a/internal/cli/handler/configfile.go b/internal/cli/handler/configfile.go
212-deleted file mode 100644
213-index e1a3f6b..0000000
214---- a/internal/cli/handler/configfile.go
215-+++ /dev/null
216-@@ -1,16 +0,0 @@
217--package handler
218--
219--import "github.com/bnema/gordon/internal/app"
220--
221--// I need to handle the case of the backend URL not being present in the config file
222--func IsBackendURLPresent(a *app.App) bool {
223-- // since config.yml is loaded from newclient.go, I can access the backend URL from the app struct
224--
225-- if a.Config.Http.BackendURL == "" {
226-- return false
227-- }
228--
229-- return true
230--}
231--
232--// I Need to handle the case the token is not present in the config file
@@ -1,232 +0,0 @@
1-diff --git a/cmd/cli/main.go b/cmd/cli/main.go
2-index 8cf8d6c..8ba374c 100644
3---- a/cmd/cli/main.go
4-+++ b/cmd/cli/main.go
5-@@ -1,11 +1,16 @@
6- package main
7-
8- import (
9-+ "fmt"
10-+
11- "github.com/bnema/gordon/internal/app"
12- "github.com/bnema/gordon/internal/cli"
13- )
14-
15- func main() {
16-- a := app.NewClientApp()
17-+ a, err := app.NewClientApp()
18-+ if err != nil {
19-+ fmt.Println("Error initializing app:", err)
20-+ }
21- cli.Execute(a)
22- }
23-diff --git a/internal/app/newclient.go b/internal/app/newclient.go
24-index ec6bb4b..c51c1bf 100644
25---- a/internal/app/newclient.go
26-+++ b/internal/app/newclient.go
27-@@ -11,118 +11,105 @@ import (
28- "gopkg.in/yaml.v3"
29- )
30-
31--func NewClientApp() *App {
32-- // Initialize AppConfig
33-+// NewClientApp initializes a new App with configuration.
34-+func NewClientApp() (*App, error) {
35- config := &Config{}
36- config, err := config.LoadClientConfig()
37- if err != nil {
38-- log.Fatalf("Failed to load configuration: %v", err)
39-+ return nil, fmt.Errorf("failed to load configuration: %w", err)
40- }
41--
42-- // Initialize App
43-- a := &App{
44-- Config: *config,
45-- }
46-- return a
47-+ return &App{Config: *config}, nil
48- }
49-
50--func (config *Config) LoadClientConfig() (*Config, error) {
51-- configFileName := "config.yml"
52-+// getConfigDir returns the configuration directory based on the operating system.
53-+func getConfigDir() (string, error) {
54- usr, err := user.Current()
55- if err != nil {
56-- return nil, fmt.Errorf("error getting user's home directory: %w", err)
57-+ return "", fmt.Errorf("error getting user's home directory: %w", err)
58- }
59-
60- var configDir string
61--
62- if runtime.GOOS == "windows" {
63- configDir = filepath.Join(usr.HomeDir, "AppData", "Roaming", "Gordon")
64- } else {
65- configDir = filepath.Join(usr.HomeDir, ".config", "Gordon")
66- }
67-+ return configDir, nil
68-+}
69-
70-- if err := os.MkdirAll(configDir, 0755); err != nil {
71-- return nil, fmt.Errorf("error creating configuration directory: %w", err)
72-+// configPath returns the path of the configuration directory and file.
73-+func configPath() (string, string) {
74-+ configDir, err := getConfigDir()
75-+ if err != nil {
76-+ log.Fatalf("error getting configuration directory: %v", err)
77- }
78-+ configFilePath := filepath.Join(configDir, "config.yml")
79-+ return configDir, configFilePath
80-+}
81-
82-- configFilePath := filepath.Join(configDir, configFileName)
83--
84-- if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
85-- fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
86-- defaultConfig := []byte("some: default\ndata: here\n")
87-- if err := os.WriteFile(configFilePath, defaultConfig, 0666); err != nil {
88-- return nil, fmt.Errorf("error writing default configuration: %w", err)
89-- }
90-- } else if err != nil {
91-- return nil, fmt.Errorf("error checking configuration file: %w", err)
92-+// SaveConfig saves the current configuration to a file.
93-+func (config *Config) SaveConfig() error {
94-+ configDir, err := getConfigDir()
95-+ if err != nil {
96-+ return err
97- }
98--
99-- // Read and unmarshal the config file
100-- configData, err := os.ReadFile(configFilePath)
101-+ configFilePath := filepath.Join(configDir, "config.yml")
102-+ data, err := yaml.Marshal(config)
103- if err != nil {
104-- return nil, fmt.Errorf("error reading configuration file: %w", err)
105-+ return fmt.Errorf("failed to marshal config: %v", err)
106-+ }
107-+ if err := os.WriteFile(configFilePath, data, 0644); err != nil {
108-+ return fmt.Errorf("failed to write to config file: %v", err)
109- }
110-+ return nil
111-+}
112-
113-- err = yaml.Unmarshal(configData, &config)
114-+// readAndUnmarshalConfig reads the config file and unmarshals it into the given config.
115-+func readAndUnmarshalConfig(filePath string, config *Config) error {
116-+ configData, err := os.ReadFile(filePath)
117- if err != nil {
118-- return nil, fmt.Errorf("error unmarshalling configuration file: %w", err)
119-+ return fmt.Errorf("error reading configuration file: %w", err)
120- }
121-+ return yaml.Unmarshal(configData, config)
122-+}
123-
124-- // New logic for backend URL and token
125-- needsSave := false
126-+// LoadClientConfig loads the client configuration.
127-+func (config *Config) LoadClientConfig() (*Config, error) {
128-+ configDir, configFilePath := configPath()
129-
130-- if config.Http.BackendURL == "" {
131-- fmt.Println("Please enter the backend URL:")
132-- var backendURL string
133-- fmt.Scanln(&backendURL)
134-- config.Http.BackendURL = backendURL
135-- needsSave = true
136-+ if err := os.MkdirAll(configDir, 0755); err != nil {
137-+ return nil, fmt.Errorf("error creating configuration directory: %w", err)
138- }
139-
140-- if config.General.GordonToken == "" {
141-- fmt.Println("Please enter the token:")
142-- var token string
143-- fmt.Scanln(&token)
144-- config.General.GordonToken = token
145-- needsSave = true
146-- }
147-+ // Check if the file exists
148-+ if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
149-+ fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
150-
151-- if needsSave {
152-- err := config.SaveConfig() // Assuming SaveConfig is a method that saves the config to file
153-- if err != nil {
154-- return nil, fmt.Errorf("error saving configuration file: %w", err)
155-+ // Prompt user for initial settings
156-+ config.Http.BackendURL = readUserInput("Enter the backend URL:")
157-+ config.General.GordonToken = readUserInput("Enter the Gordon token:")
158-+
159-+ // Save the new configuration
160-+ if err := config.SaveConfig(); err != nil {
161-+ return nil, fmt.Errorf("error saving new configuration: %w", err)
162-+ }
163-+ } else if err != nil {
164-+ // Some other error occurred while checking the file
165-+ return nil, fmt.Errorf("error checking configuration file: %w", err)
166-+ } else {
167-+ // File exists, read and unmarshal it
168-+ if err := readAndUnmarshalConfig(configFilePath, config); err != nil {
169-+ return nil, err
170- }
171- }
172-
173- return config, nil
174- }
175-
176--func (config *Config) SaveConfig() error {
177-- usr, err := user.Current()
178-- if err != nil {
179-- return fmt.Errorf("error getting user's home directory: %w", err)
180-- }
181--
182-- var configDir string
183-- if runtime.GOOS == "windows" {
184-- configDir = filepath.Join(usr.HomeDir, "AppData", "Roaming", "Gordon")
185-- } else {
186-- configDir = filepath.Join(usr.HomeDir, ".config", "Gordon")
187-- }
188--
189-- configFilePath := filepath.Join(configDir, "config.yml")
190--
191-- // Marshal the config struct into YAML
192-- data, err := yaml.Marshal(config)
193-- if err != nil {
194-- return fmt.Errorf("failed to marshal config: %v", err)
195-- }
196--
197-- // Write the data to the config file
198-- err = os.WriteFile(configFilePath, data, 0644)
199-- if err != nil {
200-- return fmt.Errorf("failed to write to config file: %v", err)
201-- }
202--
203-- return nil
204-+// readUserInput reads a string input from the user with a prompt.
205-+func readUserInput(prompt string) string {
206-+ fmt.Println(prompt)
207-+ var input string
208-+ fmt.Scanln(&input)
209-+ return input
210- }
211-diff --git a/internal/cli/handler/configfile.go b/internal/cli/handler/configfile.go
212-deleted file mode 100644
213-index e1a3f6b..0000000
214---- a/internal/cli/handler/configfile.go
215-+++ /dev/null
216-@@ -1,16 +0,0 @@
217--package handler
218--
219--import "github.com/bnema/gordon/internal/app"
220--
221--// I need to handle the case of the backend URL not being present in the config file
222--func IsBackendURLPresent(a *app.App) bool {
223-- // since config.yml is loaded from newclient.go, I can access the backend URL from the app struct
224--
225-- if a.Config.Http.BackendURL == "" {
226-- return false
227-- }
228--
229-- return true
230--}
231--
232--// I Need to handle the case the token is not present in the config file
M cmd/cli/main.go +4 -0
@@ -5,6 +5,7 @@ import (
55
66 "github.com/bnema/gordon/internal/cli"
77 "github.com/bnema/gordon/internal/cli/cmd"
8+ "github.com/bnema/gordon/internal/common"
89 "github.com/bnema/gordon/internal/server"
910 "github.com/spf13/cobra"
1011 )
@@ -14,6 +15,7 @@ var rootCmd = &cobra.Command{Use: "gordon"}
1415 func InitializeCommands(a *cli.App, s *server.App) {
1516 rootCmd.AddCommand(cmd.NewServeCommand(s))
1617 rootCmd.AddCommand(cmd.NewPingCommand(a))
18+ rootCmd.AddCommand(cmd.NewPushCommand(a))
1719 }
1820
1921 func Execute(a *cli.App, s *server.App) {
@@ -31,5 +33,7 @@ func main() {
3133 fmt.Println("Error initializing app:", err)
3234 }
3335
36+ common.DockerInit(&s.Config.ContainerEngine)
37+
3438 Execute(a, s)
3539 }
@@ -5,6 +5,7 @@ import (
5 5
6 "github.com/bnema/gordon/internal/cli"6 "github.com/bnema/gordon/internal/cli"
7 "github.com/bnema/gordon/internal/cli/cmd"7 "github.com/bnema/gordon/internal/cli/cmd"
8+ "github.com/bnema/gordon/internal/common"
8 "github.com/bnema/gordon/internal/server"9 "github.com/bnema/gordon/internal/server"
9 "github.com/spf13/cobra"10 "github.com/spf13/cobra"
10 )11 )
@@ -14,6 +15,7 @@ var rootCmd = &cobra.Command{Use: "gordon"}
14 func InitializeCommands(a *cli.App, s *server.App) {15 func InitializeCommands(a *cli.App, s *server.App) {
15 rootCmd.AddCommand(cmd.NewServeCommand(s))16 rootCmd.AddCommand(cmd.NewServeCommand(s))
16 rootCmd.AddCommand(cmd.NewPingCommand(a))17 rootCmd.AddCommand(cmd.NewPingCommand(a))
18+ rootCmd.AddCommand(cmd.NewPushCommand(a))
17 }19 }
18 20
19 func Execute(a *cli.App, s *server.App) {21 func Execute(a *cli.App, s *server.App) {
@@ -31,5 +33,7 @@ func main() {
31 fmt.Println("Error initializing app:", err)33 fmt.Println("Error initializing app:", err)
32 }34 }
33 35
36+ common.DockerInit(&s.Config.ContainerEngine)
37+
34 Execute(a, s)38 Execute(a, s)
35 }39 }
D cmd/srv/main.go +0 -65
@@ -1,65 +0,0 @@
1-package main
2-
3-import (
4- "fmt"
5- "log"
6- "os"
7- "os/signal"
8- "syscall"
9-
10- "github.com/labstack/echo/v4"
11-
12- "github.com/bnema/gordon/internal/httpserve"
13- "github.com/bnema/gordon/internal/server"
14- "github.com/bnema/gordon/pkg/docker"
15-)
16-
17-func cleanup(a *server.App) {
18- if err := server.CloseDB(a); err != nil {
19- log.Fatal("Failed to close and backup database:", err)
20- }
21-}
22-
23-func main() {
24- a, err := server.NewServerApp()
25- if err != nil {
26- log.Fatal("Failed to initialize app:", err)
27- }
28-
29- _, err = server.InitializeDB(a)
30- if err != nil {
31- log.Fatal("Failed to initialize database:", err)
32- }
33-
34- _, err = server.HandleNewTokenInitialization(a)
35- if err != nil {
36- log.Print(err)
37- }
38-
39- dockerClient := &docker.DockerClient{}
40- err = dockerClient.InitializeClient(a.Config.NewDockerConfig())
41- if err != nil {
42- log.Printf("Error: %s", err)
43- }
44-
45- // Setup a channel to capture termination signals
46- sigs := make(chan os.Signal, 1)
47- signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
48-
49- go func() {
50- sig := <-sigs
51- log.Println("Received signal:", sig)
52- cleanup(a) // <- Call the cleanup function
53- os.Exit(0)
54- }()
55-
56- e := echo.New()
57- e.HideBanner = true
58- e.HidePort = true
59- e = httpserve.RegisterRoutes(e, a)
60-
61- log.Println("Starting server on port", a.Config.Http.Port)
62- if err := e.Start(fmt.Sprintf(":%d", a.Config.Http.Port)); err != nil {
63- log.Fatal("Server error:", err)
64- }
65-}
@@ -1,65 +0,0 @@
1-package main
2-
3-import (
4- "fmt"
5- "log"
6- "os"
7- "os/signal"
8- "syscall"
9-
10- "github.com/labstack/echo/v4"
11-
12- "github.com/bnema/gordon/internal/httpserve"
13- "github.com/bnema/gordon/internal/server"
14- "github.com/bnema/gordon/pkg/docker"
15-)
16-
17-func cleanup(a *server.App) {
18- if err := server.CloseDB(a); err != nil {
19- log.Fatal("Failed to close and backup database:", err)
20- }
21-}
22-
23-func main() {
24- a, err := server.NewServerApp()
25- if err != nil {
26- log.Fatal("Failed to initialize app:", err)
27- }
28-
29- _, err = server.InitializeDB(a)
30- if err != nil {
31- log.Fatal("Failed to initialize database:", err)
32- }
33-
34- _, err = server.HandleNewTokenInitialization(a)
35- if err != nil {
36- log.Print(err)
37- }
38-
39- dockerClient := &docker.DockerClient{}
40- err = dockerClient.InitializeClient(a.Config.NewDockerConfig())
41- if err != nil {
42- log.Printf("Error: %s", err)
43- }
44-
45- // Setup a channel to capture termination signals
46- sigs := make(chan os.Signal, 1)
47- signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
48-
49- go func() {
50- sig := <-sigs
51- log.Println("Received signal:", sig)
52- cleanup(a) // <- Call the cleanup function
53- os.Exit(0)
54- }()
55-
56- e := echo.New()
57- e.HideBanner = true
58- e.HidePort = true
59- e = httpserve.RegisterRoutes(e, a)
60-
61- log.Println("Starting server on port", a.Config.Http.Port)
62- if err := e.Start(fmt.Sprintf(":%d", a.Config.Http.Port)); err != nil {
63- log.Fatal("Server error:", err)
64- }
65-}
D internal/cli/appcli.go +0 -18
@@ -1,18 +0,0 @@
1-package cli
2-
3-type App struct {
4- Config Config
5-}
6-
7-type Config struct {
8- General GeneralConfig `yaml:"General"`
9- Http HttpConfig `yaml:"Http"`
10-}
11-
12-type GeneralConfig struct {
13- Token string `yaml:"token"`
14-}
15-
16-type HttpConfig struct {
17- BackendURL string `yaml:"backendURL"`
18-}
@@ -1,18 +0,0 @@
1-package cli
2-
3-type App struct {
4- Config Config
5-}
6-
7-type Config struct {
8- General GeneralConfig `yaml:"General"`
9- Http HttpConfig `yaml:"Http"`
10-}
11-
12-type GeneralConfig struct {
13- Token string `yaml:"token"`
14-}
15-
16-type HttpConfig struct {
17- BackendURL string `yaml:"backendURL"`
18-}
M internal/cli/cmd/push.go +57 -0
@@ -1,5 +1,15 @@
11 package cmd
22
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/cli"
7+ "github.com/bnema/gordon/internal/cli/handler"
8+ "github.com/bnema/gordon/internal/common"
9+ "github.com/bnema/gordon/pkg/docker"
10+ "github.com/spf13/cobra"
11+)
12+
313 // the push command is used to push a container image to gordon's endpoint
414
515 // Pseudo code of the steps for the push command
@@ -13,3 +23,50 @@ package cmd
1323 // 4. Send the request to the backend
1424
1525 // 5. If the response is 200, print the success message
26+
27+func NewPushCommand(a *cli.App) *cobra.Command {
28+ var port string
29+ var targetDomain string
30+
31+ pushCmd := &cobra.Command{
32+ Use: "push [image:tag]",
33+ Short: "Push an image to the server, if no tag is specified, latest is used",
34+ Args: cobra.ExactArgs(1), // Require exactly one positional argument for image:tag
35+ Run: func(cmd *cobra.Command, args []string) {
36+ imageName := args[0]
37+ fmt.Println("Exporting image:", imageName)
38+ // Export the image using Docker client
39+ data, err := docker.ExportImageFromEngine(imageName)
40+ if err != nil {
41+ fmt.Println("Error exporting image:", err)
42+ return
43+ }
44+ fmt.Println("Image exported successfully")
45+ // Create a RequestPayload and populate it
46+ payload := common.RequestPayload{
47+ Type: "push",
48+ Payload: common.PushPayload{
49+ Ports: port,
50+ TargetDomain: targetDomain,
51+ ImageName: imageName,
52+ Data: data,
53+ },
54+ }
55+ fmt.Println("Sending image to backend")
56+ // Send the request to the backend
57+ resp, err := handler.SendHTTPRequest(a, &payload, "/push")
58+ if err != nil {
59+ fmt.Println("Error sending HTTP request:", err)
60+ return
61+ }
62+
63+ fmt.Print(string(resp.Body))
64+ },
65+ }
66+
67+ // Add flags
68+ pushCmd.Flags().StringVarP(&port, "port", "p", "", "Port mapping for the container")
69+ pushCmd.Flags().StringVarP(&targetDomain, "target", "t", "", "Target domain for Traefik")
70+
71+ return pushCmd
72+}
@@ -1,5 +1,15 @@
1 package cmd1 package cmd
2 2
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/cli"
7+ "github.com/bnema/gordon/internal/cli/handler"
8+ "github.com/bnema/gordon/internal/common"
9+ "github.com/bnema/gordon/pkg/docker"
10+ "github.com/spf13/cobra"
11+)
12+
3 // the push command is used to push a container image to gordon's endpoint13 // the push command is used to push a container image to gordon's endpoint
4 14
5 // Pseudo code of the steps for the push command15 // Pseudo code of the steps for the push command
@@ -13,3 +23,50 @@ package cmd
13 // 4. Send the request to the backend23 // 4. Send the request to the backend
14 24
15 // 5. If the response is 200, print the success message25 // 5. If the response is 200, print the success message
26+
27+func NewPushCommand(a *cli.App) *cobra.Command {
28+ var port string
29+ var targetDomain string
30+
31+ pushCmd := &cobra.Command{
32+ Use: "push [image:tag]",
33+ Short: "Push an image to the server, if no tag is specified, latest is used",
34+ Args: cobra.ExactArgs(1), // Require exactly one positional argument for image:tag
35+ Run: func(cmd *cobra.Command, args []string) {
36+ imageName := args[0]
37+ fmt.Println("Exporting image:", imageName)
38+ // Export the image using Docker client
39+ data, err := docker.ExportImageFromEngine(imageName)
40+ if err != nil {
41+ fmt.Println("Error exporting image:", err)
42+ return
43+ }
44+ fmt.Println("Image exported successfully")
45+ // Create a RequestPayload and populate it
46+ payload := common.RequestPayload{
47+ Type: "push",
48+ Payload: common.PushPayload{
49+ Ports: port,
50+ TargetDomain: targetDomain,
51+ ImageName: imageName,
52+ Data: data,
53+ },
54+ }
55+ fmt.Println("Sending image to backend")
56+ // Send the request to the backend
57+ resp, err := handler.SendHTTPRequest(a, &payload, "/push")
58+ if err != nil {
59+ fmt.Println("Error sending HTTP request:", err)
60+ return
61+ }
62+
63+ fmt.Print(string(resp.Body))
64+ },
65+ }
66+
67+ // Add flags
68+ pushCmd.Flags().StringVarP(&port, "port", "p", "", "Port mapping for the container")
69+ pushCmd.Flags().StringVarP(&targetDomain, "target", "t", "", "Target domain for Traefik")
70+
71+ return pushCmd
72+}
M internal/cli/cmd/serve.go +16 -6
@@ -8,14 +8,24 @@ import (
88 "github.com/spf13/cobra"
99 )
1010
11-// execute cmd/srv/main.go main function
12-
11+// NewServeCommand creates a new serve command
1312 func NewServeCommand(a *server.App) *cobra.Command {
14- return &cobra.Command{
15- Use: "serve",
16- Short: "Start the gordon server",
13+ var port string
14+ defaultport := "1323"
15+
16+ // if no flags -p or --port are specified, the default port is used
17+ serveCmd := &cobra.Command{
18+ Use: "serve",
19+ Args: cobra.NoArgs,
1720 Run: func(cmd *cobra.Command, args []string) {
18- handler.StartServer(a)
21+ // handler.StartServer will use the value of port, which will be the default
22+ // value if no flag is specified
23+ handler.StartServer(a, port)
1924 },
2025 }
26+
27+ // Attach the flag to serveCmd and store its value in the variable port
28+ serveCmd.Flags().StringVarP(&port, "port", "p", defaultport, "Port to listen on")
29+
30+ return serveCmd
2131 }
@@ -8,14 +8,24 @@ import (
8 "github.com/spf13/cobra"8 "github.com/spf13/cobra"
9 )9 )
10 10
11-// execute cmd/srv/main.go main function11+// NewServeCommand creates a new serve command
12-
13 func NewServeCommand(a *server.App) *cobra.Command {12 func NewServeCommand(a *server.App) *cobra.Command {
14- return &cobra.Command{13+ var port string
15- Use: "serve",14+ defaultport := "1323"
16- Short: "Start the gordon server",15+
16+ // if no flags -p or --port are specified, the default port is used
17+ serveCmd := &cobra.Command{
18+ Use: "serve",
19+ Args: cobra.NoArgs,
17 Run: func(cmd *cobra.Command, args []string) {20 Run: func(cmd *cobra.Command, args []string) {
18- handler.StartServer(a)21+ // handler.StartServer will use the value of port, which will be the default
22+ // value if no flag is specified
23+ handler.StartServer(a, port)
19 },24 },
20 }25 }
26+
27+ // Attach the flag to serveCmd and store its value in the variable port
28+ serveCmd.Flags().StringVarP(&port, "port", "p", defaultport, "Port to listen on")
29+
30+ return serveCmd
21 }31 }
D internal/cli/flags/ports.go +0 -0

no textual change.

D internal/cli/flags/targetdomain.go +0 -3
@@ -1,3 +0,0 @@
1-package flags
2-
3-// Domain flags is -d, --domain
@@ -1,3 +0,0 @@
1-package flags
2-
3-// Domain flags is -d, --domain
M internal/cli/handler/startserver.go +3 -10
@@ -9,13 +9,12 @@ import (
99
1010 "github.com/bnema/gordon/internal/httpserve"
1111 "github.com/bnema/gordon/internal/server"
12- "github.com/bnema/gordon/pkg/docker"
1312 "github.com/labstack/echo/v4"
1413 )
1514
1615 // execute cmd/srv/main.go main function
1716
18-func StartServer(a *server.App) error {
17+func StartServer(a *server.App, port string) error {
1918 _, err := server.InitializeDB(a)
2019 if err != nil {
2120 log.Fatal("Failed to initialize database:", err)
@@ -26,12 +25,6 @@ func StartServer(a *server.App) error {
2625 log.Print(err)
2726 }
2827
29- dockerClient := &docker.DockerClient{}
30- err = dockerClient.InitializeClient(a.Config.NewDockerConfig())
31- if err != nil {
32- log.Printf("Error: %s", err)
33- }
34-
3528 // Setup a channel to capture termination signals
3629 sigs := make(chan os.Signal, 1)
3730 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
@@ -47,8 +40,8 @@ func StartServer(a *server.App) error {
4740 e.HidePort = true
4841 e = httpserve.RegisterRoutes(e, a)
4942
50- log.Println("Starting server on port", a.Config.Http.Port)
51- if err := e.Start(fmt.Sprintf(":%d", a.Config.Http.Port)); err != nil {
43+ log.Println("Starting server on port", port)
44+ if err := e.Start(fmt.Sprintf(":%s", port)); err != nil {
5245 log.Fatal("Server error:", err)
5346 }
5447
@@ -9,13 +9,12 @@ import (
9 9
10 "github.com/bnema/gordon/internal/httpserve"10 "github.com/bnema/gordon/internal/httpserve"
11 "github.com/bnema/gordon/internal/server"11 "github.com/bnema/gordon/internal/server"
12- "github.com/bnema/gordon/pkg/docker"
13 "github.com/labstack/echo/v4"12 "github.com/labstack/echo/v4"
14 )13 )
15 14
16 // execute cmd/srv/main.go main function15 // execute cmd/srv/main.go main function
17 16
18-func StartServer(a *server.App) error {17+func StartServer(a *server.App, port string) error {
19 _, err := server.InitializeDB(a)18 _, err := server.InitializeDB(a)
20 if err != nil {19 if err != nil {
21 log.Fatal("Failed to initialize database:", err)20 log.Fatal("Failed to initialize database:", err)
@@ -26,12 +25,6 @@ func StartServer(a *server.App) error {
26 log.Print(err)25 log.Print(err)
27 }26 }
28 27
29- dockerClient := &docker.DockerClient{}
30- err = dockerClient.InitializeClient(a.Config.NewDockerConfig())
31- if err != nil {
32- log.Printf("Error: %s", err)
33- }
34-
35 // Setup a channel to capture termination signals28 // Setup a channel to capture termination signals
36 sigs := make(chan os.Signal, 1)29 sigs := make(chan os.Signal, 1)
37 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)30 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
@@ -47,8 +40,8 @@ func StartServer(a *server.App) error {
47 e.HidePort = true40 e.HidePort = true
48 e = httpserve.RegisterRoutes(e, a)41 e = httpserve.RegisterRoutes(e, a)
49 42
50- log.Println("Starting server on port", a.Config.Http.Port)43+ log.Println("Starting server on port", port)
51- if err := e.Start(fmt.Sprintf(":%d", a.Config.Http.Port)); err != nil {44+ if err := e.Start(fmt.Sprintf(":%s", port)); err != nil {
52 log.Fatal("Server error:", err)45 log.Fatal("Server error:", err)
53 }46 }
54 47
M internal/cli/newclient.go +12 -101
@@ -2,116 +2,27 @@ package cli
22
33 import (
44 "fmt"
5- "log"
6- "os"
7- "os/user"
8- "path/filepath"
9- "runtime"
105
11- "gopkg.in/yaml.v3"
6+ "github.com/bnema/gordon/internal/common"
127 )
138
9+type App struct {
10+ Config common.Config
11+}
12+
1413 // NewClientApp initializes a new App with configuration.
1514 func NewClientApp() (*App, error) {
16- config := &Config{}
17- config, err := config.LoadClientConfig()
15+ // Initialize AppConfig
16+ config := common.Config{}
17+ _, err := config.LoadConfig()
1818 if err != nil {
1919 return nil, fmt.Errorf("failed to load configuration: %w", err)
2020 }
21- return &App{Config: *config}, nil
22-}
23-
24-// getConfigDir returns the configuration directory based on the operating system.
25-func getConfigDir() (string, error) {
26- usr, err := user.Current()
27- if err != nil {
28- return "", fmt.Errorf("error getting user's home directory: %w", err)
29- }
30-
31- var configDir string
32- if runtime.GOOS == "windows" {
33- configDir = filepath.Join(usr.HomeDir, "AppData", "Roaming", "Gordon")
34- } else {
35- configDir = filepath.Join(usr.HomeDir, ".config", "Gordon")
36- }
37- return configDir, nil
38-}
39-
40-// configPath returns the path of the configuration directory and file.
41-func configPath() (string, string) {
42- configDir, err := getConfigDir()
43- if err != nil {
44- log.Fatalf("error getting configuration directory: %v", err)
45- }
46- configFilePath := filepath.Join(configDir, "config.yml")
47- return configDir, configFilePath
48-}
4921
50-// SaveConfig saves the current configuration to a file.
51-func (config *Config) SaveConfig() error {
52- configDir, err := getConfigDir()
53- if err != nil {
54- return err
55- }
56- configFilePath := filepath.Join(configDir, "config.yml")
57- data, err := yaml.Marshal(config)
58- if err != nil {
59- return fmt.Errorf("failed to marshal config: %v", err)
60- }
61- if err := os.WriteFile(configFilePath, data, 0644); err != nil {
62- return fmt.Errorf("failed to write to config file: %v", err)
22+ // Initialize App
23+ a := &App{
24+ Config: config,
6325 }
64- return nil
65-}
66-
67-// readAndUnmarshalConfig reads the config file and unmarshals it into the given config.
68-func readAndUnmarshalConfig(filePath string, config *Config) error {
69- configData, err := os.ReadFile(filePath)
70- if err != nil {
71- return fmt.Errorf("error reading configuration file: %w", err)
72- }
73- return yaml.Unmarshal(configData, config)
74-}
75-
76-func (config *Config) LoadClientConfig() (*Config, error) {
77- configDir, configFilePath := configPath()
78-
79- err := os.MkdirAll(configDir, 0755)
80- if err != nil {
81- return nil, fmt.Errorf("error creating configuration directory: %w", err)
82- }
83-
84- _, err = os.Stat(configFilePath)
85- if os.IsNotExist(err) {
86- fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
87-
88- config.Http.BackendURL = readUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
89- config.General.Token = readUserInput("Enter the token (check your backend config.yml):")
90-
91- err = config.SaveConfig()
92- if err != nil {
93- return nil, fmt.Errorf("error saving new configuration: %w", err)
94- }
95-
96- return config, nil
97- }
98-
99- if err != nil {
100- return nil, fmt.Errorf("error checking configuration file: %w", err)
101- }
102-
103- err = readAndUnmarshalConfig(configFilePath, config)
104- if err != nil {
105- return nil, err
106- }
107-
108- return config, nil
109-}
11026
111-// readUserInput reads a string input from the user with a prompt.
112-func readUserInput(prompt string) string {
113- fmt.Println(prompt)
114- var input string
115- fmt.Scanln(&input)
116- return input
27+ return a, nil
11728 }
@@ -2,116 +2,27 @@ package cli
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5- "log"
6- "os"
7- "os/user"
8- "path/filepath"
9- "runtime"
10 5
11- "gopkg.in/yaml.v3"6+ "github.com/bnema/gordon/internal/common"
12 )7 )
13 8
9+type App struct {
10+ Config common.Config
11+}
12+
14 // NewClientApp initializes a new App with configuration.13 // NewClientApp initializes a new App with configuration.
15 func NewClientApp() (*App, error) {14 func NewClientApp() (*App, error) {
16- config := &Config{}15+ // Initialize AppConfig
17- config, err := config.LoadClientConfig()16+ config := common.Config{}
17+ _, err := config.LoadConfig()
18 if err != nil {18 if err != nil {
19 return nil, fmt.Errorf("failed to load configuration: %w", err)19 return nil, fmt.Errorf("failed to load configuration: %w", err)
20 }20 }
21- return &App{Config: *config}, nil
22-}
23-
24-// getConfigDir returns the configuration directory based on the operating system.
25-func getConfigDir() (string, error) {
26- usr, err := user.Current()
27- if err != nil {
28- return "", fmt.Errorf("error getting user's home directory: %w", err)
29- }
30-
31- var configDir string
32- if runtime.GOOS == "windows" {
33- configDir = filepath.Join(usr.HomeDir, "AppData", "Roaming", "Gordon")
34- } else {
35- configDir = filepath.Join(usr.HomeDir, ".config", "Gordon")
36- }
37- return configDir, nil
38-}
39-
40-// configPath returns the path of the configuration directory and file.
41-func configPath() (string, string) {
42- configDir, err := getConfigDir()
43- if err != nil {
44- log.Fatalf("error getting configuration directory: %v", err)
45- }
46- configFilePath := filepath.Join(configDir, "config.yml")
47- return configDir, configFilePath
48-}
49 21
50-// SaveConfig saves the current configuration to a file.22+ // Initialize App
51-func (config *Config) SaveConfig() error {23+ a := &App{
52- configDir, err := getConfigDir()24+ Config: config,
53- if err != nil {
54- return err
55- }
56- configFilePath := filepath.Join(configDir, "config.yml")
57- data, err := yaml.Marshal(config)
58- if err != nil {
59- return fmt.Errorf("failed to marshal config: %v", err)
60- }
61- if err := os.WriteFile(configFilePath, data, 0644); err != nil {
62- return fmt.Errorf("failed to write to config file: %v", err)
63 }25 }
64- return nil
65-}
66-
67-// readAndUnmarshalConfig reads the config file and unmarshals it into the given config.
68-func readAndUnmarshalConfig(filePath string, config *Config) error {
69- configData, err := os.ReadFile(filePath)
70- if err != nil {
71- return fmt.Errorf("error reading configuration file: %w", err)
72- }
73- return yaml.Unmarshal(configData, config)
74-}
75-
76-func (config *Config) LoadClientConfig() (*Config, error) {
77- configDir, configFilePath := configPath()
78-
79- err := os.MkdirAll(configDir, 0755)
80- if err != nil {
81- return nil, fmt.Errorf("error creating configuration directory: %w", err)
82- }
83-
84- _, err = os.Stat(configFilePath)
85- if os.IsNotExist(err) {
86- fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
87-
88- config.Http.BackendURL = readUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
89- config.General.Token = readUserInput("Enter the token (check your backend config.yml):")
90-
91- err = config.SaveConfig()
92- if err != nil {
93- return nil, fmt.Errorf("error saving new configuration: %w", err)
94- }
95-
96- return config, nil
97- }
98-
99- if err != nil {
100- return nil, fmt.Errorf("error checking configuration file: %w", err)
101- }
102-
103- err = readAndUnmarshalConfig(configFilePath, config)
104- if err != nil {
105- return nil, err
106- }
107-
108- return config, nil
109-}
110 26
111-// readUserInput reads a string input from the user with a prompt.27+ return a, nil
112-func readUserInput(prompt string) string {
113- fmt.Println(prompt)
114- var input string
115- fmt.Scanln(&input)
116- return input
117 }28 }
A internal/common/initconfig.go +133 -0
@@ -0,0 +1,133 @@
1+package common
2+
3+import (
4+ "errors"
5+ "fmt"
6+ "io/fs"
7+ "os"
8+ "os/user"
9+ "path/filepath"
10+ "runtime"
11+
12+ "github.com/bnema/gordon/pkg/parser"
13+)
14+
15+var (
16+ sock = "/var/run/docker.sock"
17+ podmansock = "/run/user/1000/podman/podman.sock"
18+)
19+
20+func isMaybeRunningInDocker() bool { // Thanks Pocketbase
21+ _, err := os.Stat("/.dockerenv")
22+ return err == nil
23+}
24+
25+func getConfigDir() (string, error) {
26+ usr, err := user.Current()
27+ if err != nil {
28+ return "", fmt.Errorf("error getting user's home directory: %w", err)
29+ }
30+
31+ if isMaybeRunningInDocker() {
32+ return ".", nil // The directory where the binary is located
33+ }
34+
35+ var configDir string
36+ if runtime.GOOS == "windows" {
37+ configDir = filepath.Join(usr.HomeDir, "AppData", "Roaming", "Gordon")
38+ } else {
39+ configDir = filepath.Join(usr.HomeDir, ".config", "Gordon")
40+ }
41+ return configDir, nil
42+}
43+
44+func readAndUnmarshalConfig(fs fs.FS, filePath string, config *Config) error {
45+ err := parser.ParseYAMLFile(fs, filePath, config)
46+ if err != nil {
47+ return fmt.Errorf("error reading and unmarshaling configuration file: %w", err)
48+ }
49+ return nil
50+}
51+
52+func (config *Config) LoadConfig() (*Config, error) {
53+ configDir, err := getConfigDir()
54+ if err != nil {
55+ return nil, fmt.Errorf("error getting configuration directory: %w", err)
56+ }
57+
58+ configFilePath := filepath.Join(configDir, "config.yml")
59+
60+ _, err = os.Stat(configFilePath)
61+ if errors.Is(err, fs.ErrNotExist) {
62+ fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
63+
64+ // Create config dir if it doesn't exist
65+ err = os.MkdirAll(configDir, 0755)
66+ if err != nil {
67+ return nil, fmt.Errorf("error creating configuration directory: %w", err)
68+ }
69+
70+ // Create config file with the default values of ContainerEngineConfig
71+ config = &Config{
72+ ContainerEngine: ContainerEngineConfig{
73+ Sock: sock,
74+ PodmanSock: podmansock,
75+ Podman: readUserInput("Are you using podman ? (y/n)") == "y",
76+ },
77+ }
78+
79+ err = config.SaveConfig()
80+ if err != nil {
81+ return nil, fmt.Errorf("error saving new configuration: %w", err)
82+ }
83+ return config, nil
84+ }
85+
86+ if err != nil {
87+ return nil, fmt.Errorf("error checking configuration file: %w", err)
88+ }
89+
90+ err = readAndUnmarshalConfig(os.DirFS(configDir), "config.yml", config)
91+ if err != nil {
92+ return nil, err
93+ }
94+
95+ // Load env elements
96+ config.General.BuildVersion = os.Getenv("BUILD_VERSION")
97+ config.General.RunEnv = os.Getenv("RUN_ENV")
98+ config.General.BuildDir = os.Getenv("BUILD_DIR")
99+
100+ // if RUN_ENV is not set, assume "prod" and config dir is the current dir
101+ if config.General.RunEnv == "" {
102+ config.General.RunEnv = "prod"
103+ config.General.BuildDir = "."
104+ }
105+
106+ return config, nil
107+}
108+
109+func (config *Config) SaveConfig() error {
110+ configDir, err := getConfigDir()
111+ if err != nil {
112+ return fmt.Errorf("error getting configuration directory: %w", err)
113+ }
114+ configFilePath := filepath.Join(configDir, "config.yml")
115+
116+ err = parser.WriteYAMLFile(configFilePath, config)
117+ if err != nil {
118+ return fmt.Errorf("error writing configuration file: %w", err)
119+ }
120+
121+ fmt.Printf("Configuration saved to %s\n", configFilePath)
122+ return nil
123+}
124+
125+func (config *Config) GetToken() (string, error) {
126+ token := config.General.Token
127+ if token == "" {
128+ return "", fmt.Errorf("no token found in config.yml")
129+ }
130+
131+ return token, nil
132+
133+}
@@ -0,0 +1,133 @@
1+package common
2+
3+import (
4+ "errors"
5+ "fmt"
6+ "io/fs"
7+ "os"
8+ "os/user"
9+ "path/filepath"
10+ "runtime"
11+
12+ "github.com/bnema/gordon/pkg/parser"
13+)
14+
15+var (
16+ sock = "/var/run/docker.sock"
17+ podmansock = "/run/user/1000/podman/podman.sock"
18+)
19+
20+func isMaybeRunningInDocker() bool { // Thanks Pocketbase
21+ _, err := os.Stat("/.dockerenv")
22+ return err == nil
23+}
24+
25+func getConfigDir() (string, error) {
26+ usr, err := user.Current()
27+ if err != nil {
28+ return "", fmt.Errorf("error getting user's home directory: %w", err)
29+ }
30+
31+ if isMaybeRunningInDocker() {
32+ return ".", nil // The directory where the binary is located
33+ }
34+
35+ var configDir string
36+ if runtime.GOOS == "windows" {
37+ configDir = filepath.Join(usr.HomeDir, "AppData", "Roaming", "Gordon")
38+ } else {
39+ configDir = filepath.Join(usr.HomeDir, ".config", "Gordon")
40+ }
41+ return configDir, nil
42+}
43+
44+func readAndUnmarshalConfig(fs fs.FS, filePath string, config *Config) error {
45+ err := parser.ParseYAMLFile(fs, filePath, config)
46+ if err != nil {
47+ return fmt.Errorf("error reading and unmarshaling configuration file: %w", err)
48+ }
49+ return nil
50+}
51+
52+func (config *Config) LoadConfig() (*Config, error) {
53+ configDir, err := getConfigDir()
54+ if err != nil {
55+ return nil, fmt.Errorf("error getting configuration directory: %w", err)
56+ }
57+
58+ configFilePath := filepath.Join(configDir, "config.yml")
59+
60+ _, err = os.Stat(configFilePath)
61+ if errors.Is(err, fs.ErrNotExist) {
62+ fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
63+
64+ // Create config dir if it doesn't exist
65+ err = os.MkdirAll(configDir, 0755)
66+ if err != nil {
67+ return nil, fmt.Errorf("error creating configuration directory: %w", err)
68+ }
69+
70+ // Create config file with the default values of ContainerEngineConfig
71+ config = &Config{
72+ ContainerEngine: ContainerEngineConfig{
73+ Sock: sock,
74+ PodmanSock: podmansock,
75+ Podman: readUserInput("Are you using podman ? (y/n)") == "y",
76+ },
77+ }
78+
79+ err = config.SaveConfig()
80+ if err != nil {
81+ return nil, fmt.Errorf("error saving new configuration: %w", err)
82+ }
83+ return config, nil
84+ }
85+
86+ if err != nil {
87+ return nil, fmt.Errorf("error checking configuration file: %w", err)
88+ }
89+
90+ err = readAndUnmarshalConfig(os.DirFS(configDir), "config.yml", config)
91+ if err != nil {
92+ return nil, err
93+ }
94+
95+ // Load env elements
96+ config.General.BuildVersion = os.Getenv("BUILD_VERSION")
97+ config.General.RunEnv = os.Getenv("RUN_ENV")
98+ config.General.BuildDir = os.Getenv("BUILD_DIR")
99+
100+ // if RUN_ENV is not set, assume "prod" and config dir is the current dir
101+ if config.General.RunEnv == "" {
102+ config.General.RunEnv = "prod"
103+ config.General.BuildDir = "."
104+ }
105+
106+ return config, nil
107+}
108+
109+func (config *Config) SaveConfig() error {
110+ configDir, err := getConfigDir()
111+ if err != nil {
112+ return fmt.Errorf("error getting configuration directory: %w", err)
113+ }
114+ configFilePath := filepath.Join(configDir, "config.yml")
115+
116+ err = parser.WriteYAMLFile(configFilePath, config)
117+ if err != nil {
118+ return fmt.Errorf("error writing configuration file: %w", err)
119+ }
120+
121+ fmt.Printf("Configuration saved to %s\n", configFilePath)
122+ return nil
123+}
124+
125+func (config *Config) GetToken() (string, error) {
126+ token := config.General.Token
127+ if token == "" {
128+ return "", fmt.Errorf("no token found in config.yml")
129+ }
130+
131+ return token, nil
132+
133+}
A internal/common/initdockerclient.go +29 -0
@@ -0,0 +1,29 @@
1+package common
2+
3+import (
4+ "log"
5+
6+ "github.com/bnema/gordon/pkg/docker"
7+)
8+
9+func DockerInit(cc *ContainerEngineConfig) {
10+ dockerClient := docker.DockerClient{}
11+ err := dockerClient.InitializeClient(NewDockerConfig(cc))
12+ if err != nil {
13+ log.Printf("Error: %s", err)
14+ }
15+}
16+
17+// NewDockerConfig creates and returns a new Docker client configuration
18+func NewDockerConfig(cc *ContainerEngineConfig) *docker.Config {
19+ if cc.Podman {
20+ return &docker.Config{
21+ Sock: cc.PodmanSock,
22+ PodmanEnable: true,
23+ }
24+ }
25+ return &docker.Config{
26+ Sock: cc.Sock,
27+ PodmanEnable: false,
28+ }
29+}
@@ -0,0 +1,29 @@
1+package common
2+
3+import (
4+ "log"
5+
6+ "github.com/bnema/gordon/pkg/docker"
7+)
8+
9+func DockerInit(cc *ContainerEngineConfig) {
10+ dockerClient := docker.DockerClient{}
11+ err := dockerClient.InitializeClient(NewDockerConfig(cc))
12+ if err != nil {
13+ log.Printf("Error: %s", err)
14+ }
15+}
16+
17+// NewDockerConfig creates and returns a new Docker client configuration
18+func NewDockerConfig(cc *ContainerEngineConfig) *docker.Config {
19+ if cc.Podman {
20+ return &docker.Config{
21+ Sock: cc.PodmanSock,
22+ PodmanEnable: true,
23+ }
24+ }
25+ return &docker.Config{
26+ Sock: cc.Sock,
27+ PodmanEnable: false,
28+ }
29+}
A internal/common/inputs.go +11 -0
@@ -0,0 +1,11 @@
1+package common
2+
3+import "fmt"
4+
5+// readUserInput reads a string input from the user with a prompt.
6+func readUserInput(prompt string) string {
7+ fmt.Println(prompt)
8+ var input string
9+ fmt.Scanln(&input)
10+ return input
11+}
@@ -0,0 +1,11 @@
1+package common
2+
3+import "fmt"
4+
5+// readUserInput reads a string input from the user with a prompt.
6+func readUserInput(prompt string) string {
7+ fmt.Println(prompt)
8+ var input string
9+ fmt.Scanln(&input)
10+ return input
11+}
M internal/common/payload.go +12 -4
@@ -50,10 +50,6 @@ func (p PingPayload) GetType() string {
5050 return "ping"
5151 }
5252
53-type PushPayload struct {
54- Data []byte `json:"data"`
55-}
56-
5753 func (p PushPayload) GetType() string {
5854 return "push"
5955 }
@@ -65,3 +61,15 @@ func NewPingPayload(data map[string]interface{}) (PingPayload, error) {
6561 }
6662 return PingPayload{Message: message}, nil
6763 }
64+
65+// What do we need inside the push payload :
66+// the ports as a string (80:80/tcp, 443:443/tcp)
67+// the image+tag as a string (nginx:latest)
68+// the .tar container image as a byte array
69+
70+type PushPayload struct {
71+ Ports string `json:"ports"`
72+ TargetDomain string `json:"targetdomain"`
73+ ImageName string `json:"imagename"`
74+ Data []byte `json:"data"`
75+}
@@ -50,10 +50,6 @@ func (p PingPayload) GetType() string {
50 return "ping"50 return "ping"
51 }51 }
52 52
53-type PushPayload struct {
54- Data []byte `json:"data"`
55-}
56-
57 func (p PushPayload) GetType() string {53 func (p PushPayload) GetType() string {
58 return "push"54 return "push"
59 }55 }
@@ -65,3 +61,15 @@ func NewPingPayload(data map[string]interface{}) (PingPayload, error) {
65 }61 }
66 return PingPayload{Message: message}, nil62 return PingPayload{Message: message}, nil
67 }63 }
64+
65+// What do we need inside the push payload :
66+// the ports as a string (80:80/tcp, 443:443/tcp)
67+// the image+tag as a string (nginx:latest)
68+// the .tar container image as a byte array
69+
70+type PushPayload struct {
71+ Ports string `json:"ports"`
72+ TargetDomain string `json:"targetdomain"`
73+ ImageName string `json:"imagename"`
74+ Data []byte `json:"data"`
75+}
A internal/common/types.go +36 -0
@@ -0,0 +1,36 @@
1+package common
2+
3+type Config struct {
4+ General GeneralConfig `yaml:"General"`
5+ Http HttpConfig `yaml:"Http"`
6+ Admin AdminConfig `yaml:"Admin"`
7+ ContainerEngine ContainerEngineConfig `yaml:"ContainerEngine"`
8+}
9+
10+type AdminConfig struct {
11+ Path string `yaml:"path"`
12+}
13+
14+type GeneralConfig struct {
15+ RunEnv string // come from env
16+ BuildDir string // come from env
17+ BuildVersion string // come from env
18+ StorageDir string `yaml:"storageDir"` // = buildir/storage
19+ Token string `yaml:"token"`
20+}
21+type HttpConfig struct {
22+ Port int `yaml:"port"`
23+ TopDomain string `yaml:"topDomain"`
24+ SubDomain string `yaml:"subDomain"`
25+ BackendURL string `yaml:"backendURL"`
26+}
27+
28+type ContainerEngineConfig struct {
29+ Sock string `yaml:"dockersock"`
30+ PodmanSock string `yaml:"podmansock"`
31+ Podman bool `yaml:"podman"`
32+ Network string `yaml:"network"`
33+}
34+
35+// config.Http.BackendURL = readUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
36+// config.General.Token = readUserInput("Enter the token (check your backend config.yml):")
@@ -0,0 +1,36 @@
1+package common
2+
3+type Config struct {
4+ General GeneralConfig `yaml:"General"`
5+ Http HttpConfig `yaml:"Http"`
6+ Admin AdminConfig `yaml:"Admin"`
7+ ContainerEngine ContainerEngineConfig `yaml:"ContainerEngine"`
8+}
9+
10+type AdminConfig struct {
11+ Path string `yaml:"path"`
12+}
13+
14+type GeneralConfig struct {
15+ RunEnv string // come from env
16+ BuildDir string // come from env
17+ BuildVersion string // come from env
18+ StorageDir string `yaml:"storageDir"` // = buildir/storage
19+ Token string `yaml:"token"`
20+}
21+type HttpConfig struct {
22+ Port int `yaml:"port"`
23+ TopDomain string `yaml:"topDomain"`
24+ SubDomain string `yaml:"subDomain"`
25+ BackendURL string `yaml:"backendURL"`
26+}
27+
28+type ContainerEngineConfig struct {
29+ Sock string `yaml:"dockersock"`
30+ PodmanSock string `yaml:"podmansock"`
31+ Podman bool `yaml:"podman"`
32+ Network string `yaml:"network"`
33+}
34+
35+// config.Http.BackendURL = readUserInput("Enter the backend URL (e.g. https://gordon.mydomain.com):")
36+// config.General.Token = readUserInput("Enter the token (check your backend config.yml):")
M internal/httpserve/handler/endpoints.go +58 -3
@@ -8,6 +8,7 @@ import (
88
99 "github.com/bnema/gordon/internal/common"
1010 "github.com/bnema/gordon/internal/server"
11+ "github.com/bnema/gordon/pkg/store"
1112 "github.com/labstack/echo/v4"
1213 )
1314
@@ -23,14 +24,12 @@ type InfoResponse struct {
2324
2425 func (info *InfoResponse) Populate(a *server.App) {
2526 info.Uptime = a.GetUptime()
26- info.Version = a.Config.GetVersion()
27+ info.Version = a.GetVersionstring()
2728 }
2829
2930 // Handle GET on /api/ping endpoint
3031 func GetInfos(c echo.Context, a *server.App) error {
31- fmt.Println("GET /api/ping")
3232 body, _ := io.ReadAll(c.Request().Body)
33- fmt.Println("Request Body:", string(body))
3433 c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body
3534
3635 payload := new(common.RequestPayload)
@@ -58,3 +57,59 @@ func GetInfos(c echo.Context, a *server.App) error {
5857 return c.JSON(http.StatusOK, info)
5958
6059 }
60+
61+func PostPush(c echo.Context, a *server.App) error {
62+ body, _ := io.ReadAll(c.Request().Body)
63+ c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body
64+
65+ payload := new(common.RequestPayload)
66+ if err := c.Bind(payload); err != nil {
67+ fmt.Println("Bind Error:", err)
68+ return c.JSON(http.StatusBadRequest, err.Error())
69+ }
70+
71+ if payload.Type != "push" {
72+ return c.JSON(http.StatusBadRequest, "Invalid payload type")
73+ }
74+
75+ pushPayload, ok := payload.Payload.(common.PushPayload)
76+ if !ok {
77+ return c.JSON(http.StatusBadRequest, "Invalid payload type")
78+ }
79+
80+ // Push payload contains Ports as a string, Image as string (image:tag), and the .tar image as a byte array
81+ // Validate the ports
82+ ports := pushPayload.Ports
83+ if ports == "" {
84+ return c.JSON(http.StatusBadRequest, "Ports cannot be empty")
85+ }
86+
87+ // Validate the target domain
88+ domain := pushPayload.TargetDomain
89+ if domain == "" {
90+ return c.JSON(http.StatusBadRequest, "Image cannot be empty")
91+ }
92+
93+ // Validate the image tar
94+ imageTar := pushPayload.Data
95+ if imageTar == nil {
96+ return c.JSON(http.StatusBadRequest, "Image tar cannot be empty")
97+ }
98+
99+ imageName := pushPayload.ImageName
100+ if imageName == "" {
101+ return c.JSON(http.StatusBadRequest, "Image name cannot be empty")
102+ }
103+
104+ // 1 - Import the image tar
105+ imageId, err := store.SaveImageToStorage(&a.Config, imageName, bytes.NewBuffer(imageTar))
106+ if err != nil {
107+ return c.JSON(http.StatusBadRequest, err.Error())
108+ }
109+ // 2 - Create the container with traefik labels
110+ fmt.Println("Image ID:", imageId)
111+ // 3 - Start the container
112+ // 4 - Return :
113+
114+ return c.JSON(http.StatusOK, "OK")
115+}
@@ -8,6 +8,7 @@ import (
8 8
9 "github.com/bnema/gordon/internal/common"9 "github.com/bnema/gordon/internal/common"
10 "github.com/bnema/gordon/internal/server"10 "github.com/bnema/gordon/internal/server"
11+ "github.com/bnema/gordon/pkg/store"
11 "github.com/labstack/echo/v4"12 "github.com/labstack/echo/v4"
12 )13 )
13 14
@@ -23,14 +24,12 @@ type InfoResponse struct {
23 24
24 func (info *InfoResponse) Populate(a *server.App) {25 func (info *InfoResponse) Populate(a *server.App) {
25 info.Uptime = a.GetUptime()26 info.Uptime = a.GetUptime()
26- info.Version = a.Config.GetVersion()27+ info.Version = a.GetVersionstring()
27 }28 }
28 29
29 // Handle GET on /api/ping endpoint30 // Handle GET on /api/ping endpoint
30 func GetInfos(c echo.Context, a *server.App) error {31 func GetInfos(c echo.Context, a *server.App) error {
31- fmt.Println("GET /api/ping")
32 body, _ := io.ReadAll(c.Request().Body)32 body, _ := io.ReadAll(c.Request().Body)
33- fmt.Println("Request Body:", string(body))
34 c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body33 c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body
35 34
36 payload := new(common.RequestPayload)35 payload := new(common.RequestPayload)
@@ -58,3 +57,59 @@ func GetInfos(c echo.Context, a *server.App) error {
58 return c.JSON(http.StatusOK, info)57 return c.JSON(http.StatusOK, info)
59 58
60 }59 }
60+
61+func PostPush(c echo.Context, a *server.App) error {
62+ body, _ := io.ReadAll(c.Request().Body)
63+ c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body
64+
65+ payload := new(common.RequestPayload)
66+ if err := c.Bind(payload); err != nil {
67+ fmt.Println("Bind Error:", err)
68+ return c.JSON(http.StatusBadRequest, err.Error())
69+ }
70+
71+ if payload.Type != "push" {
72+ return c.JSON(http.StatusBadRequest, "Invalid payload type")
73+ }
74+
75+ pushPayload, ok := payload.Payload.(common.PushPayload)
76+ if !ok {
77+ return c.JSON(http.StatusBadRequest, "Invalid payload type")
78+ }
79+
80+ // Push payload contains Ports as a string, Image as string (image:tag), and the .tar image as a byte array
81+ // Validate the ports
82+ ports := pushPayload.Ports
83+ if ports == "" {
84+ return c.JSON(http.StatusBadRequest, "Ports cannot be empty")
85+ }
86+
87+ // Validate the target domain
88+ domain := pushPayload.TargetDomain
89+ if domain == "" {
90+ return c.JSON(http.StatusBadRequest, "Image cannot be empty")
91+ }
92+
93+ // Validate the image tar
94+ imageTar := pushPayload.Data
95+ if imageTar == nil {
96+ return c.JSON(http.StatusBadRequest, "Image tar cannot be empty")
97+ }
98+
99+ imageName := pushPayload.ImageName
100+ if imageName == "" {
101+ return c.JSON(http.StatusBadRequest, "Image name cannot be empty")
102+ }
103+
104+ // 1 - Import the image tar
105+ imageId, err := store.SaveImageToStorage(&a.Config, imageName, bytes.NewBuffer(imageTar))
106+ if err != nil {
107+ return c.JSON(http.StatusBadRequest, err.Error())
108+ }
109+ // 2 - Create the container with traefik labels
110+ fmt.Println("Image ID:", imageId)
111+ // 3 - Start the container
112+ // 4 - Return :
113+
114+ return c.JSON(http.StatusOK, "OK")
115+}
M internal/httpserve/handler/oauth.go +2 -2
@@ -72,7 +72,7 @@ func StartOAuthGithub(c echo.Context, a *server.App) error {
7272
7373 //Initiate the Github OAuth flow
7474 clientID := os.Getenv("GITHUB_APP_ID")
75- redirectDomain := a.Config.GenerateOauthCallbackURL()
75+ redirectDomain := a.GenerateOauthCallbackURL()
7676 encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))
7777 // Redirect to Gordon's Proxy to grab the oauth access
7878 oauthURL := fmt.Sprintf(
@@ -101,7 +101,7 @@ func OAuthCallback(c echo.Context, a *server.App) error {
101101 a.DBTables.Sessions.AccessToken = accessToken
102102
103103 // Compare the state parameter with the redirect domain
104- redirectDomain := a.Config.GenerateOauthCallbackURL()
104+ redirectDomain := a.GenerateOauthCallbackURL()
105105 encodedRedirectDomain := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))
106106 if encodedState != encodedRedirectDomain {
107107 return c.String(http.StatusBadRequest, "invalid state parameter")
@@ -72,7 +72,7 @@ func StartOAuthGithub(c echo.Context, a *server.App) error {
72 72
73 //Initiate the Github OAuth flow73 //Initiate the Github OAuth flow
74 clientID := os.Getenv("GITHUB_APP_ID")74 clientID := os.Getenv("GITHUB_APP_ID")
75- redirectDomain := a.Config.GenerateOauthCallbackURL()75+ redirectDomain := a.GenerateOauthCallbackURL()
76 encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))76 encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))
77 // Redirect to Gordon's Proxy to grab the oauth access77 // Redirect to Gordon's Proxy to grab the oauth access
78 oauthURL := fmt.Sprintf(78 oauthURL := fmt.Sprintf(
@@ -101,7 +101,7 @@ func OAuthCallback(c echo.Context, a *server.App) error {
101 a.DBTables.Sessions.AccessToken = accessToken101 a.DBTables.Sessions.AccessToken = accessToken
102 102
103 // Compare the state parameter with the redirect domain103 // Compare the state parameter with the redirect domain
104- redirectDomain := a.Config.GenerateOauthCallbackURL()104+ redirectDomain := a.GenerateOauthCallbackURL()
105 encodedRedirectDomain := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))105 encodedRedirectDomain := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))
106 if encodedState != encodedRedirectDomain {106 if encodedState != encodedRedirectDomain {
107 return c.String(http.StatusBadRequest, "invalid state parameter")107 return c.String(http.StatusBadRequest, "invalid state parameter")
M internal/httpserve/handler/uploadcontainerimage.go +12 -1
@@ -2,11 +2,13 @@ package handler
22
33 import (
44 "bytes"
5+ "fmt"
56 "io"
67 "net/http"
78
89 "github.com/bnema/gordon/internal/server"
910 "github.com/bnema/gordon/internal/templating/render"
11+ "github.com/bnema/gordon/pkg/docker"
1012 "github.com/bnema/gordon/pkg/store"
1113 "github.com/labstack/echo/v4"
1214 )
@@ -56,9 +58,18 @@ func UploadImagePOSTHandler(c echo.Context, a *server.App) error {
5658 filename := header.Filename
5759
5860 // Save the image to the storage directory
59- _, err = store.SaveImageToStorage(&a.Config, filename, buf)
61+ saveInPath, err := store.SaveImageToStorage(&a.Config, filename, buf)
6062 if err != nil {
6163 return sendError(c, err)
6264 }
65+
66+ // Import the image into Docker
67+ imageId, err := docker.ImportImageToEngine(saveInPath)
68+ if err != nil {
69+ return fmt.Errorf("failed to import image to Docker: %v", err)
70+ }
71+
72+ fmt.Println("Image ID:", imageId)
73+
6374 return c.HTML(http.StatusOK, ActionSuccess(a))
6475 }
@@ -2,11 +2,13 @@ package handler
2 2
3 import (3 import (
4 "bytes"4 "bytes"
5+ "fmt"
5 "io"6 "io"
6 "net/http"7 "net/http"
7 8
8 "github.com/bnema/gordon/internal/server"9 "github.com/bnema/gordon/internal/server"
9 "github.com/bnema/gordon/internal/templating/render"10 "github.com/bnema/gordon/internal/templating/render"
11+ "github.com/bnema/gordon/pkg/docker"
10 "github.com/bnema/gordon/pkg/store"12 "github.com/bnema/gordon/pkg/store"
11 "github.com/labstack/echo/v4"13 "github.com/labstack/echo/v4"
12 )14 )
@@ -56,9 +58,18 @@ func UploadImagePOSTHandler(c echo.Context, a *server.App) error {
56 filename := header.Filename58 filename := header.Filename
57 59
58 // Save the image to the storage directory60 // Save the image to the storage directory
59- _, err = store.SaveImageToStorage(&a.Config, filename, buf)61+ saveInPath, err := store.SaveImageToStorage(&a.Config, filename, buf)
60 if err != nil {62 if err != nil {
61 return sendError(c, err)63 return sendError(c, err)
62 }64 }
65+
66+ // Import the image into Docker
67+ imageId, err := docker.ImportImageToEngine(saveInPath)
68+ if err != nil {
69+ return fmt.Errorf("failed to import image to Docker: %v", err)
70+ }
71+
72+ fmt.Println("Image ID:", imageId)
73+
63 return c.HTML(http.StatusOK, ActionSuccess(a))74 return c.HTML(http.StatusOK, ActionSuccess(a))
64 }75 }
M internal/server/appserver.go +8 -109
@@ -4,13 +4,11 @@ import (
44 "database/sql"
55 "fmt"
66 "io/fs"
7- "os"
87 "time"
98
9+ "github.com/bnema/gordon/internal/common"
1010 "github.com/bnema/gordon/internal/db"
11- "github.com/bnema/gordon/pkg/docker"
1211 _ "github.com/joho/godotenv/autoload"
13- "gopkg.in/yaml.v3"
1412 )
1513
1614 var (
@@ -25,43 +23,11 @@ type App struct {
2523 DBFilename string
2624 DBPath string
2725 InitialChecksum string
28- Config Config
26+ Config common.Config
2927 DB *sql.DB
3028 DBTables DBTables
3129 StartTime time.Time
3230 }
33-type Config struct {
34- General GeneralConfig `yaml:"General"`
35- Http HttpConfig `yaml:"Http"`
36- Admin AdminConfig `yaml:"Admin"`
37- ContainerEngine ContainerEngineConfig `yaml:"ContainerEngine"`
38-}
39-
40-type GeneralConfig struct {
41- RunEnv string // come from env
42- BuildDir string // come from env
43- BuildVersion string // come from env
44- StorageDir string `yaml:"storageDir"` // = buildir/storage
45- Token string `yaml:"token"`
46-}
47-
48-type HttpConfig struct {
49- Port int `yaml:"port"`
50- TopDomain string `yaml:"topDomain"`
51- SubDomain string `yaml:"subDomain"`
52- BackendURL string `yaml:"backendURL"`
53-}
54-
55-type AdminConfig struct {
56- Path string `yaml:"path"`
57-}
58-
59-type ContainerEngineConfig struct {
60- Sock string `yaml:"dockersock"`
61- PodmanSock string `yaml:"podmansock"`
62- Podman bool `yaml:"podman"`
63- Network string `yaml:"network"`
64-}
6531
6632 type DBTables struct {
6733 User db.User `sql:"user"`
@@ -70,50 +36,10 @@ type DBTables struct {
7036 Provider db.Provider `sql:"provider"`
7137 }
7238
73-func LoadConfig(config *Config) (*Config, error) {
74- // Load env elements
75- config.General.BuildVersion = os.Getenv("BUILD_VERSION")
76- config.General.RunEnv = os.Getenv("RUN_ENV")
77- config.General.BuildDir = os.Getenv("BUILD_DIR")
78-
79- // if RUN_ENV is not set, assume "prod" and config dir is the current dir
80- if config.General.RunEnv == "" {
81- config.General.RunEnv = "prod"
82- config.General.BuildDir = "."
83- }
84- // Load config file
85- configFile := fmt.Sprintf("%s/config.yml", config.General.BuildDir)
86- configData, err := os.ReadFile(configFile)
87- if err != nil {
88- return config, err
89- }
90-
91- err = yaml.Unmarshal(configData, &config)
92- if err != nil {
93- return config, err
94- }
95-
96- return config, nil
97-}
98-
99-// NewDockerConfig creates and returns a new Docker client configuration
100-func (config *Config) NewDockerConfig() *docker.Config {
101- if config.ContainerEngine.Podman {
102- return &docker.Config{
103- Sock: config.ContainerEngine.PodmanSock,
104- PodmanEnable: true,
105- }
106- }
107- return &docker.Config{
108- Sock: config.ContainerEngine.Sock,
109- PodmanEnable: false,
110- }
111-}
112-
11339 // GenerateOauthCallbackURL generates the OAuth callback URL
114-func (config *Config) GenerateOauthCallbackURL() string {
40+func (a *App) GenerateOauthCallbackURL() string {
11541 var scheme, port string
116-
42+ config := a.Config
11743 if config.General.RunEnv == "dev" {
11844 scheme = "http"
11945 port = fmt.Sprintf(":%d", config.Http.Port)
@@ -130,9 +56,9 @@ func (config *Config) GenerateOauthCallbackURL() string {
13056 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)
13157 }
13258
133-func (config *Config) GenerateAPIURL() string {
59+func (a *App) GenerateAPIURL() string {
13460 var scheme, port string
135-
61+ config := a.Config
13662 if config.General.RunEnv == "dev" {
13763 scheme = "http"
13864 port = fmt.Sprintf(":%d", config.Http.Port)
@@ -149,39 +75,12 @@ func (config *Config) GenerateAPIURL() string {
14975 return fmt.Sprintf("%s://%s%s/api", scheme, domain, port)
15076 }
15177
152-// UpdateConfig updates the config file
153-func (config *Config) UpdateConfig() error {
154- // Marshal the config struct into YAML
155- data, err := yaml.Marshal(config)
156- if err != nil {
157- return fmt.Errorf("failed to marshal config: %v", err)
158- }
159-
160- // Write the data to the config file (located in the build directory)
161- err = os.WriteFile(fmt.Sprintf("%s/config.yml", config.General.BuildDir), data, 0644)
162- if err != nil {
163- return fmt.Errorf("failed to write to config file: %v", err)
164- }
165-
166- return nil
167-}
168-
169-func (config *Config) GetToken() (string, error) {
170- token := config.General.Token
171- if token == "" {
172- return "", fmt.Errorf("no token found in config.yml")
173- }
174-
175- return token, nil
176-
177-}
178-
17978 func (a *App) GetUptime() string {
18079 uptime := time.Since(a.StartTime)
18180 return uptime.String()
18281 }
18382
18483 // GetVersion returns the version of the app
185-func (config *Config) GetVersion() string {
186- return config.General.BuildVersion
84+func (a *App) GetVersionstring() string {
85+ return fmt.Sprint(a.Config.General.BuildVersion)
18786 }
@@ -4,13 +4,11 @@ import (
4 "database/sql"4 "database/sql"
5 "fmt"5 "fmt"
6 "io/fs"6 "io/fs"
7- "os"
8 "time"7 "time"
9 8
9+ "github.com/bnema/gordon/internal/common"
10 "github.com/bnema/gordon/internal/db"10 "github.com/bnema/gordon/internal/db"
11- "github.com/bnema/gordon/pkg/docker"
12 _ "github.com/joho/godotenv/autoload"11 _ "github.com/joho/godotenv/autoload"
13- "gopkg.in/yaml.v3"
14 )12 )
15 13
16 var (14 var (
@@ -25,43 +23,11 @@ type App struct {
25 DBFilename string23 DBFilename string
26 DBPath string24 DBPath string
27 InitialChecksum string25 InitialChecksum string
28- Config Config26+ Config common.Config
29 DB *sql.DB27 DB *sql.DB
30 DBTables DBTables28 DBTables DBTables
31 StartTime time.Time29 StartTime time.Time
32 }30 }
33-type Config struct {
34- General GeneralConfig `yaml:"General"`
35- Http HttpConfig `yaml:"Http"`
36- Admin AdminConfig `yaml:"Admin"`
37- ContainerEngine ContainerEngineConfig `yaml:"ContainerEngine"`
38-}
39-
40-type GeneralConfig struct {
41- RunEnv string // come from env
42- BuildDir string // come from env
43- BuildVersion string // come from env
44- StorageDir string `yaml:"storageDir"` // = buildir/storage
45- Token string `yaml:"token"`
46-}
47-
48-type HttpConfig struct {
49- Port int `yaml:"port"`
50- TopDomain string `yaml:"topDomain"`
51- SubDomain string `yaml:"subDomain"`
52- BackendURL string `yaml:"backendURL"`
53-}
54-
55-type AdminConfig struct {
56- Path string `yaml:"path"`
57-}
58-
59-type ContainerEngineConfig struct {
60- Sock string `yaml:"dockersock"`
61- PodmanSock string `yaml:"podmansock"`
62- Podman bool `yaml:"podman"`
63- Network string `yaml:"network"`
64-}
65 31
66 type DBTables struct {32 type DBTables struct {
67 User db.User `sql:"user"`33 User db.User `sql:"user"`
@@ -70,50 +36,10 @@ type DBTables struct {
70 Provider db.Provider `sql:"provider"`36 Provider db.Provider `sql:"provider"`
71 }37 }
72 38
73-func LoadConfig(config *Config) (*Config, error) {
74- // Load env elements
75- config.General.BuildVersion = os.Getenv("BUILD_VERSION")
76- config.General.RunEnv = os.Getenv("RUN_ENV")
77- config.General.BuildDir = os.Getenv("BUILD_DIR")
78-
79- // if RUN_ENV is not set, assume "prod" and config dir is the current dir
80- if config.General.RunEnv == "" {
81- config.General.RunEnv = "prod"
82- config.General.BuildDir = "."
83- }
84- // Load config file
85- configFile := fmt.Sprintf("%s/config.yml", config.General.BuildDir)
86- configData, err := os.ReadFile(configFile)
87- if err != nil {
88- return config, err
89- }
90-
91- err = yaml.Unmarshal(configData, &config)
92- if err != nil {
93- return config, err
94- }
95-
96- return config, nil
97-}
98-
99-// NewDockerConfig creates and returns a new Docker client configuration
100-func (config *Config) NewDockerConfig() *docker.Config {
101- if config.ContainerEngine.Podman {
102- return &docker.Config{
103- Sock: config.ContainerEngine.PodmanSock,
104- PodmanEnable: true,
105- }
106- }
107- return &docker.Config{
108- Sock: config.ContainerEngine.Sock,
109- PodmanEnable: false,
110- }
111-}
112-
113 // GenerateOauthCallbackURL generates the OAuth callback URL39 // GenerateOauthCallbackURL generates the OAuth callback URL
114-func (config *Config) GenerateOauthCallbackURL() string {40+func (a *App) GenerateOauthCallbackURL() string {
115 var scheme, port string41 var scheme, port string
116-42+ config := a.Config
117 if config.General.RunEnv == "dev" {43 if config.General.RunEnv == "dev" {
118 scheme = "http"44 scheme = "http"
119 port = fmt.Sprintf(":%d", config.Http.Port)45 port = fmt.Sprintf(":%d", config.Http.Port)
@@ -130,9 +56,9 @@ func (config *Config) GenerateOauthCallbackURL() string {
130 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)56 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)
131 }57 }
132 58
133-func (config *Config) GenerateAPIURL() string {59+func (a *App) GenerateAPIURL() string {
134 var scheme, port string60 var scheme, port string
135-61+ config := a.Config
136 if config.General.RunEnv == "dev" {62 if config.General.RunEnv == "dev" {
137 scheme = "http"63 scheme = "http"
138 port = fmt.Sprintf(":%d", config.Http.Port)64 port = fmt.Sprintf(":%d", config.Http.Port)
@@ -149,39 +75,12 @@ func (config *Config) GenerateAPIURL() string {
149 return fmt.Sprintf("%s://%s%s/api", scheme, domain, port)75 return fmt.Sprintf("%s://%s%s/api", scheme, domain, port)
150 }76 }
151 77
152-// UpdateConfig updates the config file
153-func (config *Config) UpdateConfig() error {
154- // Marshal the config struct into YAML
155- data, err := yaml.Marshal(config)
156- if err != nil {
157- return fmt.Errorf("failed to marshal config: %v", err)
158- }
159-
160- // Write the data to the config file (located in the build directory)
161- err = os.WriteFile(fmt.Sprintf("%s/config.yml", config.General.BuildDir), data, 0644)
162- if err != nil {
163- return fmt.Errorf("failed to write to config file: %v", err)
164- }
165-
166- return nil
167-}
168-
169-func (config *Config) GetToken() (string, error) {
170- token := config.General.Token
171- if token == "" {
172- return "", fmt.Errorf("no token found in config.yml")
173- }
174-
175- return token, nil
176-
177-}
178-
179 func (a *App) GetUptime() string {78 func (a *App) GetUptime() string {
180 uptime := time.Since(a.StartTime)79 uptime := time.Since(a.StartTime)
181 return uptime.String()80 return uptime.String()
182 }81 }
183 82
184 // GetVersion returns the version of the app83 // GetVersion returns the version of the app
185-func (config *Config) GetVersion() string {84+func (a *App) GetVersionstring() string {
186- return config.General.BuildVersion85+ return fmt.Sprint(a.Config.General.BuildVersion)
187 }86 }
M internal/server/newserver.go +5 -4
@@ -6,14 +6,15 @@ import (
66 "log"
77 "time"
88
9+ "github.com/bnema/gordon/internal/common"
910 "github.com/bnema/gordon/internal/templating"
1011 "github.com/bnema/gordon/internal/webui"
1112 )
1213
1314 func NewServerApp() (*App, error) {
1415 // Initialize AppConfig
15- config := &Config{}
16- _, err := LoadConfig(config)
16+ config := common.Config{}
17+ _, err := config.LoadConfig()
1718 if err != nil {
1819 log.Fatalf("Failed to load configuration: %v", err)
1920 }
@@ -42,10 +43,10 @@ func NewServerApp() (*App, error) {
4243 LocYML: bytes,
4344 DBDir: DBDir,
4445 DBFilename: DBFilename,
45- Config: *config,
46+ Config: config,
4647 StartTime: time.Now(),
4748 }
4849
49- OauthCallbackURL = config.GenerateOauthCallbackURL()
50+ OauthCallbackURL = a.GenerateOauthCallbackURL()
5051 return a, nil
5152 }
@@ -6,14 +6,15 @@ import (
6 "log"6 "log"
7 "time"7 "time"
8 8
9+ "github.com/bnema/gordon/internal/common"
9 "github.com/bnema/gordon/internal/templating"10 "github.com/bnema/gordon/internal/templating"
10 "github.com/bnema/gordon/internal/webui"11 "github.com/bnema/gordon/internal/webui"
11 )12 )
12 13
13 func NewServerApp() (*App, error) {14 func NewServerApp() (*App, error) {
14 // Initialize AppConfig15 // Initialize AppConfig
15- config := &Config{}16+ config := common.Config{}
16- _, err := LoadConfig(config)17+ _, err := config.LoadConfig()
17 if err != nil {18 if err != nil {
18 log.Fatalf("Failed to load configuration: %v", err)19 log.Fatalf("Failed to load configuration: %v", err)
19 }20 }
@@ -42,10 +43,10 @@ func NewServerApp() (*App, error) {
42 LocYML: bytes,43 LocYML: bytes,
43 DBDir: DBDir,44 DBDir: DBDir,
44 DBFilename: DBFilename,45 DBFilename: DBFilename,
45- Config: *config,46+ Config: config,
46 StartTime: time.Now(),47 StartTime: time.Now(),
47 }48 }
48 49
49- OauthCallbackURL = config.GenerateOauthCallbackURL()50+ OauthCallbackURL = a.GenerateOauthCallbackURL()
50 return a, nil51 return a, nil
51 }52 }
M internal/server/tokeninit.go +1 -1
@@ -33,7 +33,7 @@ func HandleNewTokenInitialization(a *App) (string, error) {
3333
3434 // Store the token in the config file
3535 a.Config.General.Token = token
36- err = a.Config.UpdateConfig()
36+ err = a.Config.SaveConfig()
3737 if err != nil {
3838 return "", fmt.Errorf("failed to save config: %v", err)
3939 }
@@ -33,7 +33,7 @@ func HandleNewTokenInitialization(a *App) (string, error) {
33 33
34 // Store the token in the config file34 // Store the token in the config file
35 a.Config.General.Token = token35 a.Config.General.Token = token
36- err = a.Config.UpdateConfig()36+ err = a.Config.SaveConfig()
37 if err != nil {37 if err != nil {
38 return "", fmt.Errorf("failed to save config: %v", err)38 return "", fmt.Errorf("failed to save config: %v", err)
39 }39 }
M pkg/docker/images.go +25 -0
@@ -1,7 +1,9 @@
11 package docker
22
33 import (
4+ "bytes"
45 "context"
6+ "io"
57 "os"
68
79 "github.com/docker/docker/api/types"
@@ -95,6 +97,29 @@ func ImportImageToEngine(imagePath string) (string, error) {
9597 return imageID, nil
9698 }
9799
100+// ExportImageFromEngine exports an image from the Docker engine and returns it as a byte array
101+func ExportImageFromEngine(imageName string) ([]byte, error) {
102+ // we check if the image exists
103+ _, err := GetImageID(imageName)
104+ if err != nil {
105+ return nil, err
106+ }
107+
108+ image, err := dockerCli.ImageSave(context.Background(), []string{imageName})
109+ if err != nil {
110+ return nil, err
111+ }
112+ defer image.Close()
113+
114+ var buf bytes.Buffer
115+ _, err = io.Copy(&buf, image)
116+ if err != nil {
117+ return nil, err
118+ }
119+
120+ return buf.Bytes(), nil
121+}
122+
98123 // From an ID, get the all the information about the image
99124 func GetImageInfo(imageID string) (*types.ImageInspect, error) {
100125 // Get the image information using the Docker client
@@ -1,7 +1,9 @@
1 package docker1 package docker
2 2
3 import (3 import (
4+ "bytes"
4 "context"5 "context"
6+ "io"
5 "os"7 "os"
6 8
7 "github.com/docker/docker/api/types"9 "github.com/docker/docker/api/types"
@@ -95,6 +97,29 @@ func ImportImageToEngine(imagePath string) (string, error) {
95 return imageID, nil97 return imageID, nil
96 }98 }
97 99
100+// ExportImageFromEngine exports an image from the Docker engine and returns it as a byte array
101+func ExportImageFromEngine(imageName string) ([]byte, error) {
102+ // we check if the image exists
103+ _, err := GetImageID(imageName)
104+ if err != nil {
105+ return nil, err
106+ }
107+
108+ image, err := dockerCli.ImageSave(context.Background(), []string{imageName})
109+ if err != nil {
110+ return nil, err
111+ }
112+ defer image.Close()
113+
114+ var buf bytes.Buffer
115+ _, err = io.Copy(&buf, image)
116+ if err != nil {
117+ return nil, err
118+ }
119+
120+ return buf.Bytes(), nil
121+}
122+
98 // From an ID, get the all the information about the image123 // From an ID, get the all the information about the image
99 func GetImageInfo(imageID string) (*types.ImageInspect, error) {124 func GetImageInfo(imageID string) (*types.ImageInspect, error) {
100 // Get the image information using the Docker client125 // Get the image information using the Docker client
M pkg/parser/yaml.go +16 -23
@@ -4,34 +4,12 @@ import (
44 "fmt"
55 "io"
66 "io/fs"
7+ "os"
78 "path/filepath"
89
910 "gopkg.in/yaml.v3"
1011 )
1112
12-// // OpenYamlFile opens a YAML file and unmarshals it into the out interface
13-// func OpenYamlFile(fs fs.FS, filename string, out interface{}) error {
14-// // Open the YAML file
15-// file, err := fs.Open(filename)
16-// if err != nil {
17-// return fmt.Errorf("failed to open %s: %w", filename, err)
18-// }
19-// defer file.Close()
20-
21-// // Read the content of the file
22-// content, err := io.ReadAll(file)
23-// if err != nil {
24-// return fmt.Errorf("failed to read %s: %w", filename, err)
25-// }
26-
27-// // Unmarshal the YAML content into the out interface
28-// if err := yaml.Unmarshal(content, out); err != nil {
29-// return fmt.Errorf("failed to unmarshal %s: %w", filename, err)
30-// }
31-
32-// return nil
33-// }
34-
3513 // OpenYamlFile opens a YAML file and unmarshals it into the out interface
3614 func ParseYAMLFile(fs fs.FS, filename string, out interface{}, dir ...string) error {
3715 // Construct the full path of the YAML file
@@ -60,3 +38,18 @@ func ParseYAMLFile(fs fs.FS, filename string, out interface{}, dir ...string) er
6038
6139 return nil
6240 }
41+
42+func WriteYAMLFile(filename string, in interface{}) error {
43+ // Marshal the in interface to YAML
44+ data, err := yaml.Marshal(in)
45+ if err != nil {
46+ return fmt.Errorf("failed to marshal to YAML: %w", err)
47+ }
48+
49+ // Write the YAML data to the file
50+ err = os.WriteFile(filename, data, 0644)
51+ if err != nil {
52+ return fmt.Errorf("failed to write to %s: %w", filename, err)
53+ }
54+ return nil
55+}
@@ -4,34 +4,12 @@ import (
4 "fmt"4 "fmt"
5 "io"5 "io"
6 "io/fs"6 "io/fs"
7+ "os"
7 "path/filepath"8 "path/filepath"
8 9
9 "gopkg.in/yaml.v3"10 "gopkg.in/yaml.v3"
10 )11 )
11 12
12-// // OpenYamlFile opens a YAML file and unmarshals it into the out interface
13-// func OpenYamlFile(fs fs.FS, filename string, out interface{}) error {
14-// // Open the YAML file
15-// file, err := fs.Open(filename)
16-// if err != nil {
17-// return fmt.Errorf("failed to open %s: %w", filename, err)
18-// }
19-// defer file.Close()
20-
21-// // Read the content of the file
22-// content, err := io.ReadAll(file)
23-// if err != nil {
24-// return fmt.Errorf("failed to read %s: %w", filename, err)
25-// }
26-
27-// // Unmarshal the YAML content into the out interface
28-// if err := yaml.Unmarshal(content, out); err != nil {
29-// return fmt.Errorf("failed to unmarshal %s: %w", filename, err)
30-// }
31-
32-// return nil
33-// }
34-
35 // OpenYamlFile opens a YAML file and unmarshals it into the out interface13 // OpenYamlFile opens a YAML file and unmarshals it into the out interface
36 func ParseYAMLFile(fs fs.FS, filename string, out interface{}, dir ...string) error {14 func ParseYAMLFile(fs fs.FS, filename string, out interface{}, dir ...string) error {
37 // Construct the full path of the YAML file15 // Construct the full path of the YAML file
@@ -60,3 +38,18 @@ func ParseYAMLFile(fs fs.FS, filename string, out interface{}, dir ...string) er
60 38
61 return nil39 return nil
62 }40 }
41+
42+func WriteYAMLFile(filename string, in interface{}) error {
43+ // Marshal the in interface to YAML
44+ data, err := yaml.Marshal(in)
45+ if err != nil {
46+ return fmt.Errorf("failed to marshal to YAML: %w", err)
47+ }
48+
49+ // Write the YAML data to the file
50+ err = os.WriteFile(filename, data, 0644)
51+ if err != nil {
52+ return fmt.Errorf("failed to write to %s: %w", filename, err)
53+ }
54+ return nil
55+}
M pkg/store/storeimages.go +4 -11
@@ -6,8 +6,7 @@ import (
66 "os"
77 "path/filepath"
88
9- "github.com/bnema/gordon/internal/server"
10- "github.com/bnema/gordon/pkg/docker"
9+ "github.com/bnema/gordon/internal/common"
1110 "github.com/docker/docker/api/types"
1211 )
1312
@@ -17,13 +16,13 @@ type StorageConfig struct {
1716 Images []types.ImageSummary
1817 }
1918
20-func NewStorageConfig(config *server.Config) *StorageConfig {
19+func NewStorageConfig(config *common.Config) *StorageConfig {
2120 return &StorageConfig{
2221 StorageDir: config.General.StorageDir,
2322 }
2423 }
2524
26-func SaveImageToStorage(config *server.Config, filename string, buf io.Reader) (string, error) {
25+func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (string, error) {
2726 // Check if the folder exist if not create it
2827 if _, err := os.Stat(config.General.StorageDir); os.IsNotExist(err) {
2928 err := os.MkdirAll(config.General.StorageDir, 0755)
@@ -52,13 +51,7 @@ func SaveImageToStorage(config *server.Config, filename string, buf io.Reader) (
5251 return "", fmt.Errorf("file does not exist: %v", err)
5352 }
5453
55- // Import the image into Docker
56- imageId, err := docker.ImportImageToEngine(saveInPath)
57- if err != nil {
58- return "", fmt.Errorf("failed to import image to Docker: %v", err)
59- }
60-
61- return imageId, nil
54+ return saveInPath, nil
6255 }
6356
6457 func (sc *StorageConfig) DeleteImageFromStorage(imageId string) error {
@@ -6,8 +6,7 @@ import (
6 "os"6 "os"
7 "path/filepath"7 "path/filepath"
8 8
9- "github.com/bnema/gordon/internal/server"9+ "github.com/bnema/gordon/internal/common"
10- "github.com/bnema/gordon/pkg/docker"
11 "github.com/docker/docker/api/types"10 "github.com/docker/docker/api/types"
12 )11 )
13 12
@@ -17,13 +16,13 @@ type StorageConfig struct {
17 Images []types.ImageSummary16 Images []types.ImageSummary
18 }17 }
19 18
20-func NewStorageConfig(config *server.Config) *StorageConfig {19+func NewStorageConfig(config *common.Config) *StorageConfig {
21 return &StorageConfig{20 return &StorageConfig{
22 StorageDir: config.General.StorageDir,21 StorageDir: config.General.StorageDir,
23 }22 }
24 }23 }
25 24
26-func SaveImageToStorage(config *server.Config, filename string, buf io.Reader) (string, error) {25+func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (string, error) {
27 // Check if the folder exist if not create it26 // Check if the folder exist if not create it
28 if _, err := os.Stat(config.General.StorageDir); os.IsNotExist(err) {27 if _, err := os.Stat(config.General.StorageDir); os.IsNotExist(err) {
29 err := os.MkdirAll(config.General.StorageDir, 0755)28 err := os.MkdirAll(config.General.StorageDir, 0755)
@@ -52,13 +51,7 @@ func SaveImageToStorage(config *server.Config, filename string, buf io.Reader) (
52 return "", fmt.Errorf("file does not exist: %v", err)51 return "", fmt.Errorf("file does not exist: %v", err)
53 }52 }
54 53
55- // Import the image into Docker54+ return saveInPath, nil
56- imageId, err := docker.ImportImageToEngine(saveInPath)
57- if err != nil {
58- return "", fmt.Errorf("failed to import image to Docker: %v", err)
59- }
60-
61- return imageId, nil
62 }55 }
63 56
64 func (sc *StorageConfig) DeleteImageFromStorage(imageId string) error {57 func (sc *StorageConfig) DeleteImageFromStorage(imageId string) error {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close