diff --git a/Makefile b/Makefile index b2cb4b1..39f4c9c 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ coverhtml: go tool cover -html=coverage.out run: - PC_DEBUG_MODE=1 ./bin/${NAME}${EXT} -e .env -e .env.local + PC_DEBUG_MODE=1 ./bin/${NAME}${EXT} -e .env clean: $(RM) bin/${NAME}* diff --git a/src/api/pc_api.go b/src/api/pc_api.go index 0005f80..067adb9 100644 --- a/src/api/pc_api.go +++ b/src/api/pc_api.go @@ -1,6 +1,7 @@ package api import ( + "github.com/f1bonacc1/process-compose/src/types" "net/http" "strconv" @@ -275,6 +276,31 @@ func (api *PcApi) ShutDownProject(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "stopped"}) } +// @Schemes +// @Description Update running project +// @Tags Project +// @Summary Updates running processes +// @Produce json +// @Success 200 +// @Router /project [post] +func (api *PcApi) UpdateProject(c *gin.Context) { + var project types.Project + if err := c.ShouldBindJSON(&project); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + status, err := api.project.UpdateProject(&project) + if err != nil { + if len(status) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } else { + c.JSON(http.StatusMultiStatus, status) + } + return + } + c.JSON(http.StatusOK, status) +} + // @Schemes // @Description Retrieves project state information // @Tags Project @@ -285,12 +311,12 @@ func (api *PcApi) ShutDownProject(c *gin.Context) { func (api *PcApi) GetProjectState(c *gin.Context) { withMemory := c.DefaultQuery("withMemory", "false") checkMem, _ := strconv.ParseBool(withMemory) - ports, err := api.project.GetProjectState(checkMem) + state, err := api.project.GetProjectState(checkMem) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, ports) + c.JSON(http.StatusOK, state) } diff --git a/src/api/routes.go b/src/api/routes.go index 2178af8..069aa9e 100644 --- a/src/api/routes.go +++ b/src/api/routes.go @@ -50,6 +50,7 @@ func InitRoutes(useLogger bool, handler *PcApi) *gin.Engine { r.POST("/process/start/:name", handler.StartProcess) r.POST("/process/restart/:name", handler.RestartProcess) r.POST("/project/stop", handler.ShutDownProject) + r.POST("/project", handler.UpdateProject) r.GET("/project/state", handler.GetProjectState) r.PATCH("/process/scale/:name/:scale", handler.ScaleProcess) diff --git a/src/app/project_interface.go b/src/app/project_interface.go index ff59fef..c02308f 100644 --- a/src/app/project_interface.go +++ b/src/app/project_interface.go @@ -29,4 +29,5 @@ type IProject interface { ScaleProcess(name string, scale int) error GetProcessPorts(name string) (*types.ProcessPorts, error) SetProcessPassword(name string, password string) error + UpdateProject(project *types.Project) (map[string]string, error) } diff --git a/src/app/project_runner.go b/src/app/project_runner.go index 60bac35..c27ed1b 100644 --- a/src/app/project_runner.go +++ b/src/app/project_runner.go @@ -2,6 +2,7 @@ package app import ( "context" + "errors" "fmt" "github.com/f1bonacc1/process-compose/src/config" "github.com/f1bonacc1/process-compose/src/pclog" @@ -842,13 +843,59 @@ func NewProjectRunner(opts *ProjectOpts) (*ProjectRunner, error) { return runner, nil } -//func getProcessName(process *Process) string { -// return process.getNameWithSmartReplica() -//} -// -//func getProcessNameFromConf(process types.ProcessConfig, replica int) string { -// if process.Replicas > 1 { -// return fmt.Sprintf("%s-%d", process.Name, replica) -// } -// return process.Name -//} +func (p *ProjectRunner) UpdateProject(project *types.Project) (map[string]string, error) { + newProcs := make(map[string]types.ProcessConfig) + delProcs := make(map[string]types.ProcessConfig) + updatedProcs := make(map[string]types.ProcessConfig) + for name, newProc := range project.Processes { + if currentProc, ok := p.project.Processes[name]; ok { + equal := currentProc.Compare(&newProc) + if equal { + log.Debug().Msgf("Process %s is up to date", name) + continue + } + log.Debug().Msgf("Process %s is updated", name) + updatedProcs[name] = newProc + } else { + log.Debug().Msgf("Process %s is new", name) + newProcs[name] = newProc + } + } + for name, currentProc := range p.project.Processes { + if _, ok := project.Processes[name]; !ok { + log.Debug().Msgf("Process %s is deleted", name) + delProcs[name] = currentProc + } + } + status := make(map[string]string) + errs := make([]error, 0) + //Delete removed processes + for name := range delProcs { + err := p.removeProcess(name) + if err != nil { + log.Err(err).Msgf("Failed to remove process %s", name) + errs = append(errs, err) + status[name] = types.ProcessUpdateError + continue + } + status[name] = types.ProcessUpdateRemoved + } + //Add new processes + for name, proc := range newProcs { + p.addProcessAndRun(proc) + status[name] = types.ProcessUpdateAdded + } + //Update processes + for name, proc := range updatedProcs { + err := p.removeProcess(name) + if err != nil { + log.Err(err).Msgf("Failed to remove process %s", name) + errs = append(errs, err) + status[name] = types.ProcessUpdateError + continue + } + p.addProcessAndRun(proc) + status[name] = types.ProcessUpdateUpdated + } + return status, errors.Join(errs...) +} diff --git a/src/app/system_test.go b/src/app/system_test.go index 80cde96..e88ac5f 100644 --- a/src/app/system_test.go +++ b/src/app/system_test.go @@ -639,3 +639,173 @@ func TestSystem_TestReadyLine(t *testing.T) { return } } + +func TestUpdateProject(t *testing.T) { + proc1 := "process1" + proc2 := "process2" + proc3 := "process3" + shell := command.DefaultShellConfig() + p, err := NewProjectRunner(&ProjectOpts{ + project: &types.Project{ + ShellConfig: shell, + Processes: map[string]types.ProcessConfig{ + "process1": { + Name: proc1, + ReplicaName: proc1, + Executable: shell.ShellCommand, + Args: []string{shell.ShellArgument, "echo process1"}, + Environment: []string{ + "VAR1=value1", + "VAR2=value2", + }, + }, + "process2": { + Name: proc2, + ReplicaName: proc2, + Executable: shell.ShellCommand, + Args: []string{shell.ShellArgument, "echo process2"}, + Environment: []string{ + "VAR3=value3", + "VAR4=value4", + }, + }, + }, + }, + }) + if err != nil { + t.Errorf(err.Error()) + return + } + go p.Run() + time.Sleep(100 * time.Millisecond) + + // Test when no changes are made + project := &types.Project{ + ShellConfig: shell, + Processes: map[string]types.ProcessConfig{ + "process1": { + Name: proc1, + ReplicaName: proc1, + Executable: shell.ShellCommand, + Args: []string{shell.ShellArgument, "echo process1"}, + Environment: []string{ + "VAR1=value1", + "VAR2=value2", + }, + }, + "process2": { + Name: proc2, + ReplicaName: proc2, + Executable: shell.ShellCommand, + Args: []string{shell.ShellArgument, "echo process2"}, + Environment: []string{ + "VAR3=value3", + "VAR4=value4", + }, + }, + }, + } + status, err := p.UpdateProject(project) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(status) != 0 { + t.Errorf("Unexpected status: %v", status) + } + + // Test when a process is updated + project = &types.Project{ + ShellConfig: shell, + Processes: map[string]types.ProcessConfig{ + "process1": { + Name: proc1, + ReplicaName: proc1, + Executable: shell.ShellCommand, + Args: []string{shell.ShellArgument, "echo updated"}, + Environment: []string{ + "VAR1=value1", + "VAR2=value2", + }, + }, + "process2": { + Name: proc2, + ReplicaName: proc2, + Executable: shell.ShellCommand, + Args: []string{shell.ShellArgument, "echo process2"}, + Environment: []string{ + "VAR3=value3", + "VAR4=value4", + }, + }, + }, + } + status, err = p.UpdateProject(project) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + proc, ok := p.project.Processes[proc1] + if !ok { + t.Errorf("Process 'process1' not found in updated project") + } + if proc.Args[1] != "echo updated" { + t.Errorf("Process 'process1' command is %s want 'echo updated'", proc.Args[1]) + } + updatedStatus := status[proc1] + if updatedStatus != types.ProcessUpdateUpdated { + t.Errorf("Process 'process1' status is %s want %s", updatedStatus, types.ProcessUpdateUpdated) + } + + // Test when a process is deleted + project = &types.Project{ + Processes: map[string]types.ProcessConfig{ + "process2": { + Name: proc2, + ReplicaName: proc2, + Executable: shell.ShellCommand, + Args: []string{shell.ShellArgument, "echo process2"}, + Environment: []string{ + "VAR3=value3", + "VAR4=value4", + }, + }, + }, + } + status, err = p.UpdateProject(project) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if _, ok = p.project.Processes[proc1]; ok { + t.Errorf("Process 'process1' still exists in updated project") + } + updatedStatus = status[proc1] + if updatedStatus != types.ProcessUpdateRemoved { + t.Errorf("Process 'process1' status is %s want %s", updatedStatus, types.ProcessUpdateRemoved) + } + + // Test when a new process is added + project = &types.Project{ + Processes: map[string]types.ProcessConfig{ + "process3": { + Name: proc3, + ReplicaName: proc3, + Executable: shell.ShellCommand, + Args: []string{shell.ShellArgument, "echo process3"}, + Environment: []string{ + "VAR5=value5", + "VAR6=value6", + }, + }, + }, + } + status, err = p.UpdateProject(project) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if _, ok = p.project.Processes[proc3]; !ok { + t.Errorf("Process 'process3' not found in updated project") + } + updatedStatus = status[proc3] + if updatedStatus != types.ProcessUpdateAdded { + t.Errorf("Process 'process1' status is %s want %s", updatedStatus, types.ProcessUpdateAdded) + } +} diff --git a/src/client/client.go b/src/client/client.go index b93b286..a2e2be6 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -163,3 +163,7 @@ func (p *PcClient) GetProjectState(withMemory bool) (*types.ProjectState, error) func (p *PcClient) SetProcessPassword(_, _ string) error { return fmt.Errorf("set process password not allowed for PC client") } + +func (p *PcClient) UpdateProject(project *types.Project) (map[string]string, error) { + return p.updateProject(project) +} diff --git a/src/client/project.go b/src/client/project.go index f8c6590..63529f3 100644 --- a/src/client/project.go +++ b/src/client/project.go @@ -1,6 +1,7 @@ package client import ( + "bytes" "encoding/json" "fmt" "github.com/f1bonacc1/process-compose/src/types" @@ -40,9 +41,40 @@ func (p *PcClient) getProjectState(withMemory bool) (*types.ProjectState, error) var sResp types.ProjectState //Decode the data - if err := json.NewDecoder(resp.Body).Decode(&sResp); err != nil { + if err = json.NewDecoder(resp.Body).Decode(&sResp); err != nil { log.Err(err).Msgf("failed to decode process states") return nil, err } return &sResp, nil } + +func (p *PcClient) updateProject(project *types.Project) (map[string]string, error) { + url := fmt.Sprintf("http://%s/project", p.address) + jsonData, err := json.Marshal(project) + if err != nil { + log.Err(err).Msg("failed to marshal project") + return nil, err + } + resp, err := p.client.Post(url, "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + log.Err(err).Msg("failed to update project") + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusMultiStatus { + status := map[string]string{} + if err = json.NewDecoder(resp.Body).Decode(&status); err != nil { + log.Err(err).Msg("failed to decode updated processes") + return status, err + } + log.Info().Msgf("status: %v", status) + + return status, nil + } + var respErr pcError + if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil { + log.Err(err).Msg("failed to decode err update project") + return nil, err + } + return nil, fmt.Errorf(respErr.Error) +} diff --git a/src/client/stop.go b/src/client/stop.go index 58e82a4..cf39976 100644 --- a/src/client/stop.go +++ b/src/client/stop.go @@ -59,7 +59,7 @@ func (p *PcClient) stopProcesses(names []string) (map[string]string, error) { } var respErr pcError if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil { - log.Err(err).Msgf("failed to decode err stop process %v", names) + log.Err(err).Msgf("failed to decode err stop processes %v", names) return nil, err } return nil, fmt.Errorf(respErr.Error) diff --git a/src/cmd/stop.go b/src/cmd/stop.go index abbf8eb..80be148 100644 --- a/src/cmd/stop.go +++ b/src/cmd/stop.go @@ -10,7 +10,7 @@ import ( ) var ( - verboseOutput = false + stopVerboseOutput = false ) // stopCmd represents the stop command @@ -24,7 +24,7 @@ var stopCmd = &cobra.Command{ log.Fatal().Err(err).Msgf("failed to stop processes %v", args) } - if verboseOutput { + if stopVerboseOutput { status, exitCode := prepareVerboseOutput(stopped, args) fmt.Print(status) os.Exit(exitCode) @@ -78,5 +78,5 @@ func prepareConciseOutput(stopped map[string]string, processes []string) (string func init() { processCmd.AddCommand(stopCmd) - stopCmd.Flags().BoolVarP(&verboseOutput, "verbose", "v", verboseOutput, "verbose output") + stopCmd.Flags().BoolVarP(&stopVerboseOutput, "verbose", "v", stopVerboseOutput, "verbose output") } diff --git a/src/cmd/update.go b/src/cmd/update.go new file mode 100644 index 0000000..6126e63 --- /dev/null +++ b/src/cmd/update.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "github.com/f1bonacc1/process-compose/src/config" + "github.com/f1bonacc1/process-compose/src/loader" + "github.com/f1bonacc1/process-compose/src/types" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +var ( + updateVerboseOutput = false +) + +// updateCmd represents the update command +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update an already running process-compose instance by passing an updated process-compose.yaml file", + Run: func(cmd *cobra.Command, args []string) { + runUpdateCmd(args) // TODO: add error + }, +} + +func runUpdateCmd(args []string) { + project, err := loader.Load(opts) + if err != nil { + log.Fatal().Err(err).Msg("Failed to load project") + } + status, err := getClient().UpdateProject(project) + if err != nil { + log.Fatal().Err(err).Msg("Failed to update project") + } + printStatus(status) +} + +func printStatus(updateStatus map[string]string) { + if len(updateStatus) == 0 { + fmt.Println("No processes were updated") + return + } + if updateVerboseOutput { + printStatusAsTable(updateStatus) + } else { + fmt.Println("Project updated successfully") + } + for name, status := range updateStatus { + log.Debug().Msgf("%s: %s", name, status) + } +} + +func printStatusAsTable(updateStatus map[string]string) { + colStatus := "STATUS" + colName := "PROCESS" + // Calculate column widths + maxNameWidth := len(colName) + for proc := range updateStatus { + if len(proc) > maxNameWidth { + maxNameWidth = len(proc) + } + } + fmt.Printf(" %-*s %s\n", maxNameWidth, colName, colStatus) + for proc, status := range updateStatus { + procWithIcon := getStatusIcon(status) + " " + proc + fmt.Printf("%-*s %s\n", maxNameWidth+2, procWithIcon, status) + } +} + +func getStatusIcon(status string) string { + switch status { + case types.ProcessUpdateUpdated: + return "↺" + case types.ProcessUpdateAdded: + return "▲" + case types.ProcessUpdateRemoved: + return "▼" + case types.ProcessUpdateError: + return "✘" + default: + return "?" + } +} + +func init() { + projectCmd.AddCommand(updateCmd) + updateCmd.Flags().StringArrayVarP(&opts.FileNames, "config", "f", config.GetConfigDefault(), "path to config files to load (env: "+config.EnvVarNameConfig+")") + updateCmd.Flags().BoolVarP(&updateVerboseOutput, "verbose", "v", updateVerboseOutput, "verbose output") + updateCmd.MarkFlagRequired("config") +} diff --git a/src/docs/docs.go b/src/docs/docs.go index 3728f3a..06f393f 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -357,6 +357,23 @@ const docTemplate = `{ } } }, + "/project": { + "post": { + "description": "Update running project", + "produces": [ + "application/json" + ], + "tags": [ + "Project" + ], + "summary": "Updates running processes", + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/project/state": { "get": { "description": "Retrieves project state information", diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 4b6c695..6c88715 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -345,6 +345,23 @@ } } }, + "/project": { + "post": { + "description": "Update running project", + "produces": [ + "application/json" + ], + "tags": [ + "Project" + ], + "summary": "Updates running processes", + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/project/state": { "get": { "description": "Retrieves project state information", diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 4c96609..cd01c8f 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -225,6 +225,17 @@ paths: summary: Stop processes tags: - Process + /project: + post: + description: Update running project + produces: + - application/json + responses: + "200": + description: OK + summary: Updates running processes + tags: + - Project /project/state: get: description: Retrieves project state information diff --git a/src/health/health_checks.go b/src/health/health_checks.go index 65cd2e0..a587fc5 100644 --- a/src/health/health_checks.go +++ b/src/health/health_checks.go @@ -23,7 +23,7 @@ type Prober struct { } func New(name string, probe Probe, onCheckEnd func(bool, bool, string)) (*Prober, error) { - probe.validateAndSetDefaults() + probe.ValidateAndSetDefaults() p := &Prober{ probe: probe, name: name, diff --git a/src/health/probe.go b/src/health/probe.go index 4f6aacd..058bef7 100644 --- a/src/health/probe.go +++ b/src/health/probe.go @@ -27,21 +27,21 @@ type HttpProbe struct { Path string `yaml:"path,omitempty"` Scheme string `yaml:"scheme,omitempty"` Port string `yaml:"port,omitempty"` - numPort int + NumPort int `yaml:"num_port,omitempty"` } func (h *HttpProbe) getUrl() (*url.URL, error) { urlStr := "" - if h.numPort != 0 { - urlStr = fmt.Sprintf("%s://%s:%d%s", h.Scheme, h.Host, h.numPort, h.Path) + if h.NumPort != 0 { + urlStr = fmt.Sprintf("%s://%s:%d%s", h.Scheme, h.Host, h.NumPort, h.Path) } - if h.numPort == 0 { + if h.NumPort == 0 { urlStr = fmt.Sprintf("%s://%s%s", h.Scheme, h.Host, h.Path) } return url.Parse(urlStr) } -func (p *Probe) validateAndSetDefaults() { +func (p *Probe) ValidateAndSetDefaults() { if p.InitialDelay < 0 { p.InitialDelay = 0 } @@ -74,12 +74,12 @@ func (p *HttpProbe) validateAndSetHttpDefaults() { p.Path = "/" } if p.Port == "" { - p.numPort = 0 + p.NumPort = 0 } else { - p.numPort, _ = strconv.Atoi(p.Port) + p.NumPort, _ = strconv.Atoi(p.Port) } - if p.numPort < 1 || p.numPort > 65535 { + if p.NumPort < 1 || p.NumPort > 65535 { // if undefined or wrong value - will be treated as undefined - p.numPort = 0 + p.NumPort = 0 } } diff --git a/src/health/probe_test.go b/src/health/probe_test.go index 457b5cf..c93e39e 100644 --- a/src/health/probe_test.go +++ b/src/health/probe_test.go @@ -78,7 +78,7 @@ func TestProbe_validateAndSetDefaults(t *testing.T) { SuccessThreshold: tt.fields.SuccessThreshold, FailureThreshold: tt.fields.FailureThreshold, } - p.validateAndSetDefaults() + p.ValidateAndSetDefaults() if p.InitialDelay != tt.want.InitialDelay { t.Errorf("Probe.InitialDelay = %v, want %v", p.InitialDelay, tt.want.InitialDelay) } diff --git a/src/loader/mutators.go b/src/loader/mutators.go index 52eeb11..2845ec7 100644 --- a/src/loader/mutators.go +++ b/src/loader/mutators.go @@ -162,4 +162,5 @@ func renderProbe(probe *health.Probe, tpl *templater.Templater, vars types.Vars) probe.HttpGet.Scheme = tpl.RenderWithExtraVars(probe.HttpGet.Scheme, vars) probe.HttpGet.Port = tpl.RenderWithExtraVars(probe.HttpGet.Port, vars) } + probe.ValidateAndSetDefaults() } diff --git a/src/pclog/process_log_buffer.go b/src/pclog/process_log_buffer.go index 2fb8842..c9c4882 100644 --- a/src/pclog/process_log_buffer.go +++ b/src/pclog/process_log_buffer.go @@ -86,5 +86,7 @@ func (b *ProcessLogBuffer) UnSubscribe(observer LogObserver) { } func (b *ProcessLogBuffer) Close() { + b.mx.Lock() + defer b.mx.Unlock() b.observers = map[string]LogObserver{} } diff --git a/src/types/process.go b/src/types/process.go index 1d1b1ed..b8b2de4 100644 --- a/src/types/process.go +++ b/src/types/process.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/f1bonacc1/process-compose/src/health" "math" + "reflect" "time" ) @@ -66,6 +67,68 @@ func (p *ProcessConfig) IsDeferred() bool { return p.IsForeground || p.Disabled } +// Compare returns true if two process configs are equal +func (p *ProcessConfig) Compare(another *ProcessConfig) bool { + if p == nil || another == nil { + return p == another + } + + // Compare simple fields + if p.Name != another.Name || + p.Disabled != another.Disabled || + p.IsDaemon != another.IsDaemon || + p.Command != another.Command || + p.LogLocation != another.LogLocation || + p.ReadyLogLine != another.ReadyLogLine || + p.DisableAnsiColors != another.DisableAnsiColors || + p.WorkingDir != another.WorkingDir || + p.Namespace != another.Namespace || + p.Replicas != another.Replicas || + p.Description != another.Description || + p.IsForeground != another.IsForeground || + p.IsTty != another.IsTty || + p.IsElevated != another.IsElevated { + return false + } + + if !reflect.DeepEqual(p.LoggerConfig, another.LoggerConfig) || + !reflect.DeepEqual(p.LivenessProbe, another.LivenessProbe) || + !reflect.DeepEqual(p.ReadinessProbe, another.ReadinessProbe) || + !reflect.DeepEqual(p.ShutDownParams, another.ShutDownParams) || + !reflect.DeepEqual(p.Vars, another.Vars) || + !reflect.DeepEqual(p.Extensions, another.Extensions) || + !reflect.DeepEqual(p.DependsOn, another.DependsOn) || + !reflect.DeepEqual(p.RestartPolicy, another.RestartPolicy) || + !reflect.DeepEqual(p.Environment, another.Environment) || + !reflect.DeepEqual(p.Args, another.Args) { + return false + } + + return true +} + +func compareStructs(a, b interface{}) []string { + var differences []string + aValue := reflect.ValueOf(a) + bValue := reflect.ValueOf(b) + + if aValue.Type() != bValue.Type() { + return []string{"Types are different"} + } + + for i := 0; i < aValue.NumField(); i++ { + aField := aValue.Field(i) + bField := bValue.Field(i) + fieldName := aValue.Type().Field(i).Name + + if !reflect.DeepEqual(aField.Interface(), bField.Interface()) { + differences = append(differences, fmt.Sprintf("Field %s differs: %v != %v", fieldName, aField, bField)) + } + } + + return differences +} + func NewProcessState(proc *ProcessConfig) *ProcessState { state := &ProcessState{ Name: proc.ReplicaName, @@ -100,9 +163,8 @@ type ProcessState struct { Pid int `json:"pid"` IsElevated bool `json:"is_elevated"` PasswordProvided bool `json:"password_provided"` - Mem int64 `json:"mem"` - IsRunning bool - + Mem int64 `json:"mem"` + IsRunning bool } type ProcessPorts struct { @@ -180,3 +242,10 @@ type ProcessDependency struct { Condition string `yaml:",omitempty"` Extensions map[string]interface{} `yaml:",inline"` } + +const ( + ProcessUpdateUpdated = "updated" + ProcessUpdateRemoved = "removed" + ProcessUpdateAdded = "added" + ProcessUpdateError = "error" +) diff --git a/src/types/process_test.go b/src/types/process_test.go new file mode 100644 index 0000000..d7ba399 --- /dev/null +++ b/src/types/process_test.go @@ -0,0 +1,130 @@ +package types + +import ( + "github.com/f1bonacc1/process-compose/src/health" + "testing" +) + +func TestCompareProcessConfigs(t *testing.T) { + tests := []struct { + name string + p *ProcessConfig + another *ProcessConfig + expected bool + }{ + { + name: "equal process configs", + p: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + }, + another: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + }, + expected: true, + }, + { + name: "inequal process configs (simple fields)", + p: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + }, + another: &ProcessConfig{ + Name: "test2", + Command: "cmd", + LogLocation: "log", + }, + expected: false, + }, + { + name: "inequal process configs (complex fields)", + p: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + LoggerConfig: &LoggerConfig{ + TimestampFormat: "format", + }, + }, + another: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + LoggerConfig: &LoggerConfig{ + TimestampFormat: "format2", + }, + }, + expected: false, + }, + { + name: "equal process configs with nil fields", + p: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + LoggerConfig: nil, + }, + another: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + LoggerConfig: nil, + }, + expected: true, + }, + { + name: "inequal process configs with one nil and one non-nil field", + p: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + LoggerConfig: nil, + }, + another: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + LoggerConfig: &LoggerConfig{ + TimestampFormat: "format", + }, + }, + expected: false, + }, + { + name: "inequal process configs with probes", + p: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + ReadinessProbe: &health.Probe{ + Exec: &health.ExecProbe{ + Command: "echo 1", + }, + }, + }, + another: &ProcessConfig{ + Name: "test", + Command: "cmd", + LogLocation: "log", + ReadinessProbe: &health.Probe{ + Exec: &health.ExecProbe{ + Command: "echo 2", + }, + }, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.p.Compare(tt.another); got != tt.expected { + t.Errorf("Compare() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/www/docs/cli/process-compose.md b/www/docs/cli/process-compose.md index 1ccb839..5416ba7 100644 --- a/www/docs/cli/process-compose.md +++ b/www/docs/cli/process-compose.md @@ -44,4 +44,4 @@ process-compose [flags] * [process-compose up](process-compose_up.md) - Run process compose project * [process-compose version](process-compose_version.md) - Print version and build info -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_attach.md b/www/docs/cli/process-compose_attach.md index 9b7f607..0eb7db5 100644 --- a/www/docs/cli/process-compose_attach.md +++ b/www/docs/cli/process-compose_attach.md @@ -34,4 +34,4 @@ process-compose attach [flags] * [process-compose](process-compose.md) - Processes scheduler and orchestrator -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_completion.md b/www/docs/cli/process-compose_completion.md index cfb55be..9dd4d79 100644 --- a/www/docs/cli/process-compose_completion.md +++ b/www/docs/cli/process-compose_completion.md @@ -34,4 +34,4 @@ See each sub-command's help for details on how to use the generated script. * [process-compose completion powershell](process-compose_completion_powershell.md) - Generate the autocompletion script for powershell * [process-compose completion zsh](process-compose_completion_zsh.md) - Generate the autocompletion script for zsh -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_completion_bash.md b/www/docs/cli/process-compose_completion_bash.md index fc927df..d5e68e9 100644 --- a/www/docs/cli/process-compose_completion_bash.md +++ b/www/docs/cli/process-compose_completion_bash.md @@ -53,4 +53,4 @@ process-compose completion bash * [process-compose completion](process-compose_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_completion_fish.md b/www/docs/cli/process-compose_completion_fish.md index 6b27648..5897179 100644 --- a/www/docs/cli/process-compose_completion_fish.md +++ b/www/docs/cli/process-compose_completion_fish.md @@ -44,4 +44,4 @@ process-compose completion fish [flags] * [process-compose completion](process-compose_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_completion_powershell.md b/www/docs/cli/process-compose_completion_powershell.md index f88c137..100894a 100644 --- a/www/docs/cli/process-compose_completion_powershell.md +++ b/www/docs/cli/process-compose_completion_powershell.md @@ -41,4 +41,4 @@ process-compose completion powershell [flags] * [process-compose completion](process-compose_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_completion_zsh.md b/www/docs/cli/process-compose_completion_zsh.md index 4aae42a..1c60715 100644 --- a/www/docs/cli/process-compose_completion_zsh.md +++ b/www/docs/cli/process-compose_completion_zsh.md @@ -55,4 +55,4 @@ process-compose completion zsh [flags] * [process-compose completion](process-compose_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_down.md b/www/docs/cli/process-compose_down.md index 0aaf97c..0d52a46 100644 --- a/www/docs/cli/process-compose_down.md +++ b/www/docs/cli/process-compose_down.md @@ -29,4 +29,4 @@ process-compose down [flags] * [process-compose](process-compose.md) - Processes scheduler and orchestrator -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_info.md b/www/docs/cli/process-compose_info.md index 9a92257..7867bd4 100644 --- a/www/docs/cli/process-compose_info.md +++ b/www/docs/cli/process-compose_info.md @@ -28,4 +28,4 @@ process-compose info [flags] * [process-compose](process-compose.md) - Processes scheduler and orchestrator -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_process.md b/www/docs/cli/process-compose_process.md index 7c808fd..19981f1 100644 --- a/www/docs/cli/process-compose_process.md +++ b/www/docs/cli/process-compose_process.md @@ -32,4 +32,4 @@ Execute operations on the available processes * [process-compose process start](process-compose_process_start.md) - Start a process * [process-compose process stop](process-compose_process_stop.md) - Stop running processes -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_process_list.md b/www/docs/cli/process-compose_process_list.md index 6903b4c..e56d145 100644 --- a/www/docs/cli/process-compose_process_list.md +++ b/www/docs/cli/process-compose_process_list.md @@ -30,4 +30,4 @@ process-compose process list [flags] * [process-compose process](process-compose_process.md) - Execute operations on the available processes -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_process_logs.md b/www/docs/cli/process-compose_process_logs.md index 808104c..90a13d4 100644 --- a/www/docs/cli/process-compose_process_logs.md +++ b/www/docs/cli/process-compose_process_logs.md @@ -31,4 +31,4 @@ process-compose process logs [PROCESS] [flags] * [process-compose process](process-compose_process.md) - Execute operations on the available processes -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_process_ports.md b/www/docs/cli/process-compose_process_ports.md index aa0f382..7203d88 100644 --- a/www/docs/cli/process-compose_process_ports.md +++ b/www/docs/cli/process-compose_process_ports.md @@ -29,4 +29,4 @@ process-compose process ports [PROCESS] [flags] * [process-compose process](process-compose_process.md) - Execute operations on the available processes -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_process_restart.md b/www/docs/cli/process-compose_process_restart.md index 9ec8ca3..8069eab 100644 --- a/www/docs/cli/process-compose_process_restart.md +++ b/www/docs/cli/process-compose_process_restart.md @@ -29,4 +29,4 @@ process-compose process restart [PROCESS] [flags] * [process-compose process](process-compose_process.md) - Execute operations on the available processes -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_process_scale.md b/www/docs/cli/process-compose_process_scale.md index b9cb45d..b9e3140 100644 --- a/www/docs/cli/process-compose_process_scale.md +++ b/www/docs/cli/process-compose_process_scale.md @@ -29,4 +29,4 @@ process-compose process scale [PROCESS] [COUNT] [flags] * [process-compose process](process-compose_process.md) - Execute operations on the available processes -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_process_start.md b/www/docs/cli/process-compose_process_start.md index 6b5149d..c989ebf 100644 --- a/www/docs/cli/process-compose_process_start.md +++ b/www/docs/cli/process-compose_process_start.md @@ -29,4 +29,4 @@ process-compose process start [PROCESS] [flags] * [process-compose process](process-compose_process.md) - Execute operations on the available processes -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_process_stop.md b/www/docs/cli/process-compose_process_stop.md index a86ac34..1aa96e0 100644 --- a/www/docs/cli/process-compose_process_stop.md +++ b/www/docs/cli/process-compose_process_stop.md @@ -30,4 +30,4 @@ process-compose process stop [PROCESS...] [flags] * [process-compose process](process-compose_process.md) - Execute operations on the available processes -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_project.md b/www/docs/cli/process-compose_project.md index 68a73dd..c4ad737 100644 --- a/www/docs/cli/process-compose_project.md +++ b/www/docs/cli/process-compose_project.md @@ -25,5 +25,6 @@ Execute operations on a running Process Compose project * [process-compose](process-compose.md) - Processes scheduler and orchestrator * [process-compose project state](process-compose_project_state.md) - Get Process Compose project state +* [process-compose project update](process-compose_project_update.md) - Update an already running process-compose instance by passing an updated process-compose.yaml file -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_project_state.md b/www/docs/cli/process-compose_project_state.md index 9070785..3ae3ce1 100644 --- a/www/docs/cli/process-compose_project_state.md +++ b/www/docs/cli/process-compose_project_state.md @@ -30,4 +30,4 @@ process-compose project state [flags] * [process-compose project](process-compose_project.md) - Execute operations on a running Process Compose project -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_project_update.md b/www/docs/cli/process-compose_project_update.md new file mode 100644 index 0000000..7386e00 --- /dev/null +++ b/www/docs/cli/process-compose_project_update.md @@ -0,0 +1,34 @@ +## process-compose project update + +Update an already running process-compose instance by passing an updated process-compose.yaml file + +``` +process-compose project update [flags] +``` + +### Options + +``` + -f, --config stringArray path to config files to load (env: PC_CONFIG_FILES) + -h, --help help for update + -v, --verbose verbose output +``` + +### Options inherited from parent commands + +``` + -a, --address string address of the target process compose server (default "localhost") + -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") + --no-server disable HTTP server (env: PC_NO_SERVER) + --ordered-shutdown shut down processes in reverse dependency order + -p, --port int port number (env: PC_PORT_NUM) (default 8080) + --read-only enable read-only mode (env: PC_READ_ONLY) + -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") + -U, --use-uds use unix domain sockets instead of tcp +``` + +### SEE ALSO + +* [process-compose project](process-compose_project.md) - Execute operations on a running Process Compose project + +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_run.md b/www/docs/cli/process-compose_run.md index 7133d7f..7f4564e 100644 --- a/www/docs/cli/process-compose_run.md +++ b/www/docs/cli/process-compose_run.md @@ -36,4 +36,4 @@ process-compose run PROCESS [flags] -- [process_args] * [process-compose](process-compose.md) - Processes scheduler and orchestrator -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_up.md b/www/docs/cli/process-compose_up.md index bd86202..65d69b7 100644 --- a/www/docs/cli/process-compose_up.md +++ b/www/docs/cli/process-compose_up.md @@ -47,4 +47,4 @@ process-compose up [PROCESS...] [flags] * [process-compose](process-compose.md) - Processes scheduler and orchestrator -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024 diff --git a/www/docs/cli/process-compose_version.md b/www/docs/cli/process-compose_version.md index 4f9ebe9..509fe98 100644 --- a/www/docs/cli/process-compose_version.md +++ b/www/docs/cli/process-compose_version.md @@ -28,4 +28,4 @@ process-compose version [flags] * [process-compose](process-compose.md) - Processes scheduler and orchestrator -###### Auto generated by spf13/cobra on 17-Aug-2024 +###### Auto generated by spf13/cobra on 13-Sep-2024