hmdeploy/models/project.go
2025-05-02 10:10:50 +02:00

200 lines
3.8 KiB
Go

package models
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/rs/zerolog/log"
)
const (
MainDir string = ".homeserver"
ComposeFile string = "docker-compose.deploy.yml"
EnvFile string = ".env"
NginxFile = "nginx.conf"
ConfFile = "hmdeploy.json"
)
var ErrProjectConfFile = errors.New("project error")
type filepathOption struct {
isDir bool
}
type fnFilepathOption func(*filepathOption)
func IsDir() fnFilepathOption {
return func(fo *filepathOption) {
fo.isDir = true
}
}
func getFilepath(baseDir, filePath string, options ...fnFilepathOption) (string, error) {
var opts filepathOption
for _, opt := range options {
opt(&opts)
}
if !filepath.IsAbs(filePath) {
filePath = filepath.Join(baseDir, filePath)
filePath, err := filepath.Abs(filePath) //nolint: govet
if err != nil {
return filePath, fmt.Errorf(
"%w, file=%s, err=%v",
ErrProjectConfFile,
filePath,
err,
)
}
}
fileInfo, err := os.Stat(filePath)
if err != nil {
return filePath, fmt.Errorf(
"%w, file=%s, err=%v",
ErrProjectConfFile,
filePath,
err,
)
}
if fileInfo.IsDir() && !opts.isDir {
return filePath, fmt.Errorf(
"%w, file=%s, err=%s",
ErrProjectConfFile,
filePath,
"must be a file",
)
}
if !fileInfo.IsDir() && opts.isDir {
return filePath, fmt.Errorf(
"%w, file=%s, err=%s",
ErrProjectConfFile,
filePath,
"must be a dir",
)
}
return filePath, nil
}
// Project handles the details and file informations of your project.
type Project struct {
Name string `json:"name"`
Dir string
Deps struct {
Nginx struct {
Conf string `json:"conf"`
Assets string `json:"assets"`
}
Swarm struct {
EnvFile string `json:"env"`
ComposeFile string `json:"compose"`
ImageNames []string `json:"images"`
} `json:"swarm"`
} `json:"dependencies"`
}
func (p *Project) validate() error {
if compf := p.Deps.Swarm.ComposeFile; compf != "" {
cpath, err := getFilepath(p.Dir, compf)
if err != nil {
return err
}
p.Deps.Swarm.ComposeFile = cpath
} else {
log.Warn().Msg("no docker-compose file provided, Swarm deployment discards")
}
if env := p.Deps.Swarm.EnvFile; env != "" {
epath, err := getFilepath(p.Dir, env)
if err != nil {
return err
}
p.Deps.Swarm.EnvFile = epath
}
if conf := p.Deps.Nginx.Conf; conf != "" {
npath, err := getFilepath(p.Dir, conf)
if err != nil {
return err
}
p.Deps.Nginx.Conf = npath
}
if assets := p.Deps.Nginx.Assets; assets != "" {
apath, err := getFilepath(p.Dir, assets, IsDir())
if err != nil {
return err
}
p.Deps.Nginx.Assets = apath
}
return nil
}
func (p Project) GetComposePath() string {
return p.Deps.Swarm.ComposeFile
}
func (p Project) GetEnvPath() string {
return p.Deps.Swarm.EnvFile
}
func (p Project) GetNginxConfPath() string {
return p.Deps.Nginx.Conf
}
func (p Project) GetNginxAssetsPath() string {
return p.Deps.Nginx.Assets
}
// 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) {
var p Project
dir = filepath.Join(dir, MainDir)
p.Dir = dir
content, err := os.ReadFile(filepath.Join(dir, ConfFile))
if err != nil {
return p, fmt.Errorf(
"%w, unable to read conf file=%s, err=%v",
ErrProjectConfFile,
ConfFile,
err,
)
}
if err := json.Unmarshal(content, &p); err != nil {
return p, fmt.Errorf(
"%w, unable to parse conf file=%s, err=%v",
ErrProjectConfFile,
ConfFile,
err,
)
}
if err := p.validate(); err != nil {
return p, fmt.Errorf(
"%w, unable to validate project, name=%s, dir=%s, err=%v",
ErrProjectConfFile,
p.Name,
p.Dir,
err,
)
}
return p, nil
}