From 2c0d4b599cdabcf18ba741825eb2d5808532c8f0 Mon Sep 17 00:00:00 2001 From: clyang82 Date: Mon, 27 May 2024 20:38:47 +0800 Subject: [PATCH] Return delete_option and update_strategy Signed-off-by: clyang82 --- README.md | 19 ++++++++++-- pkg/api/presenters/resource.go | 24 ++++++++------- pkg/api/resource_types.go | 56 +++++++++++++++++++++++++++++----- pkg/api/resource_types_test.go | 40 +++++++++++++++--------- pkg/services/validation.go | 7 +++-- test/grpc_codec.go | 4 +-- 6 files changed, 111 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 5660f9b9..2855d23b 100755 --- a/README.md +++ b/README.md @@ -175,15 +175,24 @@ ocm post /api/maestro/v1/resources << EOF } }, "update_strategy": { - "type": "Orphan" + "type": "ServerSideApply" }, "delete_option": { - "propagationPolicy": "CreateOnly" + "propagationPolicy": "Foreground" } } EOF ``` +delete_option defines the option to delete the resource. It is optional when creating a resource. The propagationPolicy of `delete_option` can be: +- `Foreground` represents that the resource should be fourground deleted. This is a default value. +- `Orphan` represents that the resource is orphaned when deleting the resource. + +update_strategy defines the strategy to update the resource. It is optional when creating a resource. The type of `update_strategy` can be: +- `ServerSideApply` means to update resource using server side apply with work-controller as the field manager. This is a default value. +- `Update` means to update resource by an update call. +- `CreateOnly` means do not update resource based on current manifest. +- `ReadOnly` means only check the existence of the resource based on the resource's metadata. #### Get your Resource @@ -194,6 +203,9 @@ ocm get /api/maestro/v1/resources { "consumer_name": "cluster1", "created_at": "2023-11-23T09:26:13.43061Z", + "delete_option": { + "propagationPolicy":"Foreground" + }, "href": "/api/maestro/v1/resources/f428e21d-71cb-47a4-8d7f-82a65d9a4048", "id": "f428e21d-71cb-47a4-8d7f-82a65d9a4048", "kind": "Resource", @@ -282,6 +294,9 @@ ocm get /api/maestro/v1/resources "SequenceID": "1744926882802962432" } }, + "update_strategy": { + "type":"ServerSideApply" + }, "updated_at": "2023-11-23T09:26:13.457419Z", "version": 1 } diff --git a/pkg/api/presenters/resource.go b/pkg/api/presenters/resource.go index 105513c1..5b27062b 100755 --- a/pkg/api/presenters/resource.go +++ b/pkg/api/presenters/resource.go @@ -32,7 +32,7 @@ func ConvertResourceManifest(manifest, deleteOption, updateStrategy map[string]i // PresentResource converts a resource from the API to the openapi representation. func PresentResource(resource *api.Resource) (*openapi.Resource, error) { - manifest, err := api.DecodeManifest(resource.Manifest) + manifest, deleteOption, updateStrategy, err := api.DecodeManifest(resource.Manifest) if err != nil { return nil, err } @@ -42,15 +42,17 @@ func PresentResource(resource *api.Resource) (*openapi.Resource, error) { } reference := PresentReference(resource.ID, resource) return &openapi.Resource{ - Id: reference.Id, - Kind: reference.Kind, - Href: reference.Href, - Name: openapi.PtrString(resource.Name), - ConsumerName: openapi.PtrString(resource.ConsumerName), - Version: openapi.PtrInt32(resource.Version), - CreatedAt: openapi.PtrTime(resource.CreatedAt), - UpdatedAt: openapi.PtrTime(resource.UpdatedAt), - Manifest: manifest, - Status: status, + Id: reference.Id, + Kind: reference.Kind, + Href: reference.Href, + Name: openapi.PtrString(resource.Name), + ConsumerName: openapi.PtrString(resource.ConsumerName), + Version: openapi.PtrInt32(resource.Version), + CreatedAt: openapi.PtrTime(resource.CreatedAt), + UpdatedAt: openapi.PtrTime(resource.UpdatedAt), + Manifest: manifest, + DeleteOption: deleteOption, + UpdateStrategy: updateStrategy, + Status: status, }, nil } diff --git a/pkg/api/resource_types.go b/pkg/api/resource_types.go index 224ec756..52c67d5d 100755 --- a/pkg/api/resource_types.go +++ b/pkg/api/resource_types.go @@ -121,7 +121,7 @@ func CloudEventToJSONMap(evt *cloudevents.Event) (datatypes.JSONMap, error) { return res, nil } -// EncodeManifest converts a resource manifest (map[string]interface{}) into a CloudEvent JSONMap representation. +// EncodeManifest converts resource manifest, deleteOption and updateStrategy (map[string]interface{}) into a CloudEvent JSONMap representation. func EncodeManifest(manifest, deleteOption, updateStrategy map[string]interface{}) (datatypes.JSONMap, error) { if len(manifest) == 0 { return nil, nil @@ -193,23 +193,65 @@ func EncodeManifest(manifest, deleteOption, updateStrategy map[string]interface{ } // DecodeManifest converts a CloudEvent JSONMap representation of a resource manifest -// into resource manifest (map[string]interface{}). -func DecodeManifest(manifest datatypes.JSONMap) (map[string]interface{}, error) { +// into resource manifest, deleteOption and updateStrategy (map[string]interface{}). +func DecodeManifest(manifest datatypes.JSONMap) (map[string]interface{}, map[string]interface{}, map[string]interface{}, error) { if len(manifest) == 0 { - return nil, nil + return nil, nil, nil, nil } evt, err := JSONMAPToCloudEvent(manifest) if err != nil { - return nil, fmt.Errorf("failed to convert resource manifest to cloudevent: %v", err) + return nil, nil, nil, fmt.Errorf("failed to convert resource manifest to cloudevent: %v", err) } eventPayload := &workpayload.Manifest{} if err := evt.DataAs(eventPayload); err != nil { - return nil, fmt.Errorf("failed to decode cloudevent payload as resource manifest: %v", err) + return nil, nil, nil, fmt.Errorf("failed to decode cloudevent payload as resource manifest: %v", err) + } + + deleteOptionObj := &map[string]interface{}{} + if eventPayload.DeleteOption != nil { + deleteOptionJsonData, err := json.Marshal(eventPayload.DeleteOption) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to marshal deleteOption to json: %v", err) + } + if err := json.Unmarshal(deleteOptionJsonData, deleteOptionObj); err != nil { + return nil, nil, nil, fmt.Errorf("failed to unmarshal deleteOption to cloudevent: %v", err) + } + } + + updateStrategyObj := &map[string]interface{}{} + if eventPayload.ConfigOption != nil && eventPayload.ConfigOption.UpdateStrategy != nil { + updateStrategyJsonData, err := json.Marshal(eventPayload.ConfigOption.UpdateStrategy) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to marshal updateStrategy to json: %v", err) + } + if err := json.Unmarshal(updateStrategyJsonData, updateStrategyObj); err != nil { + return nil, nil, nil, fmt.Errorf("failed to unmarshal updateStrategy to cloudevent: %v", err) + } + } + + return eventPayload.Manifest.Object, *deleteOptionObj, *updateStrategyObj, nil +} + +// DecodeDeleteOption converts a CloudEvent JSONMap representation of a resoure deleteOption +// into resource deleteOption (map[string]interface{}). +func DecodeDeleteOption(deleteOption datatypes.JSONMap) (map[string]interface{}, error) { + if len(deleteOption) == 0 { + return nil, nil + } + + jsonData, err := deleteOption.MarshalJSON() + if err != nil { + return nil, fmt.Errorf("failed to marshal deleteOption to json: %v", err) + } + + obj := &map[string]interface{}{} + if err := json.Unmarshal(jsonData, obj); err != nil { + return nil, fmt.Errorf("failed to unmarshal deleteOption to cloudevent: %v", err) } - return eventPayload.Manifest.Object, nil + return *obj, nil } // DecodeManifestBundle converts a CloudEvent JSONMap representation of a list of resource manifest diff --git a/pkg/api/resource_types_test.go b/pkg/api/resource_types_test.go index 4f9e8b4e..a667ad93 100644 --- a/pkg/api/resource_types_test.go +++ b/pkg/api/resource_types_test.go @@ -53,34 +53,46 @@ func TestEncodeManifest(t *testing.T) { func TestDecodeManifest(t *testing.T) { cases := []struct { - name string - input datatypes.JSONMap - expected map[string]interface{} - expectedErrorMsg string + name string + input datatypes.JSONMap + expectedManifest map[string]interface{} + expectedDeleteOption map[string]interface{} + expectedUpdateStrategy map[string]interface{} + expectedErrorMsg string }{ { - name: "empty", - input: datatypes.JSONMap{}, - expected: nil, - expectedErrorMsg: "", + name: "empty", + input: datatypes.JSONMap{}, + expectedManifest: nil, + expectedDeleteOption: nil, + expectedUpdateStrategy: nil, + expectedErrorMsg: "", }, { - name: "valid", - input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - expected: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), + name: "valid", + input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"configOption\":{\"updateStrategy\": {\"type\": \"CreateOnly\"}},\"deleteOption\": {\"propagationPolicy\": \"Orphan\"},\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + expectedManifest: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), + expectedDeleteOption: newJSONMap(t, "{\"propagationPolicy\": \"Orphan\"}"), + expectedUpdateStrategy: newJSONMap(t, "{\"type\": \"CreateOnly\"}"), }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - gotManifest, err := DecodeManifest(c.input) + gotManifest, gotDeleteOption, gotUpdateStrategy, err := DecodeManifest(c.input) if err != nil { if err.Error() != c.expectedErrorMsg { t.Errorf("expected %#v but got: %#v", c.expectedErrorMsg, err) } return } - if !equality.Semantic.DeepDerivative(c.expected, gotManifest) { - t.Errorf("expected %#v but got: %#v", c.expected, gotManifest) + if !equality.Semantic.DeepDerivative(c.expectedManifest, gotManifest) { + t.Errorf("expected %#v but got: %#v", c.expectedManifest, gotManifest) + } + if !equality.Semantic.DeepDerivative(c.expectedDeleteOption, gotDeleteOption) { + t.Errorf("expected %#v but got: %#v", c.expectedDeleteOption, gotDeleteOption) + } + if !equality.Semantic.DeepDerivative(c.expectedUpdateStrategy, gotUpdateStrategy) { + t.Errorf("expected %#v but got: %#v", c.expectedUpdateStrategy, gotUpdateStrategy) } }) } diff --git a/pkg/services/validation.go b/pkg/services/validation.go index 88e9c457..3ee6bba2 100644 --- a/pkg/services/validation.go +++ b/pkg/services/validation.go @@ -43,7 +43,8 @@ func ValidateConsumer(consumer *api.Consumer) error { func ValidateManifest(resType api.ResourceType, manifest datatypes.JSONMap) error { switch resType { case api.ResourceTypeSingle: - obj, err := api.DecodeManifest(manifest) + // TODO: validate the deleteOption and updateStrategy + obj, _, _, err := api.DecodeManifest(manifest) if err != nil { return fmt.Errorf("failed to decode manifest: %v", err) } @@ -104,11 +105,11 @@ func ValidateObject(obj datatypes.JSONMap) error { func ValidateManifestUpdate(resType api.ResourceType, new, old datatypes.JSONMap) error { switch resType { case api.ResourceTypeSingle: - newObj, err := api.DecodeManifest(new) + newObj, _, _, err := api.DecodeManifest(new) if err != nil { return fmt.Errorf("failed to decode new manifest: %v", err) } - oldObj, err := api.DecodeManifest(old) + oldObj, _, _, err := api.DecodeManifest(old) if err != nil { return fmt.Errorf("failed to decode old manifest: %v", err) } diff --git a/test/grpc_codec.go b/test/grpc_codec.go index 1a2e7a30..fd9292b4 100644 --- a/test/grpc_codec.go +++ b/test/grpc_codec.go @@ -41,7 +41,7 @@ func (c *ResourceCodec) Encode(source string, eventType types.CloudEventsType, r return &evt, nil } - manifest, err := api.DecodeManifest(resource.Manifest) + manifest, _, _, err := api.DecodeManifest(resource.Manifest) if err != nil { return nil, fmt.Errorf("failed to decode manifest: %v", err) } @@ -153,7 +153,7 @@ func (c *ResourceBundleCodec) Encode(source string, eventType types.CloudEventsT return &evt, nil } - manifest, err := api.DecodeManifest(resource.Manifest) + manifest, _, _, err := api.DecodeManifest(resource.Manifest) if err != nil { return nil, fmt.Errorf("failed to decode manifest: %v", err) }