Abstract, add mysql

This commit is contained in:
Marc Campbell
2020-03-20 07:19:03 -07:00
parent 562a565f1f
commit 77eba55ee8
14 changed files with 315 additions and 108 deletions
+1
View File
@@ -17,6 +17,7 @@ require (
github.com/gizak/termui/v3 v3.1.0
github.com/go-openapi/spec v0.19.4 // indirect
github.com/go-openapi/validate v0.19.5 // indirect
github.com/go-sql-driver/mysql v1.5.0
github.com/golang/snappy v0.0.1 // indirect
github.com/google/gofuzz v1.1.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
+2
View File
@@ -212,6 +212,8 @@ github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/flect v0.1.5 h1:xpKq9ap8MbYfhuPCF0dBH854Gp9CxZjr/IocxELFflo=
github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
+13 -3
View File
@@ -157,15 +157,25 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
}
return analyzeTextAnalyze(analyzer.TextAnalyze, getFile)
}
if analyzer.PostgresAnalyze != nil {
isExcluded, err := isExcluded(analyzer.PostgresAnalyze.Exclude)
if analyzer.Postgres != nil {
isExcluded, err := isExcluded(analyzer.Postgres.Exclude)
if err != nil {
return nil, err
}
if isExcluded {
return nil, nil
}
return analyzePostgresAnalyze(analyzer.PostgresAnalyze, getFile)
return analyzePostgres(analyzer.Postgres, getFile)
}
if analyzer.Mysql != nil {
isExcluded, err := isExcluded(analyzer.Mysql.Exclude)
if err != nil {
return nil, err
}
if isExcluded {
return nil, nil
}
return analyzeMysql(analyzer.Mysql, getFile)
}
return nil, errors.New("invalid analyzer")
+52
View File
@@ -0,0 +1,52 @@
package analyzer
import (
"fmt"
"strconv"
"strings"
"github.com/blang/semver"
"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func compareDatabaseConditionalToActual(conditional string, result *collect.DatabaseConnection) (bool, error) {
parts := strings.Split(strings.TrimSpace(conditional), " ")
if len(parts) != 3 {
return false, errors.New("unable to parse conditional")
}
switch parts[0] {
case "connected":
expected, err := strconv.ParseBool(parts[2])
if err != nil {
return false, errors.Wrap(err, "failed to parse bool")
}
switch parts[1] {
case "=", "==", "===":
return expected == result.IsConnected, nil
case "!=", "!==":
return expected != result.IsConnected, nil
}
return false, errors.New("unable to parse postgres connected analyzer")
case "version":
expectedRange, err := semver.ParseRange(fmt.Sprintf("%s %s", parts[1], parts[2]))
if err != nil {
return false, errors.Wrap(err, "failed to parse semver range")
}
actual, err := semver.Parse(result.Version)
if err != nil {
return false, errors.Wrap(err, "failed to parse actual psotgres version")
}
return expectedRange(actual), nil
}
return false, nil
}
+110
View File
@@ -0,0 +1,110 @@
package analyzer
import (
"encoding/json"
"fmt"
"path"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeMysql(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collectorName := analyzer.CollectorName
if collectorName == "" {
collectorName = "mysql"
}
fullPath := path.Join("mysql", fmt.Sprintf("%s.json", collectorName))
collected, err := getCollectedFileContents(fullPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
}
databaseConnection := collect.DatabaseConnection{}
if err := json.Unmarshal(collected, &databaseConnection); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal databased connection result")
}
checkName := analyzer.CheckName
if checkName == "" {
checkName = collectorName
}
result := &AnalyzeResult{
Title: checkName,
IconKey: "kubernetes_mysql_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/mysql-analyze.svg",
}
for _, outcome := range analyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return result, nil
}
isMatch, err := compareDatabaseConditionalToActual(outcome.Fail.When, &databaseConnection)
if err != nil {
return result, errors.Wrap(err, "failed to compare mysql database conditional")
}
if isMatch {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return result, nil
}
} else if outcome.Warn != nil {
if outcome.Pass.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return result, nil
}
isMatch, err := compareDatabaseConditionalToActual(outcome.Warn.When, &databaseConnection)
if err != nil {
return result, errors.Wrap(err, "failed to compare mysql database conditional")
}
if isMatch {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return result, nil
}
isMatch, err := compareDatabaseConditionalToActual(outcome.Pass.When, &databaseConnection)
if err != nil {
return result, errors.Wrap(err, "failed to compare mysql database conditional")
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return result, nil
}
}
}
return result, nil
}
@@ -4,16 +4,13 @@ import (
"encoding/json"
"fmt"
"path"
"strconv"
"strings"
"github.com/blang/semver"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzePostgresAnalyze(analyzer *troubleshootv1beta1.PostgresAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzePostgres(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collectorName := analyzer.CollectorName
if collectorName == "" {
collectorName = "postgres"
@@ -111,44 +108,3 @@ func analyzePostgresAnalyze(analyzer *troubleshootv1beta1.PostgresAnalyze, getCo
return result, nil
}
func compareDatabaseConditionalToActual(conditional string, result *collect.DatabaseConnection) (bool, error) {
parts := strings.Split(strings.TrimSpace(conditional), " ")
if len(parts) != 3 {
return false, errors.New("unable to parse conditional")
}
switch parts[0] {
case "connected":
expected, err := strconv.ParseBool(parts[2])
if err != nil {
return false, errors.Wrap(err, "failed to parse bool")
}
switch parts[1] {
case "=", "==", "===":
return expected == result.IsConnected, nil
case "!=", "!==":
return expected != result.IsConnected, nil
}
return false, errors.New("unable to parse postgres connected analyzer")
case "version":
expectedRange, err := semver.ParseRange(fmt.Sprintf("%s %s", parts[1], parts[2]))
if err != nil {
return false, errors.Wrap(err, "failed to parse semver range")
}
actual, err := semver.Parse(result.Version)
if err != nil {
return false, errors.Wrap(err, "failed to parse actual psotgres version")
}
return expectedRange(actual), nil
}
return false, nil
}
@@ -103,7 +103,7 @@ type TextAnalyze struct {
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type PostgresAnalyze struct {
type DatabaseAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
CollectorName string `json:"collectorName" yaml:"collectorName"`
@@ -128,5 +128,6 @@ type Analyze struct {
Distribution *Distribution `json:"distribution,omitempty" yaml:"distribution,omitempty"`
NodeResources *NodeResources `json:"nodeResources,omitempty" yaml:"nodeResources,omitempty"`
TextAnalyze *TextAnalyze `json:"textAnalyze,omitempty" yaml:"textAnalyze,omitempty"`
PostgresAnalyze *PostgresAnalyze `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Postgres *DatabaseAnalyze `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Mysql *DatabaseAnalyze `json:"mysql,omitempty" yaml:"mysql,omitempty"`
}
@@ -109,7 +109,7 @@ type Put struct {
Body string `json:"body,omitempty" yaml:"body,omitempty"`
}
type Postgres struct {
type Database struct {
CollectorMeta `json:",inline" yaml:",inline"`
URI string `json:"uri" yaml:"uri"`
}
@@ -124,7 +124,8 @@ type Collect struct {
Data *Data `json:"data,omitempty" yaml:"data,omitempty"`
Copy *Copy `json:"copy,omitempty" yaml:"copy,omitempty"`
HTTP *HTTP `json:"http,omitempty" yaml:"http,omitempty"`
Postgres *Postgres `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Postgres *Database `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Mysql *Database `json:"mysql,omitempty" yaml:"mysql,omitempty"`
}
func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSubjectAccessReviewSpec {
@@ -112,9 +112,14 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = new(TextAnalyze)
(*in).DeepCopyInto(*out)
}
if in.PostgresAnalyze != nil {
in, out := &in.PostgresAnalyze, &out.PostgresAnalyze
*out = new(PostgresAnalyze)
if in.Postgres != nil {
in, out := &in.Postgres, &out.Postgres
*out = new(DatabaseAnalyze)
(*in).DeepCopyInto(*out)
}
if in.Mysql != nil {
in, out := &in.Mysql, &out.Mysql
*out = new(DatabaseAnalyze)
(*in).DeepCopyInto(*out)
}
}
@@ -470,7 +475,12 @@ func (in *Collect) DeepCopyInto(out *Collect) {
}
if in.Postgres != nil {
in, out := &in.Postgres, &out.Postgres
*out = new(Postgres)
*out = new(Database)
**out = **in
}
if in.Mysql != nil {
in, out := &in.Mysql, &out.Mysql
*out = new(Database)
**out = **in
}
}
@@ -823,6 +833,49 @@ func (in *Data) DeepCopy() *Data {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Database) DeepCopyInto(out *Database) {
*out = *in
out.CollectorMeta = in.CollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Database.
func (in *Database) DeepCopy() *Database {
if in == nil {
return nil
}
out := new(Database)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DatabaseAnalyze) DeepCopyInto(out *DatabaseAnalyze) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseAnalyze.
func (in *DatabaseAnalyze) DeepCopy() *DatabaseAnalyze {
if in == nil {
return nil
}
out := new(DatabaseAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) {
*out = *in
@@ -1160,49 +1213,6 @@ func (in *Post) DeepCopy() *Post {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Postgres) DeepCopyInto(out *Postgres) {
*out = *in
out.CollectorMeta = in.CollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Postgres.
func (in *Postgres) DeepCopy() *Postgres {
if in == nil {
return nil
}
out := new(Postgres)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PostgresAnalyze) DeepCopyInto(out *PostgresAnalyze) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresAnalyze.
func (in *PostgresAnalyze) DeepCopy() *PostgresAnalyze {
if in == nil {
return nil
}
out := new(PostgresAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Preflight) DeepCopyInto(out *Preflight) {
*out = *in
+10
View File
@@ -141,6 +141,16 @@ func (c *Collector) RunCollectorSync() ([]byte, error) {
}
return Postgres(c.GetContext(), c.Collect.Postgres)
}
if c.Collect.Mysql != nil {
isExcluded, err := isExcluded(c.Collect.Mysql.Exclude)
if err != nil {
return nil, err
}
if isExcluded {
return nil, nil
}
return Mysql(c.GetContext(), c.Collect.Mysql)
}
return nil, errors.New("no spec found to run")
}
+7
View File
@@ -0,0 +1,7 @@
package collect
type DatabaseConnection struct {
IsConnected bool `json:"isConnected"`
Error string `json:"error"`
Version string `json:"version"`
}
+53
View File
@@ -0,0 +1,53 @@
package collect
import (
"database/sql"
"encoding/json"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
)
type MysqlOutput map[string][]byte
func Mysql(ctx *Context, databaseCollector *troubleshootv1beta1.Database) ([]byte, error) {
databaseConnection := DatabaseConnection{}
db, err := sql.Open("mysql", databaseCollector.URI)
if err != nil {
databaseConnection.Error = err.Error()
} else {
query := `select version()`
row := db.QueryRow(query)
version := ""
if err := row.Scan(&version); err != nil {
databaseConnection.Error = err.Error()
} else {
databaseConnection.IsConnected = true
databaseConnection.Version = version
}
}
b, err := json.Marshal(databaseConnection)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal database connection")
}
collectorName := databaseCollector.CollectorName
if collectorName == "" {
collectorName = "mysql"
}
mysqlOutput := map[string][]byte{
fmt.Sprintf("mysql/%s.json", collectorName): b,
}
bb, err := json.Marshal(mysqlOutput)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal mysql output")
}
return bb, nil
}
+3 -9
View File
@@ -12,16 +12,10 @@ import (
type PostgresOutput map[string][]byte
type DatabaseConnection struct {
IsConnected bool `json:"isConnected"`
Error string `json:"error"`
Version string `json:"version"`
}
func Postgres(ctx *Context, postgresCollector *troubleshootv1beta1.Postgres) ([]byte, error) {
func Postgres(ctx *Context, databaseCollector *troubleshootv1beta1.Database) ([]byte, error) {
databaseConnection := DatabaseConnection{}
db, err := sql.Open("postgres", postgresCollector.URI)
db, err := sql.Open("postgres", databaseCollector.URI)
if err != nil {
databaseConnection.Error = err.Error()
} else {
@@ -41,7 +35,7 @@ func Postgres(ctx *Context, postgresCollector *troubleshootv1beta1.Postgres) ([]
return nil, errors.Wrap(err, "failed to marshal database connection")
}
collectorName := postgresCollector.CollectorName
collectorName := databaseCollector.CollectorName
if collectorName == "" {
collectorName = "postgres"
}