mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
232 lines
5.6 KiB
Go
232 lines
5.6 KiB
Go
package analyzer
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"text/template"
|
|
|
|
"github.com/pkg/errors"
|
|
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)) ([]*AnalyzeResult, error) {
|
|
hostAnalyzer := a.hostAnalyzer
|
|
|
|
packagesFileName := "host-collectors/system/packages.json"
|
|
if a.hostAnalyzer.CollectorName != "" {
|
|
packagesFileName = fmt.Sprintf("host-collectors/system/%s-packages.json", a.hostAnalyzer.CollectorName)
|
|
}
|
|
|
|
contents, err := getCollectedFileContents(packagesFileName)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get collected file")
|
|
}
|
|
|
|
var info collect.SystemPackagesInfo
|
|
if err := json.Unmarshal(contents, &info); err != nil {
|
|
return nil, errors.Wrap(err, "failed to unmarshal system packages info")
|
|
}
|
|
|
|
allResults := []*AnalyzeResult{}
|
|
|
|
for _, pkg := range info.Packages {
|
|
templateMap := getSystemPackageTemplateMap(pkg, info.OS, info.OSVersion)
|
|
|
|
for _, outcome := range hostAnalyzer.Outcomes {
|
|
r := AnalyzeResult{}
|
|
when := ""
|
|
|
|
if outcome.Fail != nil {
|
|
r.IsFail = true
|
|
r.Message = outcome.Fail.Message
|
|
r.URI = outcome.Fail.URI
|
|
when = outcome.Fail.When
|
|
} else if outcome.Warn != nil {
|
|
r.IsWarn = true
|
|
r.Message = outcome.Warn.Message
|
|
r.URI = outcome.Warn.URI
|
|
when = outcome.Warn.When
|
|
} else if outcome.Pass != nil {
|
|
r.IsPass = true
|
|
r.Message = outcome.Pass.Message
|
|
r.URI = outcome.Pass.URI
|
|
when = outcome.Pass.When
|
|
} else {
|
|
println("error: found an empty outcome in a systemPackages analyzer") // don't stop
|
|
continue
|
|
}
|
|
|
|
match, err := compareSystemPackagesConditionalToActual(when, templateMap)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to compare %s", when)
|
|
}
|
|
|
|
if !match {
|
|
continue
|
|
}
|
|
|
|
tmpl := template.New("package")
|
|
|
|
// template the title
|
|
titleTmpl, err := tmpl.Parse(a.Title())
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to create new title template")
|
|
}
|
|
var t bytes.Buffer
|
|
err = titleTmpl.Execute(&t, templateMap)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to execute title template")
|
|
}
|
|
r.Title = t.String()
|
|
|
|
// template the message
|
|
msgTmpl, err := tmpl.Parse(r.Message)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to create new message template")
|
|
}
|
|
var m bytes.Buffer
|
|
err = msgTmpl.Execute(&m, templateMap)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to execute message template")
|
|
}
|
|
r.Message = m.String()
|
|
|
|
// add to results, break and check the next pod
|
|
allResults = append(allResults, &r)
|
|
break
|
|
}
|
|
}
|
|
|
|
return allResults, 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
|
|
}
|
|
|
|
tmpl := template.New("conditional")
|
|
|
|
conditionalTmpl, err := tmpl.Parse(conditional)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "failed to create new when template")
|
|
}
|
|
|
|
var when bytes.Buffer
|
|
err = conditionalTmpl.Execute(&when, templateMap)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "failed to execute when template")
|
|
}
|
|
|
|
t, err := strconv.ParseBool(when.String())
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "failed to parse templated when expression as a boolean")
|
|
}
|
|
|
|
return t, nil
|
|
}
|