diff --git a/pkg/apis/troubleshoot/v1beta2/collector_shared.go b/pkg/apis/troubleshoot/v1beta2/collector_shared.go index e3c1398d0..fda95631b 100644 --- a/pkg/apis/troubleshoot/v1beta2/collector_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/collector_shared.go @@ -6,6 +6,7 @@ import ( "github.com/replicatedhq/troubleshoot/pkg/multitype" authorizationv1 "k8s.io/api/authorization/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -77,6 +78,15 @@ type Run struct { ServiceAccountName string `json:"serviceAccountName,omitempty" yaml:"serviceAccountName,omitempty"` } +type RunPod struct { + CollectorMeta `json:",inline" yaml:",inline"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Namespace string `json:"namespace" yaml:"namespace"` + Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"` + ImagePullSecret *ImagePullSecrets `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"` + PodSpec corev1.PodSpec `json:"podSpec,omitempty" yaml:"podSpec,omitempty"` +} + type ImagePullSecrets struct { Name string `json:"name,omitempty" yaml:"name,omitempty"` Data map[string]string `json:"data,omitempty" yaml:"data,omitempty"` @@ -196,6 +206,7 @@ type Collect struct { ConfigMap *ConfigMap `json:"configMap,omitempty" yaml:"configMap,omitempty"` Logs *Logs `json:"logs,omitempty" yaml:"logs,omitempty"` Run *Run `json:"run,omitempty" yaml:"run,omitempty"` + RunPod *RunPod `json:"runPod,omitempty" yaml:"runPod,omitempty"` Exec *Exec `json:"exec,omitempty" yaml:"exec,omitempty"` Data *Data `json:"data,omitempty" yaml:"data,omitempty"` Copy *Copy `json:"copy,omitempty" yaml:"copy,omitempty"` @@ -329,6 +340,19 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub }, NonResourceAttributes: nil, }) + } else if c.RunPod != nil { + result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Namespace: pickNamespaceOrDefault(c.RunPod.Namespace, overrideNS), + Verb: "create", + Group: "", + Version: "", + Resource: "pods", + Subresource: "", + Name: "", + }, + NonResourceAttributes: nil, + }) } else if c.Exec != nil { result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{ ResourceAttributes: &authorizationv1.ResourceAttributes{ @@ -434,6 +458,10 @@ func (c *Collect) GetName() string { collector = "run" name = c.Run.CollectorName } + if c.RunPod != nil { + collector = "run-pod" + name = c.RunPod.CollectorName + } if c.Exec != nil { collector = "exec" name = c.Exec.CollectorName diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index c05172b8d..2b2195865 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -650,6 +650,11 @@ func (in *Collect) DeepCopyInto(out *Collect) { *out = new(Run) (*in).DeepCopyInto(*out) } + if in.RunPod != nil { + in, out := &in.RunPod, &out.RunPod + *out = new(RunPod) + (*in).DeepCopyInto(*out) + } if in.Exec != nil { in, out := &in.Exec, &out.Exec *out = new(Exec) @@ -3520,6 +3525,28 @@ func (in *Run) DeepCopy() *Run { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RunPod) DeepCopyInto(out *RunPod) { + *out = *in + out.CollectorMeta = in.CollectorMeta + if in.ImagePullSecret != nil { + in, out := &in.ImagePullSecret, &out.ImagePullSecret + *out = new(ImagePullSecrets) + (*in).DeepCopyInto(*out) + } + in.PodSpec.DeepCopyInto(&out.PodSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunPod. +func (in *RunPod) DeepCopy() *RunPod { + if in == nil { + return nil + } + out := new(RunPod) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Secret) DeepCopyInto(out *Secret) { *out = *in diff --git a/pkg/collect/collector.go b/pkg/collect/collector.go index 2eda407c9..1b4adafba 100644 --- a/pkg/collect/collector.go +++ b/pkg/collect/collector.go @@ -97,6 +97,14 @@ func (c *Collector) IsExcluded() bool { 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 { @@ -225,6 +233,8 @@ func (c *Collector) RunCollectorSync(clientConfig *rest.Config, client kubernete 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 { diff --git a/pkg/collect/run.go b/pkg/collect/run.go index 6a42424aa..c16b1188b 100644 --- a/pkg/collect/run.go +++ b/pkg/collect/run.go @@ -16,6 +16,45 @@ import ( ) func Run(c *Collector, runCollector *troubleshootv1beta2.Run) (CollectorResult, error) { + pullPolicy := corev1.PullIfNotPresent + if runCollector.ImagePullPolicy != "" { + pullPolicy = corev1.PullPolicy(runCollector.ImagePullPolicy) + } + + namespace := "default" + if runCollector.Namespace != "" { + namespace = runCollector.Namespace + } + + serviceAccountName := "default" + if runCollector.ServiceAccountName != "" { + serviceAccountName = runCollector.ServiceAccountName + } + + runPodCollector := &troubleshootv1beta2.RunPod{ + Name: runCollector.CollectorName, + Namespace: namespace, + Timeout: runCollector.Timeout, + ImagePullSecret: runCollector.ImagePullSecret, + PodSpec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + ServiceAccountName: serviceAccountName, + Containers: []corev1.Container{ + { + Image: runCollector.Image, + ImagePullPolicy: pullPolicy, + Name: "collector", + Command: runCollector.Command, + Args: runCollector.Args, + }, + }, + }, + } + + return RunPod(c, runPodCollector) +} + +func RunPod(c *Collector, runPodCollector *troubleshootv1beta2.RunPod) (CollectorResult, error) { ctx := context.Background() client, err := kubernetes.NewForConfig(c.ClientConfig) @@ -23,7 +62,7 @@ func Run(c *Collector, runCollector *troubleshootv1beta2.Run) (CollectorResult, return nil, errors.Wrap(err, "failed to create client from config") } - pod, err := runPod(ctx, client, runCollector, c.Namespace) + pod, err := runPodWithSpec(ctx, client, runPodCollector) if err != nil { return nil, errors.Wrap(err, "failed to run pod") } @@ -33,7 +72,7 @@ func Run(c *Collector, runCollector *troubleshootv1beta2.Run) (CollectorResult, logger.Printf("Failed to delete pod %s: %v", pod.Name, err) } }() - if runCollector.ImagePullSecret != nil && runCollector.ImagePullSecret.Data != nil { + if runPodCollector.ImagePullSecret != nil && runPodCollector.ImagePullSecret.Data != nil { defer func() { for _, k := range pod.Spec.ImagePullSecrets { if err := client.CoreV1().Secrets(pod.Namespace).Delete(ctx, k.Name, metav1.DeleteOptions{}); err != nil { @@ -42,11 +81,11 @@ func Run(c *Collector, runCollector *troubleshootv1beta2.Run) (CollectorResult, } }() } - if runCollector.Timeout == "" { - return runWithoutTimeout(ctx, c, pod, runCollector) + if runPodCollector.Timeout == "" { + return runWithoutTimeout(ctx, c, pod, runPodCollector) } - timeout, err := time.ParseDuration(runCollector.Timeout) + timeout, err := time.ParseDuration(runPodCollector.Timeout) if err != nil { return nil, errors.Wrap(err, "failed to parse timeout") } @@ -58,7 +97,7 @@ func Run(c *Collector, runCollector *troubleshootv1beta2.Run) (CollectorResult, defer cancel() go func() { - b, err := runWithoutTimeout(timeoutCtx, c, pod, runCollector) + b, err := runWithoutTimeout(timeoutCtx, c, pod, runPodCollector) if err != nil { errCh <- err } else { @@ -76,7 +115,52 @@ func Run(c *Collector, runCollector *troubleshootv1beta2.Run) (CollectorResult, } } -func runWithoutTimeout(ctx context.Context, c *Collector, pod *corev1.Pod, runCollector *troubleshootv1beta2.Run) (CollectorResult, error) { +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") @@ -104,10 +188,15 @@ func runWithoutTimeout(ctx context.Context, c *Collector, pod *corev1.Pod, runCo output := NewResult() + collectorName := runPodCollector.Name + if collectorName == "" { + collectorName = runPodCollector.CollectorName + } + limits := troubleshootv1beta2.LogLimits{ MaxLines: 10000, } - podLogs, err := savePodLogs(ctx, c.BundlePath, client, *pod, runCollector.Name, "", &limits, true) + podLogs, err := savePodLogs(ctx, c.BundlePath, client, *pod, collectorName, "", &limits, true) if err != nil { return nil, errors.Wrap(err, "failed to get pod logs") } @@ -119,67 +208,6 @@ func runWithoutTimeout(ctx context.Context, c *Collector, pod *corev1.Pod, runCo return output, nil } -func runPod(ctx context.Context, client *kubernetes.Clientset, runCollector *troubleshootv1beta2.Run, namespace string) (*corev1.Pod, error) { - podLabels := make(map[string]string) - podLabels["troubleshoot-role"] = "run-collector" - - pullPolicy := corev1.PullIfNotPresent - if runCollector.ImagePullPolicy != "" { - pullPolicy = corev1.PullPolicy(runCollector.ImagePullPolicy) - } - - if namespace == "" { - namespace = runCollector.Namespace - } - if namespace == "" { - namespace = "default" - } - - serviceAccountName := "default" - if runCollector.ServiceAccountName != "" { - serviceAccountName = runCollector.ServiceAccountName - } - - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: runCollector.CollectorName, - Namespace: namespace, - Labels: podLabels, - }, - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Pod", - }, - Spec: corev1.PodSpec{ - RestartPolicy: corev1.RestartPolicyNever, - ServiceAccountName: serviceAccountName, - Containers: []corev1.Container{ - { - Image: runCollector.Image, - ImagePullPolicy: pullPolicy, - Name: "collector", - Command: runCollector.Command, - Args: runCollector.Args, - }, - }, - }, - } - - if runCollector.ImagePullSecret != nil && runCollector.ImagePullSecret.Data != nil { - secretName, err := createSecret(ctx, client, pod.Namespace, runCollector.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 createSecret(ctx context.Context, client kubernetes.Interface, namespace string, imagePullSecret *troubleshootv1beta2.ImagePullSecrets) (string, error) { if imagePullSecret.Data == nil { return "", nil