pedroMMM/goss

View on GitHub
system/system.go

Summary

Maintainability
A
1 hr
Test Coverage
package system

import (
    "bytes"
    "io/ioutil"
    "os"
    "os/exec"
    "strconv"
    "sync"

    "github.com/aelsabbahy/GOnetstat"
    // This needs a better name
    "github.com/aelsabbahy/go-ps"

    util2 "github.com/aelsabbahy/goss/util"
)

type Resource interface {
    Exists() (bool, error)
}

type System struct {
    NewPackage     func(string, *System, util2.Config) Package
    NewFile        func(string, *System, util2.Config) File
    NewAddr        func(string, *System, util2.Config) Addr
    NewPort        func(string, *System, util2.Config) Port
    NewService     func(string, *System, util2.Config) Service
    NewUser        func(string, *System, util2.Config) User
    NewGroup       func(string, *System, util2.Config) Group
    NewCommand     func(string, *System, util2.Config) Command
    NewDNS         func(string, *System, util2.Config) DNS
    NewProcess     func(string, *System, util2.Config) (Process, error)
    NewGossfile    func(string, *System, util2.Config) Gossfile
    NewKernelParam func(string, *System, util2.Config) KernelParam
    NewMount       func(string, *System, util2.Config) Mount
    NewInterface   func(string, *System, util2.Config) Interface
    NewHTTP        func(string, *System, util2.Config) HTTP
    ports          map[string][]GOnetstat.Process
    portsOnce      sync.Once
    procMap        map[string][]ps.Process
    procOnce       sync.Once
}

func (s *System) Ports() map[string][]GOnetstat.Process {
    s.portsOnce.Do(func() {
        s.ports = GetPorts(false)
    })
    return s.ports
}

func (s *System) ProcMap() (map[string][]ps.Process, error) {
    var err error

    s.procOnce.Do(func() {
        s.procMap, err = GetProcs()
    })

    return s.procMap, err
}

func New(packageManager string) *System {
    sys := &System{
        NewFile:        NewDefFile,
        NewAddr:        NewDefAddr,
        NewPort:        NewDefPort,
        NewUser:        NewDefUser,
        NewGroup:       NewDefGroup,
        NewCommand:     NewDefCommand,
        NewDNS:         NewDefDNS,
        NewProcess:     NewDefProcess,
        NewGossfile:    NewDefGossfile,
        NewKernelParam: NewDefKernelParam,
        NewMount:       NewDefMount,
        NewInterface:   NewDefInterface,
        NewHTTP:        NewDefHTTP,
    }

    sys.detectService()
    sys.detectPackage(packageManager)

    return sys
}

// detectPackage adds the correct package creation function to a System struct
func (sys *System) detectPackage(p string) {
    if p != "dpkg" && p != "apk" && p != "pacman" && p != "rpm" {
        p = DetectPackageManager()
    }
    switch p {
    case "dpkg":
        sys.NewPackage = NewDebPackage
    case "apk":
        sys.NewPackage = NewAlpinePackage
    case "pacman":
        sys.NewPackage = NewPacmanPackage
    default:
        sys.NewPackage = NewRpmPackage
    }
}

// detectService adds the correct service creation function to a System struct
func (sys *System) detectService() {
    switch DetectService() {
    case "upstart":
        sys.NewService = NewServiceUpstart
    case "systemd":
        sys.NewService = NewServiceSystemd
    case "systemdlegacy":
        sys.NewService = NewServiceSystemdLegacy
    case "alpineinit":
        sys.NewService = NewAlpineServiceInit
    default:
        sys.NewService = NewServiceInit
    }
}

// DetectPackageManager attempts to detect whether or not the system is using
// "dpkg", "rpm", "apk", or "pacman" package managers. It first attempts to
// detect the distro. If that fails, it falls back to finding package manager
// executables. If that fails, it returns the empty string.
func DetectPackageManager() string {
    switch DetectDistro() {
    case "ubuntu":
        return "dpkg"
    case "redhat":
        return "rpm"
    case "alpine":
        return "apk"
    case "arch":
        return "pacman"
    case "debian":
        return "dpkg"
    }
    for _, manager := range []string{"dpkg", "rpm", "apk", "pacman"} {
        if HasCommand(manager) {
            return manager
        }
    }
    return ""
}

// DetectService attempts to detect what kind of service management the system
// is using, "systemd", "upstart", "alpineinit", or "init". It looks for systemctl
// command to detect systemd, and falls back on DetectDistro otherwise. If it can't
// decide, it returns "init".
func DetectService() string {
    if HasCommand("systemctl") {
        if isLegacySystemd() {
            return "systemdlegacy"
        }
        return "systemd"
    }
    // Centos Docker container doesn't run systemd, so we detect it or use init.
    switch DetectDistro() {
    case "ubuntu":
        return "upstart"
    case "alpine":
        return "alpineinit"
    case "arch":
        return "systemd"
    }
    return "init"
}

// DetectDistro attempts to detect which Linux distribution this computer is
// using. One of "ubuntu", "redhat" (including Centos), "alpine", "arch", or
// "debian". If it can't decide, it returns an empty string.
func DetectDistro() string {
    if b, e := ioutil.ReadFile("/etc/lsb-release"); e == nil && bytes.Contains(b, []byte("Ubuntu")) {
        return "ubuntu"
    } else if isRedhat() {
        return "redhat"
    } else if _, err := os.Stat("/etc/alpine-release"); err == nil {
        return "alpine"
    } else if _, err := os.Stat("/etc/arch-release"); err == nil {
        return "arch"
    } else if _, err := os.Stat("/etc/debian_version"); err == nil {
        return "debian"
    }
    return ""
}

// HasCommand returns whether or not an executable by this name is on the PATH.
func HasCommand(cmd string) bool {
    if _, err := exec.LookPath(cmd); err == nil {
        return true
    }
    return false
}

func isLegacySystemd() bool {
    if b, err := ioutil.ReadFile("/etc/debian_version"); err == nil {
        i := bytes.Index(b, []byte("."))
        if i < 0 {
            return false
        }
        if major, err := strconv.Atoi(string(b[:i])); err == nil {
            return major < 9
        }
    }
    return false
}

func isRedhat() bool {
    if _, err := os.Stat("/etc/redhat-release"); err == nil {
        return true
    } else if _, err := os.Stat("/etc/system-release"); err == nil {
        return true
    }
    return false
}