add knecht
This commit is contained in:
144
knecht/stack/stack.go
Normal file
144
knecht/stack/stack.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Local struct {
|
||||
Name string
|
||||
ComposePath string
|
||||
EnvExample string // path to .env.example, may not exist
|
||||
}
|
||||
|
||||
// Discover finds all stacks in the services directory.
|
||||
func Discover(servicesPath string) ([]Local, error) {
|
||||
entries, err := os.ReadDir(servicesPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading services dir %s: %w", servicesPath, err)
|
||||
}
|
||||
|
||||
var stacks []Local
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
composePath := filepath.Join(servicesPath, e.Name(), "docker-compose.yml")
|
||||
if _, err := os.Stat(composePath); err != nil {
|
||||
continue // no compose file, skip
|
||||
}
|
||||
s := Local{
|
||||
Name: e.Name(),
|
||||
ComposePath: composePath,
|
||||
EnvExample: filepath.Join(servicesPath, e.Name(), ".env.example"),
|
||||
}
|
||||
stacks = append(stacks, s)
|
||||
}
|
||||
return stacks, nil
|
||||
}
|
||||
|
||||
// Get returns a single local stack by name.
|
||||
func Get(servicesPath, name string) (*Local, error) {
|
||||
composePath := filepath.Join(servicesPath, name, "docker-compose.yml")
|
||||
if _, err := os.Stat(composePath); err != nil {
|
||||
return nil, fmt.Errorf("no docker-compose.yml found for stack %q in %s", name, servicesPath)
|
||||
}
|
||||
return &Local{
|
||||
Name: name,
|
||||
ComposePath: composePath,
|
||||
EnvExample: filepath.Join(servicesPath, name, ".env.example"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadCompose returns the raw compose file content.
|
||||
func (s *Local) ReadCompose() (string, error) {
|
||||
b, err := os.ReadFile(s.ComposePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// EnvExampleKeys returns the variable names defined in .env.example.
|
||||
// Returns empty slice if the file doesn't exist.
|
||||
func (s *Local) EnvExampleKeys() ([]string, error) {
|
||||
f, err := os.Open(s.EnvExample)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var keys []string
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
key, _, _ := strings.Cut(line, "=")
|
||||
keys = append(keys, strings.TrimSpace(key))
|
||||
}
|
||||
return keys, scanner.Err()
|
||||
}
|
||||
|
||||
// WriteEnvExample creates a .env.example file from a list of keys (values left empty).
|
||||
// Existing file is overwritten.
|
||||
func (s *Local) WriteEnvExample(keys []string) error {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("# Generated by knecht from Portainer env vars\n")
|
||||
for _, k := range keys {
|
||||
sb.WriteString(k + "=\n")
|
||||
}
|
||||
return os.WriteFile(s.EnvExample, []byte(sb.String()), 0644)
|
||||
}
|
||||
|
||||
// AppendEnvExample appends missing keys to an existing .env.example file.
|
||||
func (s *Local) AppendEnvExample(keys []string) error {
|
||||
f, err := os.OpenFile(s.EnvExample, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
f.WriteString("\n# Added by knecht\n")
|
||||
for _, k := range keys {
|
||||
f.WriteString(k + "=\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServicesPath resolves the services directory: uses cfg value if set,
|
||||
// otherwise walks up from cwd to find the git root and appends /services.
|
||||
func ServicesPath(cfgPath string) (string, error) {
|
||||
if cfgPath != "" {
|
||||
return cfgPath, nil
|
||||
}
|
||||
return findServicesDir()
|
||||
}
|
||||
|
||||
func findServicesDir() (string, error) {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
|
||||
p := filepath.Join(dir, "services")
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return p, nil
|
||||
}
|
||||
return "", fmt.Errorf("found git root at %s but no services/ directory", dir)
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
break
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
return "", fmt.Errorf("could not find git root with a services/ directory")
|
||||
}
|
||||
Reference in New Issue
Block a user