diff --git a/dev/citools/kibana.go b/dev/citools/kibana.go new file mode 100644 index 00000000000..aef47205cb0 --- /dev/null +++ b/dev/citools/kibana.go @@ -0,0 +1,50 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package citools + +import ( + "fmt" + "strings" + + "github.com/Masterminds/semver/v3" +) + +func KibanaConstraintPackage(path string) (*semver.Constraints, error) { + manifest, err := readPackageManifest(path) + if err != nil { + return nil, fmt.Errorf("failed to read package manifest: %w", err) + } + + kibanaVersion := manifest.Conditions.Kibana.Version + if kibanaVersion == "" { + return nil, nil + } + + constraint, err := semver.NewConstraint(kibanaVersion) + if err != nil { + return nil, fmt.Errorf("failed to parse kibana constraint: %w", err) + } + return constraint, nil +} + +func IsPackageSupportedInStackVersion(stackVersion string, path string) (bool, error) { + stackVersion = strings.TrimSuffix(stackVersion, "-SNAPSHOT") + + stackSemVersion, err := semver.NewVersion(stackVersion) + if err != nil { + return false, fmt.Errorf("failed to parse stack version: %w", err) + } + + packageConstraint, err := KibanaConstraintPackage(path) + if err != nil { + return false, err + } + + if packageConstraint == nil { + return true, nil + } + + return packageConstraint.Check(stackSemVersion), nil +} diff --git a/dev/citools/kibana_test.go b/dev/citools/kibana_test.go new file mode 100644 index 00000000000..8f550080bb3 --- /dev/null +++ b/dev/citools/kibana_test.go @@ -0,0 +1,141 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package citools + +import ( + "os" + "path/filepath" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestKibanaConstraintPackage(t *testing.T) { + constraintTest, err := semver.NewConstraint("^8.0.0") + require.NoError(t, err) + + cases := []struct { + title string + contents string + expected *semver.Constraints + }{ + { + title: "kibana constrasint defined", + contents: `name: "version" +conditions: + kibana: + version: "^8.0.0" +`, + expected: constraintTest, + }, + { + title: "kibana constraint defined with dotted field", + contents: `name: "version" +conditions: + kibana.version: "^8.0.0" +`, + expected: constraintTest, + }, + { + title: "kibana constraint not defined", + contents: `name: "version" +`, + expected: nil, + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + directory := t.TempDir() + pkgManifestPath := filepath.Join(directory, "manifest.yml") + err := os.WriteFile(pkgManifestPath, []byte(c.contents), 0o644) + require.NoError(t, err) + constraint, err := KibanaConstraintPackage(pkgManifestPath) + require.NoError(t, err) + assert.Equal(t, c.expected, constraint) + }) + } +} + +func TestIsPackageSupportedInStackVersion(t *testing.T) { + cases := []struct { + title string + contents string + stackVersion string + supported bool + }{ + { + title: "Test simple kibana constraint", + stackVersion: "8.18.0", + contents: `name: "stack" +conditions: + kibana: + version: "^8.0.0" +`, + supported: true, + }, + { + title: "Test or condition", + stackVersion: "8.18.0", + contents: `name: "stack" +conditions: + kibana: + version: "^8.0.0 || ^9.0.0" +`, + supported: true, + }, + { + title: "Test snapshot", + stackVersion: "8.18.0-SNAPSHOT", + contents: `name: "stack" +conditions: + kibana: + version: "^8.0.0 || ^9.0.0" +`, + supported: true, + }, + { + title: "Test greater or equal", + stackVersion: "8.18.0-SNAPSHOT", + contents: `name: "stack" +conditions: + kibana: + version: ">=8.0.0" +`, + supported: true, + }, + { + title: "Test not supported", + stackVersion: "8.18.0-SNAPSHOT", + contents: `name: "stack" +conditions: + kibana: + version: "^9.0.0" +`, + supported: false, + }, + { + title: "Test missing kibana version", + stackVersion: "8.18.0-SNAPSHOT", + contents: `name: "stack" +`, + supported: true, + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + directory := t.TempDir() + pkgManifestPath := filepath.Join(directory, "manifest.yml") + err := os.WriteFile(pkgManifestPath, []byte(c.contents), 0o644) + require.NoError(t, err) + supported, err := IsPackageSupportedInStackVersion(c.stackVersion, pkgManifestPath) + require.NoError(t, err) + assert.Equal(t, c.supported, supported) + }) + } +} diff --git a/dev/citools/logsdb.go b/dev/citools/logsdb.go new file mode 100644 index 00000000000..70829a82214 --- /dev/null +++ b/dev/citools/logsdb.go @@ -0,0 +1,59 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package citools + +import ( + "fmt" + + "github.com/Masterminds/semver/v3" +) + +var ( + semver8_17_0 = semver.MustParse("8.17.0") + semver8_19_99 = semver.MustParse("8.19.99") + semver9_99_99 = semver.MustParse("9.99.99") +) + +func IsVersionLessThanLogsDBGA(version *semver.Version) bool { + return version.LessThan(semver8_17_0) +} + +func packageKibanaConstraint(path string) (*semver.Constraints, error) { + manifest, err := readPackageManifest(path) + if err != nil { + return nil, err + } + + kibanaConstraint := manifest.Conditions.Kibana.Version + if kibanaConstraint == "" { + return nil, nil + } + + constraints, err := semver.NewConstraint(kibanaConstraint) + if err != nil { + return nil, err + } + + return constraints, nil +} + +func IsLogsDBSupportedInPackage(path string) (bool, error) { + constraint, err := packageKibanaConstraint(path) + if err != nil { + return false, fmt.Errorf("failed to read kibana.constraint fro mmanifest: %w", err) + } + + if constraint == nil { + // Package does not contain any kibana.version + return true, nil + } + + // Ensure that the package supports LogsDB mode + // It is not used here "semver8_17_0" since a constraint like "^8.18.0 || ^9.0.0" would return false + if constraint.Check(semver8_19_99) || constraint.Check(semver9_99_99) { + return true, nil + } + return false, nil +} diff --git a/dev/citools/logsdb_test.go b/dev/citools/logsdb_test.go new file mode 100644 index 00000000000..1db3db1997c --- /dev/null +++ b/dev/citools/logsdb_test.go @@ -0,0 +1,104 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package citools + +import ( + "os" + "path/filepath" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsVersionLessThanLogsDBGA(t *testing.T) { + cases := []struct { + title string + version *semver.Version + expected bool + }{ + { + title: "less than LogsDB GA", + version: semver.MustParse("8.12.0"), + expected: true, + }, + { + title: "greater or equal than LogsSB GA", + version: semver.MustParse("8.17.0"), + expected: false, + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + value := IsVersionLessThanLogsDBGA(c.version) + assert.Equal(t, c.expected, value) + }) + } + +} + +func TestIsLogsDBSupportedInPackage(t *testing.T) { + cases := []struct { + title string + contents string + expectedError bool + supported bool + }{ + { + title: "Supported LogsDB field", + contents: `name: "logsdb" +conditions: + kibana: + version: "^7.16.0 || ^8.0.0 || ^9.0.0" +`, + expectedError: false, + supported: true, + }, + { + title: "Kibana constraint dotted field", + contents: `name: "subscription" +conditions: + kibana.version: "^7.16.0 || ^8.0.0 || ^9.0.0" +`, + expectedError: false, + supported: true, + }, + { + title: "LogsDB not supported", + contents: `name: "subscription" +conditions: + kibana.version: "^7.16.0" +`, + expectedError: false, + supported: false, + }, + { + title: "No Kibana constraint", + contents: `name: "subscription" +`, + expectedError: false, + supported: true, + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + directory := t.TempDir() + pkgManifestPath := filepath.Join(directory, "manifest.yml") + err := os.WriteFile(pkgManifestPath, []byte(c.contents), 0o644) + require.NoError(t, err) + supported, err := IsLogsDBSupportedInPackage(pkgManifestPath) + if c.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, c.supported, supported) + } + }) + } + +} diff --git a/dev/citools/packagemanifest.go b/dev/citools/packagemanifest.go new file mode 100644 index 00000000000..7c2c61ca9e2 --- /dev/null +++ b/dev/citools/packagemanifest.go @@ -0,0 +1,48 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package citools + +import ( + "fmt" + + "github.com/elastic/go-ucfg" + "github.com/elastic/go-ucfg/yaml" +) + +// kibanaConditions defines conditions for Kibana (e.g. required version). +type kibanaConditions struct { + Version string `config:"version" json:"version" yaml:"version"` +} + +// elasticConditions defines conditions related to Elastic subscriptions or partnerships. +type elasticConditions struct { + Subscription string `config:"subscription" json:"subscription" yaml:"subscription"` +} + +// conditions define requirements for different parts of the Elastic stack. +type conditions struct { + Kibana kibanaConditions `config:"kibana" json:"kibana" yaml:"kibana"` + Elastic elasticConditions `config:"elastic" json:"elastic" yaml:"elastic"` +} + +type packageManifest struct { + Name string `config:"name" json:"name" yaml:"name"` + License string `config:"license" json:"license" yaml:"license"` + Conditions conditions `config:"conditions" json:"conditions" yaml:"conditions"` +} + +func readPackageManifest(path string) (*packageManifest, error) { + cfg, err := yaml.NewConfigWithFile(path, ucfg.PathSep(".")) + if err != nil { + return nil, fmt.Errorf("reading file failed (path: %s): %w", path, err) + } + + var manifest packageManifest + err = cfg.Unpack(&manifest) + if err != nil { + return nil, fmt.Errorf("unpacking package manifest failed (path: %s): %w", path, err) + } + return &manifest, nil +} diff --git a/dev/citools/subscription.go b/dev/citools/subscription.go new file mode 100644 index 00000000000..a14169df68d --- /dev/null +++ b/dev/citools/subscription.go @@ -0,0 +1,47 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package citools + +import ( + "fmt" +) + +func packageSubscription(path string) (string, error) { + manifest, err := readPackageManifest(path) + if err != nil { + return "", err + } + + packageSubscription := manifest.Conditions.Elastic.Subscription + if packageSubscription == "" { + packageSubscription = manifest.License + } + if packageSubscription == "" { + packageSubscription = "basic" + } + + return packageSubscription, nil +} + +func IsSubscriptionCompatible(stackSubscription, path string) (bool, error) { + pkgSubscription, err := packageSubscription(path) + if err != nil { + return false, fmt.Errorf("failed to read subscription from manifest: %w", err) + } + + if stackSubscription == "trial" { + // All subscriptions supported + return true, nil + } + + if stackSubscription == "basic" { + if pkgSubscription != "basic" { + return false, nil + } + return true, nil + } + + return false, fmt.Errorf("unknown subscription %s", stackSubscription) +} diff --git a/dev/citools/subscription_test.go b/dev/citools/subscription_test.go new file mode 100644 index 00000000000..08d86754513 --- /dev/null +++ b/dev/citools/subscription_test.go @@ -0,0 +1,180 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package citools + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPackageSubscription(t *testing.T) { + cases := []struct { + title string + contents string + expected string + }{ + { + title: "Subscription field", + contents: `name: "subscription" +conditions: + elastic: + subscription: foo +`, + expected: "foo", + }, + { + title: "Dotted Subscription field", + contents: `name: "subscription" +conditions: + elastic.subscription: dotted +`, + expected: "dotted", + }, + { + title: "Deprecated Subscription field", + contents: `name: "subscription" +license: deprecated +`, + expected: "deprecated", + }, + { + title: "No Subscription field", + contents: `name: "subscription" +`, + expected: "basic", + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + directory := t.TempDir() + pkgManifestPath := filepath.Join(directory, "manifest.yml") + err := os.WriteFile(pkgManifestPath, []byte(c.contents), 0o644) + require.NoError(t, err) + pkgSubscription, err := packageSubscription(pkgManifestPath) + require.NoError(t, err) + assert.Equal(t, c.expected, pkgSubscription) + }) + } +} + +func TestIsSubscriptionCompatible(t *testing.T) { + cases := []struct { + title string + contents string + stackSubscription string + expectedError bool + supported bool + }{ + { + title: "Trial with Basic Subscription field", + stackSubscription: "trial", + contents: `name: "subscription" +conditions: + elastic: + subscription: basic +`, + expectedError: false, + supported: true, + }, + { + title: "Trial with Enterprise Subscription", + stackSubscription: "trial", + contents: `name: "subscription" +conditions: + elastic: + subscription: enterprise +`, + expectedError: false, + supported: true, + }, + { + title: "Trial with Platinum Subscription", + stackSubscription: "trial", + contents: `name: "subscription" +conditions: + elastic: + subscription: platinum +`, + expectedError: false, + supported: true, + }, + { + title: "Trial with Platinum Subscription", + stackSubscription: "trial", + contents: `name: "subscription" +conditions: + elastic: + subscription: platinum +`, + expectedError: false, + supported: true, + }, + { + title: "Basic with Basic Subscription field", + stackSubscription: "basic", + contents: `name: "subscription" +conditions: + elastic: + subscription: basic +`, + expectedError: false, + supported: true, + }, + { + title: "Basic with Enterprise Subscription", + stackSubscription: "basic", + contents: `name: "subscription" +conditions: + elastic: + subscription: enterprise +`, + expectedError: false, + supported: false, + }, + { + title: "Basic with Platinum Subscription", + stackSubscription: "basic", + contents: `name: "subscription" +conditions: + elastic: + subscription: platinum +`, + expectedError: false, + supported: false, + }, + { + title: "Unknown Stack Subscription", + stackSubscription: "other", + contents: `name: "subscription" +conditions: + elastic: + subscription: platinum +`, + expectedError: true, + supported: false, + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + directory := t.TempDir() + pkgManifestPath := filepath.Join(directory, "manifest.yml") + err := os.WriteFile(pkgManifestPath, []byte(c.contents), 0o644) + require.NoError(t, err) + supported, err := IsSubscriptionCompatible(c.stackSubscription, pkgManifestPath) + if c.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, c.supported, supported) + } + }) + } +} diff --git a/dev/testsreporter/_static/summary.tmpl b/dev/testsreporter/_static/summary.tmpl index 9e5bbe073d4..260d1ad665f 100644 --- a/dev/testsreporter/_static/summary.tmpl +++ b/dev/testsreporter/_static/summary.tmpl @@ -1,5 +1,11 @@ {{ if ne .stackVersion "" -}} - Stack version: {{ .stackVersion }} +{{ else -}} +{{ if .logsDB -}} +- Stack version: maximum of either the version used in PR builds or 8.17.0 (GA version for LogsDB index mode) +{{ else -}} +- Stack version: Same as in Pull Request builds +{{ end -}} {{ end -}} {{ if .serverless -}} - Serverless: {{ .serverlessProject}} @@ -7,6 +13,9 @@ {{ if .logsDB -}} - LogsDB: enabled {{ end -}} +{{ if ne .subscription "" -}} +- Subscription: {{ .subscription }} +{{ end -}} {{ if and (ne .packageName "") (ne .packageName nil) -}} - Package: {{ .packageName }} - Failing test: {{ .testName }} @@ -20,7 +29,7 @@ - {{ . }} {{- end }} {{ end -}} -{{ if ne (len .owners) 0 -}} +{{ if and (ne .owners nil) (ne (len .owners) 0) -}} - Owners: {{- range .owners }} - {{ . }} diff --git a/dev/testsreporter/builderror.go b/dev/testsreporter/builderror.go index 44865cf742b..ca97b1fc5d2 100644 --- a/dev/testsreporter/builderror.go +++ b/dev/testsreporter/builderror.go @@ -5,7 +5,6 @@ package testsreporter import ( - "fmt" "strings" ) @@ -14,14 +13,6 @@ const ( buildReportingTeamLabel = "Team:Ecosystem" ) -type dataError struct { - errorLinks - serverless bool - serverlessProject string - logsDB bool - stackVersion string -} - type buildError struct { dataError teams []string @@ -33,6 +24,7 @@ type buildErrorOptions struct { ServerlessProject string LogsDB bool StackVersion string + Subscription string Packages []string BuildURL string PreviousBuilds []string @@ -49,6 +41,7 @@ func newBuildError(options buildErrorOptions) (*buildError, error) { serverlessProject: options.ServerlessProject, logsDB: options.LogsDB, stackVersion: options.StackVersion, + subscription: options.Subscription, errorLinks: errorLinks{ firstBuild: options.BuildURL, closedIssueURL: options.ClosedIssueURL, @@ -65,53 +58,39 @@ func newBuildError(options buildErrorOptions) (*buildError, error) { func (b *buildError) String() string { var sb strings.Builder - if b.logsDB { - sb.WriteString("[LogsDB] ") - } - if b.serverless { - sb.WriteString(fmt.Sprintf("[Serverless %s] ", b.serverlessProject)) - } - if b.stackVersion != "" { - sb.WriteString("[Stack ") - sb.WriteString(b.stackVersion) - sb.WriteString("] ") - } + sb.WriteString(b.dataError.String()) sb.WriteString("Too many packages failing in daily job") return sb.String() } -func (p *buildError) FirstBuild() string { - return p.errorLinks.firstBuild +func (b *buildError) FirstBuild() string { + return b.errorLinks.firstBuild } -func (p *buildError) UpdateLinks(links errorLinks) { - p.errorLinks = links +func (b *buildError) UpdateLinks(links errorLinks) { + b.errorLinks = links } -func (p *buildError) Teams() []string { - return p.teams +func (b *buildError) Teams() []string { + return b.teams } -func (p *buildError) SummaryData() map[string]any { - return map[string]any{ - "stackVersion": p.stackVersion, - "serverless": p.serverless, - "serverlessProject": p.serverlessProject, - "logsDB": p.logsDB, - "packages": p.packages, - "owners": p.teams, - } +func (b *buildError) SummaryData() map[string]any { + data := b.dataError.Data() + data["packages"] = b.packages + data["owners"] = b.teams + return data } -func (p *buildError) DescriptionData() map[string]any { - return map[string]any{ - "firstBuild": p.errorLinks.firstBuild, - "closedIssueURL": p.errorLinks.closedIssueURL, - "previousBuilds": p.errorLinks.previousBuilds, +func (b *buildError) DescriptionData() map[string]any { + data := b.SummaryData() + for key, value := range b.errorLinks.Data() { + data[key] = value } + return data } -func (p *buildError) Labels() []string { +func (b *buildError) Labels() []string { return []string{buildReportingTeamLabel} } diff --git a/dev/testsreporter/dataerror.go b/dev/testsreporter/dataerror.go new file mode 100644 index 00000000000..b9b82c5312b --- /dev/null +++ b/dev/testsreporter/dataerror.go @@ -0,0 +1,51 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package testsreporter + +import ( + "fmt" + "strings" +) + +type dataError struct { + errorLinks + serverless bool + serverlessProject string + logsDB bool + stackVersion string + subscription string +} + +func (d *dataError) String() string { + var sb strings.Builder + + if d.logsDB { + sb.WriteString("[LogsDB] ") + } + if d.serverless { + sb.WriteString(fmt.Sprintf("[Serverless %s] ", d.serverlessProject)) + } + if d.stackVersion != "" { + sb.WriteString("[Stack ") + sb.WriteString(d.stackVersion) + sb.WriteString("] ") + } + if d.subscription != "" { + sb.WriteString("[Subscription ") + sb.WriteString(d.subscription) + sb.WriteString("] ") + } + return sb.String() +} + +func (d *dataError) Data() map[string]any { + return map[string]any{ + "stackVersion": d.stackVersion, + "serverless": d.serverless, + "serverlessProject": d.serverlessProject, + "logsDB": d.logsDB, + "subscription": d.subscription, + } +} diff --git a/dev/testsreporter/errorlinks.go b/dev/testsreporter/errorlinks.go new file mode 100644 index 00000000000..f4f426ec8ad --- /dev/null +++ b/dev/testsreporter/errorlinks.go @@ -0,0 +1,20 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package testsreporter + +type errorLinks struct { + currentIssueURL string + firstBuild string + previousBuilds []string + closedIssueURL string +} + +func (e *errorLinks) Data() map[string]any { + return map[string]any{ + "firstBuild": e.firstBuild, + "closedIssueURL": e.closedIssueURL, + "previousBuilds": e.previousBuilds, + } +} diff --git a/dev/testsreporter/format_test.go b/dev/testsreporter/format_test.go index b4301d06ce6..ea969ef0f40 100644 --- a/dev/testsreporter/format_test.go +++ b/dev/testsreporter/format_test.go @@ -36,7 +36,7 @@ func TestSummary(t *testing.T) { `, }, { - title: "summary stack version with owners wihtout data stream", + title: "summary stack version with owners without data stream", resultError: &packageError{ dataError: dataError{ stackVersion: "8.14", @@ -91,7 +91,8 @@ func TestSummary(t *testing.T) { }, teams: []string{"team1", "team2"}, }, - expected: `- Serverless: observability + expected: `- Stack version: Same as in Pull Request builds +- Serverless: observability - Package: foo - Failing test: mytest - DataStream: data @@ -113,7 +114,8 @@ func TestSummary(t *testing.T) { }, teams: []string{"team1", "team2"}, }, - expected: `- Serverless: observability + expected: `- Stack version: Same as in Pull Request builds +- Serverless: observability - Package: foo - Failing test: mytest - Owners: @@ -122,7 +124,7 @@ func TestSummary(t *testing.T) { `, }, { - title: "summary logsdb", + title: "summary logsdb without stack version defined", resultError: &packageError{ dataError: dataError{ logsDB: true, @@ -133,7 +135,8 @@ func TestSummary(t *testing.T) { }, teams: []string{"team1", "team2"}, }, - expected: `- LogsDB: enabled + expected: `- Stack version: maximum of either the version used in PR builds or 8.17.0 (GA version for LogsDB index mode) +- LogsDB: enabled - Package: foo - Failing test: mytest - Owners: @@ -156,6 +159,53 @@ func TestSummary(t *testing.T) { teams: []string{"team1"}, }, expected: `- Stack version: 8.16 +- Packages: + - foo + - bar +- Owners: + - team1 +`, + }, + { + title: "summary with basic license", + resultError: &buildError{ + dataError: dataError{ + logsDB: false, + serverless: false, + subscription: "basic", + stackVersion: "8.16", + }, + packages: []string{ + "foo", + "bar", + }, + teams: []string{"team1"}, + }, + expected: `- Stack version: 8.16 +- Subscription: basic +- Packages: + - foo + - bar +- Owners: + - team1 +`, + }, + { + title: "summary with basic license no stack", + resultError: &buildError{ + dataError: dataError{ + logsDB: false, + serverless: false, + subscription: "basic", + }, + packages: []string{ + "foo", + "bar", + }, + teams: []string{"team1"}, + }, + expected: `- Stack version: Same as in Pull Request builds +- Subscription: basic - Packages: - foo - bar @@ -181,14 +231,12 @@ func TestSummary(t *testing.T) { func TestDescription(t *testing.T) { cases := []struct { title string - summary string resultError failureObserver maxLinks int expected string }{ { - title: "description error all fields", - summary: "summary", + title: "description error all fields", resultError: &packageError{ dataError: dataError{ stackVersion: "8.14", @@ -226,8 +274,7 @@ Latest failed builds: `, }, { - title: "description failure all fields", - summary: "summary", + title: "description failure all fields", resultError: &packageError{ dataError: dataError{ stackVersion: "8.14", @@ -265,8 +312,7 @@ Latest failed builds: `, }, { - title: "description no closed issue", - summary: "summary", + title: "description no closed issue", resultError: &packageError{ dataError: dataError{ stackVersion: "8.14", @@ -302,7 +348,6 @@ Latest failed builds: }, { title: "description max links", - summary: "summary", maxLinks: 2, resultError: &packageError{ dataError: dataError{ @@ -368,6 +413,47 @@ Latest 2 failed builds: +Latest issue closed for the same test: http://issue.link/1 + +First build failed: http://link/1 + +Latest failed builds: +- http://link/2 +- http://link/3 +`, + }, + { + title: "description basic license no stack", + resultError: &buildError{ + dataError: dataError{ + logsDB: false, + serverless: false, + subscription: "basic", + errorLinks: errorLinks{ + firstBuild: "http://link/1", + previousBuilds: []string{ + "http://link/2", + "http://link/3", + }, + closedIssueURL: "http://issue.link/1", + }, + }, + packages: []string{ + "foo", + "bar", + }, + teams: []string{"team1"}, + }, + expected: `- Stack version: Same as in Pull Request builds +- Subscription: basic +- Packages: + - foo + - bar +- Owners: + - team1 + + + Latest issue closed for the same test: http://issue.link/1 First build failed: http://link/1 diff --git a/dev/testsreporter/packageerror.go b/dev/testsreporter/packageerror.go index aa775ed381a..dc6b873eeb5 100644 --- a/dev/testsreporter/packageerror.go +++ b/dev/testsreporter/packageerror.go @@ -11,13 +11,6 @@ import ( "github.com/elastic/integrations/dev/codeowners" ) -type errorLinks struct { - currentIssueURL string - firstBuild string - previousBuilds []string - closedIssueURL string -} - type packageError struct { testCase dataError @@ -31,6 +24,7 @@ type packageErrorOptions struct { ServerlessProject string LogsDB bool StackVersion string + Subscription string BuildURL string TestCase testCase CodeownersPath string @@ -49,6 +43,7 @@ func newPackageError(options packageErrorOptions) (*packageError, error) { serverlessProject: options.ServerlessProject, logsDB: options.LogsDB, stackVersion: options.StackVersion, + subscription: options.Subscription, errorLinks: errorLinks{ firstBuild: options.BuildURL, closedIssueURL: options.ClosedIssueURL, @@ -88,17 +83,7 @@ func (p *packageError) Teams() []string { func (p *packageError) String() string { var sb strings.Builder - if p.logsDB { - sb.WriteString("[LogsDB] ") - } - if p.serverless { - sb.WriteString(fmt.Sprintf("[Serverless %s] ", p.serverlessProject)) - } - if p.stackVersion != "" { - sb.WriteString("[Stack ") - sb.WriteString(p.stackVersion) - sb.WriteString("] ") - } + sb.WriteString(p.dataError.String()) sb.WriteString("[") sb.WriteString(p.packageName) sb.WriteString("] ") @@ -109,26 +94,22 @@ func (p *packageError) String() string { } func (p *packageError) SummaryData() map[string]any { - return map[string]any{ - "stackVersion": p.stackVersion, - "serverless": p.serverless, - "serverlessProject": p.serverlessProject, - "logsDB": p.logsDB, - "packageName": p.packageName, - "testName": p.Name, - "dataStream": p.dataStream, - "owners": p.teams, - } + data := p.dataError.Data() + data["packageName"] = p.packageName + data["testName"] = p.Name + data["dataStream"] = p.dataStream + data["owners"] = p.teams + return data } func (p *packageError) DescriptionData() map[string]any { - return map[string]any{ - "failure": truncateText(p.Failure, defaultMaxLengthMessages), - "error": truncateText(p.Error, defaultMaxLengthMessages), - "firstBuild": p.errorLinks.firstBuild, - "closedIssueURL": p.errorLinks.closedIssueURL, - "previousBuilds": p.errorLinks.previousBuilds, + data := p.SummaryData() + for key, value := range p.errorLinks.Data() { + data[key] = value } + data["failure"] = truncateText(p.Failure, defaultMaxLengthMessages) + data["error"] = truncateText(p.Error, defaultMaxLengthMessages) + return data } func (p *packageError) Labels() []string { diff --git a/dev/testsreporter/packageerror_test.go b/dev/testsreporter/packageerror_test.go index 55c0abdd3a9..8fc1b304803 100644 --- a/dev/testsreporter/packageerror_test.go +++ b/dev/testsreporter/packageerror_test.go @@ -25,6 +25,7 @@ func TestNewPackageError(t *testing.T) { ServerlessProject: "observability", LogsDB: false, StackVersion: "8.16.0-SNAPSHOT", + Subscription: "basic", BuildURL: "https://buildkite.com/elastic/integrations/build/1", TestCase: testCase{ Name: "failing test", @@ -40,6 +41,7 @@ func TestNewPackageError(t *testing.T) { serverlessProject: "observability", logsDB: false, stackVersion: "8.16.0-SNAPSHOT", + subscription: "basic", errorLinks: errorLinks{ firstBuild: "https://buildkite.com/elastic/integrations/build/1", }, diff --git a/dev/testsreporter/reporter.go b/dev/testsreporter/reporter.go index dcec3614923..83e4480b663 100644 --- a/dev/testsreporter/reporter.go +++ b/dev/testsreporter/reporter.go @@ -14,12 +14,20 @@ import ( type reporter struct { ghCli *ghCli maxPreviousLinks int + verbose bool } -func newReporter(ghCli *ghCli, maxPreviousLinks int) reporter { +type reporterOptions struct { + GhCli *ghCli + MaxPreviousLinks int + Verbose bool +} + +func newReporter(options reporterOptions) reporter { return reporter{ - ghCli: ghCli, - maxPreviousLinks: maxPreviousLinks, + ghCli: options.GhCli, + maxPreviousLinks: options.MaxPreviousLinks, + verbose: options.Verbose, } } @@ -57,6 +65,12 @@ func (r reporter) Report(ctx context.Context, issue *githubIssue, resultError fa fmt.Printf("Summary:\n%s", summary) fmt.Println("----") fmt.Println() + if r.verbose { + fmt.Println("---- Full Description ----") + fmt.Print(description) + fmt.Println("----") + fmt.Println() + } return r.createOrUpdateIssue(ctx, nextIssue) } diff --git a/dev/testsreporter/reporter_test.go b/dev/testsreporter/reporter_test.go index 497f6ea3095..e3993ccd4ed 100644 --- a/dev/testsreporter/reporter_test.go +++ b/dev/testsreporter/reporter_test.go @@ -201,7 +201,10 @@ func TestReporterUpdateLinks(t *testing.T) { Runner: &runner, }) - reporter := newReporter(ghCli, 5) + reporter := newReporter(reporterOptions{ + GhCli: ghCli, + MaxPreviousLinks: 5, + }) links, newIssue, err := reporter.updateLinks(context.Background(), c.issue, c.firstBuild) require.NoError(t, err) diff --git a/dev/testsreporter/testsreporter.go b/dev/testsreporter/testsreporter.go index fd33819982d..aec29d7b52b 100644 --- a/dev/testsreporter/testsreporter.go +++ b/dev/testsreporter/testsreporter.go @@ -20,13 +20,15 @@ type CheckOptions struct { ServerlessProject string LogsDB bool StackVersion string + Subscription string BuildURL string CodeownersPath string MaxPreviousLinks int MaxTestsReported int - DryRun bool + DryRun bool + Verbose bool } func Check(ctx context.Context, resultsPath string, options CheckOptions) error { @@ -49,11 +51,15 @@ func Check(ctx context.Context, resultsPath string, options CheckOptions) error DryRun: options.DryRun, }) - aReporter := newReporter(ghCli, options.MaxPreviousLinks) + aReporter := newReporter(reporterOptions{ + GhCli: ghCli, + MaxPreviousLinks: options.MaxPreviousLinks, + Verbose: options.Verbose, + }) if len(packageErrors) > options.MaxTestsReported { fmt.Printf("Skip creating GitHub issues, hit the maximum number (%d) of tests to be reported. Total failing tests: %d.\n", options.MaxTestsReported, len(packageErrors)) - packages, err := packagesFromTests(resultsPath, options) + packages, err := packagesFromTests(resultsPath) if err != nil { return fmt.Errorf("failed to get packages from results files: %w", err) } @@ -62,6 +68,7 @@ func Check(ctx context.Context, resultsPath string, options CheckOptions) error ServerlessProject: options.ServerlessProject, LogsDB: options.LogsDB, StackVersion: options.StackVersion, + Subscription: options.Subscription, BuildURL: options.BuildURL, Packages: packages, }) @@ -117,6 +124,7 @@ func errorsFromTests(resultsPath string, options CheckOptions) ([]*packageError, ServerlessProject: options.ServerlessProject, LogsDB: options.LogsDB, StackVersion: options.StackVersion, + Subscription: options.Subscription, BuildURL: options.BuildURL, TestCase: c, CodeownersPath: options.CodeownersPath, @@ -137,7 +145,7 @@ func errorsFromTests(resultsPath string, options CheckOptions) ([]*packageError, } // packagesFromTests returns the sorted packages failing given the results file -func packagesFromTests(resultsPath string, options CheckOptions) ([]string, error) { +func packagesFromTests(resultsPath string) ([]string, error) { packages := []string{} err := filepath.Walk(resultsPath, func(path string, info os.FileInfo, err error) error { if err != nil { diff --git a/go.mod b/go.mod index 2558a1e9b48..f713d1538e8 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,12 @@ go 1.23.0 toolchain go1.23.4 require ( + github.com/Masterminds/semver/v3 v3.3.1 github.com/blang/semver v3.5.1+incompatible github.com/cli/go-gh/v2 v2.11.2 github.com/elastic/elastic-package v0.110.2 github.com/elastic/go-licenser v0.4.2 + github.com/elastic/go-ucfg v0.8.8 github.com/elastic/package-registry v1.27.0 github.com/magefile/mage v1.15.0 github.com/pkg/errors v0.9.1 @@ -35,7 +37,6 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/PaesslerAG/gval v1.2.2 // indirect github.com/PaesslerAG/jsonpath v0.1.1 // indirect @@ -65,7 +66,6 @@ require ( github.com/elastic/go-elasticsearch/v7 v7.17.10 // indirect github.com/elastic/go-resource v0.2.0 // indirect github.com/elastic/go-sysinfo v1.9.0 // indirect - github.com/elastic/go-ucfg v0.8.8 // indirect github.com/elastic/go-windows v1.0.1 // indirect github.com/elastic/gojsonschema v1.2.1 // indirect github.com/elastic/kbncontent v0.1.4 // indirect diff --git a/magefile.go b/magefile.go index 5ff8fa9301a..f8e57f44166 100644 --- a/magefile.go +++ b/magefile.go @@ -14,10 +14,12 @@ import ( "path/filepath" "strconv" + "github.com/Masterminds/semver/v3" "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "github.com/pkg/errors" + "github.com/elastic/integrations/dev/citools" "github.com/elastic/integrations/dev/codeowners" "github.com/elastic/integrations/dev/coverage" "github.com/elastic/integrations/dev/testsreporter" @@ -155,6 +157,7 @@ func ReportFailedTests(ctx context.Context, testResultsFolder string) error { dryRunEnv := os.Getenv("DRY_RUN") serverlessProjectEnv := os.Getenv("SERVERLESS_PROJECT") buildURL := os.Getenv("BUILDKITE_BUILD_URL") + subscription := os.Getenv("ELASTIC_SUBSCRIPTION") serverless := false if serverlessEnv != "" { @@ -173,6 +176,11 @@ func ReportFailedTests(ctx context.Context, testResultsFolder string) error { logsDBEnabled = true } + verboseMode := false + if v, found := os.LookupEnv("VERBOSE_MODE_ENABLED"); found && v == "true" { + verboseMode = true + } + maxIssuesString := os.Getenv("CI_MAX_TESTS_REPORTED") maxIssues := defaultMaximumTestsReported if maxIssuesString != "" { @@ -197,10 +205,95 @@ func ReportFailedTests(ctx context.Context, testResultsFolder string) error { ServerlessProject: serverlessProjectEnv, LogsDB: logsDBEnabled, StackVersion: stackVersion, + Subscription: subscription, BuildURL: buildURL, MaxPreviousLinks: defaultPreviousLinksNumber, MaxTestsReported: maxIssues, DryRun: dryRun, + Verbose: verboseMode, } return testsreporter.Check(ctx, testResultsFolder, options) } + +// IsSubscriptionCompatible checks whether or not the package in the current directory allows to run with the given subscription (ELASTIC_SUBSCRIPTION env var). +func IsSubscriptionCompatible() error { + subscription := os.Getenv("ELASTIC_SUBSCRIPTION") + if subscription == "" { + fmt.Println("true") + return nil + } + + supported, err := citools.IsSubscriptionCompatible(subscription, "manifest.yml") + if err != nil { + return err + } + if supported { + fmt.Println("true") + return nil + } + fmt.Println("false") + return nil +} + +// KibanaConstraintPackage returns the Kibana version constraint defined in the package manifest +func KibanaConstraintPackage() error { + constraint, err := citools.KibanaConstraintPackage("manifest.yml") + if err != nil { + return fmt.Errorf("faile") + } + if constraint == nil { + fmt.Println("null") + return nil + } + fmt.Println(constraint) + return nil +} + +// IsSupportedStack checks whether or not the package in the current directory is allowed to be installed in the given stack version +func IsSupportedStack(stackVersion string) error { + if stackVersion == "" { + fmt.Println("true") + return nil + } + + supported, err := citools.IsPackageSupportedInStackVersion(stackVersion, "manifest.yml") + if err != nil { + return err + } + + if supported { + fmt.Println("true") + return nil + } + fmt.Println("false") + return nil +} + +// IsLogsDBSupportedInPackage checks whether or not the package in the current directory supports LogsDB +func IsLogsDBSupportedInPackage() error { + supported, err := citools.IsLogsDBSupportedInPackage("manifest.yml") + if err != nil { + return err + } + if !supported { + fmt.Println("false") + return nil + } + fmt.Println("true") + return nil +} + +// IsVersionLessThanLogsDBGA checks whether or not the given version supports LogsDB. Minimum version that supports LogsDB as GA 8.17.0. +func IsVersionLessThanLogsDBGA(version string) error { + stackVersion, err := semver.NewVersion(version) + if err != nil { + return fmt.Errorf("failed to parse version %q: %w", version, err) + } + lessThan := citools.IsVersionLessThanLogsDBGA(stackVersion) + if lessThan { + fmt.Println("true") + return nil + } + fmt.Println("false") + return nil +}