94 lines
3.1 KiB
Go
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")
|
|
}
|
|
}
|