Files
portainer/api/stacks/stackbuilders/director.go
T

94 lines
3.1 KiB
Go

package stackbuilders
import (
"context"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/stacks/stackutils"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/rs/zerolog/log"
)
// stackBuildProcess is the common interface shared by all stack build methods.
type stackBuildProcess interface {
setGeneralInfo(payload *StackPayload, endpoint *portainer.Endpoint)
// prepare handles all pre-save steps: sets type-specific metadata, stores
// files on disk, or clones the git repository.
prepare(ctx context.Context, payload *StackPayload, userID portainer.UserID) error
saveStack() (*portainer.Stack, error)
deploy(ctx context.Context, endpoint *portainer.Endpoint) error
// postDeploy runs after a successful deployment: for git builders it enables
// auto-update; for other builders it is a no-op.
postDeploy(ctx context.Context, stack *portainer.Stack) error
}
// Build executes the stack build process. It returns the created stack and any
// error encountered during the process. The returned error is of type
// *httperror.HandlerError, which could be an InternalServerError depending on
// the error encountered during the stack build process.
//
// The stack is saved to DB with Status=Deploying and returned immediately.
// Deployment runs in a background goroutine. The caller must poll
// GET /stacks/{id} to track completion.
func Build(ctx context.Context, dataStore dataservices.DataStore, builder stackBuildProcess, payload *StackPayload, endpoint *portainer.Endpoint, userID portainer.UserID) (*portainer.Stack, *httperror.HandlerError) {
builder.setGeneralInfo(payload, endpoint)
if err := builder.prepare(ctx, payload, userID); err != nil {
return nil, httperror.InternalServerError("Failed to prepare stack", err)
}
stack, err := builder.saveStack()
if err != nil {
return nil, httperror.InternalServerError("Failed to save stack", err)
}
go deploy(dataStore, builder, stack.ID, endpoint)
return stack, nil
}
func deploy(dataStore dataservices.DataStore, builder stackBuildProcess, stackID portainer.StackID, endpoint *portainer.Endpoint) {
backgroundCtx := context.Background()
ctx, cancel := context.WithTimeout(backgroundCtx, 15*time.Minute)
defer cancel()
deployErr := builder.deploy(ctx, endpoint)
var stack *portainer.Stack
if err := dataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
var err error
stack, err = tx.Stack().Read(stackID)
if err != nil {
return err
}
stackutils.UpdateStackStatusFromDeploymentResult(stack, deployErr)
return tx.Stack().Update(stack.ID, stack)
}); err != nil {
log.Error().Err(err).
AnErr("deploy_error", deployErr).
Int("stack_id", int(stackID)).
Str("context", "deploy").
Msg("Failed to update stack status after async deployment")
return
}
if deployErr != nil {
return
}
if err := builder.postDeploy(backgroundCtx, stack); err != nil {
log.Error().Err(err).
Int("stack_id", int(stackID)).
Str("context", "deploy").
Msg("Failed to run post-deployment hook")
}
}