init repo
This commit is contained in:
commit
04e14928f3
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
map.json
|
||||
|
||||
hmdeploy
|
||||
146
.golangci.yml
Normal file
146
.golangci.yml
Normal file
@ -0,0 +1,146 @@
|
||||
linters-settings:
|
||||
depguard:
|
||||
rules:
|
||||
Main:
|
||||
files:
|
||||
- $all
|
||||
- '!$test'
|
||||
allow:
|
||||
- $gostd
|
||||
- gitea.thegux.fr
|
||||
- github.com
|
||||
Test:
|
||||
files:
|
||||
- $test
|
||||
allow:
|
||||
- $gostd
|
||||
- gitea.thegux.fr
|
||||
- github.com
|
||||
dupl:
|
||||
threshold: 100
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 50
|
||||
gci:
|
||||
sections:
|
||||
- "standard"
|
||||
- "default"
|
||||
- "blank"
|
||||
- "dot"
|
||||
# - "alias"
|
||||
- "prefix(gitea.thegux.fr)"
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
disabled-checks:
|
||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
# - whyNoLint
|
||||
- wrapperFunc
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
goimports:
|
||||
local-prefixes: gitea.thegux.fr
|
||||
mnd:
|
||||
checks:
|
||||
- argument
|
||||
- case
|
||||
- condition
|
||||
- return
|
||||
govet:
|
||||
disable:
|
||||
- fieldalignment
|
||||
lll:
|
||||
line-length: 200
|
||||
misspell:
|
||||
locale: US
|
||||
nolintlint:
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-explanation: false # don't require an explanation for nolint directives
|
||||
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||
errcheck:
|
||||
check-blank: true
|
||||
exclude-functions:
|
||||
- '(*github.com/gin-gonic/gin.Error).SetType'
|
||||
- '(*github.com/gin-gonic/gin.Context).Error'
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
# - mnd
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
# - inamedparam
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- noctx
|
||||
- nolintlint
|
||||
# - perfsprint
|
||||
- rowserrcheck
|
||||
# - sloglint
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
# - gochecknoglobals # too many global in ds9
|
||||
|
||||
# don't enable:
|
||||
# - asciicheck
|
||||
# - scopelint
|
||||
# - gocognit
|
||||
# - godot
|
||||
# - godox
|
||||
# - goerr113
|
||||
# - interfacer
|
||||
# - maligned
|
||||
# - nestif
|
||||
# - prealloc
|
||||
# - testpackage
|
||||
# - revive
|
||||
# - wsl
|
||||
|
||||
# issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
# fix: true
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: '(.+)_test\.go'
|
||||
linters:
|
||||
- funlen
|
||||
- goconst
|
||||
- dupl
|
||||
exclude-dirs:
|
||||
- ..
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
8
Makefile
Normal file
8
Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
run: lint
|
||||
go run main.go
|
||||
|
||||
build: lint
|
||||
go build -o hmdeploy main.go
|
||||
|
||||
lint:
|
||||
golangci-lint run ./...
|
||||
8
connection/connection.go
Normal file
8
connection/connection.go
Normal file
@ -0,0 +1,8 @@
|
||||
package connection
|
||||
|
||||
type IConnection interface {
|
||||
Execute(string) (string, error)
|
||||
CopyFile(src, dest string) error
|
||||
|
||||
Close() error
|
||||
}
|
||||
132
connection/ssh_connection.go
Normal file
132
connection/ssh_connection.go
Normal file
@ -0,0 +1,132 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type SSHConn struct {
|
||||
addr string
|
||||
client *ssh.Client
|
||||
}
|
||||
|
||||
var _ IConnection = (*SSHConn)(nil)
|
||||
|
||||
func NewSSHConn(addr, user string, port int, privkey string) (SSHConn, error) {
|
||||
var newconn SSHConn
|
||||
|
||||
sshAddr := addr + ":" + strconv.Itoa(port)
|
||||
newconn.addr = sshAddr
|
||||
|
||||
conn, err := net.Dial("tcp", sshAddr)
|
||||
if err != nil {
|
||||
log.Err(err).Str("addr", addr).Msg("unable to dial ssh addr")
|
||||
return newconn, err
|
||||
}
|
||||
|
||||
c, err := os.ReadFile(privkey)
|
||||
if err != nil {
|
||||
log.Err(err).Str("private key", privkey).Msg("unable to read ssh private key")
|
||||
return newconn, err
|
||||
}
|
||||
|
||||
sshPrivKey, err := ssh.ParsePrivateKey(c)
|
||||
if err != nil {
|
||||
log.Err(err).Str("private key", privkey).Msg("unable to parse ssh private key")
|
||||
return newconn, err
|
||||
}
|
||||
|
||||
sshConfig := ssh.ClientConfig{
|
||||
User: user,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(sshPrivKey),
|
||||
},
|
||||
}
|
||||
|
||||
sshConn, chNewChannel, chReq, err := ssh.NewClientConn(conn, sshAddr, &sshConfig)
|
||||
if err != nil {
|
||||
log.Err(err).Str("addr", sshAddr).Msg("unable to establish a new connection to the swarm")
|
||||
return newconn, err
|
||||
}
|
||||
|
||||
sshClient := ssh.NewClient(sshConn, chNewChannel, chReq)
|
||||
newconn.client = sshClient
|
||||
|
||||
log.Info().Str("addr", addr).Int("port", port).Msg("ssh connection sucessfully initialized")
|
||||
return newconn, nil
|
||||
}
|
||||
|
||||
func (c *SSHConn) Close() error {
|
||||
return c.client.Close()
|
||||
}
|
||||
|
||||
func (c *SSHConn) CopyFile(src, dest string) error {
|
||||
sshSession, err := c.client.NewSession()
|
||||
if err != nil {
|
||||
log.Err(err).Str("addr", c.addr).Msg("unable to open an ssh session")
|
||||
return err
|
||||
}
|
||||
defer sshSession.Close()
|
||||
|
||||
fileInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", src).Msg("unable to stat scp source file")
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Open(src)
|
||||
if err != nil {
|
||||
log.Err(err).Str("file", src).Msg("unable to open scp source file")
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
go func() {
|
||||
w, _ := sshSession.StdinPipe()
|
||||
defer w.Close()
|
||||
|
||||
fmt.Fprintf(w, "C0644 %d %s\n", fileInfo.Size(), filepath.Base(dest))
|
||||
|
||||
if _, err := io.Copy(w, file); err != nil {
|
||||
log.Err(err).Str("src", src).Str("dest", dest).Msg("unable to scp src to dest")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, "\x00")
|
||||
}()
|
||||
|
||||
if err := sshSession.Run(fmt.Sprintf("scp -t %s", dest)); err != nil {
|
||||
log.Err(err).Str("addr", c.addr).Str("dest", dest).Msg("unable to run scp command")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Str("src", src).Str("dest", dest).Msg("file successfully uploaded")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SSHConn) Execute(cmd string) (string, error) {
|
||||
sshSession, err := c.client.NewSession()
|
||||
if err != nil {
|
||||
log.Err(err).Str("addr", c.addr).Msg("unable to open an ssh session")
|
||||
return "", err
|
||||
}
|
||||
defer sshSession.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
sshSession.Stdout = &buf
|
||||
if err := sshSession.Run(cmd); err != nil {
|
||||
log.Err(err).Str("addr", c.addr).Str("command", cmd).Msg("unable to execute an ssh command")
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
6
deployers/commons.go
Normal file
6
deployers/commons.go
Normal file
@ -0,0 +1,6 @@
|
||||
package deployers
|
||||
|
||||
type IDeployer interface {
|
||||
Deploy() error
|
||||
Close() error
|
||||
}
|
||||
96
deployers/swarm.go
Normal file
96
deployers/swarm.go
Normal file
@ -0,0 +1,96 @@
|
||||
package deployers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"gitea.thegux.fr/hmdeploy/connection"
|
||||
"gitea.thegux.fr/hmdeploy/docker"
|
||||
"gitea.thegux.fr/hmdeploy/models"
|
||||
)
|
||||
|
||||
type SwarmDeployer struct {
|
||||
ctx context.Context
|
||||
fnCancel context.CancelFunc
|
||||
|
||||
conn connection.IConnection
|
||||
dcli docker.IClient
|
||||
|
||||
project *models.Project
|
||||
}
|
||||
|
||||
var _ IDeployer = (*SwarmDeployer)(nil)
|
||||
|
||||
func NewSwarmDeployer(ctx context.Context, dockerClient docker.IClient, netInfo *models.HMNetInfo, project *models.Project) (SwarmDeployer, error) {
|
||||
var sm SwarmDeployer
|
||||
|
||||
conn, err := connection.NewSSHConn(netInfo.IP.String(), netInfo.SSH.User, netInfo.SSH.Port, netInfo.SSH.PrivKey)
|
||||
if err != nil {
|
||||
return sm, err
|
||||
}
|
||||
|
||||
ctxChild, fnCancel := context.WithCancel(ctx)
|
||||
|
||||
sm.ctx = ctxChild
|
||||
sm.fnCancel = fnCancel
|
||||
|
||||
sm.conn = &conn
|
||||
sm.dcli = dockerClient
|
||||
sm.project = project
|
||||
|
||||
return sm, nil
|
||||
}
|
||||
|
||||
func (sd *SwarmDeployer) Close() error {
|
||||
return sd.conn.Close()
|
||||
}
|
||||
|
||||
func (sd *SwarmDeployer) clean() (err error) {
|
||||
_, err = sd.conn.Execute(fmt.Sprintf("rm -f %s %s", models.ComposeFile, models.EnvFile))
|
||||
return
|
||||
}
|
||||
|
||||
func (sd *SwarmDeployer) Deploy() error {
|
||||
defer sd.clean()
|
||||
|
||||
if sd.project.ImageName != "" {
|
||||
tarFile, err := sd.dcli.Save(sd.project.ImageName, sd.project.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer os.Remove(tarFile)
|
||||
|
||||
tarFileBase := filepath.Base(tarFile)
|
||||
if err := sd.conn.CopyFile(tarFile, tarFileBase); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sd.conn.Execute(fmt.Sprintf("docker load -i %s && rm %s", tarFileBase, tarFileBase)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if envFilePath := sd.project.Deps.EnvFile; envFilePath != "" {
|
||||
envFileBase := filepath.Base(envFilePath)
|
||||
if err := sd.conn.CopyFile(filepath.Join(sd.project.Dir, envFileBase), envFileBase); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
composeFileBase := filepath.Base(sd.project.Deps.ComposeFile)
|
||||
if err := sd.conn.CopyFile(filepath.Join(sd.project.Dir, composeFileBase), composeFileBase); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sd.conn.Execute(fmt.Sprintf("docker stack deploy -c %s %s", composeFileBase, sd.project.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msg("project deployed successfully on swarm")
|
||||
return nil
|
||||
}
|
||||
46
docker/client.go
Normal file
46
docker/client.go
Normal file
@ -0,0 +1,46 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type IClient interface {
|
||||
Save(imageName, dest string) (string, error)
|
||||
}
|
||||
|
||||
type Client struct{}
|
||||
|
||||
var _ IClient = (*Client)(nil)
|
||||
|
||||
func NewClient() Client {
|
||||
return Client{}
|
||||
}
|
||||
|
||||
func (c *Client) Save(imageName, dest string) (string, error) {
|
||||
destInfo, err := os.Stat(dest)
|
||||
if err != nil {
|
||||
log.Err(err).Str("dest", dest).Msg("unable to stat dest directory")
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !destInfo.IsDir() {
|
||||
log.Err(err).Str("dest", dest).Msg("dest directory must be a directory")
|
||||
}
|
||||
|
||||
tarFile := fmt.Sprintf("%s.tar", imageName)
|
||||
|
||||
cmd := exec.Command("docker", "save", "-o", tarFile, imageName)
|
||||
cmd.Dir = dest
|
||||
if _, err := cmd.Output(); err != nil {
|
||||
log.Err(err).Str("image", imageName).Str("dest", dest).Msg("unable to save image into tar")
|
||||
return dest, err
|
||||
}
|
||||
|
||||
log.Info().Str("image", imageName).Str("dest", dest).Msg("image successfully saved")
|
||||
return filepath.Join(dest, tarFile), nil
|
||||
}
|
||||
16
go.mod
Normal file
16
go.mod
Normal file
@ -0,0 +1,16 @@
|
||||
module gitea.thegux.fr/hmdeploy
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.7
|
||||
|
||||
require (
|
||||
github.com/rs/zerolog v1.34.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
)
|
||||
20
go.sum
Normal file
20
go.sum
Normal file
@ -0,0 +1,20 @@
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
75
main.go
Normal file
75
main.go
Normal file
@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"gitea.thegux.fr/hmdeploy/deployers"
|
||||
"gitea.thegux.fr/hmdeploy/docker"
|
||||
"gitea.thegux.fr/hmdeploy/models"
|
||||
)
|
||||
|
||||
const HMDEPLOY_DIRNAME = ".homeserver"
|
||||
const NETWORK_FILENAME = "map.json"
|
||||
|
||||
var HOME_PATH = os.Getenv("HOME")
|
||||
|
||||
func initLogger() {
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
log.Logger = log.With().Caller().Logger().Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, stop := signal.NotifyContext(
|
||||
context.Background(),
|
||||
os.Interrupt,
|
||||
os.Kill,
|
||||
)
|
||||
defer stop()
|
||||
|
||||
initLogger()
|
||||
log.Info().Msg("hmdeploy started")
|
||||
|
||||
projectDir := flag.String("path", ".", "define the .homeserver project root dir")
|
||||
flag.Parse()
|
||||
|
||||
hmmap_path := path.Join(HOME_PATH, HMDEPLOY_DIRNAME, NETWORK_FILENAME)
|
||||
c, err := os.ReadFile(hmmap_path)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Str("conf", hmmap_path).Msg("unable to load configuration")
|
||||
}
|
||||
|
||||
var hmmap models.HMMap
|
||||
if err := json.Unmarshal(c, &hmmap); err != nil {
|
||||
log.Fatal().Err(err).Str("conf", hmmap_path).Msg("unable to parse configuration")
|
||||
}
|
||||
log.Info().Str("conf", hmmap_path).Msg("hmmap load successfully")
|
||||
|
||||
swarmNet := hmmap.GetSwarmNetInfo()
|
||||
if swarmNet == nil {
|
||||
log.Fatal().Err(err).Msg("unable to get swarm net info, does not exist")
|
||||
}
|
||||
|
||||
project, err := models.ProjectFromDir(*projectDir)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dcli := docker.NewClient()
|
||||
|
||||
sd, err := deployers.NewSwarmDeployer(ctx, &dcli, swarmNet, &project)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := sd.Deploy(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
36
models/hm.go
Normal file
36
models/hm.go
Normal file
@ -0,0 +1,36 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type HMNetInfo struct {
|
||||
IP net.IP `json:"ip"`
|
||||
WebURL string `json:"web_url,omitempty"`
|
||||
SSH struct {
|
||||
User string `json:"user"`
|
||||
PrivKey string `json:"privkey"`
|
||||
Port int `json:"port"`
|
||||
} `json:"ssh,omitempty"`
|
||||
}
|
||||
|
||||
type HMVM map[string]*HMNetInfo
|
||||
type HMLXC map[string]*HMNetInfo
|
||||
|
||||
type HMMap struct {
|
||||
*HMNetInfo
|
||||
VM HMVM `json:"vm,omitempty"`
|
||||
LXC HMLXC `json:"lxc,omitempty"`
|
||||
}
|
||||
|
||||
func (hm *HMMap) GetSwarmNetInfo() *HMNetInfo {
|
||||
data, ok := hm.VM["swarm"]
|
||||
if !ok {
|
||||
log.Error().Msg("unable to get swarm net info, check your configuration")
|
||||
return nil
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
114
models/project.go
Normal file
114
models/project.go
Normal file
@ -0,0 +1,114 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
MainDir string = ".homeserver"
|
||||
|
||||
ComposeFile string = "docker-compose.deploy.yml"
|
||||
EnvFile = ".env"
|
||||
NginxFile = "nginx.conf"
|
||||
|
||||
ConfFile = "hmdeploy.json"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidEnvFile = errors.New("unable to stat .env file")
|
||||
ErrInvalidComposeFile = errors.New("unable to stat compose file")
|
||||
ErrInvalidNginxFile = errors.New("unable to stat nginx file")
|
||||
)
|
||||
|
||||
func getFileInfo(baseDir, filePath string) (fs.FileInfo, error) {
|
||||
var fInf fs.FileInfo
|
||||
|
||||
filePath = filepath.Clean(filePath)
|
||||
filePath = filepath.Join(baseDir, filePath)
|
||||
|
||||
composePath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
return fInf, fmt.Errorf("%w, %v", ErrInvalidComposeFile, err)
|
||||
}
|
||||
|
||||
fInf, err = os.Stat(composePath)
|
||||
if err != nil {
|
||||
return fInf, fmt.Errorf("%w, %v", ErrInvalidComposeFile, err)
|
||||
}
|
||||
|
||||
return fInf, nil
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
Name string `json:"name"`
|
||||
Dir string
|
||||
ImageName string `json:"image"`
|
||||
Deps struct {
|
||||
EnvFile string `json:"env"`
|
||||
EnvFileInfo fs.FileInfo
|
||||
|
||||
ComposeFile string `json:"compose"`
|
||||
ComposeFileInfo fs.FileInfo
|
||||
|
||||
NginxFile string `json:"nginx"`
|
||||
NginxFileInfo fs.FileInfo
|
||||
} `json:"dependencies"`
|
||||
}
|
||||
|
||||
func (p *Project) validate() error {
|
||||
cfs, err := getFileInfo(p.Dir, p.Deps.ComposeFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w, %v", ErrInvalidComposeFile, err)
|
||||
}
|
||||
p.Deps.ComposeFileInfo = cfs
|
||||
|
||||
if p.Deps.EnvFile != "" {
|
||||
efs, err := getFileInfo(p.Dir, p.Deps.EnvFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w, %v", ErrInvalidEnvFile, err)
|
||||
}
|
||||
p.Deps.EnvFileInfo = efs
|
||||
} else {
|
||||
log.Warn().Msg("no .env file provided, hoping one is set elsewhere...")
|
||||
}
|
||||
|
||||
nfs, err := getFileInfo(p.Dir, p.Deps.EnvFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w, %v", ErrInvalidNginxFile, err)
|
||||
}
|
||||
p.Deps.NginxFileInfo = nfs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
log.Err(err).Str("dir", dir).Str("conf", ConfFile).Msg("unable to read conf file")
|
||||
return p, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(content, &p); err != nil {
|
||||
log.Err(err).Str("dir", dir).Str("conf", ConfFile).Msg("unable to parse conf file")
|
||||
return p, err
|
||||
}
|
||||
|
||||
if err := p.validate(); err != nil {
|
||||
log.Err(err).Str("dir", dir).Msg("unable to validate project")
|
||||
return p, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user