This commit is contained in:
rmanach 2025-04-04 12:22:30 +02:00
parent 40ff6408f3
commit 7681f346bd
10 changed files with 65 additions and 2 deletions

View File

@ -31,6 +31,9 @@ var (
ErrSSHExecute = errors.New("unable to execute command") ErrSSHExecute = errors.New("unable to execute command")
) )
// NewSSHConn instanciates a new SSH connection.
// The `privkey` arg is the path of private key where the corresponding
// public has been deployed on the `user` server.
func NewSSHConn(addr, user string, port int, privkey string) (SSHConn, error) { func NewSSHConn(addr, user string, port int, privkey string) (SSHConn, error) {
var newconn SSHConn var newconn SSHConn
@ -75,6 +78,13 @@ func (c *SSHConn) Close() error {
return c.client.Close() return c.client.Close()
} }
// CopyFile copies a local `src` file to the remote `dest` server.
//
// NOTE: for now the `dest` filepath (absolute or relative) does not
// create a push the file to the desired location.
// All the files are copied in the remote user HOME.
//
// TODO: create the `dest` if not exist.
func (c *SSHConn) CopyFile(src, dest string) error { func (c *SSHConn) CopyFile(src, dest string) error {
sshSession, err := c.client.NewSession() sshSession, err := c.client.NewSession()
if err != nil { if err != nil {
@ -134,6 +144,7 @@ func (c *SSHConn) CopyFile(src, dest string) error {
return nil return nil
} }
// Execute executes a shell command remotly and returns the output.
func (c *SSHConn) Execute(cmd string) (string, error) { func (c *SSHConn) Execute(cmd string) (string, error) {
sshSession, err := c.client.NewSession() sshSession, err := c.client.NewSession()
if err != nil { if err != nil {

View File

@ -30,6 +30,14 @@ const (
GracefulTimeout = 10 * time.Second 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 { type deployer struct {
ctx context.Context ctx context.Context
fnCancel context.CancelFunc fnCancel context.CancelFunc
@ -63,6 +71,12 @@ func (d *deployer) setDone(err error) {
} }
} }
// 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) { func (d *deployer) SetCancellationFunc(fnCancel context.CancelFunc) {
d.fnCancel = fnCancel d.fnCancel = fnCancel
} }
@ -75,6 +89,13 @@ func (d *deployer) Error() error {
return d.errFlag 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{} { func (d *deployer) Done() <-chan struct{} {
chDone := make(chan struct{}) chDone := make(chan struct{})
go func() { go func() {

View File

@ -10,6 +10,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
// NginxDeployer handles the deployment of an Nginx configuration.
type NginxDeployer struct { type NginxDeployer struct {
*deployer *deployer
conn connection.IConnection conn connection.IConnection

View File

@ -16,6 +16,7 @@ import (
var ErrSwarmDeployerNoArchive = errors.New("no archive found to be deployed") var ErrSwarmDeployerNoArchive = errors.New("no archive found to be deployed")
// SwarmDeployer handles the deployment of a Docker service on the swarm instance.
type SwarmDeployer struct { type SwarmDeployer struct {
*deployer *deployer
conn connection.IConnection conn connection.IConnection
@ -78,6 +79,10 @@ func (sd *SwarmDeployer) Clear() error {
return nil return nil
} }
// 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
// the `archivePath` field.
func (sd *SwarmDeployer) Build() error { func (sd *SwarmDeployer) Build() error {
sd.processing.Store(true) sd.processing.Store(true)
defer sd.processing.Store(false) defer sd.processing.Store(false)

View File

@ -14,6 +14,11 @@ type IClient interface {
var ErrDockerClientSave = errors.New("unable to save image into tar") var ErrDockerClientSave = errors.New("unable to save image into tar")
// Client is a simple Docker client wrapping the local Docker daemon.
// It does not use the Docker API but instead shell command and collect the output.
//
// NOTE: for now, it's ok, it only needs one command so, no need to add a fat dedicated
// library with full Docker client API.
type Client struct{} type Client struct{}
var _ IClient = (*Client)(nil) var _ IClient = (*Client)(nil)
@ -22,6 +27,8 @@ func NewClient() Client {
return Client{} return Client{}
} }
// Save saves the `imageName` (tag included) in tar format in the target directory: `dest`.
// The `dest` directory must exist with correct permissions.
func (c *Client) Save(imageName, dest string) (string, error) { func (c *Client) Save(imageName, dest string) (string, error) {
destInfo, err := os.Stat(dest) destInfo, err := os.Stat(dest)
if err != nil { if err != nil {

View File

@ -11,12 +11,13 @@ import (
"path" "path"
"sync" "sync"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gitea.thegux.fr/hmdeploy/deployers" "gitea.thegux.fr/hmdeploy/deployers"
"gitea.thegux.fr/hmdeploy/docker" "gitea.thegux.fr/hmdeploy/docker"
"gitea.thegux.fr/hmdeploy/models" "gitea.thegux.fr/hmdeploy/models"
"gitea.thegux.fr/hmdeploy/scheduler" "gitea.thegux.fr/hmdeploy/scheduler"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
) )
const ( const (

View File

@ -19,6 +19,7 @@ type (
HMLXC map[string]*HMNetInfo HMLXC map[string]*HMNetInfo
) )
// HMMap handles all the informations of your home server instances.
type HMMap struct { type HMMap struct {
*HMNetInfo *HMNetInfo
VM HMVM `json:"vm,omitempty"` VM HMVM `json:"vm,omitempty"`

View File

@ -47,6 +47,7 @@ func getFileInfo(baseDir, filePath string) (fs.FileInfo, error) {
return fInf, nil return fInf, nil
} }
// Project handles the details and file informations of your project.
type Project struct { type Project struct {
Name string `json:"name"` Name string `json:"name"`
Dir string Dir string
@ -89,6 +90,10 @@ func (p *Project) validate() error {
return nil return nil
} }
// ProjectFromDir instantiates a new project from a directory path.
//
// The directory path must refers to the path including the `.homeserver` dir not
// the `.homeserver` path itself.
func ProjectFromDir(dir string) (Project, error) { func ProjectFromDir(dir string) (Project, error) {
var p Project var p Project

View File

@ -25,6 +25,7 @@ const (
type FnJob func() error type FnJob func() error
// taskStore is a thread safe `Task` store.
type taskStore struct { type taskStore struct {
l sync.RWMutex l sync.RWMutex
tasks map[string]*Task tasks map[string]*Task
@ -62,6 +63,9 @@ func (ts *taskStore) len() int {
return len(ts.tasks) return len(ts.tasks)
} }
// Task represents an execution unit handle by the scheduler.
//
// Next field links to next executable tasks (tree kind).
type Task struct { type Task struct {
Name string Name string
Job FnJob Job FnJob
@ -78,6 +82,8 @@ func NewTask(name string, job FnJob, next ...*Task) *Task {
} }
} }
// Scheduler is a simple scheduler.
// Handling tasks and executes them, that's all.
type Scheduler struct { type Scheduler struct {
ctx context.Context ctx context.Context
fnCancel context.CancelFunc fnCancel context.CancelFunc
@ -90,6 +96,10 @@ type Scheduler struct {
tasks taskStore tasks taskStore
} }
// NewScheduler instantiates a new `Scheduler`.
//
// If you want to run tasks immediately after the scheduler creation, you can pass a list of
// `Task` with `tasks` argument.
func NewScheduler(ctx context.Context, capacity uint32, workers uint8, tasks ...*Task) *Scheduler { func NewScheduler(ctx context.Context, capacity uint32, workers uint8, tasks ...*Task) *Scheduler {
ctxChild, fnCancel := context.WithCancel(ctx) ctxChild, fnCancel := context.WithCancel(ctx)
s := Scheduler{ s := Scheduler{

View File

@ -37,6 +37,7 @@ func addToArchive(tw *tar.Writer, filename string) error {
return err return err
} }
// CreateArchive creates a gzip tar archive in the `destDir` path including `files`.
func CreateArchive(destDir, name string, files ...string) (string, error) { func CreateArchive(destDir, name string, files ...string) (string, error) {
now := time.Now().UTC() now := time.Now().UTC()
archivePath := filepath.Join( archivePath := filepath.Join(