diff --git a/go.mod b/go.mod index 5e5cef2..f797eca 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/gin-gonic/gin v1.8.1 github.com/joho/godotenv v1.4.0 github.com/rivo/tview v0.0.0-20220916081518-2e69b7385a37 - github.com/swaggo/swag v1.8.6 + github.com/swaggo/swag v1.8.7 gopkg.in/yaml.v2 v2.4.0 ) @@ -18,7 +18,9 @@ replace github.com/InVisionApp/go-health/v2 => github.com/f1bonacc1/go-health/v2 require ( github.com/InVisionApp/go-logger v1.0.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/gdamore/encoding v1.0.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect @@ -39,9 +41,11 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/rivo/uniseg v0.4.2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/cobra v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/ugorji/go/codec v1.2.7 // indirect + github.com/urfave/cli/v2 v2.3.0 // indirect golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect golang.org/x/net v0.0.0-20220926192436-02166a98028e // indirect golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect diff --git a/go.sum b/go.sum index af4a0f5..a4b8eea 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -33,6 +34,7 @@ github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04= github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0= github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= @@ -153,6 +155,7 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -182,10 +185,13 @@ github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89 github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg= github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg= +github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU= +github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= diff --git a/src/api/pc_api.go b/src/api/pc_api.go index 3dd315c..a9396d6 100644 --- a/src/api/pc_api.go +++ b/src/api/pc_api.go @@ -97,3 +97,22 @@ func StartProcess(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"name": name}) } + +// @Schemes +// @Description Restarts the process +// @Tags Process +// @Summary Restart a process +// @Produce json +// @Param name path string true "Process Name" +// @Success 200 {string} string "Restarted Process Name" +// @Router /process/restart/{name} [post] +func RestartProcess(c *gin.Context) { + name := c.Param("name") + err := app.PROJ.RestartProcess(name) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"name": name}) +} diff --git a/src/api/routes.go b/src/api/routes.go index 0a6a205..969831c 100644 --- a/src/api/routes.go +++ b/src/api/routes.go @@ -36,6 +36,7 @@ func InitRoutes(useLogger bool) *gin.Engine { r.GET("/process/logs/:name/:endOffset/:limit", GetProcessLogs) r.PATCH("/process/stop/:name", StopProcess) r.POST("/process/start/:name", StartProcess) + r.POST("/process/restart/:name", RestartProcess) return r } diff --git a/src/app/process.go b/src/app/process.go index 3d71edf..6d2cd58 100644 --- a/src/app/process.go +++ b/src/app/process.go @@ -116,7 +116,7 @@ func (p *Process) run() error { p.waitForDaemonCompletion() } - if !p.isRestartable(p.procState.ExitCode) { + if !p.isRestartable() { break } p.setState(ProcessStateRestarting) @@ -149,7 +149,8 @@ func (p *Process) getProcessEnvironment() []string { return env } -func (p *Process) isRestartable(exitCode int) bool { +func (p *Process) isRestartable() bool { + exitCode := p.procState.ExitCode if p.procConf.RestartPolicy.Restart == RestartPolicyNo || p.procConf.RestartPolicy.Restart == "" { return false diff --git a/src/app/project.go b/src/app/project.go index e4d8251..39e2488 100644 --- a/src/app/project.go +++ b/src/app/project.go @@ -3,13 +3,12 @@ package app import ( "errors" "fmt" - "io/ioutil" + "github.com/f1bonacc1/process-compose/src/pclog" "os" "path/filepath" "sort" "strings" - - "github.com/f1bonacc1/process-compose/src/pclog" + "time" "github.com/joho/godotenv" "github.com/rs/zerolog" @@ -170,9 +169,9 @@ func (p *Project) StartProcess(name string) error { log.Error().Msgf("Process %s is already running", name) return fmt.Errorf("process %s is already running", name) } - if proc, ok := p.Processes[name]; ok { - proc.Name = name - p.runProcess(proc) + if processConfig, ok := p.Processes[name]; ok { + processConfig.Name = name + p.runProcess(processConfig) } else { return fmt.Errorf("no such process: %s", name) } @@ -190,6 +189,25 @@ func (p *Project) StopProcess(name string) error { return nil } +func (p *Project) RestartProcess(name string) error { + proc := p.getRunningProcess(name) + if proc != nil { + _ = proc.shutDown() + if proc.isRestartable() { + return nil + } + time.Sleep(proc.getBackoff()) + } + + if processConfig, ok := p.Processes[name]; ok { + processConfig.Name = name + p.runProcess(processConfig) + } else { + return fmt.Errorf("no such process: %s", name) + } + return nil +} + func (p *Project) ShutDownProject() { p.mapMutex.Lock() runProc := p.runningProcesses @@ -331,7 +349,7 @@ func (p *Project) GetLexicographicProcessNames() []string { } func CreateProject(inputFile string) *Project { - yamlFile, err := ioutil.ReadFile(inputFile) + yamlFile, err := os.ReadFile(inputFile) if err != nil { if errors.Is(err, os.ErrNotExist) { diff --git a/src/client/restart.go b/src/client/restart.go new file mode 100644 index 0000000..b7d1a6a --- /dev/null +++ b/src/client/restart.go @@ -0,0 +1,26 @@ +package client + +import ( + "encoding/json" + "fmt" + "github.com/rs/zerolog/log" + "net/http" +) + +func RestartProcesses(address string, port int, name string) error { + url := fmt.Sprintf("http://%s:%d/process/restart/%s", address, port, name) + resp, err := http.Post(url, "application/json", nil) + if err != nil { + return err + } + if resp.StatusCode == http.StatusOK { + return nil + } + defer resp.Body.Close() + var respErr pcError + if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil { + log.Error().Msgf("failed to decode restart process %s response: %v", name, err) + return err + } + return fmt.Errorf(respErr.Error) +} diff --git a/src/cmd/restart.go b/src/cmd/restart.go new file mode 100644 index 0000000..1e1b5b7 --- /dev/null +++ b/src/cmd/restart.go @@ -0,0 +1,30 @@ +/* +Copyright © 2022 NAME HERE +*/ +package cmd + +import ( + "github.com/f1bonacc1/process-compose/src/client" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +// restartCmd represents the restart command +var restartCmd = &cobra.Command{ + Use: "restart [PROCESS]", + Short: "Restart a process", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + name := args[0] + err := client.RestartProcesses(pcAddress, port, name) + if err != nil { + log.Error().Msgf("Failed to restart processes %s: %v", name, err) + return + } + log.Info().Msgf("Process %s restarted", name) + }, +} + +func init() { + processCmd.AddCommand(restartCmd) +} diff --git a/src/cmd/start.go b/src/cmd/start.go index 57e78d1..6ca2292 100644 --- a/src/cmd/start.go +++ b/src/cmd/start.go @@ -13,7 +13,7 @@ import ( // startCmd represents the start command var startCmd = &cobra.Command{ Use: "start [PROCESS]", - Short: "Start process", + Short: "Start a process", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { name := args[0] diff --git a/src/docs/docs.go b/src/docs/docs.go index 61d83d5..5c6c494 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -59,6 +59,35 @@ const docTemplate = `{ } } }, + "/process/restart/{name}": { + "post": { + "description": "Restarts the process", + "produces": [ + "application/json" + ], + "tags": [ + "Process" + ], + "summary": "Restart a process", + "parameters": [ + { + "type": "string", + "description": "Process Name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Restarted Process Name", + "schema": { + "type": "string" + } + } + } + } + }, "/process/start/{name}": { "post": { "description": "Starts the process if the state is not 'running' or 'pending'", diff --git a/src/docs/swagger.json b/src/docs/swagger.json index d401d31..6425873 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -47,6 +47,35 @@ } } }, + "/process/restart/{name}": { + "post": { + "description": "Restarts the process", + "produces": [ + "application/json" + ], + "tags": [ + "Process" + ], + "summary": "Restart a process", + "parameters": [ + { + "type": "string", + "description": "Process Name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Restarted Process Name", + "schema": { + "type": "string" + } + } + } + } + }, "/process/start/{name}": { "post": { "description": "Starts the process if the state is not 'running' or 'pending'", diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 3d5e335..eef1759 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -30,6 +30,25 @@ paths: summary: Get process logs tags: - Process + /process/restart/{name}: + post: + description: Restarts the process + parameters: + - description: Process Name + in: path + name: name + required: true + type: string + produces: + - application/json + responses: + "200": + description: Restarted Process Name + schema: + type: string + summary: Restart a process + tags: + - Process /process/start/{name}: post: description: Starts the process if the state is not 'running' or 'pending' diff --git a/src/tui/view.go b/src/tui/view.go index af8641f..831498c 100644 --- a/src/tui/view.go +++ b/src/tui/view.go @@ -216,6 +216,9 @@ func (pv *pcView) createProcTable() *tview.Table { case tcell.KeyF7: name := pv.getSelectedProcName() app.PROJ.StartProcess(name) + case tcell.KeyCtrlR: + name := pv.getSelectedProcName() + app.PROJ.RestartProcess(name) } return event }) @@ -294,6 +297,7 @@ func (pv *pcView) updateHelpTextView() { fmt.Fprintf(pv.helpText, "%s ", "F7[black:green]Start[-:-:-]") fmt.Fprintf(pv.helpText, "%s%s%s ", "F8[black:green]", procScr, " Screen[-:-:-]") fmt.Fprintf(pv.helpText, "%s ", "F9[black:green]Kill[-:-:-]") + fmt.Fprintf(pv.helpText, "%s ", "CTRL+R[black:green]Restart[-:-:-]") fmt.Fprintf(pv.helpText, "%s ", "F10[black:green]Quit[-:-:-]") }