add destroy command

This commit is contained in:
rmanach 2025-04-04 16:59:46 +02:00
parent 43a78573f5
commit cfdaa9538b
6 changed files with 98 additions and 27 deletions

View File

@ -5,7 +5,7 @@ run: lint
build: lint build: lint
@echo "building binary..." @echo "building binary..."
@go build -o $(BIN_NAME) -race main.go && echo "$(BIN_NAME) built" @go build -o $(BIN_NAME) main.go && echo "$(BIN_NAME) built"
install: install:
@$(shell whereis $(BIN_NAME) | cut -d ' ' -f2 | xargs rm -f) @$(shell whereis $(BIN_NAME) | cut -d ' ' -f2 | xargs rm -f)

View File

@ -15,12 +15,12 @@ You have an Nginx instance in front of a Docker Swarm instance (ip are here for
|docker swarm | |nginx | |docker swarm | |nginx |
| | | | | | | |
|ip: 10.0.0.2 | |ip: 10.0.0.1 | |ip: 10.0.0.2 | |ip: 10.0.0.1 |
| | | | HTTP Request
| +----------+ |<-----------
| | | |
| | | |
| | | |
| | | | | | | |
| +------+ +------+ +----------+ |<-----------
| |app1 | |app2 | | | | HTTP request
| | | | | | | |
| |:8080 | |:8081 | | | |
| +------+ +------+ | | |
| | | | | | | |
| | | | | | | |
+-------------------------+ +-------------------------+ +-------------------------+ +-------------------------+
@ -120,14 +120,18 @@ The binary is then installed in your **$GOPATH/bin**.
# you can then launch the program directly # you can then launch the program directly
hmdeploy hmdeploy
# if you want the deploy a specific project # if you want to deploy a specific project
# use --path to point the project dir where `.homeserver` is located # use --path to point the project dir where `.homeserver` is located
hmdeploy --path /path/my-project hmdeploy --path /path/my-project
# if you want to undeploy a specific project
# do not worry, volumes are preserved
hmdeploy --path /path/my-project --destroy
``` ```
## Next steps ## Next steps
* Improve the CLI arguments * Improve the CLI arguments
* Destroy * ~~Destroy~~
* post-install script * post-install script
* Deals with bugs * Deals with bugs

View File

@ -16,6 +16,7 @@ type IDeployer interface {
Type() DeployerType Type() DeployerType
Deploy() error Deploy() error
Build() error Build() error
Destroy() error
Clear() error Clear() error
Error() error Error() error
Done() <-chan struct{} Done() <-chan struct{}
@ -65,7 +66,7 @@ func newDeployer(ctx context.Context, type_ DeployerType, project *models.Projec
func (d *deployer) setDone(err error) { func (d *deployer) setDone(err error) {
d.chDone <- struct{}{} d.chDone <- struct{}{}
d.errFlag = err d.errFlag = errors.Join(d.ctx.Err(), err)
if err != nil && d.fnCancel != nil { if err != nil && d.fnCancel != nil {
d.fnCancel() d.fnCancel()
} }
@ -110,6 +111,10 @@ func (d *deployer) Done() <-chan struct{} {
timeout := time.NewTicker(GracefulTimeout) timeout := time.NewTicker(GracefulTimeout)
tick := time.NewTicker(time.Second) tick := time.NewTicker(time.Second)
defer tick.Stop()
defer timeout.Stop()
for { for {
select { select {
case <-timeout.C: case <-timeout.C:

View File

@ -70,7 +70,7 @@ func (nd *NginxDeployer) Build() error {
select { select {
case <-nd.ctx.Done(): case <-nd.ctx.Done():
nd.errFlag = ErrContextDone nd.setDone(nil)
return fmt.Errorf("%w, build nginx archive skipped", ErrContextDone) return fmt.Errorf("%w, build nginx archive skipped", ErrContextDone)
default: default:
} }
@ -94,7 +94,7 @@ func (nd *NginxDeployer) Deploy() (err error) {
select { select {
case <-nd.ctx.Done(): case <-nd.ctx.Done():
nd.errFlag = ErrContextDone nd.setDone(nil)
return fmt.Errorf("%w, nginx deployment skipped", ErrContextDone) return fmt.Errorf("%w, nginx deployment skipped", ErrContextDone)
default: default:
} }
@ -119,3 +119,33 @@ func (nd *NginxDeployer) Deploy() (err error) {
return err return err
} }
func (nd *NginxDeployer) Destroy() (err error) {
nd.processing.Store(true)
defer nd.processing.Store(false)
select {
case <-nd.ctx.Done():
nd.setDone(nil)
return fmt.Errorf("%w, nginx destroy skipped", ErrContextDone)
default:
}
nginxConf := nd.project.Name + ".conf"
log.Info().Str("nginx", nginxConf).Msg("destroying nginx conf...")
_, err = nd.conn.Execute(
fmt.Sprintf(
"unlink /etc/nginx/sites-enabled/%s",
nginxConf,
),
)
nd.setDone(err)
if err == nil {
log.Info().Str("nginx", nginxConf).Msg("nginx conf successfully destroyed")
}
return err
}

View File

@ -56,7 +56,9 @@ func (sd *SwarmDeployer) close() error {
} }
func (sd *SwarmDeployer) clean() (err error) { func (sd *SwarmDeployer) clean() (err error) {
defer os.Remove(sd.archivePath) //nolint: errcheck // defered if err := os.Remove(sd.archivePath); err != nil {
log.Err(err).Str("archive", sd.archivePath).Msg("unable to clean local swarm archive file")
}
_, err = sd.conn.Execute( _, err = sd.conn.Execute(
fmt.Sprintf("rm -f %s %s *.tar.gz *.tar", models.ComposeFile, models.EnvFile), fmt.Sprintf("rm -f %s %s *.tar.gz *.tar", models.ComposeFile, models.EnvFile),
) )
@ -89,7 +91,7 @@ func (sd *SwarmDeployer) Build() error {
select { select {
case <-sd.ctx.Done(): case <-sd.ctx.Done():
sd.errFlag = ErrContextDone sd.setDone(nil)
return fmt.Errorf("%w, swarm project build skipped", ErrContextDone) return fmt.Errorf("%w, swarm project build skipped", ErrContextDone)
default: default:
} }
@ -148,7 +150,7 @@ func (sd *SwarmDeployer) Deploy() error {
select { select {
case <-sd.ctx.Done(): case <-sd.ctx.Done():
sd.errFlag = ErrContextDone sd.setDone(nil)
return fmt.Errorf("%w, swarm deployment skipped", ErrContextDone) return fmt.Errorf("%w, swarm deployment skipped", ErrContextDone)
default: default:
} }
@ -186,3 +188,19 @@ func (sd *SwarmDeployer) Deploy() error {
sd.setDone(nil) sd.setDone(nil)
return nil return nil
} }
func (sd *SwarmDeployer) Destroy() error {
sd.processing.Store(true)
defer sd.processing.Store(false)
log.Info().Str("project", sd.project.Name).Msg("destroying swarm project...")
if _, err := sd.conn.Execute(fmt.Sprintf("docker stack rm %s", sd.project.Name)); err != nil {
sd.setDone(err)
return err
}
log.Info().Msg("swarm undeployment done with success")
sd.setDone(nil)
return nil
}

22
main.go
View File

@ -136,7 +136,7 @@ func initDeployers(
// generateTasksTree returns a list of linked `Task` to submit. // generateTasksTree returns a list of linked `Task` to submit.
// //
// It's here that all tasks are linked each other to provide the deployment ordering. // It's here that all tasks are linked each other to provide the deployment ordering.
func generateTasksTree(deployers []deployers.IDeployer) ([]*scheduler.Task, error) { func generateTasksTree(deployers []deployers.IDeployer, destroy bool) ([]*scheduler.Task, error) {
if len(deployers) != MaxDeployers { if len(deployers) != MaxDeployers {
return nil, fmt.Errorf("%w, deployers len should be equals to 2", ErrGenerateTasksTree) return nil, fmt.Errorf("%w, deployers len should be equals to 2", ErrGenerateTasksTree)
} }
@ -146,6 +146,13 @@ func generateTasksTree(deployers []deployers.IDeployer) ([]*scheduler.Task, erro
tasks := []*scheduler.Task{} tasks := []*scheduler.Task{}
if destroy {
swarmDestroy := scheduler.NewTask("swarm-destroy", sd.Destroy)
destroyTask := scheduler.NewTask("nginx-destroy", nd.Destroy, swarmDestroy)
tasks = append(tasks, destroyTask)
return tasks, nil
}
var swarmTask *scheduler.Task var swarmTask *scheduler.Task
if nd != nil { if nd != nil {
deployNginx := scheduler.NewTask("nginx-deploy", nd.Deploy) deployNginx := scheduler.NewTask("nginx-deploy", nd.Deploy)
@ -164,7 +171,11 @@ func generateTasksTree(deployers []deployers.IDeployer) ([]*scheduler.Task, erro
// //
// After the completion, deployers `Clear` methods are executed to clean all ressources. // After the completion, deployers `Clear` methods are executed to clean all ressources.
// Then the scheduler is stopped to terminate the engine. // Then the scheduler is stopped to terminate the engine.
func waitForCompletion(deployers []deployers.IDeployer, s *scheduler.Scheduler) error { func waitForCompletion(
deployers []deployers.IDeployer,
s *scheduler.Scheduler,
destroy bool,
) error {
var wg sync.WaitGroup var wg sync.WaitGroup
for idx := range deployers { for idx := range deployers {
@ -183,9 +194,11 @@ func waitForCompletion(deployers []deployers.IDeployer, s *scheduler.Scheduler)
for idx := range deployers { for idx := range deployers {
if d := deployers[idx]; d != nil { if d := deployers[idx]; d != nil {
errs = append(errs, d.Error()) errs = append(errs, d.Error())
if !destroy {
s.Submit(scheduler.NewTask(string(d.Type()), d.Clear)) //nolint: errcheck // TODO s.Submit(scheduler.NewTask(string(d.Type()), d.Clear)) //nolint: errcheck // TODO
} }
} }
}
s.Stop() s.Stop()
<-s.Done() <-s.Done()
@ -204,6 +217,7 @@ func main() {
log.Info().Msg("hmdeploy started") log.Info().Msg("hmdeploy started")
projectDir := flag.String("path", ".", "define the .homeserver project root dir") projectDir := flag.String("path", ".", "define the .homeserver project root dir")
destroy := flag.Bool("destroy", false, "delete the deployed project")
flag.Parse() flag.Parse()
hmmap, err := loadHMMap() hmmap, err := loadHMMap()
@ -225,7 +239,7 @@ func main() {
log.Fatal().Err(err).Msg("unable to init deployers") log.Fatal().Err(err).Msg("unable to init deployers")
} }
tasks, err := generateTasksTree(deployers) tasks, err := generateTasksTree(deployers, *destroy)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("unable to generate tasks tree") log.Fatal().Err(err).Msg("unable to generate tasks tree")
} }
@ -237,7 +251,7 @@ func main() {
tasks..., tasks...,
) )
if err := waitForCompletion(deployers, s); err != nil { if err := waitForCompletion(deployers, s, *destroy); err != nil {
log.Fatal(). log.Fatal().
Err(err). Err(err).
Str("name", project.Name). Str("name", project.Name).