Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions src/loader/mutators.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@
}
}

func cloneReplicas(p *types.Project) {

Check failure on line 100 in src/loader/mutators.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 30 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=F1bonacc1_process-compose&issues=AZ5cvKbozxOyo7KkcvU6&open=AZ5cvKbozxOyo7KkcvU6&pullRequest=496
groupReplicas := make(map[string][]string)
procsToAdd := make([]types.ProcessConfig, 0)
procsToDel := make([]string, 0)
for name, proc := range p.Processes {
Expand All @@ -109,6 +110,7 @@
newProc.ReplicaNum = replica
repName := newProc.CalculateReplicaName()
newProc.ReplicaName = repName
groupReplicas[name] = append(groupReplicas[name], repName)
if proc.Replicas == 1 {
// Even if replicas == 1, we use newProc to ensure
// it has its own memory separate from any other references.
Expand All @@ -124,23 +126,67 @@
for _, proc := range procsToAdd {
p.Processes[proc.ReplicaName] = proc
}

// dependency rewrite when replicas are present
for name, proc := range p.Processes {
if len(proc.DependsOn) == 0 {
continue
}
newDependsOn := make(types.DependsOnConfig)
for depName, depConf := range proc.DependsOn {
if replicas, ok := groupReplicas[depName]; ok && len(replicas) > 1 {
for _, replicaName := range replicas {
// if the user explicitly addressed this replica, dont overwrite it
if _, exists := proc.DependsOn[replicaName]; !exists {
newDependsOn[replicaName] = cloneDependency(depConf)
}
}
} else {
newDependsOn[depName] = cloneDependency(depConf)
}
}
proc.DependsOn = newDependsOn
p.Processes[name] = proc
}
}

func cloneProcess(proc *types.ProcessConfig) *types.ProcessConfig {
// 1. Create a copy of the struct (Shallow copy)
newProc := *proc

// 2. DEEP COPY the Vars Map
maps.Copy(newProc.Vars, proc.Vars)
newProc.Vars = maps.Clone(proc.Vars)

// 3. DEEP COPY the Environment Slices
// 3. DEEP COPY the DependsOn Map
newProc.DependsOn = cloneDependencies(proc.DependsOn)

// 4. DEEP COPY the Environment Slices
newProc.Environment = slices.Clone(proc.Environment)
newProc.Args = slices.Clone(proc.Args)
newProc.Entrypoint = slices.Clone(proc.Entrypoint)

return &newProc
}

func cloneDependencies(deps types.DependsOnConfig) types.DependsOnConfig {
if deps == nil {
return nil
}
newDeps := make(types.DependsOnConfig, len(deps))
for k, v := range deps {
newDeps[k] = cloneDependency(v)
}
return newDeps
}

func cloneDependency(dep types.ProcessDependency) types.ProcessDependency {
newDep := dep
if dep.Extensions != nil {
newDep.Extensions = maps.Clone(dep.Extensions)
}
return newDep
}

func assignExecutableAndArgs(p *types.Project) {
elevatedShellArg := p.GetElevatedShellArg()
for name, proc := range p.Processes {
Expand Down
90 changes: 90 additions & 0 deletions src/loader/mutators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,96 @@ func Test_cloneReplicas(t *testing.T) {
}
}

func Test_cloneReplicas_DependsOn(t *testing.T) {
p := &types.Project{
Processes: types.Processes{
"p0": {
Name: "p0",
},
"p1": {
Name: "p1",
Replicas: 2,
},
"t0": {
Name: "t0",
DependsOn: types.DependsOnConfig{
"p0": {Condition: types.ProcessConditionStarted},
},
},
"t1": {
Name: "t1",
DependsOn: types.DependsOnConfig{
"p1": {Condition: types.ProcessConditionHealthy},
},
},
"t2": {
Name: "t2",
DependsOn: types.DependsOnConfig{
"p0": {Condition: types.ProcessConditionStarted},
"p1-0": {Condition: types.ProcessConditionHealthy},
},
},
"t3": {
Name: "t3",
DependsOn: types.DependsOnConfig{
"p1": {Condition: types.ProcessConditionStarted},
"p1-1": {Condition: types.ProcessConditionHealthy},
},
},
},
}
assignDefaultProcessValues(p)
cloneReplicas(p)

// depends on p0, no expansion needed
if len(p.Processes["t0"].DependsOn) != 1 {
t.Errorf("t0 should depend on a single process")
}
if _, ok := p.Processes["t0"].DependsOn["p0"]; !ok {
t.Errorf("t0 should depend on p0")
}

// depends on p1, expansion is needed
if len(p.Processes["t1"].DependsOn) != 2 {
t.Errorf("t1 should depend on exactly 2 processes")
}
if _, ok := p.Processes["t1"].DependsOn["p1-0"]; !ok {
t.Errorf("t1 should depend on p1-0")
}
if _, ok := p.Processes["t1"].DependsOn["p1-1"]; !ok {
t.Errorf("t1 should depend on p1-1")
}

// depends on p0 and p1-0, explicitly
if len(p.Processes["t2"].DependsOn) != 2 {
t.Errorf("t2 should depend on exactly 2 processes")
}
if _, ok := p.Processes["t2"].DependsOn["p0"]; !ok {
t.Errorf("t2 should depend on p0")
}
if p.Processes["t2"].DependsOn["p0"].Condition != types.ProcessConditionStarted {
condition := p.Processes["t2"].DependsOn["p0"].Condition
t.Errorf("t2 should depend on p0 and it should be Started, got %+v", condition)
}
if p.Processes["t2"].DependsOn["p1-0"].Condition != types.ProcessConditionHealthy {
condition := p.Processes["t2"].DependsOn["p1-0"].Condition
t.Errorf("t2 should depend on p1-0 and it should be Healthy, got %+v", condition)
}

// depends on p1, with an override for p1-1
if len(p.Processes["t3"].DependsOn) != 2 {
t.Errorf("t3 should depend on exactly 2 processes")
}
if p.Processes["t3"].DependsOn["p1-0"].Condition != types.ProcessConditionStarted {
condition := p.Processes["t3"].DependsOn["p1-0"].Condition
t.Errorf("t3 should depend on p1-0 and it should be Started, got %v", condition)
}
if p.Processes["t3"].DependsOn["p1-1"].Condition != types.ProcessConditionHealthy {
condition := p.Processes["t3"].DependsOn["p1-1"].Condition
t.Errorf("t3's dependency on p1-1 should be Healthy, got %v", condition)
}
}

func Test_renderTemplates(t *testing.T) {
procNoWorkingDir := "noWorkingDir"

Expand Down
53 changes: 52 additions & 1 deletion www/docs/launcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,57 @@ processes:
replicas: 2
```

When replicas are present (`processes.process_name.replicas > 1`), other processes can depend on the group (`process-name`) or specific replicas (`process-name-N` where `N` is a value between 0 and the number of `replicas` minus one).

```yaml
processes:
consumer:
command: "/some/binary"
replicas: 2

# Older versions (<= v1.110.0) require the dependencies to be manually expanded
producer:
command: "/some/other/binary"
depends_on:
consumer-0:
condition: process_healthy
consumer-1:
condition: process_healthy
```

```yaml
processes:
consumer:
command: "/some/binary"
replicas: 2

# Newer versions (> v1.110.0) allow the group name to be used directly
producer:
command: "/some/other/binary"
depends_on:
consumer:
condition: process_healthy
```

```yaml
processes:
consumer:
command: "/some/binary"
replicas: 2

# Also we can set conditions for each instance in the group independently
producer:
command: "/some/other/binary"
depends_on:
# set the defaults for the group
consumer:
condition: process_healthy

# override a single instance
consumer-0:
condition: process_started
```

To scale a process on the fly CLI:

```shell
Expand Down Expand Up @@ -101,7 +152,7 @@ processes:
echo 'Preparing...'
sleep 1
echo 'I am ready to accept connections now'
ready_log_line: "ready to accept connections" # equal to *.ready to accept connections.*\n regex
ready_log_line: "ready to accept connections" # equal to *.ready to accept connections.*\n regex
```

> :bulb: `ready_log_line` and readiness probe are incompatible and can't be used at the same time.
Expand Down
Loading