remove test cases disabling the gates

please review with whitespace change ignored (-w)
This commit is contained in:
Haowei Cai 2019-08-26 14:35:33 -07:00
parent 7997058d73
commit 6960bf9f4f

View File

@ -24,19 +24,15 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
) )
func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) { func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
t.Log("testing unversioned validation..") t.Log("testing unversioned validation..")
for _, validationEnabled := range []bool{true, false} {
crdWithUnversionedValidation := func() *apiextensions.CustomResourceDefinition { crdWithUnversionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with non-versioned validation // crd with non-versioned validation
return &apiextensions.CustomResourceDefinition{ return &apiextensions.CustomResourceDefinition{
@ -55,35 +51,30 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
} }
crdInfos := []struct { crdInfos := []struct {
name string name string
hasCRValidation bool
crd func() *apiextensions.CustomResourceDefinition crd func() *apiextensions.CustomResourceDefinition
}{ }{
{ {
name: "has unversioned validation", name: "has unversioned validation",
hasCRValidation: true,
crd: crdWithUnversionedValidation, crd: crdWithUnversionedValidation,
}, },
{ {
name: "doesn't have unversioned validation", name: "doesn't have unversioned validation",
hasCRValidation: false,
crd: crdWithoutUnversionedValidation, crd: crdWithoutUnversionedValidation,
}, },
{ {
name: "nil", name: "nil",
hasCRValidation: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil }, crd: func() *apiextensions.CustomResourceDefinition { return nil },
}, },
} }
for _, oldCRDInfo := range crdInfos { for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos { for _, newCRDInfo := range crdInfos {
oldCRDHasValidation, oldCRD := oldCRDInfo.hasCRValidation, oldCRDInfo.crd() oldCRD := oldCRDInfo.crd()
newCRDHasValidation, newCRD := newCRDInfo.hasCRValidation, newCRDInfo.crd() newCRD := newCRDInfo.crd()
if newCRD == nil { if newCRD == nil {
continue continue
} }
t.Run(fmt.Sprintf("validation feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name), t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) { func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceValidation, validationEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil { if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec oldCRDSpec = &oldCRD.Spec
@ -93,31 +84,15 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) { if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd())) t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
} }
switch {
case validationEnabled || oldCRDHasValidation:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) { if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd())) t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
} }
case newCRDHasValidation:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutUnversionedValidation()) {
t.Errorf("new crd had unversioned validation: %v", diff.ObjectReflectDiff(newCRD, crdWithoutUnversionedValidation()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
}, },
) )
} }
} }
}
t.Log("testing unversioned subresources...") t.Log("testing unversioned subresources...")
for _, validationEnabled := range []bool{true, false} {
crdWithUnversionedSubresources := func() *apiextensions.CustomResourceDefinition { crdWithUnversionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd with unversioned subresources // crd with unversioned subresources
return &apiextensions.CustomResourceDefinition{ return &apiextensions.CustomResourceDefinition{
@ -132,37 +107,32 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
Spec: apiextensions.CustomResourceDefinitionSpec{}, Spec: apiextensions.CustomResourceDefinitionSpec{},
} }
} }
crdInfos := []struct { crdInfos = []struct {
name string name string
hasCRSubresources bool
crd func() *apiextensions.CustomResourceDefinition crd func() *apiextensions.CustomResourceDefinition
}{ }{
{ {
name: "has unversioned subresources", name: "has unversioned subresources",
hasCRSubresources: true,
crd: crdWithUnversionedSubresources, crd: crdWithUnversionedSubresources,
}, },
{ {
name: "doesn't have unversioned subresources", name: "doesn't have unversioned subresources",
hasCRSubresources: false,
crd: crdWithoutUnversionedSubresources, crd: crdWithoutUnversionedSubresources,
}, },
{ {
name: "nil", name: "nil",
hasCRSubresources: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil }, crd: func() *apiextensions.CustomResourceDefinition { return nil },
}, },
} }
for _, oldCRDInfo := range crdInfos { for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos { for _, newCRDInfo := range crdInfos {
oldCRDHasSubresources, oldCRD := oldCRDInfo.hasCRSubresources, oldCRDInfo.crd() oldCRD := oldCRDInfo.crd()
newCRDHasSubresources, newCRD := newCRDInfo.hasCRSubresources, newCRDInfo.crd() newCRD := newCRDInfo.crd()
if newCRD == nil { if newCRD == nil {
continue continue
} }
t.Run(fmt.Sprintf("subresources feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name), t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) { func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceSubresources, validationEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil { if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec oldCRDSpec = &oldCRD.Spec
@ -172,32 +142,15 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) { if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd())) t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
} }
switch {
case validationEnabled || oldCRDHasSubresources:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) { if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd())) t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
} }
case newCRDHasSubresources:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutUnversionedSubresources()) {
t.Errorf("new crd had unversioned subresources: %v", diff.ObjectReflectDiff(newCRD, crdWithoutUnversionedSubresources()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
}, },
) )
} }
} }
}
t.Log("testing versioned validation..") t.Log("testing versioned validation..")
for _, conversionEnabled := range []bool{true, false} {
for _, validationEnabled := range []bool{true, false} {
crdWithVersionedValidation := func() *apiextensions.CustomResourceDefinition { crdWithVersionedValidation := func() *apiextensions.CustomResourceDefinition {
// crd with versioned validation // crd with versioned validation
return &apiextensions.CustomResourceDefinition{ return &apiextensions.CustomResourceDefinition{
@ -225,38 +178,32 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
}, },
} }
} }
crdInfos := []struct { crdInfos = []struct {
name string name string
hasCRValidation bool
crd func() *apiextensions.CustomResourceDefinition crd func() *apiextensions.CustomResourceDefinition
}{ }{
{ {
name: "has versioned validation", name: "has versioned validation",
hasCRValidation: true,
crd: crdWithVersionedValidation, crd: crdWithVersionedValidation,
}, },
{ {
name: "doesn't have versioned validation", name: "doesn't have versioned validation",
hasCRValidation: false,
crd: crdWithoutVersionedValidation, crd: crdWithoutVersionedValidation,
}, },
{ {
name: "nil", name: "nil",
hasCRValidation: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil }, crd: func() *apiextensions.CustomResourceDefinition { return nil },
}, },
} }
for _, oldCRDInfo := range crdInfos { for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos { for _, newCRDInfo := range crdInfos {
oldCRDHasValidation, oldCRD := oldCRDInfo.hasCRValidation, oldCRDInfo.crd() oldCRD := oldCRDInfo.crd()
newCRDHasValidation, newCRD := newCRDInfo.hasCRValidation, newCRDInfo.crd() newCRD := newCRDInfo.crd()
if newCRD == nil { if newCRD == nil {
continue continue
} }
t.Run(fmt.Sprintf("validation feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name), t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) { func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceValidation, validationEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceWebhookConversion, conversionEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil { if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec oldCRDSpec = &oldCRD.Spec
@ -266,37 +213,15 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) { if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd())) t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
} }
switch {
case (conversionEnabled && validationEnabled) || oldCRDHasValidation:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) { if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd())) t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
} }
case !conversionEnabled && !oldCRDHasValidation:
if !reflect.DeepEqual(newCRD, crdWithoutVersionedValidation()) {
t.Errorf("new crd was not changed")
}
case newCRDHasValidation:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutVersionedValidation()) {
t.Errorf("new crd had unversioned validation: %v", diff.ObjectReflectDiff(newCRD, crdWithoutVersionedValidation()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
}, },
) )
} }
} }
}
}
t.Log("testing versioned subresources w/ conversion enabled..") t.Log("testing versioned subresources w/ conversion enabled..")
for _, conversionEnabled := range []bool{true, false} {
for _, validationEnabled := range []bool{true, false} {
crdWithVersionedSubresources := func() *apiextensions.CustomResourceDefinition { crdWithVersionedSubresources := func() *apiextensions.CustomResourceDefinition {
// crd with versioned subresources // crd with versioned subresources
return &apiextensions.CustomResourceDefinition{ return &apiextensions.CustomResourceDefinition{
@ -322,38 +247,32 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
}, },
} }
} }
crdInfos := []struct { crdInfos = []struct {
name string name string
hasCRSubresources bool
crd func() *apiextensions.CustomResourceDefinition crd func() *apiextensions.CustomResourceDefinition
}{ }{
{ {
name: "has versioned subresources", name: "has versioned subresources",
hasCRSubresources: true,
crd: crdWithVersionedSubresources, crd: crdWithVersionedSubresources,
}, },
{ {
name: "doesn't have versioned subresources", name: "doesn't have versioned subresources",
hasCRSubresources: false,
crd: crdWithoutVersionedSubresources, crd: crdWithoutVersionedSubresources,
}, },
{ {
name: "nil", name: "nil",
hasCRSubresources: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil }, crd: func() *apiextensions.CustomResourceDefinition { return nil },
}, },
} }
for _, oldCRDInfo := range crdInfos { for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos { for _, newCRDInfo := range crdInfos {
oldCRDHasSubresources, oldCRD := oldCRDInfo.hasCRSubresources, oldCRDInfo.crd() oldCRD := oldCRDInfo.crd()
newCRDHasSubresources, newCRD := newCRDInfo.hasCRSubresources, newCRDInfo.crd() newCRD := newCRDInfo.crd()
if newCRD == nil { if newCRD == nil {
continue continue
} }
t.Run(fmt.Sprintf("subresources feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name), t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) { func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceSubresources, validationEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceWebhookConversion, conversionEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil { if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec oldCRDSpec = &oldCRD.Spec
@ -363,36 +282,15 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) { if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd())) t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
} }
switch {
case (conversionEnabled && validationEnabled) || oldCRDHasSubresources:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) { if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd())) t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
} }
case !conversionEnabled && !oldCRDHasSubresources:
if !reflect.DeepEqual(newCRD, crdWithoutVersionedSubresources()) {
t.Errorf("new crd was not changed")
}
case newCRDHasSubresources:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutVersionedSubresources()) {
t.Errorf("new crd had versioned subresources: %v", diff.ObjectReflectDiff(newCRD, crdWithoutVersionedSubresources()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
}, },
) )
} }
} }
}
}
t.Log("testing conversion webhook..") t.Log("testing conversion webhook..")
for _, validationEnabled := range []bool{true, false} {
crdWithUnversionedConversionWebhook := func() *apiextensions.CustomResourceDefinition { crdWithUnversionedConversionWebhook := func() *apiextensions.CustomResourceDefinition {
// crd with conversion webhook // crd with conversion webhook
return &apiextensions.CustomResourceDefinition{ return &apiextensions.CustomResourceDefinition{
@ -411,37 +309,32 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
}, },
} }
} }
crdInfos := []struct { crdInfos = []struct {
name string name string
hasCRConversionWebhook bool
crd func() *apiextensions.CustomResourceDefinition crd func() *apiextensions.CustomResourceDefinition
}{ }{
{ {
name: "has conversion webhook", name: "has conversion webhook",
hasCRConversionWebhook: true,
crd: crdWithUnversionedConversionWebhook, crd: crdWithUnversionedConversionWebhook,
}, },
{ {
name: "doesn't have conversion webhook", name: "doesn't have conversion webhook",
hasCRConversionWebhook: false,
crd: crdWithoutUnversionedConversionWebhook, crd: crdWithoutUnversionedConversionWebhook,
}, },
{ {
name: "nil", name: "nil",
hasCRConversionWebhook: false,
crd: func() *apiextensions.CustomResourceDefinition { return nil }, crd: func() *apiextensions.CustomResourceDefinition { return nil },
}, },
} }
for _, oldCRDInfo := range crdInfos { for _, oldCRDInfo := range crdInfos {
for _, newCRDInfo := range crdInfos { for _, newCRDInfo := range crdInfos {
oldCRDHasConversionWebhook, oldCRD := oldCRDInfo.hasCRConversionWebhook, oldCRDInfo.crd() oldCRD := oldCRDInfo.crd()
newCRDHasConversionWebhook, newCRD := newCRDInfo.hasCRConversionWebhook, newCRDInfo.crd() newCRD := newCRDInfo.crd()
if newCRD == nil { if newCRD == nil {
continue continue
} }
t.Run(fmt.Sprintf("subresources feature enabled=%v, old CRD %v, new CRD %v", validationEnabled, oldCRDInfo.name, newCRDInfo.name), t.Run(fmt.Sprintf("old CRD %v, new CRD %v", oldCRDInfo.name, newCRDInfo.name),
func(t *testing.T) { func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceWebhookConversion, validationEnabled)()
var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec var oldCRDSpec *apiextensions.CustomResourceDefinitionSpec
if oldCRD != nil { if oldCRD != nil {
oldCRDSpec = &oldCRD.Spec oldCRDSpec = &oldCRD.Spec
@ -451,29 +344,13 @@ func TestDropDisableFieldsCustomResourceDefinition(t *testing.T) {
if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) { if !reflect.DeepEqual(oldCRD, oldCRDInfo.crd()) {
t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd())) t.Errorf("old crd changed: %v", diff.ObjectReflectDiff(oldCRD, oldCRDInfo.crd()))
} }
switch {
case validationEnabled || oldCRDHasConversionWebhook:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) { if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd())) t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
} }
case newCRDHasConversionWebhook:
if reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd was not changed")
}
if !reflect.DeepEqual(newCRD, crdWithoutUnversionedConversionWebhook()) {
t.Errorf("new crd had webhook conversion: %v", diff.ObjectReflectDiff(newCRD, crdWithoutUnversionedConversionWebhook()))
}
default:
if !reflect.DeepEqual(newCRD, newCRDInfo.crd()) {
t.Errorf("new crd changed: %v", diff.ObjectReflectDiff(newCRD, newCRDInfo.crd()))
}
}
}, },
) )
} }
} }
}
} }
func strPtr(in string) *string { func strPtr(in string) *string {