From 03398921c2d353f315c7088a1dd98b33a4652aa0 Mon Sep 17 00:00:00 2001 From: Hasan Turken Date: Tue, 29 Aug 2023 08:08:18 +0300 Subject: [PATCH] Add support for convert from json to object/list Signed-off-by: Hasan Turken --- .../v1/composition_transforms.go | 12 ++-- .../zz_generated.composition_transforms.go | 12 ++-- ...ns.crossplane.io_compositionrevisions.yaml | 54 ++++++++++++++---- ...extensions.crossplane.io_compositions.yaml | 27 +++++++-- .../composite/composition_transforms.go | 8 +++ .../composite/composition_transforms_test.go | 56 +++++++++++++++++++ pkg/validation/internal/schema/schema.go | 6 +- 7 files changed, 148 insertions(+), 27 deletions(-) diff --git a/apis/apiextensions/v1/composition_transforms.go b/apis/apiextensions/v1/composition_transforms.go index a1dd6b65f..76e4cf020 100644 --- a/apis/apiextensions/v1/composition_transforms.go +++ b/apis/apiextensions/v1/composition_transforms.go @@ -448,12 +448,13 @@ const ( TransformIOTypeFloat64 TransformIOType = "float64" TransformIOTypeObject TransformIOType = "object" + TransformIOTypeArray TransformIOType = "array" ) // IsValid checks if the given TransformIOType is valid. func (c TransformIOType) IsValid() bool { switch c { - case TransformIOTypeString, TransformIOTypeBool, TransformIOTypeInt, TransformIOTypeInt64, TransformIOTypeFloat64, TransformIOTypeObject: + case TransformIOTypeString, TransformIOTypeBool, TransformIOTypeInt, TransformIOTypeInt64, TransformIOTypeFloat64, TransformIOTypeObject, TransformIOTypeArray: return true } return false @@ -467,12 +468,13 @@ type ConvertTransformFormat string const ( ConvertTransformFormatNone ConvertTransformFormat = "none" ConvertTransformFormatQuantity ConvertTransformFormat = "quantity" + ConvertTransformFormatJSON ConvertTransformFormat = "json" ) // IsValid returns true if the format is valid. func (c ConvertTransformFormat) IsValid() bool { switch c { - case ConvertTransformFormatNone, ConvertTransformFormatQuantity: + case ConvertTransformFormatNone, ConvertTransformFormatQuantity, ConvertTransformFormatJSON: return true } return false @@ -481,17 +483,19 @@ func (c ConvertTransformFormat) IsValid() bool { // A ConvertTransform converts the input into a new object whose type is supplied. type ConvertTransform struct { // ToType is the type of the output of this transform. - // +kubebuilder:validation:Enum=string;int;int64;bool;float64 + // +kubebuilder:validation:Enum=string;int;int64;bool;float64;object;list ToType TransformIOType `json:"toType"` // The expected input format. // // * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). // Only used during `string -> float64` conversions. + // * `json` - parses the input as a JSON string. + // Only used during `string -> object` or `string -> list` conversions. // // If this property is null, the default conversion is applied. // - // +kubebuilder:validation:Enum=none;quantity + // +kubebuilder:validation:Enum=none;quantity;json // +kubebuilder:validation:Default=none Format *ConvertTransformFormat `json:"format,omitempty"` } diff --git a/apis/apiextensions/v1beta1/zz_generated.composition_transforms.go b/apis/apiextensions/v1beta1/zz_generated.composition_transforms.go index f1da98a3b..58f1ba656 100644 --- a/apis/apiextensions/v1beta1/zz_generated.composition_transforms.go +++ b/apis/apiextensions/v1beta1/zz_generated.composition_transforms.go @@ -450,12 +450,13 @@ const ( TransformIOTypeFloat64 TransformIOType = "float64" TransformIOTypeObject TransformIOType = "object" + TransformIOTypeArray TransformIOType = "array" ) // IsValid checks if the given TransformIOType is valid. func (c TransformIOType) IsValid() bool { switch c { - case TransformIOTypeString, TransformIOTypeBool, TransformIOTypeInt, TransformIOTypeInt64, TransformIOTypeFloat64, TransformIOTypeObject: + case TransformIOTypeString, TransformIOTypeBool, TransformIOTypeInt, TransformIOTypeInt64, TransformIOTypeFloat64, TransformIOTypeObject, TransformIOTypeArray: return true } return false @@ -469,12 +470,13 @@ type ConvertTransformFormat string const ( ConvertTransformFormatNone ConvertTransformFormat = "none" ConvertTransformFormatQuantity ConvertTransformFormat = "quantity" + ConvertTransformFormatJSON ConvertTransformFormat = "json" ) // IsValid returns true if the format is valid. func (c ConvertTransformFormat) IsValid() bool { switch c { - case ConvertTransformFormatNone, ConvertTransformFormatQuantity: + case ConvertTransformFormatNone, ConvertTransformFormatQuantity, ConvertTransformFormatJSON: return true } return false @@ -483,17 +485,19 @@ func (c ConvertTransformFormat) IsValid() bool { // A ConvertTransform converts the input into a new object whose type is supplied. type ConvertTransform struct { // ToType is the type of the output of this transform. - // +kubebuilder:validation:Enum=string;int;int64;bool;float64 + // +kubebuilder:validation:Enum=string;int;int64;bool;float64;object;list ToType TransformIOType `json:"toType"` // The expected input format. // // * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). // Only used during `string -> float64` conversions. + // * `json` - parses the input as a JSON string. + // Only used during `string -> object` or `string -> list` conversions. // // If this property is null, the default conversion is applied. // - // +kubebuilder:validation:Enum=none;quantity + // +kubebuilder:validation:Enum=none;quantity;json // +kubebuilder:validation:Default=none Format *ConvertTransformFormat `json:"format,omitempty"` } diff --git a/cluster/crds/apiextensions.crossplane.io_compositionrevisions.yaml b/cluster/crds/apiextensions.crossplane.io_compositionrevisions.yaml index d904646d9..dd2c0568b 100644 --- a/cluster/crds/apiextensions.crossplane.io_compositionrevisions.yaml +++ b/cluster/crds/apiextensions.crossplane.io_compositionrevisions.yaml @@ -264,11 +264,14 @@ spec: description: "The expected input format. \n * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). Only used during `string -> float64` conversions. - \n If this property is null, the default conversion - is applied." + * `json` - parses the input as a JSON string. + Only used during `string -> object` or `string + -> list` conversions. \n If this property is + null, the default conversion is applied." enum: - none - quantity + - json type: string toType: description: ToType is the type of the output @@ -279,6 +282,8 @@ spec: - int64 - bool - float64 + - object + - list type: string required: - toType @@ -748,11 +753,14 @@ spec: description: "The expected input format. \n * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). Only used during `string -> float64` conversions. - \n If this property is null, the default conversion - is applied." + * `json` - parses the input as a JSON string. + Only used during `string -> object` or `string + -> list` conversions. \n If this property + is null, the default conversion is applied." enum: - none - quantity + - json type: string toType: description: ToType is the type of the output @@ -763,6 +771,8 @@ spec: - int64 - bool - float64 + - object + - list type: string required: - toType @@ -1163,11 +1173,14 @@ spec: description: "The expected input format. \n * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). Only used during `string -> float64` conversions. - \n If this property is null, the default conversion - is applied." + * `json` - parses the input as a JSON string. + Only used during `string -> object` or `string + -> list` conversions. \n If this property + is null, the default conversion is applied." enum: - none - quantity + - json type: string toType: description: ToType is the type of the output @@ -1178,6 +1191,8 @@ spec: - int64 - bool - float64 + - object + - list type: string required: - toType @@ -1744,11 +1759,14 @@ spec: description: "The expected input format. \n * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). Only used during `string -> float64` conversions. - \n If this property is null, the default conversion - is applied." + * `json` - parses the input as a JSON string. + Only used during `string -> object` or `string + -> list` conversions. \n If this property is + null, the default conversion is applied." enum: - none - quantity + - json type: string toType: description: ToType is the type of the output @@ -1759,6 +1777,8 @@ spec: - int64 - bool - float64 + - object + - list type: string required: - toType @@ -2228,11 +2248,14 @@ spec: description: "The expected input format. \n * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). Only used during `string -> float64` conversions. - \n If this property is null, the default conversion - is applied." + * `json` - parses the input as a JSON string. + Only used during `string -> object` or `string + -> list` conversions. \n If this property + is null, the default conversion is applied." enum: - none - quantity + - json type: string toType: description: ToType is the type of the output @@ -2243,6 +2266,8 @@ spec: - int64 - bool - float64 + - object + - list type: string required: - toType @@ -2643,11 +2668,14 @@ spec: description: "The expected input format. \n * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). Only used during `string -> float64` conversions. - \n If this property is null, the default conversion - is applied." + * `json` - parses the input as a JSON string. + Only used during `string -> object` or `string + -> list` conversions. \n If this property + is null, the default conversion is applied." enum: - none - quantity + - json type: string toType: description: ToType is the type of the output @@ -2658,6 +2686,8 @@ spec: - int64 - bool - float64 + - object + - list type: string required: - toType diff --git a/cluster/crds/apiextensions.crossplane.io_compositions.yaml b/cluster/crds/apiextensions.crossplane.io_compositions.yaml index a48921433..931b7f27c 100644 --- a/cluster/crds/apiextensions.crossplane.io_compositions.yaml +++ b/cluster/crds/apiextensions.crossplane.io_compositions.yaml @@ -261,11 +261,14 @@ spec: description: "The expected input format. \n * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). Only used during `string -> float64` conversions. - \n If this property is null, the default conversion - is applied." + * `json` - parses the input as a JSON string. + Only used during `string -> object` or `string + -> list` conversions. \n If this property is + null, the default conversion is applied." enum: - none - quantity + - json type: string toType: description: ToType is the type of the output @@ -276,6 +279,8 @@ spec: - int64 - bool - float64 + - object + - list type: string required: - toType @@ -748,11 +753,14 @@ spec: description: "The expected input format. \n * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). Only used during `string -> float64` conversions. - \n If this property is null, the default conversion - is applied." + * `json` - parses the input as a JSON string. + Only used during `string -> object` or `string + -> list` conversions. \n If this property + is null, the default conversion is applied." enum: - none - quantity + - json type: string toType: description: ToType is the type of the output @@ -763,6 +771,8 @@ spec: - int64 - bool - float64 + - object + - list type: string required: - toType @@ -1167,11 +1177,14 @@ spec: description: "The expected input format. \n * `quantity` - parses the input as a K8s [`resource.Quantity`](https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity). Only used during `string -> float64` conversions. - \n If this property is null, the default conversion - is applied." + * `json` - parses the input as a JSON string. + Only used during `string -> object` or `string + -> list` conversions. \n If this property + is null, the default conversion is applied." enum: - none - quantity + - json type: string toType: description: ToType is the type of the output @@ -1182,6 +1195,8 @@ spec: - int64 - bool - float64 + - object + - list type: string required: - toType diff --git a/internal/controller/apiextensions/composite/composition_transforms.go b/internal/controller/apiextensions/composite/composition_transforms.go index 2f49d5fa3..9e8b618b0 100644 --- a/internal/controller/apiextensions/composite/composition_transforms.go +++ b/internal/controller/apiextensions/composite/composition_transforms.go @@ -488,4 +488,12 @@ var conversions = map[conversionPair]func(any) (any, error){ {from: v1.TransformIOTypeFloat64, to: v1.TransformIOTypeBool, format: v1.ConvertTransformFormatNone}: func(i any) (any, error) { //nolint:unparam // See note above. return i.(float64) == float64(1), nil }, + {from: v1.TransformIOTypeString, to: v1.TransformIOTypeObject, format: v1.ConvertTransformFormatJSON}: func(i any) (any, error) { + o := map[string]any{} + return o, json.Unmarshal([]byte(i.(string)), &o) + }, + {from: v1.TransformIOTypeString, to: v1.TransformIOTypeArray, format: v1.ConvertTransformFormatJSON}: func(i any) (any, error) { + var o []any + return o, json.Unmarshal([]byte(i.(string)), &o) + }, } diff --git a/internal/controller/apiextensions/composite/composition_transforms_test.go b/internal/controller/apiextensions/composite/composition_transforms_test.go index 720f3d999..d6dbfda6e 100644 --- a/internal/controller/apiextensions/composite/composition_transforms_test.go +++ b/internal/controller/apiextensions/composite/composition_transforms_test.go @@ -1111,6 +1111,30 @@ func TestConvertResolve(t *testing.T) { o: int64(1), }, }, + "StringToObject": { + args: args{ + i: "{\"foo\":\"bar\"}", + to: v1.TransformIOTypeObject, + format: (*v1.ConvertTransformFormat)(pointer.String(string(v1.ConvertTransformFormatJSON))), + }, + want: want{ + o: map[string]any{ + "foo": "bar", + }, + }, + }, + "StringToList": { + args: args{ + i: "[\"foo\", \"bar\", \"baz\"]", + to: v1.TransformIOTypeArray, + format: (*v1.ConvertTransformFormat)(pointer.String(string(v1.ConvertTransformFormatJSON))), + }, + want: want{ + o: []any{ + "foo", "bar", "baz", + }, + }, + }, "InputTypeNotSupported": { args: args{ i: []int{64}, @@ -1236,6 +1260,38 @@ func TestConvertTransformGetConversionFunc(t *testing.T) { from: v1.TransformIOTypeBool, }, }, + "JSONStringToObject": { + reason: "JSON string to Object should be valid", + args: args{ + ct: &v1.ConvertTransform{ + ToType: v1.TransformIOTypeObject, + Format: &[]v1.ConvertTransformFormat{v1.ConvertTransformFormatJSON}[0], + }, + from: v1.TransformIOTypeString, + }, + }, + "JSONStringToArray": { + reason: "JSON string to Array should be valid", + args: args{ + ct: &v1.ConvertTransform{ + ToType: v1.TransformIOTypeArray, + Format: &[]v1.ConvertTransformFormat{v1.ConvertTransformFormatJSON}[0], + }, + from: v1.TransformIOTypeString, + }, + }, + "StringToObjectMissingFormat": { + reason: "String to Object without format should be invalid", + args: args{ + ct: &v1.ConvertTransform{ + ToType: v1.TransformIOTypeObject, + }, + from: v1.TransformIOTypeString, + }, + want: want{ + err: fmt.Errorf("conversion from string to object is not supported with format none"), + }, + }, "StringToIntInvalidFormat": { reason: "String to Int with invalid format should be invalid", args: args{ diff --git a/pkg/validation/internal/schema/schema.go b/pkg/validation/internal/schema/schema.go index dfd434df8..81ded85e1 100644 --- a/pkg/validation/internal/schema/schema.go +++ b/pkg/validation/internal/schema/schema.go @@ -68,6 +68,8 @@ func FromTransformIOType(c v1.TransformIOType) KnownJSONType { return KnownJSONTypeNumber case v1.TransformIOTypeObject: return KnownJSONTypeObject + case v1.TransformIOTypeArray: + return KnownJSONTypeArray } // should never happen return "" @@ -86,7 +88,9 @@ func FromKnownJSONType(t KnownJSONType) (v1.TransformIOType, error) { return v1.TransformIOTypeFloat64, nil case KnownJSONTypeObject: return v1.TransformIOTypeObject, nil - case KnownJSONTypeArray, KnownJSONTypeNull: + case KnownJSONTypeArray: + return v1.TransformIOTypeObject, nil + case KnownJSONTypeNull: return "", errors.Errorf(errFmtUnsupportedJSONType, t) default: return "", errors.Errorf(errFmtUnknownJSONType, t)