diff --git a/Makefile b/Makefile index 264c77f..850246c 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ run: lint build: lint @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: @$(shell whereis $(BIN_NAME) | cut -d ' ' -f2 | xargs rm -f) diff --git a/README.md b/README.md index c1edf0a..7212fef 100644 --- a/README.md +++ b/README.md @@ -11,19 +11,19 @@ For production environment, use it **with caution**. You have an Nginx instance in front of a Docker Swarm instance (ip are here for demonstration): ```ascii -+-------------------------+ +-------------------------+ -|docker swarm | |nginx | -| | | | -|ip: 10.0.0.2 | |ip: 10.0.0.1 | -| | | | HTTP Request -| +----------+ |<----------- -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -+-------------------------+ +-------------------------+ ++-------------------------+ +-------------------------+ +|docker swarm | |nginx | +| | | | +|ip: 10.0.0.2 | |ip: 10.0.0.1 | +| | | | +| +------+ +------+ +----------+ |<----------- +| |app1 | |app2 | | | | HTTP request +| | | | | | | | +| |:8080 | |:8081 | | | | +| +------+ +------+ | | | +| | | | +| | | | ++-------------------------+ +-------------------------+ ``` You want to deploy a service and its Nginx conf easyly with a simple CLI ? This tool is for you. @@ -120,14 +120,18 @@ The binary is then installed in your **$GOPATH/bin**. # you can then launch the program directly 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 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 * Improve the CLI arguments -* Destroy +* ~~Destroy~~ * post-install script * Deals with bugs diff --git a/deployers/commons.go b/deployers/commons.go index 58e4a6c..a27122e 100644 --- a/deployers/commons.go +++ b/deployers/commons.go @@ -16,6 +16,7 @@ type IDeployer interface { Type() DeployerType Deploy() error Build() error + Destroy() error Clear() error Error() error Done() <-chan struct{} @@ -65,7 +66,7 @@ func newDeployer(ctx context.Context, type_ DeployerType, project *models.Projec func (d *deployer) setDone(err error) { d.chDone <- struct{}{} - d.errFlag = err + d.errFlag = errors.Join(d.ctx.Err(), err) if err != nil && d.fnCancel != nil { d.fnCancel() } @@ -110,6 +111,10 @@ func (d *deployer) Done() <-chan struct{} { timeout := time.NewTicker(GracefulTimeout) tick := time.NewTicker(time.Second) + + defer tick.Stop() + defer timeout.Stop() + for { select { case <-timeout.C: diff --git a/deployers/nginx.go b/deployers/nginx.go index c04a53c..073981f 100644 --- a/deployers/nginx.go +++ b/deployers/nginx.go @@ -70,7 +70,7 @@ func (nd *NginxDeployer) Build() error { select { case <-nd.ctx.Done(): - nd.errFlag = ErrContextDone + nd.setDone(nil) return fmt.Errorf("%w, build nginx archive skipped", ErrContextDone) default: } @@ -94,7 +94,7 @@ func (nd *NginxDeployer) Deploy() (err error) { select { case <-nd.ctx.Done(): - nd.errFlag = ErrContextDone + nd.setDone(nil) return fmt.Errorf("%w, nginx deployment skipped", ErrContextDone) default: } @@ -119,3 +119,33 @@ func (nd *NginxDeployer) Deploy() (err error) { 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 +} diff --git a/deployers/swarm.go b/deployers/swarm.go index 02e9a2a..72fdbad 100644 --- a/deployers/swarm.go +++ b/deployers/swarm.go @@ -56,7 +56,9 @@ func (sd *SwarmDeployer) close() 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( fmt.Sprintf("rm -f %s %s *.tar.gz *.tar", models.ComposeFile, models.EnvFile), ) @@ -89,7 +91,7 @@ func (sd *SwarmDeployer) Build() error { select { case <-sd.ctx.Done(): - sd.errFlag = ErrContextDone + sd.setDone(nil) return fmt.Errorf("%w, swarm project build skipped", ErrContextDone) default: } @@ -148,7 +150,7 @@ func (sd *SwarmDeployer) Deploy() error { select { case <-sd.ctx.Done(): - sd.errFlag = ErrContextDone + sd.setDone(nil) return fmt.Errorf("%w, swarm deployment skipped", ErrContextDone) default: } @@ -186,3 +188,19 @@ func (sd *SwarmDeployer) Deploy() error { sd.setDone(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 +} diff --git a/main.go b/main.go index 256c1cf..607dea2 100644 --- a/main.go +++ b/main.go @@ -136,7 +136,7 @@ func initDeployers( // generateTasksTree returns a list of linked `Task` to submit. // // 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 { 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{} + 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 if nd != nil { 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. // 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 for idx := range deployers { @@ -183,7 +194,9 @@ func waitForCompletion(deployers []deployers.IDeployer, s *scheduler.Scheduler) for idx := range deployers { if d := deployers[idx]; d != nil { errs = append(errs, d.Error()) - s.Submit(scheduler.NewTask(string(d.Type()), d.Clear)) //nolint: errcheck // TODO + if !destroy { + s.Submit(scheduler.NewTask(string(d.Type()), d.Clear)) //nolint: errcheck // TODO + } } } @@ -204,6 +217,7 @@ func main() { log.Info().Msg("hmdeploy started") projectDir := flag.String("path", ".", "define the .homeserver project root dir") + destroy := flag.Bool("destroy", false, "delete the deployed project") flag.Parse() hmmap, err := loadHMMap() @@ -225,7 +239,7 @@ func main() { log.Fatal().Err(err).Msg("unable to init deployers") } - tasks, err := generateTasksTree(deployers) + tasks, err := generateTasksTree(deployers, *destroy) if err != nil { log.Fatal().Err(err).Msg("unable to generate tasks tree") } @@ -237,7 +251,7 @@ func main() { tasks..., ) - if err := waitForCompletion(deployers, s); err != nil { + if err := waitForCompletion(deployers, s, *destroy); err != nil { log.Fatal(). Err(err). Str("name", project.Name).