// phpConfig checks the value of a PHP configuration variable func phpConfig(parameters []string) (exitCode int, exitMessage string) { // getPHPVariable returns the value of a PHP configuration value as a string // or just "" if it doesn't exist getPHPVariable := func(name string) (val string) { quote := func(str string) string { return "\"" + str + "\"" } // php -r 'echo get_cfg_var("default_mimetype"); echo := fmt.Sprintf("echo get_cfg_var(%s);", quote(name)) cmd := exec.Command("php", "-r", echo) out, err := cmd.CombinedOutput() if err != nil { wrkutils.ExecError(cmd, string(out), err) } return string(out) } name := parameters[0] value := parameters[1] actualValue := getPHPVariable(name) if actualValue == value { return 0, "" } else if actualValue == "" { msg := "PHP configuration variable not set" return wrkutils.GenericError(msg, value, []string{actualValue}) } msg := "PHP variable did not match expected value" return wrkutils.GenericError(msg, value, []string{actualValue}) }
// getIPWorker(exitCode int, exitMessage string) is an abstraction of Ip4 and Ip6 func getIPWorker(name string, address string, version int) (exitCode int, exitMessage string) { ips := getInterfaceIPs(name, version) if tabular.StrIn(address, ips) { return 0, "" } return wrkutils.GenericError("Interface does not have IP", address, ips) }
// routingTableMatch(exitCode int, exitMessage string) constructs a Worker that returns whether or not the // given string was found in the given column of the routing table. It is an // astraction of routingTableDestination, routingTableInterface, and // routingTableGateway func routingTableMatch(col string, str string) (exitCode int, exitMessage string) { column := routingTableColumn(col) if tabular.StrIn(str, column) { return 0, "" } return wrkutils.GenericError("Not found in routing table", str, column) }
// timers(exitCode int, exitMessage string) is pure DRY for systemctlTimer and systemctlTimerLoaded func timersWorker(unit string, all bool) (exitCode int, exitMessage string) { timers := getTimers(all) if tabular.StrIn(unit, timers) { return 0, "" } return wrkutils.GenericError("Timer not found", unit, timers) }
// systemctlUnitFileStatus checks whether or not the given unit file has the // given status: static | enabled | disabled func systemctlUnitFileStatus(parameters []string) (exitCode int, exitMessage string) { // getUnitFilesWithStatuses returns a pair of string slices that hold // the name of unit files with their current statuses. getUnitFilesWithStatuses := func() (units []string, statuses []string) { cmd := exec.Command("systemctl", "--no-pager", "list-unit-files") units = wrkutils.CommandColumnNoHeader(0, cmd) cmd = exec.Command("systemctl", "--no-pager", "list-unit-files") statuses = wrkutils.CommandColumnNoHeader(1, cmd) // last two are empty line and junk statistics we don't care about msg := fmt.Sprint(cmd.Args) + " didn't output enough lines" wrkutils.IndexError(msg, 2, units) wrkutils.IndexError(msg, 2, statuses) return units[:len(units)-2], statuses[:len(statuses)-2] } unit := parameters[0] status := parameters[1] units, statuses := getUnitFilesWithStatuses() var actualStatus string // TODO check if unit could be found at all for i, un := range units { if un == unit { actualStatus = statuses[i] if actualStatus == status { return 0, "" } } } msg := "Unit didn't have status" return wrkutils.GenericError(msg, status, []string{actualStatus}) }
// freeMemOrSwap is an abstraction of freeMemory and freeSwap, which measures // if the desired resource has a quantity free above the amount specified func freeMemOrSwap(input string, swapOrMem string) (exitCode int, exitMessage string) { // get numbers and units units := wrkutils.GetByteUnits(input) re := regexp.MustCompile(`\d+`) amountString := re.FindString(input) // report errors if amountString == "" { log.WithFields(log.Fields{ "input": input, "regexp": re.String(), }).Fatal("Configuration error: couldn't extract number from string") } else if units == "" { log.WithFields(log.Fields{ "input": input, }).Fatal("Configuration error: couldn't extract byte units from string") } amount := wrkutils.ParseMyInt(amountString) actualAmount := getSwapOrMemory("free", swapOrMem, units) if actualAmount > amount { return 0, "" } msg := "Free " + swapOrMem + " lower than defined threshold" actualString := fmt.Sprint(actualAmount) + units return wrkutils.GenericError(msg, input, []string{actualString}) }
// groupNotFound creates generic error messages and exit codes for groupExits, // userInGroup, and groupID func groupNotFound(name string) (int, string) { // get a nicely formatted list of groups that do exist var existing []string for _, group := range getGroups() { existing = append(existing, group.Name) } return wrkutils.GenericError("Group not found", name, existing) }
// responseMatchesGeneral is an abstraction of responseMatches and // responseMatchesInsecure that simply varies in the security of the connection func responseMatchesGeneral(parameters []string, secure bool) (exitCode int, exitMessage string) { urlstr := parameters[0] re := wrkutils.ParseUserRegex(parameters[1]) body := wrkutils.URLToBytes(urlstr, secure) if re.Match(body) { return 0, "" } msg := "Response didn't match regexp" return wrkutils.GenericError(msg, re.String(), []string{string(body)}) }
// swapUsage checks to see whether or not the system has a swap usage // percentage below a certain threshold func swapUsage(parameters []string) (exitCode int, exitMessage string) { maxPercentUsed := wrkutils.ParseMyInt(parameters[0]) actualPercentUsed := getUsedPercent("swap") if actualPercentUsed < float32(maxPercentUsed) { return 0, "" } msg := "Swap usage above defined maximum" slc := []string{fmt.Sprint(actualPercentUsed)} return wrkutils.GenericError(msg, fmt.Sprint(maxPercentUsed), slc) }
// systemctlSock is an abstraction of systemctlSockPath and systemctlSockUnit, // it reads from `systemctl list-sockets` and sees if the value is in the // appropriate column. func systemctlSock(value string, column string) (exitCode int, exitMessage string) { outstr := wrkutils.CommandOutput(exec.Command("systemctl", "list-sockets")) lines := tabular.Lines(outstr) msg := "systemctl list-sockers didn't output enough rows" wrkutils.IndexError(msg, len(lines)-4, lines) unlines := tabular.Unlines(lines[:len(lines)-4]) table := tabular.SeparateOnAlignment(unlines) values := tabular.GetColumnByHeader(column, table) if tabular.StrIn(value, values) { return 0, "" } return wrkutils.GenericError("Socket not found", value, values) }
// userInGroup checks whether or not a given user is in a given group func userInGroup(parameters []string) (exitCode int, exitMessage string) { user := parameters[0] group := parameters[0] groups := getGroups() for _, g := range groups { if g.Name == group { if tabular.StrIn(user, g.Users) { return 0, "" } return wrkutils.GenericError("User not found in group", user, g.Users) } } return groupNotFound(group) }
// commandOutputMatches checks to see if a command's combined output matches a // given regexp func commandOutputMatches(parameters []string) (exitCode int, exitMessage string) { toExec := parameters[0] re := wrkutils.ParseUserRegex(parameters[1]) cmd := exec.Command("bash", "-c", toExec) out, err := cmd.CombinedOutput() if err != nil { wrkutils.ExecError(cmd, string(out), err) } if re.Match(out) { return 0, "" } msg := "Command output did not match regexp" return wrkutils.GenericError(msg, re.String(), []string{string(out)}) }
// module checks to see if a kernel module is installed func module(parameters []string) (exitCode int, exitMessage string) { // kernelModules returns a list of all modules that are currently loaded // TODO just read from /proc/modules kernelModules := func() (modules []string) { cmd := exec.Command("/sbin/lsmod") return wrkutils.CommandColumnNoHeader(0, cmd) } name := parameters[0] modules := kernelModules() if tabular.StrIn(name, modules) { return 0, "" } return wrkutils.GenericError("Module is not loaded", name, modules) }
// permissions checks to see if a file's octal permissions match the given set func permissions(parameters []string) (exitCode int, exitMessage string) { path := parameters[0] givenMode := parameters[1] finfo, err := os.Stat(path) if err != nil { wrkutils.CouldntReadError(path, err) } actualMode := fmt.Sprint(finfo.Mode().Perm()) // -rwxrw-r-- format if actualMode == givenMode { return 0, "" } msg := "File modes did not match" return wrkutils.GenericError(msg, givenMode, []string{actualMode}) }
// temp parses the output of lm_sensors and determines if Core 0 (all cores) are // over a certain threshold as specified in the JSON. func temp(parameters []string) (exitCode int, exitMessage string) { // TODO: check for negative, outrageously high temperatures // allCoreTemps returns the temperature of each core allCoreTemps := func() (temps []int) { cmd := exec.Command("sensors") out, err := cmd.CombinedOutput() outstr := string(out) wrkutils.ExecError(cmd, outstr, err) restr := `Core\s\d+:\s+[\+\-](?P<temp>\d+)\.*\d*°C` re := regexp.MustCompile(restr) for _, line := range regexp.MustCompile(`\n+`).Split(outstr, -1) { if re.MatchString(line) { // submatch captures only the integer part of the temperature matchDict := wrkutils.SubmatchMap(re, line) if _, ok := matchDict["temp"]; !ok { log.WithFields(log.Fields{ "regexp": re.String(), "matchDict": matchDict, "output": outstr, }).Fatal("Couldn't find any temperatures in `sensors` output") } tempInt64, err := strconv.ParseInt(matchDict["temp"], 10, 64) if err != nil { log.WithFields(log.Fields{ "regexp": re.String(), "matchDict": matchDict, "output": outstr, "error": err.Error(), }).Fatal("Couldn't parse integer from `sensors` output") } temps = append(temps, int(tempInt64)) } } return temps } // getCoreTemp returns an integer temperature for a certain core getCoreTemp := func(core int) (temp int) { temps := allCoreTemps() wrkutils.IndexError("No such core available", core, temps) return temps[core] } max := wrkutils.ParseMyInt(parameters[0]) temp := getCoreTemp(0) if temp < max { return 0, "" } msg := "Core temp exceeds defined maximum" return wrkutils.GenericError(msg, max, []string{fmt.Sprint(temp)}) }
// groupID checks to see if a group of a certain name has a given integer id func groupID(parameters []string) (exitCode int, exitMessage string) { name := parameters[0] id := wrkutils.ParseMyInt(parameters[1]) groups := getGroups() for _, g := range groups { if g.Name == name { if g.ID == id { return 0, "" } msg := "Group does not have expected ID" return wrkutils.GenericError(msg, fmt.Sprint(id), []string{fmt.Sprint(g.ID)}) } } return groupNotFound(name) }
// systemctlService checks to see if a service has a givens status // status: active | loaded func systemctlService(service string, activeOrLoaded string) (exitCode int, exitMessage string) { // cmd depends on whether we're checking active or loaded cmd := exec.Command("systemctl", "show", "-p", "ActiveState", service) if activeOrLoaded == "loaded" { cmd = exec.Command("systemctl", "show", "-p", "LoadState", service) } outString := wrkutils.CommandOutput(cmd) contained := "ActiveState=active" if activeOrLoaded == "loaded" { contained = "LoadState=loaded" } if strings.Contains(outString, contained) { return 0, "" } msg := "Service not " + activeOrLoaded return wrkutils.GenericError(msg, service, []string{outString}) }
// interfaceExists detects if a network interface exists, func interfaceExists(parameters []string) (exitCode int, exitMessage string) { // getInterfaceNames returns the names of all network interfaces getInterfaceNames := func() (interfaces []string) { for _, iface := range getInterfaces() { interfaces = append(interfaces, iface.Name) } return } name := parameters[0] interfaces := getInterfaceNames() for _, iface := range interfaces { if iface == name { return 0, "" } } return wrkutils.GenericError("Interface does not exist", name, interfaces) }
// up determines if a network interface is up and running or not func up(parameters []string) (exitCode int, exitMessage string) { // getUpInterfaces returns all the names of the interfaces that are up getUpInterfaces := func() (interfaceNames []string) { for _, iface := range getInterfaces() { if iface.Flags&net.FlagUp != 0 { interfaceNames = append(interfaceNames, iface.Name) } } return interfaceNames } name := parameters[0] upInterfaces := getUpInterfaces() if tabular.StrIn(name, upInterfaces) { return 0, "" } return wrkutils.GenericError("Interface is not up", name, upInterfaces) }
// pacmanIgnore checks to see whether a given package is in /etc/pacman.conf's // IgnorePkg setting func pacmanIgnore(parameters []string) (exitCode int, exitMessage string) { pkg := parameters[0] path := "/etc/pacman.conf" data := wrkutils.FileToString(path) re := regexp.MustCompile(`[^#]IgnorePkg\s+=\s+.+`) find := re.FindString(data) var packages []string if find != "" { spl := strings.Split(find, " ") wrkutils.IndexError("Not enough lines in "+path, 2, spl) packages = spl[2:] // first two are "IgnorePkg" and "=" if tabular.StrIn(pkg, packages) { return 0, "" } } msg := "Couldn't find package in IgnorePkg" return wrkutils.GenericError(msg, pkg, packages) }
// cpuUsage checks to see whether or not CPU usage is below a certain %. func cpuUsage(parameters []string) (exitCode int, exitMessage string) { // TODO check that parameters are in range 0 < x < 100 cpuPercentUsed := func(sampleTime time.Duration) float32 { idle0, total0 := getCPUSample() time.Sleep(sampleTime) idle1, total1 := getCPUSample() idleTicks := float32(idle1 - idle0) totalTicks := float32(total1 - total0) return (100 * (totalTicks - idleTicks) / totalTicks) } maxPercentUsed := wrkutils.ParseMyInt(parameters[0]) actualPercentUsed := cpuPercentUsed(3 * time.Second) if actualPercentUsed < float32(maxPercentUsed) { return 0, "" } msg := "CPU usage above defined maximum" slc := []string{fmt.Sprint(actualPercentUsed)} return wrkutils.GenericError(msg, fmt.Sprint(maxPercentUsed), slc) }
// gateway checks to see that the default gateway has a certain IP func gateway(parameters []string) (exitCode int, exitMessage string) { // getGatewayAddress filters all gateway IPs for a non-zero value getGatewayAddress := func() (addr string) { ips := routingTableColumn("Gateway") for _, ip := range ips { if ip != "0.0.0.0" { return ip } } return "0.0.0.0" } address := parameters[0] gatewayIP := getGatewayAddress() if address == gatewayIP { return 0, "" } msg := "Gateway does not have address" return wrkutils.GenericError(msg, address, []string{gatewayIP}) }
// running checks if a process is running using `ps aux`, and searching for the // process name, excluding this process (in case the process name is in the JSON // file name) func running(parameters []string) (exitCode int, exitMessage string) { // getRunningCommands returns the entries in the "COMMAND" column of `ps aux` getRunningCommands := func() (commands []string) { cmd := exec.Command("ps", "aux") return wrkutils.CommandColumnNoHeader(10, cmd) } proc := parameters[0] // remove this process from consideration commands := getRunningCommands() var filtered []string for _, cmd := range commands { if !strings.Contains(cmd, "distributive") { filtered = append(filtered, cmd) } } if tabular.StrIn(proc, filtered) { return 0, "" } return wrkutils.GenericError("Process not running", proc, filtered) }
// checksum checks the hash of a given file using the given algorithm func checksum(parameters []string) (exitCode int, exitMessage string) { // getChecksum returns the checksum of some data, using a specified // algorithm getChecksum := func(algorithm string, data []byte) (checksum string) { algorithm = strings.ToUpper(algorithm) // default hasher := md5.New() switch algorithm { case "SHA1": hasher = sha1.New() case "SHA224": hasher = sha256.New224() case "SHA256": hasher = sha256.New() case "SHA384": hasher = sha512.New384() case "SHA512": hasher = sha512.New() } hasher.Write(data) str := hex.EncodeToString(hasher.Sum(nil)) return str } // getFileChecksum is self-explanatory getFileChecksum := func(algorithm string, path string) (checksum string) { return getChecksum(algorithm, wrkutils.FileToBytes(path)) } algorithm := parameters[0] checkAgainst := parameters[1] path := parameters[2] chksum := getFileChecksum(algorithm, path) // TODO warn on unequal lengths if chksum == checkAgainst { return 0, "" } msg := "Checksums do not match for file: " + path return wrkutils.GenericError(msg, checkAgainst, []string{chksum}) }
// existsRepoWithProperty is an abstraction of YumRepoExists and YumRepoURL. // It takes a struct field name to check, and an expected value. If the expected // value is found in the field of a repo, it returns 0, "" else an error message. // Valid choices for prop: "URL" | "Name" | "Name" func existsRepoWithProperty(prop string, val *regexp.Regexp, manager string) (int, string) { var properties []string for _, repo := range getRepos(manager) { switch prop { case "URL": properties = append(properties, repo.URL) case "Name": properties = append(properties, repo.Name) case "Status": properties = append(properties, repo.Status) case "ID": properties = append(properties, repo.ID) default: log.Fatal("Repos don't have the requested property: " + prop) } } if tabular.ReIn(val, properties) { return 0, "" } msg := "Repo with given " + prop + " not found" return wrkutils.GenericError(msg, val.String(), properties) }
// gatewayInterface checks that the default gateway is using a specified interface func gatewayInterface(parameters []string) (exitCode int, exitMessage string) { // getGatewayInterface returns the interface that the default gateway is // operating on getGatewayInterface := func() (iface string) { ips := routingTableColumn("Gateway") names := routingTableColumn("Iface") for i, ip := range ips { if ip != "0.0.0.0" { msg := "Fewer names in kernel routing table than IPs" wrkutils.IndexError(msg, i, names) return names[i] // interface name } } return "" } name := parameters[0] iface := getGatewayInterface() if name == iface { return 0, "" } msg := "Default gateway does not operate on interface" return wrkutils.GenericError(msg, name, []string{iface}) }
func diskUsage(parameters []string) (exitCode int, exitMessage string) { // percentFSUsed gets the percent of the filesystem that is occupied percentFSUsed := func(path string) int { // get FS info (*nix systems only!) var stat syscall.Statfs_t syscall.Statfs(path, &stat) // blocks * size of block = available size totalBytes := stat.Blocks * uint64(stat.Bsize) availableBytes := stat.Bavail * uint64(stat.Bsize) usedBytes := totalBytes - availableBytes percentUsed := int((float64(usedBytes) / float64(totalBytes)) * 100) return percentUsed } maxPercentUsed := wrkutils.ParseMyInt(parameters[1]) actualPercentUsed := percentFSUsed(parameters[0]) if actualPercentUsed < maxPercentUsed { return 0, "" } msg := "More disk space used than expected" slc := []string{fmt.Sprint(actualPercentUsed) + "%"} return wrkutils.GenericError(msg, fmt.Sprint(maxPercentUsed)+"%", slc) }
// port parses /proc/net/tcp to determine if a given port is in an open state // and returns an error if it is not. func port(parameters []string) (exitCode int, exitMessage string) { // getHexPorts gets all open ports as hex strings from /proc/net/tcp getHexPorts := func() (ports []string) { paths := [2]string{"/proc/net/tcp", "/proc/net/udp"} for _, path := range paths { data := wrkutils.FileToString(path) table := tabular.ProbabalisticSplit(data) // TODO by header isn't working //localAddresses := tabular.GetColumnByHeader("local_address", table) localAddresses := tabular.GetAllNoHeader(table) portRe := regexp.MustCompile(`([0-9A-F]{8}):([0-9A-F]{4})`) for _, address := range localAddresses { port := portRe.FindString(address) if port != "" { if len(port) < 10 { log.WithFields(log.Fields{ "port": port, "length": len(port), }).Fatal("Couldn't parse port number in " + path) } portString := string(port[9:]) ports = append(ports, portString) } } } return ports } // strHexToDecimal converts from string containing hex number to int strHexToDecimal := func(hex string) int { portInt, err := strconv.ParseInt(hex, 16, 64) if err != nil { log.WithFields(log.Fields{ "number": hex, "error": err.Error(), }).Fatal("Couldn't parse hex number") } return int(portInt) } // getOpenPorts gets a list of open/listening ports as integers getOpenPorts := func() (ports []int) { for _, port := range getHexPorts() { ports = append(ports, strHexToDecimal(port)) } return ports } // TODO check if it is in a valid range port := wrkutils.ParseMyInt(parameters[0]) open := getOpenPorts() for _, p := range open { if p == port { return 0, "" } } // convert ports to string to send to wrkutils.GenericError var strPorts []string for _, port := range open { strPorts = append(strPorts, fmt.Sprint(port)) } return wrkutils.GenericError("Port not open", fmt.Sprint(port), strPorts) }