add a dedicated support bundle metadata collector (#1992)

* add support bundle metadata collector

* add e2e test for the new collector

* make fmt

* properly include v1beta3

* remove the ability to specify an arbitrary secret
This commit is contained in:
Andrew Lavery
2026-03-12 12:38:45 -04:00
committed by GitHub
parent 596a1f21a6
commit 94db56d668
17 changed files with 672 additions and 30 deletions
+1
View File
@@ -158,6 +158,7 @@ generate: controller-gen client-gen
--input-base github.com/replicatedhq/troubleshoot/pkg/apis \
--input troubleshoot/v1beta1 \
--input troubleshoot/v1beta2 \
--input troubleshoot/v1beta3 \
--go-header-file ./hack/boilerplate.go.txt
cp -r troubleshootclientset pkg/client
rm -rf troubleshootclientset
@@ -318,37 +318,43 @@ type Etcd struct {
Image string `json:"image" yaml:"image"`
}
type SupportBundleMetadata struct {
CollectorMeta `json:",inline" yaml:",inline"`
Namespace string `json:"namespace" yaml:"namespace"`
}
type Collect struct {
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
Secret *Secret `json:"secret,omitempty" yaml:"secret,omitempty"`
CustomMetrics *CustomMetrics `json:"customMetrics,omitempty" yaml:"customMetrics,omitempty"`
ConfigMap *ConfigMap `json:"configMap,omitempty" yaml:"configMap,omitempty"`
Logs *Logs `json:"logs,omitempty" yaml:"logs,omitempty"`
Run *Run `json:"run,omitempty" yaml:"run,omitempty"`
RunPod *RunPod `json:"runPod,omitempty" yaml:"runPod,omitempty"`
RunDaemonSet *RunDaemonSet `json:"runDaemonSet,omitempty" yaml:"runDaemonSet,omitempty"`
Exec *Exec `json:"exec,omitempty" yaml:"exec,omitempty"`
Data *Data `json:"data,omitempty" yaml:"data,omitempty"`
Copy *Copy `json:"copy,omitempty" yaml:"copy,omitempty"`
CopyFromHost *CopyFromHost `json:"copyFromHost,omitempty" yaml:"copyFromHost,omitempty"`
HTTP *HTTP `json:"http,omitempty" yaml:"http,omitempty"`
Postgres *Database `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Mssql *Database `json:"mssql,omitempty" yaml:"mssql,omitempty"`
Mysql *Database `json:"mysql,omitempty" yaml:"mysql,omitempty"`
Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"`
Collectd *Collectd `json:"collectd,omitempty" yaml:"collectd,omitempty"`
Ceph *Ceph `json:"ceph,omitempty" yaml:"ceph,omitempty"`
Longhorn *Longhorn `json:"longhorn,omitempty" yaml:"longhorn,omitempty"`
RegistryImages *RegistryImages `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
Sysctl *Sysctl `json:"sysctl,omitempty" yaml:"sysctl,omitempty"`
Certificates *Certificates `json:"certificates,omitempty" yaml:"certificates,omitempty"`
Helm *Helm `json:"helm,omitempty" yaml:"helm,omitempty"`
Goldpinger *Goldpinger `json:"goldpinger,omitempty" yaml:"goldpinger,omitempty"`
Sonobuoy *Sonobuoy `json:"sonobuoy,omitempty" yaml:"sonobuoy,omitempty"`
NodeMetrics *NodeMetrics `json:"nodeMetrics,omitempty" yaml:"nodeMetrics,omitempty"`
DNS *DNS `json:"dns,omitempty" yaml:"dns,omitempty"`
Etcd *Etcd `json:"etcd,omitempty" yaml:"etcd,omitempty"`
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
Secret *Secret `json:"secret,omitempty" yaml:"secret,omitempty"`
CustomMetrics *CustomMetrics `json:"customMetrics,omitempty" yaml:"customMetrics,omitempty"`
ConfigMap *ConfigMap `json:"configMap,omitempty" yaml:"configMap,omitempty"`
Logs *Logs `json:"logs,omitempty" yaml:"logs,omitempty"`
Run *Run `json:"run,omitempty" yaml:"run,omitempty"`
RunPod *RunPod `json:"runPod,omitempty" yaml:"runPod,omitempty"`
RunDaemonSet *RunDaemonSet `json:"runDaemonSet,omitempty" yaml:"runDaemonSet,omitempty"`
Exec *Exec `json:"exec,omitempty" yaml:"exec,omitempty"`
Data *Data `json:"data,omitempty" yaml:"data,omitempty"`
Copy *Copy `json:"copy,omitempty" yaml:"copy,omitempty"`
CopyFromHost *CopyFromHost `json:"copyFromHost,omitempty" yaml:"copyFromHost,omitempty"`
HTTP *HTTP `json:"http,omitempty" yaml:"http,omitempty"`
Postgres *Database `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Mssql *Database `json:"mssql,omitempty" yaml:"mssql,omitempty"`
Mysql *Database `json:"mysql,omitempty" yaml:"mysql,omitempty"`
Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"`
Collectd *Collectd `json:"collectd,omitempty" yaml:"collectd,omitempty"`
Ceph *Ceph `json:"ceph,omitempty" yaml:"ceph,omitempty"`
Longhorn *Longhorn `json:"longhorn,omitempty" yaml:"longhorn,omitempty"`
RegistryImages *RegistryImages `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
Sysctl *Sysctl `json:"sysctl,omitempty" yaml:"sysctl,omitempty"`
Certificates *Certificates `json:"certificates,omitempty" yaml:"certificates,omitempty"`
Helm *Helm `json:"helm,omitempty" yaml:"helm,omitempty"`
Goldpinger *Goldpinger `json:"goldpinger,omitempty" yaml:"goldpinger,omitempty"`
Sonobuoy *Sonobuoy `json:"sonobuoy,omitempty" yaml:"sonobuoy,omitempty"`
NodeMetrics *NodeMetrics `json:"nodeMetrics,omitempty" yaml:"nodeMetrics,omitempty"`
DNS *DNS `json:"dns,omitempty" yaml:"dns,omitempty"`
Etcd *Etcd `json:"etcd,omitempty" yaml:"etcd,omitempty"`
SupportBundleMetadata *SupportBundleMetadata `json:"supportBundleMetadata,omitempty" yaml:"supportBundleMetadata,omitempty"`
}
func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSubjectAccessReviewSpec {
@@ -568,6 +574,19 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
})
} else if c.Sysctl != nil {
// TODO
} else if c.SupportBundleMetadata != nil {
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: pickNamespaceOrDefault(c.SupportBundleMetadata.Namespace, overrideNS),
Verb: "get",
Group: "",
Version: "",
Resource: "secrets",
Subresource: "",
Name: "replicated-support-metadata",
},
NonResourceAttributes: nil,
})
}
return result
@@ -671,6 +690,10 @@ func (c *Collect) GetName() string {
collector = "certificates"
name = c.Certificates.CollectorName
}
if c.SupportBundleMetadata != nil {
collector = "support-bundle-metadata"
name = c.SupportBundleMetadata.CollectorName
}
if collector == "" {
return "<none>"
@@ -985,6 +985,11 @@ func (in *Collect) DeepCopyInto(out *Collect) {
*out = new(Etcd)
(*in).DeepCopyInto(*out)
}
if in.SupportBundleMetadata != nil {
in, out := &in.SupportBundleMetadata, &out.SupportBundleMetadata
*out = new(SupportBundleMetadata)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
@@ -4943,6 +4948,22 @@ func (in *SupportBundleList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SupportBundleMetadata) DeepCopyInto(out *SupportBundleMetadata) {
*out = *in
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SupportBundleMetadata.
func (in *SupportBundleMetadata) DeepCopy() *SupportBundleMetadata {
if in == nil {
return nil
}
out := new(SupportBundleMetadata)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SupportBundleSpec) DeepCopyInto(out *SupportBundleSpec) {
*out = *in
@@ -23,6 +23,7 @@ import (
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3"
discovery "k8s.io/client-go/discovery"
rest "k8s.io/client-go/rest"
flowcontrol "k8s.io/client-go/util/flowcontrol"
@@ -32,6 +33,7 @@ type Interface interface {
Discovery() discovery.DiscoveryInterface
TroubleshootV1beta1() troubleshootv1beta1.TroubleshootV1beta1Interface
TroubleshootV1beta2() troubleshootv1beta2.TroubleshootV1beta2Interface
TroubleshootV1beta3() troubleshootv1beta3.TroubleshootV1beta3Interface
}
// Clientset contains the clients for groups.
@@ -39,6 +41,7 @@ type Clientset struct {
*discovery.DiscoveryClient
troubleshootV1beta1 *troubleshootv1beta1.TroubleshootV1beta1Client
troubleshootV1beta2 *troubleshootv1beta2.TroubleshootV1beta2Client
troubleshootV1beta3 *troubleshootv1beta3.TroubleshootV1beta3Client
}
// TroubleshootV1beta1 retrieves the TroubleshootV1beta1Client
@@ -51,6 +54,11 @@ func (c *Clientset) TroubleshootV1beta2() troubleshootv1beta2.TroubleshootV1beta
return c.troubleshootV1beta2
}
// TroubleshootV1beta3 retrieves the TroubleshootV1beta3Client
func (c *Clientset) TroubleshootV1beta3() troubleshootv1beta3.TroubleshootV1beta3Interface {
return c.troubleshootV1beta3
}
// Discovery retrieves the DiscoveryClient
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
if c == nil {
@@ -103,6 +111,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
if err != nil {
return nil, err
}
cs.troubleshootV1beta3, err = troubleshootv1beta3.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
@@ -126,6 +138,7 @@ func New(c rest.Interface) *Clientset {
var cs Clientset
cs.troubleshootV1beta1 = troubleshootv1beta1.New(c)
cs.troubleshootV1beta2 = troubleshootv1beta2.New(c)
cs.troubleshootV1beta3 = troubleshootv1beta3.New(c)
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
return &cs
@@ -23,6 +23,8 @@ import (
faketroubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2"
faketroubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3"
faketroubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3/fake"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
@@ -98,3 +100,8 @@ func (c *Clientset) TroubleshootV1beta1() troubleshootv1beta1.TroubleshootV1beta
func (c *Clientset) TroubleshootV1beta2() troubleshootv1beta2.TroubleshootV1beta2Interface {
return &faketroubleshootv1beta2.FakeTroubleshootV1beta2{Fake: &c.Fake}
}
// TroubleshootV1beta3 retrieves the TroubleshootV1beta3Client
func (c *Clientset) TroubleshootV1beta3() troubleshootv1beta3.TroubleshootV1beta3Interface {
return &faketroubleshootv1beta3.FakeTroubleshootV1beta3{Fake: &c.Fake}
}
@@ -20,6 +20,7 @@ package fake
import (
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta3"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -33,6 +34,7 @@ var codecs = serializer.NewCodecFactory(scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
troubleshootv1beta1.AddToScheme,
troubleshootv1beta2.AddToScheme,
troubleshootv1beta3.AddToScheme,
}
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
@@ -0,0 +1,19 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
// This package has the automatically generated typed clients.
package v1beta3
@@ -0,0 +1,19 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
// Package fake has the automatically generated clients.
package fake
@@ -0,0 +1,51 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1beta3 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta3"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3"
gentype "k8s.io/client-go/gentype"
)
// fakeSupportBundles implements SupportBundleInterface
type fakeSupportBundles struct {
*gentype.FakeClientWithList[*v1beta3.SupportBundle, *v1beta3.SupportBundleList]
Fake *FakeTroubleshootV1beta3
}
func newFakeSupportBundles(fake *FakeTroubleshootV1beta3, namespace string) troubleshootv1beta3.SupportBundleInterface {
return &fakeSupportBundles{
gentype.NewFakeClientWithList[*v1beta3.SupportBundle, *v1beta3.SupportBundleList](
fake.Fake,
namespace,
v1beta3.SchemeGroupVersion.WithResource("supportbundles"),
v1beta3.SchemeGroupVersion.WithKind("SupportBundle"),
func() *v1beta3.SupportBundle { return &v1beta3.SupportBundle{} },
func() *v1beta3.SupportBundleList { return &v1beta3.SupportBundleList{} },
func(dst, src *v1beta3.SupportBundleList) { dst.ListMeta = src.ListMeta },
func(list *v1beta3.SupportBundleList) []*v1beta3.SupportBundle {
return gentype.ToPointerSlice(list.Items)
},
func(list *v1beta3.SupportBundleList, items []*v1beta3.SupportBundle) {
list.Items = gentype.FromPointerSlice(items)
},
),
fake,
}
}
@@ -0,0 +1,39 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeTroubleshootV1beta3 struct {
*testing.Fake
}
func (c *FakeTroubleshootV1beta3) SupportBundles(namespace string) v1beta3.SupportBundleInterface {
return newFakeSupportBundles(c, namespace)
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeTroubleshootV1beta3) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
@@ -0,0 +1,20 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta3
type SupportBundleExpansion interface{}
@@ -0,0 +1,69 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta3
import (
context "context"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta3"
scheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
gentype "k8s.io/client-go/gentype"
)
// SupportBundlesGetter has a method to return a SupportBundleInterface.
// A group's client should implement this interface.
type SupportBundlesGetter interface {
SupportBundles(namespace string) SupportBundleInterface
}
// SupportBundleInterface has methods to work with SupportBundle resources.
type SupportBundleInterface interface {
Create(ctx context.Context, supportBundle *troubleshootv1beta3.SupportBundle, opts v1.CreateOptions) (*troubleshootv1beta3.SupportBundle, error)
Update(ctx context.Context, supportBundle *troubleshootv1beta3.SupportBundle, opts v1.UpdateOptions) (*troubleshootv1beta3.SupportBundle, error)
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
UpdateStatus(ctx context.Context, supportBundle *troubleshootv1beta3.SupportBundle, opts v1.UpdateOptions) (*troubleshootv1beta3.SupportBundle, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*troubleshootv1beta3.SupportBundle, error)
List(ctx context.Context, opts v1.ListOptions) (*troubleshootv1beta3.SupportBundleList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *troubleshootv1beta3.SupportBundle, err error)
SupportBundleExpansion
}
// supportBundles implements SupportBundleInterface
type supportBundles struct {
*gentype.ClientWithList[*troubleshootv1beta3.SupportBundle, *troubleshootv1beta3.SupportBundleList]
}
// newSupportBundles returns a SupportBundles
func newSupportBundles(c *TroubleshootV1beta3Client, namespace string) *supportBundles {
return &supportBundles{
gentype.NewClientWithList[*troubleshootv1beta3.SupportBundle, *troubleshootv1beta3.SupportBundleList](
"supportbundles",
c.RESTClient(),
scheme.ParameterCodec,
namespace,
func() *troubleshootv1beta3.SupportBundle { return &troubleshootv1beta3.SupportBundle{} },
func() *troubleshootv1beta3.SupportBundleList { return &troubleshootv1beta3.SupportBundleList{} },
),
}
}
@@ -0,0 +1,100 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta3
import (
http "net/http"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta3"
scheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
rest "k8s.io/client-go/rest"
)
type TroubleshootV1beta3Interface interface {
RESTClient() rest.Interface
SupportBundlesGetter
}
// TroubleshootV1beta3Client is used to interact with features provided by the troubleshoot.sh group.
type TroubleshootV1beta3Client struct {
restClient rest.Interface
}
func (c *TroubleshootV1beta3Client) SupportBundles(namespace string) SupportBundleInterface {
return newSupportBundles(c, namespace)
}
// NewForConfig creates a new TroubleshootV1beta3Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*TroubleshootV1beta3Client, error) {
config := *c
setConfigDefaults(&config)
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new TroubleshootV1beta3Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*TroubleshootV1beta3Client, error) {
config := *c
setConfigDefaults(&config)
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &TroubleshootV1beta3Client{client}, nil
}
// NewForConfigOrDie creates a new TroubleshootV1beta3Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *TroubleshootV1beta3Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new TroubleshootV1beta3Client for the given RESTClient.
func New(c rest.Interface) *TroubleshootV1beta3Client {
return &TroubleshootV1beta3Client{c}
}
func setConfigDefaults(config *rest.Config) {
gv := troubleshootv1beta3.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *TroubleshootV1beta3Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
+5
View File
@@ -128,6 +128,8 @@ func GetCollector(collector *troubleshootv1beta2.Collect, bundlePath string, nam
return &CollectDNS{collector.DNS, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Etcd != nil:
return &CollectEtcd{collector.Etcd, bundlePath, clientConfig, client, ctx, RBACErrors}, true
case collector.SupportBundleMetadata != nil:
return &CollectSupportBundleMetadata{collector.SupportBundleMetadata, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
default:
return nil, false
}
@@ -223,6 +225,9 @@ func getCollectorName(c interface{}) string {
collector = "dns"
case *CollectEtcd:
collector = "etcd"
case *CollectSupportBundleMetadata:
collector = "support-bundle-metadata"
name = v.Collector.CollectorName
default:
collector = "<none>"
}
+54
View File
@@ -0,0 +1,54 @@
package collect
import (
"bytes"
"context"
"encoding/json"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
type CollectSupportBundleMetadata struct {
Collector *troubleshootv1beta2.SupportBundleMetadata
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectSupportBundleMetadata) Title() string {
return getCollectorName(c)
}
func (c *CollectSupportBundleMetadata) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectSupportBundleMetadata) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
output := NewResult()
const secretName = "replicated-support-metadata"
secret, err := c.Client.CoreV1().Secrets(c.Collector.Namespace).Get(c.Context, secretName, metav1.GetOptions{})
if err != nil {
return output, errors.Wrapf(err, "failed to get secret %s/%s", c.Collector.Namespace, secretName)
}
metadata := make(map[string]string)
for k, v := range secret.Data {
metadata[k] = string(v)
}
b, err := json.MarshalIndent(metadata, "", " ")
if err != nil {
return output, errors.Wrap(err, "failed to marshal metadata")
}
output.SaveResult(c.BundlePath, "metadata/cluster.json", bytes.NewBuffer(b))
return output, nil
}
@@ -0,0 +1,94 @@
package collect
import (
"context"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testclient "k8s.io/client-go/kubernetes/fake"
)
func TestCollectSupportBundleMetadata(t *testing.T) {
tests := []struct {
name string
collector *troubleshootv1beta2.SupportBundleMetadata
mockSecrets []corev1.Secret
want CollectorResult
wantErr bool
}{
{
name: "reads all data fields from secret",
collector: &troubleshootv1beta2.SupportBundleMetadata{
Namespace: "test-ns",
},
mockSecrets: []corev1.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: "replicated-support-metadata",
Namespace: "test-ns",
},
Data: map[string][]byte{
"mykey": []byte("myvalue"),
"myversion": []byte("1.0.0-example"),
"numCrashes": []byte("57"),
},
},
},
want: CollectorResult{
"metadata/cluster.json": mustJSONMarshalIndent(t, map[string]string{
"mykey": "myvalue",
"myversion": "1.0.0-example",
"numCrashes": "57",
}),
},
},
{
name: "empty data map",
collector: &troubleshootv1beta2.SupportBundleMetadata{
Namespace: "test-ns",
},
mockSecrets: []corev1.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: "replicated-support-metadata",
Namespace: "test-ns",
},
Data: map[string][]byte{},
},
},
want: CollectorResult{
"metadata/cluster.json": mustJSONMarshalIndent(t, map[string]string{}),
},
},
{
name: "secret not found returns error",
collector: &troubleshootv1beta2.SupportBundleMetadata{
Namespace: "test-ns",
},
mockSecrets: []corev1.Secret{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
client := testclient.NewSimpleClientset()
for _, secret := range tt.mockSecrets {
_, err := client.CoreV1().Secrets(secret.Namespace).Create(ctx, &secret, metav1.CreateOptions{})
require.NoError(t, err)
}
c := &CollectSupportBundleMetadata{tt.collector, "", "", nil, client, ctx, nil}
got, err := c.Collect(nil)
if tt.wantErr {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.want, got)
}
})
}
}
@@ -0,0 +1,105 @@
package e2e
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/e2e-framework/pkg/envconf"
"sigs.k8s.io/e2e-framework/pkg/features"
)
var metadataSpecTemplate = `
apiVersion: troubleshoot.sh/v1beta2
kind: SupportBundle
metadata:
name: metadata-test
spec:
collectors:
- clusterResources:
exclude: true
- clusterInfo:
exclude: true
- supportBundleMetadata:
namespace: $NAMESPACE
`
func TestSupportBundleMetadata(t *testing.T) {
secretName := "replicated-support-metadata"
expectedData := map[string]string{
"appVersion": "1.2.3",
"environment": "staging",
"clusterID": "abc-123",
}
feature := features.New("Support Bundle Metadata Collector").
Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: c.Namespace(),
},
Data: make(map[string][]byte),
}
for k, v := range expectedData {
secret.Data[k] = []byte(v)
}
client, err := c.NewClient()
if err != nil {
t.Fatal(err)
}
if err = client.Resources().Create(ctx, secret); err != nil {
t.Fatal(err)
}
return ctx
}).
Assess("check metadata/cluster.json contents", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
var out bytes.Buffer
namespace := c.Namespace()
supportBundleName := "metadata-test"
spec := strings.ReplaceAll(metadataSpecTemplate, "$NAMESPACE", namespace)
specPath := filepath.Join(t.TempDir(), "supportBundleMetadata.yaml")
err := os.WriteFile(specPath, []byte(spec), 0644)
require.NoError(t, err)
tarPath := fmt.Sprintf("%s.tar.gz", supportBundleName)
cmd := exec.CommandContext(ctx, sbBinary(), specPath, "--interactive=false", fmt.Sprintf("-o=%s", supportBundleName))
cmd.Stdout = &out
err = cmd.Run()
if err != nil {
t.Fatal(err)
}
defer func() {
err := os.Remove(tarPath)
if err != nil {
t.Fatal("Error removing file:", err)
}
}()
metadataJSON, err := readFileFromTar(tarPath, fmt.Sprintf("%s/metadata/cluster.json", supportBundleName))
require.NoError(t, err)
var result map[string]string
err = json.Unmarshal(metadataJSON, &result)
require.NoError(t, err)
assert.Equal(t, expectedData, result)
return ctx
}).Feature()
testenv.Test(t, feature)
}