diff --git a/CHANGELOG.md b/CHANGELOG.md index 5887d7115..0dd2dd42e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Add new query parameter "capabilities" in search endpoint [#1054](https://github.com/elastic/package-registry/pull/1054) + ### Deprecated ### Known Issues diff --git a/packages/package.go b/packages/package.go index ca224e6a2..c54ba6605 100644 --- a/packages/package.go +++ b/packages/package.go @@ -120,7 +120,8 @@ type KibanaConditions struct { // ElasticConditions defines conditions related to Elastic subscriptions or partnerships. type ElasticConditions struct { - Subscription string `config:"subscription" json:"subscription" yaml:"subscription"` + Subscription string `config:"subscription" json:"subscription" yaml:"subscription"` + Capabilities []string `config:"capabilities,omitempty" json:"capabilities,omitempty" yaml:"capabilities,omitempty"` } type Version struct { @@ -387,6 +388,19 @@ func (p *Package) HasKibanaVersion(version *semver.Version) bool { return p.Conditions.Kibana.constraint.Check(version) } +func (p *Package) WorksWithCapabilities(capabilities []string) bool { + if p.Conditions == nil || p.Conditions.Elastic == nil || p.Conditions.Elastic.Capabilities == nil || capabilities == nil { + return true + } + + for _, requiredCapability := range p.Conditions.Elastic.Capabilities { + if !util.StringsContains(capabilities, requiredCapability) { + return false + } + } + return true +} + func (p *Package) IsNewerOrEqual(pp *Package) bool { return !p.versionSemVer.LessThan(pp.versionSemVer) } diff --git a/packages/packages.go b/packages/packages.go index 6043b0e43..bcfb0ad3c 100644 --- a/packages/packages.go +++ b/packages/packages.go @@ -293,6 +293,7 @@ type Filter struct { PackageName string PackageVersion string PackageType string + Capabilities []string // Deprecated, release tags to be removed. Experimental bool @@ -342,6 +343,12 @@ func (f *Filter) Apply(ctx context.Context, packages Packages) Packages { continue } + if f.Capabilities != nil { + if valid := p.WorksWithCapabilities(f.Capabilities); !valid { + continue + } + } + addPackage := true if !f.AllVersions { // Check if the version exists and if it should be added or not. @@ -405,6 +412,12 @@ func (f *Filter) legacyApply(ctx context.Context, packages Packages) Packages { continue } + if f.Capabilities != nil { + if valid := p.WorksWithCapabilities(f.Capabilities); !valid { + continue + } + } + addPackage := true if !f.AllVersions { // Check if the version exists and if it should be added or not. diff --git a/packages/packages_test.go b/packages/packages_test.go index ef4a1c1db..babb1c0d4 100644 --- a/packages/packages_test.go +++ b/packages/packages_test.go @@ -84,6 +84,36 @@ func TestPackagesFilter(t *testing.T) { Type: "integration", KibanaVersion: "^8.0.0", }, + { + Name: "obs_package", + Version: "1.1.0", + Type: "integration", + Capabilities: []string{"observability"}, + }, + { + Name: "obs_sec_package", + Version: "1.0.0", + Type: "integration", + Capabilities: []string{"observability", "security"}, + }, + { + Name: "obs_sec_package", + Version: "2.0.0-rc1", + Type: "integration", + Capabilities: []string{"observability", "security"}, + }, + { + Name: "obs_sec_package", + Version: "2.0.0", + Type: "integration", + Capabilities: []string{"observability", "security"}, + }, + { + Name: "obs_sec_uptime_package", + Version: "2.0.0", + Type: "integration", + Capabilities: []string{"observability", "security", "uptime"}, + }, } packages := buildFilterTestPackages(filterTestPackages) @@ -164,6 +194,9 @@ func TestPackagesFilter(t *testing.T) { {Name: "apache", Version: "1.0.0"}, {Name: "nginx", Version: "2.0.0"}, {Name: "redisenterprise", Version: "1.0.0"}, + {Name: "obs_package", Version: "1.1.0"}, + {Name: "obs_sec_package", Version: "2.0.0"}, + {Name: "obs_sec_uptime_package", Version: "2.0.0"}, }, }, { @@ -231,6 +264,7 @@ func TestPackagesFilter(t *testing.T) { filterTestPackage{Name: "apache", Version: "1.0.0-rc1"}, filterTestPackage{Name: "apache", Version: "2.0.0-rc2"}, filterTestPackage{Name: "redisenterprise", Version: "0.1.1"}, + filterTestPackage{Name: "obs_sec_package", Version: "2.0.0-rc1"}, ), }, { @@ -350,6 +384,60 @@ func TestPackagesFilter(t *testing.T) { {Name: "etcd", Version: "1.0.0-rc2"}, }, }, + { + Title: "non existing capabilities search", + Filter: Filter{ + Capabilities: []string{"no_match"}, + }, + Expected: []filterTestPackage{ + {Name: "apache", Version: "1.0.0"}, + {Name: "nginx", Version: "2.0.0"}, + {Name: "redisenterprise", Version: "1.0.0"}, + }, + }, + { + Title: "observability capabilities search", + Filter: Filter{ + Capabilities: []string{"observability"}, + }, + Expected: []filterTestPackage{ + {Name: "apache", Version: "1.0.0"}, + {Name: "nginx", Version: "2.0.0"}, + {Name: "redisenterprise", Version: "1.0.0"}, + {Name: "obs_package", Version: "1.1.0"}, + }, + }, + { + Title: "observability and security capabilities search", + Filter: Filter{ + Capabilities: []string{"observability", "security"}, + }, + Expected: []filterTestPackage{ + {Name: "apache", Version: "1.0.0"}, + {Name: "nginx", Version: "2.0.0"}, + {Name: "redisenterprise", Version: "1.0.0"}, + {Name: "obs_package", Version: "1.1.0"}, + {Name: "obs_sec_package", Version: "2.0.0"}, + }, + }, + { + Title: "observability, security and uptime capabilities search - legacy kibana", + Filter: Filter{ + Experimental: true, + Capabilities: []string{"observability", "security", "uptime"}, + }, + Expected: []filterTestPackage{ + {Name: "apache", Version: "1.0.0"}, + {Name: "nginx", Version: "2.0.0"}, + {Name: "mysql", Version: "0.9.0"}, + {Name: "logstash", Version: "1.1.0"}, + {Name: "etcd", Version: "1.0.0-rc2"}, + {Name: "redisenterprise", Version: "1.0.0"}, + {Name: "obs_package", Version: "1.1.0"}, + {Name: "obs_sec_package", Version: "2.0.0"}, + {Name: "obs_sec_uptime_package", Version: "2.0.0"}, + }, + }, } for _, c := range cases { @@ -366,6 +454,7 @@ type filterTestPackage struct { Release string Type string KibanaVersion string + Capabilities []string } func (p filterTestPackage) Build() *Package { @@ -377,15 +466,29 @@ func (p filterTestPackage) Build() *Package { build.Release = p.Release build.Type = p.Type - constraints, err := semver.NewConstraint(p.KibanaVersion) - if err != nil { - panic(err) + if p.KibanaVersion != "" { + constraints, err := semver.NewConstraint(p.KibanaVersion) + if err != nil { + panic(err) + } + build.Conditions = &Conditions{ + Kibana: &KibanaConditions{ + Version: p.KibanaVersion, + constraint: constraints, + }, + } } - build.Conditions = &Conditions{ - Kibana: &KibanaConditions{ - Version: p.KibanaVersion, - constraint: constraints, - }, + if p.Capabilities != nil { + elasticConditions := ElasticConditions{ + Capabilities: p.Capabilities, + } + if build.Conditions != nil { + build.Conditions.Elastic = &elasticConditions + } else { + build.Conditions = &Conditions{ + Elastic: &elasticConditions, + } + } } return &build } diff --git a/search.go b/search.go index 6d99ac5cc..8af88645c 100644 --- a/search.go +++ b/search.go @@ -11,6 +11,7 @@ import ( "net/url" "sort" "strconv" + "strings" "time" "github.com/Masterminds/semver/v3" @@ -99,6 +100,10 @@ func newSearchFilterFromQuery(query url.Values) (*packages.Filter, error) { filter.PackageType = v } + if v := query.Get("capabilities"); v != "" { + filter.Capabilities = strings.Split(v, ",") + } + if v := query.Get("all"); v != "" { // Default is false, also on error filter.AllVersions, err = strconv.ParseBool(v)