Compare commits

..

No commits in common. "main" and "feat/nginx-assets-deployment" have entirely different histories.

6 changed files with 117 additions and 131 deletions

View File

@ -123,8 +123,6 @@ The binary is then installed in your **$GOPATH/bin**.
```bash ```bash
hmdeploy --help hmdeploy --help
Usage of hmdeploy: Usage of hmdeploy:
-config string
define the configuration directory (default "/home/romain/.homeserver")
-confirm -confirm
do not ask for confirmation, you're the best, you don't need confirmation do not ask for confirmation, you're the best, you don't need confirmation
-debug -debug
@ -135,8 +133,6 @@ Usage of hmdeploy:
extract swarm details and return extract swarm details and return
-no-nginx -no-nginx
no Nginx deployment no Nginx deployment
-no-swarm
no Swarm deployment
-path string -path string
define the .homeserver project root dir (default ".") define the .homeserver project root dir (default ".")
-version -version
@ -159,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 😮‍💨

View File

@ -3,13 +3,9 @@ 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"
) )
@ -55,43 +51,21 @@ type deployer struct { //nolint:govet // ll
type_ DeployerType type_ DeployerType
errFlag error errFlag error
conn connection.IConnection project *models.Project
project *models.Project archivePath string
archivePath string
deploymentDir string
} }
func newDeployer( func newDeployer(ctx context.Context, type_ DeployerType, project *models.Project) *deployer {
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, nil return d
}
func (d *deployer) close() error {
return d.conn.Close()
} }
func (d *deployer) setDone(err error) { func (d *deployer) setDone(err error) {
@ -102,50 +76,6 @@ 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
@ -164,18 +94,6 @@ 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.
// //

View File

@ -3,8 +3,10 @@ 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"
@ -13,6 +15,7 @@ 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)
@ -24,11 +27,18 @@ func NewNginxDeployer(
) (NginxDeployer, error) { ) (NginxDeployer, error) {
var nd NginxDeployer var nd NginxDeployer
deployer, err := newDeployer(ctx, Nginx, project, netInfo) conn, err := connection.NewSSHConn(
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
} }
@ -41,6 +51,38 @@ 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)
@ -92,7 +134,14 @@ func (nd *NginxDeployer) Deploy() error {
default: default:
} }
if err := nd.copyUntarArchive(); err != nil { archiveDestPath := filepath.Base(nd.archivePath)
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
} }
@ -104,7 +153,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.Join(nd.deploymentDir, filepath.Base(nd.getAssetsPath())), filepath.Base(nd.getAssetsPath()),
nd.project.Name, nd.project.Name,
), ),
); err != nil { ); err != nil {
@ -120,7 +169,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.Join(nd.deploymentDir, filepath.Base(cp)), filepath.Base(cp),
nginxConf, nginxConf,
nginxConf, nginxConf,
nginxConf, nginxConf,

View File

@ -35,14 +35,20 @@ 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
} }
@ -55,6 +61,36 @@ 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
@ -141,13 +177,20 @@ func (sd *SwarmDeployer) Deploy() error {
return err return err
} }
if err := sd.copyUntarArchive(); err != nil { archiveDestPath := filepath.Base(sd.archivePath)
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(), docker.WithBaseDir(sd.deploymentDir)); err != nil { if err := sd.drem.DeployStack(sd.ctx, sd.project.Name, composeFileBase, docker.WithCheckState()); err != nil {
sd.setDone(err) sd.setDone(err)
return err return err
} }

View File

@ -31,7 +31,6 @@ var (
) )
type stackOption struct { type stackOption struct {
baseDir string
checkState bool checkState bool
} }
@ -43,12 +42,6 @@ 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{}
@ -208,19 +201,15 @@ 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)
} }
@ -300,10 +289,6 @@ func (c *RemoteClient) checkState(
ready := true ready := true
mainloop: mainloop:
for idx := range srvs { for idx := range srvs {
// ensure app name is the right one
if srvs[idx].App != projectName {
continue
}
for idy := range srvs[idx].Replicas { for idy := range srvs[idx].Replicas {
if srvs[idx].Replicas[idy].State != target { if srvs[idx].Replicas[idy].State != target {
log.Info().Dur("retry (ms)", stateTickDuration).Msg("project not in good state yet, retrying...") log.Info().Dur("retry (ms)", stateTickDuration).Msg("project not in good state yet, retrying...")

11
main.go
View File

@ -219,10 +219,10 @@ func initLogger(debug bool) {
} }
// loadHMMap loads your instance configuration map from `$HOME/.homeserver` dir. // loadHMMap loads your instance configuration map from `$HOME/.homeserver` dir.
func loadHMMap(baseDir string) (models.HMMap, error) { func loadHMMap() (models.HMMap, error) {
var hmmap models.HMMap var hmmap models.HMMap
hmmap_path := path.Join(baseDir, NetworkFilename) hmmap_path := path.Join(HOME_PATH, HMDeployDirname, NetworkFilename)
c, err := os.ReadFile(hmmap_path) c, err := os.ReadFile(hmmap_path)
if err != nil { if err != nil {
return hmmap, fmt.Errorf( return hmmap, fmt.Errorf(
@ -428,11 +428,6 @@ func main() { //nolint: funlen // TODO: to reduce
) )
projectDir := flag.String("path", ".", "define the .homeserver project root dir") projectDir := flag.String("path", ".", "define the .homeserver project root dir")
configDir := flag.String(
"config",
path.Join(HOME_PATH, HMDeployDirname),
"define the configuration directory",
)
destroy := flag.Bool("destroy", false, "delete the deployed project") destroy := flag.Bool("destroy", false, "delete the deployed project")
noNginx := flag.Bool("no-nginx", false, "no Nginx deployment") noNginx := flag.Bool("no-nginx", false, "no Nginx deployment")
noSwarm := flag.Bool("no-swarm", false, "no Swarm deployment") noSwarm := flag.Bool("no-swarm", false, "no Swarm deployment")
@ -453,7 +448,7 @@ func main() { //nolint: funlen // TODO: to reduce
initLogger(*debug) initLogger(*debug)
hmmap, err := loadHMMap(*configDir) hmmap, err := loadHMMap()
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("failed to load conf") log.Fatal().Err(err).Msg("failed to load conf")
} }