Merge pull request #53 from replicatedhq/divolgin/upload

Automatically upload generated support bundle file
This commit is contained in:
divolgin
2019-08-21 07:18:54 -07:00
committed by GitHub
8 changed files with 546 additions and 283 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"time"
@@ -13,6 +14,7 @@ import (
"github.com/ahmetalpbalkan/go-cursor"
"github.com/fatih/color"
"github.com/mholt/archiver"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/spf13/viper"
@@ -32,25 +34,25 @@ func runTroubleshootNoCRD(v *viper.Viper, arg string) error {
b, err := ioutil.ReadFile(arg)
if err != nil {
return err
return errors.Wrap(err, "read spec file")
}
collectorContent = string(b)
} else {
req, err := http.NewRequest("GET", arg, nil)
if err != nil {
return err
return errors.Wrap(err, "make request")
}
req.Header.Set("User-Agent", "Replicated_Troubleshoot/v1beta1")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
return errors.Wrap(err, "execute request")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
return errors.Wrap(err, "read responce body")
}
collectorContent = string(body)
@@ -83,7 +85,7 @@ func runTroubleshootNoCRD(v *viper.Viper, arg string) error {
if currentDir == "" {
fmt.Printf("\r%s \033[36mCollecting support bundle\033[m %s", cursor.ClearEntireLine(), s.Next())
} else {
fmt.Printf("\r%s \033[36mCollecting support bundle\033[m %s: %s", cursor.ClearEntireLine(), s.Next(), currentDir)
fmt.Printf("\r%s \033[36mCollecting support bundle\033[m %s %s", cursor.ClearEntireLine(), s.Next(), currentDir)
}
}
}
@@ -94,38 +96,55 @@ func runTroubleshootNoCRD(v *viper.Viper, arg string) error {
archivePath, err := runCollectors(v, collector, progressChan)
if err != nil {
return err
return errors.Wrap(err, "run collectors")
}
fmt.Printf("\r%s", cursor.ClearEntireLine())
msg := archivePath
if appName := collector.Labels["applicationName"]; appName != "" {
f := `A support bundle for %s has been created in this directory
if len(collector.Spec.AfterCollection) == 0 {
msg := archivePath
if appName := collector.Labels["applicationName"]; appName != "" {
f := `A support bundle for %s has been created in this directory
named %s. Please upload it on the Troubleshoot page of
the %s Admin Console to begin analysis.`
msg = fmt.Sprintf(f, appName, archivePath, appName)
msg = fmt.Sprintf(f, appName, archivePath, appName)
}
fmt.Printf("%s\n", msg)
return nil
}
fmt.Printf("%s\n", msg)
for _, ac := range collector.Spec.AfterCollection {
if ac.UploadResultsTo != nil {
if err := uploadSupportBundle(ac.UploadResultsTo, archivePath); err != nil {
return errors.Wrap(err, "upload support bundle")
}
} else if ac.Callback != nil {
if err := callbackSupportBundleAPI(ac.Callback, archivePath); err != nil {
return errors.Wrap(err, "execute callback")
}
}
}
fmt.Printf("A support bundle has been created in the current directory named %q\n", archivePath)
return nil
}
func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, progressChan chan interface{}) (string, error) {
bundlePath, err := ioutil.TempDir("", "troubleshoot")
if err != nil {
return "", err
return "", errors.Wrap(err, "create temp dir")
}
defer os.RemoveAll(bundlePath)
versionFilename, err := writeVersionFile(bundlePath)
if err != nil {
return "", err
return "", errors.Wrap(err, "write version file")
}
desiredCollectors := make([]*troubleshootv1beta1.Collect, 0, 0)
for _, definedCollector := range collector.Spec {
for _, definedCollector := range collector.Spec.Collectors {
desiredCollectors = append(desiredCollectors, definedCollector)
}
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
@@ -175,7 +194,7 @@ func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, prog
}
if err := tarGz.Archive(paths, "support-bundle.tar.gz"); err != nil {
return "", err
return "", errors.Wrap(err, "create archive")
}
return "support-bundle.tar.gz", nil
@@ -186,7 +205,7 @@ func parseAndSaveCollectorOutput(output string, bundlePath string) (string, erro
input := make(map[string]interface{})
if err := json.Unmarshal([]byte(output), &input); err != nil {
return "", err
return "", errors.Wrap(err, "unmarshal output")
}
for filename, maybeContents := range input {
@@ -195,33 +214,33 @@ func parseAndSaveCollectorOutput(output string, bundlePath string) (string, erro
dir = outPath
if err := os.MkdirAll(outPath, 0777); err != nil {
return "", err
return "", errors.Wrap(err, "create output file")
}
switch maybeContents.(type) {
case string:
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
if err != nil {
return "", err
return "", errors.Wrap(err, "decode collector output")
}
if err := writeFile(filepath.Join(outPath, fileName), decoded); err != nil {
return "", err
return "", errors.Wrap(err, "write collector output")
}
case map[string]interface{}:
for k, v := range maybeContents.(map[string]interface{}) {
s, _ := filepath.Split(filepath.Join(outPath, fileName, k))
if err := os.MkdirAll(s, 0777); err != nil {
return "", err
return "", errors.Wrap(err, "write output directories")
}
decoded, err := base64.StdEncoding.DecodeString(v.(string))
if err != nil {
return "", err
return "", errors.Wrap(err, "decode output")
}
if err := writeFile(filepath.Join(outPath, fileName, k), decoded); err != nil {
return "", err
return "", errors.Wrap(err, "write output")
}
}
}
@@ -229,3 +248,67 @@ func parseAndSaveCollectorOutput(output string, bundlePath string) (string, erro
return dir, nil
}
func uploadSupportBundle(r *troubleshootv1beta1.ResultRequest, archivePath string) error {
contentType := getExpectedContentType(r.URI)
if contentType != "" && contentType != "application/tar+gzip" {
return fmt.Errorf("cannot upload content type %s", contentType)
}
f, err := os.Open(archivePath)
if err != nil {
return errors.Wrap(err, "open file")
}
defer f.Close()
fileStat, err := f.Stat()
if err != nil {
return errors.Wrap(err, "stat file")
}
req, err := http.NewRequest(r.Method, r.URI, f)
if err != nil {
return errors.Wrap(err, "create request")
}
req.ContentLength = fileStat.Size()
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "execute request")
}
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
return nil
}
func getExpectedContentType(uploadURL string) string {
parsedURL, err := url.Parse(uploadURL)
if err != nil {
return ""
}
return parsedURL.Query().Get("Content-Type")
}
func callbackSupportBundleAPI(r *troubleshootv1beta1.ResultRequest, archivePath string) error {
req, err := http.NewRequest(r.Method, r.URI, nil)
if err != nil {
return errors.Wrap(err, "create request")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "execute request")
}
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
return nil
}

View File

@@ -390,167 +390,195 @@ spec:
type: string
type: object
spec:
items:
properties:
clusterInfo:
type: object
clusterResources:
type: object
copy:
properties:
afterCollection:
items:
properties:
collectorName:
type: string
containerName:
type: string
containerPath:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
required:
- selector
- namespace
- containerPath
type: object
exec:
properties:
args:
items:
type: string
type: array
collectorName:
type: string
command:
items:
type: string
type: array
containerName:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
timeout:
type: string
required:
- selector
- namespace
type: object
http:
properties:
collectorName:
type: string
get:
callback:
properties:
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
method:
type: string
uri:
type: string
required:
- url
- uri
- method
type: object
post:
uploadResultsTo:
properties:
body:
method:
type: string
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
uri:
type: string
required:
- url
- uri
- method
type: object
put:
type: object
type: array
collectors:
items:
properties:
clusterInfo:
type: object
clusterResources:
type: object
copy:
properties:
body:
collectorName:
type: string
headers:
additionalProperties:
containerName:
type: string
containerPath:
type: string
namespace:
type: string
selector:
items:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
type: array
required:
- selector
- namespace
- containerPath
type: object
exec:
properties:
args:
items:
type: string
type: array
collectorName:
type: string
command:
items:
type: string
type: array
containerName:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
timeout:
type: string
required:
- url
- selector
- namespace
type: object
type: object
logs:
properties:
collectorName:
type: string
limits:
http:
properties:
maxAge:
collectorName:
type: string
maxLines:
format: int64
type: integer
get:
properties:
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
type: string
required:
- url
type: object
post:
properties:
body:
type: string
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
type: string
required:
- url
type: object
put:
properties:
body:
type: string
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
type: string
required:
- url
type: object
type: object
logs:
properties:
collectorName:
type: string
limits:
properties:
maxAge:
type: string
maxLines:
format: int64
type: integer
type: object
namespace:
type: string
selector:
items:
type: string
type: array
required:
- selector
type: object
run:
properties:
args:
items:
type: string
type: array
collectorName:
type: string
command:
items:
type: string
type: array
image:
type: string
imagePullPolicy:
type: string
namespace:
type: string
timeout:
type: string
required:
- namespace
- image
type: object
secret:
properties:
collectorName:
type: string
includeValue:
type: boolean
key:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
namespace:
type: string
selector:
items:
type: string
type: array
required:
- selector
type: object
run:
properties:
args:
items:
type: string
type: array
collectorName:
type: string
command:
items:
type: string
type: array
image:
type: string
imagePullPolicy:
type: string
namespace:
type: string
timeout:
type: string
required:
- namespace
- image
type: object
secret:
properties:
collectorName:
type: string
includeValue:
type: boolean
key:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: object
type: array
type: array
type: object
status:
type: object
type: object

View File

@@ -8,6 +8,31 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AfterCollection) DeepCopyInto(out *AfterCollection) {
*out = *in
if in.UploadResultsTo != nil {
in, out := &in.UploadResultsTo, &out.UploadResultsTo
*out = new(ResultRequest)
**out = **in
}
if in.Callback != nil {
in, out := &in.Callback, &out.Callback
*out = new(ResultRequest)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AfterCollection.
func (in *AfterCollection) DeepCopy() *AfterCollection {
if in == nil {
return nil
}
out := new(AfterCollection)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = *in
@@ -390,17 +415,7 @@ func (in *Collector) DeepCopyInto(out *Collector) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
if in.Spec != nil {
in, out := &in.Spec, &out.Spec
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
@@ -589,6 +604,43 @@ func (in *CollectorRef) DeepCopy() *CollectorRef {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorSpec) DeepCopyInto(out *CollectorSpec) {
*out = *in
if in.Collectors != nil {
in, out := &in.Collectors, &out.Collectors
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
if in.AfterCollection != nil {
in, out := &in.AfterCollection, &out.AfterCollection
*out = make([]*AfterCollection, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(AfterCollection)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorSpec.
func (in *CollectorSpec) DeepCopy() *CollectorSpec {
if in == nil {
return nil
}
out := new(CollectorSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorStatus) DeepCopyInto(out *CollectorStatus) {
*out = *in
@@ -1151,6 +1203,21 @@ func (in *Put) DeepCopy() *Put {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResultRequest) DeepCopyInto(out *ResultRequest) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResultRequest.
func (in *ResultRequest) DeepCopy() *ResultRequest {
if in == nil {
return nil
}
out := new(ResultRequest)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Run) DeepCopyInto(out *Run) {
*out = *in

View File

@@ -3,76 +3,77 @@ kind: Collector
metadata:
name: collector-sample
spec:
- clusterInfo: {}
- clusterResources: {}
# - secret:
# name: illmannered-cricket-mysql
# namespace: default
# key: mysql-password
# - logs:
# selector:
# - name=nginx-ingress-microk8s
# namespace: default
# limits:
# maxAge: 30d
# maxLines: 10000
# - run:
# collectorName: ping-google
# namespace: default
# image: flungo/netutils
# command: ["ping"]
# args: ["www.google.com"]
# timeout: 5s
- exec:
collectorName: mysql-vars
selector:
- app=mysql
namespace: default
command: ["mysql"]
args: ["-ureplicated", "-ppassword", "-e", "show processlist"]
timeout: 60m
- exec:
collectorName: hosts
selector:
- app=graphql-api
namespace: default
command: ["cat"]
args: ["/etc/hosts"]
timeout: 60m
- exec:
collectorName: broken
selector:
- app=graphql-api
namespace: default
command: ["cat"]
args: ["/etc/hostdasddsda"]
timeout: 60m
# - copy:
# selector:
# - app=illmannered-cricket-mysql
# namespace: default
# containerPath: /etc/hosts
- http:
collectorName: test-get
get:
url: https://api.staging.replicated.com/market/v1/echo/ip
insecureSkipVerify: false
headers: {}
- http:
collectorName: test-post
post:
url: http://httpbin.org/headers
insecureSkipVerify: false
headers:
X-Custom-Header: "post-request"
- http:
collectorName: test-put
put:
url: http://httpbin.org/anything
insecureSkipVerify: false
headers:
X-Custom-Header: "put-request"
- http:
collectorName: test-broken
put:
url: ""
collectors:
- clusterInfo: {}
- clusterResources: {}
# - secret:
# name: illmannered-cricket-mysql
# namespace: default
# key: mysql-password
# - logs:
# selector:
# - name=nginx-ingress-microk8s
# namespace: default
# limits:
# maxAge: 30d
# maxLines: 10000
# - run:
# collectorName: ping-google
# namespace: default
# image: flungo/netutils
# command: ["ping"]
# args: ["www.google.com"]
# timeout: 5s
- exec:
collectorName: mysql-vars
selector:
- app=mysql
namespace: default
command: ["mysql"]
args: ["-ureplicated", "-ppassword", "-e", "show processlist"]
timeout: 60m
- exec:
collectorName: hosts
selector:
- app=graphql-api
namespace: default
command: ["cat"]
args: ["/etc/hosts"]
timeout: 60m
- exec:
collectorName: broken
selector:
- app=graphql-api
namespace: default
command: ["cat"]
args: ["/etc/hostdasddsda"]
timeout: 60m
# - copy:
# selector:
# - app=illmannered-cricket-mysql
# namespace: default
# containerPath: /etc/hosts
- http:
collectorName: test-get
get:
url: https://api.staging.replicated.com/market/v1/echo/ip
insecureSkipVerify: false
headers: {}
- http:
collectorName: test-post
post:
url: http://httpbin.org/headers
insecureSkipVerify: false
headers:
X-Custom-Header: "post-request"
- http:
collectorName: test-put
put:
url: http://httpbin.org/anything
insecureSkipVerify: false
headers:
X-Custom-Header: "put-request"
- http:
collectorName: test-broken
put:
url: ""

View File

@@ -3,26 +3,27 @@ kind: Collector
metadata:
name: collector-sample
spec:
- secret:
name: myapp-postgres
namespace: default
key: uri
includeValue: false
- logs:
selector:
- name=cilium-operator
namespace: kube-system
limits:
maxAge: 30d
maxLines: 10000
- run:
collectorName: ping-google
namespace: default
image: flungo/netutils
command: ["ping"]
args: ["www.google.com"]
timeout: 5s
- http:
collectorName: echo-ip
get:
url: https://api.replicated.com/market/v1/echo/ip
collectors:
- secret:
name: myapp-postgres
namespace: default
key: uri
includeValue: false
- logs:
selector:
- name=cilium-operator
namespace: kube-system
limits:
maxAge: 30d
maxLines: 10000
- run:
collectorName: ping-google
namespace: default
image: flungo/netutils
command: ["ping"]
args: ["www.google.com"]
timeout: 5s
- http:
collectorName: echo-ip
get:
url: https://api.replicated.com/market/v1/echo/ip

View File

@@ -20,6 +20,22 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ResultRequest struct {
URI string `json:"uri" yaml:"uri"`
Method string `json:"method" yaml:"method"`
}
type AfterCollection struct {
UploadResultsTo *ResultRequest `json:"uploadResultsTo,omitempty" yaml:"uploadResultsTo,omitempty"`
Callback *ResultRequest `json:"callback,omitempty" yaml:"callback,omitempty"`
}
// CollectorSpec defines the desired state of Collector
type CollectorSpec struct {
Collectors []*Collect `json:"collectors,omitempty" yaml:"collectors,omitempty"`
AfterCollection []*AfterCollection `json:"afterCollection,omitempty" yaml:"afterCollection,omitempty"`
}
// CollectorStatus defines the observed state of Collector
type CollectorStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
@@ -35,7 +51,7 @@ type Collector struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
Spec []*Collect `json:"spec,omitempty" yaml:"spec,omitempty"`
Spec CollectorSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
Status CollectorStatus `json:"status,omitempty"`
}

View File

@@ -24,6 +24,31 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AfterCollection) DeepCopyInto(out *AfterCollection) {
*out = *in
if in.UploadResultsTo != nil {
in, out := &in.UploadResultsTo, &out.UploadResultsTo
*out = new(ResultRequest)
**out = **in
}
if in.Callback != nil {
in, out := &in.Callback, &out.Callback
*out = new(ResultRequest)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AfterCollection.
func (in *AfterCollection) DeepCopy() *AfterCollection {
if in == nil {
return nil
}
out := new(AfterCollection)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = *in
@@ -406,17 +431,7 @@ func (in *Collector) DeepCopyInto(out *Collector) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
if in.Spec != nil {
in, out := &in.Spec, &out.Spec
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
@@ -605,6 +620,43 @@ func (in *CollectorRef) DeepCopy() *CollectorRef {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorSpec) DeepCopyInto(out *CollectorSpec) {
*out = *in
if in.Collectors != nil {
in, out := &in.Collectors, &out.Collectors
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
if in.AfterCollection != nil {
in, out := &in.AfterCollection, &out.AfterCollection
*out = make([]*AfterCollection, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(AfterCollection)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorSpec.
func (in *CollectorSpec) DeepCopy() *CollectorSpec {
if in == nil {
return nil
}
out := new(CollectorSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorStatus) DeepCopyInto(out *CollectorStatus) {
*out = *in
@@ -1167,6 +1219,21 @@ func (in *Put) DeepCopy() *Put {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResultRequest) DeepCopyInto(out *ResultRequest) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResultRequest.
func (in *ResultRequest) DeepCopy() *ResultRequest {
if in == nil {
return nil
}
out := new(ResultRequest)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Run) DeepCopyInto(out *Run) {
*out = *in

View File

@@ -137,7 +137,7 @@ func (r *ReconcileCollectorJob) Reconcile(request reconcile.Request) (reconcile.
return reconcile.Result{}, err
}
for _, collector := range collectorSpec.Spec {
for _, collector := range collectorSpec.Spec.Collectors {
if err := r.reconileOneCollectorJob(instance, collector); err != nil {
return reconcile.Result{}, err
}