refactor(stack): change stack update flow to async model [BE-12741] (#2306)

This commit is contained in:
Oscar Zhou
2026-04-22 10:05:17 +12:00
committed by GitHub
parent 7e544ee449
commit 463d539194
28 changed files with 490 additions and 162 deletions
@@ -57,6 +57,7 @@ func (payload *stackGitRedeployPayload) Validate(r *http.Request) error {
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 "Not found"
// @failure 409 "Conflict"
// @failure 500 "Server error"
// @router /stacks/{id}/git/redeploy [put]
func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
@@ -76,6 +77,10 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
return httperror.BadRequest("Stack is not created from git", err)
}
if stack.Status == portainer.StackStatusDeploying {
return httperror.Conflict("Unable to update stack", errors.New("Stack deployment is already in progress"))
}
// TODO: this is a work-around for stacks created with Portainer version >= 1.17.1
// The EndpointID property is not available for these stacks, this API environment(endpoint)
// can use the optional EndpointID query parameter to associate a valid environment(endpoint) identifier to the stack.
@@ -178,7 +183,8 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
defer clean()
if err := handler.deployStack(r, stack, payload.RepullImageAndRedeploy, endpoint); err != nil {
deployGate := newDeployGate()
if err := handler.deployStack(r, stack, payload.RepullImageAndRedeploy, endpoint, deployGate); err != nil {
return err
}
@@ -202,18 +208,17 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
stack.UpdatedBy = user.Username
stack.UpdateDate = time.Now().Unix()
stack.Status = portainer.StackStatusActive
// TODO: move to async job when stack update becomes async
stack.DeploymentStatus = []portainer.StackDeploymentStatus{
{Status: portainer.StackStatusActive, Time: time.Now().Unix()},
}
stackutils.PrepareStackStatusForDeployment(stack)
if err := handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
return tx.Stack().Update(stack.ID, stack)
}); err != nil {
deployGate.abortDeploy()
return httperror.InternalServerError("Unable to persist the stack changes inside the database", errors.Wrap(err, "failed to update the stack"))
}
deployGate.startDeploy()
if stack.GitConfig != nil && stack.GitConfig.Authentication != nil && stack.GitConfig.Authentication.Password != "" {
// Sanitize password in the http response to minimise possible security leaks
stack.GitConfig.Authentication.Password = ""
@@ -222,7 +227,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
return response.JSON(w, stack)
}
func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, pullImage bool, endpoint *portainer.Endpoint) *httperror.HandlerError {
func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, pullImage bool, endpoint *portainer.Endpoint, gate *deployGate) *httperror.HandlerError {
var deploymentConfiger deployments.StackDeploymentConfiger
switch stack.Type {
@@ -281,9 +286,7 @@ func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, pul
return httperror.InternalServerError("Unsupported stack", errors.Errorf("unsupported stack type: %v", stack.Type))
}
if err := deploymentConfiger.Deploy(context.TODO()); err != nil {
return httperror.InternalServerError(err.Error(), err)
}
go stackDeploy(handler.DataStore, stack.ID, deploymentConfiger, gate, nil)
return nil
}