set deployment dir + refactor deployer
This commit is contained in:
parent
0e4eda972e
commit
7c01fa438c
@ -155,7 +155,7 @@ hmdeploy --path /path/my-project --destroy
|
|||||||
* ~~Improve the CLI arguments~~
|
* ~~Improve the CLI arguments~~
|
||||||
* ~~Destroy~~
|
* ~~Destroy~~
|
||||||
* ~~Check deployment/undeployment state~~
|
* ~~Check deployment/undeployment state~~
|
||||||
* Create a deployment temp dir (lock to avoid concurrent deployments)
|
* ~~Create a deployment temp dir (lock to avoid concurrent deployments)~~
|
||||||
* Deals with bugs
|
* Deals with bugs
|
||||||
* Tests 😮💨
|
* Tests 😮💨
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,13 @@ package deployers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.thegux.fr/hmdeploy/connection"
|
||||||
"gitea.thegux.fr/hmdeploy/models"
|
"gitea.thegux.fr/hmdeploy/models"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@ -51,21 +55,43 @@ type deployer struct { //nolint:govet // ll
|
|||||||
type_ DeployerType
|
type_ DeployerType
|
||||||
errFlag error
|
errFlag error
|
||||||
|
|
||||||
project *models.Project
|
conn connection.IConnection
|
||||||
archivePath string
|
project *models.Project
|
||||||
|
archivePath string
|
||||||
|
deploymentDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDeployer(ctx context.Context, type_ DeployerType, project *models.Project) *deployer {
|
func newDeployer(
|
||||||
|
ctx context.Context,
|
||||||
|
type_ DeployerType,
|
||||||
|
project *models.Project,
|
||||||
|
netInfo *models.HMNetInfo,
|
||||||
|
) (*deployer, error) {
|
||||||
|
conn, err := connection.NewSSHConn(
|
||||||
|
netInfo.IP.String(),
|
||||||
|
netInfo.SSH.User,
|
||||||
|
netInfo.SSH.Port,
|
||||||
|
netInfo.SSH.PrivKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
d := &deployer{
|
d := &deployer{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
type_: type_,
|
type_: type_,
|
||||||
project: project,
|
project: project,
|
||||||
processing: atomic.Bool{},
|
processing: atomic.Bool{},
|
||||||
chDone: make(chan struct{}, 1),
|
chDone: make(chan struct{}, 1),
|
||||||
|
conn: &conn,
|
||||||
}
|
}
|
||||||
|
|
||||||
d.processing.Store(false)
|
d.processing.Store(false)
|
||||||
return d
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deployer) close() error {
|
||||||
|
return d.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deployer) setDone(err error) {
|
func (d *deployer) setDone(err error) {
|
||||||
@ -76,6 +102,50 @@ func (d *deployer) setDone(err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *deployer) clean() {
|
||||||
|
if err := os.Remove(d.archivePath); err != nil {
|
||||||
|
log.Err(err).Str("archive", d.archivePath).Msg("unable to clean local archive file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.deploymentDir != "" {
|
||||||
|
if _, err := d.conn.Execute("rm -rf " + d.deploymentDir); err != nil {
|
||||||
|
log.Err(err).
|
||||||
|
Str("dir", d.deploymentDir).
|
||||||
|
Str("type", string(d.type_)).
|
||||||
|
Msg("unable to clean deployment dir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deployer) copyUntarArchive() error {
|
||||||
|
deploymentDir := "." + d.project.Name
|
||||||
|
|
||||||
|
// TODO(rmanach): check cmd error output to check if's not an other error
|
||||||
|
if _, err := d.conn.Execute("mkdir " + deploymentDir); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("dir", deploymentDir).
|
||||||
|
Msg("deployment dir already exists, unable to deploy now")
|
||||||
|
d.setDone(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.deploymentDir = deploymentDir
|
||||||
|
|
||||||
|
archiveName := filepath.Base(d.archivePath)
|
||||||
|
archiveDestPath := filepath.Join(deploymentDir, archiveName)
|
||||||
|
if err := d.conn.CopyFile(d.archivePath, archiveDestPath); err != nil {
|
||||||
|
d.setDone(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := d.conn.Execute(fmt.Sprintf("cd %s && tar xzvf %s", deploymentDir, archiveName)); err != nil {
|
||||||
|
d.setDone(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetCancellationFunc sets a context cancellation function for the deployer.
|
// SetCancellationFunc sets a context cancellation function for the deployer.
|
||||||
//
|
//
|
||||||
// If two deployers are related on the same context, one failed and you want
|
// If two deployers are related on the same context, one failed and you want
|
||||||
@ -94,6 +164,18 @@ func (d *deployer) Error() error {
|
|||||||
return d.errFlag
|
return d.errFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *deployer) Clear() error {
|
||||||
|
log.Debug().Str("type", string(d.type_)).Msg("clearing deployment...")
|
||||||
|
d.clean()
|
||||||
|
|
||||||
|
if err := d.close(); err != nil {
|
||||||
|
log.Err(err).Msg("unable to close conn")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Str("type", string(d.type_)).Msg("clear deployment done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Done returns a channel providing the shutdown or the termination
|
// Done returns a channel providing the shutdown or the termination
|
||||||
// of the deployer.
|
// of the deployer.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -3,10 +3,8 @@ package deployers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"gitea.thegux.fr/hmdeploy/connection"
|
|
||||||
"gitea.thegux.fr/hmdeploy/models"
|
"gitea.thegux.fr/hmdeploy/models"
|
||||||
"gitea.thegux.fr/hmdeploy/utils"
|
"gitea.thegux.fr/hmdeploy/utils"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -15,7 +13,6 @@ import (
|
|||||||
// NginxDeployer handles the deployment of an Nginx configuration.
|
// NginxDeployer handles the deployment of an Nginx configuration.
|
||||||
type NginxDeployer struct {
|
type NginxDeployer struct {
|
||||||
*deployer
|
*deployer
|
||||||
conn connection.IConnection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ IDeployer = (*NginxDeployer)(nil)
|
var _ IDeployer = (*NginxDeployer)(nil)
|
||||||
@ -27,18 +24,11 @@ func NewNginxDeployer(
|
|||||||
) (NginxDeployer, error) {
|
) (NginxDeployer, error) {
|
||||||
var nd NginxDeployer
|
var nd NginxDeployer
|
||||||
|
|
||||||
conn, err := connection.NewSSHConn(
|
deployer, err := newDeployer(ctx, Nginx, project, netInfo)
|
||||||
netInfo.IP.String(),
|
|
||||||
netInfo.SSH.User,
|
|
||||||
netInfo.SSH.Port,
|
|
||||||
netInfo.SSH.PrivKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nd, err
|
return nd, err
|
||||||
}
|
}
|
||||||
|
nd.deployer = deployer
|
||||||
nd.conn = &conn
|
|
||||||
nd.deployer = newDeployer(ctx, Nginx, project)
|
|
||||||
|
|
||||||
return nd, nil
|
return nd, nil
|
||||||
}
|
}
|
||||||
@ -51,38 +41,6 @@ func (nd NginxDeployer) getAssetsPath() string {
|
|||||||
return nd.project.GetNginxAssetsPath()
|
return nd.project.GetNginxAssetsPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nd *NginxDeployer) close() error {
|
|
||||||
return nd.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nd *NginxDeployer) clean() (err error) {
|
|
||||||
if err = os.Remove(nd.archivePath); err != nil {
|
|
||||||
log.Err(err).Str("archive", nd.archivePath).Msg("unable to clean local nginx archive file")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := "rm -rf nginx.conf build/ *.tar.gz"
|
|
||||||
if ap := nd.getAssetsPath(); ap != "" {
|
|
||||||
cmd += " " + filepath.Base(nd.getAssetsPath())
|
|
||||||
}
|
|
||||||
_, err = nd.conn.Execute(cmd)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nd *NginxDeployer) Clear() error {
|
|
||||||
log.Debug().Msg("clearing nginx deployment...")
|
|
||||||
|
|
||||||
if err := nd.clean(); err != nil {
|
|
||||||
log.Err(err).Msg("unable to clean nginx conf remotly")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := nd.close(); err != nil {
|
|
||||||
log.Err(err).Msg("unable to close nginx conn")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Msg("clear nginx deployment done")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nd *NginxDeployer) Build() error {
|
func (nd *NginxDeployer) Build() error {
|
||||||
nd.processing.Store(true)
|
nd.processing.Store(true)
|
||||||
defer nd.processing.Store(false)
|
defer nd.processing.Store(false)
|
||||||
@ -134,14 +92,7 @@ func (nd *NginxDeployer) Deploy() error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
archiveDestPath := filepath.Base(nd.archivePath)
|
if err := nd.copyUntarArchive(); err != nil {
|
||||||
if err := nd.conn.CopyFile(nd.archivePath, archiveDestPath); err != nil {
|
|
||||||
nd.setDone(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := nd.conn.Execute(fmt.Sprintf("tar xzvf %s", archiveDestPath)); err != nil {
|
|
||||||
nd.setDone(err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +104,7 @@ func (nd *NginxDeployer) Deploy() error {
|
|||||||
"rm -rf /var/www/static/%s/* && mkdir -p /var/www/static/%s && mv %s/* /var/www/static/%s",
|
"rm -rf /var/www/static/%s/* && mkdir -p /var/www/static/%s && mv %s/* /var/www/static/%s",
|
||||||
nd.project.Name,
|
nd.project.Name,
|
||||||
nd.project.Name,
|
nd.project.Name,
|
||||||
filepath.Base(nd.getAssetsPath()),
|
filepath.Join(nd.deploymentDir, filepath.Base(nd.getAssetsPath())),
|
||||||
nd.project.Name,
|
nd.project.Name,
|
||||||
),
|
),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@ -169,7 +120,7 @@ func (nd *NginxDeployer) Deploy() error {
|
|||||||
if _, err := nd.conn.Execute(
|
if _, err := nd.conn.Execute(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"mv %s /etc/nginx/sites-available/%s && ln -sf /etc/nginx/sites-available/%s /etc/nginx/sites-enabled/%s",
|
"mv %s /etc/nginx/sites-available/%s && ln -sf /etc/nginx/sites-available/%s /etc/nginx/sites-enabled/%s",
|
||||||
filepath.Base(cp),
|
filepath.Join(nd.deploymentDir, filepath.Base(cp)),
|
||||||
nginxConf,
|
nginxConf,
|
||||||
nginxConf,
|
nginxConf,
|
||||||
nginxConf,
|
nginxConf,
|
||||||
|
|||||||
@ -35,20 +35,14 @@ func NewSwarmDeployer(
|
|||||||
) (SwarmDeployer, error) {
|
) (SwarmDeployer, error) {
|
||||||
var sd SwarmDeployer
|
var sd SwarmDeployer
|
||||||
|
|
||||||
conn, err := connection.NewSSHConn(
|
|
||||||
netInfo.IP.String(),
|
|
||||||
netInfo.SSH.User,
|
|
||||||
netInfo.SSH.Port,
|
|
||||||
netInfo.SSH.PrivKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return sd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sd.conn = &conn
|
|
||||||
sd.dloc = dloc
|
sd.dloc = dloc
|
||||||
sd.drem = drem
|
sd.drem = drem
|
||||||
sd.deployer = newDeployer(ctx, Swarm, project)
|
|
||||||
|
deployer, err := newDeployer(ctx, Swarm, project, netInfo)
|
||||||
|
if err != nil {
|
||||||
|
return sd, err
|
||||||
|
}
|
||||||
|
sd.deployer = deployer
|
||||||
|
|
||||||
return sd, nil
|
return sd, nil
|
||||||
}
|
}
|
||||||
@ -61,36 +55,6 @@ func (sd SwarmDeployer) getEnvPath() string {
|
|||||||
return sd.project.GetEnvPath()
|
return sd.project.GetEnvPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *SwarmDeployer) close() error {
|
|
||||||
return sd.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sd *SwarmDeployer) clean() (err error) {
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sd *SwarmDeployer) Clear() error {
|
|
||||||
log.Debug().Msg("clearing swarm deployment...")
|
|
||||||
|
|
||||||
if err := sd.clean(); err != nil {
|
|
||||||
log.Err(err).Msg("unable to clean swarm conf remotly")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := sd.close(); err != nil {
|
|
||||||
log.Err(err).Msg("unable to close swarm conn")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Msg("clear swarm deployment done")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build builds the archive with mandatory files to deploy a swarm service.
|
// Build builds the archive with mandatory files to deploy a swarm service.
|
||||||
//
|
//
|
||||||
// After the build, the path of the local archive built is set in
|
// After the build, the path of the local archive built is set in
|
||||||
@ -177,20 +141,13 @@ func (sd *SwarmDeployer) Deploy() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
archiveDestPath := filepath.Base(sd.archivePath)
|
if err := sd.copyUntarArchive(); err != nil {
|
||||||
if err := sd.conn.CopyFile(sd.archivePath, archiveDestPath); err != nil {
|
|
||||||
sd.setDone(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sd.conn.Execute(fmt.Sprintf("tar xzvf %s", archiveDestPath)); err != nil {
|
|
||||||
sd.setDone(err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Str("project", sd.project.Name).Msg("deploying swarm project...")
|
log.Info().Str("project", sd.project.Name).Msg("deploying swarm project...")
|
||||||
composeFileBase := filepath.Base(sd.getComposePath())
|
composeFileBase := filepath.Base(sd.getComposePath())
|
||||||
if err := sd.drem.DeployStack(sd.ctx, sd.project.Name, composeFileBase, docker.WithCheckState()); err != nil {
|
if err := sd.drem.DeployStack(sd.ctx, sd.project.Name, composeFileBase, docker.WithCheckState(), docker.WithBaseDir(sd.deploymentDir)); err != nil {
|
||||||
sd.setDone(err)
|
sd.setDone(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type stackOption struct {
|
type stackOption struct {
|
||||||
|
baseDir string
|
||||||
checkState bool
|
checkState bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,12 @@ func WithCheckState() fnStackOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithBaseDir(dir string) fnStackOption {
|
||||||
|
return func(s *stackOption) {
|
||||||
|
s.baseDir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func parseIDs(cmdOutput string) []string {
|
func parseIDs(cmdOutput string) []string {
|
||||||
ids := []string{}
|
ids := []string{}
|
||||||
bufLine := []rune{}
|
bufLine := []rune{}
|
||||||
@ -201,15 +208,19 @@ func (c *RemoteClient) DeployStack(
|
|||||||
projectName, composeFilepath string,
|
projectName, composeFilepath string,
|
||||||
options ...fnStackOption,
|
options ...fnStackOption,
|
||||||
) error {
|
) error {
|
||||||
if _, err := c.conn.Execute(fmt.Sprintf("docker stack deploy -c %s %s --with-registry-auth", composeFilepath, projectName)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts stackOption
|
var opts stackOption
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
opt(&opts)
|
opt(&opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bd := opts.baseDir; bd != "" {
|
||||||
|
composeFilepath = filepath.Join(bd, composeFilepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := c.conn.Execute(fmt.Sprintf("docker stack deploy -c %s %s --with-registry-auth", composeFilepath, projectName)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if opts.checkState {
|
if opts.checkState {
|
||||||
return c.checkState(ctx, projectName, Running)
|
return c.checkState(ctx, projectName, Running)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user