-
Notifications
You must be signed in to change notification settings - Fork 155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement rollback for script run as pre defined stage #4743
Implement rollback for script run as pre defined stage #4743
Conversation
Signed-off-by: Yoshiki Fujikane <[email protected]>
Signed-off-by: Yoshiki Fujikane <[email protected]>
Signed-off-by: Yoshiki Fujikane <[email protected]>
Signed-off-by: Yoshiki Fujikane <[email protected]>
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## master #4743 +/- ##
==========================================
+ Coverage 30.80% 31.01% +0.21%
==========================================
Files 221 225 +4
Lines 26015 26325 +310
==========================================
+ Hits 8013 8164 +151
- Misses 17352 17510 +158
- Partials 650 651 +1 ☔ View full report in Codecov by Sentry. |
|
||
if rollbackStages, ok := s.deployment.FindRollbackStages(); ok { | ||
// Update to change deployment status to ROLLING_BACK. | ||
if err := s.reportDeploymentStatusChanged(ctx, model.DeploymentStatus_DEPLOYMENT_ROLLING_BACK, statusReason); err != nil { | ||
return err | ||
} | ||
|
||
// Start running rollback stage. | ||
var ( | ||
sig, handler = executor.NewStopSignal() | ||
doneCh = make(chan struct{}) | ||
) | ||
go func() { | ||
rbs := *stage | ||
rbs.Requires = []string{lastStage.Id} | ||
s.executeStage(sig, rbs, func(in executor.Input) (executor.Executor, bool) { | ||
return s.executorRegistry.RollbackExecutor(s.deployment.Kind, in) | ||
}) | ||
close(doneCh) | ||
}() | ||
|
||
select { | ||
case <-ctx.Done(): | ||
handler.Terminate() | ||
<-doneCh | ||
return nil | ||
|
||
case <-doneCh: | ||
break | ||
for _, stage := range rollbackStages { | ||
// Start running rollback stage. | ||
var ( | ||
sig, handler = executor.NewStopSignal() | ||
doneCh = make(chan struct{}) | ||
) | ||
go func() { | ||
rbs := *stage | ||
rbs.Requires = []string{lastStage.Id} | ||
s.executeStage(sig, rbs, func(in executor.Input) (executor.Executor, bool) { | ||
return s.executorRegistry.RollbackExecutor(s.deployment.Kind, in) | ||
}) | ||
close(doneCh) | ||
}() | ||
|
||
select { | ||
case <-ctx.Done(): | ||
handler.Terminate() | ||
<-doneCh | ||
return nil | ||
|
||
case <-doneCh: | ||
break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Changed to execute multiple kinds of rollback stages.
The changes are to do logic just like the same as before with each rollback stage.
if s.Name == model.StageScriptRun { | ||
// Use metadata as a way to pass parameters to the stage. | ||
envStr, _ := json.Marshal(s.ScriptRunStageOptions.Env) | ||
metadata := map[string]string{ | ||
"baseStageID": out[i].Id, | ||
"onRollback": s.ScriptRunStageOptions.OnRollback, | ||
"env": string(envStr), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 I use metadata for the stage to recognize below on executing it.
- The commands which are executed on the rollback script run stage. (onRollback, env)
- The stage IDs of script run stages, which are used to check whether to rollback it. (baseStageID)
- It is because I want to execute the command to rollback SCRIPT_RUN to the point where the deployment was canceled or failed.
if ps.Name == model.StageScriptRunRollback.String() { | ||
baseStageID := ps.Metadata["baseStageID"] | ||
if baseStageID == "" { | ||
return | ||
} | ||
|
||
baseStageStatus, ok := s.stageStatuses[baseStageID] | ||
if !ok { | ||
return | ||
} | ||
|
||
if baseStageStatus == model.StageStatus_STAGE_NOT_STARTED_YET || baseStageStatus == model.StageStatus_STAGE_SKIPPED { | ||
return | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Check the status of the script run stage to decide whether to rollback or not.
It is because the target stages should be already executed.
case model.StageScriptRunRollback: | ||
status = e.ensureScriptRunRollback(ctx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Added the logic to rollback executor for each application (firstly, I added on the k8s's one) because rollback stages are executed per the kind of application not per stage.
I don't have any idea to separate the rollback for application and the rollback for the stage.
The PR summary
The result apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
name: canary-with-script-run
labels:
env: example
team: product
pipeline:
stages:
- name: K8S_CANARY_ROLLOUT
with:
replicas: 10%
- name: WAIT
with:
duration: 10s
- name: SCRIPT_RUN
with:
env:
MSG: "execute script1"
R_MSG: "rollback script1"
run: |
echo $MSG
sleep 10
onRollback: |
echo $R_MSG
sleep 10
- name: K8S_PRIMARY_ROLLOUT
- name: K8S_CANARY_CLEAN
some problems |
/review |
PR AnalysisMain theme"Implement rollback for SCRIPT_RUN stages in deployment pipelines" PR summaryThis PR introduces the capability to handle rollback for custom script run stages within Kubernetes deployment pipelines. It ensures that if a deployment fails or is canceled, any executed SCRIPT_RUN stages are rolled back in the reverse order they were applied. Type of PREnhancement PR Feedback:General suggestionsThe PR implements rollbacks for SCRIPT_RUN stages, which is an important feature to maintain consistency and ensure clean reverts of deployments when failures occur. Expanding the rollback mechanism to include custom script stages is a valuable addition that helps in managing complex deployment scenarios. In the addition to the The Code feedback
Security concerns:no This PR doesn't seem to introduce any typical security concerns like SQL injection, XSS, CSRF, etc. However, the execution of external scripts does carry the inherent risk of executing malicious code if the scripts or the environment containing the hooks are compromised. Ensure that the rollback scripts are securely managed and reviewed to prevent such issues. |
@@ -171,3 +179,45 @@ func (e *rollbackExecutor) ensureRollback(ctx context.Context) model.StageStatus | |||
} | |||
return model.StageStatus_STAGE_SUCCESS | |||
} | |||
|
|||
func (e *rollbackExecutor) ensureScriptRunRollback(ctx context.Context) model.StageStatus { | |||
e.LogPersister.Infof("Runnnig commands for rollback...") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.LogPersister.Infof("Runnnig commands for rollback...") | |
e.LogPersister.Info("Runnnig commands for rollback...") |
if onRollback == "" { | ||
e.LogPersister.Info("No commands to run") | ||
return model.StageStatus_STAGE_SUCCESS | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just want to ensure I understand this right: This means that if there is no user-defined onRollback
script, this script run rollback will not do anything, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ffjlabo What I mean here is that we may make this:
- Add rollbackable of type bool to specify whether the current script run can be rollbacked or not, default set to false 🤔
- In case of rollbackable stage, if
onRollback
is not set, re-run script instead or return error rollback script is missing
wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the second thought, I got your idea.
- If users did not define
onRollback
script, then there is nothing to run - If users defined the
onRollback
script, then run that script
Let's keep this idea, thanks 👍
Signed-off-by: Yoshiki Fujikane <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm okay with this approach, let's rethink about making this script rollback kind independent later when we introduce app kind plugin pattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's go 🚀
@t-kikuc Please check 🙏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go 🚀
* Add ROLLBACK_SCRIPT_RUN stage and enable to plan itn Signed-off-by: Yoshiki Fujikane <[email protected]> * Enable to execute multiple rollback stages Signed-off-by: Yoshiki Fujikane <[email protected]> * Add script run rollback logic to k8s app Signed-off-by: Yoshiki Fujikane <[email protected]> * Fix rfc Signed-off-by: Yoshiki Fujikane <[email protected]> * Use log.Info Signed-off-by: Yoshiki Fujikane <[email protected]> --------- Signed-off-by: Yoshiki Fujikane <[email protected]>
What this PR does / why we need it:
Implement rollback for script run as pre defined stage.
Which issue(s) this PR fixes:
Part of #4643
Does this PR introduce a user-facing change?: Yes
How are users affected by this change:
Users can use
onRollback
feature forSCRIPT_RUN
stage.They can execute any command when the deployment failed and the executed
SCRIPT_RUN
stage is included in the pipelineIs this breaking change: no