mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae63ccba8d | ||
|
|
5c5e30a9a5 | ||
|
|
ac2a0484e2 | ||
|
|
bc91151883 | ||
|
|
de8feb167e | ||
|
|
0a6ef1c2c3 | ||
|
|
4a89067912 | ||
|
|
1e17c5c4c2 | ||
|
|
32cca1215a | ||
|
|
052d10fec7 | ||
|
|
7a0c6e5383 | ||
|
|
1dc03db254 | ||
|
|
fe414af556 | ||
|
|
f8dca39899 | ||
|
|
19aef8a02f | ||
|
|
4b6606e323 | ||
|
|
559e18d996 | ||
|
|
bf4d26acd2 | ||
|
|
f3b599c19a | ||
|
|
810b3cbf78 | ||
|
|
81789181ed | ||
|
|
256c68feca | ||
|
|
b01e6ef578 | ||
|
|
62afc87af8 | ||
|
|
17bff4b2bd | ||
|
|
5f2525b663 |
@@ -35,27 +35,29 @@ define LDFLAGS
|
||||
"
|
||||
endef
|
||||
|
||||
BUILDFLAGS = -tags "netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp" -installsuffix netgo
|
||||
|
||||
all: test
|
||||
|
||||
.PHONY: ffi
|
||||
ffi: fmt vet
|
||||
go build ${LDFLAGS} -o bin/troubleshoot.so -buildmode=c-shared ffi/main.go
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/troubleshoot.so -buildmode=c-shared ffi/main.go
|
||||
|
||||
# Run tests
|
||||
test: generate fmt vet
|
||||
go test ./pkg/... ./cmd/... -coverprofile cover.out
|
||||
go test ${BUILDFLAGS} ./pkg/... ./cmd/... -coverprofile cover.out
|
||||
|
||||
.PHONY: support-bundle
|
||||
support-bundle: generate fmt vet
|
||||
go build -tags netgo ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
|
||||
.PHONY: preflight
|
||||
preflight: generate fmt vet
|
||||
go build -tags netgo ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
|
||||
.PHONY: analyze
|
||||
analyze: generate fmt vet
|
||||
go build -tags netgo ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@@ -63,7 +65,7 @@ fmt:
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet ./pkg/... ./cmd/...
|
||||
go vet ${BUILDFLAGS} ./pkg/... ./cmd/...
|
||||
|
||||
.PHONY: generate
|
||||
generate: controller-gen client-gen
|
||||
|
||||
@@ -121,6 +121,20 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
// make sure we don't block any senders
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-progressCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
case <-finishedCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
|
||||
@@ -38,6 +38,7 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
|
||||
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
|
||||
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
|
||||
cmd.Flags().Bool("interactive", true, "enable/disable interactive mode")
|
||||
cmd.Flags().Bool("collect-without-permissions", false, "always generate a support bundle, even if it some require additional permissions")
|
||||
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
|
||||
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
|
||||
|
||||
@@ -200,7 +200,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
c.Printf("%s\r * Failed to analyze support bundle: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
|
||||
interactive := isatty.IsTerminal(os.Stdout.Fd())
|
||||
interactive := v.GetBool("interactive") && isatty.IsTerminal(os.Stdout.Fd())
|
||||
|
||||
if interactive {
|
||||
close(finishedCh) // this removes the spinner
|
||||
|
||||
+2
-19
@@ -4,18 +4,6 @@ release:
|
||||
owner: replicatedhq
|
||||
name: troubleshoot
|
||||
builds:
|
||||
- id: so
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- GO111MODULE=on
|
||||
main: ffi/main.go
|
||||
flags: -buildmode=c-shared
|
||||
binary: troubleshoot.so
|
||||
hooks: {}
|
||||
- id: preflight
|
||||
goos:
|
||||
- linux
|
||||
@@ -31,7 +19,7 @@ builds:
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
flags: -tags netgo -tags containers_image_ostree_stub -tags exclude_graphdriver_devicemapper -tags exclude_graphdriver_btrfs -tags containers_image_openpgp -installsuffix netgo
|
||||
binary: preflight
|
||||
hooks: {}
|
||||
- id: support-bundle
|
||||
@@ -49,15 +37,10 @@ builds:
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
flags: -tags netgo -tags containers_image_ostree_stub -tags exclude_graphdriver_devicemapper -tags exclude_graphdriver_btrfs -tags containers_image_openpgp -installsuffix netgo
|
||||
binary: support-bundle
|
||||
hooks: {}
|
||||
archives:
|
||||
- id: so
|
||||
builds:
|
||||
- so
|
||||
format: tar.gz
|
||||
name_template: 'troubleshoot.so_{{ .Os }}_{{ .Arch }}'
|
||||
- id: preflight
|
||||
builds:
|
||||
- preflight
|
||||
|
||||
@@ -3,53 +3,52 @@ module github.com/replicatedhq/troubleshoot
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.57.0 // indirect
|
||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
|
||||
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
|
||||
github.com/aws/aws-sdk-go v1.25.18 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/chzyer/logex v1.1.11-0.20160617073814-96a4d311aa9b // indirect
|
||||
github.com/containers/image/v5 v5.10.4
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible // indirect
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||
github.com/go-openapi/spec v0.19.4 // indirect
|
||||
github.com/go-redis/redis/v7 v7.2.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/godbus/dbus v4.1.0+incompatible
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/google/gofuzz v1.1.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/imdario/mergo v0.3.8 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lib/pq v1.3.0
|
||||
github.com/manifoldco/promptui v0.3.2
|
||||
github.com/manifoldco/promptui v0.8.0
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.9
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/nicksnyder/go-i18n v1.10.1 // indirect
|
||||
github.com/onsi/gomega v1.9.0 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/procfs v0.0.5 // indirect
|
||||
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851
|
||||
github.com/segmentio/ksuid v1.0.3
|
||||
github.com/shirou/gopsutil v3.21.1+incompatible
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tj/go-spin v1.1.0
|
||||
github.com/ulikunitz/xz v0.5.6 // indirect
|
||||
go.opencensus.io v0.22.0 // indirect
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff // indirect
|
||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
k8s.io/api v0.18.3
|
||||
k8s.io/apiextensions-apiserver v0.18.2
|
||||
k8s.io/apimachinery v0.18.3
|
||||
k8s.io/cli-runtime v0.18.0
|
||||
k8s.io/client-go v0.18.2
|
||||
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901
|
||||
golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
k8s.io/api v0.20.2
|
||||
k8s.io/apiextensions-apiserver v0.20.2
|
||||
k8s.io/apimachinery v0.20.2
|
||||
k8s.io/cli-runtime v0.20.2
|
||||
k8s.io/client-go v0.20.2
|
||||
sigs.k8s.io/controller-runtime v0.8.3
|
||||
)
|
||||
|
||||
+23
-1
@@ -60,9 +60,16 @@ func HostAnalyze(hostAnalyzer *troubleshootv1beta2.HostAnalyze, getFile getColle
|
||||
}
|
||||
|
||||
func NewAnalyzeResultError(analyzer HostAnalyzer, err error) []*AnalyzeResult {
|
||||
if analyzer != nil {
|
||||
return []*AnalyzeResult{{
|
||||
IsFail: true,
|
||||
Title: analyzer.Title(),
|
||||
Message: fmt.Sprintf("Analyzer Failed: %v", err),
|
||||
}}
|
||||
}
|
||||
return []*AnalyzeResult{{
|
||||
IsFail: true,
|
||||
Title: analyzer.Title(),
|
||||
Title: "nil analyzer",
|
||||
Message: fmt.Sprintf("Analyzer Failed: %v", err),
|
||||
}}
|
||||
}
|
||||
@@ -293,5 +300,20 @@ func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileCont
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
if analyzer.RegistryImages != nil {
|
||||
isExcluded, err := isExcluded(analyzer.RegistryImages.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
result, err := analyzeRegistry(analyzer.RegistryImages, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid analyzer")
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b
|
||||
return &AnalyzeHostFilesystemPerformance{analyzer.FilesystemPerformance}, true
|
||||
case analyzer.Certificate != nil:
|
||||
return &AnalyzeHostCertificate{analyzer.Certificate}, true
|
||||
case analyzer.HostServices != nil:
|
||||
return &AnalyzeHostServices{analyzer.HostServices}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (a *AnalyzeHostBlockDevices) Analyze(getCollectedFileContents func(string)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Fail.When, devices)
|
||||
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Fail.When, hostAnalyzer.MinimumAcceptableSize, hostAnalyzer.IncludeUnmountedPartitions, devices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func (a *AnalyzeHostBlockDevices) Analyze(getCollectedFileContents func(string)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Warn.When, devices)
|
||||
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Warn.When, hostAnalyzer.MinimumAcceptableSize, hostAnalyzer.IncludeUnmountedPartitions, devices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func (a *AnalyzeHostBlockDevices) Analyze(getCollectedFileContents func(string)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Pass.When, devices)
|
||||
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Pass.When, hostAnalyzer.MinimumAcceptableSize, hostAnalyzer.IncludeUnmountedPartitions, devices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func (a *AnalyzeHostBlockDevices) Analyze(getCollectedFileContents func(string)
|
||||
|
||||
// <regexp> <op> <count>
|
||||
// example: sdb > 0
|
||||
func compareHostBlockDevicesConditionalToActual(conditional string, devices []collect.BlockDeviceInfo) (res bool, err error) {
|
||||
func compareHostBlockDevicesConditionalToActual(conditional string, minimumAcceptableSize uint64, includeUnmountedPartitions bool, devices []collect.BlockDeviceInfo) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Expected exactly 3 parts, got %d", len(parts))
|
||||
@@ -123,7 +123,7 @@ func compareHostBlockDevicesConditionalToActual(conditional string, devices []co
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to compile regex %q", parts[0])
|
||||
}
|
||||
count := countEligibleBlockDevices(rx, devices)
|
||||
count := countEligibleBlockDevices(rx, minimumAcceptableSize, includeUnmountedPartitions, devices)
|
||||
|
||||
desiredInt, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
@@ -146,11 +146,11 @@ func compareHostBlockDevicesConditionalToActual(conditional string, devices []co
|
||||
return false, fmt.Errorf("Unexpected operator %q", parts[1])
|
||||
}
|
||||
|
||||
func countEligibleBlockDevices(rx *regexp.Regexp, devices []collect.BlockDeviceInfo) int {
|
||||
func countEligibleBlockDevices(rx *regexp.Regexp, minimumAcceptableSize uint64, includeUnmountedPartitions bool, devices []collect.BlockDeviceInfo) int {
|
||||
count := 0
|
||||
|
||||
for _, device := range devices {
|
||||
if isEligibleBlockDevice(rx, device, devices) {
|
||||
if isEligibleBlockDevice(rx, minimumAcceptableSize, includeUnmountedPartitions, device, devices) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
@@ -158,13 +158,25 @@ func countEligibleBlockDevices(rx *regexp.Regexp, devices []collect.BlockDeviceI
|
||||
return count
|
||||
}
|
||||
|
||||
func isEligibleBlockDevice(rx *regexp.Regexp, device collect.BlockDeviceInfo, devices []collect.BlockDeviceInfo) bool {
|
||||
func isEligibleBlockDevice(rx *regexp.Regexp, minimumAcceptableSize uint64, includeUnmountedPartitions bool, device collect.BlockDeviceInfo, devices []collect.BlockDeviceInfo) bool {
|
||||
if !rx.MatchString(device.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
if device.Type != "disk" {
|
||||
return false
|
||||
if includeUnmountedPartitions {
|
||||
if device.Type != "disk" && device.Type != "part" {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if device.Type != "disk" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if minimumAcceptableSize != 0 {
|
||||
if device.Size < minimumAcceptableSize {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if device.Mountpoint != "" {
|
||||
|
||||
@@ -149,6 +149,97 @@ func TestAnalyzeBlockDevices(t *testing.T) {
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".* > 1, pass with unmounted partition",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
},
|
||||
{
|
||||
Name: "sdb1",
|
||||
KernelName: "sdb1",
|
||||
ParentKernelName: "sdb",
|
||||
Type: "part",
|
||||
Major: 8,
|
||||
Minor: 1,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
IncludeUnmountedPartitions: true,
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ".* >= 1",
|
||||
Message: "Block device or partition available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsPass: true,
|
||||
Message: "Block device or partition available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".* = 2, pass with two unmounted partitions/devices of at least 10gb in size",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
Size: 1024 * 1024 * 1024 * 128,
|
||||
},
|
||||
{
|
||||
Name: "sdb1",
|
||||
KernelName: "sdb1",
|
||||
ParentKernelName: "sdb",
|
||||
Type: "part",
|
||||
Major: 8,
|
||||
Minor: 1,
|
||||
Size: 1024 * 1024 * 1024 * 128,
|
||||
},
|
||||
{
|
||||
Name: "sdc",
|
||||
KernelName: "sdc",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk2",
|
||||
Size: 1024 * 1024 * 1024 * 16,
|
||||
},
|
||||
{
|
||||
Name: "sdd",
|
||||
KernelName: "sdd",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk3",
|
||||
Size: 1024 * 1024 * 1024 * 8,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
IncludeUnmountedPartitions: true,
|
||||
MinimumAcceptableSize: 1024 * 1024 * 1024 * 10,
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ".* = 2",
|
||||
Message: "Two block devices or partitions of >10gb size available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsPass: true,
|
||||
Message: "Two block devices or partitions of >10gb size available",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
type AnalyzeHostServices struct {
|
||||
hostAnalyzer *troubleshootv1beta2.HostServicesAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostServices) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Host Services")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostServices) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostServices) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
contents, err := getCollectedFileContents(collect.HostServicesPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
var services []collect.ServiceInfo
|
||||
if err := json.Unmarshal(contents, &services); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal systemctl service info")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
result.Title = a.Title()
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostServicesConditionalToActual(outcome.Fail.When, services)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostServicesConditionalToActual(outcome.Warn.When, services)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostServicesConditionalToActual(outcome.Pass.When, services)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// <service> <op> <state>
|
||||
// example: ufw.service = active
|
||||
func compareHostServicesConditionalToActual(conditional string, services []collect.ServiceInfo) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("expected exactly 3 parts, got %d", len(parts))
|
||||
}
|
||||
|
||||
matchParams := strings.Split(parts[2], ",")
|
||||
activeMatch := matchParams[0]
|
||||
subMatch := ""
|
||||
loadMatch := ""
|
||||
if len(matchParams) > 1 {
|
||||
subMatch = matchParams[1]
|
||||
}
|
||||
if len(matchParams) > 2 {
|
||||
loadMatch = matchParams[2]
|
||||
}
|
||||
|
||||
switch parts[1] {
|
||||
case "=", "==":
|
||||
for _, service := range services {
|
||||
if isServiceMatch(service.Unit, parts[0]) {
|
||||
isMatch := true
|
||||
if activeMatch != "" && activeMatch != "*" {
|
||||
isMatch = isMatch && (activeMatch == service.Active)
|
||||
}
|
||||
if subMatch != "" && subMatch != "*" {
|
||||
isMatch = isMatch && (subMatch == service.Sub)
|
||||
}
|
||||
if loadMatch != "" && loadMatch != "*" {
|
||||
isMatch = isMatch && (loadMatch == service.Load)
|
||||
}
|
||||
|
||||
return isMatch, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
case "!=", "<>":
|
||||
for _, service := range services {
|
||||
if isServiceMatch(service.Unit, parts[0]) {
|
||||
isMatch := false
|
||||
if activeMatch != "" && activeMatch != "*" {
|
||||
isMatch = isMatch || (activeMatch != service.Active)
|
||||
}
|
||||
if subMatch != "" && subMatch != "*" {
|
||||
isMatch = isMatch || (subMatch != service.Sub)
|
||||
}
|
||||
if loadMatch != "" && loadMatch != "*" {
|
||||
isMatch = isMatch || (loadMatch != service.Load)
|
||||
}
|
||||
|
||||
return isMatch, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("unexpected operator %q", parts[1])
|
||||
}
|
||||
|
||||
func isServiceMatch(serviceName string, matchName string) bool {
|
||||
if serviceName == matchName {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(serviceName, matchName) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeHostServices(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
info []collect.ServiceInfo
|
||||
hostAnalyzer *troubleshootv1beta2.HostServicesAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "service 'a' is active",
|
||||
info: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "a.service",
|
||||
Active: "active",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostServicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "a.service == active",
|
||||
Message: "the service 'a' is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Host Services",
|
||||
IsFail: true,
|
||||
Message: "the service 'a' is active",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "connected, fail",
|
||||
info: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "a.service",
|
||||
Active: "active",
|
||||
},
|
||||
{
|
||||
Unit: "b.service",
|
||||
Active: "inactive",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostServicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "a.service != active",
|
||||
Message: "service 'a' is active",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "b.service != active",
|
||||
Message: "service 'b' is not active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Host Services",
|
||||
IsPass: true,
|
||||
Message: "service 'b' is not active",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostServices{test.hostAnalyzer}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_compareHostServicesConditionalToActual(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
conditional string
|
||||
services []collect.ServiceInfo
|
||||
wantRes bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "match second item",
|
||||
conditional: "abc.service = active",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "first",
|
||||
},
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "active",
|
||||
Sub: "running",
|
||||
},
|
||||
},
|
||||
wantRes: true,
|
||||
},
|
||||
{
|
||||
name: "item not in list",
|
||||
conditional: "abc = active",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "first",
|
||||
},
|
||||
},
|
||||
wantRes: false,
|
||||
},
|
||||
{
|
||||
name: "item does not match",
|
||||
conditional: "abc = active",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "inactive",
|
||||
Sub: "exited",
|
||||
},
|
||||
},
|
||||
wantRes: false,
|
||||
},
|
||||
{
|
||||
name: "other operator",
|
||||
conditional: "abc * active",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "inactive",
|
||||
Sub: "exited",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "item active matches but not sub",
|
||||
conditional: "abc = active,running",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "active",
|
||||
Sub: "exited",
|
||||
},
|
||||
},
|
||||
wantRes: false,
|
||||
},
|
||||
{
|
||||
name: "item active,sub,load matches",
|
||||
conditional: "abc = active,*,loaded",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "active",
|
||||
Sub: "exited",
|
||||
Load: "loaded",
|
||||
},
|
||||
},
|
||||
wantRes: true,
|
||||
},
|
||||
{
|
||||
name: "one item active,sub,load does not match with !=",
|
||||
conditional: "abc != active,running,loaded",
|
||||
services: []collect.ServiceInfo{
|
||||
{
|
||||
Unit: "abc.service",
|
||||
Active: "active",
|
||||
Sub: "exited",
|
||||
Load: "loaded",
|
||||
},
|
||||
},
|
||||
wantRes: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
gotRes, err := compareHostServicesConditionalToActual(tt.conditional, tt.services)
|
||||
if tt.wantErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
req.Equal(tt.wantRes, gotRes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func analyzePostgres(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollected
|
||||
return result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
@@ -62,7 +62,7 @@ func analyzeRedis(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFil
|
||||
return result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeRegistry(analyzer *troubleshootv1beta2.RegistryImagesAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collectorName := analyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "images"
|
||||
}
|
||||
|
||||
fullPath := path.Join("registry", fmt.Sprintf("%s.json", collectorName))
|
||||
|
||||
collected, err := getCollectedFileContents(fullPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
|
||||
}
|
||||
|
||||
registryInfo := collect.RegistryInfo{}
|
||||
if err := json.Unmarshal(collected, ®istryInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal database connection result")
|
||||
}
|
||||
|
||||
numMissingImages := 0
|
||||
numVerifiedImages := 0
|
||||
numErrors := 0
|
||||
for _, image := range registryInfo.Images {
|
||||
if image.Error != "" {
|
||||
numErrors++
|
||||
} else if !image.Exists {
|
||||
numMissingImages++
|
||||
} else {
|
||||
numVerifiedImages++
|
||||
}
|
||||
}
|
||||
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = collectorName
|
||||
}
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: title,
|
||||
IconKey: "kubernetes_registry_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/registry-analyze.svg",
|
||||
}
|
||||
|
||||
for _, outcome := range analyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareRegistryConditionalToActual(outcome.Fail.When, numVerifiedImages, numMissingImages, numErrors)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare registry conditional")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareRegistryConditionalToActual(outcome.Warn.When, numVerifiedImages, numMissingImages, numErrors)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare registry conditional")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareRegistryConditionalToActual(outcome.Pass.When, numVerifiedImages, numMissingImages, numErrors)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare registry conditional")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func compareRegistryConditionalToActual(conditional string, numVerifiedImages int, numMissingImages int, numErrors int) (bool, error) {
|
||||
parts := strings.Split(strings.TrimSpace(conditional), " ")
|
||||
|
||||
if len(parts) != 3 {
|
||||
return false, errors.Errorf("unable to parse conditional: %s", conditional)
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "verified":
|
||||
result, err := doCompareRegistryImageCount(parts[1], parts[2], numVerifiedImages)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to compare number of verified images")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
case "missing":
|
||||
result, err := doCompareRegistryImageCount(parts[1], parts[2], numMissingImages)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to compare number of missing images")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
case "errors":
|
||||
result, err := doCompareRegistryImageCount(parts[1], parts[2], numErrors)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to compare number of errors")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return false, errors.Errorf("unknown term %q in conditional", parts[0])
|
||||
}
|
||||
|
||||
func doCompareRegistryImageCount(operator string, desired string, actual int) (bool, error) {
|
||||
desiredInt, err := strconv.Atoi(desired)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse")
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case "<":
|
||||
return actual < desiredInt, nil
|
||||
case "<=":
|
||||
return actual <= desiredInt, nil
|
||||
case ">":
|
||||
return actual > desiredInt, nil
|
||||
case ">=":
|
||||
return actual >= desiredInt, nil
|
||||
case "=", "==", "===":
|
||||
return actual == desiredInt, nil
|
||||
}
|
||||
|
||||
return false, errors.Errorf("unknown operator: %s", operator)
|
||||
}
|
||||
@@ -116,6 +116,12 @@ type CephStatusAnalyze struct {
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
}
|
||||
|
||||
type RegistryImagesAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
CollectorName string `json:"collectorName" yaml:"collectorName"`
|
||||
}
|
||||
|
||||
type AnalyzeMeta struct {
|
||||
CheckName string `json:"checkName,omitempty" yaml:"checkName,omitempty"`
|
||||
Exclude multitype.BoolOrString `json:"exclude,omitempty" yaml:"exclude,omitempty"`
|
||||
@@ -138,4 +144,5 @@ type Analyze struct {
|
||||
Mysql *DatabaseAnalyze `json:"mysql,omitempty" yaml:"mysql,omitempty"`
|
||||
Redis *DatabaseAnalyze `json:"redis,omitempty" yaml:"redis,omitempty"`
|
||||
CephStatus *CephStatusAnalyze `json:"cephStatus,omitempty" yaml:"cephStatus,omitempty"`
|
||||
RegistryImages *RegistryImagesAnalyze `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
|
||||
}
|
||||
|
||||
@@ -139,6 +139,13 @@ type Ceph struct {
|
||||
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
type RegistryImages struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Images []string `json:"images" yaml:"images"`
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
ImagePullSecrets *ImagePullSecrets `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"`
|
||||
}
|
||||
|
||||
type Collect struct {
|
||||
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
|
||||
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
|
||||
@@ -154,6 +161,7 @@ type Collect struct {
|
||||
Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"`
|
||||
Collectd *Collectd `json:"collectd,omitempty" yaml:"collectd,omitempty"`
|
||||
Ceph *Ceph `json:"ceph,omitempty" yaml:"ceph,omitempty"`
|
||||
RegistryImages *RegistryImages `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSubjectAccessReviewSpec {
|
||||
@@ -313,6 +321,21 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
})
|
||||
} else if c.HTTP != nil {
|
||||
// NOOP
|
||||
} else if c.RegistryImages != nil &&
|
||||
c.RegistryImages.ImagePullSecrets != nil &&
|
||||
c.RegistryImages.ImagePullSecrets.Data == nil {
|
||||
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
||||
Namespace: pickNamespaceOrDefault(c.RegistryImages.Namespace, overrideNS),
|
||||
Verb: "get",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "secrets",
|
||||
Subresource: "",
|
||||
Name: c.RegistryImages.ImagePullSecrets.Name,
|
||||
},
|
||||
NonResourceAttributes: nil,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -361,6 +384,10 @@ func (c *Collect) GetName() string {
|
||||
collector = "ceph"
|
||||
name = c.Ceph.CollectorName
|
||||
}
|
||||
if c.RegistryImages != nil {
|
||||
collector = "registry-images"
|
||||
name = c.RegistryImages.CollectorName
|
||||
}
|
||||
|
||||
if collector == "" {
|
||||
return "<none>"
|
||||
|
||||
@@ -46,8 +46,10 @@ type TimeAnalyze struct {
|
||||
}
|
||||
|
||||
type BlockDevicesAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
MinimumAcceptableSize uint64 `json:"minimumAcceptableSize" yaml:"minimumAcceptableSize"`
|
||||
IncludeUnmountedPartitions bool `json:"includeUnmountedPartitions" yaml:"includeUnmountedPartitions"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type TCPConnectAnalyze struct {
|
||||
@@ -73,6 +75,11 @@ type CertificateAnalyze struct {
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HostServicesAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HostAnalyze struct {
|
||||
CPU *CPUAnalyze `json:"cpu,omitempty" yaml:"cpu,omitempty"`
|
||||
//
|
||||
@@ -98,4 +105,6 @@ type HostAnalyze struct {
|
||||
FilesystemPerformance *FilesystemPerformanceAnalyze `json:"filesystemPerformance,omitempty" yaml:"filesystemPerformance,omitempty"`
|
||||
|
||||
Certificate *CertificateAnalyze `json:"certificate,omitempty" yaml:"certificate,omitempty"`
|
||||
|
||||
HostServices *HostServicesAnalyze `json:"hostServices,omitempty" yaml:"hostServices,omitempty"`
|
||||
}
|
||||
|
||||
@@ -88,6 +88,10 @@ type Certificate struct {
|
||||
KeyPath string `json:"keyPath" yaml:"keyPath"`
|
||||
}
|
||||
|
||||
type HostServices struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
type HostCollect struct {
|
||||
CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"`
|
||||
Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"`
|
||||
@@ -103,6 +107,7 @@ type HostCollect struct {
|
||||
TCPConnect *TCPConnect `json:"tcpConnect,omitempty" yaml:"tcpConnect,omitempty"`
|
||||
FilesystemPerformance *FilesystemPerformance `json:"filesystemPerformance,omitempty" yaml:"filesystemPerformance,omitempty"`
|
||||
Certificate *Certificate `json:"certificate,omitempty" yaml:"certificate,omitempty"`
|
||||
HostServices *HostServices `json:"hostServices,omitempty" yaml:"hostServices,omitempty"`
|
||||
}
|
||||
|
||||
func (c *HostCollect) GetName() string {
|
||||
|
||||
@@ -132,6 +132,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
|
||||
*out = new(CephStatusAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.RegistryImages != nil {
|
||||
in, out := &in.RegistryImages, &out.RegistryImages
|
||||
*out = new(RegistryImagesAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
|
||||
@@ -575,6 +580,11 @@ func (in *Collect) DeepCopyInto(out *Collect) {
|
||||
*out = new(Ceph)
|
||||
**out = **in
|
||||
}
|
||||
if in.RegistryImages != nil {
|
||||
in, out := &in.RegistryImages, &out.RegistryImages
|
||||
*out = new(RegistryImages)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
|
||||
@@ -1278,6 +1288,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
|
||||
*out = new(CertificateAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HostServices != nil {
|
||||
in, out := &in.HostServices, &out.HostServices
|
||||
*out = new(HostServicesAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze.
|
||||
@@ -1379,6 +1394,11 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) {
|
||||
*out = new(Certificate)
|
||||
**out = **in
|
||||
}
|
||||
if in.HostServices != nil {
|
||||
in, out := &in.HostServices, &out.HostServices
|
||||
*out = new(HostServices)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect.
|
||||
@@ -1549,6 +1569,49 @@ func (in *HostPreflightStatus) DeepCopy() *HostPreflightStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostServices) DeepCopyInto(out *HostServices) {
|
||||
*out = *in
|
||||
out.HostCollectorMeta = in.HostCollectorMeta
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostServices.
|
||||
func (in *HostServices) DeepCopy() *HostServices {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostServices)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostServicesAnalyze) DeepCopyInto(out *HostServicesAnalyze) {
|
||||
*out = *in
|
||||
out.AnalyzeMeta = in.AnalyzeMeta
|
||||
if in.Outcomes != nil {
|
||||
in, out := &in.Outcomes, &out.Outcomes
|
||||
*out = make([]*Outcome, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(Outcome)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostServicesAnalyze.
|
||||
func (in *HostServicesAnalyze) DeepCopy() *HostServicesAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostServicesAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostTime) DeepCopyInto(out *HostTime) {
|
||||
*out = *in
|
||||
@@ -2181,6 +2244,59 @@ func (in *Regex) DeepCopy() *Regex {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RegistryImages) DeepCopyInto(out *RegistryImages) {
|
||||
*out = *in
|
||||
out.CollectorMeta = in.CollectorMeta
|
||||
if in.Images != nil {
|
||||
in, out := &in.Images, &out.Images
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ImagePullSecrets != nil {
|
||||
in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
|
||||
*out = new(ImagePullSecrets)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegistryImages.
|
||||
func (in *RegistryImages) DeepCopy() *RegistryImages {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RegistryImages)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RegistryImagesAnalyze) DeepCopyInto(out *RegistryImagesAnalyze) {
|
||||
*out = *in
|
||||
out.AnalyzeMeta = in.AnalyzeMeta
|
||||
if in.Outcomes != nil {
|
||||
in, out := &in.Outcomes, &out.Outcomes
|
||||
*out = make([]*Outcome, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(Outcome)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegistryImagesAnalyze.
|
||||
func (in *RegistryImagesAnalyze) DeepCopy() *RegistryImagesAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RegistryImagesAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Removals) DeepCopyInto(out *Removals) {
|
||||
*out = *in
|
||||
|
||||
@@ -199,6 +199,8 @@ func (c *Collector) RunCollectorSync(globalRedactors []*troubleshootv1beta2.Reda
|
||||
result, err = Collectd(c, c.Collect.Collectd)
|
||||
} else if c.Collect.Ceph != nil {
|
||||
result, err = Ceph(c, c.Collect.Ceph)
|
||||
} else if c.Collect.RegistryImages != nil {
|
||||
result, err = Registry(c, c.Collect.RegistryImages)
|
||||
} else {
|
||||
err = errors.New("no spec found to run")
|
||||
return
|
||||
|
||||
@@ -38,6 +38,8 @@ func GetHostCollector(collector *troubleshootv1beta2.HostCollect) (HostCollector
|
||||
return &CollectHostFilesystemPerformance{collector.FilesystemPerformance}, true
|
||||
case collector.Certificate != nil:
|
||||
return &CollectHostCertificate{collector.Certificate}, true
|
||||
case collector.HostServices != nil:
|
||||
return &CollectHostServices{collector.HostServices}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type ServiceInfo struct {
|
||||
Unit string `json:"Unit"`
|
||||
Load string `json:"Load"`
|
||||
Active string `json:"Active"`
|
||||
Sub string `json:"Sub"`
|
||||
}
|
||||
|
||||
const systemctlFormat = `%s %s %s %s` // this leaves off the description
|
||||
const HostServicesPath = `system/systemctl_services.json`
|
||||
|
||||
type CollectHostServices struct {
|
||||
hostCollector *troubleshootv1beta2.HostServices
|
||||
}
|
||||
|
||||
func (c *CollectHostServices) Title() string {
|
||||
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Block Devices")
|
||||
}
|
||||
|
||||
func (c *CollectHostServices) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.hostCollector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHostServices) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
||||
var devices []ServiceInfo
|
||||
|
||||
cmd := exec.Command("systemctl", "list-units", "--type=service", "--no-legend", "--all")
|
||||
stdout, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to execute systemctl")
|
||||
}
|
||||
buf := bytes.NewBuffer(stdout)
|
||||
scanner := bufio.NewScanner(buf)
|
||||
|
||||
for scanner.Scan() {
|
||||
bdi := ServiceInfo{}
|
||||
fmt.Sscanf(
|
||||
scanner.Text(),
|
||||
systemctlFormat,
|
||||
&bdi.Unit,
|
||||
&bdi.Load,
|
||||
&bdi.Active,
|
||||
&bdi.Sub,
|
||||
)
|
||||
|
||||
devices = append(devices, bdi)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(devices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal systemctl service info")
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
HostServicesPath: b,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
imagedocker "github.com/containers/image/v5/docker"
|
||||
dockerref "github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
registryv2 "github.com/docker/distribution/registry/api/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type RegistryImage struct {
|
||||
Exists bool `json:"exists"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type RegistryInfo struct {
|
||||
Images map[string]RegistryImage `json:"images"`
|
||||
}
|
||||
|
||||
type registryAuthConfig struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func Registry(c *Collector, registryCollector *troubleshootv1beta2.RegistryImages) (map[string][]byte, error) {
|
||||
registryInfo := RegistryInfo{
|
||||
Images: map[string]RegistryImage{},
|
||||
}
|
||||
|
||||
for _, image := range registryCollector.Images {
|
||||
exists, err := imageExists(c, registryCollector, image)
|
||||
if err != nil {
|
||||
registryInfo.Images[image] = RegistryImage{
|
||||
Error: err.Error(),
|
||||
}
|
||||
} else {
|
||||
registryInfo.Images[image] = RegistryImage{
|
||||
Exists: exists,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(registryInfo, "", " ")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal database connection")
|
||||
}
|
||||
|
||||
collectorName := registryCollector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "images"
|
||||
}
|
||||
|
||||
registryOutput := map[string][]byte{
|
||||
fmt.Sprintf("registry/%s.json", collectorName): b,
|
||||
}
|
||||
|
||||
return registryOutput, nil
|
||||
}
|
||||
|
||||
func imageExists(c *Collector, registryCollector *troubleshootv1beta2.RegistryImages, image string) (bool, error) {
|
||||
imageRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", image))
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse image name %s", image)
|
||||
}
|
||||
|
||||
authConfig, err := getImageAuthConfig(c, registryCollector, imageRef)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to get auth config")
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for i := 0; i < 3; i++ {
|
||||
sysCtx := types.SystemContext{
|
||||
DockerDisableV1Ping: true,
|
||||
DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
|
||||
}
|
||||
if authConfig != nil {
|
||||
sysCtx.DockerAuthConfig = &types.DockerAuthConfig{
|
||||
Username: authConfig.username,
|
||||
Password: authConfig.password,
|
||||
}
|
||||
}
|
||||
|
||||
remoteImage, err := imageRef.NewImage(context.Background(), &sysCtx)
|
||||
if err == nil {
|
||||
remoteImage.Close()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "no image found in manifest list for architecture") {
|
||||
// manifest was downloaded, but no matching architecture found in manifest
|
||||
// should this count as image does not exist?
|
||||
// this binary's architecture is not necessarily what will run in the cluster
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if isNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "EOF") {
|
||||
lastErr = err
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
return false, errors.Wrap(err, "failed to get image manifest")
|
||||
}
|
||||
|
||||
return false, errors.Wrap(lastErr, "failed to retry")
|
||||
}
|
||||
|
||||
func getImageAuthConfig(c *Collector, registryCollector *troubleshootv1beta2.RegistryImages, imageRef types.ImageReference) (*registryAuthConfig, error) {
|
||||
if registryCollector.ImagePullSecrets == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if registryCollector.ImagePullSecrets.Data != nil {
|
||||
config, err := getImageAuthConfigFromData(c, imageRef, registryCollector.ImagePullSecrets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get auth from data")
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
if registryCollector.ImagePullSecrets.Name != "" {
|
||||
namespace := registryCollector.Namespace
|
||||
if namespace == "" {
|
||||
namespace = c.Namespace
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
config, err := getImageAuthConfigFromSecret(c, imageRef, registryCollector.ImagePullSecrets, namespace)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get auth from secret")
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("image pull secret spec is not valid")
|
||||
}
|
||||
|
||||
func getImageAuthConfigFromData(c *Collector, imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets) (*registryAuthConfig, error) {
|
||||
if pullSecrets.SecretType != "kubernetes.io/dockerconfigjson" {
|
||||
return nil, errors.Errorf("secret type is not supported: %s", pullSecrets.SecretType)
|
||||
}
|
||||
|
||||
configJsonBase64 := pullSecrets.Data[".dockerconfigjson"]
|
||||
registry := dockerref.Domain(imageRef.DockerReference())
|
||||
|
||||
configJson, err := base64.StdEncoding.DecodeString(configJsonBase64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode docker config string")
|
||||
}
|
||||
|
||||
dockerCfgJSON := struct {
|
||||
Auths map[string]struct {
|
||||
Auth []byte `json:"auth"`
|
||||
} `json:"auths"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal([]byte(configJson), &dockerCfgJSON)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal config json")
|
||||
}
|
||||
|
||||
auth, ok := dockerCfgJSON.Auths[registry]
|
||||
if !ok {
|
||||
// Suport a mix of public and private images
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(string(auth.Auth), ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.Errorf("expected 2 parts in the string, but found %d", len(parts))
|
||||
}
|
||||
|
||||
authConfig := registryAuthConfig{
|
||||
username: parts[0],
|
||||
password: parts[1],
|
||||
}
|
||||
|
||||
return &authConfig, nil
|
||||
}
|
||||
|
||||
func getImageAuthConfigFromSecret(c *Collector, imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets, namespace string) (*registryAuthConfig, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create client from config")
|
||||
}
|
||||
|
||||
secret, err := client.CoreV1().Secrets(namespace).Get(ctx, pullSecrets.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get secret")
|
||||
}
|
||||
|
||||
foundSecrets := &v1beta2.ImagePullSecrets{
|
||||
Name: secret.Name,
|
||||
SecretType: string(secret.Type),
|
||||
Data: map[string]string{
|
||||
".dockerconfigjson": base64.StdEncoding.EncodeToString(secret.Data[".dockerconfigjson"]),
|
||||
},
|
||||
}
|
||||
|
||||
config, err := getImageAuthConfigFromData(c, imageRef, foundSecrets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get auth from secret data")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func isNotFound(err error) bool {
|
||||
switch err := err.(type) {
|
||||
case errcode.Errors:
|
||||
for _, e := range err {
|
||||
if isNotFound(e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case errcode.Error:
|
||||
return err.Message == registryv2.ErrorCodeManifestUnknown.Message()
|
||||
}
|
||||
|
||||
// this type will cause panic when compared to error type
|
||||
if _, ok := err.(imagedocker.ErrUnauthorizedForCredentials); ok {
|
||||
return false
|
||||
}
|
||||
|
||||
cause := errors.Cause(err)
|
||||
if cause, ok := cause.(error); ok {
|
||||
if cause == err {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return isNotFound(cause)
|
||||
}
|
||||
@@ -18,6 +18,13 @@ type CollectOpts struct {
|
||||
ProgressChan chan interface{}
|
||||
}
|
||||
|
||||
type CollectProgress struct {
|
||||
CurrentName string
|
||||
CurrentStatus string
|
||||
CompletedCount int
|
||||
TotalCount int
|
||||
}
|
||||
|
||||
type CollectResult interface {
|
||||
Analyze() []*analyze.AnalyzeResult
|
||||
IsRBACAllowed() bool
|
||||
@@ -134,22 +141,48 @@ func Collect(opts CollectOpts, p *troubleshootv1beta2.Preflight) (CollectResult,
|
||||
}
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for _, collector := range collectors {
|
||||
for i, collector := range collectors {
|
||||
if len(collector.RBACErrors) > 0 {
|
||||
// don't skip clusterResources collector due to RBAC issues
|
||||
if collector.Collect.ClusterResources == nil {
|
||||
collectResult.isRBACAllowed = false // not failing, but going to report this
|
||||
opts.ProgressChan <- fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.GetDisplayName())
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentStatus: "skipped",
|
||||
CompletedCount: i + 1,
|
||||
TotalCount: len(collectors),
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentStatus: "running",
|
||||
CompletedCount: i,
|
||||
TotalCount: len(collectors),
|
||||
}
|
||||
|
||||
result, err := collector.RunCollectorSync(nil)
|
||||
if err != nil {
|
||||
opts.ProgressChan <- errors.Errorf("failed to run collector %s: %v\n", collector.GetDisplayName(), err)
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentStatus: "failed",
|
||||
CompletedCount: i + 1,
|
||||
TotalCount: len(collectors),
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentStatus: "completed",
|
||||
CompletedCount: i + 1,
|
||||
TotalCount: len(collectors),
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
for k, v := range result {
|
||||
allCollectedData[k] = v
|
||||
|
||||
Reference in New Issue
Block a user