mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
Analyze spec
This commit is contained in:
22
Makefile
22
Makefile
@@ -22,14 +22,14 @@ manager: generate fmt vet
|
||||
support-bundle: generate fmt vet
|
||||
go build -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
|
||||
.PHONY: collector
|
||||
collector: generate fmt vet
|
||||
go build -o bin/collector github.com/replicatedhq/troubleshoot/cmd/collector
|
||||
|
||||
.PHONY: preflight
|
||||
preflight: generate fmt vet
|
||||
go build -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
|
||||
.PHONY: analyze
|
||||
analyze: generate fmt vet
|
||||
go build -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
|
||||
.PHONY: run
|
||||
run: generate fmt vet
|
||||
TROUBLESHOOT_EXTERNAL_MANAGER=1 go run ./cmd/manager/main.go
|
||||
@@ -103,14 +103,12 @@ local-release:
|
||||
|
||||
.PHONY: run-preflight
|
||||
run-preflight: preflight
|
||||
./bin/preflight \
|
||||
--image=localhost:32000/troubleshoot:alpha \
|
||||
--pullpolicy=Always \
|
||||
./examples/preflight/sample-preflight.yaml
|
||||
./bin/preflight ./examples/preflight/sample-preflight.yaml
|
||||
|
||||
.PHONY: run-troubleshoot
|
||||
run-troubleshoot: support-bundle
|
||||
./bin/support-bundle \
|
||||
--image=localhost:32000/troubleshoot:alpha \
|
||||
--pullpolicy=Always \
|
||||
./examples/troubleshoot/sample-troubleshoot.yaml
|
||||
./bin/support-bundle ./examples/troubleshoot/sample-troubleshoot.yaml
|
||||
|
||||
.PHONY: run-analyze
|
||||
run-analyze: analyze
|
||||
./bin/analyze --analyzers ./examples/troubleshoot/sample-analyzers.yaml ./support-bundle.tar.gz
|
||||
|
||||
60
cmd/analyze/cli/root.go
Normal file
60
cmd/analyze/cli/root.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
var (
|
||||
KubernetesConfigFlags *genericclioptions.ConfigFlags
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "analyze [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Analyze a support bundle",
|
||||
Long: `Run a series of analyzers on a support bundle archive`,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
|
||||
return runAnalyzers(v, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.Flags().String("analyzers", "", "filename or url of the analyzers to use")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
|
||||
KubernetesConfigFlags.AddFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetEnvPrefix("TROUBLESHOOT")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
75
cmd/analyze/cli/run.go
Normal file
75
cmd/analyze/cli/run.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func runAnalyzers(v *viper.Viper, bundlePath string) error {
|
||||
specPath := v.GetString("analyzers")
|
||||
|
||||
specContent := ""
|
||||
if !isURL(specPath) {
|
||||
if _, err := os.Stat(specPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s was not found", specPath)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(specPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
specContent = string(b)
|
||||
} else {
|
||||
req, err := http.NewRequest("GET", specPath, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Analyzer/v1beta1")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
specContent = string(body)
|
||||
}
|
||||
|
||||
analyzeResults, err := analyzer.DownloadAndAnalyze(specContent, bundlePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to download and analyze bundle")
|
||||
}
|
||||
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
if analyzeResult.IsPass {
|
||||
fmt.Printf("Pass: %s\n %s\n", analyzeResult.Title, analyzeResult.Message)
|
||||
} else if analyzeResult.IsWarn {
|
||||
fmt.Printf("Warn: %s\n %s\n", analyzeResult.Title, analyzeResult.Message)
|
||||
} else if analyzeResult.IsFail {
|
||||
fmt.Printf("Fail: %s\n %s\n", analyzeResult.Title, analyzeResult.Message)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isURL(str string) bool {
|
||||
_, err := url.ParseRequestURI(str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
10
cmd/analyze/main.go
Normal file
10
cmd/analyze/main.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/analyze/cli"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.InitAndExecute()
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
@@ -28,7 +27,7 @@ func Analyze() *cobra.Command {
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
|
||||
result, err := analyzer.DownloadAndAnalyze(context.TODO(), v.GetString("url"))
|
||||
result, err := analyzer.DownloadAndAnalyze("", v.GetString("url"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
19
examples/troubleshoot/sample-analyzers.yaml
Normal file
19
examples/troubleshoot/sample-analyzers.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
kind: Analyzer
|
||||
metadata:
|
||||
name: defaultAnalyzers
|
||||
spec:
|
||||
analyzers:
|
||||
- clusterVersion:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "< 1.13.0"
|
||||
message: The application requires at Kubernetes 1.13.0 or later, and recommends 1.15.0.
|
||||
uri: https://www.kubernetes.io
|
||||
- warn:
|
||||
when: "< 1.15.0"
|
||||
message: Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.15.0 or later.
|
||||
uri: https://kubernetes.io
|
||||
- pass:
|
||||
when: ">= 1.15.0"
|
||||
message: Your cluster meets the recommended and required versions of Kubernetes.
|
||||
@@ -15,10 +15,10 @@ import (
|
||||
)
|
||||
|
||||
//export Analyze
|
||||
func Analyze(bundleURL string, outputFormat string, compatibility string) *C.char {
|
||||
func Analyze(bundleURL string, analyzers string, outputFormat string, compatibility string) *C.char {
|
||||
logger.SetQuiet(true)
|
||||
|
||||
result, err := analyzer.DownloadAndAnalyze(context.TODO(), bundleURL)
|
||||
result, err := analyzer.DownloadAndAnalyze(context.Background(), bundleURL, analyzers)
|
||||
if err != nil {
|
||||
fmt.Printf("error downloading and analyzing: %s\n", err.Error())
|
||||
return C.CString("")
|
||||
|
||||
@@ -3,26 +3,24 @@ package analyzer
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
getter "github.com/hashicorp/go-getter"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
type fileContentProvider struct {
|
||||
rootDir string
|
||||
}
|
||||
|
||||
func DownloadAndAnalyze(ctx context.Context, bundleURL string) ([]*AnalyzeResult, error) {
|
||||
func DownloadAndAnalyze(analyzersSpec string, bundleURL string) ([]*AnalyzeResult, error) {
|
||||
tmpDir, err := ioutil.TempDir("", "troubleshoot-k8s")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create temp dir")
|
||||
@@ -38,9 +36,20 @@ func DownloadAndAnalyze(ctx context.Context, bundleURL string) ([]*AnalyzeResult
|
||||
return nil, errors.Wrap(err, "failed to read version.yaml")
|
||||
}
|
||||
|
||||
analyzers, err := getTroubleshootAnalyzers()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get analyzers")
|
||||
analyzers := []*troubleshootv1beta1.Analyze{}
|
||||
|
||||
if analyzersSpec == "" {
|
||||
defaultAnalyzers, err := getDefaultAnalyzers()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get default analyzers")
|
||||
}
|
||||
analyzers = defaultAnalyzers
|
||||
} else {
|
||||
parsedAnalyzers, err := parseAnalyzers(analyzersSpec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse analyzers")
|
||||
}
|
||||
analyzers = parsedAnalyzers
|
||||
}
|
||||
|
||||
fcp := fileContentProvider{rootDir: tmpDir}
|
||||
@@ -138,29 +147,41 @@ func extractTroubleshootBundle(reader io.Reader, destDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTroubleshootAnalyzers() ([]*troubleshootv1beta1.Analyze, error) {
|
||||
specURL := `https://troubleshoot.replicated.com/`
|
||||
resp, err := http.Get(specURL)
|
||||
func parseAnalyzers(spec string) ([]*troubleshootv1beta1.Analyze, error) {
|
||||
troubleshootscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
obj, _, err := decode([]byte(spec), nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("could not download analyzer spec, status code: %v", resp.StatusCode)
|
||||
return nil, errors.Wrap(err, "failed to decode analyzers")
|
||||
}
|
||||
|
||||
spec, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
analyzer := obj.(*troubleshootv1beta1.Analyzer)
|
||||
return analyzer.Spec.Analyzers, nil
|
||||
}
|
||||
|
||||
preflight := troubleshootv1beta1.Preflight{}
|
||||
if err := yaml.Unmarshal([]byte(spec), &preflight); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func getDefaultAnalyzers() ([]*troubleshootv1beta1.Analyze, error) {
|
||||
spec := `apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
kind: Analyzer
|
||||
metadata:
|
||||
name: defaultAnalyzers
|
||||
spec:
|
||||
analyzers:
|
||||
- clusterVersion:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "< 1.13.0"
|
||||
message: The application requires at Kubernetes 1.13.0 or later, and recommends 1.15.0.
|
||||
uri: https://www.kubernetes.io
|
||||
- warn:
|
||||
when: "< 1.15.0"
|
||||
message: Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.15.0 or later.
|
||||
uri: https://kubernetes.io
|
||||
- pass:
|
||||
when: ">= 1.15.0"
|
||||
message: Your cluster meets the recommended and required versions of Kubernetes.`
|
||||
|
||||
return preflight.Spec.Analyzers, nil
|
||||
return parseAnalyzers(spec)
|
||||
}
|
||||
|
||||
func (f fileContentProvider) getFileContents(fileName string) ([]byte, error) {
|
||||
|
||||
@@ -20,19 +20,13 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// AnalyzerSpec defines the desired state of Analyzer
|
||||
type AnalyzerSpec struct {
|
||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
Analyzers []*Analyze `json:"analyzers,omitempty"`
|
||||
}
|
||||
|
||||
// AnalyzerStatus defines the observed state of Analyzer
|
||||
type AnalyzerStatus struct {
|
||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
}
|
||||
|
||||
// +genclient
|
||||
|
||||
@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// autogenerated by controller-gen object, do not modify manually
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -141,7 +141,7 @@ func (in *Analyzer) DeepCopyInto(out *Analyzer) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
}
|
||||
|
||||
@@ -287,6 +287,17 @@ func (in *AnalyzerList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AnalyzerSpec) DeepCopyInto(out *AnalyzerSpec) {
|
||||
*out = *in
|
||||
if in.Analyzers != nil {
|
||||
in, out := &in.Analyzers, &out.Analyzers
|
||||
*out = make([]*Analyze, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(Analyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnalyzerSpec.
|
||||
|
||||
Reference in New Issue
Block a user