hmdeploy/deployers/commons.go
2025-04-30 14:26:15 +02:00

143 lines
3.3 KiB
Go

package deployers
import (
"context"
"errors"
"sync/atomic"
"time"
"gitea.thegux.fr/hmdeploy/models"
"github.com/rs/zerolog/log"
)
var ErrContextDone = errors.New("unable to execute, context done")
type IDeployer interface {
Type() DeployerType
Deploy() error
Build() error
Destroy() error
Clear() error
Error() error
Done() <-chan struct{}
}
type DeployerType string
const (
Nginx DeployerType = "nginx"
Swarm DeployerType = "swarm"
GracefulTimeout = 10 * time.Second
)
// Base struct of the deployers.
// It handles the main informations to build a deployer.
//
// "Inherited" deployers must implement three methods in order
// to satify the `IDeployer` contract:
// - `Deploy() error`: run shell command to deploy the archive remotly
// - `Build() error`: build the archive
// - `Clear() error`: clean all the ressources locally and remotly
type deployer struct { //nolint:govet // ll
ctx context.Context
fnCancel context.CancelFunc
chDone chan struct{}
processing atomic.Bool
type_ DeployerType
errFlag error
project *models.Project
}
func newDeployer(ctx context.Context, type_ DeployerType, project *models.Project) *deployer {
d := &deployer{
ctx: ctx,
type_: type_,
project: project,
processing: atomic.Bool{},
chDone: make(chan struct{}, 1),
}
d.processing.Store(false)
return d
}
func (d *deployer) setDone(err error) {
d.chDone <- struct{}{}
d.errFlag = errors.Join(d.ctx.Err(), err)
if err != nil && d.fnCancel != nil {
d.fnCancel()
}
}
// SetCancellationFunc sets a context cancellation function for the deployer.
//
// If two deployers are related on the same context, one failed and you want
// to stop the execution of the others. Then, associate a cancel function
// for the deployer and the cancel func will be fired if error occurred
// during a deployment step. Stopping all the deployers.
func (d *deployer) SetCancellationFunc(fnCancel context.CancelFunc) {
d.fnCancel = fnCancel
}
func (d *deployer) Type() DeployerType {
return d.type_
}
func (d *deployer) Error() error {
return d.errFlag
}
// Done returns a channel providing the shutdown or the termination
// of the deployer.
//
// If the context is done, it will wait until all the current actions are done
// for a graceful shutdown. It has a graceful timeout (see: `GracefulTimeout`).
//
// If the deployer is done, succeed or failed, it simply returns.
func (d *deployer) Done() <-chan struct{} {
chDone := make(chan struct{})
go func() {
defer func() {
close(chDone)
}()
for {
select {
case <-d.ctx.Done():
log.Debug().Str("deployer", string(d.type_)).Msg("context done catch")
timeout := time.NewTicker(GracefulTimeout)
tick := time.NewTicker(time.Second)
defer tick.Stop()
defer timeout.Stop()
for {
select {
case <-timeout.C:
log.Error().
Str("deployer", string(d.type_)).
Msg("timeout while waiting for graceful shutdown")
chDone <- struct{}{}
return
case <-tick.C:
if !d.processing.Load() {
chDone <- struct{}{}
return
}
tick.Reset(1 * time.Second)
}
}
case <-d.chDone:
log.Debug().Str("deployer", string(d.type_)).Msg("terminated")
chDone <- struct{}{}
return
}
}
}()
return chDone
}