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
- 4f623708286c0d0cb93e019d448c040bc7281bf3
- author
- bnema · · 2023-10-25 14:45 UTC
- parent
- a53577b
- signature
- unsigned
25 files · +446 -595
M .gitignore +2 -2
| @@ -2,8 +2,8 @@ | |||
| 2 | 2 | node_modules/ | |
| 3 | 3 | *tmp/ | |
| 4 | 4 | tmp/* | |
| 5 | - | gordon.yml | |
| 5 | + | config.yml | |
| 6 | 6 | bun.lockb | |
| 7 | 7 | *.log | |
| 8 | 8 | *.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.yml | 5 | + | config.yml |
| 6 | bun.lockb | 6 | bun.lockb | ||
| 7 | *.log | 7 | *.log | ||
| 8 | *.txt | 8 | *.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 ( | |||
| 5 | 5 | ||
| 6 | 6 | "github.com/bnema/gordon/internal/cli" | |
| 7 | 7 | "github.com/bnema/gordon/internal/cli/cmd" | |
| 8 | + | "github.com/bnema/gordon/internal/common" | |
| 8 | 9 | "github.com/bnema/gordon/internal/server" | |
| 9 | 10 | "github.com/spf13/cobra" | |
| 10 | 11 | ) | |
| @@ -14,6 +15,7 @@ var rootCmd = &cobra.Command{Use: "gordon"} | |||
| 14 | 15 | func InitializeCommands(a *cli.App, s *server.App) { | |
| 15 | 16 | rootCmd.AddCommand(cmd.NewServeCommand(s)) | |
| 16 | 17 | rootCmd.AddCommand(cmd.NewPingCommand(a)) | |
| 18 | + | rootCmd.AddCommand(cmd.NewPushCommand(a)) | |
| 17 | 19 | } | |
| 18 | 20 | ||
| 19 | 21 | func Execute(a *cli.App, s *server.App) { | |
| @@ -31,5 +33,7 @@ func main() { | |||
| 31 | 33 | fmt.Println("Error initializing app:", err) | |
| 32 | 34 | } | |
| 33 | 35 | ||
| 36 | + | common.DockerInit(&s.Config.ContainerEngine) | |
| 37 | + | ||
| 34 | 38 | Execute(a, s) | |
| 35 | 39 | } | |
| @@ -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 @@ | |||
| 1 | 1 | 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 | 13 | // the push command is used to push a container image to gordon's endpoint | |
| 4 | 14 | ||
| 5 | 15 | // Pseudo code of the steps for the push command | |
| @@ -13,3 +23,50 @@ package cmd | |||
| 13 | 23 | // 4. Send the request to the backend | |
| 14 | 24 | ||
| 15 | 25 | // 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 cmd | 1 | 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 endpoint | 13 | // 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 command | 15 | // Pseudo code of the steps for the push command | ||
| @@ -13,3 +23,50 @@ package cmd | |||||
| 13 | // 4. Send the request to the backend | 23 | // 4. Send the request to the backend | ||
| 14 | 24 | ||||
| 15 | // 5. If the response is 200, print the success message | 25 | // 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 ( | |||
| 8 | 8 | "github.com/spf13/cobra" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | - | // execute cmd/srv/main.go main function | |
| 12 | - | ||
| 11 | + | // NewServeCommand creates a new serve command | |
| 13 | 12 | 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, | |
| 17 | 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 | } | |
| @@ -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 function | 11 | + | // 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 ( | |||
| 9 | 9 | ||
| 10 | 10 | "github.com/bnema/gordon/internal/httpserve" | |
| 11 | 11 | "github.com/bnema/gordon/internal/server" | |
| 12 | - | "github.com/bnema/gordon/pkg/docker" | |
| 13 | 12 | "github.com/labstack/echo/v4" | |
| 14 | 13 | ) | |
| 15 | 14 | ||
| 16 | 15 | // 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 | 18 | _, err := server.InitializeDB(a) | |
| 20 | 19 | if err != nil { | |
| 21 | 20 | log.Fatal("Failed to initialize database:", err) | |
| @@ -26,12 +25,6 @@ func StartServer(a *server.App) error { | |||
| 26 | 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 | 28 | // Setup a channel to capture termination signals | |
| 36 | 29 | sigs := make(chan os.Signal, 1) | |
| 37 | 30 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | |
| @@ -47,8 +40,8 @@ func StartServer(a *server.App) error { | |||
| 47 | 40 | e.HidePort = true | |
| 48 | 41 | e = httpserve.RegisterRoutes(e, a) | |
| 49 | 42 | ||
| 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 { | |
| 52 | 45 | log.Fatal("Server error:", err) | |
| 53 | 46 | } | |
| 54 | 47 | ||
| @@ -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 function | 15 | // 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 signals | 28 | // 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 = true | 40 | 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 | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 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 | 13 | // NewClientApp initializes a new App with configuration. | |
| 15 | 14 | func NewClientApp() (*App, error) { | |
| 16 | - | config := &Config{} | |
| 17 | - | config, err := config.LoadClientConfig() | |
| 15 | + | // Initialize AppConfig | |
| 16 | + | config := common.Config{} | |
| 17 | + | _, err := config.LoadConfig() | |
| 18 | 18 | if err != nil { | |
| 19 | 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. | |
| 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, | |
| 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. | |
| 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 | |
| 117 | 28 | } | |
| @@ -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 { | |||
| 50 | 50 | return "ping" | |
| 51 | 51 | } | |
| 52 | 52 | ||
| 53 | - | type PushPayload struct { | |
| 54 | - | Data []byte `json:"data"` | |
| 55 | - | } | |
| 56 | - | ||
| 57 | 53 | func (p PushPayload) GetType() string { | |
| 58 | 54 | return "push" | |
| 59 | 55 | } | |
| @@ -65,3 +61,15 @@ func NewPingPayload(data map[string]interface{}) (PingPayload, error) { | |||
| 65 | 61 | } | |
| 66 | 62 | 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 | + | } | |
| @@ -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}, nil | 62 | 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 ( | |||
| 8 | 8 | ||
| 9 | 9 | "github.com/bnema/gordon/internal/common" | |
| 10 | 10 | "github.com/bnema/gordon/internal/server" | |
| 11 | + | "github.com/bnema/gordon/pkg/store" | |
| 11 | 12 | "github.com/labstack/echo/v4" | |
| 12 | 13 | ) | |
| 13 | 14 | ||
| @@ -23,14 +24,12 @@ type InfoResponse struct { | |||
| 23 | 24 | ||
| 24 | 25 | func (info *InfoResponse) Populate(a *server.App) { | |
| 25 | 26 | info.Uptime = a.GetUptime() | |
| 26 | - | info.Version = a.Config.GetVersion() | |
| 27 | + | info.Version = a.GetVersionstring() | |
| 27 | 28 | } | |
| 28 | 29 | ||
| 29 | 30 | // Handle GET on /api/ping endpoint | |
| 30 | 31 | func GetInfos(c echo.Context, a *server.App) error { | |
| 31 | - | fmt.Println("GET /api/ping") | |
| 32 | 32 | body, _ := io.ReadAll(c.Request().Body) | |
| 33 | - | fmt.Println("Request Body:", string(body)) | |
| 34 | 33 | c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body | |
| 35 | 34 | ||
| 36 | 35 | payload := new(common.RequestPayload) | |
| @@ -58,3 +57,59 @@ func GetInfos(c echo.Context, a *server.App) error { | |||
| 58 | 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 | + | } | |
| @@ -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 endpoint | 30 | // 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 body | 33 | 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 { | |||
| 72 | 72 | ||
| 73 | 73 | //Initiate the Github OAuth flow | |
| 74 | 74 | clientID := os.Getenv("GITHUB_APP_ID") | |
| 75 | - | redirectDomain := a.Config.GenerateOauthCallbackURL() | |
| 75 | + | redirectDomain := a.GenerateOauthCallbackURL() | |
| 76 | 76 | encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain)) | |
| 77 | 77 | // Redirect to Gordon's Proxy to grab the oauth access | |
| 78 | 78 | oauthURL := fmt.Sprintf( | |
| @@ -101,7 +101,7 @@ func OAuthCallback(c echo.Context, a *server.App) error { | |||
| 101 | 101 | a.DBTables.Sessions.AccessToken = accessToken | |
| 102 | 102 | ||
| 103 | 103 | // Compare the state parameter with the redirect domain | |
| 104 | - | redirectDomain := a.Config.GenerateOauthCallbackURL() | |
| 104 | + | redirectDomain := a.GenerateOauthCallbackURL() | |
| 105 | 105 | encodedRedirectDomain := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain)) | |
| 106 | 106 | if encodedState != encodedRedirectDomain { | |
| 107 | 107 | 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 flow | 73 | //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 access | 77 | // 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 = accessToken | 101 | a.DBTables.Sessions.AccessToken = accessToken | ||
| 102 | 102 | ||||
| 103 | // Compare the state parameter with the redirect domain | 103 | // 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 | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "bytes" | |
| 5 | + | "fmt" | |
| 5 | 6 | "io" | |
| 6 | 7 | "net/http" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/gordon/internal/server" | |
| 9 | 10 | "github.com/bnema/gordon/internal/templating/render" | |
| 11 | + | "github.com/bnema/gordon/pkg/docker" | |
| 10 | 12 | "github.com/bnema/gordon/pkg/store" | |
| 11 | 13 | "github.com/labstack/echo/v4" | |
| 12 | 14 | ) | |
| @@ -56,9 +58,18 @@ func UploadImagePOSTHandler(c echo.Context, a *server.App) error { | |||
| 56 | 58 | filename := header.Filename | |
| 57 | 59 | ||
| 58 | 60 | // 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 | 62 | if err != nil { | |
| 61 | 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 | 74 | return c.HTML(http.StatusOK, ActionSuccess(a)) | |
| 64 | 75 | } | |
| @@ -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.Filename | 58 | filename := header.Filename | ||
| 57 | 59 | ||||
| 58 | // Save the image to the storage directory | 60 | // 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 ( | |||
| 4 | 4 | "database/sql" | |
| 5 | 5 | "fmt" | |
| 6 | 6 | "io/fs" | |
| 7 | - | "os" | |
| 8 | 7 | "time" | |
| 9 | 8 | ||
| 9 | + | "github.com/bnema/gordon/internal/common" | |
| 10 | 10 | "github.com/bnema/gordon/internal/db" | |
| 11 | - | "github.com/bnema/gordon/pkg/docker" | |
| 12 | 11 | _ "github.com/joho/godotenv/autoload" | |
| 13 | - | "gopkg.in/yaml.v3" | |
| 14 | 12 | ) | |
| 15 | 13 | ||
| 16 | 14 | var ( | |
| @@ -25,43 +23,11 @@ type App struct { | |||
| 25 | 23 | DBFilename string | |
| 26 | 24 | DBPath string | |
| 27 | 25 | InitialChecksum string | |
| 28 | - | Config Config | |
| 26 | + | Config common.Config | |
| 29 | 27 | DB *sql.DB | |
| 30 | 28 | DBTables DBTables | |
| 31 | 29 | 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 | 32 | type DBTables struct { | |
| 67 | 33 | User db.User `sql:"user"` | |
| @@ -70,50 +36,10 @@ type DBTables struct { | |||
| 70 | 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 | 39 | // GenerateOauthCallbackURL generates the OAuth callback URL | |
| 114 | - | func (config *Config) GenerateOauthCallbackURL() string { | |
| 40 | + | func (a *App) GenerateOauthCallbackURL() string { | |
| 115 | 41 | var scheme, port string | |
| 116 | - | ||
| 42 | + | config := a.Config | |
| 117 | 43 | if config.General.RunEnv == "dev" { | |
| 118 | 44 | scheme = "http" | |
| 119 | 45 | port = fmt.Sprintf(":%d", config.Http.Port) | |
| @@ -130,9 +56,9 @@ func (config *Config) GenerateOauthCallbackURL() string { | |||
| 130 | 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 | 60 | var scheme, port string | |
| 135 | - | ||
| 61 | + | config := a.Config | |
| 136 | 62 | if config.General.RunEnv == "dev" { | |
| 137 | 63 | scheme = "http" | |
| 138 | 64 | port = fmt.Sprintf(":%d", config.Http.Port) | |
| @@ -149,39 +75,12 @@ func (config *Config) GenerateAPIURL() string { | |||
| 149 | 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 | 78 | func (a *App) GetUptime() string { | |
| 180 | 79 | uptime := time.Since(a.StartTime) | |
| 181 | 80 | return uptime.String() | |
| 182 | 81 | } | |
| 183 | 82 | ||
| 184 | 83 | // 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) | |
| 187 | 86 | } | |
| @@ -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 string | 23 | DBFilename string | ||
| 26 | DBPath string | 24 | DBPath string | ||
| 27 | InitialChecksum string | 25 | InitialChecksum string | ||
| 28 | - | Config Config | 26 | + | Config common.Config |
| 29 | DB *sql.DB | 27 | DB *sql.DB | ||
| 30 | DBTables DBTables | 28 | DBTables DBTables | ||
| 31 | StartTime time.Time | 29 | 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 URL | 39 | // GenerateOauthCallbackURL generates the OAuth callback URL | ||
| 114 | - | func (config *Config) GenerateOauthCallbackURL() string { | 40 | + | func (a *App) GenerateOauthCallbackURL() string { |
| 115 | var scheme, port string | 41 | 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 string | 60 | 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 app | 83 | // GetVersion returns the version of the app | ||
| 185 | - | func (config *Config) GetVersion() string { | 84 | + | func (a *App) GetVersionstring() string { |
| 186 | - | return config.General.BuildVersion | 85 | + | return fmt.Sprint(a.Config.General.BuildVersion) |
| 187 | } | 86 | } | ||
M internal/server/newserver.go +5 -4
| @@ -6,14 +6,15 @@ import ( | |||
| 6 | 6 | "log" | |
| 7 | 7 | "time" | |
| 8 | 8 | ||
| 9 | + | "github.com/bnema/gordon/internal/common" | |
| 9 | 10 | "github.com/bnema/gordon/internal/templating" | |
| 10 | 11 | "github.com/bnema/gordon/internal/webui" | |
| 11 | 12 | ) | |
| 12 | 13 | ||
| 13 | 14 | func NewServerApp() (*App, error) { | |
| 14 | 15 | // Initialize AppConfig | |
| 15 | - | config := &Config{} | |
| 16 | - | _, err := LoadConfig(config) | |
| 16 | + | config := common.Config{} | |
| 17 | + | _, err := config.LoadConfig() | |
| 17 | 18 | if err != nil { | |
| 18 | 19 | log.Fatalf("Failed to load configuration: %v", err) | |
| 19 | 20 | } | |
| @@ -42,10 +43,10 @@ func NewServerApp() (*App, error) { | |||
| 42 | 43 | LocYML: bytes, | |
| 43 | 44 | DBDir: DBDir, | |
| 44 | 45 | DBFilename: DBFilename, | |
| 45 | - | Config: *config, | |
| 46 | + | Config: config, | |
| 46 | 47 | StartTime: time.Now(), | |
| 47 | 48 | } | |
| 48 | 49 | ||
| 49 | - | OauthCallbackURL = config.GenerateOauthCallbackURL() | |
| 50 | + | OauthCallbackURL = a.GenerateOauthCallbackURL() | |
| 50 | 51 | return a, nil | |
| 51 | 52 | } | |
| @@ -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 AppConfig | 15 | // 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, nil | 51 | return a, nil | ||
| 51 | } | 52 | } | ||
M internal/server/tokeninit.go +1 -1
| @@ -33,7 +33,7 @@ func HandleNewTokenInitialization(a *App) (string, error) { | |||
| 33 | 33 | ||
| 34 | 34 | // Store the token in the config file | |
| 35 | 35 | a.Config.General.Token = token | |
| 36 | - | err = a.Config.UpdateConfig() | |
| 36 | + | err = a.Config.SaveConfig() | |
| 37 | 37 | if err != nil { | |
| 38 | 38 | return "", fmt.Errorf("failed to save config: %v", err) | |
| 39 | 39 | } | |
| @@ -33,7 +33,7 @@ func HandleNewTokenInitialization(a *App) (string, error) { | |||||
| 33 | 33 | ||||
| 34 | // Store the token in the config file | 34 | // Store the token in the config file | ||
| 35 | a.Config.General.Token = token | 35 | 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 @@ | |||
| 1 | 1 | package docker | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "bytes" | |
| 4 | 5 | "context" | |
| 6 | + | "io" | |
| 5 | 7 | "os" | |
| 6 | 8 | ||
| 7 | 9 | "github.com/docker/docker/api/types" | |
| @@ -95,6 +97,29 @@ func ImportImageToEngine(imagePath string) (string, error) { | |||
| 95 | 97 | 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 | 123 | // From an ID, get the all the information about the image | |
| 99 | 124 | func GetImageInfo(imageID string) (*types.ImageInspect, error) { | |
| 100 | 125 | // Get the image information using the Docker client | |
| @@ -1,7 +1,9 @@ | |||||
| 1 | package docker | 1 | 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, nil | 97 | 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 image | 123 | // 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 client | 125 | // Get the image information using the Docker client | ||
M pkg/parser/yaml.go +16 -23
| @@ -4,34 +4,12 @@ import ( | |||
| 4 | 4 | "fmt" | |
| 5 | 5 | "io" | |
| 6 | 6 | "io/fs" | |
| 7 | + | "os" | |
| 7 | 8 | "path/filepath" | |
| 8 | 9 | ||
| 9 | 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 | 13 | // OpenYamlFile opens a YAML file and unmarshals it into the out interface | |
| 36 | 14 | func ParseYAMLFile(fs fs.FS, filename string, out interface{}, dir ...string) error { | |
| 37 | 15 | // 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 | 39 | 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 | + | } | |
| @@ -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 interface | 13 | // 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 file | 15 | // 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 nil | 39 | 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 ( | |||
| 6 | 6 | "os" | |
| 7 | 7 | "path/filepath" | |
| 8 | 8 | ||
| 9 | - | "github.com/bnema/gordon/internal/server" | |
| 10 | - | "github.com/bnema/gordon/pkg/docker" | |
| 9 | + | "github.com/bnema/gordon/internal/common" | |
| 11 | 10 | "github.com/docker/docker/api/types" | |
| 12 | 11 | ) | |
| 13 | 12 | ||
| @@ -17,13 +16,13 @@ type StorageConfig struct { | |||
| 17 | 16 | Images []types.ImageSummary | |
| 18 | 17 | } | |
| 19 | 18 | ||
| 20 | - | func NewStorageConfig(config *server.Config) *StorageConfig { | |
| 19 | + | func NewStorageConfig(config *common.Config) *StorageConfig { | |
| 21 | 20 | return &StorageConfig{ | |
| 22 | 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 | 26 | // Check if the folder exist if not create it | |
| 28 | 27 | if _, err := os.Stat(config.General.StorageDir); os.IsNotExist(err) { | |
| 29 | 28 | err := os.MkdirAll(config.General.StorageDir, 0755) | |
| @@ -52,13 +51,7 @@ func SaveImageToStorage(config *server.Config, filename string, buf io.Reader) ( | |||
| 52 | 51 | return "", fmt.Errorf("file does not exist: %v", err) | |
| 53 | 52 | } | |
| 54 | 53 | ||
| 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 | |
| 62 | 55 | } | |
| 63 | 56 | ||
| 64 | 57 | 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.ImageSummary | 16 | 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 it | 26 | // 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 Docker | 54 | + | 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 { | ||