// how many inodes are in this given state? state can be one of: // total, used, free, percent func inodesInState(filesystem, state string) (total uint64, err error) { cmd := exec.Command("df", "-i") out, err := cmd.CombinedOutput() if err != nil { return total, err } table := tabular.ProbabalisticSplit(string(out)) filesystems := tabular.GetColumnByHeader("Filesystem", table) var totals []string switch state { case "total": totals = tabular.GetColumnByHeader("Inodes", table) case "free": totals = tabular.GetColumnByHeader("IFree", table) case "used": totals = tabular.GetColumnByHeader("IUsed", table) case "percent": totals = tabular.GetColumnByHeader("IUse%", table) default: formatStr := "Internal error: unexpected state in inodesInState: %v" return total, fmt.Errorf(formatStr, state) } if len(filesystems) != len(totals) { formatStr := "The number of filesystems (%d) didn't match the number of" formatStr += " inode totals (%d)!" return total, fmt.Errorf(formatStr, len(filesystems), len(totals)) } for i := range totals { if filesystems[i] == filesystem { // trim the % in case the state was "percent" return strconv.ParseUint(strings.TrimSuffix(totals[i], "%"), 10, 64) } } return total, fmt.Errorf("Couldn't find that filesystem: " + filesystem) }
// GetHexPorts gets all open ports as hex strings from /proc/net/{tcp,udp} // Its protocol argument can only be one of: "tcp" | "udp" func GetHexPorts(protocol string) (ports []string) { var path string switch strings.ToLower(protocol) { case "tcp": path = "/proc/net/tcp" case "udp": path = "/proc/net/udp" default: log.WithFields(log.Fields{ "protocol": protocol, "valid protocols": "tcp|udp", }).Fatal("Invalid protocol passed to GetHexPorts!") } data := chkutil.FileToString(path) rowSep := regexp.MustCompile(`\n+`) colSep := regexp.MustCompile(`\s+`) table := tabular.SeparateString(rowSep, colSep, data) localAddresses := tabular.GetColumnByHeader("local_address", 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 }
// ListeningSockets returns a list of all sockets in the "LISTENING" state func ListeningSockets() (socks []string, err error) { out, err := exec.Command("systemctl", "list-sockets").CombinedOutput() if err != nil { return socks, errors.New(err.Error() + ": output: " + string(out)) } table := tabular.ProbabalisticSplit(string(out)) return tabular.GetColumnByHeader("LISTENING", table), nil }
// DockerImageRepositories returns a slice of the names of the Docker images // present on the host (what's under the REPOSITORIES column of `docker images`) func DockerImageRepositories() (images []string, err error) { cmd := exec.Command("docker", "images") out, err := cmd.CombinedOutput() if err != nil { return images, err } table := tabular.ProbabalisticSplit(string(out)) return tabular.GetColumnByHeader("REPOSITORIES", table), nil }
// RunningContainers returns a list of names of running docker containers // (what's under the IMAGE column of `docker ps -a` if it has status "Up". func RunningContainers() (containers []string, err error) { cmd := exec.Command("docker", "ps", "-a") out, err := cmd.CombinedOutput() if err != nil { return containers, err } // the output of `docker ps -a` has spaces in columns, but each column // is separated by 2 or more spaces. Just what Probabalistic was made for! lines := tabular.ProbabalisticSplit(string(out)) names := tabular.GetColumnByHeader("IMAGE", lines) statuses := tabular.GetColumnByHeader("STATUS", lines) for i, status := range statuses { // index error caught by second condition in if clause if strings.Contains(status, "Up") && len(names) > i { containers = append(containers, names[i]) } } return containers, nil }
// swapOrMemory returns output from `free`, it is an abstraction of swap and // memory. inputs: status: free | used | total; swapOrMem: memory | swap; // units: b | kb | mb | gb | tb func swapOrMemory(status string, swapOrMem string, units string) (int, error) { unitsToFlag := map[string]string{ "b": "--bytes", "kb": "--kilo", "mb": "--mega", "gb": "--giga", "tb": "--tera", } typeToRow := map[string]int{ "memory": 0, "swap": 1, } // check to see that our keys are really in our dict if _, ok := unitsToFlag[units]; !ok { return 0, errors.New("Invalid units in swapOrMemory: " + units) } else if _, ok := typeToRow[swapOrMem]; !ok { return 0, errors.New("Invalid option in swapOrMemory: " + swapOrMem) } // execute free and return the appropriate output cmd := exec.Command("free", unitsToFlag[units]) out, err := cmd.CombinedOutput() if err != nil { return 0, err } // TODO probabalisticsplit isn't handling this appropriately //table := tabular.ProbabalisticSplit(outStr) colSep := regexp.MustCompile(`\s+`) rowSep := regexp.MustCompile(`\n+`) table := tabular.SeparateString(rowSep, colSep, string(out)) column := tabular.GetColumnByHeader(status, table) // filter out useless row from some versions of `free` for i, row := range table { if len(row) > 0 && strings.Contains(row[0], "-/+") { table = append(table[:i], table[i+1:]...) } } row := typeToRow[swapOrMem] // check for errors in output of `free` if column == nil || len(column) < 1 { errors.New("Free column was empty") } if row >= len(column) { errors.New("`free` didn't output enough rows") } toReturn, err := strconv.ParseInt(column[row], 10, 64) if err != nil { errors.New("Couldn't parse output of `free` as an int") } return int(toReturn), nil }
// returns a column of the routing table as a slice of strings func routingTableColumn(name string) []string { cmd := exec.Command("route", "-n") out := wrkutils.CommandOutput(cmd) table := tabular.ProbabalisticSplit(out) if len(table) < 1 { log.WithFields(log.Fields{ "column": name, "table": "\n" + tabular.ToString(table), }).Fatal("Routing table was not available or not properly parsed") } finalTable := table[1:] // has extra line before headers return tabular.GetColumnByHeader(name, finalTable) }
// 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) }
// DockerImageRepositories returns a slice of the names of the Docker images // present on the host (what's under the REPOSITORIES column of `docker images`) func DockerImageRepositories() (images []string, err error) { cmd := exec.Command("docker", "images") out, err := cmd.CombinedOutput() if err != nil { // try escalating to sudo, the error might have been one of permissions cmd = exec.Command("sudo", "docker", "images") out, err = cmd.CombinedOutput() if err != nil { return images, err } } table := tabular.ProbabalisticSplit(string(out)) return tabular.GetColumnByHeader("REPOSITORY", table), nil }
// getTimers returns of all the timers under the UNIT column of // `systemctl list-timers` func getTimers(all bool) []string { cmd := exec.Command("systemctl", "list-timers") if all { cmd = exec.Command("systemctl", "list-timers", "--all") } out, err := cmd.CombinedOutput() outstr := string(out) wrkutils.ExecError(cmd, outstr, err) // last three lines are junk lines := tabular.Lines(outstr) msg := fmt.Sprint(cmd.Args) + " didn't output enough lines" wrkutils.IndexError(msg, 3, lines) table := tabular.SeparateOnAlignment(tabular.Unlines(lines[:len(lines)-3])) column := tabular.GetColumnByHeader("UNIT", table) return column }
// Timers returns a list of the active systemd timers, as found under the // UNIT column of `systemctl list-timers`. It can optionally list all timers. func Timers(all bool) (timers []string, err error) { cmd := exec.Command("systemctl", "list-timers") if all { cmd = exec.Command("systemctl", "list-timers", "--all") } out, err := cmd.CombinedOutput() if err != nil { return timers, errors.New(err.Error() + ": output: " + string(out)) } // last three lines are junk lines := tabular.Lines(string(out)) if len(lines) <= 3 { msg := fmt.Sprint(cmd.Args) + " didn't output enough lines" return timers, errors.New(msg) } table := tabular.SeparateOnAlignment(tabular.Unlines(lines[:len(lines)-3])) column := tabular.GetColumnByHeader("UNIT", table) return column, nil }
// getSwapOrMemory returns output from `free`, it is an abstraction of // getSwap and getMemory. inputs: status: free | used | total // swapOrMem: memory | swap, units: b | kb | mb | gb | tb // TODO: support kib/gib style units, with proper transformations. func getSwapOrMemory(status string, swapOrMem string, units string) int { statusToColumn := map[string]int{ "total": 1, "used": 2, "free": 3, } unitsToFlag := map[string]string{ "b": "--bytes", "kb": "--kilo", "mb": "--mega", "gb": "--giga", "tb": "--tera", } typeToRow := map[string]int{ "memory": 0, "swap": 1, } // check to see that our keys are really in our dict if _, ok := statusToColumn[status]; !ok { log.WithFields(log.Fields{ "status": status, "expected": []string{"total", "used", "free"}, }).Fatal("Internal error: invalid status in getSwapOrMemory") } else if _, ok := unitsToFlag[units]; !ok { log.WithFields(log.Fields{ "units": units, "expected": []string{"b", "kb", "mb", "gb", "tb"}, }).Fatal("Internal error: invalid units in getSwapOrMemory") } else if _, ok := typeToRow[swapOrMem]; !ok { log.WithFields(log.Fields{ "option": swapOrMem, "expected": []string{"memory", "swap"}, }).Fatal("Internal error: invalid option in getSwapOrMemory") } // execute free and return the appropriate output cmd := exec.Command("free", unitsToFlag[units]) outStr := wrkutils.CommandOutput(cmd) table := tabular.ProbabalisticSplit(outStr) column := tabular.GetColumnByHeader(status, table) row := typeToRow[swapOrMem] // check for errors in output of `free` if column == nil { log.WithFields(log.Fields{ "header": status, "table": "\n" + tabular.ToString(table), }).Fatal("Free column was empty") } if row >= len(column) { log.WithFields(log.Fields{ "output": outStr, "column": column, "row": row, }).Fatal("`free` didn't output enough rows") } toReturn, err := strconv.ParseInt(column[row], 10, 64) if err != nil { log.WithFields(log.Fields{ "cell": column[row], "error": err.Error(), "output": outStr, }).Fatal("Couldn't parse output of `free` as an int") } return int(toReturn) }