Merge pull request #7504 from dokku/7396-scale-without-restart

Only redeploy formations that have had their values changed
This commit is contained in:
Jose Diaz-Gonzalez
2025-06-30 03:04:31 -04:00
committed by GitHub
4 changed files with 90 additions and 19 deletions

View File

@@ -247,33 +247,77 @@ func scaleReport(appName string) error {
return nil
}
func scaleSet(appName string, skipDeploy bool, clearExisting bool, processTuples []string) error {
formations, err := parseProcessTuples(processTuples)
// scaleSetInput is the input for the scaleSet function
type scaleSetInput struct {
// appName is the name of the app to scale
appName string
// skipDeploy is a flag to skip the deploy phase
skipDeploy bool
// clearExisting is a flag to clear the existing scale
clearExisting bool
// processTuples is a list of process tuples to scale
processTuples []string
// deployOnlyChanged is a flag to deploy only the changed formations
deployOnlyChanged bool
}
func scaleSet(input scaleSetInput) error {
existingFormations, err := getFormations(input.appName)
if err != nil {
return err
}
if err := updateScale(appName, clearExisting, formations); err != nil {
return err
}
if skipDeploy {
return nil
}
if !common.IsDeployed(appName) {
return nil
}
imageTag, err := common.GetRunningImageTag(appName, "")
formations, err := parseProcessTuples(input.processTuples)
if err != nil {
return err
}
for _, formation := range formations {
if err := updateScale(input.appName, input.clearExisting, formations); err != nil {
return err
}
if input.skipDeploy {
return nil
}
if !common.IsDeployed(input.appName) {
return nil
}
imageTag, err := common.GetRunningImageTag(input.appName, "")
if err != nil {
return err
}
changedFormations := FormationSlice{}
if input.deployOnlyChanged {
for _, formation := range formations {
isChanged := true
for _, existingFormation := range existingFormations {
if existingFormation.ProcessType == formation.ProcessType {
if existingFormation.Quantity == formation.Quantity {
isChanged = false
break
}
}
}
if isChanged {
changedFormations = append(changedFormations, formation)
}
}
} else {
changedFormations = formations
}
for _, formation := range changedFormations {
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "deploy",
Args: []string{appName, imageTag, formation.ProcessType},
Args: []string{input.appName, imageTag, formation.ProcessType},
StreamStdio: true,
})
if err != nil {

View File

@@ -160,7 +160,13 @@ func CommandScale(appName string, skipDeploy bool, processTuples []string) error
}
common.LogInfo1(fmt.Sprintf("Scaling %s processes: %s", appName, strings.Join(processTuples, " ")))
return scaleSet(appName, skipDeploy, false, processTuples)
return scaleSet(scaleSetInput{
appName: appName,
skipDeploy: skipDeploy,
clearExisting: false,
processTuples: processTuples,
deployOnlyChanged: true,
})
}
// CommandSet sets or clears a ps property for an app

View File

@@ -339,7 +339,13 @@ func TriggerPsCurrentScale(appName string) error {
// TriggerPsSetScale configures the scale parameters for a given app
func TriggerPsSetScale(appName string, skipDeploy bool, clearExisting bool, processTuples []string) error {
return scaleSet(appName, skipDeploy, clearExisting, processTuples)
return scaleSet(scaleSetInput{
appName: appName,
skipDeploy: skipDeploy,
clearExisting: clearExisting,
processTuples: processTuples,
deployOnlyChanged: false,
})
}
func TriggerPsGetProperty(appName string, property string) error {

View File

@@ -171,3 +171,18 @@ teardown() {
assert_success
done
}
@test "(ps:scale) dockerfile unchanged process" {
run deploy_app dockerfile
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku ps:scale $TEST_APP worker=2"
echo "output: $output"
echo "status: $status"
assert_output_contains "Scaling $TEST_APP processes: worker=2"
assert_output_contains "Deploying worker"
assert_output_contains "Deploying web" 0
assert_success
}