mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-09-03 00:47:17 +00:00
Merge pull request #360 from replicatedhq/chore-refactor-support-bundle
chore(support-bundle): refactor out support bundle pkg
This commit is contained in:
+85
-626
@@ -1,20 +1,14 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -23,47 +17,50 @@ import (
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/httputil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/redact"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
|
||||
"github.com/spf13/viper"
|
||||
spin "github.com/tj/go-spin"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
httpClient *http.Client
|
||||
)
|
||||
|
||||
func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
fmt.Print(cursor.Hide())
|
||||
defer fmt.Print(cursor.Show())
|
||||
|
||||
if v.GetBool("allow-insecure-connections") || v.GetBool("insecure-skip-tls-verify") {
|
||||
httpClient = &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
} else {
|
||||
httpClient = http.DefaultClient
|
||||
k8sConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
collectorContent, err := loadSupportBundleSpec(v, arg)
|
||||
var sinceTime *time.Time
|
||||
if v.GetString("since-time") != "" || v.GetString("since") != "" {
|
||||
sinceTime, err = parseTimeFlags(v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed parse since time")
|
||||
}
|
||||
}
|
||||
|
||||
if v.GetBool("allow-insecure-connections") || v.GetBool("insecure-skip-tls-verify") {
|
||||
httputil.AddTransport(&http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
})
|
||||
}
|
||||
|
||||
collectorContent, err := supportbundle.LoadSupportBundleSpec(arg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to load collector spec")
|
||||
}
|
||||
|
||||
multidocs := strings.Split(string(collectorContent), "\n---\n")
|
||||
|
||||
// we suppory both raw collector kinds and supportbundle kinds here
|
||||
supportBundleSpec, err := parseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
// we support both raw collector kinds and supportbundle kinds here
|
||||
supportBundle, err := supportbundle.ParseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse collector")
|
||||
}
|
||||
@@ -73,24 +70,13 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{}
|
||||
for idx, redactor := range v.GetStringSlice("redactors") {
|
||||
redactorContent, err := loadRedactorSpec(v, redactor)
|
||||
redactorObj, err := supportbundle.GetRedactorFromURI(redactor)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to load redactor spec #%d", idx)
|
||||
return errors.Wrapf(err, "failed to get redactor spec %s, #%d", redactor, idx)
|
||||
}
|
||||
redactorContent, err = docrewrite.ConvertToV1Beta2(redactorContent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
obj, _, err := decode([]byte(redactorContent), nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse redactors %s", redactor)
|
||||
}
|
||||
loopRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
|
||||
if !ok {
|
||||
return fmt.Errorf("%s is not a troubleshootv1beta2 redactor type", redactor)
|
||||
}
|
||||
if loopRedactors != nil {
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, loopRedactors.Spec.Redactors...)
|
||||
|
||||
if redactorObj != nil {
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, redactorObj.Spec.Redactors...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +101,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
|
||||
s := spin.New()
|
||||
finishedCh := make(chan bool, 1)
|
||||
progressChan := make(chan interface{}, 0) // non-zero buffer can result in missed messages
|
||||
progressChan := make(chan interface{}) // non-zero buffer can result in missed messages
|
||||
isFinishedChClosed := false
|
||||
go func() {
|
||||
currentDir := ""
|
||||
@@ -147,58 +133,40 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
}
|
||||
}()
|
||||
|
||||
archivePath, err := runCollectors(v, supportBundleSpec.Spec.Collectors, additionalRedactors, progressChan)
|
||||
collectorCB := func(c chan interface{}, msg string) {
|
||||
c <- fmt.Sprintf("%s", msg)
|
||||
}
|
||||
|
||||
createOpts := supportbundle.SupportBundleCreateOpts{
|
||||
CollectorProgressCallback: collectorCB,
|
||||
CollectWithoutPermissions: v.GetBool("collect-without-permissions"),
|
||||
KubernetesRestConfig: k8sConfig,
|
||||
Namespace: v.GetString("namespace"),
|
||||
ProgressChan: progressChan,
|
||||
SinceTime: sinceTime,
|
||||
}
|
||||
|
||||
archivePath, err := supportbundle.CollectSupportBundleFromSpec(&supportBundle.Spec, additionalRedactors, createOpts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "run collectors")
|
||||
}
|
||||
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
|
||||
// upload if needed
|
||||
fileUploaded := false
|
||||
if len(supportBundleSpec.Spec.AfterCollection) > 0 {
|
||||
for _, ac := range supportBundleSpec.Spec.AfterCollection {
|
||||
if ac.UploadResultsTo != nil {
|
||||
if err := uploadSupportBundle(ac.UploadResultsTo, archivePath); err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to upload support bundle: %v\n", cursor.ClearEntireLine(), err)
|
||||
} else {
|
||||
fileUploaded = true
|
||||
}
|
||||
} else if ac.Callback != nil {
|
||||
if err := callbackSupportBundleAPI(ac.Callback, archivePath); err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to notify API that support bundle has been uploaded: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
c := color.New()
|
||||
c.Println(fmt.Sprintf("\r%s\r", cursor.ClearEntireLine()))
|
||||
|
||||
fileUploaded, err := supportbundle.ProcessSupportBundleAfterCollection(&supportBundle.Spec, archivePath)
|
||||
if err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * %v\n", cursor.ClearEntireLine(), err)
|
||||
return errors.Wrap(err, "failed to process bundle after collection")
|
||||
}
|
||||
|
||||
// perform analysis, if possible
|
||||
if len(supportBundleSpec.Spec.Analyzers) > 0 {
|
||||
tmpDir, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to make directory for analysis: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
|
||||
f, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to open support bundle for analysis: %v\n", cursor.ClearEntireLine(), err)
|
||||
|
||||
}
|
||||
if err := analyzer.ExtractTroubleshootBundle(f, tmpDir); err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to extract support bundle for analysis: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
|
||||
analyzeResults, err := analyzer.AnalyzeLocal(tmpDir, supportBundleSpec.Spec.Analyzers)
|
||||
if err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to analyze support bundle: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
analyzeResults, err := supportbundle.AnalyzeAndExtractSupportBundle(&supportBundle.Spec, archivePath)
|
||||
if err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * %v\n", cursor.ClearEntireLine(), err)
|
||||
// Don't die
|
||||
} else if len(analyzeResults) > 0 {
|
||||
|
||||
interactive := v.GetBool("interactive") && isatty.IsTerminal(os.Stdout.Fd())
|
||||
|
||||
@@ -206,12 +174,10 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
close(finishedCh) // this removes the spinner
|
||||
isFinishedChClosed = true
|
||||
|
||||
if err := showInteractiveResults(supportBundleSpec.Name, analyzeResults); err != nil {
|
||||
if err := showInteractiveResults(supportBundle.Name, analyzeResults); err != nil {
|
||||
interactive = false
|
||||
}
|
||||
}
|
||||
|
||||
if !interactive {
|
||||
} else {
|
||||
data := convert.FromAnalyzerResult(analyzeResults)
|
||||
formatted, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
@@ -225,7 +191,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
|
||||
if !fileUploaded {
|
||||
msg := archivePath
|
||||
if appName := supportBundleSpec.Labels["applicationName"]; appName != "" {
|
||||
if appName := supportBundle.Labels["applicationName"]; appName != "" {
|
||||
f := `A support bundle for %s has been created in this directory
|
||||
named %s. Please upload it on the Troubleshoot page of
|
||||
the %s Admin Console to begin analysis.`
|
||||
@@ -247,144 +213,40 @@ the %s Admin Console to begin analysis.`
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadSupportBundleSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
if strings.HasPrefix(arg, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return nil, errors.Errorf("secret path %s must have 3 components", arg)
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "support-bundle-spec")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from secret")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
return loadSpec(v, arg)
|
||||
}
|
||||
|
||||
func loadRedactorSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
if strings.HasPrefix(arg, "configmap/") {
|
||||
// format configmap/namespace-name/configmap-name[/data-key]
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) > 4 {
|
||||
return nil, errors.Errorf("configmap path %s must have at most 4 components", arg)
|
||||
}
|
||||
if len(pathParts) < 3 {
|
||||
return nil, errors.Errorf("configmap path %s must have at least 3 components", arg)
|
||||
}
|
||||
|
||||
dataKey := "redactor-spec"
|
||||
if len(pathParts) == 4 {
|
||||
dataKey = pathParts[3]
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromConfigMap(pathParts[1], pathParts[2], dataKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from configmap")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
spec, err := loadSpec(v, arg)
|
||||
func getExpectedContentType(uploadURL string) string {
|
||||
parsedURL, err := url.Parse(uploadURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load spec")
|
||||
return ""
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
return parsedURL.Query().Get("Content-Type")
|
||||
}
|
||||
|
||||
func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
var err error
|
||||
if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read spec file")
|
||||
func parseTimeFlags(v *viper.Viper) (*time.Time, error) {
|
||||
var (
|
||||
sinceTime time.Time
|
||||
err error
|
||||
)
|
||||
if v.GetString("since-time") != "" {
|
||||
if v.GetString("since") != "" {
|
||||
return nil, errors.Errorf("at most one of `sinceTime` or `since` may be specified")
|
||||
}
|
||||
|
||||
return b, nil
|
||||
} else if !util.IsURL(arg) {
|
||||
return nil, fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
|
||||
sinceTime, err = time.Parse(time.RFC3339, v.GetString("since-time"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse --since-time flag")
|
||||
}
|
||||
} else {
|
||||
parsedDuration, err := time.ParseDuration(v.GetString("since"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse --since flag")
|
||||
}
|
||||
now := time.Now()
|
||||
sinceTime = now.Add(0 - parsedDuration)
|
||||
}
|
||||
|
||||
spec, err := loadSpecFromURL(v, arg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from URL")
|
||||
}
|
||||
return spec, nil
|
||||
return &sinceTime, nil
|
||||
}
|
||||
|
||||
func loadSpecFromURL(v *viper.Viper, arg string) ([]byte, error) {
|
||||
for {
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "make request")
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Troubleshoot/v1beta1")
|
||||
req.Header.Set("Bundle-Upload-Host", fmt.Sprintf("%s://%s", req.URL.Scheme, req.URL.Host))
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err) {
|
||||
continue
|
||||
}
|
||||
return nil, errors.Wrap(err, "execute request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read responce body")
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
}
|
||||
|
||||
func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta2.SupportBundle, error) {
|
||||
doc, err := docrewrite.ConvertToV1Beta2(doc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
obj, _, err := decode(doc, nil, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse document")
|
||||
}
|
||||
|
||||
collector, ok := obj.(*troubleshootv1beta2.Collector)
|
||||
if ok {
|
||||
supportBundle := troubleshootv1beta2.SupportBundle{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "SupportBundle",
|
||||
},
|
||||
ObjectMeta: collector.ObjectMeta,
|
||||
Spec: troubleshootv1beta2.SupportBundleSpec{
|
||||
Collectors: collector.Spec.Collectors,
|
||||
Analyzers: []*troubleshootv1beta2.Analyze{},
|
||||
AfterCollection: collector.Spec.AfterCollection,
|
||||
},
|
||||
}
|
||||
|
||||
return &supportBundle, nil
|
||||
}
|
||||
|
||||
supportBundle, ok := obj.(*troubleshootv1beta2.SupportBundle)
|
||||
if ok {
|
||||
return supportBundle, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("spec was not parseable as a troubleshoot kind")
|
||||
}
|
||||
|
||||
func shouldRetryRequest(err error) bool {
|
||||
func shouldRetryRequest(err error, httpClient *http.Client) bool {
|
||||
if strings.Contains(err.Error(), "x509") && httpClient == http.DefaultClient && canTryInsecure() {
|
||||
httpClient = &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
@@ -404,408 +266,5 @@ func canTryInsecure() bool {
|
||||
}
|
||||
|
||||
_, err := prompt.Run()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta2.Collect, additionalRedactors *troubleshootv1beta2.Redactor, progressChan chan interface{}) (string, error) {
|
||||
tmpDir, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
filename, err := findFileName("support-bundle-"+time.Now().Format("2006-01-02T15_04_05"), "tar.gz")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "find file name")
|
||||
}
|
||||
|
||||
bundlePath := filepath.Join(tmpDir, strings.TrimSuffix(filename, ".tar.gz"))
|
||||
if err := os.MkdirAll(bundlePath, 0777); err != nil {
|
||||
return "", errors.Wrap(err, "create bundle dir")
|
||||
}
|
||||
|
||||
if err = writeVersionFile(bundlePath); err != nil {
|
||||
return "", errors.Wrap(err, "write version file")
|
||||
}
|
||||
|
||||
collectSpecs := make([]*troubleshootv1beta2.Collect, 0, 0)
|
||||
collectSpecs = append(collectSpecs, collectors...)
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterInfo: &troubleshootv1beta2.ClusterInfo{}})
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterResources: &troubleshootv1beta2.ClusterResources{}})
|
||||
|
||||
config, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
var cleanedCollectors collect.Collectors
|
||||
for _, desiredCollector := range collectSpecs {
|
||||
collector := collect.Collector{
|
||||
Redact: true,
|
||||
Collect: desiredCollector,
|
||||
ClientConfig: config,
|
||||
Namespace: v.GetString("namespace"),
|
||||
}
|
||||
cleanedCollectors = append(cleanedCollectors, &collector)
|
||||
}
|
||||
|
||||
if err := cleanedCollectors.CheckRBAC(context.Background()); err != nil {
|
||||
return "", errors.Wrap(err, "failed to check RBAC for collectors")
|
||||
}
|
||||
|
||||
foundForbidden := false
|
||||
for _, c := range cleanedCollectors {
|
||||
for _, e := range c.RBACErrors {
|
||||
foundForbidden = true
|
||||
progressChan <- e
|
||||
}
|
||||
}
|
||||
|
||||
if foundForbidden && !v.GetBool("collect-without-permissions") {
|
||||
return "", errors.New("insufficient permissions to run all collectors")
|
||||
}
|
||||
|
||||
globalRedactors := []*troubleshootv1beta2.Redact{}
|
||||
if additionalRedactors != nil {
|
||||
globalRedactors = additionalRedactors.Spec.Redactors
|
||||
}
|
||||
if v.GetString("since-time") != "" || v.GetString("since") != "" {
|
||||
err := parseTimeFlags(v, progressChan, &cleanedCollectors)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
progressChan <- fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.GetDisplayName())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
progressChan <- collector.GetDisplayName()
|
||||
|
||||
result, err := collector.RunCollectorSync(globalRedactors)
|
||||
if err != nil {
|
||||
progressChan <- fmt.Errorf("failed to run collector %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
err = saveCollectorOutput(result, bundlePath, collector)
|
||||
if err != nil {
|
||||
progressChan <- fmt.Errorf("failed to parse collector spec %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := tarSupportBundleDir(bundlePath, filename); err != nil {
|
||||
return "", errors.Wrap(err, "create bundle file")
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func saveCollectorOutput(output map[string][]byte, bundlePath string, c *collect.Collector) error {
|
||||
for filename, maybeContents := range output {
|
||||
if c.Collect.Copy != nil {
|
||||
err := untarAndSave(maybeContents, filepath.Join(bundlePath, filepath.Dir(filename)))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "extract copied files")
|
||||
}
|
||||
continue
|
||||
}
|
||||
fileDir, fileName := filepath.Split(filename)
|
||||
outPath := filepath.Join(bundlePath, fileDir)
|
||||
|
||||
if err := os.MkdirAll(outPath, 0777); err != nil {
|
||||
return errors.Wrap(err, "create output file")
|
||||
}
|
||||
|
||||
if err := writeFile(filepath.Join(outPath, fileName), maybeContents); err != nil {
|
||||
return errors.Wrap(err, "write collector output")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func untarAndSave(tarFile []byte, bundlePath string) error {
|
||||
keys := make([]string, 0)
|
||||
dirs := make(map[string]*tar.Header)
|
||||
files := make(map[string][]byte)
|
||||
fileHeaders := make(map[string]*tar.Header)
|
||||
tarReader := tar.NewReader(bytes.NewBuffer(tarFile))
|
||||
//Extract and separate tar contentes in file and folders, keeping header info from each one.
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
dirs[header.Name] = header
|
||||
case tar.TypeReg:
|
||||
file := new(bytes.Buffer)
|
||||
_, err = io.Copy(file, tarReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files[header.Name] = file.Bytes()
|
||||
fileHeaders[header.Name] = header
|
||||
default:
|
||||
return fmt.Errorf("Tar file entry %s contained unsupported file type %v", header.Name, header.FileInfo().Mode())
|
||||
}
|
||||
}
|
||||
//Create directories from base path: <namespace>/<pod name>/containerPath
|
||||
if err := os.MkdirAll(filepath.Join(bundlePath), 0777); err != nil {
|
||||
return errors.Wrap(err, "create output file")
|
||||
}
|
||||
//Order folders stored in variable keys to start always by parent folder. That way folder info is preserved.
|
||||
for k := range dirs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
//Orderly create folders.
|
||||
for _, k := range keys {
|
||||
if err := os.Mkdir(filepath.Join(bundlePath, k), dirs[k].FileInfo().Mode().Perm()); err != nil {
|
||||
return errors.Wrap(err, "create output file")
|
||||
}
|
||||
}
|
||||
//Populate folders with respective files and its permissions stored in the header.
|
||||
for k, v := range files {
|
||||
if err := ioutil.WriteFile(filepath.Join(bundlePath, k), v, fileHeaders[k].FileInfo().Mode().Perm()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func uploadSupportBundle(r *troubleshootv1beta2.ResultRequest, archivePath string) error {
|
||||
contentType := getExpectedContentType(r.URI)
|
||||
if contentType != "" && contentType != "application/tar+gzip" {
|
||||
return fmt.Errorf("cannot upload content type %s", contentType)
|
||||
}
|
||||
|
||||
for {
|
||||
f, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fileStat, err := f.Stat()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "stat file")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(r.Method, r.URI, f)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
req.ContentLength = fileStat.Size()
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err) {
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// send redaction report
|
||||
if r.RedactURI != "" {
|
||||
type PutSupportBundleRedactions struct {
|
||||
Redactions redact.RedactionList `json:"redactions"`
|
||||
}
|
||||
|
||||
redactBytes, err := json.Marshal(PutSupportBundleRedactions{Redactions: redact.GetRedactionList()})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get redaction report")
|
||||
}
|
||||
|
||||
for {
|
||||
req, err := http.NewRequest("PUT", r.RedactURI, bytes.NewReader(redactBytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create redaction report request")
|
||||
}
|
||||
req.ContentLength = int64(len(redactBytes))
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err) {
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "execute redaction request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected redaction status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExpectedContentType(uploadURL string) string {
|
||||
parsedURL, err := url.Parse(uploadURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return parsedURL.Query().Get("Content-Type")
|
||||
}
|
||||
|
||||
func callbackSupportBundleAPI(r *troubleshootv1beta2.ResultRequest, archivePath string) error {
|
||||
for {
|
||||
req, err := http.NewRequest(r.Method, r.URI, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err) {
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tarSupportBundleDir(inputDir, outputFilename string) error {
|
||||
fileWriter, err := os.Create(outputFilename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create output file")
|
||||
}
|
||||
defer fileWriter.Close()
|
||||
|
||||
gzipWriter := gzip.NewWriter(fileWriter)
|
||||
defer gzipWriter.Close()
|
||||
|
||||
tarWriter := tar.NewWriter(gzipWriter)
|
||||
defer tarWriter.Close()
|
||||
|
||||
err = filepath.Walk(inputDir, func(filename string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileMode := info.Mode()
|
||||
if !fileMode.IsRegular() { // support bundle can have only files
|
||||
return nil
|
||||
}
|
||||
|
||||
parentDirName := filepath.Dir(inputDir) // this is to have the files inside a subdirectory
|
||||
nameInArchive, err := filepath.Rel(parentDirName, filename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create relative file name")
|
||||
}
|
||||
|
||||
// tar.FileInfoHeader call causes a crash in static builds
|
||||
// https://github.com/golang/go/issues/24787
|
||||
hdr := &tar.Header{
|
||||
Name: nameInArchive,
|
||||
ModTime: info.ModTime(),
|
||||
Mode: int64(fileMode.Perm()),
|
||||
Typeflag: tar.TypeReg,
|
||||
Size: info.Size(),
|
||||
}
|
||||
|
||||
err = tarWriter.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to write tar header")
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
fileReader, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open source file")
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
_, err = io.Copy(tarWriter, fileReader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to copy file into archive")
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to walk source dir")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CollectorFailure struct {
|
||||
Collector *troubleshootv1beta2.Collect
|
||||
Failure string
|
||||
}
|
||||
|
||||
func parseTimeFlags(v *viper.Viper, progressChan chan interface{}, collectors *collect.Collectors) error {
|
||||
var (
|
||||
sinceTime time.Time
|
||||
err error
|
||||
)
|
||||
if v.GetString("since-time") != "" {
|
||||
if v.GetString("since") != "" {
|
||||
return errors.Errorf("at most one of `sinceTime` or `since` may be specified")
|
||||
}
|
||||
sinceTime, err = time.Parse(time.RFC3339, v.GetString("since-time"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since-time flag")
|
||||
}
|
||||
} else {
|
||||
parsedDuration, err := time.ParseDuration(v.GetString("since"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since flag")
|
||||
}
|
||||
now := time.Now()
|
||||
sinceTime = now.Add(0 - parsedDuration)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
httpTransport *http.Transport
|
||||
httpClient = &http.Client{}
|
||||
)
|
||||
|
||||
func AddTransport(transport *http.Transport) {
|
||||
httpTransport = transport
|
||||
}
|
||||
|
||||
func GetHttpClient() *http.Client {
|
||||
|
||||
if httpTransport != nil {
|
||||
httpClient.Transport = httpTransport
|
||||
return httpClient
|
||||
}
|
||||
|
||||
return http.DefaultClient
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package supportbundle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/httputil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/redact"
|
||||
)
|
||||
|
||||
func uploadSupportBundle(r *troubleshootv1beta2.ResultRequest, archivePath string) error {
|
||||
contentType := getExpectedContentType(r.URI)
|
||||
if contentType != "" && contentType != "application/tar+gzip" {
|
||||
return fmt.Errorf("cannot upload content type %s", contentType)
|
||||
}
|
||||
|
||||
for {
|
||||
f, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fileStat, err := f.Stat()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "stat file")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(r.Method, r.URI, f)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
req.ContentLength = fileStat.Size()
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
|
||||
httpClient := httputil.GetHttpClient()
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err, httpClient) {
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// send redaction report
|
||||
if r.RedactURI != "" {
|
||||
type PutSupportBundleRedactions struct {
|
||||
Redactions redact.RedactionList `json:"redactions"`
|
||||
}
|
||||
|
||||
redactBytes, err := json.Marshal(PutSupportBundleRedactions{Redactions: redact.GetRedactionList()})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get redaction report")
|
||||
}
|
||||
|
||||
for {
|
||||
req, err := http.NewRequest("PUT", r.RedactURI, bytes.NewReader(redactBytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create redaction report request")
|
||||
}
|
||||
req.ContentLength = int64(len(redactBytes))
|
||||
|
||||
httpClient := httputil.GetHttpClient()
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err, httpClient) {
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "execute redaction request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected redaction status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func callbackSupportBundleAPI(r *troubleshootv1beta2.ResultRequest, archivePath string) error {
|
||||
for {
|
||||
req, err := http.NewRequest(r.Method, r.URI, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
|
||||
httpClient := httputil.GetHttpClient()
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err, httpClient) {
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExpectedContentType(uploadURL string) string {
|
||||
parsedURL, err := url.Parse(uploadURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return parsedURL.Query().Get("Content-Type")
|
||||
}
|
||||
|
||||
func shouldRetryRequest(err error, httpClient *http.Client) bool {
|
||||
if strings.Contains(err.Error(), "x509") && httpClient == http.DefaultClient && canTryInsecure() {
|
||||
httpClient = &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func canTryInsecure() bool {
|
||||
if !isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
return false
|
||||
}
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Connection appears to be insecure. Would you like to attempt to create a support bundle anyway?",
|
||||
IsConfirm: true,
|
||||
}
|
||||
|
||||
_, err := prompt.Run()
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
package supportbundle
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||
"gopkg.in/yaml.v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// TODO (dan): This is VERY similar to the Preflight collect package and should be refactored.
|
||||
func runCollectors(collectors []*troubleshootv1beta2.Collect, additionalRedactors *troubleshootv1beta2.Redactor, filename string, bundlePath string, opts SupportBundleCreateOpts) 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: true,
|
||||
Collect: desiredCollector,
|
||||
ClientConfig: opts.KubernetesRestConfig,
|
||||
Namespace: opts.Namespace,
|
||||
}
|
||||
cleanedCollectors = append(cleanedCollectors, &collector)
|
||||
}
|
||||
|
||||
if err := cleanedCollectors.CheckRBAC(context.Background()); err != nil {
|
||||
return errors.Wrap(err, "failed to check RBAC for collectors")
|
||||
}
|
||||
|
||||
foundForbidden := false
|
||||
for _, c := range cleanedCollectors {
|
||||
for _, e := range c.RBACErrors {
|
||||
foundForbidden = true
|
||||
opts.ProgressChan <- e
|
||||
}
|
||||
}
|
||||
|
||||
if foundForbidden && !opts.CollectWithoutPermissions {
|
||||
return errors.New("insufficient permissions to run all collectors")
|
||||
}
|
||||
|
||||
globalRedactors := []*troubleshootv1beta2.Redact{}
|
||||
if additionalRedactors != nil {
|
||||
globalRedactors = additionalRedactors.Spec.Redactors
|
||||
}
|
||||
|
||||
if opts.SinceTime != nil {
|
||||
applyLogSinceTime(*opts.SinceTime, &cleanedCollectors)
|
||||
}
|
||||
|
||||
// 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())
|
||||
opts.CollectorProgressCallback(opts.ProgressChan, msg)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
opts.CollectorProgressCallback(opts.ProgressChan, collector.GetDisplayName())
|
||||
|
||||
result, err := collector.RunCollectorSync(globalRedactors)
|
||||
if err != nil {
|
||||
opts.ProgressChan <- fmt.Errorf("failed to run collector %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
// results already contain the bundle dir name in their paths
|
||||
err = saveCollectorOutput(result, bundlePath, collector)
|
||||
if err != nil {
|
||||
opts.ProgressChan <- fmt.Errorf("failed to parse collector spec %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findFileName(basename, extension string) (string, error) {
|
||||
n := 1
|
||||
name := basename
|
||||
for {
|
||||
filename := name + "." + extension
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return filename, nil
|
||||
} else if err != nil {
|
||||
return "", errors.Wrap(err, "check file exists")
|
||||
}
|
||||
|
||||
name = fmt.Sprintf("%s (%d)", basename, n)
|
||||
n = n + 1
|
||||
}
|
||||
}
|
||||
|
||||
func ensureCollectorInList(list []*troubleshootv1beta2.Collect, collector troubleshootv1beta2.Collect) []*troubleshootv1beta2.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)
|
||||
}
|
||||
|
||||
const VersionFilename = "version.yaml"
|
||||
|
||||
func writeVersionFile(path string) error {
|
||||
version := troubleshootv1beta2.SupportBundleVersion{
|
||||
ApiVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "SupportBundle",
|
||||
Spec: troubleshootv1beta2.SupportBundleVersionSpec{
|
||||
VersionNumber: version.Version(),
|
||||
},
|
||||
}
|
||||
b, err := yaml.Marshal(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := filepath.Join(path, VersionFilename)
|
||||
err = ioutil.WriteFile(filename, b, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveCollectorOutput(output map[string][]byte, bundlePath string, c *collect.Collector) error {
|
||||
for filename, maybeContents := range output {
|
||||
if c.Collect.Copy != nil {
|
||||
err := untarAndSave(maybeContents, filepath.Join(bundlePath, filepath.Dir(filename)))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "extract copied files")
|
||||
}
|
||||
continue
|
||||
}
|
||||
fileDir, fileName := filepath.Split(filename)
|
||||
outPath := filepath.Join(bundlePath, fileDir)
|
||||
|
||||
if err := os.MkdirAll(outPath, 0777); err != nil {
|
||||
return errors.Wrap(err, "create output file")
|
||||
}
|
||||
|
||||
if err := writeFile(filepath.Join(outPath, fileName), maybeContents); err != nil {
|
||||
return errors.Wrap(err, "write collector output")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func untarAndSave(tarFile []byte, bundlePath string) error {
|
||||
keys := make([]string, 0)
|
||||
dirs := make(map[string]*tar.Header)
|
||||
files := make(map[string][]byte)
|
||||
fileHeaders := make(map[string]*tar.Header)
|
||||
tarReader := tar.NewReader(bytes.NewBuffer(tarFile))
|
||||
//Extract and separate tar contents in file and folders, keeping header info from each one.
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
dirs[header.Name] = header
|
||||
case tar.TypeReg:
|
||||
file := new(bytes.Buffer)
|
||||
_, err = io.Copy(file, tarReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files[header.Name] = file.Bytes()
|
||||
fileHeaders[header.Name] = header
|
||||
default:
|
||||
return fmt.Errorf("tar file entry %s contained unsupported file type %v", header.Name, header.FileInfo().Mode())
|
||||
}
|
||||
}
|
||||
//Create directories from base path: <namespace>/<pod name>/containerPath
|
||||
if err := os.MkdirAll(filepath.Join(bundlePath), 0777); err != nil {
|
||||
return errors.Wrap(err, "create output file")
|
||||
}
|
||||
//Order folders stored in variable keys to start always by parent folder. That way folder info is preserved.
|
||||
for k := range dirs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
//Orderly create folders.
|
||||
for _, k := range keys {
|
||||
if err := os.Mkdir(filepath.Join(bundlePath, k), dirs[k].FileInfo().Mode().Perm()); err != nil {
|
||||
return errors.Wrap(err, "create output file")
|
||||
}
|
||||
}
|
||||
//Populate folders with respective files and its permissions stored in the header.
|
||||
for k, v := range files {
|
||||
if err := ioutil.WriteFile(filepath.Join(bundlePath, k), v, fileHeaders[k].FileInfo().Mode().Perm()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeFile(filename string, contents []byte) error {
|
||||
if err := ioutil.WriteFile(filename, contents, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tarSupportBundleDir(inputDir, outputFilename string) error {
|
||||
fileWriter, err := os.Create(outputFilename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create output file")
|
||||
}
|
||||
defer fileWriter.Close()
|
||||
|
||||
gzipWriter := gzip.NewWriter(fileWriter)
|
||||
defer gzipWriter.Close()
|
||||
|
||||
tarWriter := tar.NewWriter(gzipWriter)
|
||||
defer tarWriter.Close()
|
||||
|
||||
err = filepath.Walk(inputDir, func(filename string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileMode := info.Mode()
|
||||
if !fileMode.IsRegular() { // support bundle can have only files
|
||||
return nil
|
||||
}
|
||||
|
||||
parentDirName := filepath.Dir(inputDir) // this is to have the files inside a subdirectory
|
||||
nameInArchive, err := filepath.Rel(parentDirName, filename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create relative file name")
|
||||
}
|
||||
|
||||
// tar.FileInfoHeader call causes a crash in static builds
|
||||
// https://github.com/golang/go/issues/24787
|
||||
hdr := &tar.Header{
|
||||
Name: nameInArchive,
|
||||
ModTime: info.ModTime(),
|
||||
Mode: int64(fileMode.Perm()),
|
||||
Typeflag: tar.TypeReg,
|
||||
Size: info.Size(),
|
||||
}
|
||||
|
||||
err = tarWriter.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to write tar header")
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
fileReader, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open source file")
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
_, err = io.Copy(tarWriter, fileReader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to copy file into archive")
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to walk source dir")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package supportbundle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/httputil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func GetSupportBundleFromURI(bundleURI string) (*troubleshootv1beta2.SupportBundle, error) {
|
||||
collectorContent, err := LoadSupportBundleSpec(bundleURI)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load collector spec")
|
||||
}
|
||||
|
||||
multidocs := strings.Split(string(collectorContent), "\n---\n")
|
||||
|
||||
supportbundle, err := ParseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse collector")
|
||||
}
|
||||
|
||||
return supportbundle, nil
|
||||
}
|
||||
|
||||
func ParseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta2.SupportBundle, error) {
|
||||
doc, err := docrewrite.ConvertToV1Beta2(doc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
obj, _, err := decode(doc, nil, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse document")
|
||||
}
|
||||
|
||||
collector, ok := obj.(*troubleshootv1beta2.Collector)
|
||||
if ok {
|
||||
supportBundle := troubleshootv1beta2.SupportBundle{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "SupportBundle",
|
||||
},
|
||||
ObjectMeta: collector.ObjectMeta,
|
||||
Spec: troubleshootv1beta2.SupportBundleSpec{
|
||||
Collectors: collector.Spec.Collectors,
|
||||
Analyzers: []*troubleshootv1beta2.Analyze{},
|
||||
AfterCollection: collector.Spec.AfterCollection,
|
||||
},
|
||||
}
|
||||
|
||||
return &supportBundle, nil
|
||||
}
|
||||
|
||||
supportBundle, ok := obj.(*troubleshootv1beta2.SupportBundle)
|
||||
if ok {
|
||||
return supportBundle, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("spec was not parseable as a troubleshoot kind")
|
||||
}
|
||||
|
||||
func GetRedactorFromURI(redactorURI string) (*troubleshootv1beta2.Redactor, error) {
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
redactorContent, err := LoadRedactorSpec(redactorURI)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to load redactor spec %s", redactorURI)
|
||||
}
|
||||
|
||||
redactorContent, err = docrewrite.ConvertToV1Beta2(redactorContent)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
obj, _, err := decode([]byte(redactorContent), nil, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse redactors %s", redactorURI)
|
||||
}
|
||||
|
||||
redactor, ok := obj.(*troubleshootv1beta2.Redactor)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not a troubleshootv1beta2 redactor type", redactorURI)
|
||||
}
|
||||
|
||||
return redactor, nil
|
||||
}
|
||||
|
||||
func LoadSupportBundleSpec(arg string) ([]byte, error) {
|
||||
if strings.HasPrefix(arg, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return nil, errors.Errorf("secret path %s must have 3 components", arg)
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "support-bundle-spec")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from secret")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
return loadSpec(arg)
|
||||
}
|
||||
|
||||
func LoadRedactorSpec(arg string) ([]byte, error) {
|
||||
if strings.HasPrefix(arg, "configmap/") {
|
||||
// format configmap/namespace-name/configmap-name[/data-key]
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) > 4 {
|
||||
return nil, errors.Errorf("configmap path %s must have at most 4 components", arg)
|
||||
}
|
||||
if len(pathParts) < 3 {
|
||||
return nil, errors.Errorf("configmap path %s must have at least 3 components", arg)
|
||||
}
|
||||
|
||||
dataKey := "redactor-spec"
|
||||
if len(pathParts) == 4 {
|
||||
dataKey = pathParts[3]
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromConfigMap(pathParts[1], pathParts[2], dataKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from configmap")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
spec, err := loadSpec(arg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load spec")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func loadSpec(arg string) ([]byte, error) {
|
||||
var err error
|
||||
if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read spec file")
|
||||
}
|
||||
|
||||
return b, nil
|
||||
} else if !util.IsURL(arg) {
|
||||
return nil, fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
|
||||
}
|
||||
|
||||
spec, err := loadSpecFromURL(arg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from URL")
|
||||
}
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func loadSpecFromURL(arg string) ([]byte, error) {
|
||||
for {
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "make request")
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Troubleshoot/v1beta1")
|
||||
req.Header.Set("Bundle-Upload-Host", fmt.Sprintf("%s://%s", req.URL.Scheme, req.URL.Host))
|
||||
httpClient := httputil.GetHttpClient()
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err, httpClient) {
|
||||
continue
|
||||
}
|
||||
return nil, errors.Wrap(err, "execute request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read responce body")
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package supportbundle
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type SupportBundleCreateOpts struct {
|
||||
CollectorProgressCallback func(chan interface{}, string)
|
||||
CollectWithoutPermissions bool
|
||||
HttpClient *http.Client
|
||||
KubernetesRestConfig *rest.Config
|
||||
Namespace string
|
||||
ProgressChan chan interface{}
|
||||
SinceTime *time.Time
|
||||
}
|
||||
|
||||
type SupportBundleResponse struct {
|
||||
AnalyzerResults []*analyzer.AnalyzeResult
|
||||
ArchivePath string
|
||||
fileUploaded bool
|
||||
}
|
||||
|
||||
// SupportBundleCollectAnalyzeProcess collects support bundle from start to finish, including running
|
||||
// collectors, analyzers and after collection steps. Input arguments are specifications.
|
||||
func SupportBundleCollectAnalyzeProcess(spec *troubleshootv1beta2.SupportBundleSpec, additionalRedactors *troubleshootv1beta2.Redactor, opts SupportBundleCreateOpts) (*SupportBundleResponse, error) {
|
||||
|
||||
resultsResponse := SupportBundleResponse{}
|
||||
|
||||
if opts.KubernetesRestConfig == nil {
|
||||
return nil, errors.New("did not receive kube rest config")
|
||||
}
|
||||
|
||||
if opts.ProgressChan == nil {
|
||||
return nil, errors.New("did not receive collector progress chan")
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "supportbundle")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Do we need to put this in some kind of swap space?
|
||||
filename, err := findFileName("support-bundle-"+time.Now().Format("2006-01-02T15_04_05"), "tar.gz")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "find file name")
|
||||
}
|
||||
resultsResponse.ArchivePath = filename
|
||||
|
||||
bundlePath := filepath.Join(tmpDir, strings.TrimSuffix(filename, ".tar.gz"))
|
||||
if err := os.MkdirAll(bundlePath, 0777); err != nil {
|
||||
return nil, errors.Wrap(err, "create bundle dir")
|
||||
}
|
||||
|
||||
if err = writeVersionFile(bundlePath); err != nil {
|
||||
return nil, errors.Wrap(err, "write version file")
|
||||
}
|
||||
|
||||
// Run collectors
|
||||
err = runCollectors(spec.Collectors, additionalRedactors, filename, bundlePath, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to run collectors")
|
||||
}
|
||||
|
||||
// Run Analyzers
|
||||
analyzeResults, err := AnalyzeSupportBundle(spec, tmpDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to run analysis")
|
||||
}
|
||||
resultsResponse.AnalyzerResults = analyzeResults
|
||||
|
||||
if err := tarSupportBundleDir(bundlePath, filename); err != nil {
|
||||
return nil, errors.Wrap(err, "create bundle file")
|
||||
}
|
||||
|
||||
fileUploaded, err := ProcessSupportBundleAfterCollection(spec, filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to process bundle after collection")
|
||||
}
|
||||
resultsResponse.fileUploaded = fileUploaded
|
||||
|
||||
return &resultsResponse, nil
|
||||
}
|
||||
|
||||
// CollectSupportBundleFromURI collects support bundle from start to finish, including running
|
||||
// collectors, analyzers and after collection steps. Input arguments are the URIs of the support bundle and redactor specs.
|
||||
func CollectSupportBundleFromURI(specURI string, redactorURIs []string, opts SupportBundleCreateOpts) (*SupportBundleResponse, error) {
|
||||
|
||||
supportbundle, err := GetSupportBundleFromURI(specURI)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not bundle from URI")
|
||||
}
|
||||
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{}
|
||||
for _, redactor := range redactorURIs {
|
||||
redactorObj, err := GetRedactorFromURI(redactor)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get redactor spec %s", redactor)
|
||||
}
|
||||
|
||||
if redactorObj != nil {
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, redactorObj.Spec.Redactors...)
|
||||
}
|
||||
}
|
||||
|
||||
return SupportBundleCollectAnalyzeProcess(&supportbundle.Spec, additionalRedactors, opts)
|
||||
}
|
||||
|
||||
// CollectSupportBundleFromSpec run the support bundle collectors and creates an archive. The output is the name of the archive on disk
|
||||
// (the caller must remove)
|
||||
func CollectSupportBundleFromSpec(spec *troubleshootv1beta2.SupportBundleSpec, additionalRedactors *troubleshootv1beta2.Redactor, opts SupportBundleCreateOpts) (string, error) {
|
||||
|
||||
if opts.KubernetesRestConfig == nil {
|
||||
return "", errors.New("did not receive kube rest config")
|
||||
}
|
||||
|
||||
if opts.ProgressChan == nil {
|
||||
return "", errors.New("did not receive collector progress chan")
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "supportbundle")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Do we need to put this in some kind of swap space?
|
||||
filename, err := findFileName("support-bundle-"+time.Now().Format("2006-01-02T15_04_05"), "tar.gz")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "find file name")
|
||||
}
|
||||
|
||||
bundlePath := filepath.Join(tmpDir, strings.TrimSuffix(filename, ".tar.gz"))
|
||||
if err := os.MkdirAll(bundlePath, 0777); err != nil {
|
||||
return "", errors.Wrap(err, "create bundle dir")
|
||||
}
|
||||
|
||||
if err = writeVersionFile(bundlePath); err != nil {
|
||||
return "", errors.Wrap(err, "write version file")
|
||||
}
|
||||
|
||||
// Run collectors
|
||||
err = runCollectors(spec.Collectors, additionalRedactors, filename, bundlePath, opts)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "run collectors")
|
||||
}
|
||||
|
||||
if err := tarSupportBundleDir(bundlePath, filename); err != nil {
|
||||
return "", errors.Wrap(err, "create bundle file")
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
// ProcessSupportBundleAfterCollection performs the after collection actions, like Callbacks and sending the archive to a remote server.
|
||||
func ProcessSupportBundleAfterCollection(spec *troubleshootv1beta2.SupportBundleSpec, archivePath string) (bool, error) {
|
||||
fileUploaded := false
|
||||
if len(spec.AfterCollection) > 0 {
|
||||
for _, ac := range spec.AfterCollection {
|
||||
if ac.UploadResultsTo != nil {
|
||||
if err := uploadSupportBundle(ac.UploadResultsTo, archivePath); err != nil {
|
||||
return false, errors.Wrap(err, "failed to upload support bundle")
|
||||
} else {
|
||||
fileUploaded = true
|
||||
}
|
||||
} else if ac.Callback != nil {
|
||||
if err := callbackSupportBundleAPI(ac.Callback, archivePath); err != nil {
|
||||
return false, errors.Wrap(err, "failed to notify API that support bundle has been uploaded")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileUploaded, nil
|
||||
}
|
||||
|
||||
// AnalyzeAndExtractSupportBundle performs analysis on a support bundle using the archive and spec.
|
||||
func AnalyzeAndExtractSupportBundle(spec *troubleshootv1beta2.SupportBundleSpec, archivePath string) ([]*analyzer.AnalyzeResult, error) {
|
||||
|
||||
var analyzeResults []*analyzer.AnalyzeResult
|
||||
|
||||
if len(spec.Analyzers) > 0 {
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
return analyzeResults, errors.Wrap(err, "failed to make directory for analysis")
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
f, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return analyzeResults, errors.Wrap(err, "failed to open support bundle for analysis")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := analyzer.ExtractTroubleshootBundle(f, tmpDir); err != nil {
|
||||
return analyzeResults, errors.Wrap(err, "failed to extract support bundle for analysis")
|
||||
}
|
||||
|
||||
analyzeResults, err = analyzer.AnalyzeLocal(tmpDir, spec.Analyzers)
|
||||
if err != nil {
|
||||
return analyzeResults, errors.Wrap(err, "failed to analyze support bundle")
|
||||
}
|
||||
}
|
||||
return analyzeResults, nil
|
||||
}
|
||||
|
||||
// AnalyzeSupportBundle performs analysis on a support bundle using the support bundle spec and an already unpacked support
|
||||
// bundle on disk
|
||||
func AnalyzeSupportBundle(spec *troubleshootv1beta2.SupportBundleSpec, tmpDir string) ([]*analyzer.AnalyzeResult, error) {
|
||||
|
||||
var analyzeResults []*analyzer.AnalyzeResult
|
||||
|
||||
if len(spec.Analyzers) > 0 {
|
||||
|
||||
analyzeResults, err := analyzer.AnalyzeLocal(tmpDir, spec.Analyzers)
|
||||
if err != nil {
|
||||
return analyzeResults, errors.Wrap(err, "failed to analyze support bundle")
|
||||
}
|
||||
}
|
||||
return analyzeResults, nil
|
||||
}
|
||||
Reference in New Issue
Block a user