200 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			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
 | |
| }
 | 
