Analyze spec

This commit is contained in:
Marc Campbell
2019-10-29 19:45:25 +00:00
parent 25ec38bdf5
commit 14ace5294f
10 changed files with 238 additions and 51 deletions

View File

@@ -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
View 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
View 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
View 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()
}

View File

@@ -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
}

View 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.

View File

@@ -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("")

View File

@@ -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) {

View File

@@ -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

View File

@@ -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.