Skip to content

Commit f2be6f5

Browse files
Allow Preflight CLI to consume multiple specs as input (#894)
To keep both the Support Bundle and Preflight CLIs similar, this PR adds the ability for the Preflight binary to allow multiple specs be provided as CLI args and for them all to be run.
1 parent cd1511a commit f2be6f5

File tree

3 files changed

+131
-63
lines changed

3 files changed

+131
-63
lines changed

cmd/preflight/cli/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ that a cluster meets the requirements to run an application.`,
3030
},
3131
RunE: func(cmd *cobra.Command, args []string) error {
3232
v := viper.GetViper()
33-
return preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args[0])
33+
return preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args)
3434
},
3535
}
3636

pkg/preflight/concat.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package preflight
2+
3+
import (
4+
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
5+
)
6+
7+
func ConcatPreflightSpec(target *troubleshootv1beta2.Preflight, source *troubleshootv1beta2.Preflight) *troubleshootv1beta2.Preflight {
8+
newSpec := target.DeepCopy()
9+
newSpec.Spec.Collectors = append(target.Spec.Collectors, source.Spec.Collectors...)
10+
newSpec.Spec.RemoteCollectors = append(target.Spec.RemoteCollectors, source.Spec.RemoteCollectors...)
11+
newSpec.Spec.Analyzers = append(target.Spec.Analyzers, source.Spec.Analyzers...)
12+
return newSpec
13+
}
14+
15+
func ConcatHostPreflightSpec(target *troubleshootv1beta2.HostPreflight, source *troubleshootv1beta2.HostPreflight) *troubleshootv1beta2.HostPreflight {
16+
newSpec := target.DeepCopy()
17+
newSpec.Spec.Collectors = append(target.Spec.Collectors, source.Spec.Collectors...)
18+
newSpec.Spec.RemoteCollectors = append(target.Spec.RemoteCollectors, source.Spec.RemoteCollectors...)
19+
newSpec.Spec.Analyzers = append(target.Spec.Analyzers, source.Spec.Analyzers...)
20+
return newSpec
21+
}

pkg/preflight/run.go

Lines changed: 109 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
"k8s.io/client-go/kubernetes/scheme"
3131
)
3232

33-
func RunPreflights(interactive bool, output, format, arg string) error {
33+
func RunPreflights(interactive bool, output string, format string, args []string) error {
3434
if interactive {
3535
fmt.Print(cursor.Hide())
3636
defer fmt.Print(cursor.Show())
@@ -44,82 +44,107 @@ func RunPreflights(interactive bool, output, format, arg string) error {
4444
}()
4545

4646
var preflightContent []byte
47+
var preflightSpec *troubleshootv1beta2.Preflight
48+
var hostPreflightSpec *troubleshootv1beta2.HostPreflight
49+
var uploadResultSpecs []*troubleshootv1beta2.Preflight
4750
var err error
48-
if strings.HasPrefix(arg, "secret/") {
49-
// format secret/namespace-name/secret-name
50-
pathParts := strings.Split(arg, "/")
51-
if len(pathParts) != 3 {
52-
return errors.Errorf("path %s must have 3 components", arg)
53-
}
54-
55-
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "preflight-spec")
56-
if err != nil {
57-
return errors.Wrap(err, "failed to get spec from secret")
58-
}
5951

60-
preflightContent = spec
61-
} else if _, err = os.Stat(arg); err == nil {
62-
b, err := ioutil.ReadFile(arg)
63-
if err != nil {
64-
return err
65-
}
66-
67-
preflightContent = b
68-
} else {
69-
u, err := url.Parse(arg)
70-
if err != nil {
71-
return err
72-
}
73-
74-
if u.Scheme == "oci" {
75-
content, err := oci.PullPreflightFromOCI(arg)
76-
if err != nil {
77-
if err == oci.ErrNoRelease {
78-
return errors.Errorf("no release found for %s.\nCheck the oci:// uri for errors or contact the application vendor for support.", arg)
79-
}
80-
81-
return err
52+
for i, v := range args {
53+
if strings.HasPrefix(v, "secret/") {
54+
// format secret/namespace-name/secret-name
55+
pathParts := strings.Split(v, "/")
56+
if len(pathParts) != 3 {
57+
return errors.Errorf("path %s must have 3 components", v)
8258
}
8359

84-
preflightContent = content
85-
} else {
86-
if !util.IsURL(arg) {
87-
return fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
60+
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "preflight-spec")
61+
if err != nil {
62+
return errors.Wrap(err, "failed to get spec from secret")
8863
}
8964

90-
req, err := http.NewRequest("GET", arg, nil)
65+
preflightContent = spec
66+
} else if _, err = os.Stat(v); err == nil {
67+
b, err := ioutil.ReadFile(v)
9168
if err != nil {
9269
return err
9370
}
94-
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta2")
95-
resp, err := http.DefaultClient.Do(req)
71+
72+
preflightContent = b
73+
} else {
74+
u, err := url.Parse(v)
9675
if err != nil {
9776
return err
9877
}
99-
defer resp.Body.Close()
10078

101-
body, err := ioutil.ReadAll(resp.Body)
102-
if err != nil {
103-
return err
79+
if u.Scheme == "oci" {
80+
content, err := oci.PullPreflightFromOCI(v)
81+
if err != nil {
82+
if err == oci.ErrNoRelease {
83+
return errors.Errorf("no release found for %s.\nCheck the oci:// uri for errors or contact the application vendor for support.", v)
84+
}
85+
86+
return err
87+
}
88+
89+
preflightContent = content
90+
} else {
91+
if !util.IsURL(v) {
92+
return fmt.Errorf("%s is not a URL and was not found (err %s)", v, err)
93+
}
94+
95+
req, err := http.NewRequest("GET", v, nil)
96+
if err != nil {
97+
return err
98+
}
99+
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta2")
100+
resp, err := http.DefaultClient.Do(req)
101+
if err != nil {
102+
return err
103+
}
104+
defer resp.Body.Close()
105+
106+
body, err := ioutil.ReadAll(resp.Body)
107+
if err != nil {
108+
return err
109+
}
110+
111+
preflightContent = body
104112
}
113+
}
105114

106-
preflightContent = body
115+
preflightContent, err = docrewrite.ConvertToV1Beta2(preflightContent)
116+
if err != nil {
117+
return errors.Wrap(err, "failed to convert to v1beta2")
107118
}
108-
}
109119

110-
preflightContent, err = docrewrite.ConvertToV1Beta2(preflightContent)
111-
if err != nil {
112-
return errors.Wrap(err, "failed to convert to v1beta2")
113-
}
120+
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
121+
decode := scheme.Codecs.UniversalDeserializer().Decode
122+
obj, _, err := decode([]byte(preflightContent), nil, nil)
123+
if err != nil {
124+
return errors.Wrapf(err, "failed to parse %s", v)
125+
}
114126

115-
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
116-
decode := scheme.Codecs.UniversalDeserializer().Decode
117-
obj, _, err := decode([]byte(preflightContent), nil, nil)
118-
if err != nil {
119-
return errors.Wrapf(err, "failed to parse %s", arg)
127+
if spec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
128+
if spec.Spec.UploadResultsTo == "" {
129+
if i == 0 {
130+
preflightSpec = spec
131+
} else {
132+
preflightSpec = ConcatPreflightSpec(preflightSpec, spec)
133+
}
134+
} else {
135+
uploadResultSpecs = append(uploadResultSpecs, preflightSpec)
136+
}
137+
} else if spec, ok := obj.(*troubleshootv1beta2.HostPreflight); ok {
138+
if i == 0 {
139+
hostPreflightSpec = spec
140+
} else {
141+
hostPreflightSpec = ConcatHostPreflightSpec(hostPreflightSpec, spec)
142+
}
143+
}
120144
}
121145

122146
var collectResults []CollectResult
147+
var uploadCollectResults []CollectResult
123148
preflightSpecName := ""
124149

125150
progressCh := make(chan interface{})
@@ -136,14 +161,28 @@ func RunPreflights(interactive bool, output, format, arg string) error {
136161
progressCollection.Go(collectNonInteractiveProgess(ctx, progressCh))
137162
}
138163

139-
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
164+
uploadResultsMap := make(map[string][]CollectResult)
165+
166+
if preflightSpec != nil {
140167
r, err := collectInCluster(preflightSpec, progressCh)
141168
if err != nil {
142169
return errors.Wrap(err, "failed to collect in cluster")
143170
}
144171
collectResults = append(collectResults, *r)
145172
preflightSpecName = preflightSpec.Name
146-
} else if hostPreflightSpec, ok := obj.(*troubleshootv1beta2.HostPreflight); ok {
173+
}
174+
if uploadResultSpecs != nil {
175+
for _, spec := range uploadResultSpecs {
176+
r, err := collectInCluster(spec, progressCh)
177+
if err != nil {
178+
return errors.Wrap(err, "failed to collect in cluster")
179+
}
180+
uploadResultsMap[spec.Spec.UploadResultsTo] = append(uploadResultsMap[spec.Spec.UploadResultsTo], *r)
181+
uploadCollectResults = append(collectResults, *r)
182+
preflightSpecName = spec.Name
183+
}
184+
}
185+
if hostPreflightSpec != nil {
147186
if len(hostPreflightSpec.Spec.Collectors) > 0 {
148187
r, err := collectHost(hostPreflightSpec, progressCh)
149188
if err != nil {
@@ -161,7 +200,7 @@ func RunPreflights(interactive bool, output, format, arg string) error {
161200
preflightSpecName = hostPreflightSpec.Name
162201
}
163202

164-
if collectResults == nil {
203+
if collectResults == nil && uploadCollectResults == nil {
165204
return errors.New("no results")
166205
}
167206

@@ -170,9 +209,17 @@ func RunPreflights(interactive bool, output, format, arg string) error {
170209
analyzeResults = append(analyzeResults, res.Analyze()...)
171210
}
172211

173-
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
174-
if preflightSpec.Spec.UploadResultsTo != "" {
175-
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
212+
uploadAnalyzeResultsMap := make(map[string][]*analyzer.AnalyzeResult)
213+
for location, results := range uploadResultsMap {
214+
for _, res := range results {
215+
uploadAnalyzeResultsMap[location] = append(uploadAnalyzeResultsMap[location], res.Analyze()...)
216+
analyzeResults = append(analyzeResults, uploadAnalyzeResultsMap[location]...)
217+
}
218+
}
219+
220+
if uploadAnalyzeResultsMap != nil {
221+
for k, v := range uploadAnalyzeResultsMap {
222+
err := uploadResults(k, v)
176223
if err != nil {
177224
progressCh <- err
178225
}

0 commit comments

Comments
 (0)