Add ability to restart a process

This commit is contained in:
Berger Eugene 2022-10-24 00:52:36 +03:00
parent c8bb6879dd
commit 5b95c22da4
13 changed files with 197 additions and 11 deletions

6
go.mod
View File

@ -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

6
go.sum
View File

@ -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=

View File

@ -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})
}

View File

@ -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
}

View File

@ -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

View File

@ -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) {

26
src/client/restart.go Normal file
View File

@ -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)
}

30
src/cmd/restart.go Normal file
View File

@ -0,0 +1,30 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
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)
}

View File

@ -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]

View File

@ -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'",

View File

@ -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'",

View File

@ -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'

View File

@ -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[-:-:-]")
}