Merge pull request #153 from replicatedhq/export-function

Export function
This commit is contained in:
Marc Campbell
2020-03-17 14:36:38 -07:00
committed by GitHub
7 changed files with 251 additions and 328 deletions

View File

@@ -4,7 +4,6 @@ import (
"os"
"strings"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/cli-runtime/pkg/genericclioptions"
@@ -64,16 +63,3 @@ func initConfig() {
viper.SetEnvPrefix("PREFLIGHT")
viper.AutomaticEnv()
}
func ensureCollectorInList(list []*troubleshootv1beta1.Collect, collector troubleshootv1beta1.Collect) []*troubleshootv1beta1.Collect {
for _, inList := range list {
if collector.ClusterResources != nil && inList.ClusterResources != nil {
return list
}
if collector.ClusterInfo != nil && inList.ClusterInfo != nil {
return list
}
}
return append(list, &collector)
}

View File

@@ -1,23 +1,18 @@
package cli
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"
cursor "github.com/ahmetalpbalkan/go-cursor"
"github.com/fatih/color"
"github.com/pkg/errors"
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/replicatedhq/troubleshoot/pkg/preflight"
"github.com/spf13/viper"
spin "github.com/tj/go-spin"
"k8s.io/client-go/kubernetes/scheme"
@@ -66,7 +61,7 @@ func runPreflights(v *viper.Viper, arg string) error {
return errors.Wrapf(err, "failed to parse %s", arg)
}
preflight := obj.(*troubleshootv1beta1.Preflight)
preflightSpec := obj.(*troubleshootv1beta1.Preflight)
s := spin.New()
finishedCh := make(chan bool, 1)
@@ -98,48 +93,34 @@ func runPreflights(v *viper.Viper, arg string) error {
close(finishedCh)
}()
allCollectedData, err := runCollectors(v, *preflight, progressChan)
restConfig, err := KubernetesConfigFlags.ToRESTConfig()
if err != nil {
return errors.Wrap(err, "failed to convert kube flags to rest config")
}
collectOpts := preflight.CollectOpts{
Namespace: v.GetString("namespace"),
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
ProgressChan: progressChan,
KubernetesRestConfig: restConfig,
}
collectResults, err := preflight.Collect(collectOpts, preflightSpec)
if err != nil {
if !collectResults.IsRBACAllowed {
if preflightSpec.Spec.UploadResultsTo != "" {
err := uploadErrors(preflightSpec.Spec.UploadResultsTo, collectResults.Collectors)
if err != nil {
progressChan <- err
}
}
}
return err
}
getCollectedFileContents := func(fileName string) ([]byte, error) {
contents, ok := allCollectedData[fileName]
if !ok {
return nil, fmt.Errorf("file %s was not collected", fileName)
}
return contents, nil
}
getChildCollectedFileContents := func(prefix string) (map[string][]byte, error) {
matching := make(map[string][]byte)
for k, v := range allCollectedData {
if strings.HasPrefix(k, prefix) {
matching[k] = v
}
}
return matching, nil
}
analyzeResults := []*analyzerunner.AnalyzeResult{}
for _, analyzer := range preflight.Spec.Analyzers {
analyzeResult, err := analyzerunner.Analyze(analyzer, getCollectedFileContents, getChildCollectedFileContents)
if err != nil {
analyzeResult = &analyzerunner.AnalyzeResult{
IsFail: true,
Title: "Analyzer Failed",
Message: err.Error(),
}
}
if analyzeResult != nil {
analyzeResults = append(analyzeResults, analyzeResult)
}
}
if preflight.Spec.UploadResultsTo != "" {
err := uploadResults(preflight.Spec.UploadResultsTo, analyzeResults)
analyzeResults := collectResults.Analyze()
if preflightSpec.Spec.UploadResultsTo != "" {
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
if err != nil {
progressChan <- err
}
@@ -151,117 +132,8 @@ func runPreflights(v *viper.Viper, arg string) error {
if len(analyzeResults) == 0 {
return errors.New("no data has been collected")
}
return showInteractiveResults(preflight.Name, analyzeResults)
return showInteractiveResults(preflightSpec.Name, analyzeResults)
}
return showStdoutResults(v.GetString("format"), preflight.Name, analyzeResults)
}
func runCollectors(v *viper.Viper, preflight troubleshootv1beta1.Preflight, progressChan chan interface{}) (map[string][]byte, error) {
collectSpecs := make([]*troubleshootv1beta1.Collect, 0, 0)
collectSpecs = append(collectSpecs, preflight.Spec.Collectors...)
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
allCollectedData := make(map[string][]byte)
config, err := KubernetesConfigFlags.ToRESTConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
}
var collectors collect.Collectors
for _, desiredCollector := range collectSpecs {
collector := collect.Collector{
Redact: true,
Collect: desiredCollector,
ClientConfig: config,
Namespace: v.GetString("namespace"),
}
collectors = append(collectors, &collector)
}
if err := collectors.CheckRBAC(); err != nil {
return nil, errors.Wrap(err, "failed to check RBAC for collectors")
}
foundForbidden := false
for _, c := range collectors {
for _, e := range c.RBACErrors {
foundForbidden = true
progressChan <- e
}
}
if foundForbidden && !v.GetBool("collect-without-permissions") {
if preflight.Spec.UploadResultsTo != "" {
err := uploadErrors(preflight.Spec.UploadResultsTo, collectors)
if err != nil {
progressChan <- err
}
}
return nil, errors.New("insufficient permissions to run all collectors")
}
// Run preflights collectors synchronously
for _, collector := range collectors {
if len(collector.RBACErrors) > 0 {
// don't skip clusterResources collector due to RBAC issues
if collector.Collect.ClusterResources == nil {
progressChan <- fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.GetDisplayName())
continue
}
}
result, err := collector.RunCollectorSync()
if err != nil {
progressChan <- errors.Errorf("failed to run collector %s: %v\n", collector.GetDisplayName(), err)
continue
}
if result != nil {
output, err := parseCollectorOutput(string(result))
if err != nil {
progressChan <- errors.Errorf("failed to parse collector output %s: %v\n", collector.GetDisplayName(), err)
continue
}
for k, v := range output {
allCollectedData[k] = v
}
}
}
return allCollectedData, nil
}
func parseCollectorOutput(output string) (map[string][]byte, error) {
input := make(map[string]interface{})
files := make(map[string][]byte)
if err := json.Unmarshal([]byte(output), &input); err != nil {
return nil, err
}
for filename, maybeContents := range input {
fileDir, fileName := filepath.Split(filename)
switch maybeContents.(type) {
case string:
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
if err != nil {
return nil, err
}
files[filepath.Join(fileDir, fileName)] = decoded
case map[string]interface{}:
for k, v := range maybeContents.(map[string]interface{}) {
decoded, err := base64.StdEncoding.DecodeString(v.(string))
if err != nil {
return nil, err
}
files[filepath.Join(fileDir, fileName, k)] = decoded
}
}
}
return files, nil
return showStdoutResults(v.GetString("format"), preflightSpec.Name, analyzeResults)
}

View File

@@ -8,33 +8,15 @@ import (
"github.com/pkg/errors"
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/replicatedhq/troubleshoot/pkg/preflight"
)
type UploadPreflightResult struct {
IsFail bool `json:"isFail,omitempty"`
IsWarn bool `json:"isWarn,omitempty"`
IsPass bool `json:"isPass,omitempty"`
Title string `json:"title"`
Message string `json:"message"`
URI string `json:"uri,omitempty"`
}
type UploadPreflightError struct {
Error string `json:"error"`
}
type UploadPreflightResults struct {
Results []*UploadPreflightResult `json:"results,omitempty"`
Errors []*UploadPreflightError `json:"errors,omitempty"`
}
func uploadResults(uri string, analyzeResults []*analyzerunner.AnalyzeResult) error {
uploadPreflightResults := &UploadPreflightResults{
Results: []*UploadPreflightResult{},
uploadPreflightResults := &preflight.UploadPreflightResults{
Results: []*preflight.UploadPreflightResult{},
}
for _, analyzeResult := range analyzeResults {
uploadPreflightResult := &UploadPreflightResult{
uploadPreflightResult := &preflight.UploadPreflightResult{
IsFail: analyzeResult.IsFail,
IsWarn: analyzeResult.IsWarn,
IsPass: analyzeResult.IsPass,
@@ -50,23 +32,23 @@ func uploadResults(uri string, analyzeResults []*analyzerunner.AnalyzeResult) er
}
func uploadErrors(uri string, collectors collect.Collectors) error {
errors := []*UploadPreflightError{}
errors := []*preflight.UploadPreflightError{}
for _, collector := range collectors {
for _, e := range collector.RBACErrors {
errors = append(errors, &UploadPreflightError{
errors = append(errors, &preflight.UploadPreflightError{
Error: e.Error(),
})
}
}
results := &UploadPreflightResults{
results := &preflight.UploadPreflightResults{
Errors: errors,
}
return upload(uri, results)
}
func upload(uri string, payload *UploadPreflightResults) error {
func upload(uri string, payload *preflight.UploadPreflightResults) error {
b, err := json.Marshal(payload)
if err != nil {
return errors.Wrap(err, "failed to marshal payload")

48
pkg/preflight/analyze.go Normal file
View File

@@ -0,0 +1,48 @@
package preflight
import (
"fmt"
"strings"
analyze "github.com/replicatedhq/troubleshoot/pkg/analyze"
)
// Analyze runs the analyze phase of preflight checks
func (c CollectResult) Analyze() []*analyze.AnalyzeResult {
getCollectedFileContents := func(fileName string) ([]byte, error) {
contents, ok := c.AllCollectedData[fileName]
if !ok {
return nil, fmt.Errorf("file %s was not collected", fileName)
}
return contents, nil
}
getChildCollectedFileContents := func(prefix string) (map[string][]byte, error) {
matching := make(map[string][]byte)
for k, v := range c.AllCollectedData {
if strings.HasPrefix(k, prefix) {
matching[k] = v
}
}
return matching, nil
}
analyzeResults := []*analyze.AnalyzeResult{}
for _, analyzer := range c.Spec.Spec.Analyzers {
analyzeResult, err := analyze.Analyze(analyzer, getCollectedFileContents, getChildCollectedFileContents)
if err != nil {
analyzeResult = &analyze.AnalyzeResult{
IsFail: true,
Title: "Analyzer Failed",
Message: err.Error(),
}
}
if analyzeResult != nil {
analyzeResults = append(analyzeResults, analyzeResult)
}
}
return analyzeResults
}

147
pkg/preflight/collect.go Normal file
View File

@@ -0,0 +1,147 @@
package preflight
import (
"encoding/base64"
"encoding/json"
"fmt"
"path/filepath"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"k8s.io/client-go/rest"
)
type CollectOpts struct {
Namespace string
IgnorePermissionErrors bool
KubernetesRestConfig *rest.Config
ProgressChan chan interface{}
}
type CollectResult struct {
AllCollectedData map[string][]byte
Collectors collect.Collectors
IsRBACAllowed bool
Spec *troubleshootv1beta1.Preflight
}
// Collect runs the collection phase of preflight checks
func Collect(opts CollectOpts, p *troubleshootv1beta1.Preflight) (CollectResult, error) {
collectSpecs := make([]*troubleshootv1beta1.Collect, 0, 0)
collectSpecs = append(collectSpecs, p.Spec.Collectors...)
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
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 := CollectResult{
Collectors: collectors,
Spec: p,
}
if err := collectors.CheckRBAC(); err != nil {
return collectResult, errors.Wrap(err, "failed to check RBAC for collectors")
}
foundForbidden := false
for _, c := range collectors {
for _, e := range c.RBACErrors {
foundForbidden = true
opts.ProgressChan <- e
}
}
if foundForbidden && !opts.IgnorePermissionErrors {
collectResult.IsRBACAllowed = false
return collectResult, errors.New("insufficient permissions to run all collectors")
}
// Run preflights collectors synchronously
for _, collector := range collectors {
if len(collector.RBACErrors) > 0 {
// don't skip clusterResources collector due to RBAC issues
if collector.Collect.ClusterResources == nil {
collectResult.IsRBACAllowed = false // not failing, but going to report this
opts.ProgressChan <- fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.GetDisplayName())
continue
}
}
result, err := collector.RunCollectorSync()
if err != nil {
opts.ProgressChan <- errors.Errorf("failed to run collector %s: %v\n", collector.GetDisplayName(), err)
continue
}
if result != nil {
output, err := parseCollectorOutput(string(result))
if err != nil {
opts.ProgressChan <- errors.Errorf("failed to parse collector output %s: %v\n", collector.GetDisplayName(), err)
continue
}
for k, v := range output {
allCollectedData[k] = v
}
}
}
collectResult.AllCollectedData = allCollectedData
return collectResult, nil
}
func parseCollectorOutput(output string) (map[string][]byte, error) {
input := make(map[string]interface{})
files := make(map[string][]byte)
if err := json.Unmarshal([]byte(output), &input); err != nil {
return nil, err
}
for filename, maybeContents := range input {
fileDir, fileName := filepath.Split(filename)
switch maybeContents.(type) {
case string:
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
if err != nil {
return nil, err
}
files[filepath.Join(fileDir, fileName)] = decoded
case map[string]interface{}:
for k, v := range maybeContents.(map[string]interface{}) {
decoded, err := base64.StdEncoding.DecodeString(v.(string))
if err != nil {
return nil, err
}
files[filepath.Join(fileDir, fileName, k)] = decoded
}
}
}
return files, nil
}
func ensureCollectorInList(list []*troubleshootv1beta1.Collect, collector troubleshootv1beta1.Collect) []*troubleshootv1beta1.Collect {
for _, inList := range list {
if collector.ClusterResources != nil && inList.ClusterResources != nil {
return list
}
if collector.ClusterInfo != nil && inList.ClusterInfo != nil {
return list
}
}
return append(list, &collector)
}

View File

@@ -1,132 +0,0 @@
package preflight
import (
"context"
"fmt"
"time"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
type PreflightServerOptions struct {
ImageName string
PullPolicy string
Name string
Namespace string
OwnerReference metav1.Object
}
func CreatePreflightServer(client client.Client, scheme *runtime.Scheme, options PreflightServerOptions) (*corev1.Pod, *corev1.Service, error) {
name := fmt.Sprintf("%s-%s", options.Name, "preflight")
namespacedName := types.NamespacedName{
Name: name,
Namespace: options.Namespace,
}
found := &corev1.Pod{}
err := client.Get(context.Background(), namespacedName, found)
if err == nil || !kuberneteserrors.IsNotFound(err) {
return nil, nil, err
}
imageName := "replicated/troubleshoot:latest"
imagePullPolicy := corev1.PullAlways
if options.ImageName != "" {
imageName = options.ImageName
}
if options.PullPolicy != "" {
imagePullPolicy = corev1.PullPolicy(options.PullPolicy)
}
podLabels := make(map[string]string)
podLabels["preflight"] = options.Name
podLabels["troubleshoot-role"] = "preflight"
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: options.Namespace,
Labels: podLabels,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Image: imageName,
ImagePullPolicy: imagePullPolicy,
Name: "preflight",
Command: []string{"preflight"},
Args: []string{"server"},
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 8000,
},
},
},
},
},
}
if scheme != nil {
if err := controllerutil.SetControllerReference(options.OwnerReference, &pod, scheme); err != nil {
return nil, nil, err
}
}
if err := client.Create(context.Background(), &pod); err != nil {
return nil, nil, err
}
service := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: options.Namespace,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Service",
},
Spec: corev1.ServiceSpec{
Selector: podLabels,
Type: corev1.ServiceTypeClusterIP,
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 8000,
TargetPort: intstr.FromInt(8000),
},
},
},
}
if scheme != nil {
if err := controllerutil.SetControllerReference(options.OwnerReference, &service, scheme); err != nil {
return nil, nil, err
}
}
if err := client.Create(context.Background(), &service); err != nil {
return nil, nil, err
}
// wait for the server to be ready
// TODO
time.Sleep(time.Second * 5)
return &pod, &service, nil
}

20
pkg/preflight/types.go Normal file
View File

@@ -0,0 +1,20 @@
package preflight
type UploadPreflightResult struct {
IsFail bool `json:"isFail,omitempty"`
IsWarn bool `json:"isWarn,omitempty"`
IsPass bool `json:"isPass,omitempty"`
Title string `json:"title"`
Message string `json:"message"`
URI string `json:"uri,omitempty"`
}
type UploadPreflightError struct {
Error string `json:"error"`
}
type UploadPreflightResults struct {
Results []*UploadPreflightResult `json:"results,omitempty"`
Errors []*UploadPreflightError `json:"errors,omitempty"`
}