helm: Installing a CRD with Validation containing `maximum`, `minimum` and/or `multipleOf` fails with Helm v2.14.0

Output of helm version:

Client: &version.Version{SemVer:"v2.14.0", GitCommit:"05811b84a3f93603dd6c2fcfe57944dfa7ab7fd0", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.14.0", GitCommit:"05811b84a3f93603dd6c2fcfe57944dfa7ab7fd0", GitTreeState:"clean"}

Output of kubectl version:

Client Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.2", GitCommit:"66049e3b21efe110454d67df4fa62b08ea79a19b", GitTreeState:"clean", BuildDate:"2019-05-16T18:55:03Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.2", GitCommit:"66049e3b21efe110454d67df4fa62b08ea79a19b", GitTreeState:"clean", BuildDate:"2019-05-16T16:14:56Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"linux/amd64"}

Cloud Provider/Platform (AKS, GKE, Minikube etc.): Minikube and DigitalOcean

I have a chart that deploys a CRD and it has CustomResourceValidation with fields Maximum and Minimum set. This was working properly with Helm v2.13.1 but after upgrading to v2.14.0 it fails with:

Error: unable to convert to CRD type: unable to convert unstructured object to apiextensions.k8s.io/v1beta1, Kind=CustomResourceDefinition: cannot convert int64 to float64

The relevant part of the CRD is this:

  validation:
    openAPIV3Schema:
      properties:
        spec:
          properties:
            replicas:
              type: integer
              minimum: 1
              maximum: 10

I’ve been debugging the issue and I’ve figured out that the problem might come from the way JSON treats numbers during decoding. First of all, the definition of the type JSONSchemaProps that models the CustomResourceValidation contains these type declarations for minimum, maximum and multipleOf:

	Maximum              *float64                   `json:"maximum,omitempty" protobuf:"bytes,9,opt,name=maximum"`
	Minimum              *float64                   `json:"minimum,omitempty" protobuf:"bytes,11,opt,name=minimum"`
	MultipleOf           *float64                   `json:"multipleOf,omitempty" protobuf:"bytes,19,opt,name=multipleOf"`

So they expect to be of type *float64.

My guess is: New Helm v2.14.0 implements a new way to wait till the CRD is established. It is using Get for fetching the actual CRD content from Kubernetes. After fetching the CRD data from Kubernetes, it tries to convert this from Unstructured format into the actual apiextv1beta1.CustomResourceDefinition type here. When the values of any of those fields are integers, it fails as described above. Looking into the conversion code, which resides in Kubernetes codebase, it is not handling the possibility of converting from an int64 value as the source (which is read from the Unstructured representation of the CRD content coming from Kubernetes) into a float64 as the destination (this is the type declared in the JSONSchemaProps aforementioned). I think the problem is in this conversion handling.

I have done some tests to validate my guess:

  1. If I use a float value as the value for any of those fields in the CRD, it works (e.g. 10.5). But if a whole number in float format is used (e.g. 10.0) it does not work. Json decoding will render it as integer.
  2. I have edited method newCrdWithStatus in client_test.go with these content to test it:
func newCrdWithStatus(name string, status apiextv1beta1.CustomResourceDefinitionStatus) apiextv1beta1.CustomResourceDefinition {
	f1 := 1.0
	f2 := 10.0
	crd := apiextv1beta1.CustomResourceDefinition{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: metav1.NamespaceDefault,
		},
		Spec: apiextv1beta1.CustomResourceDefinitionSpec{
			Group: "aGroup",
			Validation: &apiextv1beta1.CustomResourceValidation{
				OpenAPIV3Schema: &apiextv1beta1.JSONSchemaProps{
					Properties: map[string]apiextv1beta1.JSONSchemaProps{
						"replicas": apiextv1beta1.JSONSchemaProps{
							Minimum: &f1,
							Maximum: &f2,
							Type:    "integer",
						},
					},
				},
			},
		},
		Status: status,
	}
	return crd
}

With this change, the test TestWaitUntilCRDEstablished fails. However, if f1 and f2 contain not whole numbers in float format (e.g. 10.2), it works.

  1. I have added the conversion from int64 to float64 in converter.go and it worked in any case with this change:
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		switch dt.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			dv.Set(sv.Convert(dt))
			return nil
		case reflect.Float32, reflect.Float64:
			dv.Set(sv.Convert(dt))
			return nil
		}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 7
  • Comments: 20 (6 by maintainers)

Commits related to this issue

Most upvoted comments

We are having users of Rook report this issue also. I don’t necessarily see it as a problem that the schema expects a *float64 for minimum and maximum. Mathematically speaking, 1 is just a special form of decimal (floating point). I would guess the problem could be solved in the code that parses/interprets the value to allow int forms of number to be read into a float value. I’m not sure where that code lives, however.

[Edit] We have also had problems reproducing the issue with K8s 1.20 and Helm 3.5.1. Could it be that this is fixed with that combination?

I’d push back on marking this as fixed. Moving to 1.20 isn’t an option for many users of Helm.

GKE doesn’t yet support 1.20, neither does EKS. Looks like it’s just(?) coming to AKS now (I can never quite tell what versions are supported without reading the entire release history). I’d posit that that’s a big chunk of the Kubernetes user base.

Also many companies and users are locked to a specific version of Kubernetes for a long time, and I would argue that it’s not a simple endeavour to choose a new version and re-deploy their whole stack.

@bacongobbler: I have confirmed that this bug still occurs in Helm 3.5.1 and appears to do so for the same reasons that @oscar-martin described in his original report. Though the CRD conversion code in Helm 3.5.1 now appears to be here, the underlying issue that Maximum and Minimum are defined as float64 fields in the JSONSchemaProps type remains in Kubernetes, as recent as v0.20.0.

Could you please re-open this bug until the issue has been fixed in Helm 3?

We were forced to use an older version of helm (and tiller) due to this.