@@ 0,0 1,26 @@
+{
+ "Arch Linux": {
+ "separator": "=",
+ "installed": [
+ "pacman",
+ "-Sq",
+ "--needed",
+ "--noconfirm"
+ ],
+ "latest": [
+ "pacman",
+ "-Syq",
+ "--needed",
+ "--noconfirm"
+ ],
+ "absent": [
+ "pacman",
+ "-R",
+ "--noconfirm"
+ ],
+ "query": [
+ "pacman",
+ "-Q"
+ ]
+ }
+}
@@ 2,8 2,12 @@
package main
import (
+ _ "embed"
+ "encoding/json"
+ "errors"
"fmt"
"os"
+ "strings"
"git.sr.ht/~nesv/govern/runner"
)
@@ 16,8 20,43 @@ func main() {
}
func run() error {
+ distros, err := loadDistributions()
+ if err != nil {
+ return fmt.Errorf("load config: %w", err)
+ }
+
r := runner.Runner{
- DefaultState:stateInstalled,
+ DefaultState: "installed",
+ States: map[string]runner.StateFunc{
+ "installed": distros.install,
+ "latest": distros.ensureLatest,
+ "absent": distros.remove,
+ },
+ RequiredFacts: []string{
+ "os/distribution",
+ },
+ }
+
+ return r.Exec()
+}
+
+//go:embed distributions.json
+var distributionsJSON []byte
+
+func loadDistributions() (distributions, error) {
+ dd := make(distributions)
+ if err := json.Unmarshal(distributionsJSON, &dd); err != nil {
+ return nil, err
+ }
+
+ return dd, nil
+}
+
+type distributions map[string]distroConfig
+
+func (d distributions) install(r *runner.Runner, name string, attrs map[string]string) error {
+ if d == nil {
+ return errors.New("not initialized")
}
distro, err := r.Fact("os/distribution")
@@ 25,33 64,155 @@ func run() error {
return fmt.Errorf("get fact: %w", err)
}
- handlers, ok := distroHandlers[distro]
- if !ok {
+ config, found := d[distro]
+ if !found {
return fmt.Errorf("distribution not supported: %s", distro)
}
- r.States=handlers
- return r.Exec()
+ return config.install(r, name, attrs)
}
-const (
- stateInstalled = "installed"
- stateLatest = "latest"
- stateRemoved = "removed"
-)
+func (d distributions) ensureLatest(r *runner.Runner, name string, attrs map[string]string) error {
+ if d == nil {
+ return errors.New("not initialized")
+ }
+
+ distro, err := r.Fact("os/distribution")
+ if err != nil {
+ return fmt.Errorf("get fact: %w", err)
+ }
+
+ config, found := d[distro]
+ if !found {
+ return fmt.Errorf("distribution not supported: %s", distro)
+ }
+
+ return config.ensureLatest(r, name, attrs)
+}
+
+func (d distributions) remove(r *runner.Runner, name string, attrs map[string]string) error {
+ if d == nil {
+ return errors.New("not initialized")
+ }
+
+ distro, err := r.Fact("os/distribution")
+ if err != nil {
+ return fmt.Errorf("get fact: %w", err)
+ }
+
+ config, found := d[distro]
+ if !found {
+ return fmt.Errorf("distribution not supported: %s", distro)
+ }
+
+ return config.remove(r, name, attrs)
+}
-var distroHandlers = map[string]map[string]runner.StateFunc{
- "Arch Linux": {
- stateInstalled: archInstalled,
- stateLatest: archLatest,
- stateRemoved: archRemoved,
- },
+type distroConfig struct {
+ Separator string `json:"separator"`
+ Installed []string `json:"installed"`
+ Latest []string `json:"latest"`
+ Absent []string `json:"absent"`
+ Query []string `json:"query"`
}
-var commands = map[string]map[string][]string{
- "Arch Linux": {
- "installed": {"pacman", "-Sq", "--needed", "--noconfirm"},
- "latest": {"pacman", "-Syq", "--needed", "--noconfirm"},
- "absent": {"pacman", "-R", "--noconfirm"},
- },
+func (d distroConfig) install(r *runner.Runner, name string, attrs map[string]string) error {
+ pkgName := name
+ if v := attrs["version"]; v != "" {
+ pkgName = fmt.Sprintf("%s%s%s", pkgName, d.Separator, v)
+ }
+
+ if r.Pretend() {
+ installed, err := d.pkgInstalled(pkgName)
+ if err != nil {
+ return fmt.Errorf("get installed version: %w", err)
+ }
+
+ if installed {
+ return runner.OK()
+ }
+
+ fmt.Println("would be installed")
+ return nil
+ }
+
+ return runner.OK()
+}
+
+func (d distroConfig) pkgInstalled(name string) (bool, error) {
+ version, err := d.pkgversion(name)
+ if err != nil {
+ return false, err
+ }
+
+ return version != "", nil
+}
+
+func (d distroConfig) pkgversion(name string) (string, error) {
+ out, err := runner.Command(d.Query...)
+ if err != nil {
+ return "", err
+ }
+
+ fields := strings.Fields(out)
+ if n := len(fields); n < 2 {
+ return "", fmt.Errorf("expected 2 fields, got %d", n)
+ }
+
+ return fields[1], nil
+}
+
+func (d distroConfig) ensureLatest(r *runner.Runner, name string, attrs map[string]string) error {
+ version := attrs["version"]
+
+ if r.Pretend() {
+ installedVersion, err := d.pkgversion(name)
+ if err != nil {
+ return err
+ }
+
+ if installedVersion == "" {
+ fmt.Println("will be installed")
+ return nil
+ }
+
+ if installedVersion != version {
+ fmt.Println("will be upgraded")
+ return nil
+ }
+
+ return runner.OK()
+ }
+
+ if version != "" {
+ name = fmt.Sprintf("%s%s%s", name, d.Separator, version)
+ }
+
+ if _, err := runner.Command(d.Latest...); err != nil {
+ return err
+ }
+
+ return runner.OK()
+}
+
+func (d distroConfig) remove(r *runner.Runner, name string, attrs map[string]string) error {
+ if r.Pretend() {
+ version, err := d.pkgversion(name)
+ if err != nil {
+ return err
+ }
+
+ if version == "" {
+ return runner.OK()
+ }
+
+ fmt.Println("will be removed")
+ return nil
+ }
+
+ if _, err := runner.Command(d.Absent...); err != nil {
+ return err
+ }
+
+ return runner.OK()
}