// UnitFileStatuses returns a list of all unit files with their current status, // as shown by `systemctl list-unit-files`. func UnitFileStatuses() (units, statuses []string, err error) { cmd := exec.Command("systemctl", "--no-pager", "list-unit-files") out, err := cmd.CombinedOutput() if err != nil { err2 := errors.New(err.Error() + ": output: " + string(out)) return units, statuses, err2 } table := tabular.ProbabalisticSplit(string(out)) units = tabular.GetColumnNoHeader(0, table) // last two are empty line and junk statistics we don't care about if len(units) <= 3 { msg := fmt.Sprint(cmd.Args) + " didn't output enough lines" return units, statuses, errors.New(msg) } cmd = exec.Command("systemctl", "--no-pager", "list-unit-files") table = tabular.ProbabalisticSplit(string(out)) statuses = tabular.GetColumnNoHeader(1, table) // last two are empty line and junk statistics we don't care about if len(statuses) <= 3 { msg := fmt.Sprint(cmd.Args) + " didn't output enough lines" return units, statuses, errors.New(msg) } return units[:len(units)-2], statuses[:len(statuses)-2], nil }
// 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) }
// 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 }
// 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) }
// 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 }
// getYumRepos constructs Repos from the yum.conf file at path. Gives non-zero // Names, Fullnames, and URLs. func getYumRepos() (repos []repo) { // safeAccess allows access w/o fear of a panic into a slice of strings safeAccess := func(slc []string, index int) string { // catch runtime panic defer func() { if err := recover(); err != nil { msg := "safeAccess: Please report this error" errutil.IndexError(msg, index, slc) } }() // invoke inside defer if len(slc) > index { return slc[index] } return "" } // get and parse output of `yum repolist` cmd := exec.Command("yum", "repolist") outstr := chkutil.CommandOutput(cmd) // handle empty case if strings.Contains(outstr, "repolist: 0") { return repos } slc := tabular.ProbabalisticSplit(outstr) ids := tabular.GetColumnNoHeader(0, slc) // TODO use columnbyheader here errutil.IndexError("getYumRepos", 2, ids) ids = ids[:len(ids)-2] names := tabular.GetColumnNoHeader(1, slc) // TODO and here statuses := tabular.GetColumnNoHeader(2, slc) // TODO and here if len(ids) != len(names) || len(names) != len(statuses) { log.WithFields(log.Fields{ "names": len(names), "ids": len(ids), "statuses": len(statuses), }).Warn("Could not fetch complete metadata for every repo.") } // Construct repos for i := range ids { name := safeAccess(names, i) id := safeAccess(ids, i) status := safeAccess(statuses, i) repo := repo{Name: name, ID: id, Status: status} repos = append(repos, repo) } return repos }
// 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 }
// getAptrepos constructs repos from the sources.list file at path. Gives // non-zero URLs func getAptRepos() (repos []repo) { // getAptSources returns all the urls of all apt sources (including source // code repositories getAptSources := func() (urls []string) { otherLists := chkutil.GetFilesWithExtension("/etc/apt/sources.list.d", ".list") sourceLists := append([]string{"/etc/apt/sources.list"}, otherLists...) for _, f := range sourceLists { split := tabular.ProbabalisticSplit(chkutil.FileToString(f)) // filter out comments commentRegex := regexp.MustCompile(`^\s*#`) for _, line := range split { if len(line) > 1 && !(commentRegex.MatchString(line[0])) { urls = append(urls, line[1]) } } } return urls } for _, src := range getAptSources() { repos = append(repos, repo{URL: src}) } return repos }
// 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) }
// 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) }