Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce EnterpriseSearch CRD #2688

Merged
merged 26 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
207376e
Add Enterprise Search CRD
sebgl Feb 17, 2020
2b984a5
Add the Enterprise Search controller - minimal deployment ok
sebgl Feb 17, 2020
d4dae4f
Add a working minimal Enterprise Search sample
sebgl Feb 17, 2020
d8fba95
Merge pull request #1 from sebgl/ent-search-bootstrap
sebgl Feb 17, 2020
e286001
Reconcile a configuration secret instead of env vars (#2)
sebgl Feb 18, 2020
3053c7e
Setup the EnterpriseSearch - Elasticsearch association controller
sebgl Feb 18, 2020
77544ad
Patch the EnterpriseSearch CRD to fix the subresources
sebgl Feb 18, 2020
22adc70
Merge pull request #3 from sebgl/es-association
sebgl Feb 18, 2020
612a409
Label Pods with a hash of the config for auto rotation (#4)
sebgl Feb 18, 2020
c8bd16a
Reuse the JAVA_OPTS const (#5)
sebgl Feb 18, 2020
857325e
Setup a readiness probe for smooth deployment rotation (#6)
sebgl Feb 19, 2020
2619476
Generate the default enterprise_search user in a secret (#7)
sebgl Feb 19, 2020
09c313c
Strip out private Docker image from default sample (#8)
sebgl Mar 2, 2020
4921eb2
Merge branch 'master' into enterprise-search
sebgl Mar 3, 2020
1f30227
Fix EnterpriseSearch CRD generation (#9)
sebgl Mar 3, 2020
d8160ec
Use self-signed TLS certificates by default (#10)
sebgl Mar 10, 2020
4155954
Switch http/https protocol in readiness probe and config (#11)
sebgl Mar 10, 2020
b5031cd
Inject a hash of TLS certs for pod rotation (#12)
sebgl Mar 10, 2020
775ddbf
Cleanup the code files (#13)
sebgl Mar 10, 2020
08ad2d0
Merge branch 'master' into enterprise-search
sebgl Mar 10, 2020
bfcd4cf
Struct comments improvements
sebgl Mar 11, 2020
d46f6a4
Regenerate CRDs and api docs
sebgl Mar 11, 2020
35fbf49
Fixes from PR review
sebgl Mar 12, 2020
1aa7e3c
Merge branch 'master' into enterprise-search
sebgl Mar 12, 2020
8103630
Rely on the global scheme.Scheme
sebgl Mar 12, 2020
f7087a6
Setup RBAC for Enterprise Search
sebgl Mar 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (

"github.com/elastic/cloud-on-k8s/pkg/controller/common/container"
"github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing"
"github.com/elastic/cloud-on-k8s/pkg/controller/enterprisesearch"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.elastic.co/apm"
Expand Down Expand Up @@ -42,6 +44,7 @@ import (
"github.com/elastic/cloud-on-k8s/pkg/controller/common/operator"
controllerscheme "github.com/elastic/cloud-on-k8s/pkg/controller/common/scheme"
"github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch"
entsassn "github.com/elastic/cloud-on-k8s/pkg/controller/entsearchassociation"
"github.com/elastic/cloud-on-k8s/pkg/controller/kibana"
kbassn "github.com/elastic/cloud-on-k8s/pkg/controller/kibanaassociation"
"github.com/elastic/cloud-on-k8s/pkg/controller/license"
Expand Down Expand Up @@ -329,6 +332,10 @@ func execute() {
log.Error(err, "unable to create controller", "controller", "Kibana")
os.Exit(1)
}
if err = enterprisesearch.Add(mgr, params); err != nil {
log.Error(err, "unable to create controller", "controller", "EnterpriseSearch")
os.Exit(1)
}
if err = asesassn.Add(mgr, accessReviewer, params); err != nil {
log.Error(err, "unable to create controller", "controller", "ApmServerElasticsearchAssociation")
os.Exit(1)
Expand All @@ -337,6 +344,10 @@ func execute() {
log.Error(err, "unable to create controller", "controller", "KibanaAssociation")
os.Exit(1)
}
if err = entsassn.Add(mgr, accessReviewer, params); err != nil {
log.Error(err, "unable to create controller", "controller", "EnterpriseSearchAssociation")
os.Exit(1)
}
if err = remoteca.Add(mgr, accessReviewer, params); err != nil {
log.Error(err, "unable to create controller", "controller", "RemoteClusterCertificateAuthorites")
os.Exit(1)
Expand Down
392 changes: 392 additions & 0 deletions config/crds/all-crds.yaml

Large diffs are not rendered by default.

6,173 changes: 6,173 additions & 0 deletions config/crds/bases/enterprisesearch.k8s.elastic.co_enterprisesearches.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions config/crds/bases/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ resources:
- apm.k8s.elastic.co_apmservers.yaml
- elasticsearch.k8s.elastic.co_elasticsearches.yaml
- kibana.k8s.elastic.co_kibanas.yaml
- enterprisesearch.k8s.elastic.co_enterprisesearches.yaml
28 changes: 28 additions & 0 deletions config/crds/patches/entsearch-patches.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Remove validation.openAPIV3Schema.type that causes failures on k8s 1.11.
# This should have been fixed with https://github.com/kubernetes-sigs/controller-tools/pull/72, but it looks like
# this commit has been lost in history. See https://github.com/kubernetes-sigs/controller-tools/issues/296.
# TODO: remove once fixed in controller-tools
- op: remove
path: /spec/validation/openAPIV3Schema/type

# Using `kubectl apply` stores the complete CRD file as an annotation,
# which may be too big for the annotations size limit.
# One way to mitigate this problem is to remove the (huge) podTemplate properties from the CRD.
# It also avoids the problem of having any k8s-version specific field in the Pod schema,
# that would maybe not match the user's k8s version.
- op: remove
path: /spec/validation/openAPIV3Schema/properties/spec/properties/podTemplate/properties

# TODO: remove once https://github.com/kubernetes-sigs/controller-tools/issues/392 is fixed
# without this the API server complains with "The CustomResourceDefinition "elasticsearches.elasticsearch.k8s.elastic.co"
# is invalid: spec.validation.openAPIV3Schema.type: Required value: must not be empty at the root"
- op: remove
path: /spec/validation/openAPIV3Schema/properties/spec/properties/http/properties/service/properties/spec/properties/ports/items/properties/targetPort/x-kubernetes-int-or-string

# TODO: remove once https://github.com/kubernetes-sigs/controller-tools/issues/392 is fixed
# these are not technically required by the API server, but kubectl validation will fail because
# of these markers so we remove them to make applying the CRDs easier.
- op: remove
path: /spec/validation/openAPIV3Schema/properties/spec/properties/http/properties/service/properties/spec/properties/ports/x-kubernetes-list-map-keys
- op: remove
path: /spec/validation/openAPIV3Schema/properties/spec/properties/http/properties/service/properties/spec/properties/ports/x-kubernetes-list-type
7 changes: 7 additions & 0 deletions config/crds/patches/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,10 @@ patchesJson6902:
kind: CustomResourceDefinition
name: kibanas.kibana.k8s.elastic.co
path: apm-kibana-patches.yaml
# custom patches for EnterpriseSearch
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proper name here is Enterprise Search, two words.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really refers to the EnterpriseSearch CRD (the technical implementation), I'd be in favor of keeping it as is. Also this is def not a user-facing file.

- target:
group: apiextensions.k8s.io
version: v1beta1
kind: CustomResourceDefinition
name: enterprisesearches.enterprisesearch.k8s.elastic.co
path: entsearch-patches.yaml
46 changes: 46 additions & 0 deletions config/samples/enterprisesearch/entsearch_es.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# This sample sets up an Elasticsearch cluster and a Enterprise Search instance preconfigured for that cluster
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: elasticsearch-sample
spec:
version: 7.6.0
nodeSets:
- name: default
count: 1
config:
# This setting could have performance implications for production clusters.
# See: https://www.elastic.co/guide/en/cloud-on-k8s/master/k8s-virtual-memory.html
node.store.allow_mmap: false
---
apiVersion: enterprisesearch.k8s.elastic.co/v1beta1
kind: EnterpriseSearch
metadata:
name: entsearch-sample
spec:
version: 7.6.0
image: $DOCKER_IMAGE
count: 1
elasticsearchRef:
name: elasticsearch-sample
config:
ent_search.external_url: https://localhost:3002
http:
# service:
# spec:
# type: LoadBalancer
tls:
selfSignedCertificate:
subjectAltNames:
- dns: localhost
# http:
# service:
# spec:
# type: LoadBalancer
# podTemplate:
# spec:
# containers:
# - name: enterprise-search
# resources:
# requests:
# cpu: 4
81 changes: 81 additions & 0 deletions docs/reference/api-docs.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ endif::[]
- xref:{anchor_prefix}-common-k8s-elastic-co-v1beta1[$$common.k8s.elastic.co/v1beta1$$]
- xref:{anchor_prefix}-elasticsearch-k8s-elastic-co-v1[$$elasticsearch.k8s.elastic.co/v1$$]
- xref:{anchor_prefix}-elasticsearch-k8s-elastic-co-v1beta1[$$elasticsearch.k8s.elastic.co/v1beta1$$]
- xref:{anchor_prefix}-enterprisesearch-k8s-elastic-co-v1beta1[$$enterprisesearch.k8s.elastic.co/v1beta1$$]
- xref:{anchor_prefix}-kibana-k8s-elastic-co-v1[$$kibana.k8s.elastic.co/v1$$]
- xref:{anchor_prefix}-kibana-k8s-elastic-co-v1beta1[$$kibana.k8s.elastic.co/v1beta1$$]

Expand Down Expand Up @@ -144,6 +145,7 @@ Config represents untyped YAML configuration.
.Appears In:
****
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-apm-v1-apmserverspec[$$ApmServerSpec$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchspec[$$EnterpriseSearchSpec$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-kibana-v1-kibanaspec[$$KibanaSpec$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-elasticsearch-v1-nodeset[$$NodeSet$$]
****
Expand All @@ -159,6 +161,7 @@ HTTPConfig holds the HTTP layer configuration for resources.
****
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-apm-v1-apmserverspec[$$ApmServerSpec$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-elasticsearch-v1-elasticsearchspec[$$ElasticsearchSpec$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchspec[$$EnterpriseSearchSpec$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-kibana-v1-kibanaspec[$$KibanaSpec$$]
****

Expand Down Expand Up @@ -196,6 +199,7 @@ ObjectSelector defines a reference to a Kubernetes object.
.Appears In:
****
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-apm-v1-apmserverspec[$$ApmServerSpec$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchspec[$$EnterpriseSearchSpec$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-kibana-v1-kibanaspec[$$KibanaSpec$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-elasticsearch-v1-remotecluster[$$RemoteCluster$$]
****
Expand Down Expand Up @@ -796,6 +800,83 @@ UpdateStrategy specifies how updates to the cluster should be performed.



[id="{anchor_prefix}-enterprisesearch-k8s-elastic-co-v1beta1"]
== enterprisesearch.k8s.elastic.co/v1beta1

Package v1beta1 contains API schema definitions for managing Enterprise Search resources.

.Resource Types
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearch[$$EnterpriseSearch$$]
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchlist[$$EnterpriseSearchList$$]



[id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearch"]
=== EnterpriseSearch

EnterpriseSearch is a Kubernetes CRD to represent Enterprise Search.

.Appears In:
****
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchlist[$$EnterpriseSearchList$$]
****

[cols="25a,75a", options="header"]
|===
| Field | Description
| *`apiVersion`* __string__ | `enterprisesearch.k8s.elastic.co/v1beta1`
| *`kind`* __string__ | `EnterpriseSearch`
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.

| *`spec`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchspec[$$EnterpriseSearchSpec$$]__ |
|===


[id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchlist"]
=== EnterpriseSearchList

EnterpriseSearchList contains a list of EnterpriseSearch
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment on communicating tech-level items vs. product name.




[cols="25a,75a", options="header"]
|===
| Field | Description
| *`apiVersion`* __string__ | `enterprisesearch.k8s.elastic.co/v1beta1`
| *`kind`* __string__ | `EnterpriseSearchList`
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#listmeta-v1-meta[$$ListMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.

| *`items`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearch[$$EnterpriseSearch$$]__ |
|===


[id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchspec"]
=== EnterpriseSearchSpec

EnterpriseSearchSpec holds the specification of an Enterprise Search resource.

.Appears In:
****
- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearch[$$EnterpriseSearch$$]
****

[cols="25a,75a", options="header"]
|===
| Field | Description
| *`version`* __string__ | Version of Enterprise Search.
| *`image`* __string__ | Image is the Enterprise Search Docker image to deploy.
| *`count`* __integer__ | Count of Enterprise Search instances to deploy.
| *`config`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-config[$$Config$$]__ | Config holds the Enterprise Search configuration.
| *`http`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-httpconfig[$$HTTPConfig$$]__ | HTTP holds the HTTP layer configuration for Enterprise Search resource.
| *`elasticsearchRef`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-objectselector[$$ObjectSelector$$]__ | ElasticsearchRef is a reference to the output Elasticsearch cluster running in the same Kubernetes cluster.
| *`podTemplate`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#podtemplatespec-v1-core[$$PodTemplateSpec$$]__ | PodTemplate provides customisation options (labels, annotations, affinity rules, resource requests, and so on) for the Enterprise Search pods.
| *`serviceAccountName`* __string__ | ServiceAccountName is used to check access from the current resource to a resource (eg. Elasticsearch) in a different namespace. Can only be used if ECK is enforcing RBAC on references.
|===





[id="{anchor_prefix}-kibana-k8s-elastic-co-v1"]
== kibana.k8s.elastic.co/v1

Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/enterprisesearch/v1beta1/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// 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 v1beta1 contains API schema definitions for managing Enterprise Search resources.
// +kubebuilder:object:generate=true
// +groupName=enterprisesearch.k8s.elastic.co
package v1beta1
129 changes: 129 additions & 0 deletions pkg/apis/enterprisesearch/v1beta1/enterprisesearch_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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 v1beta1

import (
commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const EnterpriseSearchContainerName = "enterprise-search"

// EnterpriseSearchSpec holds the specification of an Enterprise Search resource.
type EnterpriseSearchSpec struct {
// Version of Enterprise Search.
Version string `json:"version,omitempty"`

// Image is the Enterprise Search Docker image to deploy.
Image string `json:"image,omitempty"`

// Count of Enterprise Search instances to deploy.
Count int32 `json:"count,omitempty"`

// Config holds the Enterprise Search configuration.
Config *commonv1.Config `json:"config,omitempty"`

// HTTP holds the HTTP layer configuration for Enterprise Search resource.
HTTP commonv1.HTTPConfig `json:"http,omitempty"`

// ElasticsearchRef is a reference to the output Elasticsearch cluster running in the same Kubernetes cluster.
sebgl marked this conversation as resolved.
Show resolved Hide resolved
ElasticsearchRef commonv1.ObjectSelector `json:"elasticsearchRef,omitempty"`

// PodTemplate provides customisation options (labels, annotations, affinity rules, resource requests, and so on)
// for the Enterprise Search pods.
// +kubebuilder:validation:Optional
PodTemplate corev1.PodTemplateSpec `json:"podTemplate,omitempty"`

// ServiceAccountName is used to check access from the current resource to a resource (eg. Elasticsearch) in a different namespace.
// Can only be used if ECK is enforcing RBAC on references.
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
}

func (spec EnterpriseSearchSpec) Protocol() string {
if spec.HTTP.TLS.Enabled() {
sebgl marked this conversation as resolved.
Show resolved Hide resolved
return "https"
}
return "http"
}

// EnterpriseSearchHealth expresses the health of the Enterprise Search instances.
type EnterpriseSearchHealth string

const (
// EnterpriseSearchRed means no instance is currently available.
EnterpriseSearchRed EnterpriseSearchHealth = "red"
// EnterpriseSearchGreen means at least one instance is available.
EnterpriseSearchGreen EnterpriseSearchHealth = "green"
)

// EnterpriseSearchStatus defines the observed state of EnterpriseSearch
type EnterpriseSearchStatus struct {
commonv1.ReconcilerStatus `json:",inline"`
Health EnterpriseSearchHealth `json:"health,omitempty"`
// ExternalService is the name of the service associated to the Enterprise Search Pods.
ExternalService string `json:"service,omitempty"`
// Association is the status of any auto-linking to Elasticsearch clusters.
Association commonv1.AssociationStatus `json:"associationStatus,omitempty"`
}

// IsDegraded returns true if the current status is worse than the previous.
func (ents EnterpriseSearchStatus) IsDegraded(prev EnterpriseSearchStatus) bool {
return prev.Health == EnterpriseSearchGreen && ents.Health != EnterpriseSearchRed
}

// IsMarkedForDeletion returns true if the EnterpriseSearch is going to be deleted
func (ents *EnterpriseSearch) IsMarkedForDeletion() bool {
return !ents.DeletionTimestamp.IsZero()
}

func (ents *EnterpriseSearch) ServiceAccountName() string {
return ents.Spec.ServiceAccountName
}

func (ents *EnterpriseSearch) ElasticsearchRef() commonv1.ObjectSelector {
return ents.Spec.ElasticsearchRef
}

func (ents *EnterpriseSearch) AssociationConf() *commonv1.AssociationConf {
return ents.assocConf
}

func (ents *EnterpriseSearch) SetAssociationConf(assocConf *commonv1.AssociationConf) {
ents.assocConf = assocConf
}

// +kubebuilder:object:root=true

// EnterpriseSearch is a Kubernetes CRD to represent Enterprise Search.
// +kubebuilder:resource:categories=elastic,shortName=entsearch
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="health",type="string",JSONPath=".status.health"
// +kubebuilder:printcolumn:name="nodes",type="integer",JSONPath=".status.availableNodes",description="Available nodes"
// +kubebuilder:printcolumn:name="version",type="string",JSONPath=".spec.version",description="Enterprise Search version"
// +kubebuilder:printcolumn:name="age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:storageversion
type EnterpriseSearch struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec EnterpriseSearchSpec `json:"spec,omitempty"`
Status EnterpriseSearchStatus `json:"status,omitempty"`
assocConf *commonv1.AssociationConf `json:"-"` //nolint:govet
}

// +kubebuilder:object:root=true

// EnterpriseSearchList contains a list of EnterpriseSearch
type EnterpriseSearchList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []EnterpriseSearch `json:"items"`
}

func init() {
SchemeBuilder.Register(&EnterpriseSearch{}, &EnterpriseSearchList{})
}
Loading