Skip to content

Commit

Permalink
feat: Add PC_REPLICA_NUM variable to each process
Browse files Browse the repository at this point in the history
  • Loading branch information
F1bonacc1 committed Dec 20, 2024
1 parent c0e29e4 commit d7e0380
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 40 deletions.
7 changes: 6 additions & 1 deletion process-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,13 @@ processes:
failure_threshold: 3

server:
command: "python3 -m http.server 4040"
command: "python3 -m http.server 404{{.PC_REPLICA_NUM}}"
is_tty: true
readiness_probe:
http_get:
host: "127.0.0.1"
port: "404{{.PC_REPLICA_NUM}}"
scheme: "http"

kcalc:
command: "kcalc"
Expand Down
25 changes: 18 additions & 7 deletions src/app/project_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package app

import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/f1bonacc1/process-compose/src/config"
"github.com/f1bonacc1/process-compose/src/health"
"github.com/f1bonacc1/process-compose/src/loader"
"github.com/f1bonacc1/process-compose/src/pclog"
"github.com/f1bonacc1/process-compose/src/templater"
"github.com/f1bonacc1/process-compose/src/types"
"os"
"os/user"
Expand Down Expand Up @@ -669,10 +671,19 @@ func (p *ProjectRunner) getCurrentReplicaCount(name string) int {

func (p *ProjectRunner) scaleUpProcess(proc types.ProcessConfig, toAdd, scale, origScale int) {
for i := 0; i < toAdd; i++ {
proc.ReplicaNum = origScale + i
proc.Replicas = scale
proc.ReplicaName = proc.CalculateReplicaName()
p.addProcessAndRun(proc)
var procFromConf types.ProcessConfig
err := json.Unmarshal([]byte(proc.OriginalConfig), &procFromConf)
if err != nil {
log.Err(err).Msgf("failed to unmarshal config for %s", proc.Name)
return
}
procFromConf.ReplicaNum = origScale + i
procFromConf.Replicas = scale
procFromConf.ReplicaName = procFromConf.CalculateReplicaName()
tpl := templater.New(p.project.Vars)
tpl.RenderProcess(&procFromConf)
procFromConf.AssignProcessExecutableAndArgs(p.project.ShellConfig, p.project.GetElevatedShellArg())
p.addProcessAndRun(procFromConf)
}
}

Expand Down Expand Up @@ -735,11 +746,11 @@ func (p *ProjectRunner) renameProcess(name string, newName string) {
state.Name = newName
p.processStates[newName] = state
}
config, ok := p.project.Processes[name]
procConf, ok := p.project.Processes[name]
if ok {
delete(p.project.Processes, name)
config.ReplicaName = newName
p.project.Processes[newName] = config
procConf.ReplicaName = newName
p.project.Processes[newName] = procConf
}
}
func (p *ProjectRunner) removeProcessLogs(name string) *pclog.ProcessLogBuffer {
Expand Down
32 changes: 2 additions & 30 deletions src/loader/mutators.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package loader
import (
"fmt"
"github.com/f1bonacc1/process-compose/src/command"
"github.com/f1bonacc1/process-compose/src/health"
"github.com/f1bonacc1/process-compose/src/templater"
"github.com/f1bonacc1/process-compose/src/types"
"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -115,10 +114,7 @@ func cloneReplicas(p *types.Project) {
}

func assignExecutableAndArgs(p *types.Project) {
elevatedShellArg := p.ShellConfig.ElevatedShellArg
if p.IsTuiDisabled {
elevatedShellArg = ""
}
elevatedShellArg := p.GetElevatedShellArg()
for name, proc := range p.Processes {
proc.AssignProcessExecutableAndArgs(p.ShellConfig, elevatedShellArg)

Expand All @@ -129,15 +125,7 @@ func assignExecutableAndArgs(p *types.Project) {
func renderTemplates(p *types.Project) error {
tpl := templater.New(p.Vars)
for name, proc := range p.Processes {
if len(p.Vars) == 0 && len(proc.Vars) == 0 {
continue
}
proc.Command = tpl.RenderWithExtraVars(proc.Command, proc.Vars)
proc.WorkingDir = tpl.RenderWithExtraVars(proc.WorkingDir, proc.Vars)
proc.LogLocation = tpl.RenderWithExtraVars(proc.LogLocation, proc.Vars)
proc.Description = tpl.RenderWithExtraVars(proc.Description, proc.Vars)
renderProbe(proc.ReadinessProbe, tpl, proc.Vars)
renderProbe(proc.LivenessProbe, tpl, proc.Vars)
tpl.RenderProcess(&proc)

if tpl.GetError() != nil {
return fmt.Errorf("error rendering template for process %s: %w", name, tpl.GetError())
Expand All @@ -146,19 +134,3 @@ func renderTemplates(p *types.Project) error {
}
return nil
}

func renderProbe(probe *health.Probe, tpl *templater.Templater, vars types.Vars) {
if probe == nil {
return
}

if probe.Exec != nil {
probe.Exec.Command = tpl.RenderWithExtraVars(probe.Exec.Command, vars)
} else if probe.HttpGet != nil {
probe.HttpGet.Path = tpl.RenderWithExtraVars(probe.HttpGet.Path, vars)
probe.HttpGet.Host = tpl.RenderWithExtraVars(probe.HttpGet.Host, vars)
probe.HttpGet.Scheme = tpl.RenderWithExtraVars(probe.HttpGet.Scheme, vars)
probe.HttpGet.Port = tpl.RenderWithExtraVars(probe.HttpGet.Port, vars)
}
probe.ValidateAndSetDefaults()
}
3 changes: 2 additions & 1 deletion src/loader/mutators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ func Test_renderTemplates(t *testing.T) {
for _, p := range tt.args.p.Processes {
switch tt.name {
case "no vars":
expected := "echo {{ .TEST }}"
expected := "echo <no value>"
compareStrings(t, expected, p.ReadinessProbe.Exec.Command, "readiness probe command")
compareStrings(t, expected, p.LivenessProbe.Exec.Command, "liveness probe command")
compareStrings(t, expected, p.Command, "process command")
Expand Down Expand Up @@ -480,6 +480,7 @@ func Test_renderTemplates(t *testing.T) {
}

func compareStrings(t *testing.T, expected, actual, scope string) {
t.Helper()
if expected != actual {
t.Errorf("Expected %s '%s' to be '%s'", scope, expected, actual)
}
Expand Down
37 changes: 37 additions & 0 deletions src/templater/templater.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package templater

import (
"bytes"
"encoding/json"
"github.com/f1bonacc1/process-compose/src/health"
"github.com/f1bonacc1/process-compose/src/types"
"github.com/rs/zerolog/log"
"maps"
"text/template"
)
Expand All @@ -16,6 +19,40 @@ func New(vars types.Vars) *Templater {
return &Templater{vars: vars}
}

func (t *Templater) RenderProcess(proc *types.ProcessConfig) {
if proc.Vars == nil {
proc.Vars = make(types.Vars)
}
procConf, err := json.Marshal(proc)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal process config")
}
proc.OriginalConfig = string(procConf)
proc.Vars["PC_REPLICA_NUM"] = proc.ReplicaNum
proc.Command = t.RenderWithExtraVars(proc.Command, proc.Vars)
proc.WorkingDir = t.RenderWithExtraVars(proc.WorkingDir, proc.Vars)
proc.LogLocation = t.RenderWithExtraVars(proc.LogLocation, proc.Vars)
proc.Description = t.RenderWithExtraVars(proc.Description, proc.Vars)
t.renderProbe(proc.ReadinessProbe, proc)
t.renderProbe(proc.LivenessProbe, proc)
}

func (t *Templater) renderProbe(probe *health.Probe, procConf *types.ProcessConfig) {
if probe == nil {
return
}

if probe.Exec != nil {
probe.Exec.Command = t.RenderWithExtraVars(probe.Exec.Command, procConf.Vars)
} else if probe.HttpGet != nil {
probe.HttpGet.Path = t.RenderWithExtraVars(probe.HttpGet.Path, procConf.Vars)
probe.HttpGet.Host = t.RenderWithExtraVars(probe.HttpGet.Host, procConf.Vars)
probe.HttpGet.Scheme = t.RenderWithExtraVars(probe.HttpGet.Scheme, procConf.Vars)
probe.HttpGet.Port = t.RenderWithExtraVars(probe.HttpGet.Port, procConf.Vars)
}
probe.ValidateAndSetDefaults()
}

func (t *Templater) Render(str string) string {
return t.render(str, nil)
}
Expand Down
14 changes: 14 additions & 0 deletions src/templater/templater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ func TestTemplater_RenderWithExtraVars(t *testing.T) {
t.Errorf("Expected %s but got %s", expected, result)
}
})
t.Run("Rendering with proc conf", func(t *testing.T) {
vars := types.Vars{"Name": "Alice"}

procConf := &types.ProcessConfig{
ReplicaNum: 3,
Command: "Name: {{.Name}}, Replica: {{.PC_REPLICA_NUM}}",
}
templater := New(vars)
templater.RenderProcess(procConf)
expected := "Name: Alice, Replica: 3"
if procConf.Command != expected {
t.Errorf("Expected %s but got %s", expected, procConf.Command)
}
})
}

func TestTemplater_GetError(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions src/types/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type ProcessConfig struct {
IsTty bool `yaml:"is_tty"`
IsElevated bool `yaml:"is_elevated"`
LaunchTimeout int `yaml:"launch_timeout_seconds"`
OriginalConfig string
ReplicaNum int
ReplicaName string
Executable string
Expand Down
8 changes: 8 additions & 0 deletions src/types/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ func (p *Project) GetLexicographicProcessNames() ([]string, error) {
return names, nil
}

func (p *Project) GetElevatedShellArg() string {
elevatedShellArg := p.ShellConfig.ElevatedShellArg
if p.IsTuiDisabled {
elevatedShellArg = ""
}
return elevatedShellArg
}

func (p *Project) GetProcesses(names ...string) ([]ProcessConfig, error) {
processes := []ProcessConfig{}
if len(names) == 0 {
Expand Down
18 changes: 17 additions & 1 deletion www/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,23 @@ processes:
command: '[ $(docker inspect -f {{ "{{.State.Running}}" }} nginx_test) = true ]'
```

> :bulb: For backward compatibility, if neither global nor local variables exist in `process-compose.yaml` the template engine won't run.
### Auto Inserted Variables

Process Compose will insert `PC_REPLICA_NUM` variable that will represent the replica number of the process. This will allow to conveniently scale processes using the following example configuration:

```yaml hl_lines="3 8"
processes:
server:
command: "python3 -m http.server 404{{.PC_REPLICA_NUM}}"
is_tty: true
readiness_probe:
http_get:
host: "127.0.0.1"
port: "404{{.PC_REPLICA_NUM}}"
scheme: "http"
```



## Specify which configuration files to use

Expand Down

0 comments on commit d7e0380

Please sign in to comment.