Rename templates package to templating
- commit
- 96ba59cb5cf502144d8ec097312e6a248039ce53
- author
- bnema · · 2023-10-20 12:55 UTC
- parent
- 920de2d
- signature
- unsigned
28 files · +414 -12
M internal/app/app.go +3 -3
| @@ -9,7 +9,7 @@ import ( | |||
| 9 | 9 | "os" | |
| 10 | 10 | ||
| 11 | 11 | "github.com/bnema/gordon/internal/db" | |
| 12 | - | "github.com/bnema/gordon/internal/templates" | |
| 12 | + | "github.com/bnema/gordon/internal/templating" | |
| 13 | 13 | "github.com/bnema/gordon/internal/webui" | |
| 14 | 14 | "github.com/bnema/gordon/pkg/docker" | |
| 15 | 15 | _ "github.com/joho/godotenv/autoload" | |
| @@ -108,7 +108,7 @@ func NewApp() *App { | |||
| 108 | 108 | config.General.StorageDir = fmt.Sprintf("%s/%s", config.General.BuildDir, config.General.StorageDir) | |
| 109 | 109 | ||
| 110 | 110 | // Open the strings.yml file containing the strings for the current language | |
| 111 | - | file, err := templates.TemplateFS.Open("locstrings.yml") | |
| 111 | + | file, err := templating.TemplateFS.Open("locstrings.yml") | |
| 112 | 112 | if err != nil { | |
| 113 | 113 | log.Fatalf("Failed to open strings.yml: %v", err) | |
| 114 | 114 | } | |
| @@ -123,7 +123,7 @@ func NewApp() *App { | |||
| 123 | 123 | ||
| 124 | 124 | // Initialize App | |
| 125 | 125 | a := &App{ | |
| 126 | - | TemplateFS: templates.TemplateFS, | |
| 126 | + | TemplateFS: templating.TemplateFS, | |
| 127 | 127 | PublicFS: webui.PublicFS, | |
| 128 | 128 | LocYML: bytes, | |
| 129 | 129 | DBDir: DBDir, | |
| @@ -9,7 +9,7 @@ import ( | |||||
| 9 | "os" | 9 | "os" | ||
| 10 | 10 | ||||
| 11 | "github.com/bnema/gordon/internal/db" | 11 | "github.com/bnema/gordon/internal/db" | ||
| 12 | - | "github.com/bnema/gordon/internal/templates" | 12 | + | "github.com/bnema/gordon/internal/templating" |
| 13 | "github.com/bnema/gordon/internal/webui" | 13 | "github.com/bnema/gordon/internal/webui" | ||
| 14 | "github.com/bnema/gordon/pkg/docker" | 14 | "github.com/bnema/gordon/pkg/docker" | ||
| 15 | _ "github.com/joho/godotenv/autoload" | 15 | _ "github.com/joho/godotenv/autoload" | ||
| @@ -108,7 +108,7 @@ func NewApp() *App { | |||||
| 108 | config.General.StorageDir = fmt.Sprintf("%s/%s", config.General.BuildDir, config.General.StorageDir) | 108 | config.General.StorageDir = fmt.Sprintf("%s/%s", config.General.BuildDir, config.General.StorageDir) | ||
| 109 | 109 | ||||
| 110 | // Open the strings.yml file containing the strings for the current language | 110 | // Open the strings.yml file containing the strings for the current language | ||
| 111 | - | file, err := templates.TemplateFS.Open("locstrings.yml") | 111 | + | file, err := templating.TemplateFS.Open("locstrings.yml") |
| 112 | if err != nil { | 112 | if err != nil { | ||
| 113 | log.Fatalf("Failed to open strings.yml: %v", err) | 113 | log.Fatalf("Failed to open strings.yml: %v", err) | ||
| 114 | } | 114 | } | ||
| @@ -123,7 +123,7 @@ func NewApp() *App { | |||||
| 123 | 123 | ||||
| 124 | // Initialize App | 124 | // Initialize App | ||
| 125 | a := &App{ | 125 | a := &App{ | ||
| 126 | - | TemplateFS: templates.TemplateFS, | 126 | + | TemplateFS: templating.TemplateFS, |
| 127 | PublicFS: webui.PublicFS, | 127 | PublicFS: webui.PublicFS, | ||
| 128 | LocYML: bytes, | 128 | LocYML: bytes, | ||
| 129 | DBDir: DBDir, | 129 | DBDir: DBDir, | ||
A internal/commands/update.go +404 -0
| @@ -0,0 +1,404 @@ | |||
| 1 | + | // Heavily inspired by Pocketbase's update command | |
| 2 | + | // See: https://github.com/pocketbase/pocketbase/blob/master/plugins/ghupdate/ghupdate.go | |
| 3 | + | package commands | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "context" | |
| 7 | + | "encoding/json" | |
| 8 | + | "errors" | |
| 9 | + | "fmt" | |
| 10 | + | "io" | |
| 11 | + | "log" | |
| 12 | + | "net/http" | |
| 13 | + | "os" | |
| 14 | + | "path/filepath" | |
| 15 | + | "runtime" | |
| 16 | + | "strconv" | |
| 17 | + | "strings" | |
| 18 | + | ||
| 19 | + | "github.com/AlecAivazis/survey/v2" | |
| 20 | + | "github.com/fatih/color" | |
| 21 | + | "github.com/pocketbase/pocketbase/core" | |
| 22 | + | "github.com/pocketbase/pocketbase/tools/archive" | |
| 23 | + | "github.com/spf13/cobra" | |
| 24 | + | ) | |
| 25 | + | ||
| 26 | + | // HttpClient is a base HTTP client interface (usually used for test purposes). | |
| 27 | + | type HttpClient interface { | |
| 28 | + | Do(req *http.Request) (*http.Response, error) | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | // Config defines the config options of the ghupdate plugin. | |
| 32 | + | // | |
| 33 | + | // NB! This plugin is considered experimental and its config options may change in the future. | |
| 34 | + | type Config struct { | |
| 35 | + | // Owner specifies the account owner of the repository (default to "pocketbase"). | |
| 36 | + | Owner string | |
| 37 | + | ||
| 38 | + | // Repo specifies the name of the repository (default to "pocketbase"). | |
| 39 | + | Repo string | |
| 40 | + | ||
| 41 | + | // ArchiveExecutable specifies the name of the executable file in the release archive | |
| 42 | + | // (default to "pocketbase"; an additional ".exe" check is also performed as a fallback). | |
| 43 | + | ArchiveExecutable string | |
| 44 | + | ||
| 45 | + | // Optional context to use when fetching and downloading the latest release. | |
| 46 | + | Context context.Context | |
| 47 | + | ||
| 48 | + | // The HTTP client to use when fetching and downloading the latest release. | |
| 49 | + | // Defaults to `http.DefaultClient`. | |
| 50 | + | HttpClient HttpClient | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | // MustRegister registers the ghupdate plugin to the provided app instance | |
| 54 | + | // and panic if it fails. | |
| 55 | + | func MustRegister(app core.App, rootCmd *cobra.Command, config Config) { | |
| 56 | + | if err := Register(app, rootCmd, config); err != nil { | |
| 57 | + | panic(err) | |
| 58 | + | } | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | // Register registers the ghupdate plugin to the provided app instance. | |
| 62 | + | func Register(app core.App, rootCmd *cobra.Command, config Config) error { | |
| 63 | + | p := &plugin{ | |
| 64 | + | app: app, | |
| 65 | + | currentVersion: rootCmd.Version, | |
| 66 | + | config: config, | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | if p.config.Owner == "" { | |
| 70 | + | p.config.Owner = "pocketbase" | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | if p.config.Repo == "" { | |
| 74 | + | p.config.Repo = "pocketbase" | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | if p.config.ArchiveExecutable == "" { | |
| 78 | + | p.config.ArchiveExecutable = "pocketbase" | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | if p.config.HttpClient == nil { | |
| 82 | + | p.config.HttpClient = http.DefaultClient | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | if p.config.Context == nil { | |
| 86 | + | p.config.Context = context.Background() | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | rootCmd.AddCommand(p.updateCmd()) | |
| 90 | + | ||
| 91 | + | return nil | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | type plugin struct { | |
| 95 | + | app core.App | |
| 96 | + | currentVersion string | |
| 97 | + | config Config | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | func (p *plugin) updateCmd() *cobra.Command { | |
| 101 | + | var withBackup bool | |
| 102 | + | ||
| 103 | + | command := &cobra.Command{ | |
| 104 | + | Use: "update", | |
| 105 | + | Short: "Automatically updates the current PocketBase executable with the latest available version", | |
| 106 | + | // @todo remove after logs generalization | |
| 107 | + | // prevents printing the error log twice | |
| 108 | + | SilenceErrors: true, | |
| 109 | + | SilenceUsage: true, | |
| 110 | + | RunE: func(command *cobra.Command, args []string) error { | |
| 111 | + | var needConfirm bool | |
| 112 | + | if isMaybeRunningInDocker() { | |
| 113 | + | needConfirm = true | |
| 114 | + | color.Yellow("NB! It seems that you are in a Docker container.") | |
| 115 | + | color.Yellow("The update command may not work as expected in this context because usually the version of the app is managed by the container image itself.") | |
| 116 | + | } else if isMaybeRunningInNixOS() { | |
| 117 | + | needConfirm = true | |
| 118 | + | color.Yellow("NB! It seems that you are in a NixOS.") | |
| 119 | + | color.Yellow("Due to the non-standard filesystem implementation of the environment, the update command may not work as expected.") | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | if needConfirm { | |
| 123 | + | confirm := false | |
| 124 | + | prompt := &survey.Confirm{ | |
| 125 | + | Message: "Do you want to proceed with the update?", | |
| 126 | + | } | |
| 127 | + | survey.AskOne(prompt, &confirm) | |
| 128 | + | if !confirm { | |
| 129 | + | fmt.Println("The command has been cancelled.") | |
| 130 | + | return nil | |
| 131 | + | } | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | return p.update(withBackup) | |
| 135 | + | }, | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | command.PersistentFlags().BoolVar( | |
| 139 | + | &withBackup, | |
| 140 | + | "backup", | |
| 141 | + | true, | |
| 142 | + | "Creates a pb_data backup at the end of the update process", | |
| 143 | + | ) | |
| 144 | + | ||
| 145 | + | return command | |
| 146 | + | } | |
| 147 | + | ||
| 148 | + | func (p *plugin) update(withBackup bool) error { | |
| 149 | + | color.Yellow("Fetching release information...") | |
| 150 | + | ||
| 151 | + | latest, err := fetchLatestRelease( | |
| 152 | + | p.config.Context, | |
| 153 | + | p.config.HttpClient, | |
| 154 | + | p.config.Owner, | |
| 155 | + | p.config.Repo, | |
| 156 | + | ) | |
| 157 | + | if err != nil { | |
| 158 | + | return err | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | if compareVersions(strings.TrimPrefix(p.currentVersion, "v"), strings.TrimPrefix(latest.Tag, "v")) <= 0 { | |
| 162 | + | color.Green("You already have the latest PocketBase %s.", p.currentVersion) | |
| 163 | + | return nil | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | suffix := archiveSuffix(runtime.GOOS, runtime.GOARCH) | |
| 167 | + | if suffix == "" { | |
| 168 | + | return errors.New("unsupported platform") | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | asset, err := latest.findAssetBySuffix(suffix) | |
| 172 | + | if err != nil { | |
| 173 | + | return err | |
| 174 | + | } | |
| 175 | + | ||
| 176 | + | releaseDir := filepath.Join(p.app.DataDir(), core.LocalTempDirName) | |
| 177 | + | defer os.RemoveAll(releaseDir) | |
| 178 | + | ||
| 179 | + | color.Yellow("Downloading %s...", asset.Name) | |
| 180 | + | ||
| 181 | + | // download the release asset | |
| 182 | + | assetZip := filepath.Join(releaseDir, asset.Name) | |
| 183 | + | if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetZip); err != nil { | |
| 184 | + | return err | |
| 185 | + | } | |
| 186 | + | ||
| 187 | + | color.Yellow("Extracting %s...", asset.Name) | |
| 188 | + | ||
| 189 | + | extractDir := filepath.Join(releaseDir, "extracted_"+asset.Name) | |
| 190 | + | defer os.RemoveAll(extractDir) | |
| 191 | + | ||
| 192 | + | if err := archive.Extract(assetZip, extractDir); err != nil { | |
| 193 | + | return err | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | color.Yellow("Replacing the executable...") | |
| 197 | + | ||
| 198 | + | oldExec, err := os.Executable() | |
| 199 | + | if err != nil { | |
| 200 | + | return err | |
| 201 | + | } | |
| 202 | + | renamedOldExec := oldExec + ".old" | |
| 203 | + | defer os.Remove(renamedOldExec) | |
| 204 | + | ||
| 205 | + | newExec := filepath.Join(extractDir, p.config.ArchiveExecutable) | |
| 206 | + | if _, err := os.Stat(newExec); err != nil { | |
| 207 | + | // try again with an .exe extension | |
| 208 | + | newExec = newExec + ".exe" | |
| 209 | + | if _, fallbackErr := os.Stat(newExec); fallbackErr != nil { | |
| 210 | + | return fmt.Errorf("The executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr) | |
| 211 | + | } | |
| 212 | + | } | |
| 213 | + | ||
| 214 | + | // rename the current executable | |
| 215 | + | if err := os.Rename(oldExec, renamedOldExec); err != nil { | |
| 216 | + | return fmt.Errorf("Failed to rename the current executable: %w", err) | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | tryToRevertExecChanges := func() { | |
| 220 | + | if revertErr := os.Rename(renamedOldExec, oldExec); revertErr != nil && p.app.IsDebug() { | |
| 221 | + | log.Println(revertErr) | |
| 222 | + | } | |
| 223 | + | } | |
| 224 | + | ||
| 225 | + | // replace with the extracted binary | |
| 226 | + | if err := os.Rename(newExec, oldExec); err != nil { | |
| 227 | + | tryToRevertExecChanges() | |
| 228 | + | return fmt.Errorf("Failed replacing the executable: %w", err) | |
| 229 | + | } | |
| 230 | + | ||
| 231 | + | if withBackup { | |
| 232 | + | color.Yellow("Creating pb_data backup...") | |
| 233 | + | ||
| 234 | + | backupName := fmt.Sprintf("@update_%s.zip", latest.Tag) | |
| 235 | + | if err := p.app.CreateBackup(p.config.Context, backupName); err != nil { | |
| 236 | + | tryToRevertExecChanges() | |
| 237 | + | return err | |
| 238 | + | } | |
| 239 | + | } | |
| 240 | + | ||
| 241 | + | color.HiBlack("---") | |
| 242 | + | color.Green("Update completed successfully! You can start the executable as usual.") | |
| 243 | + | ||
| 244 | + | return nil | |
| 245 | + | } | |
| 246 | + | ||
| 247 | + | func fetchLatestRelease( | |
| 248 | + | ctx context.Context, | |
| 249 | + | client HttpClient, | |
| 250 | + | owner string, | |
| 251 | + | repo string, | |
| 252 | + | ) (*release, error) { | |
| 253 | + | url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo) | |
| 254 | + | ||
| 255 | + | req, err := http.NewRequestWithContext(ctx, "GET", url, nil) | |
| 256 | + | if err != nil { | |
| 257 | + | return nil, err | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | res, err := client.Do(req) | |
| 261 | + | if err != nil { | |
| 262 | + | return nil, err | |
| 263 | + | } | |
| 264 | + | defer res.Body.Close() | |
| 265 | + | ||
| 266 | + | rawBody, err := io.ReadAll(res.Body) | |
| 267 | + | if err != nil { | |
| 268 | + | return nil, err | |
| 269 | + | } | |
| 270 | + | ||
| 271 | + | // http.Client doesn't treat non 2xx responses as error | |
| 272 | + | if res.StatusCode >= 400 { | |
| 273 | + | return nil, fmt.Errorf( | |
| 274 | + | "(%d) failed to fetch latest releases:\n%s", | |
| 275 | + | res.StatusCode, | |
| 276 | + | string(rawBody), | |
| 277 | + | ) | |
| 278 | + | } | |
| 279 | + | ||
| 280 | + | result := &release{} | |
| 281 | + | if err := json.Unmarshal(rawBody, result); err != nil { | |
| 282 | + | return nil, err | |
| 283 | + | } | |
| 284 | + | ||
| 285 | + | return result, nil | |
| 286 | + | } | |
| 287 | + | ||
| 288 | + | func downloadFile( | |
| 289 | + | ctx context.Context, | |
| 290 | + | client HttpClient, | |
| 291 | + | url string, | |
| 292 | + | destPath string, | |
| 293 | + | ) error { | |
| 294 | + | req, err := http.NewRequestWithContext(ctx, "GET", url, nil) | |
| 295 | + | if err != nil { | |
| 296 | + | return err | |
| 297 | + | } | |
| 298 | + | ||
| 299 | + | res, err := client.Do(req) | |
| 300 | + | if err != nil { | |
| 301 | + | return err | |
| 302 | + | } | |
| 303 | + | defer res.Body.Close() | |
| 304 | + | ||
| 305 | + | // http.Client doesn't treat non 2xx responses as error | |
| 306 | + | if res.StatusCode >= 400 { | |
| 307 | + | return fmt.Errorf("(%d) failed to send download file request", res.StatusCode) | |
| 308 | + | } | |
| 309 | + | ||
| 310 | + | // ensure that the dest parent dir(s) exist | |
| 311 | + | if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil { | |
| 312 | + | return err | |
| 313 | + | } | |
| 314 | + | ||
| 315 | + | dest, err := os.Create(destPath) | |
| 316 | + | if err != nil { | |
| 317 | + | return err | |
| 318 | + | } | |
| 319 | + | defer dest.Close() | |
| 320 | + | ||
| 321 | + | if _, err := io.Copy(dest, res.Body); err != nil { | |
| 322 | + | return err | |
| 323 | + | } | |
| 324 | + | ||
| 325 | + | return nil | |
| 326 | + | } | |
| 327 | + | ||
| 328 | + | func archiveSuffix(goos, goarch string) string { | |
| 329 | + | switch goos { | |
| 330 | + | case "linux": | |
| 331 | + | switch goarch { | |
| 332 | + | case "amd64": | |
| 333 | + | return "_linux_amd64.zip" | |
| 334 | + | case "arm64": | |
| 335 | + | return "_linux_arm64.zip" | |
| 336 | + | case "arm": | |
| 337 | + | return "_linux_armv7.zip" | |
| 338 | + | } | |
| 339 | + | case "darwin": | |
| 340 | + | switch goarch { | |
| 341 | + | case "amd64": | |
| 342 | + | return "_darwin_amd64.zip" | |
| 343 | + | case "arm64": | |
| 344 | + | return "_darwin_arm64.zip" | |
| 345 | + | } | |
| 346 | + | case "windows": | |
| 347 | + | switch goarch { | |
| 348 | + | case "amd64": | |
| 349 | + | return "_windows_amd64.zip" | |
| 350 | + | case "arm64": | |
| 351 | + | return "_windows_arm64.zip" | |
| 352 | + | } | |
| 353 | + | } | |
| 354 | + | ||
| 355 | + | return "" | |
| 356 | + | } | |
| 357 | + | ||
| 358 | + | func compareVersions(a, b string) int { | |
| 359 | + | aSplit := strings.Split(a, ".") | |
| 360 | + | aTotal := len(aSplit) | |
| 361 | + | ||
| 362 | + | bSplit := strings.Split(b, ".") | |
| 363 | + | bTotal := len(bSplit) | |
| 364 | + | ||
| 365 | + | limit := aTotal | |
| 366 | + | if bTotal > aTotal { | |
| 367 | + | limit = bTotal | |
| 368 | + | } | |
| 369 | + | ||
| 370 | + | for i := 0; i < limit; i++ { | |
| 371 | + | var x, y int | |
| 372 | + | ||
| 373 | + | if i < aTotal { | |
| 374 | + | x, _ = strconv.Atoi(aSplit[i]) | |
| 375 | + | } | |
| 376 | + | ||
| 377 | + | if i < bTotal { | |
| 378 | + | y, _ = strconv.Atoi(bSplit[i]) | |
| 379 | + | } | |
| 380 | + | ||
| 381 | + | if x < y { | |
| 382 | + | return 1 // b is newer | |
| 383 | + | } | |
| 384 | + | ||
| 385 | + | if x > y { | |
| 386 | + | return -1 // a is newer | |
| 387 | + | } | |
| 388 | + | } | |
| 389 | + | ||
| 390 | + | return 0 // equal | |
| 391 | + | } | |
| 392 | + | ||
| 393 | + | // note: not completely reliable as it may not work on all platforms | |
| 394 | + | // but should at least provide a warning for the most common use cases | |
| 395 | + | func isMaybeRunningInDocker() bool { | |
| 396 | + | _, err := os.Stat("/.dockerenv") | |
| 397 | + | return err == nil | |
| 398 | + | } | |
| 399 | + | ||
| 400 | + | // note: untested | |
| 401 | + | func isMaybeRunningInNixOS() bool { | |
| 402 | + | _, err := os.Stat("/etc/NIXOS") | |
| 403 | + | return err == nil | |
| 404 | + | } | |
| @@ -0,0 +1,404 @@ | |||||
| 1 | + | // Heavily inspired by Pocketbase's update command | |||
| 2 | + | // See: https://github.com/pocketbase/pocketbase/blob/master/plugins/ghupdate/ghupdate.go | |||
| 3 | + | package commands | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "context" | |||
| 7 | + | "encoding/json" | |||
| 8 | + | "errors" | |||
| 9 | + | "fmt" | |||
| 10 | + | "io" | |||
| 11 | + | "log" | |||
| 12 | + | "net/http" | |||
| 13 | + | "os" | |||
| 14 | + | "path/filepath" | |||
| 15 | + | "runtime" | |||
| 16 | + | "strconv" | |||
| 17 | + | "strings" | |||
| 18 | + | ||||
| 19 | + | "github.com/AlecAivazis/survey/v2" | |||
| 20 | + | "github.com/fatih/color" | |||
| 21 | + | "github.com/pocketbase/pocketbase/core" | |||
| 22 | + | "github.com/pocketbase/pocketbase/tools/archive" | |||
| 23 | + | "github.com/spf13/cobra" | |||
| 24 | + | ) | |||
| 25 | + | ||||
| 26 | + | // HttpClient is a base HTTP client interface (usually used for test purposes). | |||
| 27 | + | type HttpClient interface { | |||
| 28 | + | Do(req *http.Request) (*http.Response, error) | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | // Config defines the config options of the ghupdate plugin. | |||
| 32 | + | // | |||
| 33 | + | // NB! This plugin is considered experimental and its config options may change in the future. | |||
| 34 | + | type Config struct { | |||
| 35 | + | // Owner specifies the account owner of the repository (default to "pocketbase"). | |||
| 36 | + | Owner string | |||
| 37 | + | ||||
| 38 | + | // Repo specifies the name of the repository (default to "pocketbase"). | |||
| 39 | + | Repo string | |||
| 40 | + | ||||
| 41 | + | // ArchiveExecutable specifies the name of the executable file in the release archive | |||
| 42 | + | // (default to "pocketbase"; an additional ".exe" check is also performed as a fallback). | |||
| 43 | + | ArchiveExecutable string | |||
| 44 | + | ||||
| 45 | + | // Optional context to use when fetching and downloading the latest release. | |||
| 46 | + | Context context.Context | |||
| 47 | + | ||||
| 48 | + | // The HTTP client to use when fetching and downloading the latest release. | |||
| 49 | + | // Defaults to `http.DefaultClient`. | |||
| 50 | + | HttpClient HttpClient | |||
| 51 | + | } | |||
| 52 | + | ||||
| 53 | + | // MustRegister registers the ghupdate plugin to the provided app instance | |||
| 54 | + | // and panic if it fails. | |||
| 55 | + | func MustRegister(app core.App, rootCmd *cobra.Command, config Config) { | |||
| 56 | + | if err := Register(app, rootCmd, config); err != nil { | |||
| 57 | + | panic(err) | |||
| 58 | + | } | |||
| 59 | + | } | |||
| 60 | + | ||||
| 61 | + | // Register registers the ghupdate plugin to the provided app instance. | |||
| 62 | + | func Register(app core.App, rootCmd *cobra.Command, config Config) error { | |||
| 63 | + | p := &plugin{ | |||
| 64 | + | app: app, | |||
| 65 | + | currentVersion: rootCmd.Version, | |||
| 66 | + | config: config, | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | if p.config.Owner == "" { | |||
| 70 | + | p.config.Owner = "pocketbase" | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | if p.config.Repo == "" { | |||
| 74 | + | p.config.Repo = "pocketbase" | |||
| 75 | + | } | |||
| 76 | + | ||||
| 77 | + | if p.config.ArchiveExecutable == "" { | |||
| 78 | + | p.config.ArchiveExecutable = "pocketbase" | |||
| 79 | + | } | |||
| 80 | + | ||||
| 81 | + | if p.config.HttpClient == nil { | |||
| 82 | + | p.config.HttpClient = http.DefaultClient | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | if p.config.Context == nil { | |||
| 86 | + | p.config.Context = context.Background() | |||
| 87 | + | } | |||
| 88 | + | ||||
| 89 | + | rootCmd.AddCommand(p.updateCmd()) | |||
| 90 | + | ||||
| 91 | + | return nil | |||
| 92 | + | } | |||
| 93 | + | ||||
| 94 | + | type plugin struct { | |||
| 95 | + | app core.App | |||
| 96 | + | currentVersion string | |||
| 97 | + | config Config | |||
| 98 | + | } | |||
| 99 | + | ||||
| 100 | + | func (p *plugin) updateCmd() *cobra.Command { | |||
| 101 | + | var withBackup bool | |||
| 102 | + | ||||
| 103 | + | command := &cobra.Command{ | |||
| 104 | + | Use: "update", | |||
| 105 | + | Short: "Automatically updates the current PocketBase executable with the latest available version", | |||
| 106 | + | // @todo remove after logs generalization | |||
| 107 | + | // prevents printing the error log twice | |||
| 108 | + | SilenceErrors: true, | |||
| 109 | + | SilenceUsage: true, | |||
| 110 | + | RunE: func(command *cobra.Command, args []string) error { | |||
| 111 | + | var needConfirm bool | |||
| 112 | + | if isMaybeRunningInDocker() { | |||
| 113 | + | needConfirm = true | |||
| 114 | + | color.Yellow("NB! It seems that you are in a Docker container.") | |||
| 115 | + | color.Yellow("The update command may not work as expected in this context because usually the version of the app is managed by the container image itself.") | |||
| 116 | + | } else if isMaybeRunningInNixOS() { | |||
| 117 | + | needConfirm = true | |||
| 118 | + | color.Yellow("NB! It seems that you are in a NixOS.") | |||
| 119 | + | color.Yellow("Due to the non-standard filesystem implementation of the environment, the update command may not work as expected.") | |||
| 120 | + | } | |||
| 121 | + | ||||
| 122 | + | if needConfirm { | |||
| 123 | + | confirm := false | |||
| 124 | + | prompt := &survey.Confirm{ | |||
| 125 | + | Message: "Do you want to proceed with the update?", | |||
| 126 | + | } | |||
| 127 | + | survey.AskOne(prompt, &confirm) | |||
| 128 | + | if !confirm { | |||
| 129 | + | fmt.Println("The command has been cancelled.") | |||
| 130 | + | return nil | |||
| 131 | + | } | |||
| 132 | + | } | |||
| 133 | + | ||||
| 134 | + | return p.update(withBackup) | |||
| 135 | + | }, | |||
| 136 | + | } | |||
| 137 | + | ||||
| 138 | + | command.PersistentFlags().BoolVar( | |||
| 139 | + | &withBackup, | |||
| 140 | + | "backup", | |||
| 141 | + | true, | |||
| 142 | + | "Creates a pb_data backup at the end of the update process", | |||
| 143 | + | ) | |||
| 144 | + | ||||
| 145 | + | return command | |||
| 146 | + | } | |||
| 147 | + | ||||
| 148 | + | func (p *plugin) update(withBackup bool) error { | |||
| 149 | + | color.Yellow("Fetching release information...") | |||
| 150 | + | ||||
| 151 | + | latest, err := fetchLatestRelease( | |||
| 152 | + | p.config.Context, | |||
| 153 | + | p.config.HttpClient, | |||
| 154 | + | p.config.Owner, | |||
| 155 | + | p.config.Repo, | |||
| 156 | + | ) | |||
| 157 | + | if err != nil { | |||
| 158 | + | return err | |||
| 159 | + | } | |||
| 160 | + | ||||
| 161 | + | if compareVersions(strings.TrimPrefix(p.currentVersion, "v"), strings.TrimPrefix(latest.Tag, "v")) <= 0 { | |||
| 162 | + | color.Green("You already have the latest PocketBase %s.", p.currentVersion) | |||
| 163 | + | return nil | |||
| 164 | + | } | |||
| 165 | + | ||||
| 166 | + | suffix := archiveSuffix(runtime.GOOS, runtime.GOARCH) | |||
| 167 | + | if suffix == "" { | |||
| 168 | + | return errors.New("unsupported platform") | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | asset, err := latest.findAssetBySuffix(suffix) | |||
| 172 | + | if err != nil { | |||
| 173 | + | return err | |||
| 174 | + | } | |||
| 175 | + | ||||
| 176 | + | releaseDir := filepath.Join(p.app.DataDir(), core.LocalTempDirName) | |||
| 177 | + | defer os.RemoveAll(releaseDir) | |||
| 178 | + | ||||
| 179 | + | color.Yellow("Downloading %s...", asset.Name) | |||
| 180 | + | ||||
| 181 | + | // download the release asset | |||
| 182 | + | assetZip := filepath.Join(releaseDir, asset.Name) | |||
| 183 | + | if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetZip); err != nil { | |||
| 184 | + | return err | |||
| 185 | + | } | |||
| 186 | + | ||||
| 187 | + | color.Yellow("Extracting %s...", asset.Name) | |||
| 188 | + | ||||
| 189 | + | extractDir := filepath.Join(releaseDir, "extracted_"+asset.Name) | |||
| 190 | + | defer os.RemoveAll(extractDir) | |||
| 191 | + | ||||
| 192 | + | if err := archive.Extract(assetZip, extractDir); err != nil { | |||
| 193 | + | return err | |||
| 194 | + | } | |||
| 195 | + | ||||
| 196 | + | color.Yellow("Replacing the executable...") | |||
| 197 | + | ||||
| 198 | + | oldExec, err := os.Executable() | |||
| 199 | + | if err != nil { | |||
| 200 | + | return err | |||
| 201 | + | } | |||
| 202 | + | renamedOldExec := oldExec + ".old" | |||
| 203 | + | defer os.Remove(renamedOldExec) | |||
| 204 | + | ||||
| 205 | + | newExec := filepath.Join(extractDir, p.config.ArchiveExecutable) | |||
| 206 | + | if _, err := os.Stat(newExec); err != nil { | |||
| 207 | + | // try again with an .exe extension | |||
| 208 | + | newExec = newExec + ".exe" | |||
| 209 | + | if _, fallbackErr := os.Stat(newExec); fallbackErr != nil { | |||
| 210 | + | return fmt.Errorf("The executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr) | |||
| 211 | + | } | |||
| 212 | + | } | |||
| 213 | + | ||||
| 214 | + | // rename the current executable | |||
| 215 | + | if err := os.Rename(oldExec, renamedOldExec); err != nil { | |||
| 216 | + | return fmt.Errorf("Failed to rename the current executable: %w", err) | |||
| 217 | + | } | |||
| 218 | + | ||||
| 219 | + | tryToRevertExecChanges := func() { | |||
| 220 | + | if revertErr := os.Rename(renamedOldExec, oldExec); revertErr != nil && p.app.IsDebug() { | |||
| 221 | + | log.Println(revertErr) | |||
| 222 | + | } | |||
| 223 | + | } | |||
| 224 | + | ||||
| 225 | + | // replace with the extracted binary | |||
| 226 | + | if err := os.Rename(newExec, oldExec); err != nil { | |||
| 227 | + | tryToRevertExecChanges() | |||
| 228 | + | return fmt.Errorf("Failed replacing the executable: %w", err) | |||
| 229 | + | } | |||
| 230 | + | ||||
| 231 | + | if withBackup { | |||
| 232 | + | color.Yellow("Creating pb_data backup...") | |||
| 233 | + | ||||
| 234 | + | backupName := fmt.Sprintf("@update_%s.zip", latest.Tag) | |||
| 235 | + | if err := p.app.CreateBackup(p.config.Context, backupName); err != nil { | |||
| 236 | + | tryToRevertExecChanges() | |||
| 237 | + | return err | |||
| 238 | + | } | |||
| 239 | + | } | |||
| 240 | + | ||||
| 241 | + | color.HiBlack("---") | |||
| 242 | + | color.Green("Update completed successfully! You can start the executable as usual.") | |||
| 243 | + | ||||
| 244 | + | return nil | |||
| 245 | + | } | |||
| 246 | + | ||||
| 247 | + | func fetchLatestRelease( | |||
| 248 | + | ctx context.Context, | |||
| 249 | + | client HttpClient, | |||
| 250 | + | owner string, | |||
| 251 | + | repo string, | |||
| 252 | + | ) (*release, error) { | |||
| 253 | + | url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo) | |||
| 254 | + | ||||
| 255 | + | req, err := http.NewRequestWithContext(ctx, "GET", url, nil) | |||
| 256 | + | if err != nil { | |||
| 257 | + | return nil, err | |||
| 258 | + | } | |||
| 259 | + | ||||
| 260 | + | res, err := client.Do(req) | |||
| 261 | + | if err != nil { | |||
| 262 | + | return nil, err | |||
| 263 | + | } | |||
| 264 | + | defer res.Body.Close() | |||
| 265 | + | ||||
| 266 | + | rawBody, err := io.ReadAll(res.Body) | |||
| 267 | + | if err != nil { | |||
| 268 | + | return nil, err | |||
| 269 | + | } | |||
| 270 | + | ||||
| 271 | + | // http.Client doesn't treat non 2xx responses as error | |||
| 272 | + | if res.StatusCode >= 400 { | |||
| 273 | + | return nil, fmt.Errorf( | |||
| 274 | + | "(%d) failed to fetch latest releases:\n%s", | |||
| 275 | + | res.StatusCode, | |||
| 276 | + | string(rawBody), | |||
| 277 | + | ) | |||
| 278 | + | } | |||
| 279 | + | ||||
| 280 | + | result := &release{} | |||
| 281 | + | if err := json.Unmarshal(rawBody, result); err != nil { | |||
| 282 | + | return nil, err | |||
| 283 | + | } | |||
| 284 | + | ||||
| 285 | + | return result, nil | |||
| 286 | + | } | |||
| 287 | + | ||||
| 288 | + | func downloadFile( | |||
| 289 | + | ctx context.Context, | |||
| 290 | + | client HttpClient, | |||
| 291 | + | url string, | |||
| 292 | + | destPath string, | |||
| 293 | + | ) error { | |||
| 294 | + | req, err := http.NewRequestWithContext(ctx, "GET", url, nil) | |||
| 295 | + | if err != nil { | |||
| 296 | + | return err | |||
| 297 | + | } | |||
| 298 | + | ||||
| 299 | + | res, err := client.Do(req) | |||
| 300 | + | if err != nil { | |||
| 301 | + | return err | |||
| 302 | + | } | |||
| 303 | + | defer res.Body.Close() | |||
| 304 | + | ||||
| 305 | + | // http.Client doesn't treat non 2xx responses as error | |||
| 306 | + | if res.StatusCode >= 400 { | |||
| 307 | + | return fmt.Errorf("(%d) failed to send download file request", res.StatusCode) | |||
| 308 | + | } | |||
| 309 | + | ||||
| 310 | + | // ensure that the dest parent dir(s) exist | |||
| 311 | + | if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil { | |||
| 312 | + | return err | |||
| 313 | + | } | |||
| 314 | + | ||||
| 315 | + | dest, err := os.Create(destPath) | |||
| 316 | + | if err != nil { | |||
| 317 | + | return err | |||
| 318 | + | } | |||
| 319 | + | defer dest.Close() | |||
| 320 | + | ||||
| 321 | + | if _, err := io.Copy(dest, res.Body); err != nil { | |||
| 322 | + | return err | |||
| 323 | + | } | |||
| 324 | + | ||||
| 325 | + | return nil | |||
| 326 | + | } | |||
| 327 | + | ||||
| 328 | + | func archiveSuffix(goos, goarch string) string { | |||
| 329 | + | switch goos { | |||
| 330 | + | case "linux": | |||
| 331 | + | switch goarch { | |||
| 332 | + | case "amd64": | |||
| 333 | + | return "_linux_amd64.zip" | |||
| 334 | + | case "arm64": | |||
| 335 | + | return "_linux_arm64.zip" | |||
| 336 | + | case "arm": | |||
| 337 | + | return "_linux_armv7.zip" | |||
| 338 | + | } | |||
| 339 | + | case "darwin": | |||
| 340 | + | switch goarch { | |||
| 341 | + | case "amd64": | |||
| 342 | + | return "_darwin_amd64.zip" | |||
| 343 | + | case "arm64": | |||
| 344 | + | return "_darwin_arm64.zip" | |||
| 345 | + | } | |||
| 346 | + | case "windows": | |||
| 347 | + | switch goarch { | |||
| 348 | + | case "amd64": | |||
| 349 | + | return "_windows_amd64.zip" | |||
| 350 | + | case "arm64": | |||
| 351 | + | return "_windows_arm64.zip" | |||
| 352 | + | } | |||
| 353 | + | } | |||
| 354 | + | ||||
| 355 | + | return "" | |||
| 356 | + | } | |||
| 357 | + | ||||
| 358 | + | func compareVersions(a, b string) int { | |||
| 359 | + | aSplit := strings.Split(a, ".") | |||
| 360 | + | aTotal := len(aSplit) | |||
| 361 | + | ||||
| 362 | + | bSplit := strings.Split(b, ".") | |||
| 363 | + | bTotal := len(bSplit) | |||
| 364 | + | ||||
| 365 | + | limit := aTotal | |||
| 366 | + | if bTotal > aTotal { | |||
| 367 | + | limit = bTotal | |||
| 368 | + | } | |||
| 369 | + | ||||
| 370 | + | for i := 0; i < limit; i++ { | |||
| 371 | + | var x, y int | |||
| 372 | + | ||||
| 373 | + | if i < aTotal { | |||
| 374 | + | x, _ = strconv.Atoi(aSplit[i]) | |||
| 375 | + | } | |||
| 376 | + | ||||
| 377 | + | if i < bTotal { | |||
| 378 | + | y, _ = strconv.Atoi(bSplit[i]) | |||
| 379 | + | } | |||
| 380 | + | ||||
| 381 | + | if x < y { | |||
| 382 | + | return 1 // b is newer | |||
| 383 | + | } | |||
| 384 | + | ||||
| 385 | + | if x > y { | |||
| 386 | + | return -1 // a is newer | |||
| 387 | + | } | |||
| 388 | + | } | |||
| 389 | + | ||||
| 390 | + | return 0 // equal | |||
| 391 | + | } | |||
| 392 | + | ||||
| 393 | + | // note: not completely reliable as it may not work on all platforms | |||
| 394 | + | // but should at least provide a warning for the most common use cases | |||
| 395 | + | func isMaybeRunningInDocker() bool { | |||
| 396 | + | _, err := os.Stat("/.dockerenv") | |||
| 397 | + | return err == nil | |||
| 398 | + | } | |||
| 399 | + | ||||
| 400 | + | // note: untested | |||
| 401 | + | func isMaybeRunningInNixOS() bool { | |||
| 402 | + | _, err := os.Stat("/etc/NIXOS") | |||
| 403 | + | return err == nil | |||
| 404 | + | } | |||
M internal/httpserve/handler/adminroutes.go +1 -1
| @@ -5,7 +5,7 @@ import ( | |||
| 5 | 5 | ||
| 6 | 6 | "github.com/bnema/gordon/internal/app" | |
| 7 | 7 | "github.com/bnema/gordon/internal/httpserve/middleware" | |
| 8 | - | "github.com/bnema/gordon/internal/templates/render" | |
| 8 | + | "github.com/bnema/gordon/internal/templating/render" | |
| 9 | 9 | "github.com/labstack/echo/v4" | |
| 10 | 10 | ) | |
| 11 | 11 | ||
| @@ -5,7 +5,7 @@ import ( | |||||
| 5 | 5 | ||||
| 6 | "github.com/bnema/gordon/internal/app" | 6 | "github.com/bnema/gordon/internal/app" | ||
| 7 | "github.com/bnema/gordon/internal/httpserve/middleware" | 7 | "github.com/bnema/gordon/internal/httpserve/middleware" | ||
| 8 | - | "github.com/bnema/gordon/internal/templates/render" | 8 | + | "github.com/bnema/gordon/internal/templating/render" |
| 9 | "github.com/labstack/echo/v4" | 9 | "github.com/labstack/echo/v4" | ||
| 10 | ) | 10 | ) | ||
| 11 | 11 | ||||
M internal/httpserve/handler/containers.go +1 -1
| @@ -7,7 +7,7 @@ import ( | |||
| 7 | 7 | "time" | |
| 8 | 8 | ||
| 9 | 9 | "github.com/bnema/gordon/internal/app" | |
| 10 | - | "github.com/bnema/gordon/internal/templates/render" | |
| 10 | + | "github.com/bnema/gordon/internal/templating/render" | |
| 11 | 11 | "github.com/bnema/gordon/pkg/docker" | |
| 12 | 12 | "github.com/bnema/gordon/pkg/humanize" | |
| 13 | 13 | "github.com/docker/docker/api/types" | |
| @@ -7,7 +7,7 @@ import ( | |||||
| 7 | "time" | 7 | "time" | ||
| 8 | 8 | ||||
| 9 | "github.com/bnema/gordon/internal/app" | 9 | "github.com/bnema/gordon/internal/app" | ||
| 10 | - | "github.com/bnema/gordon/internal/templates/render" | 10 | + | "github.com/bnema/gordon/internal/templating/render" |
| 11 | "github.com/bnema/gordon/pkg/docker" | 11 | "github.com/bnema/gordon/pkg/docker" | ||
| 12 | "github.com/bnema/gordon/pkg/humanize" | 12 | "github.com/bnema/gordon/pkg/humanize" | ||
| 13 | "github.com/docker/docker/api/types" | 13 | "github.com/docker/docker/api/types" | ||
M internal/httpserve/handler/createcontainer.go +1 -1
| @@ -4,7 +4,7 @@ import ( | |||
| 4 | 4 | "fmt" | |
| 5 | 5 | ||
| 6 | 6 | "github.com/bnema/gordon/internal/app" | |
| 7 | - | "github.com/bnema/gordon/internal/templates/render" | |
| 7 | + | "github.com/bnema/gordon/internal/templating/render" | |
| 8 | 8 | "github.com/bnema/gordon/pkg/docker" | |
| 9 | 9 | "github.com/bnema/gordon/pkg/sanitize" | |
| 10 | 10 | "github.com/labstack/echo/v4" | |
| @@ -4,7 +4,7 @@ import ( | |||||
| 4 | "fmt" | 4 | "fmt" | ||
| 5 | 5 | ||||
| 6 | "github.com/bnema/gordon/internal/app" | 6 | "github.com/bnema/gordon/internal/app" | ||
| 7 | - | "github.com/bnema/gordon/internal/templates/render" | 7 | + | "github.com/bnema/gordon/internal/templating/render" |
| 8 | "github.com/bnema/gordon/pkg/docker" | 8 | "github.com/bnema/gordon/pkg/docker" | ||
| 9 | "github.com/bnema/gordon/pkg/sanitize" | 9 | "github.com/bnema/gordon/pkg/sanitize" | ||
| 10 | "github.com/labstack/echo/v4" | 10 | "github.com/labstack/echo/v4" | ||
M internal/httpserve/handler/editcontainer.go +1 -1
| @@ -6,7 +6,7 @@ import ( | |||
| 6 | 6 | "strings" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/gordon/internal/app" | |
| 9 | - | "github.com/bnema/gordon/internal/templates/render" | |
| 9 | + | "github.com/bnema/gordon/internal/templating/render" | |
| 10 | 10 | "github.com/bnema/gordon/pkg/docker" | |
| 11 | 11 | "github.com/labstack/echo/v4" | |
| 12 | 12 | "gopkg.in/yaml.v3" | |
| @@ -6,7 +6,7 @@ import ( | |||||
| 6 | "strings" | 6 | "strings" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/gordon/internal/app" | 8 | "github.com/bnema/gordon/internal/app" | ||
| 9 | - | "github.com/bnema/gordon/internal/templates/render" | 9 | + | "github.com/bnema/gordon/internal/templating/render" |
| 10 | "github.com/bnema/gordon/pkg/docker" | 10 | "github.com/bnema/gordon/pkg/docker" | ||
| 11 | "github.com/labstack/echo/v4" | 11 | "github.com/labstack/echo/v4" | ||
| 12 | "gopkg.in/yaml.v3" | 12 | "gopkg.in/yaml.v3" | ||
M internal/httpserve/handler/oauth.go +1 -1
| @@ -11,7 +11,7 @@ import ( | |||
| 11 | 11 | "github.com/bnema/gordon/internal/app" | |
| 12 | 12 | "github.com/bnema/gordon/internal/db" | |
| 13 | 13 | "github.com/bnema/gordon/internal/db/queries" | |
| 14 | - | "github.com/bnema/gordon/internal/templates/render" | |
| 14 | + | "github.com/bnema/gordon/internal/templating/render" | |
| 15 | 15 | "github.com/gorilla/sessions" | |
| 16 | 16 | _ "github.com/joho/godotenv/autoload" | |
| 17 | 17 | "github.com/labstack/echo-contrib/session" | |
| @@ -11,7 +11,7 @@ import ( | |||||
| 11 | "github.com/bnema/gordon/internal/app" | 11 | "github.com/bnema/gordon/internal/app" | ||
| 12 | "github.com/bnema/gordon/internal/db" | 12 | "github.com/bnema/gordon/internal/db" | ||
| 13 | "github.com/bnema/gordon/internal/db/queries" | 13 | "github.com/bnema/gordon/internal/db/queries" | ||
| 14 | - | "github.com/bnema/gordon/internal/templates/render" | 14 | + | "github.com/bnema/gordon/internal/templating/render" |
| 15 | "github.com/gorilla/sessions" | 15 | "github.com/gorilla/sessions" | ||
| 16 | _ "github.com/joho/godotenv/autoload" | 16 | _ "github.com/joho/godotenv/autoload" | ||
| 17 | "github.com/labstack/echo-contrib/session" | 17 | "github.com/labstack/echo-contrib/session" | ||
M internal/httpserve/handler/uploadcontainerimage.go +1 -1
| @@ -6,7 +6,7 @@ import ( | |||
| 6 | 6 | "net/http" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/gordon/internal/app" | |
| 9 | - | "github.com/bnema/gordon/internal/templates/render" | |
| 9 | + | "github.com/bnema/gordon/internal/templating/render" | |
| 10 | 10 | "github.com/bnema/gordon/pkg/store" | |
| 11 | 11 | "github.com/labstack/echo/v4" | |
| 12 | 12 | ) | |
| @@ -6,7 +6,7 @@ import ( | |||||
| 6 | "net/http" | 6 | "net/http" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/gordon/internal/app" | 8 | "github.com/bnema/gordon/internal/app" | ||
| 9 | - | "github.com/bnema/gordon/internal/templates/render" | 9 | + | "github.com/bnema/gordon/internal/templating/render" |
| 10 | "github.com/bnema/gordon/pkg/store" | 10 | "github.com/bnema/gordon/pkg/store" | ||
| 11 | "github.com/labstack/echo/v4" | 11 | "github.com/labstack/echo/v4" | ||
| 12 | ) | 12 | ) | ||
R internal/templates/embed.go → internal/templating/embed.go +1 -1 renamed, 90% similar
| @@ -1,4 +1,4 @@ | |||
| 1 | - | package templates | |
| 1 | + | package templating | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "embed" | |
| @@ -1,4 +1,4 @@ | |||||
| 1 | - | package templates | 1 | + | package templating |
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "embed" | 4 | "embed" | ||
R internal/templates/models/html/admin/dashboard.gohtml → internal/templating/models/html/admin/dashboard.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/admin/index.gohtml → internal/templating/models/html/admin/index.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/admin/install.gohtml → internal/templating/models/html/admin/install.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/admin/manager.gohtml → internal/templating/models/html/admin/manager.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/admin/user.gohtml → internal/templating/models/html/admin/user.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/fragments/components.gohtml → internal/templating/models/html/fragments/components.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/fragments/containerlist.gohtml → internal/templating/models/html/fragments/containerlist.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/fragments/createcontainer.gohtml → internal/templating/models/html/fragments/createcontainer.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/fragments/editcontainer.gohtml → internal/templating/models/html/fragments/editcontainer.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/fragments/footer.gohtml → internal/templating/models/html/fragments/footer.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/fragments/header.gohtml → internal/templating/models/html/fragments/header.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/fragments/imagelist.gohtml → internal/templating/models/html/fragments/imagelist.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/fragments/menu.gohtml → internal/templating/models/html/fragments/menu.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/fragments/uploadimage.gohtml → internal/templating/models/html/fragments/uploadimage.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/html/login/index.gohtml → internal/templating/models/html/login/index.gohtml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/models/locstrings.yml → internal/templating/models/locstrings.yml +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/render/cmdparams.go → internal/templating/render/cmdparams.go +0 -2 renamed, 99% similar
| @@ -10,7 +10,6 @@ import ( | |||
| 10 | 10 | ||
| 11 | 11 | // FromInputsToCmdParams transforms the inputs map into a ContainerCommandParams struct | |
| 12 | 12 | func FromInputsToCmdParams(inputs map[string]string, a *app.App) (docker.ContainerCommandParams, error) { | |
| 13 | - | ||
| 14 | 13 | volumeStr := inputs["volumes"] | |
| 15 | 14 | volumeSlice := strings.Split(volumeStr, ",") | |
| 16 | 15 | ||
| @@ -86,7 +85,6 @@ type YAMLContainerParams struct { | |||
| 86 | 85 | ||
| 87 | 86 | // FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct | |
| 88 | 87 | func FromYAMLStructToCmdParams(yamlParams YAMLContainerParams) (docker.ContainerCommandParams, error) { | |
| 89 | - | ||
| 90 | 88 | // with the image name find the image id | |
| 91 | 89 | imageID, err := docker.GetImageID(yamlParams.Image) | |
| 92 | 90 | if err != nil { | |
| @@ -10,7 +10,6 @@ import ( | |||||
| 10 | 10 | ||||
| 11 | // FromInputsToCmdParams transforms the inputs map into a ContainerCommandParams struct | 11 | // FromInputsToCmdParams transforms the inputs map into a ContainerCommandParams struct | ||
| 12 | func FromInputsToCmdParams(inputs map[string]string, a *app.App) (docker.ContainerCommandParams, error) { | 12 | func FromInputsToCmdParams(inputs map[string]string, a *app.App) (docker.ContainerCommandParams, error) { | ||
| 13 | - | ||||
| 14 | volumeStr := inputs["volumes"] | 13 | volumeStr := inputs["volumes"] | ||
| 15 | volumeSlice := strings.Split(volumeStr, ",") | 14 | volumeSlice := strings.Split(volumeStr, ",") | ||
| 16 | 15 | ||||
| @@ -86,7 +85,6 @@ type YAMLContainerParams struct { | |||||
| 86 | 85 | ||||
| 87 | // FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct | 86 | // FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct | ||
| 88 | func FromYAMLStructToCmdParams(yamlParams YAMLContainerParams) (docker.ContainerCommandParams, error) { | 87 | func FromYAMLStructToCmdParams(yamlParams YAMLContainerParams) (docker.ContainerCommandParams, error) { | ||
| 89 | - | ||||
| 90 | // with the image name find the image id | 88 | // with the image name find the image id | ||
| 91 | imageID, err := docker.GetImageID(yamlParams.Image) | 89 | imageID, err := docker.GetImageID(yamlParams.Image) | ||
| 92 | if err != nil { | 90 | if err != nil { | ||
R internal/templates/render/html.go → internal/templating/render/html.go +0 -0 renamed, 100% similar
renamed, 100% similar
R internal/templates/render/localization.go → internal/templating/render/localization.go +0 -0 renamed, 100% similar
renamed, 100% similar