mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
Merge pull request #46101 from sttts/sttts-crd-core-names
Automatic merge from submit-queue apiextensions: add Established condition This introduces a `Established` condition on `CustomResourceDefinition`s. `Established` means that the resource has become active. A resource is established when all names are accepted initially without a conflict. A resource stays established until deleted, even during a later NameConflict due to changed names. Note that not all names can be changed. This change is necessary to allow deletion of once-active CRDs which might have still instances, but have NameConflicts now. Before this PR the REST endpoint was not active anymore in this case, making deletion of the instances impossible.
This commit is contained in:
commit
7c76e3994c
@ -70,8 +70,13 @@ const (
|
|||||||
type CustomResourceDefinitionConditionType string
|
type CustomResourceDefinitionConditionType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// NameConflict means the names chosen for this CustomResourceDefinition conflict with others in the group.
|
// Established means that the resource has become active. A resource is established when all names are
|
||||||
NameConflict CustomResourceDefinitionConditionType = "NameConflict"
|
// accepted without a conflict for the first time. A resource stays established until deleted, even during
|
||||||
|
// a later NamesAccepted due to changed names. Note that not all names can be changed.
|
||||||
|
Established CustomResourceDefinitionConditionType = "Established"
|
||||||
|
// NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in
|
||||||
|
// the group and are therefore accepted.
|
||||||
|
NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted"
|
||||||
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
||||||
Terminating CustomResourceDefinitionConditionType = "Terminating"
|
Terminating CustomResourceDefinitionConditionType = "Terminating"
|
||||||
)
|
)
|
||||||
|
@ -70,8 +70,13 @@ const (
|
|||||||
type CustomResourceDefinitionConditionType string
|
type CustomResourceDefinitionConditionType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// NameConflict means the names chosen for this CustomResourceDefinition conflict with others in the group.
|
// Established means that the resource has become active. A resource is established when all names are
|
||||||
NameConflict CustomResourceDefinitionConditionType = "NameConflict"
|
// accepted without a conflict for the first time. A resource stays established until deleted, even during
|
||||||
|
// a later NamesAccepted due to changed names. Note that not all names can be changed.
|
||||||
|
Established CustomResourceDefinitionConditionType = "Established"
|
||||||
|
// NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in
|
||||||
|
// the group and are therefore accepted.
|
||||||
|
NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted"
|
||||||
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
||||||
Terminating CustomResourceDefinitionConditionType = "Terminating"
|
Terminating CustomResourceDefinitionConditionType = "Terminating"
|
||||||
)
|
)
|
||||||
|
@ -46,7 +46,7 @@ func ValidateCustomResourceDefinition(obj *apiextensions.CustomResourceDefinitio
|
|||||||
// ValidateCustomResourceDefinitionUpdate statically validates
|
// ValidateCustomResourceDefinitionUpdate statically validates
|
||||||
func ValidateCustomResourceDefinitionUpdate(obj, oldObj *apiextensions.CustomResourceDefinition) field.ErrorList {
|
func ValidateCustomResourceDefinitionUpdate(obj, oldObj *apiextensions.CustomResourceDefinition) field.ErrorList {
|
||||||
allErrs := genericvalidation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))
|
allErrs := genericvalidation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionSpecUpdate(&obj.Spec, &oldObj.Spec, field.NewPath("spec"))...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionSpecUpdate(&obj.Spec, &oldObj.Spec, apiextensions.IsCRDConditionTrue(oldObj, apiextensions.Established), field.NewPath("spec"))...)
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionStatus(&obj.Status, field.NewPath("status"))...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionStatus(&obj.Status, field.NewPath("status"))...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
@ -64,22 +64,21 @@ func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
|
|||||||
|
|
||||||
if len(spec.Group) == 0 {
|
if len(spec.Group) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("group"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("group"), ""))
|
||||||
}
|
} else if errs := validationutil.IsDNS1123Subdomain(spec.Group); len(errs) > 0 {
|
||||||
if errs := validationutil.IsDNS1123Subdomain(spec.Group); len(errs) > 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), spec.Group, strings.Join(errs, ",")))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), spec.Group, strings.Join(errs, ",")))
|
||||||
}
|
} else if len(strings.Split(spec.Group, ".")) < 2 {
|
||||||
if len(strings.Split(spec.Group, ".")) < 2 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), spec.Group, "should be a domain with at least one dot"))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), spec.Group, "should be a domain with at least one dot"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(spec.Version) == 0 {
|
if len(spec.Version) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("version"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("version"), ""))
|
||||||
}
|
} else if errs := validationutil.IsDNS1035Label(spec.Version); len(errs) > 0 {
|
||||||
if errs := validationutil.IsDNS1035Label(spec.Version); len(errs) > 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("version"), spec.Version, strings.Join(errs, ",")))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("version"), spec.Version, strings.Join(errs, ",")))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch spec.Scope {
|
switch spec.Scope {
|
||||||
|
case "":
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("scope"), ""))
|
||||||
case apiextensions.ClusterScoped, apiextensions.NamespaceScoped:
|
case apiextensions.ClusterScoped, apiextensions.NamespaceScoped:
|
||||||
default:
|
default:
|
||||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("scope"), spec.Scope, []string{string(apiextensions.ClusterScoped), string(apiextensions.NamespaceScoped)}))
|
allErrs = append(allErrs, field.NotSupported(fldPath.Child("scope"), spec.Scope, []string{string(apiextensions.ClusterScoped), string(apiextensions.NamespaceScoped)}))
|
||||||
@ -105,17 +104,19 @@ func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateCustomResourceDefinitionSpecUpdate statically validates
|
// ValidateCustomResourceDefinitionSpecUpdate statically validates
|
||||||
func ValidateCustomResourceDefinitionSpecUpdate(spec, oldSpec *apiextensions.CustomResourceDefinitionSpec, fldPath *field.Path) field.ErrorList {
|
func ValidateCustomResourceDefinitionSpecUpdate(spec, oldSpec *apiextensions.CustomResourceDefinitionSpec, established bool, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := ValidateCustomResourceDefinitionSpec(spec, fldPath)
|
allErrs := ValidateCustomResourceDefinitionSpec(spec, fldPath)
|
||||||
|
|
||||||
// these all affect the storage, so you can't change them
|
if established {
|
||||||
genericvalidation.ValidateImmutableField(spec.Group, oldSpec.Group, fldPath.Child("group"))
|
// these effect the storage and cannot be changed therefore
|
||||||
genericvalidation.ValidateImmutableField(spec.Version, oldSpec.Version, fldPath.Child("version"))
|
allErrs = append(allErrs, genericvalidation.ValidateImmutableField(spec.Version, oldSpec.Version, fldPath.Child("version"))...)
|
||||||
genericvalidation.ValidateImmutableField(spec.Scope, oldSpec.Scope, fldPath.Child("scope"))
|
allErrs = append(allErrs, genericvalidation.ValidateImmutableField(spec.Scope, oldSpec.Scope, fldPath.Child("scope"))...)
|
||||||
genericvalidation.ValidateImmutableField(spec.Names.Kind, oldSpec.Names.Kind, fldPath.Child("names", "kind"))
|
allErrs = append(allErrs, genericvalidation.ValidateImmutableField(spec.Names.Kind, oldSpec.Names.Kind, fldPath.Child("names", "kind"))...)
|
||||||
|
}
|
||||||
|
|
||||||
// this affects the expected resource name, so you can't change it either
|
// these affects the resource name, which is always immutable, so this can't be updated.
|
||||||
genericvalidation.ValidateImmutableField(spec.Names.Plural, oldSpec.Names.Plural, fldPath.Child("names", "plural"))
|
allErrs = append(allErrs, genericvalidation.ValidateImmutableField(spec.Group, oldSpec.Group, fldPath.Child("group"))...)
|
||||||
|
allErrs = append(allErrs, genericvalidation.ValidateImmutableField(spec.Names.Plural, oldSpec.Names.Plural, fldPath.Child("names", "plural"))...)
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,17 @@ type validationMatch struct {
|
|||||||
errorType field.ErrorType
|
errorType field.ErrorType
|
||||||
}
|
}
|
||||||
|
|
||||||
func required(path *field.Path) validationMatch {
|
func required(path ...string) validationMatch {
|
||||||
return validationMatch{path: path, errorType: field.ErrorTypeRequired}
|
return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeRequired}
|
||||||
}
|
}
|
||||||
func invalid(path *field.Path) validationMatch {
|
func invalid(path ...string) validationMatch {
|
||||||
return validationMatch{path: path, errorType: field.ErrorTypeInvalid}
|
return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
|
||||||
|
}
|
||||||
|
func unsupported(path ...string) validationMatch {
|
||||||
|
return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeNotSupported}
|
||||||
|
}
|
||||||
|
func immutable(path ...string) validationMatch {
|
||||||
|
return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v validationMatch) matches(err *field.Error) bool {
|
func (v validationMatch) matches(err *field.Error) bool {
|
||||||
@ -58,7 +64,12 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
errors: []validationMatch{
|
errors: []validationMatch{
|
||||||
invalid(field.NewPath("metadata", "name")),
|
invalid("metadata", "name"),
|
||||||
|
required("spec", "version"),
|
||||||
|
required("spec", "scope"),
|
||||||
|
required("spec", "names", "singular"),
|
||||||
|
required("spec", "names", "kind"),
|
||||||
|
required("spec", "names", "listKind"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -67,13 +78,14 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
|
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
|
||||||
},
|
},
|
||||||
errors: []validationMatch{
|
errors: []validationMatch{
|
||||||
required(field.NewPath("spec", "group")),
|
invalid("metadata", "name"),
|
||||||
required(field.NewPath("spec", "version")),
|
required("spec", "group"),
|
||||||
{path: field.NewPath("spec", "scope"), errorType: field.ErrorTypeNotSupported},
|
required("spec", "version"),
|
||||||
required(field.NewPath("spec", "names", "plural")),
|
required("spec", "scope"),
|
||||||
required(field.NewPath("spec", "names", "singular")),
|
required("spec", "names", "plural"),
|
||||||
required(field.NewPath("spec", "names", "kind")),
|
required("spec", "names", "singular"),
|
||||||
required(field.NewPath("spec", "names", "listKind")),
|
required("spec", "names", "kind"),
|
||||||
|
required("spec", "names", "listKind"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -101,17 +113,20 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
errors: []validationMatch{
|
errors: []validationMatch{
|
||||||
invalid(field.NewPath("spec", "group")),
|
invalid("metadata", "name"),
|
||||||
invalid(field.NewPath("spec", "version")),
|
invalid("spec", "group"),
|
||||||
{path: field.NewPath("spec", "scope"), errorType: field.ErrorTypeNotSupported},
|
invalid("spec", "version"),
|
||||||
invalid(field.NewPath("spec", "names", "plural")),
|
unsupported("spec", "scope"),
|
||||||
invalid(field.NewPath("spec", "names", "singular")),
|
invalid("spec", "names", "plural"),
|
||||||
invalid(field.NewPath("spec", "names", "kind")),
|
invalid("spec", "names", "singular"),
|
||||||
invalid(field.NewPath("spec", "names", "listKind")),
|
invalid("spec", "names", "kind"),
|
||||||
invalid(field.NewPath("status", "acceptedNames", "plural")),
|
invalid("spec", "names", "listKind"), // invalid format
|
||||||
invalid(field.NewPath("status", "acceptedNames", "singular")),
|
invalid("spec", "names", "listKind"), // kind == listKind
|
||||||
invalid(field.NewPath("status", "acceptedNames", "kind")),
|
invalid("status", "acceptedNames", "plural"),
|
||||||
invalid(field.NewPath("status", "acceptedNames", "listKind")),
|
invalid("status", "acceptedNames", "singular"),
|
||||||
|
invalid("status", "acceptedNames", "kind"),
|
||||||
|
invalid("status", "acceptedNames", "listKind"), // invalid format
|
||||||
|
invalid("status", "acceptedNames", "listKind"), // kind == listKind
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -138,21 +153,25 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
errors: []validationMatch{
|
errors: []validationMatch{
|
||||||
invalid(field.NewPath("spec", "group")),
|
invalid("metadata", "name"),
|
||||||
invalid(field.NewPath("spec", "names", "listKind")),
|
invalid("spec", "group"),
|
||||||
invalid(field.NewPath("status", "acceptedNames", "listKind")),
|
required("spec", "scope"),
|
||||||
|
invalid("spec", "names", "listKind"),
|
||||||
|
invalid("status", "acceptedNames", "listKind"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
errs := ValidateCustomResourceDefinition(tc.resource)
|
errs := ValidateCustomResourceDefinition(tc.resource)
|
||||||
|
seenErrs := make([]bool, len(errs))
|
||||||
|
|
||||||
for _, expectedError := range tc.errors {
|
for _, expectedError := range tc.errors {
|
||||||
found := false
|
found := false
|
||||||
for _, err := range errs {
|
for i, err := range errs {
|
||||||
if expectedError.matches(err) {
|
if expectedError.matches(err) && !seenErrs[i] {
|
||||||
found = true
|
found = true
|
||||||
|
seenErrs[i] = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,5 +180,281 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
t.Errorf("%s: expected %v at %v, got %v", tc.name, expectedError.errorType, expectedError.path.String(), errs)
|
t.Errorf("%s: expected %v at %v, got %v", tc.name, expectedError.errorType, expectedError.path.String(), errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i, seen := range seenErrs {
|
||||||
|
if !seen {
|
||||||
|
t.Errorf("%s: unexpected error: %v", tc.name, errs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
old *apiextensions.CustomResourceDefinition
|
||||||
|
resource *apiextensions.CustomResourceDefinition
|
||||||
|
errors []validationMatch
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unchanged",
|
||||||
|
old: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Scope: apiextensions.ResourceScope("Cluster"),
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Scope: apiextensions.ResourceScope("Cluster"),
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unchanged-established",
|
||||||
|
old: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Scope: apiextensions.ResourceScope("Cluster"),
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
Conditions: []apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Scope: apiextensions.ResourceScope("Cluster"),
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "changes",
|
||||||
|
old: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Scope: apiextensions.ResourceScope("Cluster"),
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
Conditions: []apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
{Type: apiextensions.Established, Status: apiextensions.ConditionFalse},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "abc.com",
|
||||||
|
Version: "version2",
|
||||||
|
Scope: apiextensions.ResourceScope("Namespaced"),
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural2",
|
||||||
|
Singular: "singular2",
|
||||||
|
Kind: "kind2",
|
||||||
|
ListKind: "listkind2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural2",
|
||||||
|
Singular: "singular2",
|
||||||
|
Kind: "kind2",
|
||||||
|
ListKind: "listkind2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
immutable("spec", "group"),
|
||||||
|
immutable("spec", "names", "plural"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "changes-established",
|
||||||
|
old: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Scope: apiextensions.ResourceScope("Cluster"),
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "kind",
|
||||||
|
ListKind: "listkind",
|
||||||
|
},
|
||||||
|
Conditions: []apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
{Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "abc.com",
|
||||||
|
Version: "version2",
|
||||||
|
Scope: apiextensions.ResourceScope("Namespaced"),
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural2",
|
||||||
|
Singular: "singular2",
|
||||||
|
Kind: "kind2",
|
||||||
|
ListKind: "listkind2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural2",
|
||||||
|
Singular: "singular2",
|
||||||
|
Kind: "kind2",
|
||||||
|
ListKind: "listkind2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
immutable("spec", "group"),
|
||||||
|
immutable("spec", "version"),
|
||||||
|
immutable("spec", "scope"),
|
||||||
|
immutable("spec", "names", "kind"),
|
||||||
|
immutable("spec", "names", "plural"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
errs := ValidateCustomResourceDefinitionUpdate(tc.resource, tc.old)
|
||||||
|
seenErrs := make([]bool, len(errs))
|
||||||
|
|
||||||
|
for _, expectedError := range tc.errors {
|
||||||
|
found := false
|
||||||
|
for i, err := range errs {
|
||||||
|
if expectedError.matches(err) && !seenErrs[i] {
|
||||||
|
found = true
|
||||||
|
seenErrs[i] = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Errorf("%s: expected %v at %v, got %v", tc.name, expectedError.errorType, expectedError.path.String(), errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, seen := range seenErrs {
|
||||||
|
if !seen {
|
||||||
|
t.Errorf("%s: unexpected error: %v", tc.name, errs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,8 +85,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error {
|
|||||||
foundVersion := false
|
foundVersion := false
|
||||||
foundGroup := false
|
foundGroup := false
|
||||||
for _, crd := range crds {
|
for _, crd := range crds {
|
||||||
// if we can't definitively determine that our names are good, don't serve it
|
if !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
|
||||||
if !apiextensions.IsCRDConditionFalse(crd, apiextensions.NameConflict) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,8 +143,7 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
r.delegate.ServeHTTP(w, req)
|
r.delegate.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if we can't definitively determine that our names are good, delegate
|
if !apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
|
||||||
if !apiextensions.IsCRDConditionFalse(crd, apiextensions.NameConflict) {
|
|
||||||
r.delegate.ServeHTTP(w, req)
|
r.delegate.ServeHTTP(w, req)
|
||||||
}
|
}
|
||||||
if len(requestInfo.Subresource) > 0 {
|
if len(requestInfo.Subresource) > 0 {
|
||||||
|
@ -127,38 +127,55 @@ func (c *CRDFinalizer) sync(key string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's possible for a naming conflict to have removed this resource from the API after instances were created.
|
// Now we can start deleting items. We should use the REST API to ensure that all normal admission runs.
|
||||||
// For now we will cowardly stop finalizing. If we don't go through the REST API, weird things may happen:
|
// Since we control the endpoints, we know that delete collection works. No need to delete if not established.
|
||||||
// no audit trail, no admission checks or side effects, finalization would probably still work but defaulting
|
if apiextensions.IsCRDConditionTrue(crd, apiextensions.Established) {
|
||||||
// would be missed. It would be a mess.
|
cond, deleteErr := c.deleteInstances(crd)
|
||||||
// This requires human intervention to solve, update status so they have a reason.
|
apiextensions.SetCRDCondition(crd, *cond)
|
||||||
// TODO split coreNamesAccepted from extendedNamesAccepted. If coreNames were accepted, then we have something to cleanup
|
if deleteErr != nil {
|
||||||
// and the endpoint is serviceable. if they aren't, then there's nothing to cleanup.
|
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||||
if !apiextensions.IsCRDConditionFalse(crd, apiextensions.NameConflict) {
|
if err != nil {
|
||||||
|
utilruntime.HandleError(err)
|
||||||
|
}
|
||||||
|
return deleteErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.Terminating,
|
Type: apiextensions.Terminating,
|
||||||
Status: apiextensions.ConditionTrue,
|
Status: apiextensions.ConditionFalse,
|
||||||
Reason: "InstanceDeletionStuck",
|
Reason: "NeverEstablished",
|
||||||
Message: fmt.Sprintf("cannot proceed with deletion because of %v condition", apiextensions.NameConflict),
|
Message: "resource was never established",
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
apiextensions.CRDRemoveFinalizer(crd, apiextensions.CustomResourceCleanupFinalizer)
|
||||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fmt.Errorf("cannot proceed with deletion because of %v condition", apiextensions.NameConflict)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// and now issue another delete, which should clean it all up if no finalizers remain or no-op if they do
|
||||||
|
return c.crdClient.CustomResourceDefinitions().Delete(crd.Name, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CRDFinalizer) deleteInstances(crd *apiextensions.CustomResourceDefinition) (*apiextensions.CustomResourceDefinitionCondition, error) {
|
||||||
// Now we can start deleting items. While it would be ideal to use a REST API client, doing so
|
// Now we can start deleting items. While it would be ideal to use a REST API client, doing so
|
||||||
// could incorrectly delete a ThirdPartyResource with the same URL as the CustomResource, so we go
|
// could incorrectly delete a ThirdPartyResource with the same URL as the CustomResource, so we go
|
||||||
// directly to the storage instead. Since we control the storage, we know that delete collection works.
|
// directly to the storage instead. Since we control the storage, we know that delete collection works.
|
||||||
crClient := c.crClientGetter.GetCustomResourceListerCollectionDeleter(crd.UID)
|
crClient := c.crClientGetter.GetCustomResourceListerCollectionDeleter(crd.UID)
|
||||||
if crClient == nil {
|
if crClient == nil {
|
||||||
return fmt.Errorf("unable to find a custom resource client for %s.%s", crd.Status.AcceptedNames.Plural, crd.Spec.Group)
|
return nil, fmt.Errorf("unable to find a custom resource client for %s.%s", crd.Status.AcceptedNames.Plural, crd.Spec.Group)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := genericapirequest.NewContext()
|
ctx := genericapirequest.NewContext()
|
||||||
allResources, err := crClient.List(ctx, nil)
|
allResources, err := crClient.List(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
Type: apiextensions.Terminating,
|
||||||
|
Status: apiextensions.ConditionTrue,
|
||||||
|
Reason: "InstanceDeletionFailed",
|
||||||
|
Message: fmt.Sprintf("could not list instances: %v", err),
|
||||||
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
deletedNamespaces := sets.String{}
|
deletedNamespaces := sets.String{}
|
||||||
@ -181,23 +198,18 @@ func (c *CRDFinalizer) sync(key string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if deleteError := utilerrors.NewAggregate(deleteErrors); deleteError != nil {
|
if deleteError := utilerrors.NewAggregate(deleteErrors); deleteError != nil {
|
||||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
return &apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.Terminating,
|
Type: apiextensions.Terminating,
|
||||||
Status: apiextensions.ConditionTrue,
|
Status: apiextensions.ConditionTrue,
|
||||||
Reason: "InstanceDeletionFailed",
|
Reason: "InstanceDeletionFailed",
|
||||||
Message: fmt.Sprintf("could not issue all deletes: %v", deleteError),
|
Message: fmt.Sprintf("could not issue all deletes: %v", deleteError),
|
||||||
})
|
}, deleteError
|
||||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
|
||||||
if err != nil {
|
|
||||||
utilruntime.HandleError(err)
|
|
||||||
}
|
|
||||||
return deleteError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we need to wait until all the resources are deleted. Start with a simple poll before we do anything fancy.
|
// now we need to wait until all the resources are deleted. Start with a simple poll before we do anything fancy.
|
||||||
// TODO not all servers are synchronized on caches. It is possible for a stale one to still be creating things.
|
// TODO not all servers are synchronized on caches. It is possible for a stale one to still be creating things.
|
||||||
// Once we have a mechanism for servers to indicate their states, we should check that for concurrence.
|
// Once we have a mechanism for servers to indicate their states, we should check that for concurrence.
|
||||||
listErr := wait.PollImmediate(5*time.Second, 1*time.Minute, func() (bool, error) {
|
err = wait.PollImmediate(5*time.Second, 1*time.Minute, func() (bool, error) {
|
||||||
listObj, err := crClient.List(ctx, nil)
|
listObj, err := crClient.List(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -208,34 +220,20 @@ func (c *CRDFinalizer) sync(key string) error {
|
|||||||
glog.V(2).Infof("%s.%s waiting for %d items to be removed", crd.Status.AcceptedNames.Plural, crd.Spec.Group, len(listObj.(*unstructured.UnstructuredList).Items))
|
glog.V(2).Infof("%s.%s waiting for %d items to be removed", crd.Status.AcceptedNames.Plural, crd.Spec.Group, len(listObj.(*unstructured.UnstructuredList).Items))
|
||||||
return false, nil
|
return false, nil
|
||||||
})
|
})
|
||||||
if listErr != nil {
|
if err != nil {
|
||||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
return &apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.Terminating,
|
Type: apiextensions.Terminating,
|
||||||
Status: apiextensions.ConditionTrue,
|
Status: apiextensions.ConditionTrue,
|
||||||
Reason: "InstanceDeletionCheck",
|
Reason: "InstanceDeletionCheck",
|
||||||
Message: fmt.Sprintf("could not confirm zero CustomResources remaining: %v", listErr),
|
Message: fmt.Sprintf("could not confirm zero CustomResources remaining: %v", err),
|
||||||
})
|
}, err
|
||||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
|
||||||
if err != nil {
|
|
||||||
utilruntime.HandleError(err)
|
|
||||||
}
|
}
|
||||||
return listErr
|
return &apiextensions.CustomResourceDefinitionCondition{
|
||||||
}
|
|
||||||
|
|
||||||
apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{
|
|
||||||
Type: apiextensions.Terminating,
|
Type: apiextensions.Terminating,
|
||||||
Status: apiextensions.ConditionFalse,
|
Status: apiextensions.ConditionFalse,
|
||||||
Reason: "InstanceDeletionCompleted",
|
Reason: "InstanceDeletionCompleted",
|
||||||
Message: "removed all instances",
|
Message: "removed all instances",
|
||||||
})
|
}, nil
|
||||||
apiextensions.CRDRemoveFinalizer(crd, apiextensions.CustomResourceCleanupFinalizer)
|
|
||||||
crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// and now issue another delete, which should clean it all up if no finalizers remain or no-op if they do
|
|
||||||
return c.crdClient.CustomResourceDefinitions().Delete(crd.Name, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CRDFinalizer) Run(workers int, stopCh <-chan struct{}) {
|
func (c *CRDFinalizer) Run(workers int, stopCh <-chan struct{}) {
|
||||||
|
@ -28,6 +28,7 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/conversion"
|
"k8s.io/apimachinery/pkg/conversion"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
@ -118,12 +119,12 @@ func (c *NamingConditionController) getAcceptedNamesForGroup(group string) (allR
|
|||||||
return allResources, allKinds
|
return allResources, allKinds
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NamingConditionController) calculateNames(in *apiextensions.CustomResourceDefinition) (apiextensions.CustomResourceDefinitionNames, apiextensions.CustomResourceDefinitionCondition) {
|
func (c *NamingConditionController) calculateNamesAndConditions(in *apiextensions.CustomResourceDefinition) (apiextensions.CustomResourceDefinitionNames, apiextensions.CustomResourceDefinitionCondition, apiextensions.CustomResourceDefinitionCondition) {
|
||||||
// Get the names that have already been claimed
|
// Get the names that have already been claimed
|
||||||
allResources, allKinds := c.getAcceptedNamesForGroup(in.Spec.Group)
|
allResources, allKinds := c.getAcceptedNamesForGroup(in.Spec.Group)
|
||||||
|
|
||||||
condition := apiextensions.CustomResourceDefinitionCondition{
|
namesAcceptedCondition := apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.NameConflict,
|
Type: apiextensions.NamesAccepted,
|
||||||
Status: apiextensions.ConditionUnknown,
|
Status: apiextensions.ConditionUnknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,16 +135,16 @@ func (c *NamingConditionController) calculateNames(in *apiextensions.CustomResou
|
|||||||
// Check each name for mismatches. If there's a mismatch between spec and status, then try to deconflict.
|
// Check each name for mismatches. If there's a mismatch between spec and status, then try to deconflict.
|
||||||
// Continue on errors so that the status is the best match possible
|
// Continue on errors so that the status is the best match possible
|
||||||
if err := equalToAcceptedOrFresh(requestedNames.Plural, acceptedNames.Plural, allResources); err != nil {
|
if err := equalToAcceptedOrFresh(requestedNames.Plural, acceptedNames.Plural, allResources); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||||
condition.Reason = "Plural"
|
namesAcceptedCondition.Reason = "PluralConflict"
|
||||||
condition.Message = err.Error()
|
namesAcceptedCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.Plural = requestedNames.Plural
|
newNames.Plural = requestedNames.Plural
|
||||||
}
|
}
|
||||||
if err := equalToAcceptedOrFresh(requestedNames.Singular, acceptedNames.Singular, allResources); err != nil {
|
if err := equalToAcceptedOrFresh(requestedNames.Singular, acceptedNames.Singular, allResources); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||||
condition.Reason = "Singular"
|
namesAcceptedCondition.Reason = "SingularConflict"
|
||||||
condition.Message = err.Error()
|
namesAcceptedCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.Singular = requestedNames.Singular
|
newNames.Singular = requestedNames.Singular
|
||||||
}
|
}
|
||||||
@ -161,37 +162,58 @@ func (c *NamingConditionController) calculateNames(in *apiextensions.CustomResou
|
|||||||
|
|
||||||
}
|
}
|
||||||
if err := utilerrors.NewAggregate(errs); err != nil {
|
if err := utilerrors.NewAggregate(errs); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||||
condition.Reason = "ShortNames"
|
namesAcceptedCondition.Reason = "ShortNamesConflict"
|
||||||
condition.Message = err.Error()
|
namesAcceptedCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.ShortNames = requestedNames.ShortNames
|
newNames.ShortNames = requestedNames.ShortNames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := equalToAcceptedOrFresh(requestedNames.Kind, acceptedNames.Kind, allKinds); err != nil {
|
if err := equalToAcceptedOrFresh(requestedNames.Kind, acceptedNames.Kind, allKinds); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||||
condition.Reason = "Kind"
|
namesAcceptedCondition.Reason = "KindConflict"
|
||||||
condition.Message = err.Error()
|
namesAcceptedCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.Kind = requestedNames.Kind
|
newNames.Kind = requestedNames.Kind
|
||||||
}
|
}
|
||||||
if err := equalToAcceptedOrFresh(requestedNames.ListKind, acceptedNames.ListKind, allKinds); err != nil {
|
if err := equalToAcceptedOrFresh(requestedNames.ListKind, acceptedNames.ListKind, allKinds); err != nil {
|
||||||
condition.Status = apiextensions.ConditionTrue
|
namesAcceptedCondition.Status = apiextensions.ConditionFalse
|
||||||
condition.Reason = "ListKind"
|
namesAcceptedCondition.Reason = "ListKindConflict"
|
||||||
condition.Message = err.Error()
|
namesAcceptedCondition.Message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
newNames.ListKind = requestedNames.ListKind
|
newNames.ListKind = requestedNames.ListKind
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we haven't changed the condition, then our names must be good.
|
// if we haven't changed the condition, then our names must be good.
|
||||||
if condition.Status == apiextensions.ConditionUnknown {
|
if namesAcceptedCondition.Status == apiextensions.ConditionUnknown {
|
||||||
condition.Status = apiextensions.ConditionFalse
|
namesAcceptedCondition.Status = apiextensions.ConditionTrue
|
||||||
condition.Reason = "NoConflicts"
|
namesAcceptedCondition.Reason = "NoConflicts"
|
||||||
condition.Message = "no conflicts found"
|
namesAcceptedCondition.Message = "no conflicts found"
|
||||||
}
|
}
|
||||||
|
|
||||||
return newNames, condition
|
// set EstablishedCondition to true if all names are accepted. Never set it back to false.
|
||||||
|
establishedCondition := apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
Type: apiextensions.Established,
|
||||||
|
Status: apiextensions.ConditionFalse,
|
||||||
|
Reason: "NotAccepted",
|
||||||
|
Message: "not all names are accepted",
|
||||||
|
LastTransitionTime: metav1.NewTime(time.Now()),
|
||||||
|
}
|
||||||
|
if old := apiextensions.FindCRDCondition(in, apiextensions.Established); old != nil {
|
||||||
|
establishedCondition = *old
|
||||||
|
}
|
||||||
|
if establishedCondition.Status != apiextensions.ConditionTrue && namesAcceptedCondition.Status == apiextensions.ConditionTrue {
|
||||||
|
establishedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
Type: apiextensions.Established,
|
||||||
|
Status: apiextensions.ConditionTrue,
|
||||||
|
Reason: "InitialNamesAccepted",
|
||||||
|
Message: "the initial names have been accepted",
|
||||||
|
LastTransitionTime: metav1.NewTime(time.Now()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNames, namesAcceptedCondition, establishedCondition
|
||||||
}
|
}
|
||||||
|
|
||||||
func equalToAcceptedOrFresh(requestedName, acceptedName string, usedNames sets.String) error {
|
func equalToAcceptedOrFresh(requestedName, acceptedName string, usedNames sets.String) error {
|
||||||
@ -214,12 +236,12 @@ func (c *NamingConditionController) sync(key string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptedNames, namingCondition := c.calculateNames(inCustomResourceDefinition)
|
acceptedNames, namingCondition, establishedCondition := c.calculateNamesAndConditions(inCustomResourceDefinition)
|
||||||
// nothing to do if accepted names and NameConflict condition didn't change
|
|
||||||
|
// nothing to do if accepted names and NamesAccepted condition didn't change
|
||||||
if reflect.DeepEqual(inCustomResourceDefinition.Status.AcceptedNames, acceptedNames) &&
|
if reflect.DeepEqual(inCustomResourceDefinition.Status.AcceptedNames, acceptedNames) &&
|
||||||
apiextensions.IsCRDConditionEquivalent(
|
apiextensions.IsCRDConditionEquivalent(&namingCondition, apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.NamesAccepted)) &&
|
||||||
&namingCondition,
|
apiextensions.IsCRDConditionEquivalent(&establishedCondition, apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.Established)) {
|
||||||
apiextensions.FindCRDCondition(inCustomResourceDefinition, apiextensions.NameConflict)) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +252,7 @@ func (c *NamingConditionController) sync(key string) error {
|
|||||||
|
|
||||||
crd.Status.AcceptedNames = acceptedNames
|
crd.Status.AcceptedNames = acceptedNames
|
||||||
apiextensions.SetCRDCondition(crd, namingCondition)
|
apiextensions.SetCRDCondition(crd, namingCondition)
|
||||||
|
apiextensions.SetCRDCondition(crd, establishedCondition)
|
||||||
|
|
||||||
updatedObj, err := c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
updatedObj, err := c.crdClient.CustomResourceDefinitions().UpdateStatus(crd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -67,6 +67,12 @@ func (b *crdBuilder) StatusNames(plural, singular, kind, listKind string, shortN
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *crdBuilder) Condition(c apiextensions.CustomResourceDefinitionCondition) *crdBuilder {
|
||||||
|
b.curr.Status.Conditions = append(b.curr.Status.Conditions, c)
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func names(plural, singular, kind, listKind string, shortNames ...string) apiextensions.CustomResourceDefinitionNames {
|
func names(plural, singular, kind, listKind string, shortNames ...string) apiextensions.CustomResourceDefinitionNames {
|
||||||
ret := apiextensions.CustomResourceDefinitionNames{
|
ret := apiextensions.CustomResourceDefinitionNames{
|
||||||
Plural: plural,
|
Plural: plural,
|
||||||
@ -82,22 +88,36 @@ func (b *crdBuilder) NewOrDie() *apiextensions.CustomResourceDefinition {
|
|||||||
return &b.curr
|
return &b.curr
|
||||||
}
|
}
|
||||||
|
|
||||||
var goodCondition = apiextensions.CustomResourceDefinitionCondition{
|
var acceptedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.NameConflict,
|
Type: apiextensions.NamesAccepted,
|
||||||
Status: apiextensions.ConditionFalse,
|
Status: apiextensions.ConditionTrue,
|
||||||
Reason: "NoConflicts",
|
Reason: "NoConflicts",
|
||||||
Message: "no conflicts found",
|
Message: "no conflicts found",
|
||||||
}
|
}
|
||||||
|
|
||||||
func badCondition(reason, message string) apiextensions.CustomResourceDefinitionCondition {
|
func nameConflictCondition(reason, message string) apiextensions.CustomResourceDefinitionCondition {
|
||||||
return apiextensions.CustomResourceDefinitionCondition{
|
return apiextensions.CustomResourceDefinitionCondition{
|
||||||
Type: apiextensions.NameConflict,
|
Type: apiextensions.NamesAccepted,
|
||||||
Status: apiextensions.ConditionTrue,
|
Status: apiextensions.ConditionFalse,
|
||||||
Reason: reason,
|
Reason: reason,
|
||||||
Message: message,
|
Message: message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var establishedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
Type: apiextensions.Established,
|
||||||
|
Status: apiextensions.ConditionTrue,
|
||||||
|
Reason: "InitialNamesAccepted",
|
||||||
|
Message: "the initial names have been accepted",
|
||||||
|
}
|
||||||
|
|
||||||
|
var notEstablishedCondition = apiextensions.CustomResourceDefinitionCondition{
|
||||||
|
Type: apiextensions.Established,
|
||||||
|
Status: apiextensions.ConditionFalse,
|
||||||
|
Reason: "NotAccepted",
|
||||||
|
Message: "not all names are accepted",
|
||||||
|
}
|
||||||
|
|
||||||
func TestSync(t *testing.T) {
|
func TestSync(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -105,7 +125,8 @@ func TestSync(t *testing.T) {
|
|||||||
in *apiextensions.CustomResourceDefinition
|
in *apiextensions.CustomResourceDefinition
|
||||||
existing []*apiextensions.CustomResourceDefinition
|
existing []*apiextensions.CustomResourceDefinition
|
||||||
expectedNames apiextensions.CustomResourceDefinitionNames
|
expectedNames apiextensions.CustomResourceDefinitionNames
|
||||||
expectedCondition apiextensions.CustomResourceDefinitionCondition
|
expectedNameConflictCondition apiextensions.CustomResourceDefinitionCondition
|
||||||
|
expectedEstablishedCondition apiextensions.CustomResourceDefinitionCondition
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "first resource",
|
name: "first resource",
|
||||||
@ -114,7 +135,8 @@ func TestSync(t *testing.T) {
|
|||||||
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
Plural: "alfa",
|
Plural: "alfa",
|
||||||
},
|
},
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "different groups",
|
name: "different groups",
|
||||||
@ -123,7 +145,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("alfa.charlie.com").StatusNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
newCRD("alfa.charlie.com").StatusNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict plural to singular",
|
name: "conflict plural to singular",
|
||||||
@ -132,7 +155,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "alfa", "", "").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "alfa", "", "").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("Plural", `"alfa" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("PluralConflict", `"alfa" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict singular to shortName",
|
name: "conflict singular to shortName",
|
||||||
@ -141,7 +165,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "delta-singular").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "delta-singular").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("Singular", `"delta-singular" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("SingularConflict", `"delta-singular" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict on shortName to shortName",
|
name: "conflict on shortName to shortName",
|
||||||
@ -150,7 +175,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "hotel-shortname-2").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "", "", "hotel-shortname-2").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind"),
|
||||||
expectedCondition: badCondition("ShortNames", `"hotel-shortname-2" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("ShortNamesConflict", `"hotel-shortname-2" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict on kind to listkind",
|
name: "conflict on kind to listkind",
|
||||||
@ -159,7 +185,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "", "echo-kind").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "", "echo-kind").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "delta-singular", "", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("Kind", `"echo-kind" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("KindConflict", `"echo-kind" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "conflict on listkind to kind",
|
name: "conflict on listkind to kind",
|
||||||
@ -168,7 +195,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("ListKind", `"foxtrot-listkind" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("ListKindConflict", `"foxtrot-listkind" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no conflict on resource and kind",
|
name: "no conflict on resource and kind",
|
||||||
@ -177,7 +205,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "echo-kind", "", "").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "echo-kind", "", "").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "merge on conflicts",
|
name: "merge on conflicts",
|
||||||
@ -189,7 +218,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "", "delta-singular").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "", "delta-singular").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "yankee-singular", "echo-kind", "whiskey-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "yankee-singular", "echo-kind", "whiskey-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: badCondition("ListKind", `"foxtrot-listkind" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("ListKindConflict", `"foxtrot-listkind" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "merge on conflicts shortNames as one",
|
name: "merge on conflicts shortNames as one",
|
||||||
@ -201,7 +231,8 @@ func TestSync(t *testing.T) {
|
|||||||
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "", "delta-singular", "golf-shortname-1").NewOrDie(),
|
newCRD("india.bravo.com").StatusNames("india", "indias", "foxtrot-listkind", "", "delta-singular", "golf-shortname-1").NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "yankee-singular", "echo-kind", "whiskey-listkind", "victor-shortname-1", "uniform-shortname-2"),
|
expectedNames: names("alfa", "yankee-singular", "echo-kind", "whiskey-listkind", "victor-shortname-1", "uniform-shortname-2"),
|
||||||
expectedCondition: badCondition("ListKind", `"foxtrot-listkind" is already in use`),
|
expectedNameConflictCondition: nameConflictCondition("ListKindConflict", `"foxtrot-listkind" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no conflicts on self",
|
name: "no conflicts on self",
|
||||||
@ -216,7 +247,8 @@ func TestSync(t *testing.T) {
|
|||||||
NewOrDie(),
|
NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no conflicts on self, remove shortname",
|
name: "no conflicts on self, remove shortname",
|
||||||
@ -231,7 +263,52 @@ func TestSync(t *testing.T) {
|
|||||||
NewOrDie(),
|
NewOrDie(),
|
||||||
},
|
},
|
||||||
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1"),
|
expectedNames: names("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1"),
|
||||||
expectedCondition: goodCondition,
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "established before with true condition",
|
||||||
|
in: newCRD("alfa.bravo.com").Condition(establishedCondition).NewOrDie(),
|
||||||
|
existing: []*apiextensions.CustomResourceDefinition{},
|
||||||
|
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "alfa",
|
||||||
|
},
|
||||||
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not established before with false condition",
|
||||||
|
in: newCRD("alfa.bravo.com").Condition(notEstablishedCondition).NewOrDie(),
|
||||||
|
existing: []*apiextensions.CustomResourceDefinition{},
|
||||||
|
expectedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "alfa",
|
||||||
|
},
|
||||||
|
expectedNameConflictCondition: acceptedCondition,
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "conflicting, established before with true condition",
|
||||||
|
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||||
|
Condition(establishedCondition).
|
||||||
|
NewOrDie(),
|
||||||
|
existing: []*apiextensions.CustomResourceDefinition{
|
||||||
|
newCRD("india.bravo.com").StatusNames("india", "alfa", "", "").NewOrDie(),
|
||||||
|
},
|
||||||
|
expectedNames: names("", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
|
expectedNameConflictCondition: nameConflictCondition("PluralConflict", `"alfa" is already in use`),
|
||||||
|
expectedEstablishedCondition: establishedCondition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "conflicting, not established before with false condition",
|
||||||
|
in: newCRD("alfa.bravo.com").SpecNames("alfa", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2").
|
||||||
|
Condition(notEstablishedCondition).
|
||||||
|
NewOrDie(),
|
||||||
|
existing: []*apiextensions.CustomResourceDefinition{
|
||||||
|
newCRD("india.bravo.com").StatusNames("india", "alfa", "", "").NewOrDie(),
|
||||||
|
},
|
||||||
|
expectedNames: names("", "delta-singular", "echo-kind", "foxtrot-listkind", "golf-shortname-1", "hotel-shortname-2"),
|
||||||
|
expectedNameConflictCondition: nameConflictCondition("PluralConflict", `"alfa" is already in use`),
|
||||||
|
expectedEstablishedCondition: notEstablishedCondition,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,12 +322,15 @@ func TestSync(t *testing.T) {
|
|||||||
crdLister: listers.NewCustomResourceDefinitionLister(crdIndexer),
|
crdLister: listers.NewCustomResourceDefinitionLister(crdIndexer),
|
||||||
crdMutationCache: cache.NewIntegerResourceVersionMutationCache(crdIndexer, crdIndexer, 60*time.Second, false),
|
crdMutationCache: cache.NewIntegerResourceVersionMutationCache(crdIndexer, crdIndexer, 60*time.Second, false),
|
||||||
}
|
}
|
||||||
actualNames, actualCondition := c.calculateNames(tc.in)
|
actualNames, actualNameConflictCondition, actualEstablishedCondition := c.calculateNamesAndConditions(tc.in)
|
||||||
|
|
||||||
if e, a := tc.expectedNames, actualNames; !reflect.DeepEqual(e, a) {
|
if e, a := tc.expectedNames, actualNames; !reflect.DeepEqual(e, a) {
|
||||||
t.Errorf("%v expected %v, got %#v", tc.name, e, a)
|
t.Errorf("%v expected %v, got %#v", tc.name, e, a)
|
||||||
}
|
}
|
||||||
if e, a := tc.expectedCondition, actualCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) {
|
if e, a := tc.expectedNameConflictCondition, actualNameConflictCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) {
|
||||||
|
t.Errorf("%v expected %v, got %v", tc.name, e, a)
|
||||||
|
}
|
||||||
|
if e, a := tc.expectedEstablishedCondition, actualEstablishedCondition; !apiextensions.IsCRDConditionEquivalent(&e, &a) {
|
||||||
t.Errorf("%v expected %v, got %v", tc.name, e, a)
|
t.Errorf("%v expected %v, got %v", tc.name, e, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user