103 lines
2.6 KiB
Go
103 lines
2.6 KiB
Go
package drift
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type Result struct {
|
|
ComposeDiff []string // unified-style diff lines
|
|
MissingKeys []string // in .env.example but not in Portainer
|
|
UnknownKeys []string // in Portainer but not in .env.example (file exists)
|
|
PortainerOnlyKeys []string // Portainer has keys but no .env.example exists at all
|
|
}
|
|
|
|
func (r *Result) HasDrift() bool {
|
|
return len(r.ComposeDiff) > 0 || len(r.MissingKeys) > 0 || len(r.UnknownKeys) > 0 || len(r.PortainerOnlyKeys) > 0
|
|
}
|
|
|
|
func (r *Result) Summary() string {
|
|
parts := []string{}
|
|
if len(r.ComposeDiff) > 0 {
|
|
parts = append(parts, fmt.Sprintf("compose ~%d lines", len(r.ComposeDiff)))
|
|
}
|
|
if len(r.MissingKeys) > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d env key(s) missing", len(r.MissingKeys)))
|
|
}
|
|
if len(r.UnknownKeys) > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d env key(s) unknown", len(r.UnknownKeys)))
|
|
}
|
|
if len(r.PortainerOnlyKeys) > 0 {
|
|
parts = append(parts, fmt.Sprintf("no .env.example (%d key(s) in Portainer)", len(r.PortainerOnlyKeys)))
|
|
}
|
|
if len(parts) == 0 {
|
|
return "in sync"
|
|
}
|
|
return strings.Join(parts, ", ")
|
|
}
|
|
|
|
// Compose diffs local vs remote compose content line by line.
|
|
func Compose(local, remote string) []string {
|
|
localLines := strings.Split(strings.TrimRight(local, "\n"), "\n")
|
|
remoteLines := strings.Split(strings.TrimRight(remote, "\n"), "\n")
|
|
|
|
if local == remote {
|
|
return nil
|
|
}
|
|
|
|
// Simple line-by-line diff: mark additions and removals.
|
|
remoteSet := toSet(remoteLines)
|
|
localSet := toSet(localLines)
|
|
|
|
var diff []string
|
|
for _, l := range remoteLines {
|
|
if !localSet[l] {
|
|
diff = append(diff, "- "+l)
|
|
}
|
|
}
|
|
for _, l := range localLines {
|
|
if !remoteSet[l] {
|
|
diff = append(diff, "+ "+l)
|
|
}
|
|
}
|
|
return diff
|
|
}
|
|
|
|
// EnvKeys compares keys from .env.example against keys set in Portainer.
|
|
// If exampleKeys is nil (no .env.example file), both slices are returned nil —
|
|
// use PortainerOnlyKeys on the Result instead to handle that case.
|
|
func EnvKeys(exampleKeys []string, portainerKeys []string) (missing, unknown []string) {
|
|
if exampleKeys == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
portainerSet := make(map[string]bool, len(portainerKeys))
|
|
for _, k := range portainerKeys {
|
|
portainerSet[k] = true
|
|
}
|
|
exampleSet := make(map[string]bool, len(exampleKeys))
|
|
for _, k := range exampleKeys {
|
|
exampleSet[k] = true
|
|
}
|
|
|
|
for _, k := range exampleKeys {
|
|
if !portainerSet[k] {
|
|
missing = append(missing, k)
|
|
}
|
|
}
|
|
for _, k := range portainerKeys {
|
|
if !exampleSet[k] {
|
|
unknown = append(unknown, k)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func toSet(lines []string) map[string]bool {
|
|
s := make(map[string]bool, len(lines))
|
|
for _, l := range lines {
|
|
s[l] = true
|
|
}
|
|
return s
|
|
}
|