Skip to content

Commit 73a2d88

Browse files
authored
fix: Store custom resources in JSON & YAML format (#1360)
fix: Store custom resources as JSON and YAML files
1 parent 461cc99 commit 73a2d88

File tree

4 files changed

+122
-29
lines changed

4 files changed

+122
-29
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ build:
8585
@echo "Build cli binaries"
8686
$(MAKE) -j support-bundle preflight analyze collect
8787

88+
mod-tidy:
89+
go mod tidy
90+
8891
.PHONY: support-bundle
8992
support-bundle:
9093
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
@@ -151,7 +154,7 @@ CONTROLLER_GEN=$(shell which controller-gen)
151154
.PHONY: client-gen
152155
client-gen:
153156
ifeq (, $(shell which client-gen 2>/dev/null))
154-
go install k8s.io/code-generator/cmd/client-gen@v0.26.1
157+
go install k8s.io/code-generator/cmd/client-gen@v0.28.2
155158
CLIENT_GEN=$(shell go env GOPATH)/bin/client-gen
156159
else
157160
CLIENT_GEN=$(shell which client-gen)

cmd/collect/cli/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func runCollect(v *viper.Viper, arg string) error {
5050

5151
collectorContent = spec
5252
} else if _, err = os.Stat(arg); err == nil {
53-
b, err := ioutil.ReadFile(arg)
53+
b, err := os.ReadFile(arg)
5454
if err != nil {
5555
return err
5656
}

pkg/collect/cluster_resources.go

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,15 +1235,26 @@ func crdsV1beta(ctx context.Context, config *rest.Config) ([]byte, []string) {
12351235
}
12361236

12371237
func crs(ctx context.Context, dyn dynamic.Interface, client *kubernetes.Clientset, config *rest.Config, namespaces []string) (map[string][]byte, map[string]string) {
1238+
errorList := make(map[string]string)
12381239
ok, err := discovery.HasResource(client, "apiextensions.k8s.io/v1", "CustomResourceDefinition")
12391240
if err != nil {
12401241
return nil, map[string]string{"discover apiextensions.k8s.io/v1": err.Error()}
12411242
}
12421243
if ok {
1243-
return crsV1(ctx, dyn, config, namespaces)
1244+
crdClient, err := apiextensionsv1clientset.NewForConfig(config)
1245+
if err != nil {
1246+
errorList["crdClient"] = err.Error()
1247+
return map[string][]byte{}, errorList
1248+
}
1249+
return crsV1(ctx, dyn, crdClient, namespaces)
12441250
}
12451251

1246-
return crsV1beta(ctx, dyn, config, namespaces)
1252+
crdClient, err := apiextensionsv1beta1clientset.NewForConfig(config)
1253+
if err != nil {
1254+
errorList["crdClient"] = err.Error()
1255+
return map[string][]byte{}, errorList
1256+
}
1257+
return crsV1beta(ctx, dyn, crdClient, namespaces)
12471258
}
12481259

12491260
// Selects the newest version by kube-aware priority.
@@ -1258,16 +1269,15 @@ func selectCRDVersionByPriority(versions []string) string {
12581269
return versions[len(versions)-1]
12591270
}
12601271

1261-
func crsV1(ctx context.Context, client dynamic.Interface, config *rest.Config, namespaces []string) (map[string][]byte, map[string]string) {
1272+
func crsV1(
1273+
ctx context.Context,
1274+
client dynamic.Interface,
1275+
crdClient apiextensionsv1clientset.ApiextensionsV1Interface,
1276+
namespaces []string,
1277+
) (map[string][]byte, map[string]string) {
12621278
customResources := make(map[string][]byte)
12631279
errorList := make(map[string]string)
12641280

1265-
crdClient, err := apiextensionsv1clientset.NewForConfig(config)
1266-
if err != nil {
1267-
errorList["crdClient"] = err.Error()
1268-
return customResources, errorList
1269-
}
1270-
12711281
crds, err := crdClient.CustomResourceDefinitions().List(ctx, metav1.ListOptions{})
12721282
if err != nil {
12731283
errorList["crdList"] = err.Error()
@@ -1319,12 +1329,11 @@ func crsV1(ctx context.Context, client dynamic.Interface, config *rest.Config, n
13191329
for _, item := range customResourceList.Items {
13201330
objects = append(objects, item.Object)
13211331
}
1322-
b, err := yaml.Marshal(objects)
1332+
err := storeCustomResource(crd.Name, objects, customResources)
13231333
if err != nil {
13241334
errorList[crd.Name] = err.Error()
13251335
continue
13261336
}
1327-
customResources[fmt.Sprintf("%s.yaml", crd.Name)] = b
13281337
} else {
13291338
// Group fetched resources by the namespace
13301339
perNamespace := map[string][]map[string]interface{}{}
@@ -1353,30 +1362,27 @@ func crsV1(ctx context.Context, client dynamic.Interface, config *rest.Config, n
13531362
}
13541363

13551364
namespacedName := fmt.Sprintf("%s/%s", crd.Name, ns)
1356-
b, err := yaml.Marshal(perNamespace[ns])
1365+
err := storeCustomResource(namespacedName, perNamespace[ns], customResources)
13571366
if err != nil {
13581367
errorList[namespacedName] = err.Error()
13591368
continue
13601369
}
1361-
1362-
customResources[fmt.Sprintf("%s.yaml", namespacedName)] = b
13631370
}
13641371
}
13651372
}
13661373

13671374
return customResources, errorList
13681375
}
13691376

1370-
func crsV1beta(ctx context.Context, client dynamic.Interface, config *rest.Config, namespaces []string) (map[string][]byte, map[string]string) {
1377+
func crsV1beta(
1378+
ctx context.Context,
1379+
client dynamic.Interface,
1380+
crdClient apiextensionsv1beta1clientset.ApiextensionsV1beta1Interface,
1381+
namespaces []string,
1382+
) (map[string][]byte, map[string]string) {
13711383
customResources := make(map[string][]byte)
13721384
errorList := make(map[string]string)
13731385

1374-
crdClient, err := apiextensionsv1beta1clientset.NewForConfig(config)
1375-
if err != nil {
1376-
errorList["crdClient"] = err.Error()
1377-
return customResources, errorList
1378-
}
1379-
13801386
crds, err := crdClient.CustomResourceDefinitions().List(ctx, metav1.ListOptions{})
13811387
if err != nil {
13821388
errorList["crdList"] = err.Error()
@@ -1430,12 +1436,13 @@ func crsV1beta(ctx context.Context, client dynamic.Interface, config *rest.Confi
14301436
for _, item := range customResourceList.Items {
14311437
objects = append(objects, item.Object)
14321438
}
1433-
b, err := yaml.Marshal(customResourceList.Items)
1439+
1440+
err = storeCustomResource(crd.Name, objects, customResources)
14341441
if err != nil {
14351442
errorList[crd.Name] = err.Error()
14361443
continue
14371444
}
1438-
customResources[fmt.Sprintf("%s.yaml", crd.Name)] = b
1445+
14391446
} else {
14401447
// Group fetched resources by the namespace
14411448
perNamespace := map[string][]map[string]interface{}{}
@@ -1464,13 +1471,11 @@ func crsV1beta(ctx context.Context, client dynamic.Interface, config *rest.Confi
14641471
}
14651472

14661473
namespacedName := fmt.Sprintf("%s/%s", crd.Name, ns)
1467-
b, err := yaml.Marshal(perNamespace[ns])
1474+
err := storeCustomResource(namespacedName, perNamespace[ns], customResources)
14681475
if err != nil {
14691476
errorList[namespacedName] = err.Error()
14701477
continue
14711478
}
1472-
1473-
customResources[fmt.Sprintf("%s.yaml", namespacedName)] = b
14741479
}
14751480
}
14761481
}
@@ -1630,7 +1635,7 @@ func getSelfSubjectRulesReviews(ctx context.Context, client *kubernetes.Clientse
16301635
continue
16311636
}
16321637

1633-
if response.Status.Incomplete == true {
1638+
if response.Status.Incomplete {
16341639
errorsByNamespace[namespace] = response.Status.EvaluationError
16351640
}
16361641

@@ -2106,3 +2111,23 @@ func configMaps(ctx context.Context, client kubernetes.Interface, namespaces []s
21062111

21072112
return configmapByNamespace, errorsByNamespace
21082113
}
2114+
2115+
// storeCustomResource stores a custom resource as JSON and YAML
2116+
// We use both formats for backwards compatibility. This way we
2117+
// avoid breaking existing tools and analysers that already rely on
2118+
// the YAML format.
2119+
func storeCustomResource(name string, objects any, m map[string][]byte) error {
2120+
j, err := json.MarshalIndent(objects, "", " ")
2121+
if err != nil {
2122+
return err
2123+
}
2124+
2125+
y, err := yaml.Marshal(objects)
2126+
if err != nil {
2127+
return err
2128+
}
2129+
2130+
m[fmt.Sprintf("%s.json", name)] = j
2131+
m[fmt.Sprintf("%s.yaml", name)] = y
2132+
return nil
2133+
}

pkg/collect/cluster_resources_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,29 @@ package collect
33
import (
44
"context"
55
"encoding/json"
6+
"os"
67
"reflect"
78
"testing"
89

910
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
11+
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
1012
"github.com/stretchr/testify/assert"
1113
"github.com/stretchr/testify/require"
1214
v1 "k8s.io/api/coordination/v1"
1315
corev1 "k8s.io/api/core/v1"
1416
storagev1 "k8s.io/api/storage/v1"
17+
apixfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
1518
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
testdynamicclient "k8s.io/client-go/dynamic/fake"
1620
"k8s.io/client-go/kubernetes"
1721
testclient "k8s.io/client-go/kubernetes/fake"
22+
"sigs.k8s.io/yaml"
1823
)
1924

25+
func init() {
26+
apixfake.AddToScheme(scheme.Scheme)
27+
}
28+
2029
func Test_ConfigMaps(t *testing.T) {
2130
tests := []struct {
2231
name string
@@ -458,3 +467,59 @@ func TestClusterResources_Merge(t *testing.T) {
458467
})
459468
}
460469
}
470+
471+
func TestCollectClusterResources_CustomResource(t *testing.T) {
472+
ctx := context.Background()
473+
474+
// Register supportbundle troubleshoot CRD
475+
dat, err := os.ReadFile("../../config/crds/troubleshoot.sh_supportbundles.yaml")
476+
require.NoError(t, err)
477+
478+
obj, _, err := scheme.Codecs.UniversalDeserializer().Decode(dat, nil, nil)
479+
require.NoError(t, err)
480+
apixClient := apixfake.NewSimpleClientset(obj)
481+
482+
// Create a CR
483+
sbObject := troubleshootv1beta2.SupportBundle{
484+
ObjectMeta: metav1.ObjectMeta{
485+
Name: "supportbundle",
486+
Namespace: "default",
487+
},
488+
TypeMeta: metav1.TypeMeta{
489+
Kind: "SupportBundle",
490+
APIVersion: "troubleshoot.sh/v1beta2",
491+
},
492+
Spec: troubleshootv1beta2.SupportBundleSpec{
493+
Collectors: []*troubleshootv1beta2.Collect{
494+
{
495+
ClusterResources: &troubleshootv1beta2.ClusterResources{},
496+
},
497+
},
498+
},
499+
}
500+
501+
dynamicClient := testdynamicclient.NewSimpleDynamicClient(scheme.Scheme, &sbObject)
502+
503+
// Fetch the CR from cluster
504+
res, errs := crsV1(ctx, dynamicClient, apixClient.ApiextensionsV1(), []string{"default"})
505+
assert.Empty(t, errs)
506+
require.Equal(t, 2, len(res))
507+
assert.Equal(t, fromJSON(t, res["supportbundles.troubleshoot.sh/default.json"]), sbObject)
508+
assert.Equal(t, fromYAML(t, res["supportbundles.troubleshoot.sh/default.yaml"]), sbObject)
509+
}
510+
511+
func fromYAML(t *testing.T, dat []byte) troubleshootv1beta2.SupportBundle {
512+
sb := []troubleshootv1beta2.SupportBundle{}
513+
err := yaml.Unmarshal(dat, &sb)
514+
require.NoError(t, err)
515+
require.Equal(t, 1, len(sb))
516+
return sb[0]
517+
}
518+
519+
func fromJSON(t *testing.T, dat []byte) troubleshootv1beta2.SupportBundle {
520+
sb := []troubleshootv1beta2.SupportBundle{}
521+
err := json.Unmarshal(dat, &sb)
522+
require.NoError(t, err)
523+
require.Equal(t, 1, len(sb))
524+
return sb[0]
525+
}

0 commit comments

Comments
 (0)