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

Prepare for release v0.0.10 #86

Merged
merged 1 commit into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
k8s.io/klog/v2 v2.110.1
kmodules.xyz/client-go v0.29.6
kmodules.xyz/custom-resources v0.29.1
kubedb.dev/apimachinery v0.41.0-rc.0.0.20240128093058-c5efabadb939
kubedb.dev/apimachinery v0.41.0
sigs.k8s.io/controller-runtime v0.17.0
xorm.io/xorm v1.3.6
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -581,8 +581,8 @@ kmodules.xyz/monitoring-agent-api v0.29.0 h1:gpFl6OZrlMLb/ySMHdREI9EwGtnJ91oZBn9
kmodules.xyz/monitoring-agent-api v0.29.0/go.mod h1:iNbvaMTgVFOI5q2LJtGK91j4Dmjv4ZRiRdasGmWLKQI=
kmodules.xyz/offshoot-api v0.29.0 h1:GHLhxxT9jU1N8+FvOCCeJNyU5g0duYS46UGrs6AHNLY=
kmodules.xyz/offshoot-api v0.29.0/go.mod h1:5NxhBblXoDHWStx9HCDJR2KFTwYjEZ7i1Id3jelIunw=
kubedb.dev/apimachinery v0.41.0-rc.0.0.20240128093058-c5efabadb939 h1:7JDxXGvuovprHhmjK5J6dAAkLliUaAZ15NxHP4gTA5M=
kubedb.dev/apimachinery v0.41.0-rc.0.0.20240128093058-c5efabadb939/go.mod h1:4OVvDKcHZSTjrXCp0+G1ar64gNEvPF3uR9eR7Xpyeos=
kubedb.dev/apimachinery v0.41.0 h1:VbGQnH3YL7ICFvnCjAumlnL3HeZzg5F4F+flpjOSnG4=
kubedb.dev/apimachinery v0.41.0/go.mod h1:rNWsbBzdnZA8G2FE8igi+nsGnlWqYurC+i3RFFDwluc=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
Expand Down
15 changes: 13 additions & 2 deletions vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -1137,9 +1137,20 @@ var (
},
}

// DefaultResourcesElasticSearch must be used for elasticsearch
// DefaultResourcesCPUIntensive is for MongoDB versions >= 6
DefaultResourcesCPUIntensive = core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceCPU: resource.MustParse(".800"),
core.ResourceMemory: resource.MustParse("1024Mi"),
},
Limits: core.ResourceList{
core.ResourceMemory: resource.MustParse("1024Mi"),
},
}

// DefaultResourcesMemoryIntensive must be used for elasticsearch
// to avoid OOMKILLED while deploying ES V8
DefaultResourcesElasticSearch = core.ResourceRequirements{
DefaultResourcesMemoryIntensive = core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceCPU: resource.MustParse(".500"),
core.ResourceMemory: resource.MustParse("1.5Gi"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"kubedb.dev/apimachinery/apis/kubedb"
"kubedb.dev/apimachinery/crds"

"github.com/Masterminds/semver/v3"
"gomodules.xyz/pointer"
v1 "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -316,66 +317,85 @@ func (d *Druid) SetDefaults() {
return
}

version, err := semver.NewVersion(druidVersion.Spec.Version)
if err != nil {
klog.Errorf("failed to parse druid version :%s\n", err.Error())
return
}

if d.Spec.Topology != nil {
if d.Spec.Topology.Coordinators != nil {
if d.Spec.Topology.Coordinators.Replicas == nil {
d.Spec.Topology.Coordinators.Replicas = pointer.Int32P(1)
}
if d.Spec.Topology.Coordinators.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Coordinators.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
if version.Major() > 25 {
if d.Spec.Topology.Coordinators.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Coordinators.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Coordinators.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Coordinators.PodTemplate)
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Coordinators.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Coordinators.PodTemplate)
}
if d.Spec.Topology.Overlords != nil {
if d.Spec.Topology.Overlords.Replicas == nil {
d.Spec.Topology.Overlords.Replicas = pointer.Int32P(1)
}
if d.Spec.Topology.Overlords.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Overlords.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
if version.Major() > 25 {
if d.Spec.Topology.Overlords.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Overlords.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Overlords.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Overlords.PodTemplate)
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Overlords.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Overlords.PodTemplate)
}
if d.Spec.Topology.MiddleManagers != nil {
if d.Spec.Topology.MiddleManagers.Replicas == nil {
d.Spec.Topology.MiddleManagers.Replicas = pointer.Int32P(1)
}
if d.Spec.Topology.MiddleManagers.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.MiddleManagers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
if version.Major() > 25 {
if d.Spec.Topology.MiddleManagers.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.MiddleManagers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.MiddleManagers.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.MiddleManagers.PodTemplate)
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.MiddleManagers.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.MiddleManagers.PodTemplate)
}
if d.Spec.Topology.Historicals != nil {
if d.Spec.Topology.Historicals.Replicas == nil {
d.Spec.Topology.Historicals.Replicas = pointer.Int32P(1)
}
if d.Spec.Topology.Historicals.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Historicals.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
if d.Spec.Version > "25.0.0" {
if d.Spec.Topology.Historicals.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Historicals.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Historicals.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Historicals.PodTemplate)
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Historicals.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Historicals.PodTemplate)
}
if d.Spec.Topology.Brokers != nil {
if d.Spec.Topology.Brokers.Replicas == nil {
d.Spec.Topology.Brokers.Replicas = pointer.Int32P(1)
}
if d.Spec.Topology.Brokers.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Brokers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
if version.Major() > 25 {
if d.Spec.Topology.Brokers.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Brokers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Brokers.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Brokers.PodTemplate)

}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Brokers.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Brokers.PodTemplate)
}
if d.Spec.Topology.Routers != nil {
if d.Spec.Topology.Routers.Replicas == nil {
d.Spec.Topology.Routers.Replicas = pointer.Int32P(1)
}
if d.Spec.Topology.Routers.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Routers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
if version.Major() > 25 {
if d.Spec.Topology.Routers.PodTemplate.Spec.SecurityContext == nil {
d.Spec.Topology.Routers.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: druidVersion.Spec.SecurityContext.RunAsUser}
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Routers.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Routers.PodTemplate)
}
d.setDefaultContainerSecurityContext(&druidVersion, &d.Spec.Topology.Routers.PodTemplate)
d.setDefaultInitContainerSecurityContext(&druidVersion, &d.Spec.Topology.Routers.PodTemplate)
}
}
if d.Spec.MetadataStorage != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.Ingest.Suffix == "" {
e.Spec.Topology.Ingest.Suffix = string(ElasticsearchNodeRoleTypeIngest)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.Ingest.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.Ingest.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.Ingest.Replicas == nil {
e.Spec.Topology.Ingest.Replicas = pointer.Int32P(1)
}
Expand All @@ -463,7 +463,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.Master.Suffix == "" {
e.Spec.Topology.Master.Suffix = string(ElasticsearchNodeRoleTypeMaster)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.Master.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.Master.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.Master.Replicas == nil {
e.Spec.Topology.Master.Replicas = pointer.Int32P(1)
}
Expand All @@ -478,7 +478,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.Data.Suffix == "" {
e.Spec.Topology.Data.Suffix = string(ElasticsearchNodeRoleTypeData)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.Data.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.Data.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.Data.Replicas == nil {
e.Spec.Topology.Data.Replicas = pointer.Int32P(1)
}
Expand All @@ -493,7 +493,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.DataHot.Suffix == "" {
e.Spec.Topology.DataHot.Suffix = string(ElasticsearchNodeRoleTypeDataHot)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataHot.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataHot.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.DataHot.Replicas == nil {
e.Spec.Topology.DataHot.Replicas = pointer.Int32P(1)
}
Expand All @@ -508,7 +508,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.DataWarm.Suffix == "" {
e.Spec.Topology.DataWarm.Suffix = string(ElasticsearchNodeRoleTypeDataWarm)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataWarm.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataWarm.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.DataWarm.Replicas == nil {
e.Spec.Topology.DataWarm.Replicas = pointer.Int32P(1)
}
Expand All @@ -523,7 +523,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.DataCold.Suffix == "" {
e.Spec.Topology.DataCold.Suffix = string(ElasticsearchNodeRoleTypeDataCold)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataCold.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataCold.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.DataCold.Replicas == nil {
e.Spec.Topology.DataCold.Replicas = pointer.Int32P(1)
}
Expand All @@ -538,7 +538,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.DataFrozen.Suffix == "" {
e.Spec.Topology.DataFrozen.Suffix = string(ElasticsearchNodeRoleTypeDataFrozen)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataFrozen.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataFrozen.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.DataFrozen.Replicas == nil {
e.Spec.Topology.DataFrozen.Replicas = pointer.Int32P(1)
}
Expand All @@ -553,7 +553,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.DataContent.Suffix == "" {
e.Spec.Topology.DataContent.Suffix = string(ElasticsearchNodeRoleTypeDataContent)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataContent.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.DataContent.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.DataContent.Replicas == nil {
e.Spec.Topology.DataContent.Replicas = pointer.Int32P(1)
}
Expand All @@ -568,7 +568,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.ML.Suffix == "" {
e.Spec.Topology.ML.Suffix = string(ElasticsearchNodeRoleTypeML)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.ML.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.ML.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.ML.Replicas == nil {
e.Spec.Topology.ML.Replicas = pointer.Int32P(1)
}
Expand All @@ -583,7 +583,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
if e.Spec.Topology.Transform.Suffix == "" {
e.Spec.Topology.Transform.Suffix = string(ElasticsearchNodeRoleTypeTransform)
}
apis.SetDefaultResourceLimits(&e.Spec.Topology.Transform.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.Topology.Transform.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Topology.Transform.Replicas == nil {
e.Spec.Topology.Transform.Replicas = pointer.Int32P(1)
}
Expand All @@ -593,7 +593,7 @@ func (e *Elasticsearch) SetDefaults(esVersion *catalog.ElasticsearchVersion, top
}

} else {
apis.SetDefaultResourceLimits(&e.Spec.PodTemplate.Spec.Resources, DefaultResourcesElasticSearch)
apis.SetDefaultResourceLimits(&e.Spec.PodTemplate.Spec.Resources, DefaultResourcesMemoryIntensive)
if e.Spec.Replicas == nil {
e.Spec.Replicas = pointer.Int32P(1)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,15 @@ func (f *FerretDB) SetDefaults() {
}

defaultVersion := "13.13"
if !f.Spec.Backend.ExternallyManaged && f.Spec.Backend.Postgres == nil {
f.Spec.Backend.Postgres = &PostgresRef{
Version: &defaultVersion,
if !f.Spec.Backend.ExternallyManaged {
if f.Spec.Backend.Postgres == nil {
f.Spec.Backend.Postgres = &PostgresRef{
Version: &defaultVersion,
}
} else {
if f.Spec.Backend.Postgres.Version == nil {
f.Spec.Backend.Postgres.Version = &defaultVersion
}
}
}
f.SetTLSDefaults()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,15 @@ type PostgresRef struct {
}

type PostgresServiceRef struct {
Name *string `json:"name"`
Namespace *string `json:"namespace"`
// +optional
Name string `json:"name,omitempty"`
// +optional
Namespace string `json:"namespace,omitempty"`
// PgPort is used because the service referred to the
// pg pod can have any port between 1 and 65535, inclusive
// but targetPort is fixed to 5432
PgPort *string `json:"pgPort"`
// +optional
PgPort int32 `json:"pgPort,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ func (f *FerretDB) ValidateCreateOrUpdate() field.ErrorList {
`'spec.authSecret.name' needs to specify when auth secret is externally managed`))
}

// Termination policy related
if f.Spec.StorageType == StorageTypeEphemeral && f.Spec.TerminationPolicy == TerminationPolicyHalt {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
f.Name,
Expand All @@ -173,22 +174,26 @@ func (f *FerretDB) ValidateCreateOrUpdate() field.ErrorList {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
`'spec.postgres' is missing when backend is externally managed`))
} else {
if f.Spec.Backend.Postgres.URL == nil {
err := f.validateServiceRef(f.Spec.Backend.Postgres.Service)
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
err.Error()))
}
}
if f.Spec.Backend.Postgres != nil && f.Spec.Backend.Postgres.URL == nil && f.Spec.Backend.Postgres.Service == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
`Have to provide 'backend.postgres.url' or 'backend.postgres.service' when backend is externally managed`))
}
}
if !f.Spec.Backend.ExternallyManaged && f.Spec.Backend.Postgres != nil && f.Spec.Backend.Postgres.Version != nil {
err := f.validatePostgresVersion()
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
err.Error()))
} else {
if f.Spec.Backend.Postgres != nil && f.Spec.Backend.Postgres.Version != nil {
err := f.validatePostgresVersion()
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
err.Error()))
}
}
}

// TLS related
if f.Spec.SSLMode == SSLModeAllowSSL || f.Spec.SSLMode == SSLModePreferSSL {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("sslMode"),
f.Name,
Expand Down Expand Up @@ -239,3 +244,14 @@ func (f *FerretDB) validatePostgresVersion() error {
}
return nil
}

func (f *FerretDB) validateServiceRef(ref *PostgresServiceRef) error {
if ref == nil {
return errors.New(`have to provide 'backend.postgres.url' or 'backend.postgres.service' when backend is externally managed`)
}
// port needs to be 0 < x < 65536
if ref.Namespace == "" || ref.Name == "" || ref.PgPort <= 0 || ref.PgPort >= 65536 {
return errors.New("pg service reference name, namespace and port(0<x<65536) needs to specify properly")
}
return nil
}
Loading
Loading