mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
Refactor in-clusters collectors to use struct per collector (#670)
refactor in-clusters collectors to use struct per collector
This commit is contained in:
@@ -32,10 +32,10 @@ func uploadResults(uri string, analyzeResults []*analyzerunner.AnalyzeResult) er
|
||||
return upload(uri, uploadPreflightResults)
|
||||
}
|
||||
|
||||
func uploadErrors(uri string, collectors collect.Collectors) error {
|
||||
func uploadErrors(uri string, collectors []collect.Collector) error {
|
||||
errors := []*preflight.UploadPreflightError{}
|
||||
for _, collector := range collectors {
|
||||
for _, e := range collector.RBACErrors {
|
||||
for _, e := range collector.GetRBACErrors() {
|
||||
errors = append(errors, &preflight.UploadPreflightError{
|
||||
Error: e.Error(),
|
||||
})
|
||||
|
||||
@@ -295,7 +295,7 @@ the %s Admin Console to begin analysis.`
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", response.ArchivePath)
|
||||
fmt.Printf("\n%s\n", response.ArchivePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -105,23 +106,41 @@ var CephCommands = []CephCommand{
|
||||
},
|
||||
}
|
||||
|
||||
func Ceph(c *Collector, cephCollector *troubleshootv1beta2.Ceph) (CollectorResult, error) {
|
||||
type CollectCeph struct {
|
||||
Collector *troubleshootv1beta2.Ceph
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCeph) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Cluster Info")
|
||||
}
|
||||
|
||||
func (c *CollectCeph) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectCeph) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
if cephCollector.Namespace == "" {
|
||||
cephCollector.Namespace = DefaultCephNamespace
|
||||
if c.Namespace == "" {
|
||||
c.Namespace = DefaultCephNamespace
|
||||
}
|
||||
|
||||
pod, err := findRookCephToolsPod(ctx, c, cephCollector.Namespace)
|
||||
pod, err := findRookCephToolsPod(ctx, c, c.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output := NewResult()
|
||||
for _, command := range CephCommands {
|
||||
err := cephCommandExec(ctx, c, cephCollector, pod, command, output)
|
||||
err := cephCommandExec(ctx, progressChan, c, c.Collector, pod, command, output)
|
||||
if err != nil {
|
||||
pathPrefix := GetCephCollectorFilepath(cephCollector.CollectorName, cephCollector.Namespace)
|
||||
pathPrefix := GetCephCollectorFilepath(c.Collector.CollectorName, c.Namespace)
|
||||
dstFileName := path.Join(pathPrefix, fmt.Sprintf("%s.%s-error", command.ID, command.Format))
|
||||
output.SaveResult(c.BundlePath, dstFileName, strings.NewReader(err.Error()))
|
||||
}
|
||||
@@ -130,20 +149,24 @@ func Ceph(c *Collector, cephCollector *troubleshootv1beta2.Ceph) (CollectorResul
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func cephCommandExec(ctx context.Context, c *Collector, cephCollector *troubleshootv1beta2.Ceph, pod *corev1.Pod, command CephCommand, output CollectorResult) error {
|
||||
func cephCommandExec(ctx context.Context, progressChan chan<- interface{}, c *CollectCeph, cephCollector *troubleshootv1beta2.Ceph, pod *corev1.Pod, command CephCommand, output CollectorResult) error {
|
||||
timeout := cephCollector.Timeout
|
||||
if timeout == "" {
|
||||
timeout = command.DefaultTimeout
|
||||
}
|
||||
|
||||
execCollector := &troubleshootv1beta2.Exec{
|
||||
execSpec := &troubleshootv1beta2.Exec{
|
||||
Selector: labelsToSelector(pod.Labels),
|
||||
Namespace: pod.Namespace,
|
||||
Command: command.Command,
|
||||
Args: command.Args,
|
||||
Timeout: timeout,
|
||||
}
|
||||
results, err := Exec(c, execCollector)
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
execCollector := &CollectExec{execSpec, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.ctx, rbacErrors}
|
||||
|
||||
results, err := execCollector.Collect(progressChan)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to exec command")
|
||||
}
|
||||
@@ -171,7 +194,7 @@ func cephCommandExec(ctx context.Context, c *Collector, cephCollector *troublesh
|
||||
return nil
|
||||
}
|
||||
|
||||
func findRookCephToolsPod(ctx context.Context, c *Collector, namespace string) (*corev1.Pod, error) {
|
||||
func findRookCephToolsPod(ctx context.Context, c *CollectCeph, namespace string) (*corev1.Pod, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create kubernetes client")
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type ClusterVersion struct {
|
||||
@@ -15,7 +17,23 @@ type ClusterVersion struct {
|
||||
String string `json:"string"`
|
||||
}
|
||||
|
||||
func ClusterInfo(c *Collector) (CollectorResult, error) {
|
||||
type CollectClusterInfo struct {
|
||||
Collector *troubleshootv1beta2.ClusterInfo
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectClusterInfo) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Cluster Info")
|
||||
}
|
||||
|
||||
func (c *CollectClusterInfo) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectClusterInfo) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to create kubernetes clientset")
|
||||
|
||||
@@ -34,7 +34,28 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil/discovery"
|
||||
)
|
||||
|
||||
func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1beta2.ClusterResources) (CollectorResult, error) {
|
||||
type CollectClusterResources struct {
|
||||
Collector *troubleshootv1beta2.ClusterResources
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Cluster Resources")
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) Merge(allCollectors []Collector) ([]Collector, error) {
|
||||
result := append(allCollectors, c)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -51,9 +72,9 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
|
||||
// namespaces
|
||||
nsListedFromCluster := false
|
||||
var namespaceNames []string
|
||||
if len(clusterResourcesCollector.Namespaces) > 0 {
|
||||
namespaces, namespaceErrors := getNamespaces(ctx, client, clusterResourcesCollector.Namespaces)
|
||||
namespaceNames = clusterResourcesCollector.Namespaces
|
||||
if len(c.Collector.Namespaces) > 0 {
|
||||
namespaces, namespaceErrors := getNamespaces(ctx, client, c.Collector.Namespaces)
|
||||
namespaceNames = c.Collector.Namespaces
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/namespaces.json", bytes.NewBuffer(namespaces))
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/namespaces-errors.json", marshalErrors(namespaceErrors))
|
||||
} else if c.Namespace != "" {
|
||||
@@ -82,7 +103,7 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
|
||||
}
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/auth-cani-list-errors.json", marshalErrors(reviewStatusErrors))
|
||||
|
||||
if nsListedFromCluster && !clusterResourcesCollector.IgnoreRBAC {
|
||||
if nsListedFromCluster && !c.Collector.IgnoreRBAC {
|
||||
filteredNamespaces := []string{}
|
||||
for _, ns := range namespaceNames {
|
||||
status := reviewStatuses[ns]
|
||||
|
||||
@@ -5,19 +5,41 @@ import (
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Collectd(ctx context.Context, c *Collector, collector *troubleshootv1beta2.Collectd, namespace string, clientConfig *restclient.Config, client kubernetes.Interface) (CollectorResult, error) {
|
||||
copyFromHost := &troubleshootv1beta2.CopyFromHost{
|
||||
CollectorMeta: collector.CollectorMeta,
|
||||
Name: "collectd/rrd",
|
||||
Namespace: collector.Namespace,
|
||||
Image: collector.Image,
|
||||
ImagePullPolicy: collector.ImagePullPolicy,
|
||||
ImagePullSecret: collector.ImagePullSecret,
|
||||
Timeout: collector.Timeout,
|
||||
HostPath: collector.HostPath,
|
||||
}
|
||||
return CopyFromHost(ctx, c, copyFromHost, namespace, clientConfig, client)
|
||||
type CollectCollectd struct {
|
||||
Collector *troubleshootv1beta2.Collectd
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCollectd) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "CollectD")
|
||||
}
|
||||
|
||||
func (c *CollectCollectd) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectCollectd) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
copyFromHost := &troubleshootv1beta2.CopyFromHost{
|
||||
CollectorMeta: c.Collector.CollectorMeta,
|
||||
Name: "collectd/rrd",
|
||||
Namespace: c.Collector.Namespace,
|
||||
Image: c.Collector.Image,
|
||||
ImagePullPolicy: c.Collector.ImagePullPolicy,
|
||||
ImagePullSecret: c.Collector.ImagePullSecret,
|
||||
Timeout: c.Collector.Timeout,
|
||||
HostPath: c.Collector.HostPath,
|
||||
}
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
copyFromHostCollector := &CollectCopyFromHost{copyFromHost, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.ctx, rbacErrors}
|
||||
|
||||
return copyFromHostCollector.Collect(progressChan)
|
||||
}
|
||||
|
||||
@@ -2,29 +2,31 @@ package collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/multitype"
|
||||
authorizationv1 "k8s.io/api/authorization/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type Collector struct {
|
||||
Collect *troubleshootv1beta2.Collect
|
||||
Redact bool
|
||||
RBACErrors []error
|
||||
ClientConfig *rest.Config
|
||||
Namespace string
|
||||
BundlePath string
|
||||
type Collector interface {
|
||||
Title() string
|
||||
IsExcluded() (bool, error)
|
||||
GetRBACErrors() []error
|
||||
HasRBACErrors() bool
|
||||
CheckRBAC(ctx context.Context, c Collector, collector *troubleshootv1beta2.Collect, clientConfig *rest.Config, namespace string) error
|
||||
Collect(progressChan chan<- interface{}) (CollectorResult, error)
|
||||
}
|
||||
|
||||
type Collectors []*Collector
|
||||
type MergeableCollector interface {
|
||||
Collector
|
||||
Merge(allCollectors []Collector) ([]Collector, error)
|
||||
}
|
||||
|
||||
//type Collectors []*Collector
|
||||
|
||||
func isExcluded(excludeVal *multitype.BoolOrString) (bool, error) {
|
||||
if excludeVal == nil {
|
||||
@@ -47,305 +49,61 @@ func isExcluded(excludeVal *multitype.BoolOrString) (bool, error) {
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// checks if a given collector has a spec with 'exclude' that evaluates to true.
|
||||
func (c *Collector) IsExcluded() bool {
|
||||
if c.Collect.ClusterInfo != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.ClusterInfo.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.ClusterResources != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.ClusterResources.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Secret != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Secret.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.ConfigMap != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.ConfigMap.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Logs != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Logs.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Run != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Run.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.RunPod != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.RunPod.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Exec != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Exec.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Data != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Data.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Copy != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Copy.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.CopyFromHost != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.CopyFromHost.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.HTTP != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.HTTP.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Postgres != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Postgres.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Mysql != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Mysql.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Redis != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Redis.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Collectd != nil {
|
||||
// TODO: see if redaction breaks these
|
||||
isExcludedResult, err := isExcluded(c.Collect.Collectd.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Ceph != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Ceph.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Longhorn != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Longhorn.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Sysctl != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Sysctl.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Collector) RunCollectorSync(clientConfig *rest.Config, client kubernetes.Interface, globalRedactors []*troubleshootv1beta2.Redact) (result CollectorResult, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
_, file, line, _ := runtime.Caller(4)
|
||||
err = errors.Errorf("recovered from panic at \"%s:%d\": %v", file, line, r)
|
||||
}
|
||||
}()
|
||||
|
||||
if c.IsExcluded() {
|
||||
return
|
||||
}
|
||||
func GetCollector(collector *troubleshootv1beta2.Collect, bundlePath string, namespace string, clientConfig *rest.Config, client kubernetes.Interface, sinceTime *time.Time) (interface{}, bool) {
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
if c.Collect.ClusterInfo != nil {
|
||||
result, err = ClusterInfo(c)
|
||||
} else if c.Collect.ClusterResources != nil {
|
||||
result, err = ClusterResources(c, c.Collect.ClusterResources)
|
||||
} else if c.Collect.Secret != nil {
|
||||
result, err = Secret(ctx, c, c.Collect.Secret, client)
|
||||
} else if c.Collect.ConfigMap != nil {
|
||||
result, err = ConfigMap(ctx, c, c.Collect.ConfigMap, client)
|
||||
} else if c.Collect.Logs != nil {
|
||||
result, err = Logs(c, c.Collect.Logs)
|
||||
} else if c.Collect.Run != nil {
|
||||
result, err = Run(c, c.Collect.Run)
|
||||
} else if c.Collect.RunPod != nil {
|
||||
result, err = RunPod(c, c.Collect.RunPod)
|
||||
} else if c.Collect.Exec != nil {
|
||||
result, err = Exec(c, c.Collect.Exec)
|
||||
} else if c.Collect.Data != nil {
|
||||
result, err = Data(c, c.Collect.Data)
|
||||
} else if c.Collect.Copy != nil {
|
||||
result, err = Copy(c, c.Collect.Copy)
|
||||
} else if c.Collect.CopyFromHost != nil {
|
||||
namespace := c.Collect.CopyFromHost.Namespace
|
||||
if namespace == "" && c.Namespace == "" {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
namespace, _, _ = kubeconfig.Namespace()
|
||||
} else if namespace == "" {
|
||||
namespace = c.Namespace
|
||||
}
|
||||
result, err = CopyFromHost(ctx, c, c.Collect.CopyFromHost, namespace, clientConfig, client)
|
||||
} else if c.Collect.HTTP != nil {
|
||||
result, err = HTTP(c, c.Collect.HTTP)
|
||||
} else if c.Collect.Postgres != nil {
|
||||
result, err = Postgres(c, c.Collect.Postgres)
|
||||
} else if c.Collect.Mysql != nil {
|
||||
result, err = Mysql(c, c.Collect.Mysql)
|
||||
} else if c.Collect.Redis != nil {
|
||||
result, err = Redis(c, c.Collect.Redis)
|
||||
} else if c.Collect.Collectd != nil {
|
||||
// TODO: see if redaction breaks these
|
||||
namespace := c.Collect.Collectd.Namespace
|
||||
if namespace == "" && c.Namespace == "" {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
namespace, _, _ = kubeconfig.Namespace()
|
||||
} else if namespace == "" {
|
||||
namespace = c.Namespace
|
||||
}
|
||||
result, err = Collectd(ctx, c, c.Collect.Collectd, namespace, clientConfig, client)
|
||||
} else if c.Collect.Ceph != nil {
|
||||
result, err = Ceph(c, c.Collect.Ceph)
|
||||
} else if c.Collect.Longhorn != nil {
|
||||
result, err = Longhorn(c, c.Collect.Longhorn)
|
||||
} else if c.Collect.RegistryImages != nil {
|
||||
result, err = Registry(c, c.Collect.RegistryImages)
|
||||
} else if c.Collect.Sysctl != nil {
|
||||
if c.Collect.Sysctl.Namespace == "" {
|
||||
c.Collect.Sysctl.Namespace = c.Namespace
|
||||
}
|
||||
if c.Collect.Sysctl.Namespace == "" {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
namespace, _, _ := kubeconfig.Namespace()
|
||||
c.Collect.Sysctl.Namespace = namespace
|
||||
}
|
||||
result, err = Sysctl(ctx, c, client, c.Collect.Sysctl)
|
||||
} else {
|
||||
err = errors.New("no spec found to run")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var RBACErrors []error
|
||||
|
||||
if c.Redact {
|
||||
err = RedactResult(c.BundlePath, result, globalRedactors)
|
||||
err = errors.Wrap(err, "failed to redact")
|
||||
switch {
|
||||
case collector.ClusterInfo != nil:
|
||||
return &CollectClusterInfo{collector.ClusterInfo, bundlePath, namespace, clientConfig, RBACErrors}, true
|
||||
case collector.ClusterResources != nil:
|
||||
return &CollectClusterResources{collector.ClusterResources, bundlePath, namespace, clientConfig, RBACErrors}, true
|
||||
case collector.Secret != nil:
|
||||
return &CollectSecret{collector.Secret, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.ConfigMap != nil:
|
||||
return &CollectConfigMap{collector.ConfigMap, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Logs != nil:
|
||||
return &CollectLogs{collector.Logs, bundlePath, namespace, clientConfig, client, ctx, sinceTime, RBACErrors}, true
|
||||
case collector.Run != nil:
|
||||
return &CollectRun{collector.Run, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.RunPod != nil:
|
||||
return &CollectRunPod{collector.RunPod, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Exec != nil:
|
||||
return &CollectExec{collector.Exec, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Data != nil:
|
||||
return &CollectData{collector.Data, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Copy != nil:
|
||||
return &CollectCopy{collector.Copy, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.CopyFromHost != nil:
|
||||
return &CollectCopyFromHost{collector.CopyFromHost, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.HTTP != nil:
|
||||
return &CollectHTTP{collector.HTTP, bundlePath, namespace, clientConfig, client, RBACErrors}, true
|
||||
case collector.Postgres != nil:
|
||||
return &CollectPostgres{collector.Postgres, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Mysql != nil:
|
||||
return &CollectMysql{collector.Mysql, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Redis != nil:
|
||||
return &CollectRedis{collector.Redis, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Collectd != nil:
|
||||
return &CollectCollectd{collector.Collectd, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Ceph != nil:
|
||||
return &CollectCeph{collector.Ceph, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Longhorn != nil:
|
||||
return &CollectLonghorn{collector.Longhorn, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.RegistryImages != nil:
|
||||
return &CollectRegistry{collector.RegistryImages, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Sysctl != nil:
|
||||
return &CollectSysctl{collector.Sysctl, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Collector) GetDisplayName() string {
|
||||
return c.Collect.GetName()
|
||||
}
|
||||
|
||||
func (c *Collector) CheckRBAC(ctx context.Context) error {
|
||||
if c.IsExcluded() {
|
||||
return nil // excluded collectors require no permissions
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create client from config")
|
||||
}
|
||||
|
||||
forbidden := make([]error, 0)
|
||||
|
||||
specs := c.Collect.AccessReviewSpecs(c.Namespace)
|
||||
for _, spec := range specs {
|
||||
sar := &authorizationv1.SelfSubjectAccessReview{
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
resp, err := client.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, sar, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to run subject review")
|
||||
}
|
||||
|
||||
if !resp.Status.Allowed { // all other fields of Status are empty...
|
||||
forbidden = append(forbidden, RBACError{
|
||||
DisplayName: c.GetDisplayName(),
|
||||
Namespace: spec.ResourceAttributes.Namespace,
|
||||
Resource: spec.ResourceAttributes.Resource,
|
||||
Verb: spec.ResourceAttributes.Verb,
|
||||
})
|
||||
}
|
||||
}
|
||||
c.RBACErrors = forbidden
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs Collectors) CheckRBAC(ctx context.Context) error {
|
||||
for _, c := range cs {
|
||||
if err := c.CheckRBAC(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to check RBAC")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func collectorTitleOrDefault(meta troubleshootv1beta2.CollectorMeta, defaultTitle string) string {
|
||||
if meta.CollectorName != "" {
|
||||
return meta.CollectorName
|
||||
}
|
||||
return defaultTitle
|
||||
}
|
||||
|
||||
@@ -273,16 +273,26 @@ pwd=somethinggoeshere;`,
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
c := &Collector{
|
||||
Collect: tt.Collect,
|
||||
Redact: true,
|
||||
|
||||
var result CollectorResult
|
||||
|
||||
collector, _ := GetCollector(tt.Collect, "", "", nil, nil, nil)
|
||||
regCollector, _ := collector.(Collector)
|
||||
|
||||
if excluded, err := regCollector.IsExcluded(); !excluded {
|
||||
req.NoError(err)
|
||||
|
||||
result, err = regCollector.Collect(nil)
|
||||
req.NoError(err)
|
||||
|
||||
err = RedactResult("", result, tt.Redactors)
|
||||
|
||||
req.NoError(err)
|
||||
}
|
||||
got, err := c.RunCollectorSync(nil, nil, tt.Redactors)
|
||||
req.NoError(err)
|
||||
|
||||
// convert to string to make differences easier to see
|
||||
toString := map[string]string{}
|
||||
for k, v := range got {
|
||||
for k, v := range result {
|
||||
toString[k] = string(v)
|
||||
}
|
||||
req.EqualValues(tt.want, toString)
|
||||
@@ -332,16 +342,22 @@ pwd=somethinggoeshere;`,
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
c := &Collector{
|
||||
Collect: tt.Collect,
|
||||
Redact: false,
|
||||
|
||||
var result CollectorResult
|
||||
|
||||
collector, _ := GetCollector(tt.Collect, "", "", nil, nil, nil)
|
||||
regCollector, _ := collector.(Collector)
|
||||
|
||||
if excluded, err := regCollector.IsExcluded(); !excluded {
|
||||
req.NoError(err)
|
||||
|
||||
result, err = regCollector.Collect(nil)
|
||||
req.NoError(err)
|
||||
}
|
||||
got, err := c.RunCollectorSync(nil, nil, tt.Redactors)
|
||||
req.NoError(err)
|
||||
|
||||
// convert to string to make differences easier to see
|
||||
toString := map[string]string{}
|
||||
for k, v := range got {
|
||||
for k, v := range result {
|
||||
toString[k] = string(v)
|
||||
}
|
||||
req.EqualValues(tt.want, toString)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type ConfigMapOutput struct {
|
||||
@@ -26,28 +27,46 @@ type ConfigMapOutput struct {
|
||||
Data map[string]string `json:"data,omitonempty"`
|
||||
}
|
||||
|
||||
func ConfigMap(ctx context.Context, c *Collector, configMapCollector *troubleshootv1beta2.ConfigMap, client kubernetes.Interface) (CollectorResult, error) {
|
||||
type CollectConfigMap struct {
|
||||
Collector *troubleshootv1beta2.ConfigMap
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectConfigMap) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "ConfigMap")
|
||||
}
|
||||
|
||||
func (c *CollectConfigMap) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectConfigMap) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
output := NewResult()
|
||||
|
||||
configMaps := []corev1.ConfigMap{}
|
||||
if configMapCollector.Name != "" {
|
||||
configMap, err := client.CoreV1().ConfigMaps(configMapCollector.Namespace).Get(ctx, configMapCollector.Name, metav1.GetOptions{})
|
||||
if c.Collector.Name != "" {
|
||||
configMap, err := c.Client.CoreV1().ConfigMaps(c.Collector.Namespace).Get(c.ctx, c.Collector.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if kuberneteserrors.IsNotFound(err) {
|
||||
filePath, encoded, err := configMapToOutput(configMapCollector, nil, configMapCollector.Name)
|
||||
filePath, encoded, err := configMapToOutput(c.Collector, nil, c.Collector.Name)
|
||||
if err != nil {
|
||||
return output, errors.Wrapf(err, "collect secret %s", configMapCollector.Name)
|
||||
return output, errors.Wrapf(err, "collect secret %s", c.Collector.Name)
|
||||
}
|
||||
output.SaveResult(c.BundlePath, filePath, bytes.NewBuffer(encoded))
|
||||
}
|
||||
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(configMapCollector), marshalErrors([]string{err.Error()}))
|
||||
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
|
||||
return output, nil
|
||||
}
|
||||
configMaps = append(configMaps, *configMap)
|
||||
} else if len(configMapCollector.Selector) > 0 {
|
||||
cms, err := listConfigMapsForSelector(ctx, client, configMapCollector.Namespace, configMapCollector.Selector)
|
||||
} else if len(c.Collector.Selector) > 0 {
|
||||
cms, err := listConfigMapsForSelector(c.ctx, c.Client, c.Collector.Namespace, c.Collector.Selector)
|
||||
if err != nil {
|
||||
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(configMapCollector), marshalErrors([]string{err.Error()}))
|
||||
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
|
||||
return output, nil
|
||||
}
|
||||
configMaps = append(configMaps, cms...)
|
||||
@@ -56,7 +75,7 @@ func ConfigMap(ctx context.Context, c *Collector, configMapCollector *troublesho
|
||||
}
|
||||
|
||||
for _, configMap := range configMaps {
|
||||
filePath, encoded, err := configMapToOutput(configMapCollector, &configMap, configMap.Name)
|
||||
filePath, encoded, err := configMapToOutput(c.Collector, &configMap, configMap.Name)
|
||||
if err != nil {
|
||||
return output, errors.Wrapf(err, "collect configMap %s", configMap.Name)
|
||||
}
|
||||
|
||||
@@ -328,8 +328,8 @@ func TestConfigMap(t *testing.T) {
|
||||
_, err := client.CoreV1().ConfigMaps(configMap.Namespace).Create(ctx, &configMap, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
c := &Collector{}
|
||||
got, err := ConfigMap(ctx, c, tt.configMapCollector, client)
|
||||
configMapCollector := &CollectConfigMap{tt.configMapCollector, "", "", nil, client, ctx, nil}
|
||||
got, err := configMapCollector.Collect(nil)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
|
||||
@@ -15,12 +15,31 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
type CollectCopy struct {
|
||||
Collector *troubleshootv1beta2.Copy
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCopy) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Copy")
|
||||
}
|
||||
|
||||
func (c *CollectCopy) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
// Copy function gets a file or folder from a container specified in the specs.
|
||||
func Copy(c *Collector, copyCollector *troubleshootv1beta2.Copy) (CollectorResult, error) {
|
||||
func (c *CollectCopy) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -30,40 +49,40 @@ func Copy(c *Collector, copyCollector *troubleshootv1beta2.Copy) (CollectorResul
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, copyCollector.Namespace, copyCollector.Selector)
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, c.Collector.Namespace, c.Collector.Selector)
|
||||
if len(podsErrors) > 0 {
|
||||
output.SaveResult(c.BundlePath, getCopyErrosFileName(copyCollector), marshalErrors(podsErrors))
|
||||
output.SaveResult(c.BundlePath, getCopyErrosFileName(c.Collector), marshalErrors(podsErrors))
|
||||
}
|
||||
|
||||
if len(pods) > 0 {
|
||||
for _, pod := range pods {
|
||||
|
||||
containerName := pod.Spec.Containers[0].Name
|
||||
if copyCollector.ContainerName != "" {
|
||||
containerName = copyCollector.ContainerName
|
||||
if c.Collector.ContainerName != "" {
|
||||
containerName = c.Collector.ContainerName
|
||||
}
|
||||
|
||||
subPath := filepath.Join(copyCollector.Name, pod.Namespace, pod.Name, copyCollector.ContainerName)
|
||||
subPath := filepath.Join(c.Collector.Name, pod.Namespace, pod.Name, c.Collector.ContainerName)
|
||||
|
||||
copyCollector.ExtractArchive = true // TODO: existing regression. this flag is always ignored and this matches current behaviour
|
||||
c.Collector.ExtractArchive = true // TODO: existing regression. this flag is always ignored and this matches current behaviour
|
||||
|
||||
copyErrors := map[string]string{}
|
||||
|
||||
dstPath := filepath.Join(c.BundlePath, subPath, filepath.Dir(copyCollector.ContainerPath))
|
||||
files, stderr, err := copyFilesFromPod(ctx, dstPath, c.ClientConfig, client, pod.Name, containerName, pod.Namespace, copyCollector.ContainerPath, copyCollector.ExtractArchive)
|
||||
dstPath := filepath.Join(c.BundlePath, subPath, filepath.Dir(c.Collector.ContainerPath))
|
||||
files, stderr, err := copyFilesFromPod(ctx, dstPath, c.ClientConfig, client, pod.Name, containerName, pod.Namespace, c.Collector.ContainerPath, c.Collector.ExtractArchive)
|
||||
if err != nil {
|
||||
copyErrors[filepath.Join(copyCollector.ContainerPath, "error")] = err.Error()
|
||||
copyErrors[filepath.Join(c.Collector.ContainerPath, "error")] = err.Error()
|
||||
if len(stderr) > 0 {
|
||||
copyErrors[filepath.Join(copyCollector.ContainerPath, "stderr")] = string(stderr)
|
||||
copyErrors[filepath.Join(c.Collector.ContainerPath, "stderr")] = string(stderr)
|
||||
}
|
||||
|
||||
key := filepath.Join(subPath, copyCollector.ContainerPath+"-errors.json")
|
||||
key := filepath.Join(subPath, c.Collector.ContainerPath+"-errors.json")
|
||||
output.SaveResult(c.BundlePath, key, marshalErrors(copyErrors))
|
||||
continue
|
||||
}
|
||||
|
||||
for k, v := range files {
|
||||
output[filepath.Join(subPath, filepath.Dir(copyCollector.ContainerPath), k)] = v
|
||||
output[filepath.Join(subPath, filepath.Dir(c.Collector.ContainerPath), k)] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/segmentio/ksuid"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
@@ -20,19 +21,40 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// CopyFromHost is a function that copies a file or directory from a host or hosts to include in the bundle.
|
||||
func CopyFromHost(ctx context.Context, c *Collector, collector *troubleshootv1beta2.CopyFromHost, namespace string, clientConfig *restclient.Config, client kubernetes.Interface) (CollectorResult, error) {
|
||||
type CollectCopyFromHost struct {
|
||||
Collector *troubleshootv1beta2.CopyFromHost
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCopyFromHost) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Copy from Host")
|
||||
}
|
||||
|
||||
func (c *CollectCopyFromHost) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
// copies a file or directory from a host or hosts to include in the bundle.
|
||||
func (c *CollectCopyFromHost) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
var namespace string
|
||||
|
||||
labels := map[string]string{
|
||||
"app.kubernetes.io/managed-by": "troubleshoot.sh",
|
||||
"troubleshoot.sh/collector": "copyfromhost",
|
||||
"troubleshoot.sh/copyfromhost-id": ksuid.New().String(),
|
||||
}
|
||||
|
||||
hostPath := filepath.Clean(collector.HostPath) // strip trailing slash
|
||||
hostPath := filepath.Clean(c.Collector.HostPath) // strip trailing slash
|
||||
|
||||
hostDir := filepath.Dir(hostPath)
|
||||
fileName := filepath.Base(hostPath)
|
||||
@@ -41,18 +63,24 @@ func CopyFromHost(ctx context.Context, c *Collector, collector *troubleshootv1be
|
||||
fileName = "."
|
||||
}
|
||||
|
||||
_, cleanup, err := copyFromHostCreateDaemonSet(ctx, client, collector, hostDir, namespace, "troubleshoot-copyfromhost-", labels)
|
||||
namespace = c.Namespace
|
||||
if namespace == "" {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
namespace, _, _ = kubeconfig.Namespace()
|
||||
}
|
||||
|
||||
_, cleanup, err := copyFromHostCreateDaemonSet(c.ctx, c.Client, c.Collector, hostDir, namespace, "troubleshoot-copyfromhost-", labels)
|
||||
defer cleanup()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create daemonset")
|
||||
}
|
||||
|
||||
childCtx, cancel := context.WithCancel(ctx)
|
||||
childCtx, cancel := context.WithCancel(c.ctx)
|
||||
defer cancel()
|
||||
|
||||
timeoutCtx := context.Background()
|
||||
if collector.Timeout != "" {
|
||||
timeout, err := time.ParseDuration(collector.Timeout)
|
||||
if c.Collector.Timeout != "" {
|
||||
timeout, err := time.ParseDuration(c.Collector.Timeout)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse timeout")
|
||||
}
|
||||
@@ -67,12 +95,12 @@ func CopyFromHost(ctx context.Context, c *Collector, collector *troubleshootv1be
|
||||
resultCh := make(chan CollectorResult, 1)
|
||||
go func() {
|
||||
var outputFilename string
|
||||
if collector.Name != "" {
|
||||
outputFilename = collector.Name
|
||||
if c.Collector.Name != "" {
|
||||
outputFilename = c.Collector.Name
|
||||
} else {
|
||||
outputFilename = hostPath
|
||||
}
|
||||
b, err := copyFromHostGetFilesFromPods(childCtx, c, collector, clientConfig, client, fileName, outputFilename, labels, namespace)
|
||||
b, err := copyFromHostGetFilesFromPods(childCtx, c.BundlePath, c.Collector, c.ClientConfig, c.Client, fileName, outputFilename, labels, namespace)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
} else {
|
||||
@@ -220,7 +248,7 @@ func copyFromHostCreateDaemonSet(ctx context.Context, client kubernetes.Interfac
|
||||
return createdDS.Name, cleanup, nil
|
||||
}
|
||||
|
||||
func copyFromHostGetFilesFromPods(ctx context.Context, c *Collector, collector *troubleshootv1beta2.CopyFromHost, clientConfig *restclient.Config, client kubernetes.Interface, fileName string, outputFilename string, labelSelector map[string]string, namespace string) (CollectorResult, error) {
|
||||
func copyFromHostGetFilesFromPods(ctx context.Context, bundlePath string, collector *troubleshootv1beta2.CopyFromHost, clientConfig *restclient.Config, client kubernetes.Interface, fileName string, outputFilename string, labelSelector map[string]string, namespace string) (CollectorResult, error) {
|
||||
opts := metav1.ListOptions{
|
||||
LabelSelector: labels.SelectorFromSet(labelSelector).String(),
|
||||
}
|
||||
@@ -233,16 +261,16 @@ func copyFromHostGetFilesFromPods(ctx context.Context, c *Collector, collector *
|
||||
output := NewResult()
|
||||
for _, pod := range pods.Items {
|
||||
outputNodeFilename := filepath.Join(outputFilename, pod.Spec.NodeName)
|
||||
files, stderr, err := copyFilesFromHost(ctx, filepath.Join(c.BundlePath, outputNodeFilename), clientConfig, client, pod.Name, "collector", namespace, filepath.Join("/host", fileName), collector.ExtractArchive)
|
||||
files, stderr, err := copyFilesFromHost(ctx, filepath.Join(bundlePath, outputNodeFilename), clientConfig, client, pod.Name, "collector", namespace, filepath.Join("/host", fileName), collector.ExtractArchive)
|
||||
if err != nil {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(outputNodeFilename, "error.txt"), bytes.NewBuffer([]byte(err.Error())))
|
||||
output.SaveResult(bundlePath, filepath.Join(outputNodeFilename, "error.txt"), bytes.NewBuffer([]byte(err.Error())))
|
||||
if len(stderr) > 0 {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(outputNodeFilename, "stderr.txt"), bytes.NewBuffer(stderr))
|
||||
output.SaveResult(bundlePath, filepath.Join(outputNodeFilename, "stderr.txt"), bytes.NewBuffer(stderr))
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range files {
|
||||
relPath, err := filepath.Rel(c.BundlePath, filepath.Join(c.BundlePath, filepath.Join(outputNodeFilename, k)))
|
||||
relPath, err := filepath.Rel(bundlePath, filepath.Join(bundlePath, filepath.Join(outputNodeFilename, k)))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "relative path")
|
||||
}
|
||||
|
||||
@@ -2,16 +2,37 @@ package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Data(c *Collector, dataCollector *troubleshootv1beta2.Data) (CollectorResult, error) {
|
||||
bundlePath := filepath.Join(dataCollector.Name, dataCollector.CollectorName)
|
||||
type CollectData struct {
|
||||
Collector *troubleshootv1beta2.Data
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectData) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Data")
|
||||
}
|
||||
|
||||
func (c *CollectData) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectData) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
bundlePath := filepath.Join(c.Collector.Name, c.Collector.CollectorName)
|
||||
|
||||
output := NewResult()
|
||||
output.SaveResult(c.BundlePath, bundlePath, bytes.NewBuffer([]byte(dataCollector.Data)))
|
||||
output.SaveResult(c.BundlePath, bundlePath, bytes.NewBuffer([]byte(c.Collector.Data)))
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -3,24 +3,43 @@ package collect
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
func Exec(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResult, error) {
|
||||
if execCollector.Timeout == "" {
|
||||
return execWithoutTimeout(c, execCollector)
|
||||
type CollectExec struct {
|
||||
Collector *troubleshootv1beta2.Exec
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectExec) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Exec")
|
||||
}
|
||||
|
||||
func (c *CollectExec) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectExec) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
if c.Collector.Timeout == "" {
|
||||
return execWithoutTimeout(c.ClientConfig, c.BundlePath, c.Collector)
|
||||
}
|
||||
|
||||
timeout, err := time.ParseDuration(execCollector.Timeout)
|
||||
timeout, err := time.ParseDuration(c.Collector.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -29,7 +48,7 @@ func Exec(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResul
|
||||
resultCh := make(chan CollectorResult, 1)
|
||||
|
||||
go func() {
|
||||
b, err := execWithoutTimeout(c, execCollector)
|
||||
b, err := execWithoutTimeout(c.ClientConfig, c.BundlePath, c.Collector)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
} else {
|
||||
@@ -47,8 +66,8 @@ func Exec(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResul
|
||||
}
|
||||
}
|
||||
|
||||
func execWithoutTimeout(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
func execWithoutTimeout(clientConfig *rest.Config, bundlePath string, execCollector *troubleshootv1beta2.Exec) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -59,23 +78,23 @@ func execWithoutTimeout(c *Collector, execCollector *troubleshootv1beta2.Exec) (
|
||||
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, execCollector.Namespace, execCollector.Selector)
|
||||
if len(podsErrors) > 0 {
|
||||
output.SaveResult(c.BundlePath, getExecErrosFileName(execCollector), marshalErrors(podsErrors))
|
||||
output.SaveResult(bundlePath, getExecErrosFileName(execCollector), marshalErrors(podsErrors))
|
||||
}
|
||||
|
||||
if len(pods) > 0 {
|
||||
for _, pod := range pods {
|
||||
stdout, stderr, execErrors := getExecOutputs(c, client, pod, execCollector)
|
||||
stdout, stderr, execErrors := getExecOutputs(clientConfig, client, pod, execCollector)
|
||||
|
||||
bundlePath := filepath.Join(execCollector.Name, pod.Namespace, pod.Name)
|
||||
path := filepath.Join(execCollector.Name, pod.Namespace, pod.Name)
|
||||
if len(stdout) > 0 {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(bundlePath, execCollector.CollectorName+"-stdout.txt"), bytes.NewBuffer(stdout))
|
||||
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-stdout.txt"), bytes.NewBuffer(stdout))
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(bundlePath, execCollector.CollectorName+"-stderr.txt"), bytes.NewBuffer(stderr))
|
||||
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-stderr.txt"), bytes.NewBuffer(stderr))
|
||||
}
|
||||
|
||||
if len(execErrors) > 0 {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(bundlePath, execCollector.CollectorName+"-errors.json"), marshalErrors(execErrors))
|
||||
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-errors.json"), marshalErrors(execErrors))
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -84,7 +103,7 @@ func execWithoutTimeout(c *Collector, execCollector *troubleshootv1beta2.Exec) (
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func getExecOutputs(c *Collector, client *kubernetes.Clientset, pod corev1.Pod, execCollector *troubleshootv1beta2.Exec) ([]byte, []byte, []string) {
|
||||
func getExecOutputs(clientConfig *rest.Config, client *kubernetes.Clientset, pod corev1.Pod, execCollector *troubleshootv1beta2.Exec) ([]byte, []byte, []string) {
|
||||
container := pod.Spec.Containers[0].Name
|
||||
if execCollector.ContainerName != "" {
|
||||
container = execCollector.ContainerName
|
||||
@@ -106,7 +125,7 @@ func getExecOutputs(c *Collector, client *kubernetes.Clientset, pod corev1.Pod,
|
||||
TTY: false,
|
||||
}, parameterCodec)
|
||||
|
||||
exec, err := remotecommand.NewSPDYExecutor(c.ClientConfig, "POST", req.URL())
|
||||
exec, err := remotecommand.NewSPDYExecutor(clientConfig, "POST", req.URL())
|
||||
if err != nil {
|
||||
return nil, nil, []string{err.Error()}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func (c *CollectHostHTTP) Collect(progressChan chan<- interface{}) (map[string][
|
||||
return nil, errors.New("no supported http request type")
|
||||
}
|
||||
|
||||
responseOutput, err := responseToOutput(response, err, false)
|
||||
responseOutput, err := responseToOutput(response, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,13 +4,15 @@ import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type HTTPResponse struct {
|
||||
@@ -33,32 +35,49 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func HTTP(c *Collector, httpCollector *troubleshootv1beta2.HTTP) (CollectorResult, error) {
|
||||
type CollectHTTP struct {
|
||||
Collector *troubleshootv1beta2.HTTP
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectHTTP) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "HTTP")
|
||||
}
|
||||
|
||||
func (c *CollectHTTP) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHTTP) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
var response *http.Response
|
||||
var err error
|
||||
|
||||
if httpCollector.Get != nil {
|
||||
response, err = doGet(httpCollector.Get)
|
||||
} else if httpCollector.Post != nil {
|
||||
response, err = doPost(httpCollector.Post)
|
||||
} else if httpCollector.Put != nil {
|
||||
response, err = doPut(httpCollector.Put)
|
||||
if c.Collector.Get != nil {
|
||||
response, err = doGet(c.Collector.Get)
|
||||
} else if c.Collector.Post != nil {
|
||||
response, err = doPost(c.Collector.Post)
|
||||
} else if c.Collector.Put != nil {
|
||||
response, err = doPut(c.Collector.Put)
|
||||
} else {
|
||||
return nil, errors.New("no supported http request type")
|
||||
}
|
||||
|
||||
o, err := responseToOutput(response, err, c.Redact)
|
||||
o, err := responseToOutput(response, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileName := "result.json"
|
||||
if httpCollector.CollectorName != "" {
|
||||
fileName = httpCollector.CollectorName + ".json"
|
||||
if c.Collector.CollectorName != "" {
|
||||
fileName = c.Collector.CollectorName + ".json"
|
||||
}
|
||||
|
||||
output := NewResult()
|
||||
output.SaveResult(c.BundlePath, filepath.Join(httpCollector.Name, fileName), bytes.NewBuffer(o))
|
||||
output.SaveResult(c.BundlePath, filepath.Join(c.Collector.Name, fileName), bytes.NewBuffer(o))
|
||||
|
||||
return output, nil
|
||||
}
|
||||
@@ -117,7 +136,7 @@ func doPut(put *troubleshootv1beta2.Put) (*http.Response, error) {
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
func responseToOutput(response *http.Response, err error, doRedact bool) ([]byte, error) {
|
||||
func responseToOutput(response *http.Response, err error) ([]byte, error) {
|
||||
output := make(map[string]interface{})
|
||||
if err != nil {
|
||||
output["error"] = HTTPError{
|
||||
|
||||
@@ -13,9 +13,29 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResult, error) {
|
||||
type CollectLogs struct {
|
||||
Collector *troubleshootv1beta2.Logs
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
SinceTime *time.Time
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectLogs) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Logs")
|
||||
}
|
||||
|
||||
func (c *CollectLogs) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectLogs) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -25,14 +45,21 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, logsCollector.Namespace, logsCollector.Selector)
|
||||
if c.SinceTime != nil {
|
||||
if c.Collector.Limits == nil {
|
||||
c.Collector.Limits = new(troubleshootv1beta2.LogLimits)
|
||||
}
|
||||
c.Collector.Limits.SinceTime = metav1.NewTime(*c.SinceTime)
|
||||
}
|
||||
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, c.Collector.Namespace, c.Collector.Selector)
|
||||
if len(podsErrors) > 0 {
|
||||
output.SaveResult(c.BundlePath, getLogsErrorsFileName(logsCollector), marshalErrors(podsErrors))
|
||||
output.SaveResult(c.BundlePath, getLogsErrorsFileName(c.Collector), marshalErrors(podsErrors))
|
||||
}
|
||||
|
||||
if len(pods) > 0 {
|
||||
for _, pod := range pods {
|
||||
if len(logsCollector.ContainerNames) == 0 {
|
||||
if len(c.Collector.ContainerNames) == 0 {
|
||||
// make a list of all the containers in the pod, so that we can get logs from all of them
|
||||
containerNames := []string{}
|
||||
for _, container := range pod.Spec.Containers {
|
||||
@@ -46,11 +73,11 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
|
||||
if len(containerNames) == 1 {
|
||||
containerName = "" // if there was only one container, use the old behavior of not including the container name in the path
|
||||
}
|
||||
podLogs, err := savePodLogs(ctx, c.BundlePath, client, pod, logsCollector.Name, containerName, logsCollector.Limits, false)
|
||||
podLogs, err := savePodLogs(ctx, c.BundlePath, client, pod, c.Collector.Name, containerName, c.Collector.Limits, false)
|
||||
if err != nil {
|
||||
key := fmt.Sprintf("%s/%s-errors.json", logsCollector.Name, pod.Name)
|
||||
key := fmt.Sprintf("%s/%s-errors.json", c.Collector.Name, pod.Name)
|
||||
if containerName != "" {
|
||||
key = fmt.Sprintf("%s/%s/%s-errors.json", logsCollector.Name, pod.Name, containerName)
|
||||
key = fmt.Sprintf("%s/%s/%s-errors.json", c.Collector.Name, pod.Name, containerName)
|
||||
}
|
||||
err := output.SaveResult(c.BundlePath, key, marshalErrors([]string{err.Error()}))
|
||||
if err != nil {
|
||||
@@ -63,10 +90,10 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, container := range logsCollector.ContainerNames {
|
||||
containerLogs, err := savePodLogs(ctx, c.BundlePath, client, pod, logsCollector.Name, container, logsCollector.Limits, false)
|
||||
for _, container := range c.Collector.ContainerNames {
|
||||
containerLogs, err := savePodLogs(ctx, c.BundlePath, client, pod, c.Collector.Name, container, c.Collector.Limits, false)
|
||||
if err != nil {
|
||||
key := fmt.Sprintf("%s/%s/%s-errors.json", logsCollector.Name, pod.Name, container)
|
||||
key := fmt.Sprintf("%s/%s/%s-errors.json", c.Collector.Name, pod.Name, container)
|
||||
err := output.SaveResult(c.BundlePath, key, marshalErrors([]string{err.Error()}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -29,12 +29,30 @@ const (
|
||||
|
||||
var checksumRX = regexp.MustCompile(`(\S+)\s+(\S+)`)
|
||||
|
||||
func Longhorn(c *Collector, longhornCollector *troubleshootv1beta2.Longhorn) (CollectorResult, error) {
|
||||
type CollectLonghorn struct {
|
||||
Collector *troubleshootv1beta2.Longhorn
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectLonghorn) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Longhorn")
|
||||
}
|
||||
|
||||
func (c *CollectLonghorn) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectLonghorn) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
ns := DefaultLonghornNamespace
|
||||
if longhornCollector.Namespace != "" {
|
||||
ns = longhornCollector.Namespace
|
||||
if c.Collector.Namespace != "" {
|
||||
ns = c.Collector.Namespace
|
||||
}
|
||||
|
||||
client, err := longhornv1beta1.NewForConfig(c.ClientConfig)
|
||||
@@ -197,11 +215,15 @@ func Longhorn(c *Collector, longhornCollector *troubleshootv1beta2.Longhorn) (Co
|
||||
output.SaveResult(c.BundlePath, settingsKey, bytes.NewBuffer(settingsB))
|
||||
|
||||
// logs of all pods in namespace
|
||||
logsCollector := &troubleshootv1beta2.Logs{
|
||||
logsCollectorSpec := &troubleshootv1beta2.Logs{
|
||||
Selector: []string{""},
|
||||
Namespace: ns,
|
||||
}
|
||||
logs, err := Logs(c, logsCollector)
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
logsCollector := &CollectLogs{logsCollectorSpec, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.ctx, nil, rbacErrors}
|
||||
|
||||
logs, err := logsCollector.Collect(progressChan)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "collect longhorn logs")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -9,12 +10,32 @@ import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Mysql(c *Collector, databaseCollector *troubleshootv1beta2.Database) (CollectorResult, error) {
|
||||
type CollectMysql struct {
|
||||
Collector *troubleshootv1beta2.Database
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectMysql) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Mysql")
|
||||
}
|
||||
|
||||
func (c *CollectMysql) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectMysql) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
databaseConnection := DatabaseConnection{}
|
||||
|
||||
db, err := sql.Open("mysql", databaseCollector.URI)
|
||||
db, err := sql.Open("mysql", c.Collector.URI)
|
||||
if err != nil {
|
||||
databaseConnection.Error = err.Error()
|
||||
} else {
|
||||
@@ -30,7 +51,7 @@ func Mysql(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Colle
|
||||
databaseConnection.Version = version
|
||||
}
|
||||
|
||||
requestedParameters := databaseCollector.Parameters
|
||||
requestedParameters := c.Collector.Parameters
|
||||
if len(requestedParameters) > 0 {
|
||||
rows, err := db.Query("SHOW VARIABLES")
|
||||
|
||||
@@ -68,7 +89,7 @@ func Mysql(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Colle
|
||||
return nil, errors.Wrap(err, "failed to marshal database connection")
|
||||
}
|
||||
|
||||
collectorName := databaseCollector.CollectorName
|
||||
collectorName := c.Collector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "mysql"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -10,12 +11,32 @@ import (
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Postgres(c *Collector, databaseCollector *troubleshootv1beta2.Database) (CollectorResult, error) {
|
||||
type CollectPostgres struct {
|
||||
Collector *troubleshootv1beta2.Database
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectPostgres) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Postgres")
|
||||
}
|
||||
|
||||
func (c *CollectPostgres) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectPostgres) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
databaseConnection := DatabaseConnection{}
|
||||
|
||||
db, err := sql.Open("postgres", databaseCollector.URI)
|
||||
db, err := sql.Open("postgres", c.Collector.URI)
|
||||
if err != nil {
|
||||
databaseConnection.Error = err.Error()
|
||||
} else {
|
||||
@@ -42,7 +63,7 @@ func Postgres(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Co
|
||||
return nil, errors.Wrap(err, "failed to marshal database connection")
|
||||
}
|
||||
|
||||
collectorName := databaseCollector.CollectorName
|
||||
collectorName := c.Collector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "postgres"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,43 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
authorizationv1 "k8s.io/api/authorization/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type RBACErrors []error
|
||||
|
||||
func (e RBACErrors) GetRBACErrors() []error {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e RBACErrors) HasRBACErrors() bool {
|
||||
return len(e) > 0
|
||||
}
|
||||
|
||||
func (e *RBACErrors) CheckRBAC(ctx context.Context, c Collector, collector *troubleshootv1beta2.Collect, clientConfig *rest.Config, namespace string) error {
|
||||
exclude, err := c.IsExcluded()
|
||||
if err != nil || exclude {
|
||||
return nil
|
||||
}
|
||||
|
||||
rbacErrors, err := checkRBAC(ctx, clientConfig, namespace, c.Title(), collector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*e = rbacErrors
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RBACError struct {
|
||||
DisplayName string
|
||||
Namespace string
|
||||
@@ -24,3 +56,35 @@ func IsRBACError(err error) bool {
|
||||
_, ok := errors.Cause(err).(RBACError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func checkRBAC(ctx context.Context, clientConfig *rest.Config, namespace string, title string, collector *troubleshootv1beta2.Collect) ([]error, error) {
|
||||
client, err := kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create client from config")
|
||||
}
|
||||
|
||||
forbidden := make([]error, 0)
|
||||
|
||||
specs := collector.AccessReviewSpecs(namespace)
|
||||
for _, spec := range specs {
|
||||
sar := &authorizationv1.SelfSubjectAccessReview{
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
resp, err := client.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, sar, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to run subject review")
|
||||
}
|
||||
|
||||
if !resp.Status.Allowed { // all other fields of Status are empty...
|
||||
forbidden = append(forbidden, RBACError{
|
||||
DisplayName: title,
|
||||
Namespace: spec.ResourceAttributes.Namespace,
|
||||
Resource: spec.ResourceAttributes.Resource,
|
||||
Verb: spec.ResourceAttributes.Verb,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return forbidden, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -9,12 +10,32 @@ import (
|
||||
"github.com/go-redis/redis/v7"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Redis(c *Collector, databaseCollector *troubleshootv1beta2.Database) (CollectorResult, error) {
|
||||
type CollectRedis struct {
|
||||
Collector *troubleshootv1beta2.Database
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectRedis) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Cluster Info")
|
||||
}
|
||||
|
||||
func (c *CollectRedis) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectRedis) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
databaseConnection := DatabaseConnection{}
|
||||
|
||||
opt, err := redis.ParseURL(databaseCollector.URI)
|
||||
opt, err := redis.ParseURL(c.Collector.URI)
|
||||
if err != nil {
|
||||
databaseConnection.Error = err.Error()
|
||||
} else {
|
||||
@@ -45,7 +66,7 @@ func Redis(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Colle
|
||||
return nil, errors.Wrap(err, "failed to marshal database connection")
|
||||
}
|
||||
|
||||
collectorName := databaseCollector.CollectorName
|
||||
collectorName := c.Collector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "redis"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
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 RegistryImage struct {
|
||||
@@ -36,13 +37,31 @@ type registryAuthConfig struct {
|
||||
password string
|
||||
}
|
||||
|
||||
func Registry(c *Collector, registryCollector *troubleshootv1beta2.RegistryImages) (CollectorResult, error) {
|
||||
type CollectRegistry struct {
|
||||
Collector *troubleshootv1beta2.RegistryImages
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectRegistry) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Registry Images")
|
||||
}
|
||||
|
||||
func (c *CollectRegistry) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectRegistry) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
registryInfo := RegistryInfo{
|
||||
Images: map[string]RegistryImage{},
|
||||
}
|
||||
|
||||
for _, image := range registryCollector.Images {
|
||||
exists, err := imageExists(c, registryCollector, image)
|
||||
for _, image := range c.Collector.Images {
|
||||
exists, err := imageExists(c.Namespace, c.ClientConfig, c.Collector, image)
|
||||
if err != nil {
|
||||
registryInfo.Images[image] = RegistryImage{
|
||||
Error: err.Error(),
|
||||
@@ -59,7 +78,7 @@ func Registry(c *Collector, registryCollector *troubleshootv1beta2.RegistryImage
|
||||
return nil, errors.Wrap(err, "failed to marshal database connection")
|
||||
}
|
||||
|
||||
collectorName := registryCollector.CollectorName
|
||||
collectorName := c.Collector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "images"
|
||||
}
|
||||
@@ -70,13 +89,13 @@ func Registry(c *Collector, registryCollector *troubleshootv1beta2.RegistryImage
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func imageExists(c *Collector, registryCollector *troubleshootv1beta2.RegistryImages, image string) (bool, error) {
|
||||
func imageExists(namespace string, clientConfig *rest.Config, registryCollector *troubleshootv1beta2.RegistryImages, image string) (bool, error) {
|
||||
imageRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", image))
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse image name %s", image)
|
||||
}
|
||||
|
||||
authConfig, err := getImageAuthConfig(c, registryCollector, imageRef)
|
||||
authConfig, err := getImageAuthConfig(namespace, clientConfig, registryCollector, imageRef)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to get auth config")
|
||||
}
|
||||
@@ -123,13 +142,13 @@ func imageExists(c *Collector, registryCollector *troubleshootv1beta2.RegistryIm
|
||||
return false, errors.Wrap(lastErr, "failed to retry")
|
||||
}
|
||||
|
||||
func getImageAuthConfig(c *Collector, registryCollector *troubleshootv1beta2.RegistryImages, imageRef types.ImageReference) (*registryAuthConfig, error) {
|
||||
func getImageAuthConfig(namespace string, clientConfig *rest.Config, registryCollector *troubleshootv1beta2.RegistryImages, imageRef types.ImageReference) (*registryAuthConfig, error) {
|
||||
if registryCollector.ImagePullSecrets == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if registryCollector.ImagePullSecrets.Data != nil {
|
||||
config, err := getImageAuthConfigFromData(c, imageRef, registryCollector.ImagePullSecrets)
|
||||
config, err := getImageAuthConfigFromData(imageRef, registryCollector.ImagePullSecrets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get auth from data")
|
||||
}
|
||||
@@ -137,14 +156,14 @@ func getImageAuthConfig(c *Collector, registryCollector *troubleshootv1beta2.Reg
|
||||
}
|
||||
|
||||
if registryCollector.ImagePullSecrets.Name != "" {
|
||||
namespace := registryCollector.Namespace
|
||||
if namespace == "" {
|
||||
namespace = c.Namespace
|
||||
collectorNamespace := registryCollector.Namespace
|
||||
if collectorNamespace == "" {
|
||||
collectorNamespace = namespace
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
if collectorNamespace == "" {
|
||||
collectorNamespace = "default"
|
||||
}
|
||||
config, err := getImageAuthConfigFromSecret(c, imageRef, registryCollector.ImagePullSecrets, namespace)
|
||||
config, err := getImageAuthConfigFromSecret(clientConfig, imageRef, registryCollector.ImagePullSecrets, collectorNamespace)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get auth from secret")
|
||||
}
|
||||
@@ -154,7 +173,7 @@ func getImageAuthConfig(c *Collector, registryCollector *troubleshootv1beta2.Reg
|
||||
return nil, errors.New("image pull secret spec is not valid")
|
||||
}
|
||||
|
||||
func getImageAuthConfigFromData(c *Collector, imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets) (*registryAuthConfig, error) {
|
||||
func getImageAuthConfigFromData(imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets) (*registryAuthConfig, error) {
|
||||
if pullSecrets.SecretType != "kubernetes.io/dockerconfigjson" {
|
||||
return nil, errors.Errorf("secret type is not supported: %s", pullSecrets.SecretType)
|
||||
}
|
||||
@@ -197,10 +216,10 @@ func getImageAuthConfigFromData(c *Collector, imageRef types.ImageReference, pul
|
||||
return &authConfig, nil
|
||||
}
|
||||
|
||||
func getImageAuthConfigFromSecret(c *Collector, imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets, namespace string) (*registryAuthConfig, error) {
|
||||
func getImageAuthConfigFromSecret(clientConfig *rest.Config, imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets, namespace string) (*registryAuthConfig, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
client, err := kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create client from config")
|
||||
}
|
||||
@@ -218,7 +237,7 @@ func getImageAuthConfigFromSecret(c *Collector, imageRef types.ImageReference, p
|
||||
},
|
||||
}
|
||||
|
||||
config, err := getImageAuthConfigFromData(c, imageRef, foundSecrets)
|
||||
config, err := getImageAuthConfigFromData(imageRef, foundSecrets)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get auth from secret data")
|
||||
}
|
||||
|
||||
@@ -1,264 +1,73 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Run(c *Collector, runCollector *troubleshootv1beta2.Run) (CollectorResult, error) {
|
||||
type CollectRun struct {
|
||||
Collector *troubleshootv1beta2.Run
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectRun) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Run")
|
||||
}
|
||||
|
||||
func (c *CollectRun) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectRun) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
pullPolicy := corev1.PullIfNotPresent
|
||||
if runCollector.ImagePullPolicy != "" {
|
||||
pullPolicy = corev1.PullPolicy(runCollector.ImagePullPolicy)
|
||||
if c.Collector.ImagePullPolicy != "" {
|
||||
pullPolicy = corev1.PullPolicy(c.Collector.ImagePullPolicy)
|
||||
}
|
||||
|
||||
namespace := "default"
|
||||
if runCollector.Namespace != "" {
|
||||
namespace = runCollector.Namespace
|
||||
if c.Collector.Namespace != "" {
|
||||
namespace = c.Collector.Namespace
|
||||
}
|
||||
|
||||
serviceAccountName := "default"
|
||||
if runCollector.ServiceAccountName != "" {
|
||||
serviceAccountName = runCollector.ServiceAccountName
|
||||
if c.Collector.ServiceAccountName != "" {
|
||||
serviceAccountName = c.Collector.ServiceAccountName
|
||||
}
|
||||
|
||||
runPodCollector := &troubleshootv1beta2.RunPod{
|
||||
runPodSpec := &troubleshootv1beta2.RunPod{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: runCollector.CollectorName,
|
||||
CollectorName: c.Collector.CollectorName,
|
||||
},
|
||||
Name: runCollector.Name,
|
||||
Name: c.Collector.Name,
|
||||
Namespace: namespace,
|
||||
Timeout: runCollector.Timeout,
|
||||
ImagePullSecret: runCollector.ImagePullSecret,
|
||||
Timeout: c.Collector.Timeout,
|
||||
ImagePullSecret: c.Collector.ImagePullSecret,
|
||||
PodSpec: corev1.PodSpec{
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
ServiceAccountName: serviceAccountName,
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Image: runCollector.Image,
|
||||
Image: c.Collector.Image,
|
||||
ImagePullPolicy: pullPolicy,
|
||||
Name: "collector",
|
||||
Command: runCollector.Command,
|
||||
Args: runCollector.Args,
|
||||
Command: c.Collector.Command,
|
||||
Args: c.Collector.Args,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return RunPod(c, runPodCollector)
|
||||
}
|
||||
|
||||
func RunPod(c *Collector, runPodCollector *troubleshootv1beta2.RunPod) (CollectorResult, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create client from config")
|
||||
}
|
||||
|
||||
pod, err := runPodWithSpec(ctx, client, runPodCollector)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to run pod")
|
||||
}
|
||||
defer func() {
|
||||
if err := client.CoreV1().Pods(pod.Namespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{}); err != nil {
|
||||
logger.Printf("Failed to delete pod %s: %v", pod.Name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if runPodCollector.ImagePullSecret != nil && runPodCollector.ImagePullSecret.Data != nil {
|
||||
defer func() {
|
||||
for _, k := range pod.Spec.ImagePullSecrets {
|
||||
if err := client.CoreV1().Secrets(pod.Namespace).Delete(context.Background(), k.Name, metav1.DeleteOptions{}); err != nil {
|
||||
logger.Printf("Failed to delete secret %s: %v", k.Name, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
if runPodCollector.Timeout == "" {
|
||||
return runWithoutTimeout(ctx, c, pod, runPodCollector)
|
||||
}
|
||||
|
||||
timeout, err := time.ParseDuration(runPodCollector.Timeout)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse timeout")
|
||||
}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
resultCh := make(chan CollectorResult, 1)
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
b, err := runWithoutTimeout(timeoutCtx, c, pod, runPodCollector)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
} else {
|
||||
resultCh <- b
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
return nil, errors.New("timeout")
|
||||
case result := <-resultCh:
|
||||
return result, nil
|
||||
case err := <-errCh:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func runPodWithSpec(ctx context.Context, client *kubernetes.Clientset, runPodCollector *troubleshootv1beta2.RunPod) (*corev1.Pod, error) {
|
||||
podLabels := make(map[string]string)
|
||||
podLabels["troubleshoot-role"] = "run-collector"
|
||||
|
||||
namespace := "default"
|
||||
if runPodCollector.Namespace != "" {
|
||||
namespace = runPodCollector.Namespace
|
||||
}
|
||||
|
||||
podName := "run-pod"
|
||||
if runPodCollector.CollectorName != "" {
|
||||
podName = runPodCollector.CollectorName
|
||||
} else if runPodCollector.Name != "" {
|
||||
podName = runPodCollector.Name
|
||||
}
|
||||
|
||||
pod := corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Namespace: namespace,
|
||||
Labels: podLabels,
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
Spec: runPodCollector.PodSpec,
|
||||
}
|
||||
|
||||
if runPodCollector.ImagePullSecret != nil && runPodCollector.ImagePullSecret.Data != nil {
|
||||
secretName, err := createSecret(ctx, client, pod.Namespace, runPodCollector.ImagePullSecret)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create secret")
|
||||
}
|
||||
pod.Spec.ImagePullSecrets = append(pod.Spec.ImagePullSecrets, corev1.LocalObjectReference{Name: secretName})
|
||||
}
|
||||
|
||||
created, err := client.CoreV1().Pods(namespace).Create(ctx, &pod, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create pod")
|
||||
}
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func runWithoutTimeout(ctx context.Context, c *Collector, pod *corev1.Pod, runPodCollector *troubleshootv1beta2.RunPod) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed create client from config")
|
||||
}
|
||||
|
||||
for {
|
||||
status, err := client.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get pod")
|
||||
}
|
||||
if status.Status.Phase == corev1.PodRunning ||
|
||||
status.Status.Phase == corev1.PodFailed ||
|
||||
status.Status.Phase == corev1.PodSucceeded {
|
||||
break
|
||||
}
|
||||
if status.Status.Phase == corev1.PodPending {
|
||||
for _, v := range status.Status.ContainerStatuses {
|
||||
if v.State.Waiting != nil && v.State.Waiting.Reason == "ImagePullBackOff" {
|
||||
return nil, errors.Errorf("run pod aborted after getting pod status 'ImagePullBackOff'")
|
||||
}
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
|
||||
output := NewResult()
|
||||
|
||||
collectorName := runPodCollector.Name
|
||||
|
||||
limits := troubleshootv1beta2.LogLimits{
|
||||
MaxLines: 10000,
|
||||
}
|
||||
podLogs, err := savePodLogs(ctx, c.BundlePath, client, *pod, collectorName, "", &limits, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get pod logs")
|
||||
}
|
||||
|
||||
for k, v := range podLogs {
|
||||
output[k] = v
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func createSecret(ctx context.Context, client kubernetes.Interface, namespace string, imagePullSecret *troubleshootv1beta2.ImagePullSecrets) (string, error) {
|
||||
if imagePullSecret.Data == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
data := make(map[string][]byte)
|
||||
if imagePullSecret.SecretType != "kubernetes.io/dockerconfigjson" {
|
||||
return "", errors.Errorf("ImagePullSecret must be of type: kubernetes.io/dockerconfigjson")
|
||||
}
|
||||
|
||||
// Check if required field in data exists
|
||||
v, found := imagePullSecret.Data[".dockerconfigjson"]
|
||||
if !found {
|
||||
return "", errors.Errorf("Secret type kubernetes.io/dockerconfigjson requires argument \".dockerconfigjson\"")
|
||||
}
|
||||
if len(imagePullSecret.Data) > 1 {
|
||||
return "", errors.Errorf("Secret type kubernetes.io/dockerconfigjson accepts only one argument \".dockerconfigjson\"")
|
||||
}
|
||||
// K8s client accepts only Json formated files as data, provided data must be decoded and indented
|
||||
parsedConfig, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Unable to decode data.")
|
||||
}
|
||||
err = json.Indent(&out, parsedConfig, "", "\t")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Unable to parse encoded data.")
|
||||
}
|
||||
data[".dockerconfigjson"] = out.Bytes()
|
||||
|
||||
secret := corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: imagePullSecret.Name,
|
||||
GenerateName: "troubleshoot",
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "troubleshoot.sh",
|
||||
},
|
||||
},
|
||||
Data: data,
|
||||
Type: corev1.SecretType(imagePullSecret.SecretType),
|
||||
}
|
||||
|
||||
created, err := client.CoreV1().Secrets(namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create secret")
|
||||
}
|
||||
|
||||
return created.Name, nil
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
runPodCollector := &CollectRunPod{runPodSpec, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.ctx, rbacErrors}
|
||||
|
||||
return runPodCollector.Collect(progressChan)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,253 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type CollectRunPod struct {
|
||||
Collector *troubleshootv1beta2.RunPod
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectRunPod) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Run Pod")
|
||||
}
|
||||
|
||||
func (c *CollectRunPod) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectRunPod) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create client from config")
|
||||
}
|
||||
|
||||
pod, err := runPodWithSpec(ctx, client, c.Collector)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to run pod")
|
||||
}
|
||||
defer func() {
|
||||
if err := client.CoreV1().Pods(pod.Namespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{}); err != nil {
|
||||
logger.Printf("Failed to delete pod %s: %v", pod.Name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if c.Collector.ImagePullSecret != nil && c.Collector.ImagePullSecret.Data != nil {
|
||||
defer func() {
|
||||
for _, k := range pod.Spec.ImagePullSecrets {
|
||||
if err := client.CoreV1().Secrets(pod.Namespace).Delete(context.Background(), k.Name, metav1.DeleteOptions{}); err != nil {
|
||||
logger.Printf("Failed to delete secret %s: %v", k.Name, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
if c.Collector.Timeout == "" {
|
||||
return runWithoutTimeout(ctx, c.BundlePath, c.ClientConfig, pod, c.Collector)
|
||||
}
|
||||
|
||||
timeout, err := time.ParseDuration(c.Collector.Timeout)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse timeout")
|
||||
}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
resultCh := make(chan CollectorResult, 1)
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
b, err := runWithoutTimeout(timeoutCtx, c.BundlePath, c.ClientConfig, pod, c.Collector)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
} else {
|
||||
resultCh <- b
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
return nil, errors.New("timeout")
|
||||
case result := <-resultCh:
|
||||
return result, nil
|
||||
case err := <-errCh:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func runPodWithSpec(ctx context.Context, client *kubernetes.Clientset, runPodCollector *troubleshootv1beta2.RunPod) (*corev1.Pod, error) {
|
||||
podLabels := make(map[string]string)
|
||||
podLabels["troubleshoot-role"] = "run-collector"
|
||||
|
||||
namespace := "default"
|
||||
if runPodCollector.Namespace != "" {
|
||||
namespace = runPodCollector.Namespace
|
||||
}
|
||||
|
||||
podName := "run-pod"
|
||||
if runPodCollector.CollectorName != "" {
|
||||
podName = runPodCollector.CollectorName
|
||||
} else if runPodCollector.Name != "" {
|
||||
podName = runPodCollector.Name
|
||||
}
|
||||
|
||||
pod := corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Namespace: namespace,
|
||||
Labels: podLabels,
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
Spec: runPodCollector.PodSpec,
|
||||
}
|
||||
|
||||
if runPodCollector.ImagePullSecret != nil && runPodCollector.ImagePullSecret.Data != nil {
|
||||
secretName, err := createSecret(ctx, client, pod.Namespace, runPodCollector.ImagePullSecret)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create secret")
|
||||
}
|
||||
pod.Spec.ImagePullSecrets = append(pod.Spec.ImagePullSecrets, corev1.LocalObjectReference{Name: secretName})
|
||||
}
|
||||
|
||||
created, err := client.CoreV1().Pods(namespace).Create(ctx, &pod, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create pod")
|
||||
}
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func runWithoutTimeout(ctx context.Context, bundlePath string, clientConfig *rest.Config, pod *corev1.Pod, runPodCollector *troubleshootv1beta2.RunPod) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed create client from config")
|
||||
}
|
||||
|
||||
for {
|
||||
status, err := client.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get pod")
|
||||
}
|
||||
if status.Status.Phase == corev1.PodRunning ||
|
||||
status.Status.Phase == corev1.PodFailed ||
|
||||
status.Status.Phase == corev1.PodSucceeded {
|
||||
break
|
||||
}
|
||||
if status.Status.Phase == corev1.PodPending {
|
||||
for _, v := range status.Status.ContainerStatuses {
|
||||
if v.State.Waiting != nil && v.State.Waiting.Reason == "ImagePullBackOff" {
|
||||
return nil, errors.Errorf("run pod aborted after getting pod status 'ImagePullBackOff'")
|
||||
}
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
|
||||
output := NewResult()
|
||||
|
||||
collectorName := runPodCollector.Name
|
||||
|
||||
limits := troubleshootv1beta2.LogLimits{
|
||||
MaxLines: 10000,
|
||||
}
|
||||
podLogs, err := savePodLogs(ctx, bundlePath, client, *pod, collectorName, "", &limits, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get pod logs")
|
||||
}
|
||||
|
||||
for k, v := range podLogs {
|
||||
output[k] = v
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func createSecret(ctx context.Context, client kubernetes.Interface, namespace string, imagePullSecret *troubleshootv1beta2.ImagePullSecrets) (string, error) {
|
||||
if imagePullSecret.Data == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
data := make(map[string][]byte)
|
||||
if imagePullSecret.SecretType != "kubernetes.io/dockerconfigjson" {
|
||||
return "", errors.Errorf("ImagePullSecret must be of type: kubernetes.io/dockerconfigjson")
|
||||
}
|
||||
|
||||
// Check if required field in data exists
|
||||
v, found := imagePullSecret.Data[".dockerconfigjson"]
|
||||
if !found {
|
||||
return "", errors.Errorf("Secret type kubernetes.io/dockerconfigjson requires argument \".dockerconfigjson\"")
|
||||
}
|
||||
if len(imagePullSecret.Data) > 1 {
|
||||
return "", errors.Errorf("Secret type kubernetes.io/dockerconfigjson accepts only one argument \".dockerconfigjson\"")
|
||||
}
|
||||
// K8s client accepts only Json formated files as data, provided data must be decoded and indented
|
||||
parsedConfig, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Unable to decode data.")
|
||||
}
|
||||
err = json.Indent(&out, parsedConfig, "", "\t")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Unable to parse encoded data.")
|
||||
}
|
||||
data[".dockerconfigjson"] = out.Bytes()
|
||||
|
||||
secret := corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: imagePullSecret.Name,
|
||||
GenerateName: "troubleshoot",
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "troubleshoot.sh",
|
||||
},
|
||||
},
|
||||
Data: data,
|
||||
Type: corev1.SecretType(imagePullSecret.SecretType),
|
||||
}
|
||||
|
||||
created, err := client.CoreV1().Secrets(namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create secret")
|
||||
}
|
||||
|
||||
return created.Name, nil
|
||||
}
|
||||
|
||||
// RunPodOptions and RunPodReadyNodes currently only used for the Sysctl collector
|
||||
// TODO: refactor sysctl collector and runPod collector to share the same code
|
||||
type RunPodOptions struct {
|
||||
Image string
|
||||
ImagePullPolicy string
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type SecretOutput struct {
|
||||
@@ -25,28 +26,46 @@ type SecretOutput struct {
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func Secret(ctx context.Context, c *Collector, secretCollector *troubleshootv1beta2.Secret, client kubernetes.Interface) (CollectorResult, error) {
|
||||
type CollectSecret struct {
|
||||
Collector *troubleshootv1beta2.Secret
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectSecret) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Secret")
|
||||
}
|
||||
|
||||
func (c *CollectSecret) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectSecret) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
output := NewResult()
|
||||
|
||||
secrets := []corev1.Secret{}
|
||||
if secretCollector.Name != "" {
|
||||
secret, err := client.CoreV1().Secrets(secretCollector.Namespace).Get(ctx, secretCollector.Name, metav1.GetOptions{})
|
||||
if c.Collector.Name != "" {
|
||||
secret, err := c.Client.CoreV1().Secrets(c.Collector.Namespace).Get(c.ctx, c.Collector.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if kuberneteserrors.IsNotFound(err) {
|
||||
filePath, encoded, err := secretToOutput(secretCollector, nil, secretCollector.Name)
|
||||
filePath, encoded, err := secretToOutput(c.Collector, nil, c.Collector.Name)
|
||||
if err != nil {
|
||||
return output, errors.Wrapf(err, "collect secret %s", secretCollector.Name)
|
||||
return output, errors.Wrapf(err, "collect secret %s", c.Collector.Name)
|
||||
}
|
||||
output.SaveResult(c.BundlePath, filePath, bytes.NewBuffer(encoded))
|
||||
}
|
||||
output.SaveResult(c.BundlePath, GetSecretErrorsFileName(secretCollector), marshalErrors([]string{err.Error()}))
|
||||
output.SaveResult(c.BundlePath, GetSecretErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
|
||||
return output, nil
|
||||
}
|
||||
secrets = append(secrets, *secret)
|
||||
} else if len(secretCollector.Selector) > 0 {
|
||||
ss, err := listSecretsForSelector(ctx, client, secretCollector.Namespace, secretCollector.Selector)
|
||||
} else if len(c.Collector.Selector) > 0 {
|
||||
ss, err := listSecretsForSelector(c.ctx, c.Client, c.Collector.Namespace, c.Collector.Selector)
|
||||
if err != nil {
|
||||
output.SaveResult(c.BundlePath, GetSecretErrorsFileName(secretCollector), marshalErrors([]string{err.Error()}))
|
||||
output.SaveResult(c.BundlePath, GetSecretErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
|
||||
return output, nil
|
||||
}
|
||||
secrets = append(secrets, ss...)
|
||||
@@ -55,7 +74,7 @@ func Secret(ctx context.Context, c *Collector, secretCollector *troubleshootv1be
|
||||
}
|
||||
|
||||
for _, secret := range secrets {
|
||||
filePath, encoded, err := secretToOutput(secretCollector, &secret, secret.Name)
|
||||
filePath, encoded, err := secretToOutput(c.Collector, &secret, secret.Name)
|
||||
if err != nil {
|
||||
return output, errors.Wrapf(err, "collect secret %s", secret.Name)
|
||||
}
|
||||
|
||||
@@ -223,8 +223,8 @@ func TestSecret(t *testing.T) {
|
||||
_, err := client.CoreV1().Secrets(secret.Namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
c := &Collector{}
|
||||
got, err := Secret(ctx, c, tt.secretCollector, client)
|
||||
secretCollector := &CollectSecret{tt.secretCollector, "", "", nil, client, ctx, nil}
|
||||
got, err := secretCollector.Collect(nil)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
|
||||
@@ -8,31 +8,59 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Sysctl(ctx context.Context, c *Collector, client kubernetes.Interface, collector *troubleshootv1beta2.Sysctl) (CollectorResult, error) {
|
||||
type CollectSysctl struct {
|
||||
Collector *troubleshootv1beta2.Sysctl
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
if collector.Timeout != "" {
|
||||
timeout, err := time.ParseDuration(collector.Timeout)
|
||||
func (c *CollectSysctl) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Sysctl")
|
||||
}
|
||||
|
||||
func (c *CollectSysctl) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectSysctl) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
if c.Collector.Timeout != "" {
|
||||
timeout, err := time.ParseDuration(c.Collector.Timeout)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse timeout")
|
||||
}
|
||||
if timeout == 0 {
|
||||
timeout = time.Minute
|
||||
}
|
||||
childCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
childCtx, cancel := context.WithTimeout(c.ctx, timeout)
|
||||
defer cancel()
|
||||
ctx = childCtx
|
||||
c.ctx = childCtx
|
||||
}
|
||||
|
||||
if c.Collector.Namespace == "" {
|
||||
c.Collector.Namespace = c.Namespace
|
||||
}
|
||||
if c.Collector.Namespace == "" {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
namespace, _, _ := kubeconfig.Namespace()
|
||||
c.Collector.Namespace = namespace
|
||||
}
|
||||
|
||||
runPodOptions := RunPodOptions{
|
||||
Image: collector.Image,
|
||||
ImagePullPolicy: collector.ImagePullPolicy,
|
||||
Namespace: collector.Namespace,
|
||||
Image: c.Collector.Image,
|
||||
ImagePullPolicy: c.Collector.ImagePullPolicy,
|
||||
Namespace: c.Collector.Namespace,
|
||||
HostNetwork: true,
|
||||
}
|
||||
|
||||
@@ -42,18 +70,18 @@ find /proc/sys/net/bridge -type f | while read f; do v=$(cat $f 2>/dev/null); ec
|
||||
`
|
||||
runPodOptions.Command = []string{"sh", "-c", command}
|
||||
|
||||
if collector.ImagePullSecret != nil {
|
||||
runPodOptions.ImagePullSecretName = collector.ImagePullSecret.Name
|
||||
if c.Collector.ImagePullSecret != nil {
|
||||
runPodOptions.ImagePullSecretName = c.Collector.ImagePullSecret.Name
|
||||
|
||||
if collector.ImagePullSecret.Data != nil {
|
||||
secretName, err := createSecret(ctx, client, collector.Namespace, collector.ImagePullSecret)
|
||||
if c.Collector.ImagePullSecret.Data != nil {
|
||||
secretName, err := createSecret(c.ctx, c.Client, c.Collector.Namespace, c.Collector.ImagePullSecret)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create image pull secret")
|
||||
}
|
||||
defer func() {
|
||||
err := client.CoreV1().Secrets(collector.Namespace).Delete(context.Background(), collector.ImagePullSecret.Name, metav1.DeleteOptions{})
|
||||
err := c.Client.CoreV1().Secrets(c.Collector.Namespace).Delete(context.Background(), c.Collector.ImagePullSecret.Name, metav1.DeleteOptions{})
|
||||
if err != nil && !kuberneteserrors.IsNotFound(err) {
|
||||
logger.Printf("Failed to delete secret %s: %v", collector.ImagePullSecret.Name, err)
|
||||
logger.Printf("Failed to delete secret %s: %v", c.Collector.ImagePullSecret.Name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -61,7 +89,7 @@ find /proc/sys/net/bridge -type f | while read f; do v=$(cat $f 2>/dev/null); ec
|
||||
}
|
||||
}
|
||||
|
||||
results, err := RunPodsReadyNodes(ctx, client.CoreV1(), runPodOptions)
|
||||
results, err := RunPodsReadyNodes(c.ctx, c.Client.CoreV1(), runPodOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ type CollectResult interface {
|
||||
|
||||
type ClusterCollectResult struct {
|
||||
AllCollectedData map[string][]byte
|
||||
Collectors collect.Collectors
|
||||
Collectors []collect.Collector
|
||||
RemoteCollectors collect.RemoteCollectors
|
||||
isRBACAllowed bool
|
||||
Spec *troubleshootv1beta2.Preflight
|
||||
@@ -126,110 +126,117 @@ func Collect(opts CollectOpts, p *troubleshootv1beta2.Preflight) (CollectResult,
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterInfo: &troubleshootv1beta2.ClusterInfo{}})
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterResources: &troubleshootv1beta2.ClusterResources{}})
|
||||
|
||||
var allCollectors []collect.Collector
|
||||
|
||||
allCollectedData := make(map[string][]byte)
|
||||
|
||||
var collectors collect.Collectors
|
||||
for _, desiredCollector := range collectSpecs {
|
||||
collector := collect.Collector{
|
||||
Redact: true,
|
||||
Collect: desiredCollector,
|
||||
ClientConfig: opts.KubernetesRestConfig,
|
||||
Namespace: opts.Namespace,
|
||||
}
|
||||
collectors = append(collectors, &collector)
|
||||
}
|
||||
|
||||
collectResult := ClusterCollectResult{
|
||||
Collectors: collectors,
|
||||
Spec: p,
|
||||
}
|
||||
|
||||
k8sClient, err := kubernetes.NewForConfig(opts.KubernetesRestConfig)
|
||||
if err != nil {
|
||||
return collectResult, errors.Wrap(err, "failed to instantiate Kubernetes client")
|
||||
return nil, errors.Wrap(err, "failed to instantiate Kubernetes client")
|
||||
}
|
||||
|
||||
if err := collectors.CheckRBAC(context.Background()); err != nil {
|
||||
return collectResult, errors.Wrap(err, "failed to check RBAC for collectors")
|
||||
for _, desiredCollector := range collectSpecs {
|
||||
if collectorInterface, ok := collect.GetCollector(desiredCollector, "", opts.Namespace, opts.KubernetesRestConfig, k8sClient, nil); ok {
|
||||
if collector, ok := collectorInterface.(collect.Collector); ok {
|
||||
err := collector.CheckRBAC(context.Background(), collector, desiredCollector, opts.KubernetesRestConfig, opts.Namespace)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to check RBAC for collectors")
|
||||
}
|
||||
|
||||
if mergeCollector, ok := collectorInterface.(collect.MergeableCollector); ok {
|
||||
allCollectors, err = mergeCollector.Merge(allCollectors)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failed to merge collector: %s: %s", collector.Title(), err)
|
||||
opts.ProgressChan <- msg
|
||||
}
|
||||
} else {
|
||||
allCollectors = append(allCollectors, collector)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectResult := ClusterCollectResult{
|
||||
Collectors: allCollectors,
|
||||
Spec: p,
|
||||
}
|
||||
|
||||
foundForbidden := false
|
||||
for _, c := range collectors {
|
||||
for _, e := range c.RBACErrors {
|
||||
for _, c := range allCollectors {
|
||||
for _, e := range c.GetRBACErrors() {
|
||||
foundForbidden = true
|
||||
opts.ProgressChan <- e
|
||||
}
|
||||
}
|
||||
|
||||
if foundForbidden && !opts.IgnorePermissionErrors {
|
||||
collectResult.isRBACAllowed = false
|
||||
return collectResult, errors.New("insufficient permissions to run all collectors")
|
||||
return nil, errors.New("insufficient permissions to run all collectors")
|
||||
}
|
||||
|
||||
// generate a map of all collectors for atomic status messages
|
||||
collectorList := map[string]CollectorStatus{}
|
||||
for _, collector := range collectors {
|
||||
collectorList[collector.GetDisplayName()] = CollectorStatus{
|
||||
for _, collector := range allCollectors {
|
||||
collectorList[collector.Title()] = CollectorStatus{
|
||||
Status: "pending",
|
||||
}
|
||||
}
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for i, collector := range collectors {
|
||||
if len(collector.RBACErrors) > 0 {
|
||||
// don't skip clusterResources collector due to RBAC issues
|
||||
if collector.Collect.ClusterResources == nil {
|
||||
collectorList[collector.GetDisplayName()] = CollectorStatus{
|
||||
Status: "skipped",
|
||||
}
|
||||
collectResult.isRBACAllowed = false // not failing, but going to report this
|
||||
opts.ProgressChan <- fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.GetDisplayName())
|
||||
for i, collector := range allCollectors {
|
||||
isExcluded, _ := collector.IsExcluded()
|
||||
if isExcluded {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip collectors with RBAC errors unless its the ClusterResources collector
|
||||
if collector.HasRBACErrors() {
|
||||
if _, ok := collector.(*collect.CollectClusterResources); !ok {
|
||||
opts.ProgressChan <- fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.Title())
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentName: collector.Title(),
|
||||
CurrentStatus: "skipped",
|
||||
CompletedCount: i + 1,
|
||||
TotalCount: len(collectors),
|
||||
TotalCount: len(allCollectors),
|
||||
Collectors: collectorList,
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
collectorList[collector.GetDisplayName()] = CollectorStatus{
|
||||
collectorList[collector.Title()] = CollectorStatus{
|
||||
Status: "running",
|
||||
}
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentName: collector.Title(),
|
||||
CurrentStatus: "running",
|
||||
CompletedCount: i,
|
||||
TotalCount: len(collectors),
|
||||
TotalCount: len(allCollectors),
|
||||
Collectors: collectorList,
|
||||
}
|
||||
|
||||
result, err := collector.RunCollectorSync(opts.KubernetesRestConfig, k8sClient, nil)
|
||||
result, err := collector.Collect(opts.ProgressChan)
|
||||
if err != nil {
|
||||
collectorList[collector.GetDisplayName()] = CollectorStatus{
|
||||
collectorList[collector.Title()] = CollectorStatus{
|
||||
Status: "failed",
|
||||
}
|
||||
opts.ProgressChan <- errors.Errorf("failed to run collector %s: %v\n", collector.GetDisplayName(), err)
|
||||
opts.ProgressChan <- errors.Errorf("failed to run collector: %s: %v", collector.Title(), err)
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentName: collector.Title(),
|
||||
CurrentStatus: "failed",
|
||||
CompletedCount: i + 1,
|
||||
TotalCount: len(collectors),
|
||||
TotalCount: len(allCollectors),
|
||||
Collectors: collectorList,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
collectorList[collector.GetDisplayName()] = CollectorStatus{
|
||||
collectorList[collector.Title()] = CollectorStatus{
|
||||
Status: "completed",
|
||||
}
|
||||
opts.ProgressChan <- CollectProgress{
|
||||
CurrentName: collector.GetDisplayName(),
|
||||
CurrentName: collector.Title(),
|
||||
CurrentStatus: "completed",
|
||||
CompletedCount: i + 1,
|
||||
TotalCount: len(collectors),
|
||||
TotalCount: len(allCollectors),
|
||||
Collectors: collectorList,
|
||||
}
|
||||
|
||||
@@ -239,6 +246,7 @@ func Collect(opts CollectOpts, p *troubleshootv1beta2.Preflight) (CollectResult,
|
||||
}
|
||||
|
||||
collectResult.AllCollectedData = allCollectedData
|
||||
|
||||
return collectResult, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
analyze "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
@@ -16,7 +15,6 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||
"gopkg.in/yaml.v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
@@ -70,38 +68,45 @@ func runHostCollectors(hostCollectors []*troubleshootv1beta2.HostCollect, additi
|
||||
return collectResult, nil
|
||||
}
|
||||
|
||||
// TODO (dan): This is VERY similar to the Preflight collect package and should be refactored.
|
||||
func runCollectors(collectors []*troubleshootv1beta2.Collect, additionalRedactors *troubleshootv1beta2.Redactor, bundlePath string, opts SupportBundleCreateOpts) (collect.CollectorResult, error) {
|
||||
|
||||
collectSpecs := make([]*troubleshootv1beta2.Collect, 0)
|
||||
collectSpecs = append(collectSpecs, collectors...)
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterInfo: &troubleshootv1beta2.ClusterInfo{}})
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterResources: &troubleshootv1beta2.ClusterResources{}})
|
||||
|
||||
var cleanedCollectors collect.Collectors
|
||||
for _, desiredCollector := range collectSpecs {
|
||||
collector := collect.Collector{
|
||||
Redact: opts.Redact,
|
||||
Collect: desiredCollector,
|
||||
ClientConfig: opts.KubernetesRestConfig,
|
||||
Namespace: opts.Namespace,
|
||||
BundlePath: bundlePath,
|
||||
}
|
||||
cleanedCollectors = append(cleanedCollectors, &collector)
|
||||
}
|
||||
var allCollectors []collect.Collector
|
||||
|
||||
allCollectedData := make(map[string][]byte)
|
||||
|
||||
k8sClient, err := kubernetes.NewForConfig(opts.KubernetesRestConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to instantiate Kubernetes client")
|
||||
}
|
||||
|
||||
if err := cleanedCollectors.CheckRBAC(context.Background()); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to check RBAC for collectors")
|
||||
for _, desiredCollector := range collectSpecs {
|
||||
if collectorInterface, ok := collect.GetCollector(desiredCollector, bundlePath, opts.Namespace, opts.KubernetesRestConfig, k8sClient, opts.SinceTime); ok {
|
||||
if collector, ok := collectorInterface.(collect.Collector); ok {
|
||||
err := collector.CheckRBAC(context.Background(), collector, desiredCollector, opts.KubernetesRestConfig, opts.Namespace)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to check RBAC for collectors")
|
||||
}
|
||||
|
||||
if mergeCollector, ok := collectorInterface.(collect.MergeableCollector); ok {
|
||||
allCollectors, err = mergeCollector.Merge(allCollectors)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failed to merge collector: %s: %s", collector.Title(), err)
|
||||
opts.CollectorProgressCallback(opts.ProgressChan, msg)
|
||||
}
|
||||
} else {
|
||||
allCollectors = append(allCollectors, collector)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foundForbidden := false
|
||||
for _, c := range cleanedCollectors {
|
||||
for _, e := range c.RBACErrors {
|
||||
for _, c := range allCollectors {
|
||||
for _, e := range c.GetRBACErrors() {
|
||||
foundForbidden = true
|
||||
opts.ProgressChan <- e
|
||||
}
|
||||
@@ -111,42 +116,47 @@ func runCollectors(collectors []*troubleshootv1beta2.Collect, additionalRedactor
|
||||
return nil, errors.New("insufficient permissions to run all collectors")
|
||||
}
|
||||
|
||||
globalRedactors := []*troubleshootv1beta2.Redact{}
|
||||
if additionalRedactors != nil {
|
||||
globalRedactors = additionalRedactors.Spec.Redactors
|
||||
}
|
||||
for _, collector := range allCollectors {
|
||||
isExcluded, _ := collector.IsExcluded()
|
||||
if isExcluded {
|
||||
continue
|
||||
}
|
||||
|
||||
if opts.SinceTime != nil {
|
||||
applyLogSinceTime(*opts.SinceTime, &cleanedCollectors)
|
||||
}
|
||||
|
||||
result := collect.NewResult()
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for _, collector := range cleanedCollectors {
|
||||
if len(collector.RBACErrors) > 0 {
|
||||
// don't skip clusterResources collector due to RBAC issues
|
||||
if collector.Collect.ClusterResources == nil {
|
||||
msg := fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.GetDisplayName())
|
||||
// skip collectors with RBAC errors unless its the ClusterResources collector
|
||||
if collector.HasRBACErrors() {
|
||||
if _, ok := collector.(*collect.CollectClusterResources); !ok {
|
||||
msg := fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.Title())
|
||||
opts.CollectorProgressCallback(opts.ProgressChan, msg)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
opts.CollectorProgressCallback(opts.ProgressChan, collector.GetDisplayName())
|
||||
|
||||
files, err := collector.RunCollectorSync(opts.KubernetesRestConfig, k8sClient, globalRedactors)
|
||||
opts.ProgressChan <- fmt.Sprintf("[%s] Running collector...", collector.Title())
|
||||
result, err := collector.Collect(opts.ProgressChan)
|
||||
if err != nil {
|
||||
opts.ProgressChan <- fmt.Errorf("failed to run collector %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
opts.ProgressChan <- errors.Errorf("failed to run collector: %s: %v", collector.Title(), err)
|
||||
}
|
||||
|
||||
for k, v := range files {
|
||||
result[k] = v
|
||||
for k, v := range result {
|
||||
allCollectedData[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
collectResult := allCollectedData
|
||||
|
||||
globalRedactors := []*troubleshootv1beta2.Redact{}
|
||||
if additionalRedactors != nil {
|
||||
globalRedactors = additionalRedactors.Spec.Redactors
|
||||
}
|
||||
|
||||
if opts.Redact {
|
||||
err := collect.RedactResult(bundlePath, collectResult, globalRedactors)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to redact")
|
||||
return collectResult, err
|
||||
}
|
||||
}
|
||||
|
||||
return collectResult, nil
|
||||
}
|
||||
|
||||
func findFileName(basename, extension string) (string, error) {
|
||||
@@ -207,15 +217,3 @@ func getAnalysisFile(analyzeResults []*analyze.AnalyzeResult) (io.Reader, error)
|
||||
|
||||
return bytes.NewBuffer(analysis), nil
|
||||
}
|
||||
|
||||
func applyLogSinceTime(sinceTime time.Time, collectors *collect.Collectors) {
|
||||
|
||||
for _, collector := range *collectors {
|
||||
if collector.Collect.Logs != nil {
|
||||
if collector.Collect.Logs.Limits == nil {
|
||||
collector.Collect.Logs.Limits = new(troubleshootv1beta2.LogLimits)
|
||||
}
|
||||
collector.Collect.Logs.Limits.SinceTime = metav1.NewTime(sinceTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user