Skip to content

Commit 1dc03db

Browse files
authored
Merge pull request #343 from replicatedhq/divolgin/images
Docker registry collector/analyzer
2 parents f8dca39 + fe414af commit 1dc03db

File tree

11 files changed

+1005
-218
lines changed

11 files changed

+1005
-218
lines changed

go.mod

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/replicatedhq/troubleshoot
33
go 1.12
44

55
require (
6+
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
67
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
78
github.com/aws/aws-sdk-go v1.25.18 // indirect
89
github.com/blang/semver v3.5.1+incompatible
@@ -11,45 +12,40 @@ require (
1112
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
1213
github.com/emicklei/go-restful v2.9.6+incompatible // indirect
1314
github.com/fatih/color v1.7.0
15+
github.com/go-ole/go-ole v1.2.5 // indirect
1416
github.com/go-openapi/spec v0.19.4 // indirect
1517
github.com/go-redis/redis/v7 v7.2.0
1618
github.com/go-sql-driver/mysql v1.5.0
1719
github.com/gobwas/glob v0.2.3
1820
github.com/godbus/dbus v4.1.0+incompatible
19-
github.com/google/go-cmp v0.3.1 // indirect
21+
github.com/google/go-containerregistry v0.4.1
2022
github.com/google/gofuzz v1.1.0
2123
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
2224
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e
2325
github.com/hashicorp/go-multierror v1.0.0
24-
github.com/imdario/mergo v0.3.8 // indirect
2526
github.com/lib/pq v1.3.0
2627
github.com/manifoldco/promptui v0.3.2
2728
github.com/mattn/go-colorable v0.1.4 // indirect
2829
github.com/mattn/go-isatty v0.0.9
2930
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
3031
github.com/nicksnyder/go-i18n v1.10.1 // indirect
31-
github.com/onsi/gomega v1.9.0 // indirect
3232
github.com/pkg/errors v0.9.1
33-
github.com/prometheus/procfs v0.0.5 // indirect
3433
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851
3534
github.com/segmentio/ksuid v1.0.3
3635
github.com/shirou/gopsutil v3.21.1+incompatible
37-
github.com/spf13/cobra v0.0.5
36+
github.com/spf13/cobra v1.1.1
3837
github.com/spf13/pflag v1.0.5
39-
github.com/spf13/viper v1.4.0
40-
github.com/stretchr/testify v1.5.1
38+
github.com/spf13/viper v1.7.0
39+
github.com/stretchr/testify v1.6.1
4140
github.com/tj/go-spin v1.1.0
4241
github.com/ulikunitz/xz v0.5.6 // indirect
43-
go.opencensus.io v0.22.0 // indirect
44-
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
4542
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
46-
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff // indirect
4743
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect
48-
gopkg.in/yaml.v2 v2.2.8
49-
k8s.io/api v0.18.3
50-
k8s.io/apiextensions-apiserver v0.18.2
51-
k8s.io/apimachinery v0.18.3
52-
k8s.io/cli-runtime v0.18.0
53-
k8s.io/client-go v0.18.2
54-
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901
44+
gopkg.in/yaml.v2 v2.3.0
45+
k8s.io/api v0.20.2
46+
k8s.io/apiextensions-apiserver v0.20.2
47+
k8s.io/apimachinery v0.20.2
48+
k8s.io/cli-runtime v0.20.2
49+
k8s.io/client-go v0.20.2
50+
sigs.k8s.io/controller-runtime v0.8.3
5551
)

go.sum

Lines changed: 493 additions & 199 deletions
Large diffs are not rendered by default.

pkg/analyze/analyzer.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,5 +300,20 @@ func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileCont
300300
return []*AnalyzeResult{result}, nil
301301
}
302302

303+
if analyzer.RegistryImages != nil {
304+
isExcluded, err := isExcluded(analyzer.RegistryImages.Exclude)
305+
if err != nil {
306+
return nil, err
307+
}
308+
if isExcluded {
309+
return nil, nil
310+
}
311+
result, err := analyzeRegistry(analyzer.RegistryImages, getFile)
312+
if err != nil {
313+
return nil, err
314+
}
315+
return []*AnalyzeResult{result}, nil
316+
}
317+
303318
return nil, errors.New("invalid analyzer")
304319
}

pkg/analyze/postgres.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func analyzePostgres(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollected
6868
return result, nil
6969
}
7070
} else if outcome.Warn != nil {
71-
if outcome.Pass.When == "" {
71+
if outcome.Warn.When == "" {
7272
result.IsWarn = true
7373
result.Message = outcome.Warn.Message
7474
result.URI = outcome.Warn.URI

pkg/analyze/redis.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func analyzeRedis(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFil
6262
return result, nil
6363
}
6464
} else if outcome.Warn != nil {
65-
if outcome.Pass.When == "" {
65+
if outcome.Warn.When == "" {
6666
result.IsWarn = true
6767
result.Message = outcome.Warn.Message
6868
result.URI = outcome.Warn.URI

pkg/analyze/registry.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package analyzer
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"path"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/pkg/errors"
11+
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
12+
"github.com/replicatedhq/troubleshoot/pkg/collect"
13+
)
14+
15+
func analyzeRegistry(analyzer *troubleshootv1beta2.RegistryImagesAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
16+
collectorName := analyzer.CollectorName
17+
if collectorName == "" {
18+
collectorName = "images"
19+
}
20+
21+
fullPath := path.Join("registry", fmt.Sprintf("%s.json", collectorName))
22+
23+
collected, err := getCollectedFileContents(fullPath)
24+
if err != nil {
25+
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
26+
}
27+
28+
registryInfo := collect.RegistryInfo{}
29+
if err := json.Unmarshal(collected, &registryInfo); err != nil {
30+
return nil, errors.Wrap(err, "failed to unmarshal database connection result")
31+
}
32+
33+
numMissingImages := 0
34+
numVerifiedImages := 0
35+
numErrors := 0
36+
for _, image := range registryInfo.Images {
37+
if image.Error != "" {
38+
numErrors++
39+
} else if !image.Exists {
40+
numMissingImages++
41+
} else {
42+
numVerifiedImages++
43+
}
44+
}
45+
46+
title := analyzer.CheckName
47+
if title == "" {
48+
title = collectorName
49+
}
50+
51+
result := &AnalyzeResult{
52+
Title: title,
53+
IconKey: "kubernetes_registry_analyze",
54+
IconURI: "https://troubleshoot.sh/images/analyzer-icons/registry-analyze.svg",
55+
}
56+
57+
for _, outcome := range analyzer.Outcomes {
58+
if outcome.Fail != nil {
59+
if outcome.Fail.When == "" {
60+
result.IsFail = true
61+
result.Message = outcome.Fail.Message
62+
result.URI = outcome.Fail.URI
63+
64+
return result, nil
65+
}
66+
67+
isMatch, err := compareRegistryConditionalToActual(outcome.Fail.When, numVerifiedImages, numMissingImages, numErrors)
68+
if err != nil {
69+
return result, errors.Wrap(err, "failed to compare registry conditional")
70+
}
71+
72+
if isMatch {
73+
result.IsFail = true
74+
result.Message = outcome.Fail.Message
75+
result.URI = outcome.Fail.URI
76+
77+
return result, nil
78+
}
79+
} else if outcome.Warn != nil {
80+
if outcome.Warn.When == "" {
81+
result.IsWarn = true
82+
result.Message = outcome.Warn.Message
83+
result.URI = outcome.Warn.URI
84+
85+
return result, nil
86+
}
87+
88+
isMatch, err := compareRegistryConditionalToActual(outcome.Warn.When, numVerifiedImages, numMissingImages, numErrors)
89+
if err != nil {
90+
return result, errors.Wrap(err, "failed to compare registry conditional")
91+
}
92+
93+
if isMatch {
94+
result.IsWarn = true
95+
result.Message = outcome.Warn.Message
96+
result.URI = outcome.Warn.URI
97+
98+
return result, nil
99+
}
100+
} else if outcome.Pass != nil {
101+
if outcome.Pass.When == "" {
102+
result.IsPass = true
103+
result.Message = outcome.Pass.Message
104+
result.URI = outcome.Pass.URI
105+
106+
return result, nil
107+
}
108+
109+
isMatch, err := compareRegistryConditionalToActual(outcome.Pass.When, numVerifiedImages, numMissingImages, numErrors)
110+
if err != nil {
111+
return result, errors.Wrap(err, "failed to compare registry conditional")
112+
}
113+
114+
if isMatch {
115+
result.IsPass = true
116+
result.Message = outcome.Pass.Message
117+
result.URI = outcome.Pass.URI
118+
119+
return result, nil
120+
}
121+
}
122+
}
123+
124+
return result, nil
125+
}
126+
127+
func compareRegistryConditionalToActual(conditional string, numVerifiedImages int, numMissingImages int, numErrors int) (bool, error) {
128+
parts := strings.Split(strings.TrimSpace(conditional), " ")
129+
130+
if len(parts) != 3 {
131+
return false, errors.Errorf("unable to parse conditional: %s", conditional)
132+
}
133+
134+
switch parts[0] {
135+
case "verified":
136+
result, err := doCompareRegistryImageCount(parts[1], parts[2], numVerifiedImages)
137+
if err != nil {
138+
return false, errors.Wrap(err, "failed to compare number of verified images")
139+
}
140+
141+
return result, nil
142+
143+
case "missing":
144+
result, err := doCompareRegistryImageCount(parts[1], parts[2], numMissingImages)
145+
if err != nil {
146+
return false, errors.Wrap(err, "failed to compare number of missing images")
147+
}
148+
149+
return result, nil
150+
151+
case "errors":
152+
result, err := doCompareRegistryImageCount(parts[1], parts[2], numErrors)
153+
if err != nil {
154+
return false, errors.Wrap(err, "failed to compare number of errors")
155+
}
156+
157+
return result, nil
158+
}
159+
160+
return false, errors.Errorf("unknown term %q in conditional", parts[0])
161+
}
162+
163+
func doCompareRegistryImageCount(operator string, desired string, actual int) (bool, error) {
164+
desiredInt, err := strconv.Atoi(desired)
165+
if err != nil {
166+
return false, errors.Wrap(err, "failed to parse")
167+
}
168+
169+
switch operator {
170+
case "<":
171+
return actual < desiredInt, nil
172+
case "<=":
173+
return actual <= desiredInt, nil
174+
case ">":
175+
return actual > desiredInt, nil
176+
case ">=":
177+
return actual >= desiredInt, nil
178+
case "=", "==", "===":
179+
return actual == desiredInt, nil
180+
}
181+
182+
return false, errors.Errorf("unknown operator: %s", operator)
183+
}

pkg/apis/troubleshoot/v1beta2/analyzer_shared.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ type CephStatusAnalyze struct {
116116
Namespace string `json:"namespace" yaml:"namespace"`
117117
}
118118

119+
type RegistryImagesAnalyze struct {
120+
AnalyzeMeta `json:",inline" yaml:",inline"`
121+
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
122+
CollectorName string `json:"collectorName" yaml:"collectorName"`
123+
}
124+
119125
type AnalyzeMeta struct {
120126
CheckName string `json:"checkName,omitempty" yaml:"checkName,omitempty"`
121127
Exclude multitype.BoolOrString `json:"exclude,omitempty" yaml:"exclude,omitempty"`
@@ -138,4 +144,5 @@ type Analyze struct {
138144
Mysql *DatabaseAnalyze `json:"mysql,omitempty" yaml:"mysql,omitempty"`
139145
Redis *DatabaseAnalyze `json:"redis,omitempty" yaml:"redis,omitempty"`
140146
CephStatus *CephStatusAnalyze `json:"cephStatus,omitempty" yaml:"cephStatus,omitempty"`
147+
RegistryImages *RegistryImagesAnalyze `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
141148
}

pkg/apis/troubleshoot/v1beta2/collector_shared.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ type Ceph struct {
139139
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
140140
}
141141

142+
type RegistryImages struct {
143+
CollectorMeta `json:",inline" yaml:",inline"`
144+
Images []string `json:"images" yaml:"images"`
145+
Namespace string `json:"namespace" yaml:"namespace"`
146+
ImagePullSecrets *ImagePullSecrets `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"`
147+
}
148+
142149
type Collect struct {
143150
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
144151
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
@@ -154,6 +161,7 @@ type Collect struct {
154161
Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"`
155162
Collectd *Collectd `json:"collectd,omitempty" yaml:"collectd,omitempty"`
156163
Ceph *Ceph `json:"ceph,omitempty" yaml:"ceph,omitempty"`
164+
RegistryImages *RegistryImages `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
157165
}
158166

159167
func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSubjectAccessReviewSpec {

0 commit comments

Comments
 (0)