mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
234 lines
5.7 KiB
Go
234 lines
5.7 KiB
Go
package analyzer
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/replicatedhq/troubleshoot/internal/util"
|
|
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
|
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
|
)
|
|
|
|
type AnalyzeHostSystemPackages struct {
|
|
hostAnalyzer *troubleshootv1beta2.SystemPackagesAnalyze
|
|
}
|
|
|
|
func (a *AnalyzeHostSystemPackages) Title() string {
|
|
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "System Packages")
|
|
}
|
|
|
|
func (a *AnalyzeHostSystemPackages) IsExcluded() (bool, error) {
|
|
return isExcluded(a.hostAnalyzer.Exclude)
|
|
}
|
|
|
|
func (a *AnalyzeHostSystemPackages) Analyze(
|
|
getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents,
|
|
) ([]*AnalyzeResult, error) {
|
|
hostAnalyzer := a.hostAnalyzer
|
|
collectorName := hostAnalyzer.CollectorName
|
|
if collectorName == "" {
|
|
collectorName = "packages"
|
|
}
|
|
|
|
localPath := fmt.Sprintf("%s/%s-packages.json", collect.NodeInfoBaseDir, collectorName)
|
|
packagesFileName := fmt.Sprintf("%s-packages.json", collectorName)
|
|
|
|
collectedContents, err := retrieveCollectedContents(
|
|
getCollectedFileContents,
|
|
localPath,
|
|
collect.NodeInfoBaseDir,
|
|
packagesFileName,
|
|
)
|
|
if err != nil {
|
|
return []*AnalyzeResult{{Title: a.Title()}}, err
|
|
}
|
|
|
|
var results []*AnalyzeResult
|
|
for _, content := range collectedContents {
|
|
currentTitle := a.Title()
|
|
if content.NodeName != "" {
|
|
currentTitle = fmt.Sprintf("%s - Node %s", a.Title(), content.NodeName)
|
|
}
|
|
result, err := a.analyzeSingleNode(content, currentTitle)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to analyze system packages for %s", currentTitle)
|
|
}
|
|
if result != nil {
|
|
results = append(results, result...)
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func getSystemPackageTemplateMap(pkg collect.SystemPackage, osName string, osVersion string) map[string]interface{} {
|
|
osVersionParts := strings.Split(osVersion, ".")
|
|
osVersionMajor, _ := strconv.ParseInt(osVersionParts[0], 10, 64)
|
|
|
|
var osVersionMinor int64
|
|
if len(osVersionParts) > 1 {
|
|
osVersionMinor, _ = strconv.ParseInt(osVersionParts[1], 10, 64)
|
|
}
|
|
|
|
m := map[string]interface{}{
|
|
"OS": osName,
|
|
"OSVersion": osVersion,
|
|
"OSVersionMajor": osVersionMajor,
|
|
"OSVersionMinor": osVersionMinor,
|
|
"Name": pkg.Name,
|
|
"Error": pkg.Error,
|
|
"ExitCode": pkg.ExitCode,
|
|
"IsInstalled": isSystemPackageInstalled(pkg),
|
|
}
|
|
|
|
for k, v := range getSystemPackageDetailsMap(pkg) {
|
|
m[k] = v
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func isSystemPackageInstalled(pkg collect.SystemPackage) bool {
|
|
if pkg.Error != "" {
|
|
return false
|
|
}
|
|
if pkg.ExitCode != "0" {
|
|
return false
|
|
}
|
|
if strings.Contains(pkg.Details, "not installed") {
|
|
return false
|
|
}
|
|
if strings.Contains(pkg.Details, "No matching Packages") {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func getSystemPackageDetailsMap(pkg collect.SystemPackage) map[string]string {
|
|
// TODO: handle multiline values
|
|
m := map[string]string{}
|
|
|
|
buffer := bytes.NewBuffer([]byte(pkg.Details))
|
|
scanner := bufio.NewScanner(buffer)
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
parts := strings.SplitN(line, ":", 2)
|
|
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
key := parts[0]
|
|
|
|
// key should start with an upper case
|
|
if !unicode.IsUpper([]rune(key)[0]) {
|
|
continue
|
|
}
|
|
|
|
// key shouldn't start with a space
|
|
if unicode.IsSpace([]rune(key)[0]) {
|
|
continue
|
|
}
|
|
|
|
// sanitize the key and value
|
|
key = strings.ReplaceAll(key, " ", "")
|
|
key = strings.ReplaceAll(key, "-", "")
|
|
value := strings.TrimSpace(parts[1])
|
|
|
|
if key == "" || value == "" {
|
|
continue
|
|
}
|
|
|
|
m[key] = value
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func compareSystemPackagesConditionalToActual(conditional string, templateMap map[string]interface{}) (res bool, err error) {
|
|
if conditional == "" {
|
|
return true, nil
|
|
}
|
|
|
|
when, err := util.RenderTemplate(conditional, templateMap)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "failed to render when template")
|
|
}
|
|
|
|
t, err := strconv.ParseBool(when)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "failed to parse templated when expression as a boolean")
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
func (a *AnalyzeHostSystemPackages) analyzeSingleNode(content collectedContent, currentTitle string) ([]*AnalyzeResult, error) {
|
|
hostAnalyzer := a.hostAnalyzer
|
|
var info collect.SystemPackagesInfo
|
|
if err := json.Unmarshal(content.Data, &info); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to unmarshal system packages info from %s", currentTitle)
|
|
}
|
|
|
|
allResults := []*AnalyzeResult{}
|
|
|
|
for _, pkg := range info.Packages {
|
|
templateMap := getSystemPackageTemplateMap(pkg, info.OS, info.OSVersion)
|
|
|
|
for _, outcome := range hostAnalyzer.Outcomes {
|
|
r := AnalyzeResult{}
|
|
when := ""
|
|
switch {
|
|
case outcome.Fail != nil:
|
|
r.IsFail = true
|
|
r.Message = outcome.Fail.Message
|
|
r.URI = outcome.Fail.URI
|
|
when = outcome.Fail.When
|
|
case outcome.Warn != nil:
|
|
r.IsWarn = true
|
|
r.Message = outcome.Warn.Message
|
|
r.URI = outcome.Warn.URI
|
|
when = outcome.Warn.When
|
|
case outcome.Pass != nil:
|
|
r.IsPass = true
|
|
r.Message = outcome.Pass.Message
|
|
r.URI = outcome.Pass.URI
|
|
when = outcome.Pass.When
|
|
default:
|
|
return nil, errors.New("invalid outcome")
|
|
}
|
|
|
|
match, err := compareSystemPackagesConditionalToActual(when, templateMap)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to compare %s", when)
|
|
}
|
|
|
|
if !match {
|
|
continue
|
|
}
|
|
|
|
r.Title = currentTitle
|
|
|
|
// template the message
|
|
renderedMsg, err := util.RenderTemplate(r.Message, templateMap)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to create new message template")
|
|
}
|
|
r.Message = renderedMsg
|
|
|
|
// add to results, break and check the next pod
|
|
allResults = append(allResults, &r)
|
|
break
|
|
}
|
|
}
|
|
|
|
return allResults, nil
|
|
}
|