Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: "troubleshoot support-bundle" subcommand to succeed "support-bundle" command #1249

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ BUILDFLAGS = -tags "netgo containers_image_ostree_stub exclude_graphdriver_devic
BUILDPATHS = ./pkg/... ./cmd/... ./internal/...
TESTFLAGS ?= -v -coverprofile cover.out

all: test support-bundle preflight collect analyze
all: test build

.PHONY: ffi
ffi: fmt vet
Expand Down Expand Up @@ -73,7 +73,11 @@ support-bundle-e2e-test:
# Build all binaries in parallel ( -j )
build:
@echo "Build cli binaries"
$(MAKE) -j support-bundle preflight analyze collect
$(MAKE) -j support-bundle preflight analyze collect troubleshoot

.PHONY: troubleshoot
troubleshoot:
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/troubleshoot github.com/replicatedhq/troubleshoot/cmd/troubleshootv2

.PHONY: support-bundle
support-bundle:
Expand Down
16 changes: 16 additions & 0 deletions cmd/troubleshootv2/cli/analyze.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cli

import (
"github.com/spf13/cobra"
)

func AnalyzeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "analyze",
Short: "Analyse an existing support bundle given a troubleshoot spec with analysers",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
return cmd
}
16 changes: 16 additions & 0 deletions cmd/troubleshootv2/cli/inspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cli

import (
"github.com/spf13/cobra"
)

func InspectCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "inspect",
Short: "Launches a k8s API server from an existing support bundle which one can inspect using kubectl",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
return cmd
}
17 changes: 17 additions & 0 deletions cmd/troubleshootv2/cli/preflight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cli

import (
"github.com/spf13/cobra"
)

func PreflightCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "preflight",
Short: "Runs preflight checks",
Long: "Runs preflight checks defined in a troubleshoot spec against a cluster or node.",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
return cmd
}
16 changes: 16 additions & 0 deletions cmd/troubleshootv2/cli/redact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cli

import (
"github.com/spf13/cobra"
)

func RedactCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "redact",
Short: "Redacts an existing support bundle given a troubleshoot spec with redactors",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
return cmd
}
168 changes: 168 additions & 0 deletions cmd/troubleshootv2/cli/supportbundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package cli

import (
"bytes"
"context"
"fmt"
"os"
"path"
"sync"
"time"

"github.com/replicatedhq/troubleshoot/internal/tsbundle"
"github.com/replicatedhq/troubleshoot/internal/util"
"github.com/replicatedhq/troubleshoot/pkg/bundle"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/loader"
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel"
"k8s.io/klog/v2"
)

func SupporBundleCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "support-bundle [urls...]",
Args: cobra.MinimumNArgs(0),
Short: "Generate a support bundle",
Long: `A support bundle is an archive of files, output, metrics and state
from a server that can be used to assist when troubleshooting a Kubernetes cluster.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := doRun(cmd.Context(), args)
if err != nil {
klog.Errorf("Failure collecting support bundle: %v", err)
return err
}

return nil
},
}

// TODO: Can we group kubectl flags together e.g Kubernetes Flags, like we have Global Flags?
// Non-trivial cause cobra doesn't support it. See
// https://github.com/spf13/cobra/issues/1327
// Related issues/links:
// * https://github.com/spf13/cobra/pull/1778
// * https://github.com/karmada-io/karmada/blob/3ddee004adb21bf20b9a0807c59c015c2e28ecf5/cmd/controller-manager/app/controllermanager.go#L79-L95
// * https://github.com/kubernetes/component-base/blob/b5a495af30a7bb04642ce82f4816b47e75f78dbe/cli/flag/sectioned.go#L33-L41
// * https://github.com/aquasecurity/trivy/pull/2488
k8sutil.AddFlags(cmd.Flags())

return cmd
}

func doRun(ctx context.Context, args []string) error {
// TODO: This logic must be functionally equivalent to CollectSupportBundleFromSpec

// Boilerplate to collect progress information
var wg sync.WaitGroup
progressChan := make(chan interface{})
defer func() {
close(progressChan)
wg.Wait()
}()

wg.Add(1)
go func() {
defer wg.Done()
for msg := range progressChan {
// TODO: Expect error or string types
klog.Infof("Collecting bundle: %v", msg)
}
}()
ctxWrap, root := otel.Tracer(constants.LIB_TRACER_NAME).Start(
ctx, constants.TROUBLESHOOT_ROOT_SPAN_NAME,
)
defer root.End()
bundleDir, err := os.MkdirTemp("", "troubleshoot")
if err != nil {
return err
}
defer os.RemoveAll(bundleDir)

bdl := tsbundle.NewTroubleshootBundle(tsbundle.TroubleshootBundleOptions{
ProgressChan: progressChan,
})

// 1. Load troubleshoot specs from args
// TODO: "RawSpecsFromArgs" missing the logic to load specs from the cluster
rawSpecs, err := util.RawSpecsFromArgs(args)
if err != nil {
return err
}
kinds, err := loader.LoadSpecs(ctxWrap, loader.LoadOptions{
RawSpecs: rawSpecs,
})
if err != nil {
return err
}

// 2. Collect the support bundle
klog.Infof("Collect support bundle")
err = bdl.Collect(ctxWrap, bundle.CollectOptions{
Specs: kinds,
BundleDir: bundleDir,
})
if err != nil {
return err
}

// 3. Analyze the support bundle
// TODO: Add results to the support bundle
klog.Infof("Analyse support bundle")
out, err := bdl.Analyze(ctxWrap, bundle.AnalyzeOptions{
Specs: kinds,
})
if err != nil {
return err
}
// Save the analysis results to the bundle. Do it here so as not to redact
// TODO: Perhaps the result should already be marshalled to JSON
// i.e out.ResultsJSON propert or a function like out.ResultsJSON()
analysis, err := out.ResultsJSON()
if err != nil {
return err
}
err = bdl.BundleData().Data().SaveResult(bundleDir, supportbundle.AnalysisFilename, bytes.NewBuffer(analysis))
if err != nil {
return err
}

// 4. Redact the support bundle
klog.Infof("Redact support bundle")
err = bdl.Redact(ctxWrap, bundle.RedactOptions{
Specs: kinds,
})
if err != nil {
return err
}

// 5. Archive the support bundle
klog.Infof("Archive support bundle")
supportBundlePath := path.Join(util.HomeDir(), fmt.Sprintf("support-bundle-%s.tgz", time.Now().Format("2006-01-02T15_04_05")))
err = bdl.Archive(ctxWrap, bundle.ArchiveOptions{
ArchivePath: supportBundlePath,
})
if err != nil {
return err
}

// 6. Save the bundle to a file
klog.Infof("Save version info to support bundle")
reader, err := supportbundle.GetVersionFile()
if err != nil {
return err
}
err = bdl.BundleData().Data().SaveResult(bundleDir, constants.VERSION_FILENAME, reader)
if err != nil {
return err
}

// 7. Print outro i.e. "Support bundle saved to <filename>"
// Print to screen output of bdl.Analyze i.e "analysisResults"
fmt.Printf("Support bundle saved to %s\n", supportBundlePath)

return nil
}
77 changes: 77 additions & 0 deletions cmd/troubleshootv2/cli/troubleshoot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cli

import (
"os"
"strings"

"github.com/replicatedhq/troubleshoot/cmd/util"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/klog/v2"
)

// Load
// Collect
// Analyze
// Redact
// Archive
// Inspect (serve)

func RootCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "troubleshoot",
Short: "Troubleshoot commandline interface",
Long: "A tool for collecting, redacting, analysing support bundles and " +
"running preflight checks on Kubernetes clusters.\n\n" +
"For more information, visit https://troubleshoot.sh",
SilenceUsage: true,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
v := viper.GetViper()
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
v.BindPFlags(cmd.Flags())

logger.SetupLogger(v)

if err := util.StartProfiling(); err != nil {
klog.Errorf("Failed to start profiling: %v", err)
}
},
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if err := util.StopProfiling(); err != nil {
klog.Errorf("Failed to stop profiling: %v", err)
}
},
}

cobra.OnInitialize(initConfig)

// Subcommands
cmd.AddCommand(SupporBundleCmd())
cmd.AddCommand(PreflightCmd())
cmd.AddCommand(AnalyzeCmd())
cmd.AddCommand(RedactCmd())
cmd.AddCommand(InspectCmd())

// Initialize klog flags
logger.InitKlogFlags(cmd)

// CPU and memory profiling flags
util.AddProfilingFlags(cmd)

return cmd
}

func InitAndExecute() {
if err := RootCmd().Execute(); err != nil {
os.Exit(1)
}
}

func initConfig() {
viper.SetEnvPrefix("TROUBLESHOOT")
viper.AutomaticEnv()
}
22 changes: 22 additions & 0 deletions cmd/troubleshootv2/cli/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cli

import (
"fmt"

"github.com/replicatedhq/troubleshoot/pkg/version"
"github.com/spf13/cobra"
)

func VersionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "Print the current version and exit",
Long: `Print the current version and exit`,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("Replicated Troubleshoot %s\n", version.Version())

return nil
},
}
return cmd
}
10 changes: 10 additions & 0 deletions cmd/troubleshootv2/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import (
"github.com/replicatedhq/troubleshoot/cmd/troubleshootv2/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)

func main() {
cli.InitAndExecute()
}
Loading