mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
chore: make spec loaders internal APIs (#1313)
* chore: make specs an internal package * Some minor improvements * Use LoadClusterSpecs in support bundle implementation * Remove change accidentally committed * Use LoadFromCLIArgs in preflight CLI implementation * Update comment * Fix edge case where the label selector is an empty string * Fix failing test
This commit is contained in:
51
internal/specs/configmaps.go
Normal file
51
internal/specs/configmaps.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func LoadFromConfigMap(ctx context.Context, client kubernetes.Interface, ns string, name string, key string) ([]byte, error) {
|
||||
foundConfigMap, err := client.CoreV1().ConfigMaps(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get configmap")
|
||||
}
|
||||
|
||||
spec, ok := foundConfigMap.Data[key]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("spec not found in configmap %s", name)
|
||||
}
|
||||
|
||||
klog.V(1).InfoS("Loaded spec from config map", "name",
|
||||
foundConfigMap.Name, "namespace", foundConfigMap.Namespace, "data key", key,
|
||||
)
|
||||
|
||||
return []byte(spec), nil
|
||||
}
|
||||
|
||||
func LoadFromConfigMapMatchingLabel(ctx context.Context, client kubernetes.Interface, label string, ns string, key string) ([]string, error) {
|
||||
var configMapMatchingKey []string
|
||||
|
||||
configMaps, err := client.CoreV1().ConfigMaps(ns).List(ctx, metav1.ListOptions{LabelSelector: label})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to search for configmaps in the cluster")
|
||||
}
|
||||
|
||||
for _, configMap := range configMaps.Items {
|
||||
spec, ok := configMap.Data[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
klog.V(1).InfoS("Loaded spec from config map", "name", configMap.Name,
|
||||
"namespace", configMap.Namespace, "data key", key, "label selector", label,
|
||||
)
|
||||
configMapMatchingKey = append(configMapMatchingKey, string(spec))
|
||||
}
|
||||
|
||||
return configMapMatchingKey, nil
|
||||
}
|
||||
561
internal/specs/configmaps_test.go
Normal file
561
internal/specs/configmaps_test.go
Normal file
@@ -0,0 +1,561 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
testclient "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func Test_LoadFromConfigMapMatchingLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
supportBundleConfigMaps []corev1.ConfigMap
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "support bundle configmap with matching label and key",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- runPod:
|
||||
name: "run-ping"
|
||||
namespace: default
|
||||
podSpec:
|
||||
containers:
|
||||
- name: run-ping
|
||||
image: busybox:1
|
||||
command: ["ping"]
|
||||
args: ["-w", "5", "www.google.com"]`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- runPod:
|
||||
name: "run-ping"
|
||||
namespace: default
|
||||
podSpec:
|
||||
containers:
|
||||
- name: run-ping
|
||||
image: busybox:1
|
||||
command: ["ping"]
|
||||
args: ["-w", "5", "www.google.com"]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mutlidoc support bundle secret with matching label and key",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- runPod:
|
||||
name: "run-ping"
|
||||
namespace: default
|
||||
podSpec:
|
||||
containers:
|
||||
- name: run-ping
|
||||
image: busybox:1
|
||||
command: ["ping"]
|
||||
args: ["-w", "5", "www.google.com"]
|
||||
---
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: Usernames
|
||||
spec:
|
||||
redactors:
|
||||
- name: Redact usernames in multiline JSON
|
||||
removals:
|
||||
regex:
|
||||
- selector: '(?i)"name": *".*user[^\"]*"'
|
||||
redactor: '(?i)("value": *")(?P<mask>.*[^\"]*)(")'`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- runPod:
|
||||
name: "run-ping"
|
||||
namespace: default
|
||||
podSpec:
|
||||
containers:
|
||||
- name: run-ping
|
||||
image: busybox:1
|
||||
command: ["ping"]
|
||||
args: ["-w", "5", "www.google.com"]
|
||||
---
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: Usernames
|
||||
spec:
|
||||
redactors:
|
||||
- name: Redact usernames in multiline JSON
|
||||
removals:
|
||||
regex:
|
||||
- selector: '(?i)"name": *".*user[^\"]*"'
|
||||
redactor: '(?i)("value": *")(?P<mask>.*[^\"]*)(")'`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "support bundle configmap with missing label",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configap",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string(nil),
|
||||
},
|
||||
{
|
||||
name: "support bundle configmap with matching label but wrong key",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-specc": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string(nil),
|
||||
},
|
||||
{
|
||||
name: "multiple support bundle configmaps in the same namespace with matching label and key",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap-2",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`,
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple support bundle configmaps in different namespaces with matching label and key",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "some-namespace",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap-2",
|
||||
Namespace: "some-namespace-2",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`,
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple support bundle configmaps in different namespaces but only one with correct label and key",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "some-namespace",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle-wrong",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec-wrong": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap-2",
|
||||
Namespace: "some-namespace-2",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
for _, configmap := range tt.supportBundleConfigMaps {
|
||||
_, err := client.CoreV1().ConfigMaps(configmap.Namespace).Create(ctx, &configmap, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromConfigMapMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "", "support-bundle-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserProvidedNamespace_LoadFromConfigMapMatchingLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
supportBundleConfigMaps []corev1.ConfigMap
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "support bundle configmap with matching label and key in user provided namespace",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "some-namespace",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "support bundle configmap with matching label and key outside of user provided namespace",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "not-your-namespace",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"support-bundle-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string(nil),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
for _, configmap := range tt.supportBundleConfigMaps {
|
||||
_, err := client.CoreV1().ConfigMaps(configmap.Namespace).Create(ctx, &configmap, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromConfigMapMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "some-namespace", "support-bundle-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedactors_LoadFromConfigMapMatchingLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
supportBundleConfigMaps []corev1.ConfigMap
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "redactor configmap with matching label and key",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"redactor-spec": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: redact-some-content
|
||||
spec:
|
||||
redactors:
|
||||
- name: replace some-content
|
||||
fileSelector:
|
||||
file: result.json
|
||||
removals:
|
||||
values:
|
||||
- some-content`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: redact-some-content
|
||||
spec:
|
||||
redactors:
|
||||
- name: replace some-content
|
||||
fileSelector:
|
||||
file: result.json
|
||||
removals:
|
||||
values:
|
||||
- some-content`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "redactor configmap with matching label but wrong key",
|
||||
supportBundleConfigMaps: []corev1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"redactor-spec-wrong": `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: redact-some-content
|
||||
spec:
|
||||
redactors:
|
||||
- name: replace some-content
|
||||
fileSelector:
|
||||
file: result.json
|
||||
removals:
|
||||
values:
|
||||
- some-content`,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string(nil),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
for _, configmap := range tt.supportBundleConfigMaps {
|
||||
_, err := client.CoreV1().ConfigMaps(configmap.Namespace).Create(ctx, &configmap, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromConfigMapMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "", "redactor-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
50
internal/specs/secrets.go
Normal file
50
internal/specs/secrets.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func LoadFromSecret(ctx context.Context, client kubernetes.Interface, ns string, name string, key string) ([]byte, error) {
|
||||
foundSecret, err := client.CoreV1().Secrets(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get secret")
|
||||
}
|
||||
|
||||
spec, ok := foundSecret.Data[key]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("spec not found in secret %s", name)
|
||||
}
|
||||
|
||||
klog.V(1).InfoS("Loaded spec from secret", "name",
|
||||
foundSecret.Name, "namespace", foundSecret.Namespace, "data key", key,
|
||||
)
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func LoadFromSecretMatchingLabel(ctx context.Context, client kubernetes.Interface, label string, ns string, key string) ([]string, error) {
|
||||
var secretsMatchingKey []string
|
||||
|
||||
secrets, err := client.CoreV1().Secrets(ns).List(ctx, metav1.ListOptions{LabelSelector: label})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to search for secrets in the cluster")
|
||||
}
|
||||
|
||||
for _, secret := range secrets.Items {
|
||||
spec, ok := secret.Data[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
klog.V(1).InfoS("Loaded spec from secret", "name", secret.Name,
|
||||
"namespace", secret.Namespace, "data key", key, "label selector", label,
|
||||
)
|
||||
secretsMatchingKey = append(secretsMatchingKey, string(spec))
|
||||
}
|
||||
|
||||
return secretsMatchingKey, nil
|
||||
}
|
||||
561
internal/specs/secrets_test.go
Normal file
561
internal/specs/secrets_test.go
Normal file
@@ -0,0 +1,561 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
testclient "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func Test_LoadFromSecretMatchingLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
supportBundleSecrets []corev1.Secret
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "support bundle secret with matching label and key",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- runPod:
|
||||
name: "run-ping"
|
||||
namespace: default
|
||||
podSpec:
|
||||
containers:
|
||||
- name: run-ping
|
||||
image: busybox:1
|
||||
command: ["ping"]
|
||||
args: ["-w", "5", "www.google.com"]`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- runPod:
|
||||
name: "run-ping"
|
||||
namespace: default
|
||||
podSpec:
|
||||
containers:
|
||||
- name: run-ping
|
||||
image: busybox:1
|
||||
command: ["ping"]
|
||||
args: ["-w", "5", "www.google.com"]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mutlidoc support bundle secret with matching label and key",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- runPod:
|
||||
name: "run-ping"
|
||||
namespace: default
|
||||
podSpec:
|
||||
containers:
|
||||
- name: run-ping
|
||||
image: busybox:1
|
||||
command: ["ping"]
|
||||
args: ["-w", "5", "www.google.com"]
|
||||
---
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: Usernames
|
||||
spec:
|
||||
redactors:
|
||||
- name: Redact usernames in multiline JSON
|
||||
removals:
|
||||
regex:
|
||||
- selector: '(?i)"name": *".*user[^\"]*"'
|
||||
redactor: '(?i)("value": *")(?P<mask>.*[^\"]*)(")'`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- runPod:
|
||||
name: "run-ping"
|
||||
namespace: default
|
||||
podSpec:
|
||||
containers:
|
||||
- name: run-ping
|
||||
image: busybox:1
|
||||
command: ["ping"]
|
||||
args: ["-w", "5", "www.google.com"]
|
||||
---
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: Usernames
|
||||
spec:
|
||||
redactors:
|
||||
- name: Redact usernames in multiline JSON
|
||||
removals:
|
||||
regex:
|
||||
- selector: '(?i)"name": *".*user[^\"]*"'
|
||||
redactor: '(?i)("value": *")(?P<mask>.*[^\"]*)(")'`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "support bundle secret with missing label",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string(nil),
|
||||
},
|
||||
{
|
||||
name: "support bundle secret with matching label but wrong key",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-specc": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string(nil),
|
||||
},
|
||||
{
|
||||
name: "multiple support bundle secrets in the same namespace with matching label and key",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret-2",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`,
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple support bundle secrets in different namespaces with matching label and key",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "some-namespace",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret-2",
|
||||
Namespace: "some-namespace-2",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`,
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple support bundle secrets in different namespaces but only one with correct label and key",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "some-namespace",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle-wrong",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec-wrong": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-info
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret-2",
|
||||
Namespace: "some-namespace-2",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: cluster-resources
|
||||
spec:
|
||||
collectors:
|
||||
- clusterResources: {}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
for _, secret := range tt.supportBundleSecrets {
|
||||
_, err := client.CoreV1().Secrets(secret.Namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromSecretMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "", "support-bundle-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserProvidedNamespace_LoadFromSecretMatchingLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
supportBundleSecrets []corev1.Secret
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "support bundle secret with matching label and key in user provided namespace",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "some-namespace",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "support bundle secret with matching label and key outside of user provided namespace",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "not-your-namespace",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"support-bundle-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: static/data.txt
|
||||
data: |
|
||||
static data`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string(nil),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
for _, secret := range tt.supportBundleSecrets {
|
||||
_, err := client.CoreV1().Secrets(secret.Namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromSecretMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "some-namespace", "support-bundle-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedactors_LoadFromSecretMatchingLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
supportBundleSecrets []corev1.Secret
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "redactor secret with matching label and key",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"redactor-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: redact-some-content
|
||||
spec:
|
||||
redactors:
|
||||
- name: replace some-content
|
||||
fileSelector:
|
||||
file: result.json
|
||||
removals:
|
||||
values:
|
||||
- some-content`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: redact-some-content
|
||||
spec:
|
||||
redactors:
|
||||
- name: replace some-content
|
||||
fileSelector:
|
||||
file: result.json
|
||||
removals:
|
||||
values:
|
||||
- some-content`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "redactor secret with matching label but wrong key",
|
||||
supportBundleSecrets: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"redactor-spec-wrong": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: redact-some-content
|
||||
spec:
|
||||
redactors:
|
||||
- name: replace some-content
|
||||
fileSelector:
|
||||
file: result.json
|
||||
removals:
|
||||
values:
|
||||
- some-content`),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []string(nil),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
for _, secret := range tt.supportBundleSecrets {
|
||||
_, err := client.CoreV1().Secrets(secret.Namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromSecretMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "", "redactor-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
259
internal/specs/specs.go
Normal file
259
internal/specs/specs.go
Normal file
@@ -0,0 +1,259 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/internal/util"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/loader"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/oci"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/types"
|
||||
"github.com/spf13/viper"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// SplitTroubleshootSecretLabelSelector splits a label selector into two selectors, if applicable:
|
||||
// 1. troubleshoot.io/kind=support-bundle and non-troubleshoot (if contains) labels selector.
|
||||
// 2. troubleshoot.sh/kind=support-bundle and non-troubleshoot (if contains) labels selector.
|
||||
func SplitTroubleshootSecretLabelSelector(ctx context.Context, labelSelector labels.Selector) ([]string, error) {
|
||||
|
||||
klog.V(1).Infof("Split %q selector into troubleshoot and non-troubleshoot labels selector separately, if applicable", labelSelector.String())
|
||||
|
||||
selectorRequirements, selectorSelectable := labelSelector.Requirements()
|
||||
if !selectorSelectable {
|
||||
return nil, errors.Errorf("Selector %q is not selectable", labelSelector.String())
|
||||
}
|
||||
|
||||
var troubleshootReqs, otherReqs []labels.Requirement
|
||||
|
||||
for _, req := range selectorRequirements {
|
||||
if req.Key() == constants.TroubleshootIOLabelKey || req.Key() == constants.TroubleshootSHLabelKey {
|
||||
troubleshootReqs = append(troubleshootReqs, req)
|
||||
} else {
|
||||
otherReqs = append(otherReqs, req)
|
||||
}
|
||||
}
|
||||
|
||||
parsedSelectorStrings := make([]string, 0)
|
||||
// Combine each troubleshoot requirement with other requirements to form new selectors
|
||||
s := labelSelector.String()
|
||||
if len(troubleshootReqs) == 0 && s != "" {
|
||||
return []string{s}, nil
|
||||
}
|
||||
|
||||
for _, tReq := range troubleshootReqs {
|
||||
reqs := append(otherReqs, tReq)
|
||||
newSelector := labels.NewSelector().Add(reqs...)
|
||||
parsedSelectorStrings = append(parsedSelectorStrings, newSelector.String())
|
||||
}
|
||||
|
||||
return parsedSelectorStrings, nil
|
||||
}
|
||||
|
||||
func LoadFromCLIArgs(ctx context.Context, client kubernetes.Interface, args []string, vp *viper.Viper) (*loader.TroubleshootKinds, error) {
|
||||
rawSpecs := []string{}
|
||||
|
||||
for _, v := range args {
|
||||
if strings.HasPrefix(v, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(v, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Errorf("path %s must have 3 components", v))
|
||||
}
|
||||
|
||||
spec, err := LoadFromSecret(ctx, client, pathParts[1], pathParts[2], "preflight-spec")
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Wrap(err, "failed to get spec from secret"))
|
||||
}
|
||||
|
||||
rawSpecs = append(rawSpecs, string(spec))
|
||||
} else if _, err := os.Stat(v); err == nil {
|
||||
b, err := os.ReadFile(v)
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
|
||||
rawSpecs = append(rawSpecs, string(b))
|
||||
} else if v == "-" {
|
||||
b, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, err)
|
||||
}
|
||||
rawSpecs = append(rawSpecs, string(b))
|
||||
} else {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
|
||||
if u.Scheme == "oci" {
|
||||
content, err := oci.PullPreflightFromOCI(v)
|
||||
if err != nil {
|
||||
if err == oci.ErrNoRelease {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Errorf("no release found for %s.\nCheck the oci:// uri for errors or contact the application vendor for support.", v))
|
||||
}
|
||||
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
|
||||
rawSpecs = append(rawSpecs, string(content))
|
||||
} else {
|
||||
if !util.IsURL(v) {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, fmt.Errorf("%s is not a URL and was not found (err %s)", v, err))
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", v, nil)
|
||||
if err != nil {
|
||||
// exit code: should this be catch all or spec issues...?
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, err)
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta2")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
// exit code: should this be catch all or spec issues...?
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
|
||||
rawSpecs = append(rawSpecs, string(body))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kinds, err := loader.LoadSpecs(ctx, loader.LoadOptions{
|
||||
RawSpecs: rawSpecs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vp.GetBool("load-cluster-specs") {
|
||||
clusterKinds, err := LoadFromCluster(ctx, client, vp.GetStringSlice("selector"), vp.GetString("namespace"))
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
|
||||
kinds.Add(clusterKinds)
|
||||
}
|
||||
|
||||
return kinds, nil
|
||||
}
|
||||
|
||||
// LoadFromCluster loads troubleshoot specs from the cluster based on the provided labels.
|
||||
// By default this will be troubleshoot.io/kind=support-bundle and troubleshoot.sh/kind=support-bundle
|
||||
// labels. We search for secrets and configmaps with the label selector and extract the raw data.
|
||||
// We then load the specs from the raw data. If the user does not have sufficient permissions
|
||||
// to list & read secrets and configmaps from all namespaces, we will fallback to trying each
|
||||
// namespace individually, and eventually default to the configured kubeconfig namespace.
|
||||
func LoadFromCluster(ctx context.Context, client kubernetes.Interface, selectors []string, ns string) (*loader.TroubleshootKinds, error) {
|
||||
if reflect.DeepEqual(selectors, []string{"troubleshoot.sh/kind=support-bundle"}) {
|
||||
// Its the default selector so we append troubleshoot.io/kind=support-bundle to it due to backwards compatibility
|
||||
selectors = append(selectors, "troubleshoot.io/kind=support-bundle")
|
||||
}
|
||||
|
||||
labelSelector := strings.Join(selectors, ",")
|
||||
|
||||
parsedSelector, err := labels.Parse(labelSelector)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse selector")
|
||||
}
|
||||
|
||||
// List of namespaces we want to search for secrets and configmaps with support bundle specs
|
||||
namespaces := []string{}
|
||||
if ns != "" {
|
||||
// Just progress with the namespace provided
|
||||
namespaces = []string{ns}
|
||||
} else {
|
||||
// Check if I can read secrets and configmaps in all namespaces
|
||||
ican, err := k8sutil.CanIListAndGetAllSecretsAndConfigMaps(ctx, client)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to check if I can read secrets and configmaps")
|
||||
}
|
||||
klog.V(1).Infof("Can I read any secrets and configmaps: %v", ican)
|
||||
|
||||
if ican {
|
||||
// I can read secrets and configmaps in all namespaces
|
||||
// No need to iterate over all namespaces
|
||||
namespaces = []string{""}
|
||||
} else {
|
||||
// Get list of all namespaces and try to find specs from each namespace
|
||||
nsList, err := client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
if k8serrors.IsForbidden(err) {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
ns, _, err := kubeconfig.Namespace()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get namespace from kubeconfig")
|
||||
}
|
||||
// If we are not allowed to list namespaces, just use the default namespace
|
||||
// configured in the kubeconfig
|
||||
namespaces = []string{ns}
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "failed to list namespaces")
|
||||
}
|
||||
}
|
||||
|
||||
for _, ns := range nsList.Items {
|
||||
namespaces = append(namespaces, ns.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rawSpecs []string
|
||||
|
||||
parsedSelectorStrings, err := SplitTroubleshootSecretLabelSelector(ctx, parsedSelector)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to parse troubleshoot labels selector %s", err)
|
||||
}
|
||||
|
||||
// Iteratively search for troubleshoot specs in all namespaces using the given selectors
|
||||
for _, parsedSelectorString := range parsedSelectorStrings {
|
||||
klog.V(1).Infof("Search specs from [%q] namespace using %q selector", strings.Join(namespaces, ", "), parsedSelectorString)
|
||||
for _, ns := range namespaces {
|
||||
for _, key := range []string{constants.SupportBundleKey, constants.PreflightKey, constants.RedactorKey} {
|
||||
specs, err := LoadFromSecretMatchingLabel(ctx, client, parsedSelectorString, ns, key)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
rawSpecs = append(rawSpecs, specs...)
|
||||
|
||||
specs, err = LoadFromConfigMapMatchingLabel(ctx, client, parsedSelectorString, ns, key)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
rawSpecs = append(rawSpecs, specs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load troubleshoot specs from the raw specs
|
||||
return loader.LoadSpecs(ctx, loader.LoadOptions{
|
||||
RawSpecs: rawSpecs,
|
||||
})
|
||||
}
|
||||
203
internal/specs/specs_test.go
Normal file
203
internal/specs/specs_test.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/replicatedhq/troubleshoot/internal/testutils"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/loader"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
testclient "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func Test_SplitTroubleshootSecretLabelSelector(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
selectorString string
|
||||
expectedSelectors []string
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Split both troubleshoot and non-troubleshoot labels",
|
||||
selectorString: "troubleshoot.io/kind=support-bundle,troubleshoot.sh/kind=support-bundle,a=b",
|
||||
expectedSelectors: []string{
|
||||
"a=b,troubleshoot.io/kind=support-bundle",
|
||||
"a=b,troubleshoot.sh/kind=support-bundle",
|
||||
},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Split only troubleshoot.io label",
|
||||
selectorString: "troubleshoot.io/kind=support-bundle",
|
||||
expectedSelectors: []string{"troubleshoot.io/kind=support-bundle"},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Split only troubleshoot.sh label",
|
||||
selectorString: "troubleshoot.sh/kind=support-bundle",
|
||||
expectedSelectors: []string{"troubleshoot.sh/kind=support-bundle"},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Split only non-troubleshoot label",
|
||||
selectorString: "a=b",
|
||||
expectedSelectors: []string{"a=b"},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "No selector labels to split",
|
||||
selectorString: "",
|
||||
expectedSelectors: []string{},
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
selector, err := labels.Parse(tt.selectorString)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing selector string: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
gotSelectors, err := SplitTroubleshootSecretLabelSelector(context.TODO(), selector)
|
||||
if (err != nil) != tt.expectedError {
|
||||
t.Errorf("Expected error: %v, got: %v", tt.expectedError, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, tt.expectedSelectors, gotSelectors)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFromCluster(t *testing.T) {
|
||||
theRedactor := troubleshootv1beta2.Redactor{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "Redactor",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "redact-some-content",
|
||||
},
|
||||
Spec: troubleshootv1beta2.RedactorSpec{
|
||||
Redactors: []*troubleshootv1beta2.Redact{
|
||||
{
|
||||
Name: "redact-text-1",
|
||||
Removals: troubleshootv1beta2.Removals{
|
||||
Values: []string{"TEXT"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
selectors []string
|
||||
namespace string
|
||||
objects []runtime.Object
|
||||
want *loader.TroubleshootKinds
|
||||
}{
|
||||
{
|
||||
name: "no selectors",
|
||||
want: loader.NewTroubleshootKinds(),
|
||||
},
|
||||
{
|
||||
name: "spec in secret and default label selector",
|
||||
namespace: "bigbank",
|
||||
selectors: []string{
|
||||
"troubleshoot.sh/kind=support-bundle",
|
||||
},
|
||||
objects: []runtime.Object{
|
||||
secretObject("bigbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
},
|
||||
want: &loader.TroubleshootKinds{
|
||||
RedactorsV1Beta2: []troubleshootv1beta2.Redactor{theRedactor},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "spec in secret and no selector argument passed",
|
||||
namespace: "bigbank",
|
||||
objects: []runtime.Object{
|
||||
secretObject("bigbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
},
|
||||
want: loader.NewTroubleshootKinds(),
|
||||
},
|
||||
{
|
||||
name: "multiple specs default selector",
|
||||
namespace: "bigbank",
|
||||
selectors: []string{
|
||||
"troubleshoot.sh/kind=support-bundle",
|
||||
},
|
||||
objects: []runtime.Object{
|
||||
secretObject("bigbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
secretObject("bigbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
},
|
||||
want: &loader.TroubleshootKinds{
|
||||
RedactorsV1Beta2: []troubleshootv1beta2.Redactor{theRedactor, theRedactor},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "spec in secret but different namespace",
|
||||
namespace: "bigbank",
|
||||
objects: []runtime.Object{
|
||||
secretObject("anotherbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
},
|
||||
want: loader.NewTroubleshootKinds(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset(tt.objects...)
|
||||
got, err := LoadFromCluster(ctx, client, tt.selectors, tt.namespace)
|
||||
require.NoError(t, err)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("got = %v, want %v", testutils.AsJSON(t, got), testutils.AsJSON(t, tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func secretObject(ns string, selectors map[string]string) runtime.Object {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("secret-name-%s", uuid.New().String()),
|
||||
Namespace: ns,
|
||||
Labels: selectors,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"redactor-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: redact-some-content
|
||||
spec:
|
||||
redactors:
|
||||
- name: redact-text-1
|
||||
removals:
|
||||
values:
|
||||
- TEXT`),
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user