From 79322c4c5e752de3d23a7a8c0cc61c74abeca762 Mon Sep 17 00:00:00 2001 From: Zoe <62722391+juls0730@users.noreply.github.com> Date: Sun, 13 Apr 2025 00:53:23 -0500 Subject: [PATCH] improve cli code --- README.md | 30 +++++-- cmd/flux/commands/delete.go | 145 +++++++++++++++++++------------- cmd/flux/commands/deploy.go | 37 ++++++--- cmd/flux/commands/init.go | 23 +++--- cmd/flux/commands/list.go | 13 +-- cmd/flux/commands/project.go | 2 +- cmd/flux/commands/start.go | 14 +--- cmd/flux/commands/stop.go | 14 +--- cmd/flux/main.go | 155 +++++++++++++++++++++-------------- cmd/flux/models/cmd.go | 8 ++ cmd/flux/models/writers.go | 5 +- internal/server/deploy.go | 10 ++- pkg/version.go | 2 +- 13 files changed, 265 insertions(+), 193 deletions(-) create mode 100644 cmd/flux/models/cmd.go diff --git a/README.md b/README.md index fd1e02a..243536f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Flux is a lightweight self-hosted pseudo-PaaS for hosting Golang web apps with e - Simple but powerful configuration, flux should be able to handle most use cases, from a micro web app to a fullstack app with databases, caching layers, full text search, etc. **Limitations**: -- Theoretically only supports up to 1023 containers (roughly 500 apps assuming 2 containers per app), this is because flux uses the same bridge network for all containers (this could theoretically be increased if flux was smart enough to create new networks once we hit the max, but this is not a priority) +- Theoretically flux is likely limited by the amount of containers can fit in the bridge network, but I haven't tested this - Containers are not particularly isolated, if one malicious container wanted to scan all containers, or interact with other containers it tectically shouldnt, it totally just can (todo?) ## Features @@ -77,7 +77,6 @@ After=network.target ExecStart=/usr/local/bin/fluxd Restart=always Environment=GOPATH=/var/fluxd/go -Environment=HOME=/var/fluxd/home [Install] WantedBy=multi-user.target @@ -150,18 +149,33 @@ flux.json is the configuration file in the root of your proejct that defines dep "name": "my-app", "url": "myapp.example.com", "port": 8080, + "containers": [ + { + "name": "redis", + "image": "redis:latest", + "volumes": [ + { + "mountpoint": "/data" + } + ], + } + ], "env_file": ".env", "environment": ["DEBUG=true"] } ``` -#### Configuration Options +The project config files has the following options: -- `name`: The name of the project -- `url`: Domain for the application -- `port`: Web server's listening port -- `env_file`: Path to environment variable file -- `environment`: Additional environment variables +| field | description | required | +| ----- | ----------- | -------- | +| `name` | The name of the project | true | +| `url` | Domain for the application | true | +| `port` | Web server's listening port | true | +| `env_file` | Path to environment variable file | false | +| `environment` | Additional environment variables | false | +| `containers` | Supplemental containers to run alongside the app | false | +| `volumes` | Volumes to mount to the app's containers | false | ## Deployment Notes diff --git a/cmd/flux/commands/delete.go b/cmd/flux/commands/delete.go index 22592ea..ab98c1f 100644 --- a/cmd/flux/commands/delete.go +++ b/cmd/flux/commands/delete.go @@ -1,92 +1,121 @@ package commands import ( + "bytes" + "flag" "fmt" "io" "net/http" + "os" "strings" - "github.com/briandowns/spinner" "github.com/juls0730/flux/cmd/flux/models" - "github.com/juls0730/flux/pkg" ) -func DeleteCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error { - if seekingHelp { - fmt.Println(`Usage: - flux delete [project-name | all] +var usage = `Usage: + flux delete [project-name | all] - Options: - project-name: The name of the project to delete - all: Delete all projects - - Flux will delete the deployment of the app in the current directory or the specified project.`) - return nil - } +Options: + project-name: The name of the project to delete + all: Delete all projects - if len(args) == 1 { - if args[0] == "all" { - var response string - fmt.Print("Are you sure you want to delete all projects? this will delete all volumes and containers associated and cannot be undone. \n[y/N] ") - fmt.Scanln(&response) +Flags: +%s - if strings.ToLower(response) != "y" { - fmt.Println("Aborting...") - return nil - } +Flux will delete the deployment of the app in the current directory or the specified project. +` - response = "" +func deleteAll(ctx models.CommandCtx, noConfirm *bool) error { + if !*noConfirm { + var response string + fmt.Print("Are you sure you want to delete all projects? this will delete all volumes and containers associated and cannot be undone. [y/N] ") + fmt.Scanln(&response) - fmt.Printf("Are you really sure you want to delete all projects? \n[y/N] ") - fmt.Scanln(&response) + if strings.ToLower(response) != "y" { + fmt.Println("Aborting...") + return nil + } - if strings.ToLower(response) != "y" { - fmt.Println("Aborting...") - return nil - } + response = "" - req, err := http.NewRequest("DELETE", config.DeamonURL+"/deployments", nil) - if err != nil { - return fmt.Errorf("failed to delete deployments: %v", err) - } - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("failed to delete deployments: %v", err) - } - defer resp.Body.Close() + // since we are deleting **all** projects, I feel better asking for confirmation twice + fmt.Printf("Are you really sure you want to delete all projects? [y/N] ") + fmt.Scanln(&response) - if resp.StatusCode != http.StatusOK { - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("error reading response body: %v", err) - } - - responseBody = []byte(strings.TrimSuffix(string(responseBody), "\n")) - - return fmt.Errorf("delete failed: %s", responseBody) - } - - fmt.Printf("Successfully deleted all projects\n") + if strings.ToLower(response) != "y" { + fmt.Println("Aborting...") return nil } } + req, err := http.NewRequest("DELETE", ctx.Config.DeamonURL+"/deployments", nil) + if err != nil { + return fmt.Errorf("failed to delete deployments: %v", err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("failed to delete deployments: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("error reading response body: %v", err) + } + + responseBody = []byte(strings.TrimSuffix(string(responseBody), "\n")) + + return fmt.Errorf("delete failed: %s", responseBody) + } + + fmt.Printf("Successfully deleted all projects\n") + return nil +} + +func DeleteCommand(ctx models.CommandCtx, args []string) error { + fs := flag.NewFlagSet("delete", flag.ExitOnError) + fs.Usage = func() { + var buf bytes.Buffer + // Redirect flagset to print to buffer instead of stdout + fs.SetOutput(&buf) + fs.PrintDefaults() + + fmt.Printf(usage, strings.TrimRight(buf.String(), "\n")) + } + + noConfirm := fs.Bool("no-confirm", false, "Skip confirmation prompt") + + err := fs.Parse(args) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + args = fs.Args() + + if len(args) == 1 && args[0] == "all" { + return deleteAll(ctx, noConfirm) + } + projectName, err := GetProjectName("delete", args) if err != nil { - return err + return fmt.Errorf("\tfailed to get project name: %v.\n\tSee flux delete --help for more information", err) } // ask for confirmation - fmt.Printf("Are you sure you want to delete %s? this will delete all volumes and containers associated with the deployment, and cannot be undone. \n[y/N] ", projectName) - var response string - fmt.Scanln(&response) + if !*noConfirm { + fmt.Printf("Are you sure you want to delete %s? this will delete all volumes and containers associated with the deployment, and cannot be undone. \n[y/N] ", projectName) + var response string + fmt.Scanln(&response) - if strings.ToLower(response) != "y" { - fmt.Println("Aborting...") - return nil + if strings.ToLower(response) != "y" { + fmt.Println("Aborting...") + return nil + } } - req, err := http.NewRequest("DELETE", config.DeamonURL+"/deployments/"+projectName, nil) + req, err := http.NewRequest("DELETE", ctx.Config.DeamonURL+"/deployments/"+projectName, nil) if err != nil { return fmt.Errorf("failed to delete app: %v", err) } diff --git a/cmd/flux/commands/deploy.go b/cmd/flux/commands/deploy.go index efddfb2..761fd7c 100644 --- a/cmd/flux/commands/deploy.go +++ b/cmd/flux/commands/deploy.go @@ -1,4 +1,3 @@ - package commands import ( @@ -12,9 +11,11 @@ import ( "mime/multipart" "net/http" "os" + "os/signal" "path/filepath" "regexp" "strings" + "time" "github.com/briandowns/spinner" "github.com/juls0730/flux/cmd/flux/models" @@ -152,23 +153,35 @@ func compressDirectory(compression pkg.Compression) ([]byte, error) { return buf.Bytes(), nil } -func DeployCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error { - if seekingHelp { - fmt.Println(`Usage: - flux deploy - - Flux will deploy the app in the current directory, and start routing traffic to it.`) - return nil - } - +func DeployCommand(ctx models.CommandCtx, args []string) error { if _, err := os.Stat("flux.json"); err != nil { return fmt.Errorf("no flux.json found, please run flux init first") } + spinnerWriter := models.NewCustomSpinnerWriter() + + loadingSpinner := spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(spinnerWriter)) + defer func() { + if loadingSpinner.Active() { + loadingSpinner.Stop() + } + }() + + signalChannel := make(chan os.Signal, 1) + signal.Notify(signalChannel, os.Interrupt) + go func() { + <-signalChannel + if loadingSpinner.Active() { + loadingSpinner.Stop() + } + + os.Exit(0) + }() + loadingSpinner.Suffix = " Deploying" loadingSpinner.Start() - buf, err := compressDirectory(info.Compression) + buf, err := compressDirectory(ctx.Info.Compression) if err != nil { return fmt.Errorf("failed to compress directory: %v", err) } @@ -204,7 +217,7 @@ func DeployCommand(seekingHelp bool, config models.Config, info pkg.Info, loadin return fmt.Errorf("failed to close writer: %v", err) } - req, err := http.NewRequest("POST", config.DeamonURL+"/deploy", body) + req, err := http.NewRequest("POST", ctx.Config.DeamonURL+"/deploy", body) req.Header.Set("Content-Type", writer.FormDataContentType()) if err != nil { diff --git a/cmd/flux/commands/init.go b/cmd/flux/commands/init.go index 5398fc4..cebc640 100644 --- a/cmd/flux/commands/init.go +++ b/cmd/flux/commands/init.go @@ -7,22 +7,21 @@ import ( "strconv" "strings" - "github.com/briandowns/spinner" "github.com/juls0730/flux/cmd/flux/models" "github.com/juls0730/flux/pkg" ) -func InitCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error { - if seekingHelp { - fmt.Println(`Usage: - flux init [project-name] - - Options: - project-name: The name of the project to initialize - - Flux will initialize a new project in the current directory or the specified project.`) - return nil - } +func InitCommand(ctx models.CommandCtx, args []string) error { + // if seekingHelp { + // fmt.Println(`Usage: + // flux init [project-name] + + // Options: + // project-name: The name of the project to initialize + + // Flux will initialize a new project in the current directory or the specified project.`) + // return nil + // } var projectConfig pkg.ProjectConfig diff --git a/cmd/flux/commands/list.go b/cmd/flux/commands/list.go index a5cd8a2..bce5a2c 100644 --- a/cmd/flux/commands/list.go +++ b/cmd/flux/commands/list.go @@ -7,21 +7,12 @@ import ( "net/http" "strings" - "github.com/briandowns/spinner" "github.com/juls0730/flux/cmd/flux/models" "github.com/juls0730/flux/pkg" ) -func ListCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error { - if seekingHelp { - fmt.Println(`Usage: - flux list - - Flux will list all the apps in the daemon.`) - return nil - } - - resp, err := http.Get(config.DeamonURL + "/apps") +func ListCommand(ctx models.CommandCtx, args []string) error { + resp, err := http.Get(ctx.Config.DeamonURL + "/apps") if err != nil { return fmt.Errorf("failed to get apps: %v", err) } diff --git a/cmd/flux/commands/project.go b/cmd/flux/commands/project.go index 93a2c94..dcf33d0 100644 --- a/cmd/flux/commands/project.go +++ b/cmd/flux/commands/project.go @@ -13,7 +13,7 @@ func GetProjectName(command string, args []string) (string, error) { if len(args) == 0 { if _, err := os.Stat("flux.json"); err != nil { - return "", fmt.Errorf("usage: flux %[1]s , or run flux %[1]s in the project directory", command) + return "", fmt.Errorf("the current directory is not a flux project, please run flux %[1]s in the project directory", command) } fluxConfigFile, err := os.Open("flux.json") diff --git a/cmd/flux/commands/start.go b/cmd/flux/commands/start.go index 0da29a4..6658325 100644 --- a/cmd/flux/commands/start.go +++ b/cmd/flux/commands/start.go @@ -6,26 +6,16 @@ import ( "net/http" "strings" - "github.com/briandowns/spinner" "github.com/juls0730/flux/cmd/flux/models" - "github.com/juls0730/flux/pkg" ) -func StartCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error { - if seekingHelp { - fmt.Println(`Usage: - flux start - - Flux will start the deployment of the app in the current directory.`) - return nil - } - +func StartCommand(ctx models.CommandCtx, args []string) error { projectName, err := GetProjectName("start", args) if err != nil { return err } - req, err := http.Post(config.DeamonURL+"/start/"+projectName, "application/json", nil) + req, err := http.Post(ctx.Config.DeamonURL+"/start/"+projectName, "application/json", nil) if err != nil { return fmt.Errorf("failed to start app: %v", err) } diff --git a/cmd/flux/commands/stop.go b/cmd/flux/commands/stop.go index 6e7a12c..910988a 100644 --- a/cmd/flux/commands/stop.go +++ b/cmd/flux/commands/stop.go @@ -6,26 +6,16 @@ import ( "net/http" "strings" - "github.com/briandowns/spinner" "github.com/juls0730/flux/cmd/flux/models" - "github.com/juls0730/flux/pkg" ) -func StopCommand(seekingHelp bool, config models.Config, info pkg.Info, loadingSpinner *spinner.Spinner, spinnerWriter *models.CustomSpinnerWriter, args []string) error { - if seekingHelp { - fmt.Println(`Usage: - flux stop - - Flux will stop the deployment of the app in the current directory.`) - return nil - } - +func StopCommand(ctx models.CommandCtx, args []string) error { projectName, err := GetProjectName("stop", args) if err != nil { return err } - req, err := http.Post(config.DeamonURL+"/stop/"+projectName, "application/json", nil) + req, err := http.Post(ctx.Config.DeamonURL+"/stop/"+projectName, "application/json", nil) if err != nil { return fmt.Errorf("failed to stop app: %v", err) } diff --git a/cmd/flux/main.go b/cmd/flux/main.go index c76592e..20c834d 100644 --- a/cmd/flux/main.go +++ b/cmd/flux/main.go @@ -3,16 +3,14 @@ package main import ( _ "embed" "encoding/json" + "flag" "fmt" "net/http" "os" - "os/signal" "path/filepath" "strings" - "time" "github.com/agnivade/levenshtein" - "github.com/briandowns/spinner" "github.com/juls0730/flux/cmd/flux/commands" "github.com/juls0730/flux/cmd/flux/models" "github.com/juls0730/flux/pkg" @@ -29,62 +27,93 @@ var helpStr = `Usage: flux Available Commands: - init Initialize a new project - deploy Deploy a new version of the app - stop Stop a container - start Start a container - delete Delete a container - list List all containers +%s -Flags: - -h, --help help for flux +Available Flags: + --help, -h: Show this help message -Use "flux --help" for more information about a command.` +Use "flux --help" for more information about a command. +` var maxDistance = 3 +type CommandFunc func(models.CommandCtx, []string) error + +type Command struct { + Help string + HandlerFunc CommandFunc +} + type CommandHandler struct { - commands map[string]func(bool, models.Config, pkg.Info, *spinner.Spinner, *models.CustomSpinnerWriter, []string) error + commands map[string]Command + aliases map[string]string } -func (h *CommandHandler) RegisterCmd(name string, handler func(bool, models.Config, pkg.Info, *spinner.Spinner, *models.CustomSpinnerWriter, []string) error) { - h.commands[name] = handler +func NewCommandHandler() CommandHandler { + return CommandHandler{ + commands: make(map[string]Command), + aliases: make(map[string]string), + } } -func runCommand(command string, args []string, config models.Config, info pkg.Info, cmdHandler CommandHandler, try int) error { - if try == 2 { - return fmt.Errorf("unknown command: %s", command) +func (h *CommandHandler) RegisterCmd(name string, handler CommandFunc, help string) { + coomand := Command{ + Help: help, + HandlerFunc: handler, } - seekingHelp := false - if len(args) > 0 && (args[len(args)-1] == "--help" || args[len(args)-1] == "-h") { - seekingHelp = true - args = args[:len(args)-1] + h.commands[name] = coomand +} + +func (h *CommandHandler) RegisterAlias(alias string, command string) { + h.aliases[alias] = command +} + +// returns the command and whether or not it exists +func (h *CommandHandler) GetCommand(command string) (Command, bool) { + if command, ok := h.aliases[command]; ok { + return h.commands[command], true } - spinnerWriter := models.NewCustomSpinnerWriter() + commandStruct, ok := h.commands[command] + return commandStruct, ok +} - loadingSpinner := spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(spinnerWriter)) - defer func() { - if loadingSpinner.Active() { - loadingSpinner.Stop() - } - }() +var helpPadding = 13 - signalChannel := make(chan os.Signal, 1) - signal.Notify(signalChannel, os.Interrupt) - go func() { - <-signalChannel - if loadingSpinner.Active() { - loadingSpinner.Stop() +func (h *CommandHandler) GetHelp() { + commandsStr := "" + for command := range h.commands { + curLine := "" + + curLine += command + for alias, aliasCommand := range h.aliases { + if aliasCommand == command { + curLine += fmt.Sprintf(", %s", alias) + } } - os.Exit(0) - }() + curLine += strings.Repeat(" ", helpPadding-(len(curLine)-2)) + commandsStr += fmt.Sprintf(" %s %s\n", curLine, h.commands[command].Help) + } - handler, ok := cmdHandler.commands[command] + fmt.Printf(helpStr, strings.TrimRight(commandsStr, "\n")) +} + +func (h *CommandHandler) GetHelpCmd(models.CommandCtx, []string) error { + h.GetHelp() + return nil +} + +func runCommand(command string, args []string, config models.Config, info pkg.Info, cmdHandler CommandHandler) error { + commandCtx := models.CommandCtx{ + Config: config, + Info: info, + } + + commandStruct, ok := cmdHandler.commands[command] if ok { - return handler(seekingHelp, config, info, loadingSpinner, spinnerWriter, args) + return commandStruct.HandlerFunc(commandCtx, args) } // diff the command against the list of commands and if we find a command that is more than 80% similar, ask if that's what the user meant @@ -108,7 +137,8 @@ func runCommand(command string, args []string, config models.Config, info pkg.In } var response string - fmt.Printf("No command found with the name '%s'. Did you mean '%s'?\n", command, closestMatch.name) + // new line ommitted because it will be produced when the user presses enter to submit their response + fmt.Printf("No command found with the name '%s'. Did you mean '%s'? (y/N)", command, closestMatch.name) fmt.Scanln(&response) if strings.ToLower(response) == "y" || strings.ToLower(response) == "yes" { @@ -117,18 +147,34 @@ func runCommand(command string, args []string, config models.Config, info pkg.In return nil } - return runCommand(command, args, config, info, cmdHandler, try+1) + // re-run command after accepting the suggestion + return runCommand(command, args, config, info, cmdHandler) } func main() { - if len(os.Args) < 2 { - fmt.Println(helpStr) + cmdHandler := NewCommandHandler() + + cmdHandler.RegisterCmd("init", commands.InitCommand, "Initialize a new project") + cmdHandler.RegisterCmd("deploy", commands.DeployCommand, "Deploy a new version of the app") + cmdHandler.RegisterCmd("start", commands.StartCommand, "Start the app") + cmdHandler.RegisterCmd("stop", commands.StopCommand, "Stop the app") + cmdHandler.RegisterCmd("list", commands.ListCommand, "List all the apps") + cmdHandler.RegisterCmd("delete", commands.DeleteCommand, "Delete the app") + + fs := flag.NewFlagSet("flux", flag.ExitOnError) + fs.Usage = func() { + cmdHandler.GetHelp() + } + + err := fs.Parse(os.Args[1:]) + if err != nil { + fmt.Println(err) os.Exit(1) } - if os.Args[1] == "--help" || os.Args[1] == "-h" { - fmt.Println(helpStr) - os.Exit(0) + if len(os.Args) < 2 { + cmdHandler.GetHelp() + os.Exit(1) } if _, err := os.Stat(filepath.Join(configPath, "config.json")); err != nil { @@ -155,9 +201,6 @@ func main() { os.Exit(1) } - command := os.Args[1] - args := os.Args[2:] - resp, err := http.Get(config.DeamonURL + "/heartbeat") if err != nil { fmt.Println("Failed to connect to daemon") @@ -186,19 +229,9 @@ func main() { os.Exit(1) } - cmdHandler := CommandHandler{ - commands: make(map[string]func(bool, models.Config, pkg.Info, *spinner.Spinner, *models.CustomSpinnerWriter, []string) error), - } - - cmdHandler.RegisterCmd("deploy", commands.DeployCommand) - cmdHandler.RegisterCmd("stop", commands.StopCommand) - cmdHandler.RegisterCmd("start", commands.StartCommand) - cmdHandler.RegisterCmd("delete", commands.DeleteCommand) - cmdHandler.RegisterCmd("init", commands.InitCommand) - - err = runCommand(command, args, config, info, cmdHandler, 0) + err = runCommand(os.Args[1], fs.Args()[1:], config, info, cmdHandler) if err != nil { - fmt.Printf("%v\n", err) + fmt.Printf("Error: %v\n", err) os.Exit(1) } } diff --git a/cmd/flux/models/cmd.go b/cmd/flux/models/cmd.go new file mode 100644 index 0000000..25e3baa --- /dev/null +++ b/cmd/flux/models/cmd.go @@ -0,0 +1,8 @@ +package models + +import "github.com/juls0730/flux/pkg" + +type CommandCtx struct { + Config Config + Info pkg.Info +} diff --git a/cmd/flux/models/writers.go b/cmd/flux/models/writers.go index f5d542c..76a3877 100644 --- a/cmd/flux/models/writers.go +++ b/cmd/flux/models/writers.go @@ -44,11 +44,14 @@ func NewCustomStdout(spinner *CustomSpinnerWriter) *CustomStdout { } } +// We have this custom writer because we want to have a spinner at the bottom of the terminal, but we dont want to have +// it interfere with the output of the command func (w *CustomStdout) Write(p []byte) (n int, err error) { w.lock.Lock() defer w.lock.Unlock() - n, err = os.Stdout.Write([]byte(fmt.Sprintf("\033[2K\r%s", p))) + // clear line and carriage return + n, err = os.Stdout.Write(fmt.Appendf(nil, "\033[2K\r%s", p)) if err != nil { return n, err } diff --git a/internal/server/deploy.go b/internal/server/deploy.go index f05e663..c6d029b 100644 --- a/internal/server/deploy.go +++ b/internal/server/deploy.go @@ -43,7 +43,8 @@ func NewDeploymentLock() *DeploymentLock { } } -func (dt *DeploymentLock) StartDeployment(appName string, ctx context.Context) (context.Context, error) { +// This function will lock a deployment based on an app name so that the same app cannot be deployed twice simultaneously +func (dt *DeploymentLock) LockDeployment(appName string, ctx context.Context) (context.Context, error) { dt.mu.Lock() defer dt.mu.Unlock() @@ -61,7 +62,8 @@ func (dt *DeploymentLock) StartDeployment(appName string, ctx context.Context) ( return ctx, nil } -func (dt *DeploymentLock) CompleteDeployment(appName string) { +// This function will unlock a deployment based on an app name so that the same app can be deployed again (you would call this after a deployment has completed) +func (dt *DeploymentLock) UnlockDeployment(appName string) { dt.mu.Lock() defer dt.mu.Unlock() @@ -114,7 +116,7 @@ func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) { return } - ctx, err := deploymentLock.StartDeployment(projectConfig.Name, r.Context()) + ctx, err := deploymentLock.LockDeployment(projectConfig.Name, r.Context()) if err != nil { // This will happen if the app is already being deployed http.Error(w, err.Error(), http.StatusConflict) @@ -123,7 +125,7 @@ func (s *FluxServer) DeployHandler(w http.ResponseWriter, r *http.Request) { go func() { <-ctx.Done() - deploymentLock.CompleteDeployment(projectConfig.Name) + deploymentLock.UnlockDeployment(projectConfig.Name) }() flusher, ok := w.(http.Flusher) diff --git a/pkg/version.go b/pkg/version.go index aa527e5..15fd0b7 100644 --- a/pkg/version.go +++ b/pkg/version.go @@ -1,3 +1,3 @@ package pkg -const Version = "2bd953d" +const Version = "2025.04.13-05"