diff --git a/README.md b/README.md index 6d65c333..c1c26d93 100755 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ reducing the need for direct access to clusters. ### Make a build, run postgres and mqtt broker -```sh +```shell # 1. build the project @@ -73,7 +73,7 @@ The initial migration will create the base data model as well as providing a way ```shell # Run migrations -./maestro migration +$ ./maestro migration # Verify they ran in the database $ make db/login @@ -98,9 +98,9 @@ maestro=# \dt ```shell -make test -make test-integration -make e2e-test +$ make test +$ make test-integration +$ make e2e-test ``` @@ -108,7 +108,7 @@ make e2e-test ```shell -make run +$ make run ``` @@ -116,13 +116,13 @@ To verify that the server is working use the curl command: ```shell -curl http://localhost:8000/api/maestro/v1/resources | jq +$ curl http://localhost:8000/api/maestro/v1/consumers | jq ``` That should return a 401 response like this, because it needs authentication: -``` +```json { "kind": "Error", "id": "401", @@ -137,19 +137,22 @@ Authentication in the default configuration is done through the RedHat SSO, so y To authenticate, use the ocm tool against your local service. The ocm tool is available on https://console.redhat.com/openshift/downloads #### Login to your local service -``` + +```shell + ocm login --token=${OCM_ACCESS_TOKEN} --url=http://localhost:8000 ``` -#### Get a new Resource -This will be empty if no Resource is ever created +#### List the resource bundle -``` -ocm get /api/maestro/v1/resources +This will be empty if no resource bundle is ever created + +```shell +$ ocm get /api/maestro/v1/resource-bundles { "items": [], - "kind": "ResourceList", + "kind": "ResourceBundleList", "page": 1, "size": 0, "total": 0 @@ -158,83 +161,22 @@ ocm get /api/maestro/v1/resources #### Create a consumer: -``` -ocm post /api/maestro/v1/consumers << EOF -{ - "name": "cluster1" -} -EOF -``` - -#### Post a new Resource - ```shell - -ocm post /api/maestro/v1/resources << EOF +$ ocm post /api/maestro/v1/consumers << EOF { - "consumer_name": "cluster1", - "version": 1, - "manifest": { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": "nginx", - "namespace": "default" - }, - "spec": { - "replicas": 1, - "selector": { - "matchLabels": { - "app": "nginx" - } - }, - "template": { - "metadata": { - "labels": { - "app": "nginx" - } - }, - "spec": { - "containers": [ - { - "image": "nginxinc/nginx-unprivileged", - "name": "nginx" - } - ] - } - } - } - }, - "group_resource": { - "group": "apps", - "resource": "deployments" - }, - "update_strategy": { - "type": "ServerSideApply" - }, - "delete_option": { - "propagationPolicy": "Foreground" - } + "name": "cluster1" } EOF - ``` -group_resource specifies the group and resource of the creating Kubernetes resource, forming part of its GVR (group, version, resource) definition. For example, when creating a deployment resource with GVR `apps/v1/deployments`, the group is `apps`, and the resource is `deployments`. -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. +#### Create a resource bundle -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. +You can create a resource bundle with manifestwork client based on grpc, check the [document](./examples/manifestworkclient/client/README.md) for more details. -#### Get your Resource +#### List the resource bundle ```shell -ocm get /api/maestro/v1/resources +ocm get /api/maestro/v1/resource-bundles { "items": [ { @@ -243,146 +185,8 @@ ocm get /api/maestro/v1/resources "delete_option": { "propagationPolicy":"Foreground" }, - "href": "/api/maestro/v1/resources/f428e21d-71cb-47a4-8d7f-82a65d9a4048", + "href": "/api/maestro/v1/resource-bundles/f428e21d-71cb-47a4-8d7f-82a65d9a4048", "id": "f428e21d-71cb-47a4-8d7f-82a65d9a4048", - "kind": "Resource", - "updated_at": "2023-11-23T09:26:13.457419Z", - "version": 1 - "manifest": { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": "nginx", - "namespace": "default" - }, - "spec": { - "replicas": 1, - "selector": { - "matchLabels": { - "app": "nginx" - } - }, - "template": { - "metadata": { - "labels": { - "app": "nginx" - } - }, - "spec": { - "containers": [ - { - "image": "nginxinc/nginx-unprivileged", - "name": "nginx" - } - ] - } - } - } - }, - "group_resource": { - "group": "apps", - "resource": "deployments" - }, - "update_strategy": { - "type":"ServerSideApply" - }, - "status": { - "ContentStatus": { - "availableReplicas": 1, - "conditions": [ - { - "lastTransitionTime": "2023-11-23T07:05:50Z", - "lastUpdateTime": "2023-11-23T07:05:50Z", - "message": "Deployment has minimum availability.", - "reason": "MinimumReplicasAvailable", - "status": "True", - "type": "Available" - }, - { - "lastTransitionTime": "2023-11-23T07:05:47Z", - "lastUpdateTime": "2023-11-23T07:05:50Z", - "message": "ReplicaSet \"nginx-5d6b548959\" has successfully progressed.", - "reason": "NewReplicaSetAvailable", - "status": "True", - "type": "Progressing" - } - ], - "observedGeneration": 1, - "readyReplicas": 1, - "replicas": 1, - "updatedReplicas": 1 - }, - "ReconcileStatus": { - "Conditions": [ - { - "lastTransitionTime": "2023-11-23T09:26:13Z", - "message": "Apply manifest complete", - "reason": "AppliedManifestComplete", - "status": "True", - "type": "Applied" - }, - { - "lastTransitionTime": "2023-11-23T09:26:13Z", - "message": "Resource is available", - "reason": "ResourceAvailable", - "status": "True", - "type": "Available" - }, - { - "lastTransitionTime": "2023-11-23T09:26:13Z", - "message": "", - "reason": "StatusFeedbackSynced", - "status": "True", - "type": "StatusFeedbackSynced" - } - ], - "ObservedVersion": 1, - "SequenceID": "1744926882802962432" - } - } - } - ], - "kind": "", - "page": 1, - "size": 1, - "total": 1 -} -``` - -#### Create/Get resource bundle with multiple resources - -1. Enable gRPC server by passing `--enable-grpc-server=true` to the maestro server start command, for example: - -```shell -$ oc -n maestro patch deploy/maestro --type=json -p='[{"op": "add", "path": "/spec/template/spec/containers/0/command/-", "value": "--enable-grpc-server=true"}]' -``` - -2. Port-forward the gRPC service to your local machine, for example: - -```shell -$ oc -n maestro port-forward svc/maestro-grpc 8090 & -``` - -3. Create a resource bundle with multiple resources using the gRPC client, for example: - -```shell -go run ./examples/grpc/grpcclient.go -cloudevents_json_file ./examples/grpc/cloudevent-bundle.json -grpc_server localhost:8090 -``` - -4. Get the resource bundle with multiple resources, for example: - -```shell -ocm get /api/maestro/v1/resource-bundles -{ - "items": [ - { - "consumer_name": "cluster1", - "created_at": "2024-05-30T05:03:08.493083Z", - "delete_option": { - "propagationPolicy": "Foreground" - }, - "href": "/api/maestro/v1/resource-bundles/68ebf474-6709-48bb-b760-386181268060", - "id": "68ebf474-6709-48bb-b760-386181268060", "kind": "ResourceBundle", "manifest_configs": [ { @@ -399,7 +203,7 @@ ocm get /api/maestro/v1/resource-bundles ], "resourceIdentifier": { "group": "apps", - "name": "web", + "name": "nginx", "namespace": "default", "resource": "deployments" }, @@ -408,61 +212,58 @@ ocm get /api/maestro/v1/resource-bundles } } ], - "manifests": [ - { - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": "web", - "namespace": "default" - } + "manifest": { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "nginx", + "namespace": "default" }, - { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": "web", - "namespace": "default" + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "nginx" + } }, - "spec": { - "replicas": 1, - "selector": { - "matchLabels": { - "app": "web" + "template": { + "metadata": { + "labels": { + "app": "nginx" } }, - "template": { - "metadata": { - "labels": { - "app": "web" + "spec": { + "containers": [ + { + "image": "nginxinc/nginx-unprivileged", + "name": "nginx" } - }, - "spec": { - "containers": [ - { - "image": "nginxinc/nginx-unprivileged", - "name": "nginx" - } - ] - } + ] } } } - ], - "name": "68ebf474-6709-48bb-b760-386181268060", + }, + "metadata": { + "creationTimestamp": "2023-11-23T09:26:13.43061Z", + "name": "nginx-work", + "namespace": "cluster1", + "resourceVersion": "0", + "uid": "f428e21d-71cb-47a4-8d7f-82a65d9a4048" + }, + "name": "f428e21d-71cb-47a4-8d7f-82a65d9a4048", "status": { "ObservedVersion": 1, - "SequenceID": "1796044690592632832", + "SequenceID": "1892524904994050048", "conditions": [ { - "lastTransitionTime": "2024-05-30T05:03:08Z", + "lastTransitionTime": "2023-11-23T09:26:13Z", "message": "Apply manifest work complete", "reason": "AppliedManifestWorkComplete", "status": "True", "type": "Applied" }, { - "lastTransitionTime": "2024-05-30T05:03:08Z", + "lastTransitionTime": "2023-11-23T09:26:13Z", "message": "All resources are available", "reason": "ResourcesAvailable", "status": "True", @@ -473,56 +274,21 @@ ocm get /api/maestro/v1/resource-bundles { "conditions": [ { - "lastTransitionTime": "2024-05-30T05:03:08Z", + "lastTransitionTime": "2023-11-23T09:26:13Z", "message": "Apply manifest complete", "reason": "AppliedManifestComplete", "status": "True", "type": "Applied" }, { - "lastTransitionTime": "2024-05-30T05:03:08Z", + "lastTransitionTime": "2023-11-23T09:26:13Z", "message": "Resource is available", "reason": "ResourceAvailable", "status": "True", "type": "Available" }, { - "lastTransitionTime": "2024-05-30T05:03:08Z", - "message": "", - "reason": "NoStatusFeedbackSynced", - "status": "True", - "type": "StatusFeedbackSynced" - } - ], - "resourceMeta": { - "group": "", - "kind": "ConfigMap", - "name": "web", - "namespace": "default", - "ordinal": 0, - "resource": "configmaps", - "version": "v1" - }, - "statusFeedback": {} - }, - { - "conditions": [ - { - "lastTransitionTime": "2024-05-30T05:03:08Z", - "message": "Apply manifest complete", - "reason": "AppliedManifestComplete", - "status": "True", - "type": "Applied" - }, - { - "lastTransitionTime": "2024-05-30T05:03:08Z", - "message": "Resource is available", - "reason": "ResourceAvailable", - "status": "True", - "type": "Available" - }, - { - "lastTransitionTime": "2024-05-30T05:03:08Z", + "lastTransitionTime": "2023-11-23T09:26:13Z", "message": "", "reason": "StatusFeedbackSynced", "status": "True", @@ -532,9 +298,9 @@ ocm get /api/maestro/v1/resource-bundles "resourceMeta": { "group": "apps", "kind": "Deployment", - "name": "web", + "name": "nginx", "namespace": "default", - "ordinal": 1, + "ordinal": 0, "resource": "deployments", "version": "v1" }, @@ -542,7 +308,7 @@ ocm get /api/maestro/v1/resource-bundles "values": [ { "fieldValue": { - "jsonRaw": "{\"availableReplicas\":1,\"conditions\":[{\"lastTransitionTime\":\"2024-05-30T05:03:13Z\",\"lastUpdateTime\":\"2024-05-30T05:03:13Z\",\"message\":\"Deployment has minimum availability.\",\"reason\":\"MinimumReplicasAvailable\",\"status\":\"True\",\"type\":\"Available\"},{\"lastTransitionTime\":\"2024-05-30T05:03:08Z\",\"lastUpdateTime\":\"2024-05-30T05:03:13Z\",\"message\":\"ReplicaSet \\\"web-dcffc4f85\\\" has successfully progressed.\",\"reason\":\"NewReplicaSetAvailable\",\"status\":\"True\",\"type\":\"Progressing\"}],\"observedGeneration\":1,\"readyReplicas\":1,\"replicas\":1,\"updatedReplicas\":1}", + "jsonRaw": "{\"availableReplicas\":1,\"conditions\":[{\"lastTransitionTime\":\"2023-11-23T09:26:13Z\",\"lastUpdateTime\":\"2023-11-23T09:26:13Z\",\"message\":\"Deployment has minimum availability.\",\"reason\":\"MinimumReplicasAvailable\",\"status\":\"True\",\"type\":\"Available\"},{\"lastTransitionTime\":\"2023-11-23T09:26:13Z\",\"lastUpdateTime\":\"2023-11-23T09:26:13Z\",\"message\":\"ReplicaSet \\\"nginx-5b4fb7d77b\\\" has successfully progressed.\",\"reason\":\"NewReplicaSetAvailable\",\"status\":\"True\",\"type\":\"Progressing\"}],\"observedGeneration\":1,\"readyReplicas\":1,\"replicas\":1,\"updatedReplicas\":1}", "type": "JsonRaw" }, "name": "status" @@ -552,7 +318,7 @@ ocm get /api/maestro/v1/resource-bundles } ] }, - "updated_at": "2024-05-30T05:03:17.796496Z", + "updated_at": "2023-11-23T09:26:13.457419Z", "version": 1 } ], @@ -602,7 +368,7 @@ We will push the image to your OpenShift cluster default registry and then deplo ```shell $ make deploy -$ oc get pod -n maestro-root +$ oc get pod -n maestro NAME READY STATUS RESTARTS AGE maestro-85c847764-4xdt6 1/1 Running 0 62s maestro-db-1-deploy 0/1 Completed 0 62s @@ -642,56 +408,8 @@ NAME READY STATUS RESTARTS AGE maestro-agent-5dc9f5b4bf-8jcvq 1/1 Running 0 13s ``` -Create a resource: -```shell -$ ocm post /api/maestro/v1/resources << EOF -{ - "consumer_name": "cluster1", - "version": 1, - "manifest": { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": "nginx", - "namespace": "default" - }, - "spec": { - "replicas": 1, - "selector": { - "matchLabels": { - "app": "nginx" - } - }, - "template": { - "metadata": { - "labels": { - "app": "nginx" - } - }, - "spec": { - "containers": [ - { - "image": "nginxinc/nginx-unprivileged", - "name": "nginx" - } - ] - } - } - } - }, - "group_resource": { - "group": "apps", - "resource": "deployments" - }, -} -EOF -``` -You should be able to see the pod is created in default namespace. -```shell -$ oc get pod -n default -NAME READY STATUS RESTARTS AGE -nginx-5d6b548959-829c7 1/1 Running 0 70s -``` +Now you can create a resource bundle with manifestwork client based on grpc, check the [document](./examples/manifestworkclient/client/README.md) for more details. + ## Make a new Kind 1. Add to openapi.yaml diff --git a/cmd/maestro/server/grpc_server.go b/cmd/maestro/server/grpc_server.go index 88a3b0da..6b66531b 100644 --- a/cmd/maestro/server/grpc_server.go +++ b/cmd/maestro/server/grpc_server.go @@ -318,8 +318,6 @@ func decodeResourceSpec(evt *ce.Event) (*api.Resource, error) { return nil, fmt.Errorf("failed to convert cloudevent to resource payload: %v", err) } resource.Payload = payload - // set the resource type to bundle from grpc source - resource.Type = api.ResourceTypeBundle return resource, nil } @@ -337,6 +335,7 @@ func encodeResourceStatus(resource *api.Resource) (*ce.Event, error) { return nil, err } + // fill the resource status with resource payload if len(resource.Payload) > 0 { specEvt, err := api.JSONMAPToCloudEvent(resource.Payload) if err != nil { diff --git a/cmd/maestro/server/routes.go b/cmd/maestro/server/routes.go index 8cbaad36..543ed8b4 100755 --- a/cmd/maestro/server/routes.go +++ b/cmd/maestro/server/routes.go @@ -22,7 +22,7 @@ func (s *apiServer) routes() *mux.Router { check(err, "Can't load OpenAPI specification") } - resourceHandler := handlers.NewResourceHandler(services.Resources(), services.Generic()) + resourceBundleHandler := handlers.NewResourceBundleHandler(services.Resources(), services.Generic()) consumerHandler := handlers.NewConsumerHandler(services.Consumers(), services.Resources(), services.Generic()) errorsHandler := handlers.NewErrorsHandler() @@ -71,20 +71,11 @@ func (s *apiServer) routes() *mux.Router { apiV1ErrorsRouter.HandleFunc("", errorsHandler.List).Methods(http.MethodGet) apiV1ErrorsRouter.HandleFunc("/{id}", errorsHandler.Get).Methods(http.MethodGet) - // /api/maestro/v1/resources - apiV1ResourceRouter := apiV1Router.PathPrefix("/resources").Subrouter() - apiV1ResourceRouter.HandleFunc("", resourceHandler.List).Methods(http.MethodGet) - apiV1ResourceRouter.HandleFunc("/{id}", resourceHandler.Get).Methods(http.MethodGet) - apiV1ResourceRouter.HandleFunc("", resourceHandler.Create).Methods(http.MethodPost) - apiV1ResourceRouter.HandleFunc("/{id}", resourceHandler.Patch).Methods(http.MethodPatch) - apiV1ResourceRouter.HandleFunc("/{id}", resourceHandler.Delete).Methods(http.MethodDelete) - apiV1ResourceRouter.Use(authMiddleware.AuthenticateAccountJWT) - apiV1ResourceRouter.Use(authzMiddleware.AuthorizeApi) - // /api/maestro/v1/resource-bundles apiV1ResourceBundleRouter := apiV1Router.PathPrefix("/resource-bundles").Subrouter() - apiV1ResourceBundleRouter.HandleFunc("", resourceHandler.ListBundle).Methods(http.MethodGet) - apiV1ResourceBundleRouter.HandleFunc("/{id}", resourceHandler.GetBundle).Methods(http.MethodGet) + apiV1ResourceBundleRouter.HandleFunc("", resourceBundleHandler.List).Methods(http.MethodGet) + apiV1ResourceBundleRouter.HandleFunc("/{id}", resourceBundleHandler.Get).Methods(http.MethodGet) + apiV1ResourceBundleRouter.HandleFunc("/{id}", resourceBundleHandler.Delete).Methods(http.MethodDelete) apiV1ResourceBundleRouter.Use(authMiddleware.AuthenticateAccountJWT) apiV1ResourceBundleRouter.Use(authzMiddleware.AuthorizeApi) diff --git a/data/generated/openapi/openapi.go b/data/generated/openapi/openapi.go index 07dce32c..23943e9a 100755 --- a/data/generated/openapi/openapi.go +++ b/data/generated/openapi/openapi.go @@ -77,7 +77,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _openapiYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5c\x5f\x8f\xdb\xb8\x11\x7f\xdf\x4f\x31\x40\x5b\x38\x39\xec\xda\x4e\xef\x0a\xb4\x46\x72\x40\x72\xbd\x14\x77\xc8\x25\x69\x36\x69\x1f\x8a\xc2\x4b\x93\x23\x8b\x89\x44\x2a\x24\xb5\x59\xa7\xed\x77\x2f\x48\xea\xbf\x25\xad\xec\xf3\xc6\xca\x9e\xf3\x92\x35\x35\x33\x9c\x21\x67\x7e\x1a\x0e\xc7\x96\x09\x0a\x92\xf0\x05\x7c\x3b\x9d\x4f\xe7\x67\x5c\x04\x72\x71\x06\x60\xb8\x89\x70\x01\x31\x41\x6d\x94\x84\x4b\x54\xd7\x9c\x22\x3c\x7d\xfd\xd3\x19\x00\x43\x4d\x15\x4f\x0c\x97\xa2\x8b\xe4\x1a\x95\x76\x8f\xe7\xd3\xf9\xf4\xd1\x99\x46\x65\x47\xac\xe4\x0b\x48\x55\xb4\x80\xd0\x98\x64\x31\x9b\x45\x92\x92\x28\x94\xda\x2c\xfe\x3c\x9f\xcf\xcf\x00\x1a\xd2\x69\xaa\x14\x0a\x03\x4c\xc6\x84\x8b\x3a\xbb\x5e\xcc\x66\x24\xe1\x53\x6b\x82\x0e\x79\x60\xa6\x54\xc6\xdb\x22\x7e\x21\x5c\xc0\x83\x44\x49\x96\x52\x3b\xf2\x10\xbc\x36\xed\xc2\xb4\x21\x6b\xbc\x4d\xe4\xa5\x21\x6b\x2e\xd6\xb9\xa0\x84\x98\xd0\xd9\x66\x25\xcc\xb2\x05\x99\x5d\x3f\x9a\x29\xd4\x32\x55\x14\xdd\x43\x80\x35\x1a\xff\x07\x80\x4e\xe3\x98\xa8\xcd\x02\xde\xa0\x49\x95\xd0\x40\x20\xe2\xda\x80\x0c\xa0\x60\xca\x49\x91\xa6\x8a\x9b\x4d\xce\x6a\xd5\x7e\x86\x44\xa1\x5a\xc0\xbf\xfe\x9d\x0d\x2a\xd4\x89\x14\x3a\x9f\xc9\xfe\x9b\xfc\x71\x3e\x9f\x94\x1f\x1b\x26\x3c\x85\x9f\x2f\x5f\xbd\x04\xa2\x14\xd9\x54\x67\x05\xb9\x7a\x8f\xd4\xe8\x0a\x1f\x95\xc2\xa0\x30\x55\x51\x00\x24\x49\x22\x4e\x89\x15\x36\x7b\xaf\xa5\xa8\x3f\x05\xd0\x34\xc4\x98\x34\x47\x01\x7e\xaf\x30\x58\xc0\xe4\x77\x33\x2a\xe3\x44\x0a\x14\x46\xcf\x3c\xad\x9e\xbd\xc9\x74\x78\xc1\xb5\x99\x94\x76\x7c\x37\x7f\xd4\x63\x47\x6a\x42\x30\xf2\x03\x0a\xe0\x1a\xb8\xb8\x26\x11\x67\xc7\x50\xfe\x47\xa5\xa4\xaa\x69\xfd\x6d\xb7\xd6\xef\x04\x49\x4d\x28\x15\xff\x8c\x0c\x8c\x84\x04\x55\x20\x55\x0c\x32\x41\xe5\xd4\x1a\x83\x05\x7f\xea\xf3\x9f\x77\x02\x6f\x12\xa4\x06\x19\xa0\xe5\x03\x49\x5d\xac\x1e\x7f\xed\x13\xa2\x48\x8c\x26\x83\x1b\x70\xf1\xd2\xc6\x5c\xd2\xcd\x12\xb2\xc6\xc9\x50\x62\xcd\x3f\xef\x40\x8c\x44\xd1\x70\x30\xb9\x54\x0c\xd5\xb3\xcd\x60\xfa\x80\x63\xc4\xb4\x27\x4f\x2c\x8a\x36\xe1\xe5\x07\x85\xc4\x20\x10\x10\xf8\xa9\x88\xf1\xdd\x80\xe5\x63\x8a\xda\x3c\x93\xac\x42\x57\xf3\x84\x3c\x6a\x81\x11\x43\x0a\x12\xcb\xc7\x15\xb2\x05\x18\x95\xe2\x59\x8f\x4b\xf4\x3b\x44\xbb\x3b\x0c\x41\x91\x49\x2f\x34\xf6\x40\x8a\x5f\xb3\xa3\x38\x72\x53\x77\x87\x23\x3d\x51\xf8\x0f\x8b\x76\x4e\x05\x1f\x85\x7a\x3c\x61\x78\x02\xee\x23\x5a\xf0\x97\x6e\x0b\x8a\x70\x25\x91\x42\xc2\x36\x80\x37\x5c\x1f\xe7\x7d\xbf\xd3\x0b\xe7\xa9\x80\xb4\xeb\x9d\x03\xd4\x86\xac\xcd\xc8\x4c\x88\x4d\x98\x3b\x8e\x49\x9d\xa9\xe0\xec\x3f\x9c\xfd\xaf\x3b\x1f\xfc\x1b\x1a\x20\xa2\x4c\xc7\x56\x1b\x28\xc2\xe2\x6e\x32\xc1\xc2\x21\x02\x99\x0a\x56\x9b\xf0\x8b\x2e\x5d\x2b\xf6\x9d\x00\xe4\x38\x16\x7c\xd7\x6d\xc1\x4b\x59\x7a\xe7\x27\x6e\x42\xd0\x09\x52\x1e\x70\x64\xc0\xd9\xd7\x82\x26\x63\x4d\x5f\x13\x62\x68\xb8\x05\x0a\xef\x12\xe6\xb2\x38\x71\x47\x29\x9c\x97\xcf\xca\x7d\x1d\x59\x2a\xf7\xda\xae\xca\x1b\x6f\x46\x7f\x5a\x37\x04\xe7\xd2\xcc\x5a\x9d\x52\x8a\x5a\x07\x69\x14\x6d\x46\x03\x78\xa7\x64\xef\x0b\x6b\x7d\xc2\xea\x51\x18\x71\x0f\x33\xd6\xad\x77\x8c\x03\x1e\x9b\xa5\x8e\x22\x43\xb5\xda\x46\x68\x70\xeb\x6d\xf3\x57\x37\x0c\x64\xcf\x97\x4d\x1b\x2c\xf7\xb8\x68\x59\x3e\x70\xd3\x76\xc0\xf2\x09\x19\xbf\xbc\xd6\x27\x64\x1c\x81\x11\xbb\x21\x8c\x8b\xa1\x11\x21\x4c\xb3\x16\x7b\x6b\x41\x93\xb3\xbe\xc3\xf3\xc5\x2a\x15\x2c\xda\xef\x3a\x05\x32\xde\x3b\x3d\x4b\x77\xde\xaa\xf8\xc9\xc7\x70\xb9\xf2\xcc\x69\x72\xba\x62\x19\x05\x44\x7d\x95\x67\xd4\xdf\xea\x15\xcb\x6d\xa8\xb4\x6b\x65\xcf\x43\xc2\x17\x2c\xf0\x65\x33\x8e\xa4\xce\xe7\x81\xe8\x04\x42\x23\xb0\x60\x60\x9e\x94\xf9\xcf\xfd\x49\x97\xbe\x66\x40\x6d\xcf\x94\xa8\x14\x3a\x8d\x0b\x39\xc3\x52\xa4\x82\xe9\x8b\xe6\x46\xf9\xac\xc7\x4c\x8a\x7e\xc8\x74\x38\xa5\x43\xa3\x40\xa2\x7b\x13\xbd\x3b\x26\x44\x3b\xa6\x44\x3b\x27\x45\xbb\xa7\x45\x07\xef\x3d\xc9\xa3\x7d\x37\x88\xb9\xed\xe2\x22\x8f\xdf\xb1\x5c\x58\xe4\xfa\x7c\x8d\xbd\x27\x4d\xdd\x4f\x45\xb7\x13\x84\xef\x63\x41\x4f\x25\xbf\x08\xd7\xaf\xac\x92\x3f\xbc\xf7\xa4\x01\x73\xc7\x31\xa9\x33\x29\x1c\x76\x42\x2d\x12\xb3\xbb\x3f\x9a\x16\x0e\x71\xe4\x33\x69\x2b\xf6\x9d\x00\x64\x8c\xa7\xd1\xc2\x3b\x4f\xc7\xd0\x83\x17\xeb\xfb\x7b\x4f\xee\x26\x85\xcb\x7b\x4f\xe8\x48\x53\xb9\x83\xf4\x9e\x14\x38\x37\x96\xde\x93\x53\xb2\x37\x06\xad\x4f\x58\x3d\x0a\x23\xee\x61\xc6\xda\xdd\x7b\x32\x8a\x0c\xf5\xf6\xde\x93\xfd\x5e\x36\x3b\xf6\x9e\x94\xe5\x83\x53\xef\xc9\x09\x19\x0f\x6b\xc1\x3d\x40\xc6\x3d\x7b\x4f\x46\x82\x30\x7b\xde\xa9\x94\x4f\x2c\x5b\x8e\x3b\x97\x56\x7e\x0e\x2c\x19\xf0\x64\x52\xcd\x26\x41\xff\x1d\xe2\xb3\x8a\xde\xb8\x80\x95\x23\xcb\x06\xfd\x87\xe7\x52\xc5\xc4\x2c\xe0\xe7\x7f\xbe\x3d\xcb\x0d\xcc\x84\xbe\x72\xb7\x20\x6f\x30\x40\x85\x82\x62\x5d\xba\xbf\x22\xc9\x86\x12\x65\x5d\xdd\xf0\x2a\xce\x71\x56\x5d\x27\xcf\xa4\x8d\xe2\x62\x5d\x0c\x7f\xe0\xe2\x76\xa2\xd0\x2e\x50\x1f\xd1\x0b\x5e\x56\x7a\x07\xea\x36\x68\xe2\x84\xac\x71\x9b\x88\x0b\x83\xeb\x8a\x27\x69\xfe\x79\x00\x95\x91\x86\x44\xb7\x91\x15\x27\x8b\xca\x1b\xc5\x6a\x5a\xf9\x68\x75\xaa\x7c\xb4\x93\x57\x3e\xba\x59\x2a\x9f\xb9\xc1\xd8\x87\xad\x73\xc2\x5c\x2e\x89\xa2\x57\x41\xbf\x07\xe6\xce\xdb\x70\x81\xb2\x45\xa1\x65\xa1\xdb\x97\xda\x46\x1a\xc3\x7a\xc8\xb4\x2e\xb7\xb5\x9f\x6c\xc5\x5c\x07\x69\x81\xac\xcb\xba\x9b\xb5\x30\x38\xd3\xab\x3e\xb2\x83\xf9\xd5\x4b\xb8\x9d\x6c\x76\x2b\xdf\xa6\x98\xbb\x6b\xac\x8d\xb7\x90\x0e\x06\x94\xbc\x71\xe1\x48\x3b\x2b\x48\x3c\x6c\x67\x73\xfc\x5d\x0e\xe6\xc8\x7f\xad\xa1\x85\xb6\x19\x5b\xe0\xeb\x9d\xc8\x96\xc4\x0c\x92\x0d\x10\x64\xa0\x67\x4f\xbe\x17\x86\xc7\xd5\xa6\xc4\xec\x3c\x7c\x18\x61\x59\x16\x77\x18\x61\x31\x11\x3c\x40\xdd\x2a\xaa\xb1\x5f\x00\x6b\x25\xd3\x64\xa9\x1a\x0e\xd2\xcb\xe2\x95\x5d\x4a\xff\x3a\x1d\xc2\xe1\xd7\x6a\xa9\x8d\x22\x06\xd7\x9b\x41\x3c\xda\x10\x93\xb6\xc6\x46\x85\xb4\xfa\xbb\x0b\xf7\x25\x6e\xeb\x5f\xae\x69\xfb\x22\xd1\x8e\x6f\xb1\x96\x18\x69\x8f\x90\x36\xc7\x69\x5d\x94\x4e\x0f\x68\xa5\xee\xd9\xfd\xce\x0d\x2d\x7b\x3d\xef\xdb\xb6\x56\x9b\xc7\xea\x63\x27\x74\xfe\x4d\xa0\x33\x1a\xc2\x88\x21\x83\x40\x30\x8f\xc8\x5f\xe3\x94\x07\x43\xf1\x5c\x99\x25\x95\x22\xe0\xeb\x3b\xd0\x69\x10\xe6\xe7\xa5\x8f\xd6\x70\xd9\x33\x60\x3a\x43\xa6\x2b\x68\xda\xc2\xa6\xc7\x21\x22\xb2\xc2\x68\xe8\x2a\x38\xa3\x18\xe3\x76\x63\x48\xf4\xba\x63\xfe\xde\xf9\xba\x62\xa9\x87\xa5\xdf\x6b\xbb\x23\x6a\x0f\x91\xd5\xde\xb5\xbd\x76\xb1\xde\xf4\xb6\xf3\xd6\xf5\xb8\xe4\xb6\xff\x76\x90\xef\x72\x49\xd1\x76\x21\xb3\xe3\x3b\x7c\xdb\x81\x3a\x6c\xbe\xdd\x71\x1a\xdb\xd5\x2c\x36\x94\x07\x25\xe7\xe1\xe5\xb5\x36\x17\x0b\x48\x88\x09\xb3\x8f\xb5\x92\xca\xdb\x10\x81\x33\xff\xbd\x11\x2a\x55\xce\xd2\x7a\x07\xd6\x2c\x8e\x6c\xb9\x4f\xf5\x40\xed\x75\xa8\x1c\x67\xad\x16\x1f\x53\x54\x9b\x36\x35\x5e\x93\x35\x82\x48\xe3\x15\xaa\x52\x17\xdf\x2c\xfa\x29\x44\x51\x1b\xc0\x1b\x8a\xc8\x74\xa5\x82\x65\x67\xa9\x1e\x95\xdb\x15\x6d\xbe\xb8\x18\x06\x24\x8d\xcc\x02\x1e\x95\x79\x14\x17\x3c\x4e\xe3\x72\xa8\x5c\x87\x80\x44\xda\xcb\xaf\x16\x04\xbc\x95\x95\xa9\x7b\xad\xfc\x85\xdc\x58\xf1\x5b\x86\x6a\x30\x12\x94\xeb\x91\xdd\xd3\x82\xec\x77\xec\x6a\x36\xcc\xfb\x6c\x70\xbd\x7a\x0d\x2b\xdc\x58\x87\x1d\x6d\x42\x1a\xd6\xfd\xf7\xa2\xd0\xe1\x32\xdb\x1a\xed\x1a\x54\xbc\x60\xa0\x8a\x1b\x54\x9c\x4c\x9d\xd3\xe9\x8d\x30\xe4\xc6\xae\x81\x09\xb9\x2e\x9d\x19\x78\x59\x87\xd4\x3c\xe6\x11\x51\x76\x75\x4c\x83\x05\x61\xf9\x29\x44\x85\x4b\xa0\x11\x49\x35\xda\x51\x22\xe0\xf2\xef\x2f\xdc\xbb\x08\x63\x14\xe6\xbc\x4c\x64\x75\xde\x2c\x63\x4d\xd5\xb9\x88\xf7\x5a\x0a\x20\xc6\x28\xbe\x4a\x0d\x6a\x98\x01\x95\x51\x1a\x8b\x3a\x15\xa1\x54\xa6\xc2\x4c\xa1\x10\xf7\x5c\x2a\xc0\x1b\x12\x27\x11\x9e\x03\x17\xe0\x1a\x19\xb3\x3d\x54\x1c\xaf\xd1\x82\x62\x95\x57\xfb\x9a\x2b\x81\x54\xa3\xb2\xc2\x4b\x13\x0d\x51\xae\x82\xe9\x08\xae\xe2\xcd\xd5\xe2\xac\x78\x78\x75\x75\xa5\x3f\x46\x15\x2b\x3c\x33\x44\xfc\x03\xc2\x24\xde\xfc\x61\x52\x25\x2d\xf9\xde\x6e\x2f\x3a\x50\x22\x80\x44\x5a\xc2\x0a\x7d\x15\x14\x19\x48\x1b\x58\x51\xed\x67\x18\xa6\x7b\x18\xa9\xd3\x55\xe1\x06\xda\x03\x1e\xba\xc6\x9a\xab\x40\xca\x27\x2b\xa2\xae\xce\x3b\x6d\xaa\xf2\x2e\x3d\x56\x4e\x3f\xe0\x06\x9e\xc0\x24\x90\x72\x02\x44\xb0\x56\x9a\x6b\x12\xa5\x68\xa9\x56\x44\x75\xac\xc2\x4f\x7e\xfb\xaa\x9e\x25\x26\xc6\x82\xf4\x35\x67\xc8\xce\x41\x2a\xe0\x9e\xc6\x4b\xe3\x1a\x30\x4e\xcc\xe6\xdc\x8e\x95\x25\xfd\xad\xbd\x34\x21\x31\x6e\xc4\x6e\x08\x84\x44\x43\x82\x2a\xe6\xda\x66\xcc\x76\x81\x34\x22\x7c\xe2\x51\x04\xab\x72\x9f\x7d\x74\x23\x9b\x0e\xc5\xd2\xac\x39\xb6\x1e\xa2\xd9\xe0\x1d\xc4\xa8\xdf\xdd\xd5\xe6\xe0\x51\x9a\x0b\x1e\x16\xa8\xab\xd4\xec\x1c\xac\x8d\x30\xdd\xd1\x81\x8b\x5d\x75\x8f\xbd\xdf\xe6\x81\x36\x20\x14\x89\xa6\xed\xde\xf7\x4a\xed\x37\x27\x2c\x89\x60\x4b\x08\xb8\xd2\x06\x86\x2b\x71\xee\x39\x5e\xf6\xea\x74\xa8\x88\x10\x12\xf0\x26\x89\x38\xe5\xc6\x9b\xe0\x01\xcc\x79\x7c\x0e\x2e\x83\x1d\xdd\xf7\x74\xd7\xfd\xdc\x8f\x1d\xc6\xcd\x53\xa7\x8f\x76\xf7\xbb\x71\x4c\x2e\x34\x5a\xfb\x2d\xe6\xe5\xdf\x45\xf1\xb3\xd9\x5d\x5a\xe1\x56\xa0\x02\x3c\xf7\x8f\x65\x60\x81\xe8\x42\x1b\x95\x52\x93\x2a\x2b\x51\xb8\xc4\xc9\x65\x9e\xda\xee\x06\x3c\x2e\x9e\x7e\x3f\x7d\xec\xc4\x7e\x0f\x42\x1a\x57\xc8\x2e\x05\x3e\xd6\x26\x27\xfa\x06\x62\x24\x42\x3b\xaf\x70\xf4\x4e\x20\x14\x62\x0a\x9e\x1f\xbd\x23\x2f\xbc\x57\x13\x1a\xc2\x65\x05\x15\xad\xee\x6b\x34\xc0\xd9\xb9\xbb\x4e\x39\x87\x24\x22\xe2\x01\x67\x4e\xc7\x0f\x5c\xb0\x87\xee\x2f\x0f\x9e\xf0\xa0\x98\x4e\x3f\xac\x79\x57\xf1\xb7\xa4\xb1\x13\x58\x87\xf6\x8b\x8b\xd2\x75\x3c\xfb\x13\xce\xce\xdd\x84\x76\xbe\x29\x67\xfe\x7f\x3b\xe1\x79\x06\xd4\xdf\xd4\xb9\xd0\xd0\xf0\x85\x7b\xf2\xa4\xd6\x5d\x55\x4e\xde\xeb\x30\xff\x0f\x00\x00\xff\xff\x44\x7b\xe9\xbf\x3b\x58\x00\x00") +var _openapiYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5b\x5f\x8f\xdb\xb8\x11\x7f\xdf\x4f\x31\x40\x5b\xf8\x72\xf0\xda\x4e\xef\x0a\xb4\x46\x72\x40\x72\xbd\x14\x77\xc8\x25\x69\x36\x69\x1f\x8a\xc2\x4b\x8b\x23\x8b\xb7\x12\xa9\x90\xa3\xdd\x75\xda\x7e\xf7\x82\xa4\xfe\x5b\xd6\xca\xbe\xdd\xd8\xdd\x3a\x2f\xb1\xa8\xe1\x70\x86\xfc\xcd\x4f\x33\x24\x57\xa5\x28\x59\x2a\xe6\xf0\xcd\x64\x36\x99\x9d\x09\x19\xaa\xf9\x19\x00\x09\x8a\x71\x0e\x09\x43\x43\x5a\xc1\x05\xea\x6b\x11\x20\xbc\x78\xf7\xe3\x19\x00\x47\x13\x68\x91\x92\x50\x72\x9b\xc8\x35\x6a\xe3\x5e\xcf\x26\xb3\xc9\xd3\x33\x83\xda\xb6\x58\xcd\xe7\x90\xe9\x78\x0e\x11\x51\x3a\x9f\x4e\x63\x15\xb0\x38\x52\x86\xe6\x7f\x9c\xcd\x66\x67\x00\x2d\xed\x41\xa6\x35\x4a\x02\xae\x12\x26\x64\xb3\xbb\x99\x4f\xa7\x2c\x15\x13\xeb\x82\x89\x44\x48\x93\x40\x25\x9b\x2a\x7e\x66\x42\xc2\x57\xa9\x56\x3c\x0b\x6c\xcb\x13\xf0\xd6\x74\x2b\x33\xc4\x56\x78\x97\xca\x0b\x62\x2b\x21\x57\x85\xa2\x94\x51\xe4\x7c\xb3\x1a\xa6\xf9\x84\x4c\xaf\x9f\x4e\x35\x1a\x95\xe9\x00\xcf\x97\x99\xe4\x31\x3a\x19\x80\x15\x92\xff\x01\x60\xb2\x24\x61\x7a\x3d\x87\xf7\x48\x99\x96\x06\x18\xc4\xc2\x10\xa8\x10\x8a\xbe\x90\xf7\x2d\x7a\x60\x90\x69\x41\xeb\x42\x83\x75\xe2\x25\x32\x8d\x7a\x0e\xff\xf8\x67\xde\xa8\xd1\xa4\x4a\x9a\x62\x40\xfb\x6f\xf4\xfb\xd9\x6c\x54\x3d\xb6\x1c\x7a\x01\x3f\x5d\xbc\x7d\x03\x4c\x6b\xb6\xee\x18\x1c\xd4\xf2\x17\x0c\xc8\xd4\xba\x07\x4a\x12\x4a\xaa\x6b\x04\x60\x69\x1a\x8b\x80\x59\x9d\xd3\x5f\x8c\x92\xcd\xb7\x00\x26\x88\x30\x61\xed\x56\x80\xdf\x6a\x0c\xe7\x30\xfa\xcd\x34\x50\x49\xaa\x24\x4a\x32\x53\x2f\x6b\xa6\xef\x73\x53\x5e\x3a\x4b\x5e\x0b\x43\xa3\xca\xa9\x6f\x67\x4f\x7b\x9c\xca\x28\x02\x52\x57\x28\x41\x18\x10\xf2\x9a\xc5\x82\x1f\xc2\x85\x1f\xb4\x56\xba\x61\xf5\x37\xdb\xad\xfe\x28\x59\x46\x91\xd2\xe2\x33\x72\x20\x05\x29\xea\x50\xe9\x04\x54\x8a\xda\x99\x75\x0c\x1e\xfc\xa1\x0f\x4c\x1f\x25\xde\xa6\x18\x10\x72\x40\xdb\x0f\x54\xe0\xc2\xf8\xf0\x73\x9f\x32\xcd\x12\xa4\x9c\x89\xc0\x05\x4f\x57\xe7\x4a\x6e\x9a\xb2\x15\x8e\x86\x0a\x1b\xf1\x79\x07\x61\x64\x3a\x88\x06\x8b\x2b\xcd\x51\xbf\x5c\x0f\x96\x0f\x05\xc6\xdc\x8c\x06\xb0\xd2\xf4\x5f\x82\xff\x67\x3b\x35\xfd\x05\x09\xd8\x06\x23\x2c\xd7\x50\x06\xd3\xc3\x70\xd2\xfb\xd6\x88\xa1\xca\x24\x6f\x8c\xfb\x45\x81\xd4\xe4\xa1\x13\x07\x1d\x81\x07\xdf\x6e\xf7\xe0\x8d\xda\x40\xec\x8d\xa0\x08\x4c\x8a\x81\x08\x05\x72\x10\x1c\xf0\x56\x98\xc3\x7c\xd4\xfe\x7f\xf9\x54\xf0\x51\x9e\x4f\xc5\x48\xb8\x41\x36\x7f\x76\xcd\x9b\x7c\xf3\xeb\x99\xa6\x07\x2c\x6d\xa6\xf1\xb6\x71\x30\x59\x10\xa0\x31\x61\x16\xc7\xeb\x3a\xee\x7a\xd6\xea\x6f\x36\xbe\xdd\x8c\xfb\xb5\x32\xc7\xb3\x58\x27\xaa\x3a\x51\xd5\x17\xa7\x2a\x17\x4a\xb6\x54\xea\x8e\xe7\xc3\x79\xb2\x37\x75\xb5\xb3\xa9\x40\x49\x93\x25\xa5\x9e\x61\xc5\x5d\xd9\xe9\x8b\x56\x75\xc5\xa8\x87\x2c\xe7\xbe\xcf\x6d\x38\x15\x72\x47\xc1\x4c\x8f\x26\xf1\xd8\xb1\x94\xdb\xb1\x98\xdb\xb9\x9c\xdb\xbd\xa0\xdb\xa9\xa4\x03\x48\x95\xd9\x24\x9a\xef\x35\x32\x97\x3d\x49\xbc\x29\xa3\x7d\x37\x8a\xf9\x94\xa1\xa1\x97\x8a\xd7\xe4\x1a\x98\x28\xe2\x17\x38\x23\x56\x8a\xd8\x7e\x42\x23\x9f\x03\xe9\xac\x22\xf7\x0e\x70\xf4\x43\xa3\x1b\x18\x43\xf8\x64\xd4\x4b\x92\x3d\xe4\xe2\xe7\xec\x20\x90\x6e\xdb\x7e\x4a\x2e\x4f\x14\xbe\x8f\x07\x7f\xea\x41\x77\x11\xae\x2c\xd6\xc8\xf8\xfa\x7f\x25\x91\x7c\x21\x21\xdb\xf6\xf5\x81\xc0\x86\xac\x4d\x2a\x29\xc2\x36\xcd\x1d\xc6\xa5\xad\x49\xe1\xa0\xbd\xb5\x32\x2f\x7b\xf8\x4d\xb5\x12\x0f\x07\xde\x4d\xeb\xa4\xbe\x13\x7f\x1c\x63\x71\x5a\xa2\xf3\xf1\x54\xa5\x47\xf2\xd9\x4c\x19\x05\xd1\x06\x27\x7c\x4c\xb9\x4b\xe2\xe4\x03\x65\x70\x5e\x3f\xaf\xd6\xf5\xc8\x32\xb9\x77\x76\x56\xde\x7b\x37\xfa\xb3\xba\x21\x3c\x97\xe5\xde\x76\xee\xe5\x1d\x9a\xf0\x4e\xb9\xde\x17\xb6\xfa\xc4\xd5\x47\xe1\xc4\x23\x4c\x58\x37\xbe\x31\x8e\x78\x6c\x92\x7a\x14\x09\xea\xdd\x07\x2e\xfb\x7d\x6c\x76\x3c\x69\xa9\x76\x0f\x4e\x47\x2c\x27\x66\xbc\x5f\x0f\x1e\x01\x33\xee\x79\xb6\x72\x24\x0c\xb3\xe7\x91\x4a\xf5\xc6\x76\x2b\x78\xe7\xc2\xea\x2f\x88\x25\x27\x9e\x5c\x2b\xad\x53\xf4\x97\xf6\xce\x6a\x76\xe3\x1c\x96\x4e\x2c\x6f\xf4\x0f\xaf\x94\x4e\x18\xcd\xe1\xa7\xbf\x7f\x38\x2b\x1c\xcc\x95\xbe\x75\x87\x20\xef\x31\x44\x8d\x32\xc0\xa6\x76\x7f\x42\x92\x37\xa5\xda\x42\x9d\x44\x9d\xe7\x04\xaf\xcf\x93\xef\x64\x48\x0b\xb9\x2a\x9b\xaf\x84\xbc\x5b\x28\xb2\x13\xd4\x27\xf4\x5a\x54\x1b\xbd\x03\x6d\x1b\x34\x70\xca\x56\xb8\x29\x24\x24\xe1\xaa\x86\x24\x23\x3e\x0f\x90\x22\x45\x2c\xbe\x4b\xac\xac\x2c\x6a\x5f\x14\x6b\x69\xed\xd1\xda\x54\x7b\xb4\x83\xd7\x1e\xdd\x28\xb5\x67\x41\x98\xf8\xb0\x75\x20\x2c\xf4\xb2\x38\x7e\x1b\xf6\x23\xb0\x00\x6f\x0b\x02\xd5\xdd\xaa\x8e\x89\xee\x9e\x6a\x1b\x69\x1c\x9b\x21\xd3\x39\xdd\xd6\x7f\xb6\x11\x73\x5b\x44\x4b\x66\x5d\x34\x61\xd6\xd1\xc1\xb9\x5e\xc7\xc8\x0e\xee\xd7\xcf\xe0\x76\xf2\xd9\xcd\x7c\x97\x61\xee\xa8\xb1\xd1\xde\x21\x3a\x98\x50\x9a\x57\xae\x0e\xb4\xbe\x92\x25\xc3\xd6\xb7\x60\xe1\xc5\xe0\x1e\xc5\x25\xe9\x0e\xd9\x76\x84\x81\xdf\xf4\x44\xbe\x60\x34\x48\x37\x40\x98\x53\x9f\xad\x7f\xcf\x49\x24\xf5\x33\xf7\xbc\x2a\xbe\x1f\x65\x79\x2e\x77\x3f\xca\x12\x24\xc6\x19\xb1\x2e\x55\xad\xf5\x02\x48\x98\x14\x21\x1a\xfa\x35\x58\xdc\xa2\xda\x3b\xb5\x50\xfe\xe3\xbb\x8b\x31\x8b\x40\xc9\x50\xac\x1e\xc0\x26\x43\x8c\xb2\x4e\xbd\x35\xd1\xcd\xfb\xd2\x8f\x85\x19\xba\x6e\x60\x16\x05\x45\xa7\x8f\x7b\xd2\xc3\x56\x97\xb7\x39\xdd\x45\x12\x3d\xf0\x8f\xd9\x12\xe3\xa1\x6b\xee\x9c\xe2\x5c\x58\x18\xb2\xf8\xdd\x96\xf1\x7b\xc7\xdb\xc6\x1c\x3d\x5d\xfa\x63\x74\x3b\x7f\xec\xa1\xb2\x7e\x21\x64\xaf\x55\x6c\xde\x24\xd9\x79\xe9\x7a\x02\x70\x13\xb3\x5b\xc4\x77\xd9\xfa\xeb\xda\xe6\xdc\x31\xbf\xdb\x04\xd0\x16\x9f\xef\x06\x4e\x6b\xb9\xda\x29\x7c\x95\x7e\x38\x84\x57\x87\x45\x42\xce\x21\x65\x14\xe5\x8f\x8d\x42\xe5\x43\x84\xb6\xac\x72\x7f\x46\x12\x28\xcd\xdb\xf9\x5f\x7d\x67\xb9\x5d\x72\x6c\xc0\xa7\x9e\xa6\x7a\x1b\x6a\x49\xa2\xb5\xe2\x53\x86\x7a\xdd\x65\xc6\x3b\xb6\x42\x90\x59\xb2\x44\x5d\xd9\xe2\x6f\x60\xdd\x44\x28\x1b\x0d\x78\x1b\x20\x72\x53\xab\x0b\xed\x28\xf5\x04\xb4\xdb\xd0\xf6\x67\x9a\x63\xc8\xb2\x98\xe6\xf0\xb4\x6c\x4a\x84\x14\x49\x96\x54\x4d\xd5\x3c\x84\x2c\x36\x5e\x7f\x3d\xcd\xf6\x5e\xd6\x86\xee\xf5\xf2\x67\x76\x6b\xd5\x6f\x38\x6a\x6c\xa5\xae\xdd\xc5\xb3\x3d\x3d\xc8\xff\x1c\xab\xe1\xc3\xac\xcf\x07\x77\x01\xa6\xe5\x85\x6b\xdb\xe2\x47\x97\x92\x96\x77\xff\x3e\x2f\x6d\xb8\xc8\x97\xc6\xb8\x53\x5f\xaf\x18\x02\x2d\x08\xb5\x60\x13\x07\x3a\xb3\x96\xc4\x6e\xed\x1c\x50\x24\x4c\x05\x66\x10\x55\x75\x6f\x44\x22\x62\xa6\xed\xec\x50\xab\x0b\xc2\xe2\x26\x42\x8d\x0b\x08\x62\x96\x19\xb4\xad\x4c\xc2\xc5\x5f\x5f\xbb\x2f\x2f\x26\x28\x69\x5c\x2a\xca\x4c\x71\x02\x6d\x5d\x35\x85\x0a\x5b\x57\x03\x23\xd2\x62\x99\x11\x1a\x98\x42\xa0\xe2\x2c\x91\x4d\x29\x16\x04\x2a\x93\x34\x81\x52\xdd\x2b\xa5\x01\x6f\x59\x92\xc6\x38\x06\x21\xc1\xdd\x0e\xca\xd7\x50\x0b\xbc\x46\x4b\x8a\xf5\xbe\xc6\xef\x64\x30\xc8\x0c\x6a\xab\xbc\x72\x91\x98\x76\xfb\x02\x4e\xe0\x32\x59\x5f\xce\xcf\xca\x97\x97\x97\x97\xe6\x53\x5c\xf3\xc2\x77\x86\x58\x5c\x21\x8c\x92\xf5\xef\x46\x75\xd1\xaa\xdf\x87\xcd\x49\x87\x80\x49\x60\xb1\x51\xb0\x44\xbf\xb7\x80\x1c\x94\x0d\xac\xd8\x1d\xb9\x14\x57\x3e\x27\x7b\x38\x69\xb2\x65\x09\x03\xe3\x09\x0f\xdd\x71\xf5\x65\xa8\xd4\xf3\x25\xd3\x97\xe3\xad\x3e\xd5\xfb\x2e\x3c\x57\x4e\xae\x70\x0d\xcf\x61\x14\x2a\x35\x02\x26\x79\xa7\xcc\x35\x8b\x33\xb4\x52\x4b\xa6\xb7\xcc\xc2\x8f\x7e\xf9\xea\xc8\x92\x23\xb2\x24\x7d\x2d\x38\xf2\x31\x28\x0d\xc2\xcb\x78\x6d\xc2\x00\x26\x29\xad\xc7\xb6\xad\xda\x28\xdb\x58\x4b\x8a\x18\xb9\x16\xbb\x20\x10\x31\x03\x29\xea\x44\x18\x5b\x1f\xd8\x09\x32\x88\x70\x23\xe2\x18\x96\xd5\x3a\xfb\xe8\x46\x3e\x19\xca\xa5\xf9\x8d\xb3\x66\x88\xe6\x8d\x0f\x10\xa3\x7e\x75\x97\xeb\x7b\x8f\xd2\x42\xf1\xb0\x40\x5d\x66\xb4\x73\xb0\xb6\xc2\x74\x47\x00\x97\xab\xea\x5e\x7b\xdc\x16\x81\x36\x20\x14\x99\x09\xba\xd1\xf7\x56\xef\x37\x26\x2c\x98\xe4\x0b\x08\x85\x36\x04\xc3\x8d\x18\xfb\x1e\x6f\x7a\x6d\xba\xaf\x88\x90\x0a\xf0\x36\x8d\x45\x20\xc8\xbb\xe0\x09\xcc\x21\xbe\x20\x97\xc1\x40\xf7\x17\x25\x9b\x38\xf7\x6d\xf7\x03\xf3\xcc\xd9\x63\xdc\xa9\x49\x92\xb0\x73\x83\xd6\x7f\xcb\x79\xc5\x05\x6f\x3f\x9a\x5d\xa5\x25\x6e\x04\x2a\xc0\x2b\xff\x5a\x85\x96\x88\xce\x0d\xe9\x2c\xa0\x4c\x5b\x8d\xd2\x25\x4e\x2e\xf3\x34\x76\x35\xe0\x59\xf9\xf6\xbb\xc9\x33\xa7\xf6\x3b\x90\x8a\xdc\xf6\x50\xa5\xf0\x99\xa1\x42\xe8\x6b\x48\x90\x49\xe3\x50\xe1\xe4\x9d\x42\x28\xd5\x94\x7d\x7e\xf0\x40\x9e\x7b\x54\xb3\x20\x82\x8b\x1a\x2b\x5a\xdb\x57\x48\x20\xf8\xd8\x6d\x52\x8e\x21\x8d\x99\xfc\x4a\x70\x67\xe3\x95\x90\xfc\x89\xfb\xe5\xc9\x13\xbe\x2a\x87\x33\x4f\x1a\xe8\x2a\x7f\xab\x20\x71\x0a\x9b\xd4\x7e\x7e\x5e\x41\xc7\x77\x7f\x2e\xf8\xd8\x0d\x68\xc7\x9b\x08\xee\xff\xb7\x03\x8e\x73\xa2\xfe\xba\xd9\x0b\x29\x88\x5e\xbb\x37\xcf\x1b\x77\x16\xaa\xc1\x7b\x01\xf3\xdf\x00\x00\x00\xff\xff\x12\x6b\x26\x13\x02\x3f\x00\x00") func openapiYamlBytes() ([]byte, error) { return bindataRead( @@ -92,7 +92,7 @@ func openapiYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "openapi.yaml", size: 22587, mode: os.FileMode(493), modTime: time.Unix(1737424969, 0)} + info := bindataFileInfo{name: "openapi.yaml", size: 16130, mode: os.FileMode(493), modTime: time.Unix(1740049896, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/docs/grpc.md b/docs/grpc.md index ee6f55f8..1146cede 100644 --- a/docs/grpc.md +++ b/docs/grpc.md @@ -114,7 +114,7 @@ The `grpcClientTokenFile` stores the token for the corresponding service account // grpcOptions.CAFile = grpcServerCAFile // ClientCertFile = grpcClientCertFile // ClientKeyFile = grpcClientKeyFile - // grpcOptions.TokenFile = grpcClientTokenFile + // grpcOptions.TokenFile = grpcClientTokenFile grpcSourceOption = grpcoptions.NewSourceOptions(grpcOptions, "grpc-source-example") ``` @@ -146,9 +146,9 @@ To publish the resource with cloudevents format, you need to call the `Publish` ```golang // publish the resource in the cloudevents format grpcSourceCloudEventsClient.Publish(context.TODO(), types.CloudEventsType{ - CloudEventsDataType: payload.ManifestEventDataType, - SubResource: types.SubResourceSpec, - Action: config.CreateRequestAction, + CloudEventsDataType: payload.ManifestEventDataType, + SubResource: types.SubResourceSpec, + Action: config.CreateRequestAction, }, res) ``` @@ -161,12 +161,12 @@ see the below for an example of the resource: ```golang resource := &api.Resource{ - ConsumerID: consumerID, - Manifest: testManifest, - } + ConsumerID: consumerID, + Manifest: testManifest, + } ... testManifest := map[string]interface{}{} - json.Unmarshal(`{ + json.Unmarshal(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { diff --git a/examples/grpc/README.md b/examples/grpc/README.md index 7d5e757f..dc95bcd3 100644 --- a/examples/grpc/README.md +++ b/examples/grpc/README.md @@ -1,28 +1,75 @@ -# CURD Resource/Bundle with gRPC Client +# Resource Bundle CURD with gRPC Client ## Preparation -1. Enable gRPC server by passing `--enable-grpc-server=true` to the maestro server start command, for example: +1. Enable gRPC server by passing `--enable-grpc-server=true` to the maestro server start command: ```shell -$ oc -n maestro patch deploy/maestro --type=json -p='[{"op": "add", "path": "/spec/template/spec/containers/0/command/-", "value": "--enable-grpc-server=true"}]' +$ kubectl -n maestro patch deploy/maestro --type=json -p='[{"op":"add","path":"/spec/template/spec/containers/0/command/-","value":"--enable-grpc-server=true"}]' ``` -2. Port-forward the gRPC service to your local machine, for example: +2. Do the port-forward the maestro-grpc service: ```shell -$ oc -n maestro port-forward svc/maestro-grpc 8090 & +$ kubectl -n maestro port-forward svc/maestro-grpc 8090 & ``` -## Operate Resource Bundle with gRPC client +## How + +1. Set the source ID for the manifestwork client and consumer name: + +```shell +$ export SOURCE_ID=grpc +$ export CONSUMER_NAME=cluster1 +``` + +2. Create a resource bundle: ```shell # create -go run ./grpcclient.go -grpc_server localhost:8090 -cloudevents_json_file ./cloudevent-bundle.json +$ go run ./grpcclient.go -source=$SOURCE_ID -consumer-name=$CONSUMER_NAME -cloudevent-file ./cloudevent.json +``` -# update -go run ./grpcclient.go -grpc_server localhost:8090 -cloudevents_json_file ./cloudevent-bundle-update.json +Note: If your gRPC server enable authentication and authorization, you'll need to provide the CA file for the server and the client's token. For example, after setting up Maestro with `make e2e-test/setup`, you can retrieve the gRPC server's CA, client certificate, key, and token using the following command: -# delete -go run ./grpcclient.go -grpc_server localhost:8090 -cloudevents_json_file ./cloudevent-bundle-delete.json +```shell +kubectl -n maestro get secret maestro-grpc-cert -o jsonpath="{.data.ca\.crt}" | base64 -d > /tmp/grpc-server-ca.crt +kubectl -n maestro get secret maestro-grpc-cert -o jsonpath="{.data.client\.crt}" | base64 -d > /tmp/grpc-client-cert.crt +kubectl -n maestro get secret maestro-grpc-cert -o jsonpath="{.data.client\.key}" | base64 -d > /tmp/grpc-client-cert.key +kubectl -n maestro get secret grpc-client-token -o jsonpath="{.data.token}" | base64 -d > /tmp/grpc-client-token +``` + +You also need to create a cluster role to grant publish & subscribe permissions to the client by running this command: + +```shell +$ cat << EOF | kubectl apply -f - +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: grpc-pub-sub +rules: +- nonResourceURLs: + - /sources/${SOURCE_ID} + verbs: + - pub + - sub +EOF +``` + +then you can create a resource bundle with the following command: + +```shell +$ go run ./grpcclient.go -source=$SOURCE_ID -consumer-name=$CONSUMER_NAME -cloudevent-file ./cloudevent.json -grpc-server-tls=true -grpc-server=127.0.0.1:30090 -grpc-server-ca-file=/tmp/grpc-server-ca.crt -grpc-client-token-file=/tmp/grpc-client-token +``` + +2. Update the resource bundle: + +```shell +$ go run ./grpcclient.go -source=$SOURCE_ID -consumer-name=$CONSUMER_NAME -cloudevent-file ./cloudevent-update.json -grpc-server-tls=true -grpc-server=127.0.0.1:30090 -grpc-server-ca-file=/tmp/grpc-server-ca.crt -grpc-client-token-file=/tmp/grpc-client-token +``` + +3. Delete the resource bundle: + +```shell +$ go run ./grpcclient.go -source=$SOURCE_ID -consumer-name=$CONSUMER_NAME -cloudevent-file ./cloudevent-delete.json -grpc-server-tls=true -grpc-server=127.0.0.1:30090 -grpc-server-ca-file=/tmp/grpc-server-ca.crt -grpc-client-token-file=/tmp/grpc-client-token ``` diff --git a/examples/grpc/cloudevent-bundle-delete.json b/examples/grpc/cloudevent-delete.json similarity index 100% rename from examples/grpc/cloudevent-bundle-delete.json rename to examples/grpc/cloudevent-delete.json diff --git a/examples/grpc/cloudevent-bundle-status-resync.json b/examples/grpc/cloudevent-status-resync.json similarity index 100% rename from examples/grpc/cloudevent-bundle-status-resync.json rename to examples/grpc/cloudevent-status-resync.json diff --git a/examples/grpc/cloudevent-bundle-update.json b/examples/grpc/cloudevent-update.json similarity index 84% rename from examples/grpc/cloudevent-bundle-update.json rename to examples/grpc/cloudevent-update.json index 388f491c..9bb599f2 100644 --- a/examples/grpc/cloudevent-bundle-update.json +++ b/examples/grpc/cloudevent-update.json @@ -9,38 +9,31 @@ "datacontenttype": "application/json", "data": { "manifests": [ - { - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": "web", - "namespace": "default" - } - }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { - "name": "web", + "name": "nginx", "namespace": "default" }, "spec": { "replicas": 2, "selector": { "matchLabels": { - "app": "web" + "app": "nginx" } }, "template": { "metadata": { "labels": { - "app": "web" + "app": "nginx" } }, "spec": { "containers": [ { "image": "nginxinc/nginx-unprivileged", + "imagePullPolicy": "IfNotPresent", "name": "nginx" } ] @@ -58,7 +51,7 @@ "group": "apps", "resource": "deployments", "namespace": "default", - "name": "web" + "name": "nginx" }, "feedbackRules": [ { diff --git a/examples/grpc/cloudevent-bundle.json b/examples/grpc/cloudevent.json similarity index 84% rename from examples/grpc/cloudevent-bundle.json rename to examples/grpc/cloudevent.json index 269b2be0..eaa77900 100644 --- a/examples/grpc/cloudevent-bundle.json +++ b/examples/grpc/cloudevent.json @@ -9,38 +9,31 @@ "datacontenttype": "application/json", "data": { "manifests": [ - { - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": "web", - "namespace": "default" - } - }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { - "name": "web", + "name": "nginx", "namespace": "default" }, "spec": { "replicas": 1, "selector": { "matchLabels": { - "app": "web" + "app": "nginx" } }, "template": { "metadata": { "labels": { - "app": "web" + "app": "nginx" } }, "spec": { "containers": [ { "image": "nginxinc/nginx-unprivileged", + "imagePullPolicy": "IfNotPresent", "name": "nginx" } ] @@ -58,7 +51,7 @@ "group": "apps", "resource": "deployments", "namespace": "default", - "name": "web" + "name": "nginx" }, "feedbackRules": [ { diff --git a/examples/grpc/grpcclient.go b/examples/grpc/grpcclient.go index 7cede7d7..3df990f0 100644 --- a/examples/grpc/grpcclient.go +++ b/examples/grpc/grpcclient.go @@ -2,6 +2,8 @@ package main import ( "context" + "crypto/tls" + "crypto/x509" "encoding/json" "flag" "io" @@ -10,43 +12,87 @@ import ( cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/cloudevents/sdk-go/v2/binding" + "golang.org/x/oauth2" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/credentials/oauth" pbv1 "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc/protobuf/v1" grpcprotocol "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc/protocol" + "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" ) var ( - tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") - caFile = flag.String("ca_file", "", "The absolute file path containing the CA root cert file") - serverAddr = flag.String("grpc_server", "localhost:8090", "The server address in the format of host:port") - serverHostOverride = flag.String("server_host_override", "x.test.example.com", "The server name used to verify the hostname returned by the TLS handshake") - cloudEventFile = flag.String("cloudevents_json_file", "", "The absolute file path containing the CloudEvent resource") - subscribeStatus = flag.Bool("subscribe_status", false, "If true, subscribe to the CloudEvent resource status.") + sourceID = flag.String("source", "grpc", "The source for manifestwork client") + grpcServerAddr = flag.String("grpc-server", "127.0.0.1:8090", "The grpc server address") + grpcServerTLS = flag.Bool("grpc-server-tls", false, "Connect grpc server with TLS if true") + grpcServerCAFile = flag.String("grpc-server-ca-file", "", "The CA for grpc server") + grpcClientCertFile = flag.String("grpc-client-cert-file", "", "The client certificate to access grpc server") + grpcClientKeyFile = flag.String("grpc-client-key-file", "", "The client key to access grpc server") + grpcClientTokenFile = flag.String("grpc-client-token-file", "", "The client token to access grpc server") + consumerName = flag.String("consumer-name", "", "The Consumer Name") + cloudEventFile = flag.String("cloudevent-file", "", "The absolute file path containing the CloudEvent resource") + enableSubscribing = flag.Bool("enable-subscribing", false, "If true, subscribe to the CloudEvent resource status.") ) func main() { flag.Parse() + + if len(*cloudEventFile) == 0 { + log.Fatalf("the cloudevent file is required") + } + + if *grpcServerTLS { + if len(*grpcServerCAFile) == 0 { + log.Fatalf("the grpc server CA file is required when TLS enabled") + } + + if len(*grpcClientTokenFile) == 0 { + log.Fatalf("the grpc client token file is required when TLS enabled") + } + } + var opts []grpc.DialOption - if *tls { - creds, err := credentials.NewClientTLSFromFile(*caFile, *serverHostOverride) + if *grpcServerTLS { + certPool, err := x509.SystemCertPool() + if err != nil { + log.Fatalf("failed to load system cert pool: %v", err) + } + + caPEM, err := os.ReadFile(*grpcServerCAFile) + if err != nil { + log.Fatalf("failed to read grpc server CA file: %v", err) + } + + if ok := certPool.AppendCertsFromPEM(caPEM); !ok { + log.Fatalf("failed to append grpc server CA certificate") + } + + tlsConfig := &tls.Config{ + RootCAs: certPool, + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + } + + clientToken, err := os.ReadFile(*grpcClientTokenFile) if err != nil { - log.Fatalf("Failed to create TLS credentials: %v", err) + log.Fatalf("failed to read grpc client token file: %v", err) } - opts = append(opts, grpc.WithTransportCredentials(creds)) + perRPCCred := oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: string(clientToken)})} + + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), grpc.WithPerRPCCredentials(perRPCCred)) } else { + // no TLS and authz opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) } - conn, err := grpc.Dial(*serverAddr, opts...) + conn, err := grpc.NewClient(*grpcServerAddr, opts...) if err != nil { - log.Fatalf("fail to dial: %v", err) + log.Fatalf("failed to create grpc connection: %v", err) } defer conn.Close() client := pbv1.NewCloudEventServiceClient(conn) - cloudeventJSON, err := os.ReadFile(*cloudEventFile) if err != nil { log.Fatalf("failed to read cloudevent file: %v", err) @@ -56,6 +102,8 @@ func main() { if err := json.Unmarshal(cloudeventJSON, evt); err != nil { log.Fatalf("failed to unmarshal cloudevent: %v", err) } + // override the consumer name + evt.SetExtension(types.ExtensionClusterName, *consumerName) ctx := context.TODO() pbEvt := &pbv1.CloudEvent{} @@ -71,9 +119,9 @@ func main() { log.Printf("Published spec with cloudevent:\n%v\n\n", evt) log.Printf("=======================================") - if *subscribeStatus { + if *enableSubscribing { subReq := &pbv1.SubscriptionRequest{ - Source: "grpc", + Source: *sourceID, } subClient, err := client.Subscribe(ctx, subReq) diff --git a/examples/manifestworkclient/client/README.md b/examples/manifestworkclient/client/README.md new file mode 100644 index 00000000..bad9ce4c --- /dev/null +++ b/examples/manifestworkclient/client/README.md @@ -0,0 +1,69 @@ +# Resource Bundle CURD with Manifestwork Client + +## Preparation + +1. Enable gRPC server by passing `--enable-grpc-server=true` to the maestro server start command: + +```shell +$ kubectl -n maestro patch deploy/maestro --type=json -p='[{"op":"add","path":"/spec/template/spec/containers/0/command/-","value":"--enable-grpc-server=true"}]' +``` + +2. Do the port-forward for the maestro and maestro-grpc services: + +```shell +$ kubectl -n maestro port-forward svc/maestro 8000 & +$ kubectl -n maestro port-forward svc/maestro-grpc 8090 & +``` + +## How + +1. Set the source ID for the manifestwork client and consumer name: + +```shell +$ export SOURCE_ID=grpc +$ export CONSUMER_NAME=cluster1 +``` + +2. Create a resource bundle + +```shell +$ go run ./main.go -source=$SOURCE_ID -consumer-name=$CONSUMER_NAME -manifestwork_file=./manifestwork.json -action=create +``` + +Note: If your gRPC server enable authentication and authorization, you'll need to provide the CA file for the server and the client's token. For example, after setting up Maestro with `make e2e-test/setup`, you can retrieve the gRPC server's CA, client certificate, key, and token using the following command: + +```shell +kubectl -n maestro get secret maestro-grpc-cert -o jsonpath="{.data.ca\.crt}" | base64 -d > /tmp/grpc-server-ca.crt +kubectl -n maestro get secret maestro-grpc-cert -o jsonpath="{.data.client\.crt}" | base64 -d > /tmp/grpc-client-cert.crt +kubectl -n maestro get secret maestro-grpc-cert -o jsonpath="{.data.client\.key}" | base64 -d > /tmp/grpc-client-cert.key +kubectl -n maestro get secret grpc-client-token -o jsonpath="{.data.token}" | base64 -d > /tmp/grpc-client-token +``` + +You also need to create a cluster role to grant publish & subscribe permissions to the client by running this command: + +```shell +$ cat << EOF | kubectl apply -f - +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: grpc-pub-sub +rules: +- nonResourceURLs: + - /sources/${SOURCE_ID} + verbs: + - pub + - sub +EOF +``` + +then you can create a resource bundle with the following command: + +```shell +$ go run ./main.go -source=$SOURCE_ID -consumer-name=$CONSUMER_NAME -manifestwork_file=./manifestwork.json -maestro-server=https://127.0.0.1:30080 -grpc-server=127.0.0.1:30090 -grpc-server-ca-file=/tmp/grpc-server-ca.crt -grpc-client-token-file=/tmp/grpc-client-token -action=create +``` + +3. Delete the resource bundle: + +```shell +$ go run ./main.go -source=$SOURCE_ID -consumer-name=$CONSUMER_NAME -manifestwork_file=./manifestwork.json -maestro-server=https://127.0.0.1:30080 -grpc-server=127.0.0.1:30090 -grpc-server-ca-file=/tmp/grpc-server-ca.crt -grpc-client-token-file=/tmp/grpc-client-token -action=delete +``` diff --git a/examples/manifestworkclient/client/main.go b/examples/manifestworkclient/client/main.go new file mode 100644 index 00000000..b304468e --- /dev/null +++ b/examples/manifestworkclient/client/main.go @@ -0,0 +1,139 @@ +package main + +import ( + "context" + "crypto/tls" + "encoding/json" + "flag" + "log" + "net/http" + "os" + "time" + + "fmt" + + "github.com/openshift-online/maestro/pkg/api/openapi" + "github.com/openshift-online/maestro/pkg/client/cloudevents/grpcsource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + workv1 "open-cluster-management.io/api/work/v1" + "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc" +) + +var ( + sourceID = flag.String("source", "grpc", "The source for manifestwork client") + maestroServerAddr = flag.String("maestro-server", "https://127.0.0.1:8000", "The maestro server address") + grpcServerAddr = flag.String("grpc-server", "127.0.0.1:8090", "The grpc server address") + grpcServerCAFile = flag.String("grpc-server-ca-file", "", "The CA for grpc server") + grpcClientCertFile = flag.String("grpc-client-cert-file", "", "The client certificate to access grpc server") + grpcClientKeyFile = flag.String("grpc-client-key-file", "", "The client key to access grpc server") + grpcClientTokenFile = flag.String("grpc-client-token-file", "", "The client token to access grpc server") + consumerName = flag.String("consumer-name", "", "The Consumer Name") + manifestworkFile = flag.String("manifestwork_file", "", "The absolute file path containing the manifestwork json file") + action = flag.String("action", "create", "The action executed on the manifestwork, create or delete") +) + +func main() { + flag.Parse() + + if len(*consumerName) == 0 { + log.Fatalf("the consumer_name is required") + } + + if len(*manifestworkFile) == 0 { + log.Fatalf("the manifestwork_file is required") + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + maestroAPIClient := openapi.NewAPIClient(&openapi.Configuration{ + DefaultHeader: make(map[string]string), + UserAgent: "OpenAPI-Generator/1.0.0/go", + Debug: false, + Servers: openapi.ServerConfigurations{ + { + URL: *maestroServerAddr, + Description: "current domain", + }, + }, + OperationServers: map[string]openapi.ServerConfigurations{}, + HTTPClient: &http.Client{ + Transport: &http.Transport{TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }}, + Timeout: 10 * time.Second, + }, + }) + + grpcOptions := grpc.NewGRPCOptions() + grpcOptions.URL = *grpcServerAddr + if *grpcServerCAFile != "" { + grpcOptions.CAFile = *grpcServerCAFile + } + if *grpcClientCertFile != "" { + grpcOptions.ClientCertFile = *grpcClientCertFile + } + if *grpcClientKeyFile != "" { + grpcOptions.ClientKeyFile = *grpcClientKeyFile + } + if *grpcClientTokenFile != "" { + grpcOptions.TokenFile = *grpcClientTokenFile + } + + workClient, err := grpcsource.NewMaestroGRPCSourceWorkClient( + ctx, + maestroAPIClient, + grpcOptions, + *sourceID, + ) + if err != nil { + log.Fatal(err) + } + + workJSON, err := os.ReadFile(*manifestworkFile) + if err != nil { + log.Fatalf("failed to read manifestwork file: %v", err) + } + + manifestwork := &workv1.ManifestWork{} + if err := json.Unmarshal(workJSON, manifestwork); err != nil { + log.Fatalf("failed to unmarshal manifestwork: %v", err) + } + + // use workClient to create/get/patch/delete work + if *action == "create" { + _, err = workClient.ManifestWorks(*consumerName).Create(ctx, manifestwork, metav1.CreateOptions{}) + if err != nil { + log.Fatal(err) + } + + <-time.After(2 * time.Second) + work, err := workClient.ManifestWorks(*consumerName).Get(ctx, manifestwork.Name, metav1.GetOptions{}) + if err != nil { + log.Fatal(err) + } + log.Printf("the work %s/%s (uid=%s) is created", *consumerName, manifestwork.Name, work.UID) + } + + // newWork := work.DeepCopy() + // newWork.Spec.Workload.Manifests = []workv1.Manifest{NewManifest(manifestwork.Name)} + // patchData, err := grpcsource.ToWorkPatch(work, newWork) + // if err != nil { + // log.Fatal(err) + // } + // _, err = workClient.ManifestWorks(*consumerName).Patch(ctx, manifestwork.Name, types.MergePatchType, patchData, metav1.PatchOptions{}) + // if err != nil { + // log.Fatal(err) + // } + // log.Printf("the work %s/%s (uid=%s) is updated\n", *consumerName, manifestwork.Name, work.UID) + + // <-time.After(5 * time.Second) + + if *action == "delete" { + err = workClient.ManifestWorks(*consumerName).Delete(ctx, manifestwork.Name, metav1.DeleteOptions{}) + if err != nil { + log.Fatal(err) + } + fmt.Printf("the work %s/%s (uid=%s) is deleted\n", *consumerName, manifestwork.Name, manifestwork.UID) + } +} diff --git a/examples/manifestworkclient/client/manifestwork.json b/examples/manifestworkclient/client/manifestwork.json new file mode 100644 index 00000000..a1c92734 --- /dev/null +++ b/examples/manifestworkclient/client/manifestwork.json @@ -0,0 +1,72 @@ +{ + "apiVersion": "work.open-cluster-management.io/v1", + "kind": "ManifestWork", + "metadata": { + "name": "nginx-work" + }, + "spec": { + "workload": { + "manifests": [ + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "nginx", + "namespace": "default" + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginxinc/nginx-unprivileged", + "imagePullPolicy": "IfNotPresent" + } + ] + } + } + } + } + ] + }, + "deleteOption": { + "propagationPolicy": "Foreground" + }, + "manifestConfigs": [ + { + "resourceIdentifier": { + "group": "apps", + "resource": "deployments", + "namespace": "default", + "name": "nginx" + }, + "feedbackRules": [ + { + "type": "JSONPaths", + "jsonPaths": [ + { + "name": "status", + "path": ".status" + } + ] + } + ], + "updateStrategy": { + "type": "ServerSideApply" + } + } + ] + } +} \ No newline at end of file diff --git a/examples/resource.json b/examples/resource.json deleted file mode 100644 index b084776b..00000000 --- a/examples/resource.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "nginx", - "consumer_name": "cluster1", - "version": 1, - "manifest": { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": "nginx", - "namespace": "default" - }, - "spec": { - "replicas": 1, - "selector": { - "matchLabels": { - "app": "nginx" - } - }, - "template": { - "metadata": { - "labels": { - "app": "nginx" - } - }, - "spec": { - "serviceAccount": "default", - "containers": [ - { - "image": "nginxinc/nginx-unprivileged", - "imagePullPolicy": "IfNotPresent", - "name": "nginx" - } - ] - } - } - } - }, - "group_resource": { - "group": "apps", - "resource": "deployments" - }, - "update_strategy": { - "type": "ServerSideApply" - }, - "delete_option": { - "propagationPolicy": "Foreground" - } -} diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index ec16eb32..29f4c6c3 100755 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -11,18 +11,18 @@ servers: - url: https://api.stage.openshift.com description: Staging server paths: - /api/maestro/v1/resources: + /api/maestro/v1/resource-bundles: get: - summary: Returns a list of resources + summary: Returns a list of resource bundles security: - Bearer: [] responses: '200': - description: A JSON array of resource objects + description: A JSON array of resource bundle objects content: application/json: schema: - $ref: '#/components/schemas/ResourceList' + $ref: '#/components/schemas/ResourceBundleList' '401': description: Auth token is invalid content: @@ -47,66 +47,18 @@ paths: - $ref: '#/components/parameters/search' - $ref: '#/components/parameters/orderBy' - $ref: '#/components/parameters/fields' - post: - summary: Create a new resource - security: - - Bearer: [] - requestBody: - description: Resource data - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Resource' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/Resource' - '400': - description: Validation errors occurred - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '401': - description: Auth token is invalid - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '403': - description: Unauthorized to perform operation - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '409': - description: Resource already exists - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: An unexpected error occurred creating the resource - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /api/maestro/v1/resources/{id}: + /api/maestro/v1/resource-bundles/{id}: get: - summary: Get an resource by id + summary: Get a resource bundle by id security: - Bearer: [] responses: '200': - description: Resource found by id + description: Resource bundle found by id content: application/json: schema: - $ref: '#/components/schemas/Resource' + $ref: '#/components/schemas/ResourceBundle' '401': description: Auth token is invalid content: @@ -120,7 +72,7 @@ paths: schema: $ref: '#/components/schemas/Error' '404': - description: No resource with specified id exists + description: No resource bundle with specified id exists content: application/json: schema: @@ -131,67 +83,15 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - patch: - summary: Update an resource - security: - - Bearer: [] - requestBody: - description: Updated resource data - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ResourcePatchRequest' - responses: - '200': - description: Resource updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Resource' - '400': - description: Validation errors occurred - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '401': - description: Auth token is invalid - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '403': - description: Unauthorized to perform operation - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: No resource with specified id exists - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '409': - description: Resource already exists - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Unexpected error updating resource - content: - application/json: - schema: - $ref: '#/components/schemas/Error' + parameters: + - $ref: '#/components/parameters/id' delete: - summary: Delete a resource + summary: Delete a resource bundle security: - Bearer: [] responses: '204': - description: Resource deleted successfully + description: Resource bundle deleted successfully '400': description: Validation errors occurred content: @@ -210,80 +110,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - '404': - description: No resource with specified id exists - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Unexpected error deleting resource - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - parameters: - - $ref: '#/components/parameters/id' - /api/maestro/v1/resource-bundles: - get: - summary: Returns a list of resource bundles - security: - - Bearer: [] - responses: - '200': - description: A JSON array of resource bundle objects - content: - application/json: - schema: - $ref: '#/components/schemas/ResourceBundleList' - '401': - description: Auth token is invalid - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '403': - description: Unauthorized to perform operation - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Unexpected error occurred - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - parameters: - - $ref: '#/components/parameters/page' - - $ref: '#/components/parameters/size' - - $ref: '#/components/parameters/search' - - $ref: '#/components/parameters/orderBy' - - $ref: '#/components/parameters/fields' - /api/maestro/v1/resource-bundles/{id}: - get: - summary: Get an resource bundle by id - security: - - Bearer: [] - responses: - '200': - description: Resource bundle found by id - content: - application/json: - schema: - $ref: '#/components/schemas/ResourceBundle' - '401': - description: Auth token is invalid - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '403': - description: Unauthorized to perform operation - content: - application/json: - schema: - $ref: '#/components/schemas/Error' '404': description: No resource bundle with specified id exists content: @@ -291,7 +117,7 @@ paths: schema: $ref: '#/components/schemas/Error' '500': - description: Unexpected error occurred + description: Unexpected error deleting resource bundle content: application/json: schema: @@ -384,7 +210,7 @@ paths: $ref: '#/components/schemas/Error' /api/maestro/v1/consumers/{id}: get: - summary: Get an consumer by id + summary: Get a consumer by id security: - Bearer: [] responses: @@ -564,65 +390,6 @@ components: type: array items: $ref: '#/components/schemas/Error' - Resource: - allOf: - - $ref: '#/components/schemas/ObjectReference' - - type: object - properties: - name: - type: string - consumer_name: - type: string - version: - type: integer - created_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - deleted_at: - type: string - format: date-time - manifest: - type: object - group_resource: - type: object - delete_option: - type: object - update_strategy: - type: object - status: - type: object - ResourceList: - allOf: - - $ref: '#/components/schemas/List' - - type: object - properties: - items: - type: array - items: - $ref: '#/components/schemas/Resource' - ResourcePatchRequest: - type: object - properties: - version: - type: integer - manifest: - type: object - delete_option: - type: object - update_strategy: - type: object - ResourceBundleList: - allOf: - - $ref: '#/components/schemas/List' - - type: object - properties: - items: - type: array - items: - $ref: '#/components/schemas/ResourceBundle' ResourceBundle: allOf: - $ref: '#/components/schemas/ObjectReference' @@ -657,6 +424,15 @@ components: type: object status: type: object + ResourceBundleList: + allOf: + - $ref: '#/components/schemas/List' + - type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/ResourceBundle' Consumer: allOf: - $ref: '#/components/schemas/ObjectReference' diff --git a/pkg/api/openapi/.openapi-generator/FILES b/pkg/api/openapi/.openapi-generator/FILES index e19f8922..b13c952e 100644 --- a/pkg/api/openapi/.openapi-generator/FILES +++ b/pkg/api/openapi/.openapi-generator/FILES @@ -18,15 +18,10 @@ docs/ErrorList.md docs/ErrorListAllOf.md docs/List.md docs/ObjectReference.md -docs/Resource.md -docs/ResourceAllOf.md docs/ResourceBundle.md docs/ResourceBundleAllOf.md docs/ResourceBundleList.md docs/ResourceBundleListAllOf.md -docs/ResourceList.md -docs/ResourceListAllOf.md -docs/ResourcePatchRequest.md git_push.sh go.mod go.sum @@ -41,15 +36,10 @@ model_error_list.go model_error_list_all_of.go model_list.go model_object_reference.go -model_resource.go -model_resource_all_of.go model_resource_bundle.go model_resource_bundle_all_of.go model_resource_bundle_list.go model_resource_bundle_list_all_of.go -model_resource_list.go -model_resource_list_all_of.go -model_resource_patch_request.go response.go test/api_default_test.go utils.go diff --git a/pkg/api/openapi/README.md b/pkg/api/openapi/README.md index 04e6c4ee..f2bf39c6 100644 --- a/pkg/api/openapi/README.md +++ b/pkg/api/openapi/README.md @@ -79,16 +79,12 @@ Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- *DefaultApi* | [**ApiMaestroV1ConsumersGet**](docs/DefaultApi.md#apimaestrov1consumersget) | **Get** /api/maestro/v1/consumers | Returns a list of consumers *DefaultApi* | [**ApiMaestroV1ConsumersIdDelete**](docs/DefaultApi.md#apimaestrov1consumersiddelete) | **Delete** /api/maestro/v1/consumers/{id} | Delete a consumer -*DefaultApi* | [**ApiMaestroV1ConsumersIdGet**](docs/DefaultApi.md#apimaestrov1consumersidget) | **Get** /api/maestro/v1/consumers/{id} | Get an consumer by id +*DefaultApi* | [**ApiMaestroV1ConsumersIdGet**](docs/DefaultApi.md#apimaestrov1consumersidget) | **Get** /api/maestro/v1/consumers/{id} | Get a consumer by id *DefaultApi* | [**ApiMaestroV1ConsumersIdPatch**](docs/DefaultApi.md#apimaestrov1consumersidpatch) | **Patch** /api/maestro/v1/consumers/{id} | Update an consumer *DefaultApi* | [**ApiMaestroV1ConsumersPost**](docs/DefaultApi.md#apimaestrov1consumerspost) | **Post** /api/maestro/v1/consumers | Create a new consumer *DefaultApi* | [**ApiMaestroV1ResourceBundlesGet**](docs/DefaultApi.md#apimaestrov1resourcebundlesget) | **Get** /api/maestro/v1/resource-bundles | Returns a list of resource bundles -*DefaultApi* | [**ApiMaestroV1ResourceBundlesIdGet**](docs/DefaultApi.md#apimaestrov1resourcebundlesidget) | **Get** /api/maestro/v1/resource-bundles/{id} | Get an resource bundle by id -*DefaultApi* | [**ApiMaestroV1ResourcesGet**](docs/DefaultApi.md#apimaestrov1resourcesget) | **Get** /api/maestro/v1/resources | Returns a list of resources -*DefaultApi* | [**ApiMaestroV1ResourcesIdDelete**](docs/DefaultApi.md#apimaestrov1resourcesiddelete) | **Delete** /api/maestro/v1/resources/{id} | Delete a resource -*DefaultApi* | [**ApiMaestroV1ResourcesIdGet**](docs/DefaultApi.md#apimaestrov1resourcesidget) | **Get** /api/maestro/v1/resources/{id} | Get an resource by id -*DefaultApi* | [**ApiMaestroV1ResourcesIdPatch**](docs/DefaultApi.md#apimaestrov1resourcesidpatch) | **Patch** /api/maestro/v1/resources/{id} | Update an resource -*DefaultApi* | [**ApiMaestroV1ResourcesPost**](docs/DefaultApi.md#apimaestrov1resourcespost) | **Post** /api/maestro/v1/resources | Create a new resource +*DefaultApi* | [**ApiMaestroV1ResourceBundlesIdDelete**](docs/DefaultApi.md#apimaestrov1resourcebundlesiddelete) | **Delete** /api/maestro/v1/resource-bundles/{id} | Delete a resource bundle +*DefaultApi* | [**ApiMaestroV1ResourceBundlesIdGet**](docs/DefaultApi.md#apimaestrov1resourcebundlesidget) | **Get** /api/maestro/v1/resource-bundles/{id} | Get a resource bundle by id ## Documentation For Models @@ -104,15 +100,10 @@ Class | Method | HTTP request | Description - [ErrorListAllOf](docs/ErrorListAllOf.md) - [List](docs/List.md) - [ObjectReference](docs/ObjectReference.md) - - [Resource](docs/Resource.md) - - [ResourceAllOf](docs/ResourceAllOf.md) - [ResourceBundle](docs/ResourceBundle.md) - [ResourceBundleAllOf](docs/ResourceBundleAllOf.md) - [ResourceBundleList](docs/ResourceBundleList.md) - [ResourceBundleListAllOf](docs/ResourceBundleListAllOf.md) - - [ResourceList](docs/ResourceList.md) - - [ResourceListAllOf](docs/ResourceListAllOf.md) - - [ResourcePatchRequest](docs/ResourcePatchRequest.md) ## Documentation For Authorization diff --git a/pkg/api/openapi/api/openapi.yaml b/pkg/api/openapi/api/openapi.yaml index 4fbc9439..6169cb88 100644 --- a/pkg/api/openapi/api/openapi.yaml +++ b/pkg/api/openapi/api/openapi.yaml @@ -11,7 +11,7 @@ servers: - description: Staging server url: https://api.stage.openshift.com paths: - /api/maestro/v1/resources: + /api/maestro/v1/resource-bundles: get: parameters: - description: Page number of record list when record list exceeds specified @@ -98,8 +98,8 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ResourceList' - description: A JSON array of resource objects + $ref: '#/components/schemas/ResourceBundleList' + description: A JSON array of resource bundle objects "401": content: application/json: @@ -120,56 +120,8 @@ paths: description: Unexpected error occurred security: - Bearer: [] - summary: Returns a list of resources - post: - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Resource' - description: Resource data - required: true - responses: - "201": - content: - application/json: - schema: - $ref: '#/components/schemas/Resource' - description: Created - "400": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Validation errors occurred - "401": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Auth token is invalid - "403": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Unauthorized to perform operation - "409": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Resource already exists - "500": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: An unexpected error occurred creating the resource - security: - - Bearer: [] - summary: Create a new resource - /api/maestro/v1/resources/{id}: + summary: Returns a list of resource bundles + /api/maestro/v1/resource-bundles/{id}: delete: parameters: - description: The id of record @@ -182,7 +134,7 @@ paths: style: simple responses: "204": - description: Resource deleted successfully + description: Resource bundle deleted successfully "400": content: application/json: @@ -206,234 +158,16 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - description: No resource with specified id exists - "500": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Unexpected error deleting resource - security: - - Bearer: [] - summary: Delete a resource - get: - parameters: - - description: The id of record - explode: false - in: path - name: id - required: true - schema: - type: string - style: simple - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/Resource' - description: Resource found by id - "401": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Auth token is invalid - "403": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Unauthorized to perform operation - "404": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: No resource with specified id exists - "500": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Unexpected error occurred - security: - - Bearer: [] - summary: Get an resource by id - patch: - parameters: - - description: The id of record - explode: false - in: path - name: id - required: true - schema: - type: string - style: simple - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/ResourcePatchRequest' - description: Updated resource data - required: true - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/Resource' - description: Resource updated successfully - "400": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Validation errors occurred - "401": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Auth token is invalid - "403": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Unauthorized to perform operation - "404": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: No resource with specified id exists - "409": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Resource already exists - "500": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Unexpected error updating resource - security: - - Bearer: [] - summary: Update an resource - /api/maestro/v1/resource-bundles: - get: - parameters: - - description: Page number of record list when record list exceeds specified - page size - explode: true - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - style: form - - description: Maximum number of records to return - explode: true - in: query - name: size - required: false - schema: - default: 100 - minimum: 0 - type: integer - style: form - - description: "Specifies the search criteria. The syntax of this parameter\ - \ is\nsimilar to the syntax of the _where_ clause of an SQL statement,\n\ - using the names of the json attributes / column names of the account. \n\ - For example, in order to retrieve all the accounts with a username\nstarting\ - \ with `my`:\n\n```sql\nusername like 'my%'\n```\n\nThe search criteria\ - \ can also be applied on related resource.\nFor example, in order to retrieve\ - \ all the subscriptions labeled by `foo=bar`,\n\n```sql\nsubscription_labels.key\ - \ = 'foo' and subscription_labels.value = 'bar'\n```\n\nIf the parameter\ - \ isn't provided, or if the value is empty, then\nall the accounts that\ - \ the user has permission to see will be\nreturned." - explode: true - in: query - name: search - required: false - schema: - type: string - style: form - - description: |- - Specifies the order by criteria. The syntax of this parameter is - similar to the syntax of the _order by_ clause of an SQL statement, - but using the names of the json attributes / column of the account. - For example, in order to retrieve all accounts ordered by username: - - ```sql - username asc - ``` - - Or in order to retrieve all accounts ordered by username _and_ first name: - - ```sql - username asc, firstName asc - ``` - - If the parameter isn't provided, or if the value is empty, then - no explicit ordering will be applied. - explode: true - in: query - name: orderBy - required: false - schema: - type: string - style: form - - description: |- - Supplies a comma-separated list of fields to be returned. - Fields of sub-structures and of arrays use . notation. - .* means all field of a structure - Example: For each Subscription to get id, href, plan(id and kind) and labels (all fields) - - ``` - ocm get subscriptions --parameter fields=id,href,plan.id,plan.kind,labels.* --parameter fetchLabels=true - ``` - explode: true - in: query - name: fields - required: false - schema: - type: string - style: form - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/ResourceBundleList' - description: A JSON array of resource bundle objects - "401": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Auth token is invalid - "403": - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: Unauthorized to perform operation + description: No resource bundle with specified id exists "500": content: application/json: schema: $ref: '#/components/schemas/Error' - description: Unexpected error occurred + description: Unexpected error deleting resource bundle security: - Bearer: [] - summary: Returns a list of resource bundles - /api/maestro/v1/resource-bundles/{id}: + summary: Delete a resource bundle get: parameters: - description: The id of record @@ -477,7 +211,7 @@ paths: description: Unexpected error occurred security: - Bearer: [] - summary: Get an resource bundle by id + summary: Get a resource bundle by id /api/maestro/v1/consumers: get: parameters: @@ -726,7 +460,7 @@ paths: description: Unexpected error occurred security: - Bearer: [] - summary: Get an consumer by id + summary: Get a consumer by id patch: parameters: - description: The id of record @@ -919,38 +653,14 @@ components: allOf: - $ref: '#/components/schemas/List' - $ref: '#/components/schemas/ErrorList_allOf' - Resource: + ResourceBundle: allOf: - $ref: '#/components/schemas/ObjectReference' - - $ref: '#/components/schemas/Resource_allOf' - ResourceList: - allOf: - - $ref: '#/components/schemas/List' - - $ref: '#/components/schemas/ResourceList_allOf' - ResourcePatchRequest: - example: - delete_option: "{}" - update_strategy: "{}" - manifest: "{}" - version: 0 - properties: - version: - type: integer - manifest: - type: object - delete_option: - type: object - update_strategy: - type: object - type: object + - $ref: '#/components/schemas/ResourceBundle_allOf' ResourceBundleList: allOf: - $ref: '#/components/schemas/List' - $ref: '#/components/schemas/ResourceBundleList_allOf' - ResourceBundle: - allOf: - - $ref: '#/components/schemas/ObjectReference' - - $ref: '#/components/schemas/ResourceBundle_allOf' Consumer: allOf: - $ref: '#/components/schemas/ObjectReference' @@ -987,51 +697,6 @@ components: type: array type: object example: null - Resource_allOf: - properties: - name: - type: string - consumer_name: - type: string - version: - type: integer - created_at: - format: date-time - type: string - updated_at: - format: date-time - type: string - deleted_at: - format: date-time - type: string - manifest: - type: object - group_resource: - type: object - delete_option: - type: object - update_strategy: - type: object - status: - type: object - type: object - example: null - ResourceList_allOf: - properties: - items: - items: - $ref: '#/components/schemas/Resource' - type: array - type: object - example: null - ResourceBundleList_allOf: - properties: - items: - items: - $ref: '#/components/schemas/ResourceBundle' - type: array - type: object - example: null ResourceBundle_allOf: properties: name: @@ -1065,6 +730,14 @@ components: type: object type: object example: null + ResourceBundleList_allOf: + properties: + items: + items: + $ref: '#/components/schemas/ResourceBundle' + type: array + type: object + example: null Consumer_allOf: properties: name: diff --git a/pkg/api/openapi/api_default.go b/pkg/api/openapi/api_default.go index 96a74e55..a2de731e 100644 --- a/pkg/api/openapi/api_default.go +++ b/pkg/api/openapi/api_default.go @@ -357,7 +357,7 @@ func (r ApiApiMaestroV1ConsumersIdGetRequest) Execute() (*Consumer, *http.Respon } /* -ApiMaestroV1ConsumersIdGet Get an consumer by id +ApiMaestroV1ConsumersIdGet Get a consumer by id @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @param id The id of record @@ -1014,494 +1014,25 @@ func (a *DefaultApiService) ApiMaestroV1ResourceBundlesGetExecute(r ApiApiMaestr return localVarReturnValue, localVarHTTPResponse, nil } -type ApiApiMaestroV1ResourceBundlesIdGetRequest struct { - ctx context.Context - ApiService *DefaultApiService - id string -} - -func (r ApiApiMaestroV1ResourceBundlesIdGetRequest) Execute() (*ResourceBundle, *http.Response, error) { - return r.ApiService.ApiMaestroV1ResourceBundlesIdGetExecute(r) -} - -/* -ApiMaestroV1ResourceBundlesIdGet Get an resource bundle by id - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @param id The id of record - @return ApiApiMaestroV1ResourceBundlesIdGetRequest -*/ -func (a *DefaultApiService) ApiMaestroV1ResourceBundlesIdGet(ctx context.Context, id string) ApiApiMaestroV1ResourceBundlesIdGetRequest { - return ApiApiMaestroV1ResourceBundlesIdGetRequest{ - ApiService: a, - ctx: ctx, - id: id, - } -} - -// Execute executes the request -// -// @return ResourceBundle -func (a *DefaultApiService) ApiMaestroV1ResourceBundlesIdGetExecute(r ApiApiMaestroV1ResourceBundlesIdGetRequest) (*ResourceBundle, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *ResourceBundle - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.ApiMaestroV1ResourceBundlesIdGet") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/api/maestro/v1/resource-bundles/{id}" - localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1) - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - if localVarHTTPResponse.StatusCode == 401 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 403 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 404 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 500 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiApiMaestroV1ResourcesGetRequest struct { - ctx context.Context - ApiService *DefaultApiService - page *int32 - size *int32 - search *string - orderBy *string - fields *string -} - -// Page number of record list when record list exceeds specified page size -func (r ApiApiMaestroV1ResourcesGetRequest) Page(page int32) ApiApiMaestroV1ResourcesGetRequest { - r.page = &page - return r -} - -// Maximum number of records to return -func (r ApiApiMaestroV1ResourcesGetRequest) Size(size int32) ApiApiMaestroV1ResourcesGetRequest { - r.size = &size - return r -} - -// Specifies the search criteria. The syntax of this parameter is similar to the syntax of the _where_ clause of an SQL statement, using the names of the json attributes / column names of the account. For example, in order to retrieve all the accounts with a username starting with `my`: ```sql username like 'my%' ``` The search criteria can also be applied on related resource. For example, in order to retrieve all the subscriptions labeled by `foo=bar`, ```sql subscription_labels.key = 'foo' and subscription_labels.value = 'bar' ``` If the parameter isn't provided, or if the value is empty, then all the accounts that the user has permission to see will be returned. -func (r ApiApiMaestroV1ResourcesGetRequest) Search(search string) ApiApiMaestroV1ResourcesGetRequest { - r.search = &search - return r -} - -// Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the _order by_ clause of an SQL statement, but using the names of the json attributes / column of the account. For example, in order to retrieve all accounts ordered by username: ```sql username asc ``` Or in order to retrieve all accounts ordered by username _and_ first name: ```sql username asc, firstName asc ``` If the parameter isn't provided, or if the value is empty, then no explicit ordering will be applied. -func (r ApiApiMaestroV1ResourcesGetRequest) OrderBy(orderBy string) ApiApiMaestroV1ResourcesGetRequest { - r.orderBy = &orderBy - return r -} - -// Supplies a comma-separated list of fields to be returned. Fields of sub-structures and of arrays use <structure>.<field> notation. <stucture>.* means all field of a structure Example: For each Subscription to get id, href, plan(id and kind) and labels (all fields) ``` ocm get subscriptions --parameter fields=id,href,plan.id,plan.kind,labels.* --parameter fetchLabels=true ``` -func (r ApiApiMaestroV1ResourcesGetRequest) Fields(fields string) ApiApiMaestroV1ResourcesGetRequest { - r.fields = &fields - return r -} - -func (r ApiApiMaestroV1ResourcesGetRequest) Execute() (*ResourceList, *http.Response, error) { - return r.ApiService.ApiMaestroV1ResourcesGetExecute(r) -} - -/* -ApiMaestroV1ResourcesGet Returns a list of resources - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return ApiApiMaestroV1ResourcesGetRequest -*/ -func (a *DefaultApiService) ApiMaestroV1ResourcesGet(ctx context.Context) ApiApiMaestroV1ResourcesGetRequest { - return ApiApiMaestroV1ResourcesGetRequest{ - ApiService: a, - ctx: ctx, - } -} - -// Execute executes the request -// -// @return ResourceList -func (a *DefaultApiService) ApiMaestroV1ResourcesGetExecute(r ApiApiMaestroV1ResourcesGetRequest) (*ResourceList, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *ResourceList - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.ApiMaestroV1ResourcesGet") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/api/maestro/v1/resources" - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - if r.page != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "page", r.page, "") - } - if r.size != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "size", r.size, "") - } - if r.search != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "search", r.search, "") - } - if r.orderBy != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "orderBy", r.orderBy, "") - } - if r.fields != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "fields", r.fields, "") - } - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - if localVarHTTPResponse.StatusCode == 401 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 403 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 500 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiApiMaestroV1ResourcesIdDeleteRequest struct { - ctx context.Context - ApiService *DefaultApiService - id string -} - -func (r ApiApiMaestroV1ResourcesIdDeleteRequest) Execute() (*http.Response, error) { - return r.ApiService.ApiMaestroV1ResourcesIdDeleteExecute(r) -} - -/* -ApiMaestroV1ResourcesIdDelete Delete a resource - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @param id The id of record - @return ApiApiMaestroV1ResourcesIdDeleteRequest -*/ -func (a *DefaultApiService) ApiMaestroV1ResourcesIdDelete(ctx context.Context, id string) ApiApiMaestroV1ResourcesIdDeleteRequest { - return ApiApiMaestroV1ResourcesIdDeleteRequest{ - ApiService: a, - ctx: ctx, - id: id, - } -} - -// Execute executes the request -func (a *DefaultApiService) ApiMaestroV1ResourcesIdDeleteExecute(r ApiApiMaestroV1ResourcesIdDeleteRequest) (*http.Response, error) { - var ( - localVarHTTPMethod = http.MethodDelete - localVarPostBody interface{} - formFiles []formFile - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.ApiMaestroV1ResourcesIdDelete") - if err != nil { - return nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/api/maestro/v1/resources/{id}" - localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1) - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - if localVarHTTPResponse.StatusCode == 400 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 401 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 403 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 404 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 500 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - } - return localVarHTTPResponse, newErr - } - - return localVarHTTPResponse, nil -} - -type ApiApiMaestroV1ResourcesIdGetRequest struct { +type ApiApiMaestroV1ResourceBundlesIdDeleteRequest struct { ctx context.Context ApiService *DefaultApiService id string } -func (r ApiApiMaestroV1ResourcesIdGetRequest) Execute() (*Resource, *http.Response, error) { - return r.ApiService.ApiMaestroV1ResourcesIdGetExecute(r) +func (r ApiApiMaestroV1ResourceBundlesIdDeleteRequest) Execute() (*http.Response, error) { + return r.ApiService.ApiMaestroV1ResourceBundlesIdDeleteExecute(r) } /* -ApiMaestroV1ResourcesIdGet Get an resource by id +ApiMaestroV1ResourceBundlesIdDelete Delete a resource bundle @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @param id The id of record - @return ApiApiMaestroV1ResourcesIdGetRequest + @return ApiApiMaestroV1ResourceBundlesIdDeleteRequest */ -func (a *DefaultApiService) ApiMaestroV1ResourcesIdGet(ctx context.Context, id string) ApiApiMaestroV1ResourcesIdGetRequest { - return ApiApiMaestroV1ResourcesIdGetRequest{ +func (a *DefaultApiService) ApiMaestroV1ResourceBundlesIdDelete(ctx context.Context, id string) ApiApiMaestroV1ResourceBundlesIdDeleteRequest { + return ApiApiMaestroV1ResourceBundlesIdDeleteRequest{ ApiService: a, ctx: ctx, id: id, @@ -1509,22 +1040,19 @@ func (a *DefaultApiService) ApiMaestroV1ResourcesIdGet(ctx context.Context, id s } // Execute executes the request -// -// @return Resource -func (a *DefaultApiService) ApiMaestroV1ResourcesIdGetExecute(r ApiApiMaestroV1ResourcesIdGetRequest) (*Resource, *http.Response, error) { +func (a *DefaultApiService) ApiMaestroV1ResourceBundlesIdDeleteExecute(r ApiApiMaestroV1ResourceBundlesIdDeleteRequest) (*http.Response, error) { var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *Resource + localVarHTTPMethod = http.MethodDelete + localVarPostBody interface{} + formFiles []formFile ) - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.ApiMaestroV1ResourcesIdGet") + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.ApiMaestroV1ResourceBundlesIdDelete") if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + return nil, &GenericOpenAPIError{error: err.Error()} } - localVarPath := localBasePath + "/api/maestro/v1/resources/{id}" + localVarPath := localBasePath + "/api/maestro/v1/resource-bundles/{id}" localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1) localVarHeaderParams := make(map[string]string) @@ -1550,176 +1078,19 @@ func (a *DefaultApiService) ApiMaestroV1ResourcesIdGetExecute(r ApiApiMaestroV1R } req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - if localVarHTTPResponse.StatusCode == 401 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 403 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 404 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 500 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiApiMaestroV1ResourcesIdPatchRequest struct { - ctx context.Context - ApiService *DefaultApiService - id string - resourcePatchRequest *ResourcePatchRequest -} - -// Updated resource data -func (r ApiApiMaestroV1ResourcesIdPatchRequest) ResourcePatchRequest(resourcePatchRequest ResourcePatchRequest) ApiApiMaestroV1ResourcesIdPatchRequest { - r.resourcePatchRequest = &resourcePatchRequest - return r -} - -func (r ApiApiMaestroV1ResourcesIdPatchRequest) Execute() (*Resource, *http.Response, error) { - return r.ApiService.ApiMaestroV1ResourcesIdPatchExecute(r) -} - -/* -ApiMaestroV1ResourcesIdPatch Update an resource - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @param id The id of record - @return ApiApiMaestroV1ResourcesIdPatchRequest -*/ -func (a *DefaultApiService) ApiMaestroV1ResourcesIdPatch(ctx context.Context, id string) ApiApiMaestroV1ResourcesIdPatchRequest { - return ApiApiMaestroV1ResourcesIdPatchRequest{ - ApiService: a, - ctx: ctx, - id: id, - } -} - -// Execute executes the request -// -// @return Resource -func (a *DefaultApiService) ApiMaestroV1ResourcesIdPatchExecute(r ApiApiMaestroV1ResourcesIdPatchRequest) (*Resource, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodPatch - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *Resource - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.ApiMaestroV1ResourcesIdPatch") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/api/maestro/v1/resources/{id}" - localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1) - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - if r.resourcePatchRequest == nil { - return localVarReturnValue, nil, reportError("resourcePatchRequest is required and must be specified") - } - - // to determine the Content-Type header - localVarHTTPContentTypes := []string{"application/json"} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - // body params - localVarPostBody = r.resourcePatchRequest - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err + return nil, err } localVarHTTPResponse, err := a.client.callAPI(req) if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err + return localVarHTTPResponse, err } localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) localVarHTTPResponse.Body.Close() localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) if err != nil { - return localVarReturnValue, localVarHTTPResponse, err + return localVarHTTPResponse, err } if localVarHTTPResponse.StatusCode >= 300 { @@ -1732,137 +1103,111 @@ func (a *DefaultApiService) ApiMaestroV1ResourcesIdPatchExecute(r ApiApiMaestroV err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } if localVarHTTPResponse.StatusCode == 401 { var v Error err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } if localVarHTTPResponse.StatusCode == 403 { var v Error err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } if localVarHTTPResponse.StatusCode == 404 { var v Error err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - if localVarHTTPResponse.StatusCode == 409 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } if localVarHTTPResponse.StatusCode == 500 { var v Error err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) newErr.model = v } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr + return localVarHTTPResponse, newErr } - return localVarReturnValue, localVarHTTPResponse, nil + return localVarHTTPResponse, nil } -type ApiApiMaestroV1ResourcesPostRequest struct { +type ApiApiMaestroV1ResourceBundlesIdGetRequest struct { ctx context.Context ApiService *DefaultApiService - resource *Resource -} - -// Resource data -func (r ApiApiMaestroV1ResourcesPostRequest) Resource(resource Resource) ApiApiMaestroV1ResourcesPostRequest { - r.resource = &resource - return r + id string } -func (r ApiApiMaestroV1ResourcesPostRequest) Execute() (*Resource, *http.Response, error) { - return r.ApiService.ApiMaestroV1ResourcesPostExecute(r) +func (r ApiApiMaestroV1ResourceBundlesIdGetRequest) Execute() (*ResourceBundle, *http.Response, error) { + return r.ApiService.ApiMaestroV1ResourceBundlesIdGetExecute(r) } /* -ApiMaestroV1ResourcesPost Create a new resource +ApiMaestroV1ResourceBundlesIdGet Get a resource bundle by id @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return ApiApiMaestroV1ResourcesPostRequest + @param id The id of record + @return ApiApiMaestroV1ResourceBundlesIdGetRequest */ -func (a *DefaultApiService) ApiMaestroV1ResourcesPost(ctx context.Context) ApiApiMaestroV1ResourcesPostRequest { - return ApiApiMaestroV1ResourcesPostRequest{ +func (a *DefaultApiService) ApiMaestroV1ResourceBundlesIdGet(ctx context.Context, id string) ApiApiMaestroV1ResourceBundlesIdGetRequest { + return ApiApiMaestroV1ResourceBundlesIdGetRequest{ ApiService: a, ctx: ctx, + id: id, } } // Execute executes the request // -// @return Resource -func (a *DefaultApiService) ApiMaestroV1ResourcesPostExecute(r ApiApiMaestroV1ResourcesPostRequest) (*Resource, *http.Response, error) { +// @return ResourceBundle +func (a *DefaultApiService) ApiMaestroV1ResourceBundlesIdGetExecute(r ApiApiMaestroV1ResourceBundlesIdGetRequest) (*ResourceBundle, *http.Response, error) { var ( - localVarHTTPMethod = http.MethodPost + localVarHTTPMethod = http.MethodGet localVarPostBody interface{} formFiles []formFile - localVarReturnValue *Resource + localVarReturnValue *ResourceBundle ) - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.ApiMaestroV1ResourcesPost") + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.ApiMaestroV1ResourceBundlesIdGet") if err != nil { return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} } - localVarPath := localBasePath + "/api/maestro/v1/resources" + localVarPath := localBasePath + "/api/maestro/v1/resource-bundles/{id}" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1) localVarHeaderParams := make(map[string]string) localVarQueryParams := url.Values{} localVarFormParams := url.Values{} - if r.resource == nil { - return localVarReturnValue, nil, reportError("resource is required and must be specified") - } // to determine the Content-Type header - localVarHTTPContentTypes := []string{"application/json"} + localVarHTTPContentTypes := []string{} // set Content-Type header localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) @@ -1878,8 +1223,6 @@ func (a *DefaultApiService) ApiMaestroV1ResourcesPostExecute(r ApiApiMaestroV1Re if localVarHTTPHeaderAccept != "" { localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept } - // body params - localVarPostBody = r.resource req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) if err != nil { return localVarReturnValue, nil, err @@ -1902,17 +1245,6 @@ func (a *DefaultApiService) ApiMaestroV1ResourcesPostExecute(r ApiApiMaestroV1Re body: localVarBody, error: localVarHTTPResponse.Status, } - if localVarHTTPResponse.StatusCode == 400 { - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } if localVarHTTPResponse.StatusCode == 401 { var v Error err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) @@ -1935,7 +1267,7 @@ func (a *DefaultApiService) ApiMaestroV1ResourcesPostExecute(r ApiApiMaestroV1Re newErr.model = v return localVarReturnValue, localVarHTTPResponse, newErr } - if localVarHTTPResponse.StatusCode == 409 { + if localVarHTTPResponse.StatusCode == 404 { var v Error err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) if err != nil { diff --git a/pkg/api/openapi/docs/DefaultApi.md b/pkg/api/openapi/docs/DefaultApi.md index 093e9108..862a1c38 100644 --- a/pkg/api/openapi/docs/DefaultApi.md +++ b/pkg/api/openapi/docs/DefaultApi.md @@ -6,16 +6,12 @@ Method | HTTP request | Description ------------- | ------------- | ------------- [**ApiMaestroV1ConsumersGet**](DefaultApi.md#ApiMaestroV1ConsumersGet) | **Get** /api/maestro/v1/consumers | Returns a list of consumers [**ApiMaestroV1ConsumersIdDelete**](DefaultApi.md#ApiMaestroV1ConsumersIdDelete) | **Delete** /api/maestro/v1/consumers/{id} | Delete a consumer -[**ApiMaestroV1ConsumersIdGet**](DefaultApi.md#ApiMaestroV1ConsumersIdGet) | **Get** /api/maestro/v1/consumers/{id} | Get an consumer by id +[**ApiMaestroV1ConsumersIdGet**](DefaultApi.md#ApiMaestroV1ConsumersIdGet) | **Get** /api/maestro/v1/consumers/{id} | Get a consumer by id [**ApiMaestroV1ConsumersIdPatch**](DefaultApi.md#ApiMaestroV1ConsumersIdPatch) | **Patch** /api/maestro/v1/consumers/{id} | Update an consumer [**ApiMaestroV1ConsumersPost**](DefaultApi.md#ApiMaestroV1ConsumersPost) | **Post** /api/maestro/v1/consumers | Create a new consumer [**ApiMaestroV1ResourceBundlesGet**](DefaultApi.md#ApiMaestroV1ResourceBundlesGet) | **Get** /api/maestro/v1/resource-bundles | Returns a list of resource bundles -[**ApiMaestroV1ResourceBundlesIdGet**](DefaultApi.md#ApiMaestroV1ResourceBundlesIdGet) | **Get** /api/maestro/v1/resource-bundles/{id} | Get an resource bundle by id -[**ApiMaestroV1ResourcesGet**](DefaultApi.md#ApiMaestroV1ResourcesGet) | **Get** /api/maestro/v1/resources | Returns a list of resources -[**ApiMaestroV1ResourcesIdDelete**](DefaultApi.md#ApiMaestroV1ResourcesIdDelete) | **Delete** /api/maestro/v1/resources/{id} | Delete a resource -[**ApiMaestroV1ResourcesIdGet**](DefaultApi.md#ApiMaestroV1ResourcesIdGet) | **Get** /api/maestro/v1/resources/{id} | Get an resource by id -[**ApiMaestroV1ResourcesIdPatch**](DefaultApi.md#ApiMaestroV1ResourcesIdPatch) | **Patch** /api/maestro/v1/resources/{id} | Update an resource -[**ApiMaestroV1ResourcesPost**](DefaultApi.md#ApiMaestroV1ResourcesPost) | **Post** /api/maestro/v1/resources | Create a new resource +[**ApiMaestroV1ResourceBundlesIdDelete**](DefaultApi.md#ApiMaestroV1ResourceBundlesIdDelete) | **Delete** /api/maestro/v1/resource-bundles/{id} | Delete a resource bundle +[**ApiMaestroV1ResourceBundlesIdGet**](DefaultApi.md#ApiMaestroV1ResourceBundlesIdGet) | **Get** /api/maestro/v1/resource-bundles/{id} | Get a resource bundle by id @@ -161,7 +157,7 @@ Name | Type | Description | Notes > Consumer ApiMaestroV1ConsumersIdGet(ctx, id).Execute() -Get an consumer by id +Get a consumer by id ### Example @@ -431,151 +427,11 @@ Name | Type | Description | Notes [[Back to README]](../README.md) -## ApiMaestroV1ResourceBundlesIdGet - -> ResourceBundle ApiMaestroV1ResourceBundlesIdGet(ctx, id).Execute() - -Get an resource bundle by id - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID" -) - -func main() { - id := "id_example" // string | The id of record - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(context.Background(), id).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.ApiMaestroV1ResourceBundlesIdGet``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `ApiMaestroV1ResourceBundlesIdGet`: ResourceBundle - fmt.Fprintf(os.Stdout, "Response from `DefaultApi.ApiMaestroV1ResourceBundlesIdGet`: %v\n", resp) -} -``` - -### Path Parameters - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- -**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. -**id** | **string** | The id of record | - -### Other Parameters - -Other parameters are passed through a pointer to a apiApiMaestroV1ResourceBundlesIdGetRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - - -### Return type - -[**ResourceBundle**](ResourceBundle.md) - -### Authorization - -[Bearer](../README.md#Bearer) - -### HTTP request headers - -- **Content-Type**: Not defined -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - -## ApiMaestroV1ResourcesGet - -> ResourceList ApiMaestroV1ResourcesGet(ctx).Page(page).Size(size).Search(search).OrderBy(orderBy).Fields(fields).Execute() - -Returns a list of resources - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID" -) - -func main() { - page := int32(56) // int32 | Page number of record list when record list exceeds specified page size (optional) (default to 1) - size := int32(56) // int32 | Maximum number of records to return (optional) (default to 100) - search := "search_example" // string | Specifies the search criteria. The syntax of this parameter is similar to the syntax of the _where_ clause of an SQL statement, using the names of the json attributes / column names of the account. For example, in order to retrieve all the accounts with a username starting with `my`: ```sql username like 'my%' ``` The search criteria can also be applied on related resource. For example, in order to retrieve all the subscriptions labeled by `foo=bar`, ```sql subscription_labels.key = 'foo' and subscription_labels.value = 'bar' ``` If the parameter isn't provided, or if the value is empty, then all the accounts that the user has permission to see will be returned. (optional) - orderBy := "orderBy_example" // string | Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the _order by_ clause of an SQL statement, but using the names of the json attributes / column of the account. For example, in order to retrieve all accounts ordered by username: ```sql username asc ``` Or in order to retrieve all accounts ordered by username _and_ first name: ```sql username asc, firstName asc ``` If the parameter isn't provided, or if the value is empty, then no explicit ordering will be applied. (optional) - fields := "fields_example" // string | Supplies a comma-separated list of fields to be returned. Fields of sub-structures and of arrays use . notation. .* means all field of a structure Example: For each Subscription to get id, href, plan(id and kind) and labels (all fields) ``` ocm get subscriptions --parameter fields=id,href,plan.id,plan.kind,labels.* --parameter fetchLabels=true ``` (optional) - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultApi.ApiMaestroV1ResourcesGet(context.Background()).Page(page).Size(size).Search(search).OrderBy(orderBy).Fields(fields).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.ApiMaestroV1ResourcesGet``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `ApiMaestroV1ResourcesGet`: ResourceList - fmt.Fprintf(os.Stdout, "Response from `DefaultApi.ApiMaestroV1ResourcesGet`: %v\n", resp) -} -``` - -### Path Parameters - - - -### Other Parameters - -Other parameters are passed through a pointer to a apiApiMaestroV1ResourcesGetRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **page** | **int32** | Page number of record list when record list exceeds specified page size | [default to 1] - **size** | **int32** | Maximum number of records to return | [default to 100] - **search** | **string** | Specifies the search criteria. The syntax of this parameter is similar to the syntax of the _where_ clause of an SQL statement, using the names of the json attributes / column names of the account. For example, in order to retrieve all the accounts with a username starting with `my`: ```sql username like 'my%' ``` The search criteria can also be applied on related resource. For example, in order to retrieve all the subscriptions labeled by `foo=bar`, ```sql subscription_labels.key = 'foo' and subscription_labels.value = 'bar' ``` If the parameter isn't provided, or if the value is empty, then all the accounts that the user has permission to see will be returned. | - **orderBy** | **string** | Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the _order by_ clause of an SQL statement, but using the names of the json attributes / column of the account. For example, in order to retrieve all accounts ordered by username: ```sql username asc ``` Or in order to retrieve all accounts ordered by username _and_ first name: ```sql username asc, firstName asc ``` If the parameter isn't provided, or if the value is empty, then no explicit ordering will be applied. | - **fields** | **string** | Supplies a comma-separated list of fields to be returned. Fields of sub-structures and of arrays use <structure>.<field> notation. <stucture>.* means all field of a structure Example: For each Subscription to get id, href, plan(id and kind) and labels (all fields) ``` ocm get subscriptions --parameter fields=id,href,plan.id,plan.kind,labels.* --parameter fetchLabels=true ``` | - -### Return type - -[**ResourceList**](ResourceList.md) - -### Authorization - -[Bearer](../README.md#Bearer) - -### HTTP request headers - -- **Content-Type**: Not defined -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - +## ApiMaestroV1ResourceBundlesIdDelete -## ApiMaestroV1ResourcesIdDelete +> ApiMaestroV1ResourceBundlesIdDelete(ctx, id).Execute() -> ApiMaestroV1ResourcesIdDelete(ctx, id).Execute() - -Delete a resource +Delete a resource bundle ### Example @@ -594,9 +450,9 @@ func main() { configuration := openapiclient.NewConfiguration() apiClient := openapiclient.NewAPIClient(configuration) - r, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(context.Background(), id).Execute() + r, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdDelete(context.Background(), id).Execute() if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.ApiMaestroV1ResourcesIdDelete``: %v\n", err) + fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.ApiMaestroV1ResourceBundlesIdDelete``: %v\n", err) fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) } } @@ -612,7 +468,7 @@ Name | Type | Description | Notes ### Other Parameters -Other parameters are passed through a pointer to a apiApiMaestroV1ResourcesIdDeleteRequest struct via the builder pattern +Other parameters are passed through a pointer to a apiApiMaestroV1ResourceBundlesIdDeleteRequest struct via the builder pattern Name | Type | Description | Notes @@ -637,11 +493,11 @@ Name | Type | Description | Notes [[Back to README]](../README.md) -## ApiMaestroV1ResourcesIdGet +## ApiMaestroV1ResourceBundlesIdGet -> Resource ApiMaestroV1ResourcesIdGet(ctx, id).Execute() +> ResourceBundle ApiMaestroV1ResourceBundlesIdGet(ctx, id).Execute() -Get an resource by id +Get a resource bundle by id ### Example @@ -660,13 +516,13 @@ func main() { configuration := openapiclient.NewConfiguration() apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(context.Background(), id).Execute() + resp, r, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(context.Background(), id).Execute() if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.ApiMaestroV1ResourcesIdGet``: %v\n", err) + fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.ApiMaestroV1ResourceBundlesIdGet``: %v\n", err) fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) } - // response from `ApiMaestroV1ResourcesIdGet`: Resource - fmt.Fprintf(os.Stdout, "Response from `DefaultApi.ApiMaestroV1ResourcesIdGet`: %v\n", resp) + // response from `ApiMaestroV1ResourceBundlesIdGet`: ResourceBundle + fmt.Fprintf(os.Stdout, "Response from `DefaultApi.ApiMaestroV1ResourceBundlesIdGet`: %v\n", resp) } ``` @@ -680,7 +536,7 @@ Name | Type | Description | Notes ### Other Parameters -Other parameters are passed through a pointer to a apiApiMaestroV1ResourcesIdGetRequest struct via the builder pattern +Other parameters are passed through a pointer to a apiApiMaestroV1ResourceBundlesIdGetRequest struct via the builder pattern Name | Type | Description | Notes @@ -689,7 +545,7 @@ Name | Type | Description | Notes ### Return type -[**Resource**](Resource.md) +[**ResourceBundle**](ResourceBundle.md) ### Authorization @@ -704,137 +560,3 @@ Name | Type | Description | Notes [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - -## ApiMaestroV1ResourcesIdPatch - -> Resource ApiMaestroV1ResourcesIdPatch(ctx, id).ResourcePatchRequest(resourcePatchRequest).Execute() - -Update an resource - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID" -) - -func main() { - id := "id_example" // string | The id of record - resourcePatchRequest := *openapiclient.NewResourcePatchRequest() // ResourcePatchRequest | Updated resource data - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdPatch(context.Background(), id).ResourcePatchRequest(resourcePatchRequest).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.ApiMaestroV1ResourcesIdPatch``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `ApiMaestroV1ResourcesIdPatch`: Resource - fmt.Fprintf(os.Stdout, "Response from `DefaultApi.ApiMaestroV1ResourcesIdPatch`: %v\n", resp) -} -``` - -### Path Parameters - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- -**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. -**id** | **string** | The id of record | - -### Other Parameters - -Other parameters are passed through a pointer to a apiApiMaestroV1ResourcesIdPatchRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - - **resourcePatchRequest** | [**ResourcePatchRequest**](ResourcePatchRequest.md) | Updated resource data | - -### Return type - -[**Resource**](Resource.md) - -### Authorization - -[Bearer](../README.md#Bearer) - -### HTTP request headers - -- **Content-Type**: application/json -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - -## ApiMaestroV1ResourcesPost - -> Resource ApiMaestroV1ResourcesPost(ctx).Resource(resource).Execute() - -Create a new resource - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID" -) - -func main() { - resource := *openapiclient.NewResource() // Resource | Resource data - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultApi.ApiMaestroV1ResourcesPost(context.Background()).Resource(resource).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.ApiMaestroV1ResourcesPost``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `ApiMaestroV1ResourcesPost`: Resource - fmt.Fprintf(os.Stdout, "Response from `DefaultApi.ApiMaestroV1ResourcesPost`: %v\n", resp) -} -``` - -### Path Parameters - - - -### Other Parameters - -Other parameters are passed through a pointer to a apiApiMaestroV1ResourcesPostRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **resource** | [**Resource**](Resource.md) | Resource data | - -### Return type - -[**Resource**](Resource.md) - -### Authorization - -[Bearer](../README.md#Bearer) - -### HTTP request headers - -- **Content-Type**: application/json -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - diff --git a/pkg/api/openapi/docs/Resource.md b/pkg/api/openapi/docs/Resource.md deleted file mode 100644 index 5af52e9a..00000000 --- a/pkg/api/openapi/docs/Resource.md +++ /dev/null @@ -1,394 +0,0 @@ -# Resource - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Id** | Pointer to **string** | | [optional] -**Kind** | Pointer to **string** | | [optional] -**Href** | Pointer to **string** | | [optional] -**Name** | Pointer to **string** | | [optional] -**ConsumerName** | Pointer to **string** | | [optional] -**Version** | Pointer to **int32** | | [optional] -**CreatedAt** | Pointer to **time.Time** | | [optional] -**UpdatedAt** | Pointer to **time.Time** | | [optional] -**DeletedAt** | Pointer to **time.Time** | | [optional] -**Manifest** | Pointer to **map[string]interface{}** | | [optional] -**GroupResource** | Pointer to **map[string]interface{}** | | [optional] -**DeleteOption** | Pointer to **map[string]interface{}** | | [optional] -**UpdateStrategy** | Pointer to **map[string]interface{}** | | [optional] -**Status** | Pointer to **map[string]interface{}** | | [optional] - -## Methods - -### NewResource - -`func NewResource() *Resource` - -NewResource instantiates a new Resource object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewResourceWithDefaults - -`func NewResourceWithDefaults() *Resource` - -NewResourceWithDefaults instantiates a new Resource object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetId - -`func (o *Resource) GetId() string` - -GetId returns the Id field if non-nil, zero value otherwise. - -### GetIdOk - -`func (o *Resource) GetIdOk() (*string, bool)` - -GetIdOk returns a tuple with the Id field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetId - -`func (o *Resource) SetId(v string)` - -SetId sets Id field to given value. - -### HasId - -`func (o *Resource) HasId() bool` - -HasId returns a boolean if a field has been set. - -### GetKind - -`func (o *Resource) GetKind() string` - -GetKind returns the Kind field if non-nil, zero value otherwise. - -### GetKindOk - -`func (o *Resource) GetKindOk() (*string, bool)` - -GetKindOk returns a tuple with the Kind field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetKind - -`func (o *Resource) SetKind(v string)` - -SetKind sets Kind field to given value. - -### HasKind - -`func (o *Resource) HasKind() bool` - -HasKind returns a boolean if a field has been set. - -### GetHref - -`func (o *Resource) GetHref() string` - -GetHref returns the Href field if non-nil, zero value otherwise. - -### GetHrefOk - -`func (o *Resource) GetHrefOk() (*string, bool)` - -GetHrefOk returns a tuple with the Href field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetHref - -`func (o *Resource) SetHref(v string)` - -SetHref sets Href field to given value. - -### HasHref - -`func (o *Resource) HasHref() bool` - -HasHref returns a boolean if a field has been set. - -### GetName - -`func (o *Resource) GetName() string` - -GetName returns the Name field if non-nil, zero value otherwise. - -### GetNameOk - -`func (o *Resource) GetNameOk() (*string, bool)` - -GetNameOk returns a tuple with the Name field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetName - -`func (o *Resource) SetName(v string)` - -SetName sets Name field to given value. - -### HasName - -`func (o *Resource) HasName() bool` - -HasName returns a boolean if a field has been set. - -### GetConsumerName - -`func (o *Resource) GetConsumerName() string` - -GetConsumerName returns the ConsumerName field if non-nil, zero value otherwise. - -### GetConsumerNameOk - -`func (o *Resource) GetConsumerNameOk() (*string, bool)` - -GetConsumerNameOk returns a tuple with the ConsumerName field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetConsumerName - -`func (o *Resource) SetConsumerName(v string)` - -SetConsumerName sets ConsumerName field to given value. - -### HasConsumerName - -`func (o *Resource) HasConsumerName() bool` - -HasConsumerName returns a boolean if a field has been set. - -### GetVersion - -`func (o *Resource) GetVersion() int32` - -GetVersion returns the Version field if non-nil, zero value otherwise. - -### GetVersionOk - -`func (o *Resource) GetVersionOk() (*int32, bool)` - -GetVersionOk returns a tuple with the Version field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetVersion - -`func (o *Resource) SetVersion(v int32)` - -SetVersion sets Version field to given value. - -### HasVersion - -`func (o *Resource) HasVersion() bool` - -HasVersion returns a boolean if a field has been set. - -### GetCreatedAt - -`func (o *Resource) GetCreatedAt() time.Time` - -GetCreatedAt returns the CreatedAt field if non-nil, zero value otherwise. - -### GetCreatedAtOk - -`func (o *Resource) GetCreatedAtOk() (*time.Time, bool)` - -GetCreatedAtOk returns a tuple with the CreatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetCreatedAt - -`func (o *Resource) SetCreatedAt(v time.Time)` - -SetCreatedAt sets CreatedAt field to given value. - -### HasCreatedAt - -`func (o *Resource) HasCreatedAt() bool` - -HasCreatedAt returns a boolean if a field has been set. - -### GetUpdatedAt - -`func (o *Resource) GetUpdatedAt() time.Time` - -GetUpdatedAt returns the UpdatedAt field if non-nil, zero value otherwise. - -### GetUpdatedAtOk - -`func (o *Resource) GetUpdatedAtOk() (*time.Time, bool)` - -GetUpdatedAtOk returns a tuple with the UpdatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetUpdatedAt - -`func (o *Resource) SetUpdatedAt(v time.Time)` - -SetUpdatedAt sets UpdatedAt field to given value. - -### HasUpdatedAt - -`func (o *Resource) HasUpdatedAt() bool` - -HasUpdatedAt returns a boolean if a field has been set. - -### GetDeletedAt - -`func (o *Resource) GetDeletedAt() time.Time` - -GetDeletedAt returns the DeletedAt field if non-nil, zero value otherwise. - -### GetDeletedAtOk - -`func (o *Resource) GetDeletedAtOk() (*time.Time, bool)` - -GetDeletedAtOk returns a tuple with the DeletedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetDeletedAt - -`func (o *Resource) SetDeletedAt(v time.Time)` - -SetDeletedAt sets DeletedAt field to given value. - -### HasDeletedAt - -`func (o *Resource) HasDeletedAt() bool` - -HasDeletedAt returns a boolean if a field has been set. - -### GetManifest - -`func (o *Resource) GetManifest() map[string]interface{}` - -GetManifest returns the Manifest field if non-nil, zero value otherwise. - -### GetManifestOk - -`func (o *Resource) GetManifestOk() (*map[string]interface{}, bool)` - -GetManifestOk returns a tuple with the Manifest field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetManifest - -`func (o *Resource) SetManifest(v map[string]interface{})` - -SetManifest sets Manifest field to given value. - -### HasManifest - -`func (o *Resource) HasManifest() bool` - -HasManifest returns a boolean if a field has been set. - -### GetGroupResource - -`func (o *Resource) GetGroupResource() map[string]interface{}` - -GetGroupResource returns the GroupResource field if non-nil, zero value otherwise. - -### GetGroupResourceOk - -`func (o *Resource) GetGroupResourceOk() (*map[string]interface{}, bool)` - -GetGroupResourceOk returns a tuple with the GroupResource field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetGroupResource - -`func (o *Resource) SetGroupResource(v map[string]interface{})` - -SetGroupResource sets GroupResource field to given value. - -### HasGroupResource - -`func (o *Resource) HasGroupResource() bool` - -HasGroupResource returns a boolean if a field has been set. - -### GetDeleteOption - -`func (o *Resource) GetDeleteOption() map[string]interface{}` - -GetDeleteOption returns the DeleteOption field if non-nil, zero value otherwise. - -### GetDeleteOptionOk - -`func (o *Resource) GetDeleteOptionOk() (*map[string]interface{}, bool)` - -GetDeleteOptionOk returns a tuple with the DeleteOption field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetDeleteOption - -`func (o *Resource) SetDeleteOption(v map[string]interface{})` - -SetDeleteOption sets DeleteOption field to given value. - -### HasDeleteOption - -`func (o *Resource) HasDeleteOption() bool` - -HasDeleteOption returns a boolean if a field has been set. - -### GetUpdateStrategy - -`func (o *Resource) GetUpdateStrategy() map[string]interface{}` - -GetUpdateStrategy returns the UpdateStrategy field if non-nil, zero value otherwise. - -### GetUpdateStrategyOk - -`func (o *Resource) GetUpdateStrategyOk() (*map[string]interface{}, bool)` - -GetUpdateStrategyOk returns a tuple with the UpdateStrategy field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetUpdateStrategy - -`func (o *Resource) SetUpdateStrategy(v map[string]interface{})` - -SetUpdateStrategy sets UpdateStrategy field to given value. - -### HasUpdateStrategy - -`func (o *Resource) HasUpdateStrategy() bool` - -HasUpdateStrategy returns a boolean if a field has been set. - -### GetStatus - -`func (o *Resource) GetStatus() map[string]interface{}` - -GetStatus returns the Status field if non-nil, zero value otherwise. - -### GetStatusOk - -`func (o *Resource) GetStatusOk() (*map[string]interface{}, bool)` - -GetStatusOk returns a tuple with the Status field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetStatus - -`func (o *Resource) SetStatus(v map[string]interface{})` - -SetStatus sets Status field to given value. - -### HasStatus - -`func (o *Resource) HasStatus() bool` - -HasStatus returns a boolean if a field has been set. - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/pkg/api/openapi/docs/ResourceAllOf.md b/pkg/api/openapi/docs/ResourceAllOf.md deleted file mode 100644 index 028f47ee..00000000 --- a/pkg/api/openapi/docs/ResourceAllOf.md +++ /dev/null @@ -1,316 +0,0 @@ -# ResourceAllOf - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Name** | Pointer to **string** | | [optional] -**ConsumerName** | Pointer to **string** | | [optional] -**Version** | Pointer to **int32** | | [optional] -**CreatedAt** | Pointer to **time.Time** | | [optional] -**UpdatedAt** | Pointer to **time.Time** | | [optional] -**DeletedAt** | Pointer to **time.Time** | | [optional] -**Manifest** | Pointer to **map[string]interface{}** | | [optional] -**GroupResource** | Pointer to **map[string]interface{}** | | [optional] -**DeleteOption** | Pointer to **map[string]interface{}** | | [optional] -**UpdateStrategy** | Pointer to **map[string]interface{}** | | [optional] -**Status** | Pointer to **map[string]interface{}** | | [optional] - -## Methods - -### NewResourceAllOf - -`func NewResourceAllOf() *ResourceAllOf` - -NewResourceAllOf instantiates a new ResourceAllOf object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewResourceAllOfWithDefaults - -`func NewResourceAllOfWithDefaults() *ResourceAllOf` - -NewResourceAllOfWithDefaults instantiates a new ResourceAllOf object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetName - -`func (o *ResourceAllOf) GetName() string` - -GetName returns the Name field if non-nil, zero value otherwise. - -### GetNameOk - -`func (o *ResourceAllOf) GetNameOk() (*string, bool)` - -GetNameOk returns a tuple with the Name field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetName - -`func (o *ResourceAllOf) SetName(v string)` - -SetName sets Name field to given value. - -### HasName - -`func (o *ResourceAllOf) HasName() bool` - -HasName returns a boolean if a field has been set. - -### GetConsumerName - -`func (o *ResourceAllOf) GetConsumerName() string` - -GetConsumerName returns the ConsumerName field if non-nil, zero value otherwise. - -### GetConsumerNameOk - -`func (o *ResourceAllOf) GetConsumerNameOk() (*string, bool)` - -GetConsumerNameOk returns a tuple with the ConsumerName field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetConsumerName - -`func (o *ResourceAllOf) SetConsumerName(v string)` - -SetConsumerName sets ConsumerName field to given value. - -### HasConsumerName - -`func (o *ResourceAllOf) HasConsumerName() bool` - -HasConsumerName returns a boolean if a field has been set. - -### GetVersion - -`func (o *ResourceAllOf) GetVersion() int32` - -GetVersion returns the Version field if non-nil, zero value otherwise. - -### GetVersionOk - -`func (o *ResourceAllOf) GetVersionOk() (*int32, bool)` - -GetVersionOk returns a tuple with the Version field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetVersion - -`func (o *ResourceAllOf) SetVersion(v int32)` - -SetVersion sets Version field to given value. - -### HasVersion - -`func (o *ResourceAllOf) HasVersion() bool` - -HasVersion returns a boolean if a field has been set. - -### GetCreatedAt - -`func (o *ResourceAllOf) GetCreatedAt() time.Time` - -GetCreatedAt returns the CreatedAt field if non-nil, zero value otherwise. - -### GetCreatedAtOk - -`func (o *ResourceAllOf) GetCreatedAtOk() (*time.Time, bool)` - -GetCreatedAtOk returns a tuple with the CreatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetCreatedAt - -`func (o *ResourceAllOf) SetCreatedAt(v time.Time)` - -SetCreatedAt sets CreatedAt field to given value. - -### HasCreatedAt - -`func (o *ResourceAllOf) HasCreatedAt() bool` - -HasCreatedAt returns a boolean if a field has been set. - -### GetUpdatedAt - -`func (o *ResourceAllOf) GetUpdatedAt() time.Time` - -GetUpdatedAt returns the UpdatedAt field if non-nil, zero value otherwise. - -### GetUpdatedAtOk - -`func (o *ResourceAllOf) GetUpdatedAtOk() (*time.Time, bool)` - -GetUpdatedAtOk returns a tuple with the UpdatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetUpdatedAt - -`func (o *ResourceAllOf) SetUpdatedAt(v time.Time)` - -SetUpdatedAt sets UpdatedAt field to given value. - -### HasUpdatedAt - -`func (o *ResourceAllOf) HasUpdatedAt() bool` - -HasUpdatedAt returns a boolean if a field has been set. - -### GetDeletedAt - -`func (o *ResourceAllOf) GetDeletedAt() time.Time` - -GetDeletedAt returns the DeletedAt field if non-nil, zero value otherwise. - -### GetDeletedAtOk - -`func (o *ResourceAllOf) GetDeletedAtOk() (*time.Time, bool)` - -GetDeletedAtOk returns a tuple with the DeletedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetDeletedAt - -`func (o *ResourceAllOf) SetDeletedAt(v time.Time)` - -SetDeletedAt sets DeletedAt field to given value. - -### HasDeletedAt - -`func (o *ResourceAllOf) HasDeletedAt() bool` - -HasDeletedAt returns a boolean if a field has been set. - -### GetManifest - -`func (o *ResourceAllOf) GetManifest() map[string]interface{}` - -GetManifest returns the Manifest field if non-nil, zero value otherwise. - -### GetManifestOk - -`func (o *ResourceAllOf) GetManifestOk() (*map[string]interface{}, bool)` - -GetManifestOk returns a tuple with the Manifest field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetManifest - -`func (o *ResourceAllOf) SetManifest(v map[string]interface{})` - -SetManifest sets Manifest field to given value. - -### HasManifest - -`func (o *ResourceAllOf) HasManifest() bool` - -HasManifest returns a boolean if a field has been set. - -### GetGroupResource - -`func (o *ResourceAllOf) GetGroupResource() map[string]interface{}` - -GetGroupResource returns the GroupResource field if non-nil, zero value otherwise. - -### GetGroupResourceOk - -`func (o *ResourceAllOf) GetGroupResourceOk() (*map[string]interface{}, bool)` - -GetGroupResourceOk returns a tuple with the GroupResource field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetGroupResource - -`func (o *ResourceAllOf) SetGroupResource(v map[string]interface{})` - -SetGroupResource sets GroupResource field to given value. - -### HasGroupResource - -`func (o *ResourceAllOf) HasGroupResource() bool` - -HasGroupResource returns a boolean if a field has been set. - -### GetDeleteOption - -`func (o *ResourceAllOf) GetDeleteOption() map[string]interface{}` - -GetDeleteOption returns the DeleteOption field if non-nil, zero value otherwise. - -### GetDeleteOptionOk - -`func (o *ResourceAllOf) GetDeleteOptionOk() (*map[string]interface{}, bool)` - -GetDeleteOptionOk returns a tuple with the DeleteOption field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetDeleteOption - -`func (o *ResourceAllOf) SetDeleteOption(v map[string]interface{})` - -SetDeleteOption sets DeleteOption field to given value. - -### HasDeleteOption - -`func (o *ResourceAllOf) HasDeleteOption() bool` - -HasDeleteOption returns a boolean if a field has been set. - -### GetUpdateStrategy - -`func (o *ResourceAllOf) GetUpdateStrategy() map[string]interface{}` - -GetUpdateStrategy returns the UpdateStrategy field if non-nil, zero value otherwise. - -### GetUpdateStrategyOk - -`func (o *ResourceAllOf) GetUpdateStrategyOk() (*map[string]interface{}, bool)` - -GetUpdateStrategyOk returns a tuple with the UpdateStrategy field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetUpdateStrategy - -`func (o *ResourceAllOf) SetUpdateStrategy(v map[string]interface{})` - -SetUpdateStrategy sets UpdateStrategy field to given value. - -### HasUpdateStrategy - -`func (o *ResourceAllOf) HasUpdateStrategy() bool` - -HasUpdateStrategy returns a boolean if a field has been set. - -### GetStatus - -`func (o *ResourceAllOf) GetStatus() map[string]interface{}` - -GetStatus returns the Status field if non-nil, zero value otherwise. - -### GetStatusOk - -`func (o *ResourceAllOf) GetStatusOk() (*map[string]interface{}, bool)` - -GetStatusOk returns a tuple with the Status field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetStatus - -`func (o *ResourceAllOf) SetStatus(v map[string]interface{})` - -SetStatus sets Status field to given value. - -### HasStatus - -`func (o *ResourceAllOf) HasStatus() bool` - -HasStatus returns a boolean if a field has been set. - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/pkg/api/openapi/docs/ResourceList.md b/pkg/api/openapi/docs/ResourceList.md deleted file mode 100644 index f6c6f4e1..00000000 --- a/pkg/api/openapi/docs/ResourceList.md +++ /dev/null @@ -1,135 +0,0 @@ -# ResourceList - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Kind** | **string** | | -**Page** | **int32** | | -**Size** | **int32** | | -**Total** | **int32** | | -**Items** | [**[]Resource**](Resource.md) | | - -## Methods - -### NewResourceList - -`func NewResourceList(kind string, page int32, size int32, total int32, items []Resource, ) *ResourceList` - -NewResourceList instantiates a new ResourceList object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewResourceListWithDefaults - -`func NewResourceListWithDefaults() *ResourceList` - -NewResourceListWithDefaults instantiates a new ResourceList object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetKind - -`func (o *ResourceList) GetKind() string` - -GetKind returns the Kind field if non-nil, zero value otherwise. - -### GetKindOk - -`func (o *ResourceList) GetKindOk() (*string, bool)` - -GetKindOk returns a tuple with the Kind field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetKind - -`func (o *ResourceList) SetKind(v string)` - -SetKind sets Kind field to given value. - - -### GetPage - -`func (o *ResourceList) GetPage() int32` - -GetPage returns the Page field if non-nil, zero value otherwise. - -### GetPageOk - -`func (o *ResourceList) GetPageOk() (*int32, bool)` - -GetPageOk returns a tuple with the Page field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetPage - -`func (o *ResourceList) SetPage(v int32)` - -SetPage sets Page field to given value. - - -### GetSize - -`func (o *ResourceList) GetSize() int32` - -GetSize returns the Size field if non-nil, zero value otherwise. - -### GetSizeOk - -`func (o *ResourceList) GetSizeOk() (*int32, bool)` - -GetSizeOk returns a tuple with the Size field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetSize - -`func (o *ResourceList) SetSize(v int32)` - -SetSize sets Size field to given value. - - -### GetTotal - -`func (o *ResourceList) GetTotal() int32` - -GetTotal returns the Total field if non-nil, zero value otherwise. - -### GetTotalOk - -`func (o *ResourceList) GetTotalOk() (*int32, bool)` - -GetTotalOk returns a tuple with the Total field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetTotal - -`func (o *ResourceList) SetTotal(v int32)` - -SetTotal sets Total field to given value. - - -### GetItems - -`func (o *ResourceList) GetItems() []Resource` - -GetItems returns the Items field if non-nil, zero value otherwise. - -### GetItemsOk - -`func (o *ResourceList) GetItemsOk() (*[]Resource, bool)` - -GetItemsOk returns a tuple with the Items field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetItems - -`func (o *ResourceList) SetItems(v []Resource)` - -SetItems sets Items field to given value. - - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/pkg/api/openapi/docs/ResourceListAllOf.md b/pkg/api/openapi/docs/ResourceListAllOf.md deleted file mode 100644 index c33560cf..00000000 --- a/pkg/api/openapi/docs/ResourceListAllOf.md +++ /dev/null @@ -1,56 +0,0 @@ -# ResourceListAllOf - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Items** | Pointer to [**[]Resource**](Resource.md) | | [optional] - -## Methods - -### NewResourceListAllOf - -`func NewResourceListAllOf() *ResourceListAllOf` - -NewResourceListAllOf instantiates a new ResourceListAllOf object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewResourceListAllOfWithDefaults - -`func NewResourceListAllOfWithDefaults() *ResourceListAllOf` - -NewResourceListAllOfWithDefaults instantiates a new ResourceListAllOf object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetItems - -`func (o *ResourceListAllOf) GetItems() []Resource` - -GetItems returns the Items field if non-nil, zero value otherwise. - -### GetItemsOk - -`func (o *ResourceListAllOf) GetItemsOk() (*[]Resource, bool)` - -GetItemsOk returns a tuple with the Items field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetItems - -`func (o *ResourceListAllOf) SetItems(v []Resource)` - -SetItems sets Items field to given value. - -### HasItems - -`func (o *ResourceListAllOf) HasItems() bool` - -HasItems returns a boolean if a field has been set. - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/pkg/api/openapi/docs/ResourcePatchRequest.md b/pkg/api/openapi/docs/ResourcePatchRequest.md deleted file mode 100644 index eebeba0d..00000000 --- a/pkg/api/openapi/docs/ResourcePatchRequest.md +++ /dev/null @@ -1,134 +0,0 @@ -# ResourcePatchRequest - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Version** | Pointer to **int32** | | [optional] -**Manifest** | Pointer to **map[string]interface{}** | | [optional] -**DeleteOption** | Pointer to **map[string]interface{}** | | [optional] -**UpdateStrategy** | Pointer to **map[string]interface{}** | | [optional] - -## Methods - -### NewResourcePatchRequest - -`func NewResourcePatchRequest() *ResourcePatchRequest` - -NewResourcePatchRequest instantiates a new ResourcePatchRequest object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewResourcePatchRequestWithDefaults - -`func NewResourcePatchRequestWithDefaults() *ResourcePatchRequest` - -NewResourcePatchRequestWithDefaults instantiates a new ResourcePatchRequest object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetVersion - -`func (o *ResourcePatchRequest) GetVersion() int32` - -GetVersion returns the Version field if non-nil, zero value otherwise. - -### GetVersionOk - -`func (o *ResourcePatchRequest) GetVersionOk() (*int32, bool)` - -GetVersionOk returns a tuple with the Version field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetVersion - -`func (o *ResourcePatchRequest) SetVersion(v int32)` - -SetVersion sets Version field to given value. - -### HasVersion - -`func (o *ResourcePatchRequest) HasVersion() bool` - -HasVersion returns a boolean if a field has been set. - -### GetManifest - -`func (o *ResourcePatchRequest) GetManifest() map[string]interface{}` - -GetManifest returns the Manifest field if non-nil, zero value otherwise. - -### GetManifestOk - -`func (o *ResourcePatchRequest) GetManifestOk() (*map[string]interface{}, bool)` - -GetManifestOk returns a tuple with the Manifest field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetManifest - -`func (o *ResourcePatchRequest) SetManifest(v map[string]interface{})` - -SetManifest sets Manifest field to given value. - -### HasManifest - -`func (o *ResourcePatchRequest) HasManifest() bool` - -HasManifest returns a boolean if a field has been set. - -### GetDeleteOption - -`func (o *ResourcePatchRequest) GetDeleteOption() map[string]interface{}` - -GetDeleteOption returns the DeleteOption field if non-nil, zero value otherwise. - -### GetDeleteOptionOk - -`func (o *ResourcePatchRequest) GetDeleteOptionOk() (*map[string]interface{}, bool)` - -GetDeleteOptionOk returns a tuple with the DeleteOption field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetDeleteOption - -`func (o *ResourcePatchRequest) SetDeleteOption(v map[string]interface{})` - -SetDeleteOption sets DeleteOption field to given value. - -### HasDeleteOption - -`func (o *ResourcePatchRequest) HasDeleteOption() bool` - -HasDeleteOption returns a boolean if a field has been set. - -### GetUpdateStrategy - -`func (o *ResourcePatchRequest) GetUpdateStrategy() map[string]interface{}` - -GetUpdateStrategy returns the UpdateStrategy field if non-nil, zero value otherwise. - -### GetUpdateStrategyOk - -`func (o *ResourcePatchRequest) GetUpdateStrategyOk() (*map[string]interface{}, bool)` - -GetUpdateStrategyOk returns a tuple with the UpdateStrategy field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetUpdateStrategy - -`func (o *ResourcePatchRequest) SetUpdateStrategy(v map[string]interface{})` - -SetUpdateStrategy sets UpdateStrategy field to given value. - -### HasUpdateStrategy - -`func (o *ResourcePatchRequest) HasUpdateStrategy() bool` - -HasUpdateStrategy returns a boolean if a field has been set. - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/pkg/api/openapi/model_resource.go b/pkg/api/openapi/model_resource.go deleted file mode 100644 index 03fb10bc..00000000 --- a/pkg/api/openapi/model_resource.go +++ /dev/null @@ -1,593 +0,0 @@ -/* -maestro Service API - -maestro Service API - -API version: 0.0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package openapi - -import ( - "encoding/json" - "time" -) - -// checks if the Resource type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &Resource{} - -// Resource struct for Resource -type Resource struct { - Id *string `json:"id,omitempty"` - Kind *string `json:"kind,omitempty"` - Href *string `json:"href,omitempty"` - Name *string `json:"name,omitempty"` - ConsumerName *string `json:"consumer_name,omitempty"` - Version *int32 `json:"version,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - UpdatedAt *time.Time `json:"updated_at,omitempty"` - DeletedAt *time.Time `json:"deleted_at,omitempty"` - Manifest map[string]interface{} `json:"manifest,omitempty"` - GroupResource map[string]interface{} `json:"group_resource,omitempty"` - DeleteOption map[string]interface{} `json:"delete_option,omitempty"` - UpdateStrategy map[string]interface{} `json:"update_strategy,omitempty"` - Status map[string]interface{} `json:"status,omitempty"` -} - -// NewResource instantiates a new Resource object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewResource() *Resource { - this := Resource{} - return &this -} - -// NewResourceWithDefaults instantiates a new Resource object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewResourceWithDefaults() *Resource { - this := Resource{} - return &this -} - -// GetId returns the Id field value if set, zero value otherwise. -func (o *Resource) GetId() string { - if o == nil || IsNil(o.Id) { - var ret string - return ret - } - return *o.Id -} - -// GetIdOk returns a tuple with the Id field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetIdOk() (*string, bool) { - if o == nil || IsNil(o.Id) { - return nil, false - } - return o.Id, true -} - -// HasId returns a boolean if a field has been set. -func (o *Resource) HasId() bool { - if o != nil && !IsNil(o.Id) { - return true - } - - return false -} - -// SetId gets a reference to the given string and assigns it to the Id field. -func (o *Resource) SetId(v string) { - o.Id = &v -} - -// GetKind returns the Kind field value if set, zero value otherwise. -func (o *Resource) GetKind() string { - if o == nil || IsNil(o.Kind) { - var ret string - return ret - } - return *o.Kind -} - -// GetKindOk returns a tuple with the Kind field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetKindOk() (*string, bool) { - if o == nil || IsNil(o.Kind) { - return nil, false - } - return o.Kind, true -} - -// HasKind returns a boolean if a field has been set. -func (o *Resource) HasKind() bool { - if o != nil && !IsNil(o.Kind) { - return true - } - - return false -} - -// SetKind gets a reference to the given string and assigns it to the Kind field. -func (o *Resource) SetKind(v string) { - o.Kind = &v -} - -// GetHref returns the Href field value if set, zero value otherwise. -func (o *Resource) GetHref() string { - if o == nil || IsNil(o.Href) { - var ret string - return ret - } - return *o.Href -} - -// GetHrefOk returns a tuple with the Href field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetHrefOk() (*string, bool) { - if o == nil || IsNil(o.Href) { - return nil, false - } - return o.Href, true -} - -// HasHref returns a boolean if a field has been set. -func (o *Resource) HasHref() bool { - if o != nil && !IsNil(o.Href) { - return true - } - - return false -} - -// SetHref gets a reference to the given string and assigns it to the Href field. -func (o *Resource) SetHref(v string) { - o.Href = &v -} - -// GetName returns the Name field value if set, zero value otherwise. -func (o *Resource) GetName() string { - if o == nil || IsNil(o.Name) { - var ret string - return ret - } - return *o.Name -} - -// GetNameOk returns a tuple with the Name field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetNameOk() (*string, bool) { - if o == nil || IsNil(o.Name) { - return nil, false - } - return o.Name, true -} - -// HasName returns a boolean if a field has been set. -func (o *Resource) HasName() bool { - if o != nil && !IsNil(o.Name) { - return true - } - - return false -} - -// SetName gets a reference to the given string and assigns it to the Name field. -func (o *Resource) SetName(v string) { - o.Name = &v -} - -// GetConsumerName returns the ConsumerName field value if set, zero value otherwise. -func (o *Resource) GetConsumerName() string { - if o == nil || IsNil(o.ConsumerName) { - var ret string - return ret - } - return *o.ConsumerName -} - -// GetConsumerNameOk returns a tuple with the ConsumerName field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetConsumerNameOk() (*string, bool) { - if o == nil || IsNil(o.ConsumerName) { - return nil, false - } - return o.ConsumerName, true -} - -// HasConsumerName returns a boolean if a field has been set. -func (o *Resource) HasConsumerName() bool { - if o != nil && !IsNil(o.ConsumerName) { - return true - } - - return false -} - -// SetConsumerName gets a reference to the given string and assigns it to the ConsumerName field. -func (o *Resource) SetConsumerName(v string) { - o.ConsumerName = &v -} - -// GetVersion returns the Version field value if set, zero value otherwise. -func (o *Resource) GetVersion() int32 { - if o == nil || IsNil(o.Version) { - var ret int32 - return ret - } - return *o.Version -} - -// GetVersionOk returns a tuple with the Version field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetVersionOk() (*int32, bool) { - if o == nil || IsNil(o.Version) { - return nil, false - } - return o.Version, true -} - -// HasVersion returns a boolean if a field has been set. -func (o *Resource) HasVersion() bool { - if o != nil && !IsNil(o.Version) { - return true - } - - return false -} - -// SetVersion gets a reference to the given int32 and assigns it to the Version field. -func (o *Resource) SetVersion(v int32) { - o.Version = &v -} - -// GetCreatedAt returns the CreatedAt field value if set, zero value otherwise. -func (o *Resource) GetCreatedAt() time.Time { - if o == nil || IsNil(o.CreatedAt) { - var ret time.Time - return ret - } - return *o.CreatedAt -} - -// GetCreatedAtOk returns a tuple with the CreatedAt field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetCreatedAtOk() (*time.Time, bool) { - if o == nil || IsNil(o.CreatedAt) { - return nil, false - } - return o.CreatedAt, true -} - -// HasCreatedAt returns a boolean if a field has been set. -func (o *Resource) HasCreatedAt() bool { - if o != nil && !IsNil(o.CreatedAt) { - return true - } - - return false -} - -// SetCreatedAt gets a reference to the given time.Time and assigns it to the CreatedAt field. -func (o *Resource) SetCreatedAt(v time.Time) { - o.CreatedAt = &v -} - -// GetUpdatedAt returns the UpdatedAt field value if set, zero value otherwise. -func (o *Resource) GetUpdatedAt() time.Time { - if o == nil || IsNil(o.UpdatedAt) { - var ret time.Time - return ret - } - return *o.UpdatedAt -} - -// GetUpdatedAtOk returns a tuple with the UpdatedAt field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetUpdatedAtOk() (*time.Time, bool) { - if o == nil || IsNil(o.UpdatedAt) { - return nil, false - } - return o.UpdatedAt, true -} - -// HasUpdatedAt returns a boolean if a field has been set. -func (o *Resource) HasUpdatedAt() bool { - if o != nil && !IsNil(o.UpdatedAt) { - return true - } - - return false -} - -// SetUpdatedAt gets a reference to the given time.Time and assigns it to the UpdatedAt field. -func (o *Resource) SetUpdatedAt(v time.Time) { - o.UpdatedAt = &v -} - -// GetDeletedAt returns the DeletedAt field value if set, zero value otherwise. -func (o *Resource) GetDeletedAt() time.Time { - if o == nil || IsNil(o.DeletedAt) { - var ret time.Time - return ret - } - return *o.DeletedAt -} - -// GetDeletedAtOk returns a tuple with the DeletedAt field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetDeletedAtOk() (*time.Time, bool) { - if o == nil || IsNil(o.DeletedAt) { - return nil, false - } - return o.DeletedAt, true -} - -// HasDeletedAt returns a boolean if a field has been set. -func (o *Resource) HasDeletedAt() bool { - if o != nil && !IsNil(o.DeletedAt) { - return true - } - - return false -} - -// SetDeletedAt gets a reference to the given time.Time and assigns it to the DeletedAt field. -func (o *Resource) SetDeletedAt(v time.Time) { - o.DeletedAt = &v -} - -// GetManifest returns the Manifest field value if set, zero value otherwise. -func (o *Resource) GetManifest() map[string]interface{} { - if o == nil || IsNil(o.Manifest) { - var ret map[string]interface{} - return ret - } - return o.Manifest -} - -// GetManifestOk returns a tuple with the Manifest field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetManifestOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.Manifest) { - return map[string]interface{}{}, false - } - return o.Manifest, true -} - -// HasManifest returns a boolean if a field has been set. -func (o *Resource) HasManifest() bool { - if o != nil && !IsNil(o.Manifest) { - return true - } - - return false -} - -// SetManifest gets a reference to the given map[string]interface{} and assigns it to the Manifest field. -func (o *Resource) SetManifest(v map[string]interface{}) { - o.Manifest = v -} - -// GetGroupResource returns the GroupResource field value if set, zero value otherwise. -func (o *Resource) GetGroupResource() map[string]interface{} { - if o == nil || IsNil(o.GroupResource) { - var ret map[string]interface{} - return ret - } - return o.GroupResource -} - -// GetGroupResourceOk returns a tuple with the GroupResource field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetGroupResourceOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.GroupResource) { - return map[string]interface{}{}, false - } - return o.GroupResource, true -} - -// HasGroupResource returns a boolean if a field has been set. -func (o *Resource) HasGroupResource() bool { - if o != nil && !IsNil(o.GroupResource) { - return true - } - - return false -} - -// SetGroupResource gets a reference to the given map[string]interface{} and assigns it to the GroupResource field. -func (o *Resource) SetGroupResource(v map[string]interface{}) { - o.GroupResource = v -} - -// GetDeleteOption returns the DeleteOption field value if set, zero value otherwise. -func (o *Resource) GetDeleteOption() map[string]interface{} { - if o == nil || IsNil(o.DeleteOption) { - var ret map[string]interface{} - return ret - } - return o.DeleteOption -} - -// GetDeleteOptionOk returns a tuple with the DeleteOption field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetDeleteOptionOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.DeleteOption) { - return map[string]interface{}{}, false - } - return o.DeleteOption, true -} - -// HasDeleteOption returns a boolean if a field has been set. -func (o *Resource) HasDeleteOption() bool { - if o != nil && !IsNil(o.DeleteOption) { - return true - } - - return false -} - -// SetDeleteOption gets a reference to the given map[string]interface{} and assigns it to the DeleteOption field. -func (o *Resource) SetDeleteOption(v map[string]interface{}) { - o.DeleteOption = v -} - -// GetUpdateStrategy returns the UpdateStrategy field value if set, zero value otherwise. -func (o *Resource) GetUpdateStrategy() map[string]interface{} { - if o == nil || IsNil(o.UpdateStrategy) { - var ret map[string]interface{} - return ret - } - return o.UpdateStrategy -} - -// GetUpdateStrategyOk returns a tuple with the UpdateStrategy field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetUpdateStrategyOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.UpdateStrategy) { - return map[string]interface{}{}, false - } - return o.UpdateStrategy, true -} - -// HasUpdateStrategy returns a boolean if a field has been set. -func (o *Resource) HasUpdateStrategy() bool { - if o != nil && !IsNil(o.UpdateStrategy) { - return true - } - - return false -} - -// SetUpdateStrategy gets a reference to the given map[string]interface{} and assigns it to the UpdateStrategy field. -func (o *Resource) SetUpdateStrategy(v map[string]interface{}) { - o.UpdateStrategy = v -} - -// GetStatus returns the Status field value if set, zero value otherwise. -func (o *Resource) GetStatus() map[string]interface{} { - if o == nil || IsNil(o.Status) { - var ret map[string]interface{} - return ret - } - return o.Status -} - -// GetStatusOk returns a tuple with the Status field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Resource) GetStatusOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.Status) { - return map[string]interface{}{}, false - } - return o.Status, true -} - -// HasStatus returns a boolean if a field has been set. -func (o *Resource) HasStatus() bool { - if o != nil && !IsNil(o.Status) { - return true - } - - return false -} - -// SetStatus gets a reference to the given map[string]interface{} and assigns it to the Status field. -func (o *Resource) SetStatus(v map[string]interface{}) { - o.Status = v -} - -func (o Resource) MarshalJSON() ([]byte, error) { - toSerialize, err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o Resource) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - if !IsNil(o.Id) { - toSerialize["id"] = o.Id - } - if !IsNil(o.Kind) { - toSerialize["kind"] = o.Kind - } - if !IsNil(o.Href) { - toSerialize["href"] = o.Href - } - if !IsNil(o.Name) { - toSerialize["name"] = o.Name - } - if !IsNil(o.ConsumerName) { - toSerialize["consumer_name"] = o.ConsumerName - } - if !IsNil(o.Version) { - toSerialize["version"] = o.Version - } - if !IsNil(o.CreatedAt) { - toSerialize["created_at"] = o.CreatedAt - } - if !IsNil(o.UpdatedAt) { - toSerialize["updated_at"] = o.UpdatedAt - } - if !IsNil(o.DeletedAt) { - toSerialize["deleted_at"] = o.DeletedAt - } - if !IsNil(o.Manifest) { - toSerialize["manifest"] = o.Manifest - } - if !IsNil(o.GroupResource) { - toSerialize["group_resource"] = o.GroupResource - } - if !IsNil(o.DeleteOption) { - toSerialize["delete_option"] = o.DeleteOption - } - if !IsNil(o.UpdateStrategy) { - toSerialize["update_strategy"] = o.UpdateStrategy - } - if !IsNil(o.Status) { - toSerialize["status"] = o.Status - } - return toSerialize, nil -} - -type NullableResource struct { - value *Resource - isSet bool -} - -func (v NullableResource) Get() *Resource { - return v.value -} - -func (v *NullableResource) Set(val *Resource) { - v.value = val - v.isSet = true -} - -func (v NullableResource) IsSet() bool { - return v.isSet -} - -func (v *NullableResource) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableResource(val *Resource) *NullableResource { - return &NullableResource{value: val, isSet: true} -} - -func (v NullableResource) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableResource) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} diff --git a/pkg/api/openapi/model_resource_all_of.go b/pkg/api/openapi/model_resource_all_of.go deleted file mode 100644 index 949fa4d1..00000000 --- a/pkg/api/openapi/model_resource_all_of.go +++ /dev/null @@ -1,485 +0,0 @@ -/* -maestro Service API - -maestro Service API - -API version: 0.0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package openapi - -import ( - "encoding/json" - "time" -) - -// checks if the ResourceAllOf type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &ResourceAllOf{} - -// ResourceAllOf struct for ResourceAllOf -type ResourceAllOf struct { - Name *string `json:"name,omitempty"` - ConsumerName *string `json:"consumer_name,omitempty"` - Version *int32 `json:"version,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - UpdatedAt *time.Time `json:"updated_at,omitempty"` - DeletedAt *time.Time `json:"deleted_at,omitempty"` - Manifest map[string]interface{} `json:"manifest,omitempty"` - GroupResource map[string]interface{} `json:"group_resource,omitempty"` - DeleteOption map[string]interface{} `json:"delete_option,omitempty"` - UpdateStrategy map[string]interface{} `json:"update_strategy,omitempty"` - Status map[string]interface{} `json:"status,omitempty"` -} - -// NewResourceAllOf instantiates a new ResourceAllOf object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewResourceAllOf() *ResourceAllOf { - this := ResourceAllOf{} - return &this -} - -// NewResourceAllOfWithDefaults instantiates a new ResourceAllOf object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewResourceAllOfWithDefaults() *ResourceAllOf { - this := ResourceAllOf{} - return &this -} - -// GetName returns the Name field value if set, zero value otherwise. -func (o *ResourceAllOf) GetName() string { - if o == nil || IsNil(o.Name) { - var ret string - return ret - } - return *o.Name -} - -// GetNameOk returns a tuple with the Name field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetNameOk() (*string, bool) { - if o == nil || IsNil(o.Name) { - return nil, false - } - return o.Name, true -} - -// HasName returns a boolean if a field has been set. -func (o *ResourceAllOf) HasName() bool { - if o != nil && !IsNil(o.Name) { - return true - } - - return false -} - -// SetName gets a reference to the given string and assigns it to the Name field. -func (o *ResourceAllOf) SetName(v string) { - o.Name = &v -} - -// GetConsumerName returns the ConsumerName field value if set, zero value otherwise. -func (o *ResourceAllOf) GetConsumerName() string { - if o == nil || IsNil(o.ConsumerName) { - var ret string - return ret - } - return *o.ConsumerName -} - -// GetConsumerNameOk returns a tuple with the ConsumerName field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetConsumerNameOk() (*string, bool) { - if o == nil || IsNil(o.ConsumerName) { - return nil, false - } - return o.ConsumerName, true -} - -// HasConsumerName returns a boolean if a field has been set. -func (o *ResourceAllOf) HasConsumerName() bool { - if o != nil && !IsNil(o.ConsumerName) { - return true - } - - return false -} - -// SetConsumerName gets a reference to the given string and assigns it to the ConsumerName field. -func (o *ResourceAllOf) SetConsumerName(v string) { - o.ConsumerName = &v -} - -// GetVersion returns the Version field value if set, zero value otherwise. -func (o *ResourceAllOf) GetVersion() int32 { - if o == nil || IsNil(o.Version) { - var ret int32 - return ret - } - return *o.Version -} - -// GetVersionOk returns a tuple with the Version field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetVersionOk() (*int32, bool) { - if o == nil || IsNil(o.Version) { - return nil, false - } - return o.Version, true -} - -// HasVersion returns a boolean if a field has been set. -func (o *ResourceAllOf) HasVersion() bool { - if o != nil && !IsNil(o.Version) { - return true - } - - return false -} - -// SetVersion gets a reference to the given int32 and assigns it to the Version field. -func (o *ResourceAllOf) SetVersion(v int32) { - o.Version = &v -} - -// GetCreatedAt returns the CreatedAt field value if set, zero value otherwise. -func (o *ResourceAllOf) GetCreatedAt() time.Time { - if o == nil || IsNil(o.CreatedAt) { - var ret time.Time - return ret - } - return *o.CreatedAt -} - -// GetCreatedAtOk returns a tuple with the CreatedAt field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetCreatedAtOk() (*time.Time, bool) { - if o == nil || IsNil(o.CreatedAt) { - return nil, false - } - return o.CreatedAt, true -} - -// HasCreatedAt returns a boolean if a field has been set. -func (o *ResourceAllOf) HasCreatedAt() bool { - if o != nil && !IsNil(o.CreatedAt) { - return true - } - - return false -} - -// SetCreatedAt gets a reference to the given time.Time and assigns it to the CreatedAt field. -func (o *ResourceAllOf) SetCreatedAt(v time.Time) { - o.CreatedAt = &v -} - -// GetUpdatedAt returns the UpdatedAt field value if set, zero value otherwise. -func (o *ResourceAllOf) GetUpdatedAt() time.Time { - if o == nil || IsNil(o.UpdatedAt) { - var ret time.Time - return ret - } - return *o.UpdatedAt -} - -// GetUpdatedAtOk returns a tuple with the UpdatedAt field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetUpdatedAtOk() (*time.Time, bool) { - if o == nil || IsNil(o.UpdatedAt) { - return nil, false - } - return o.UpdatedAt, true -} - -// HasUpdatedAt returns a boolean if a field has been set. -func (o *ResourceAllOf) HasUpdatedAt() bool { - if o != nil && !IsNil(o.UpdatedAt) { - return true - } - - return false -} - -// SetUpdatedAt gets a reference to the given time.Time and assigns it to the UpdatedAt field. -func (o *ResourceAllOf) SetUpdatedAt(v time.Time) { - o.UpdatedAt = &v -} - -// GetDeletedAt returns the DeletedAt field value if set, zero value otherwise. -func (o *ResourceAllOf) GetDeletedAt() time.Time { - if o == nil || IsNil(o.DeletedAt) { - var ret time.Time - return ret - } - return *o.DeletedAt -} - -// GetDeletedAtOk returns a tuple with the DeletedAt field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetDeletedAtOk() (*time.Time, bool) { - if o == nil || IsNil(o.DeletedAt) { - return nil, false - } - return o.DeletedAt, true -} - -// HasDeletedAt returns a boolean if a field has been set. -func (o *ResourceAllOf) HasDeletedAt() bool { - if o != nil && !IsNil(o.DeletedAt) { - return true - } - - return false -} - -// SetDeletedAt gets a reference to the given time.Time and assigns it to the DeletedAt field. -func (o *ResourceAllOf) SetDeletedAt(v time.Time) { - o.DeletedAt = &v -} - -// GetManifest returns the Manifest field value if set, zero value otherwise. -func (o *ResourceAllOf) GetManifest() map[string]interface{} { - if o == nil || IsNil(o.Manifest) { - var ret map[string]interface{} - return ret - } - return o.Manifest -} - -// GetManifestOk returns a tuple with the Manifest field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetManifestOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.Manifest) { - return map[string]interface{}{}, false - } - return o.Manifest, true -} - -// HasManifest returns a boolean if a field has been set. -func (o *ResourceAllOf) HasManifest() bool { - if o != nil && !IsNil(o.Manifest) { - return true - } - - return false -} - -// SetManifest gets a reference to the given map[string]interface{} and assigns it to the Manifest field. -func (o *ResourceAllOf) SetManifest(v map[string]interface{}) { - o.Manifest = v -} - -// GetGroupResource returns the GroupResource field value if set, zero value otherwise. -func (o *ResourceAllOf) GetGroupResource() map[string]interface{} { - if o == nil || IsNil(o.GroupResource) { - var ret map[string]interface{} - return ret - } - return o.GroupResource -} - -// GetGroupResourceOk returns a tuple with the GroupResource field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetGroupResourceOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.GroupResource) { - return map[string]interface{}{}, false - } - return o.GroupResource, true -} - -// HasGroupResource returns a boolean if a field has been set. -func (o *ResourceAllOf) HasGroupResource() bool { - if o != nil && !IsNil(o.GroupResource) { - return true - } - - return false -} - -// SetGroupResource gets a reference to the given map[string]interface{} and assigns it to the GroupResource field. -func (o *ResourceAllOf) SetGroupResource(v map[string]interface{}) { - o.GroupResource = v -} - -// GetDeleteOption returns the DeleteOption field value if set, zero value otherwise. -func (o *ResourceAllOf) GetDeleteOption() map[string]interface{} { - if o == nil || IsNil(o.DeleteOption) { - var ret map[string]interface{} - return ret - } - return o.DeleteOption -} - -// GetDeleteOptionOk returns a tuple with the DeleteOption field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetDeleteOptionOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.DeleteOption) { - return map[string]interface{}{}, false - } - return o.DeleteOption, true -} - -// HasDeleteOption returns a boolean if a field has been set. -func (o *ResourceAllOf) HasDeleteOption() bool { - if o != nil && !IsNil(o.DeleteOption) { - return true - } - - return false -} - -// SetDeleteOption gets a reference to the given map[string]interface{} and assigns it to the DeleteOption field. -func (o *ResourceAllOf) SetDeleteOption(v map[string]interface{}) { - o.DeleteOption = v -} - -// GetUpdateStrategy returns the UpdateStrategy field value if set, zero value otherwise. -func (o *ResourceAllOf) GetUpdateStrategy() map[string]interface{} { - if o == nil || IsNil(o.UpdateStrategy) { - var ret map[string]interface{} - return ret - } - return o.UpdateStrategy -} - -// GetUpdateStrategyOk returns a tuple with the UpdateStrategy field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetUpdateStrategyOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.UpdateStrategy) { - return map[string]interface{}{}, false - } - return o.UpdateStrategy, true -} - -// HasUpdateStrategy returns a boolean if a field has been set. -func (o *ResourceAllOf) HasUpdateStrategy() bool { - if o != nil && !IsNil(o.UpdateStrategy) { - return true - } - - return false -} - -// SetUpdateStrategy gets a reference to the given map[string]interface{} and assigns it to the UpdateStrategy field. -func (o *ResourceAllOf) SetUpdateStrategy(v map[string]interface{}) { - o.UpdateStrategy = v -} - -// GetStatus returns the Status field value if set, zero value otherwise. -func (o *ResourceAllOf) GetStatus() map[string]interface{} { - if o == nil || IsNil(o.Status) { - var ret map[string]interface{} - return ret - } - return o.Status -} - -// GetStatusOk returns a tuple with the Status field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceAllOf) GetStatusOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.Status) { - return map[string]interface{}{}, false - } - return o.Status, true -} - -// HasStatus returns a boolean if a field has been set. -func (o *ResourceAllOf) HasStatus() bool { - if o != nil && !IsNil(o.Status) { - return true - } - - return false -} - -// SetStatus gets a reference to the given map[string]interface{} and assigns it to the Status field. -func (o *ResourceAllOf) SetStatus(v map[string]interface{}) { - o.Status = v -} - -func (o ResourceAllOf) MarshalJSON() ([]byte, error) { - toSerialize, err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o ResourceAllOf) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - if !IsNil(o.Name) { - toSerialize["name"] = o.Name - } - if !IsNil(o.ConsumerName) { - toSerialize["consumer_name"] = o.ConsumerName - } - if !IsNil(o.Version) { - toSerialize["version"] = o.Version - } - if !IsNil(o.CreatedAt) { - toSerialize["created_at"] = o.CreatedAt - } - if !IsNil(o.UpdatedAt) { - toSerialize["updated_at"] = o.UpdatedAt - } - if !IsNil(o.DeletedAt) { - toSerialize["deleted_at"] = o.DeletedAt - } - if !IsNil(o.Manifest) { - toSerialize["manifest"] = o.Manifest - } - if !IsNil(o.GroupResource) { - toSerialize["group_resource"] = o.GroupResource - } - if !IsNil(o.DeleteOption) { - toSerialize["delete_option"] = o.DeleteOption - } - if !IsNil(o.UpdateStrategy) { - toSerialize["update_strategy"] = o.UpdateStrategy - } - if !IsNil(o.Status) { - toSerialize["status"] = o.Status - } - return toSerialize, nil -} - -type NullableResourceAllOf struct { - value *ResourceAllOf - isSet bool -} - -func (v NullableResourceAllOf) Get() *ResourceAllOf { - return v.value -} - -func (v *NullableResourceAllOf) Set(val *ResourceAllOf) { - v.value = val - v.isSet = true -} - -func (v NullableResourceAllOf) IsSet() bool { - return v.isSet -} - -func (v *NullableResourceAllOf) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableResourceAllOf(val *ResourceAllOf) *NullableResourceAllOf { - return &NullableResourceAllOf{value: val, isSet: true} -} - -func (v NullableResourceAllOf) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableResourceAllOf) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} diff --git a/pkg/api/openapi/model_resource_list.go b/pkg/api/openapi/model_resource_list.go deleted file mode 100644 index 77dd5f77..00000000 --- a/pkg/api/openapi/model_resource_list.go +++ /dev/null @@ -1,223 +0,0 @@ -/* -maestro Service API - -maestro Service API - -API version: 0.0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package openapi - -import ( - "encoding/json" -) - -// checks if the ResourceList type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &ResourceList{} - -// ResourceList struct for ResourceList -type ResourceList struct { - Kind string `json:"kind"` - Page int32 `json:"page"` - Size int32 `json:"size"` - Total int32 `json:"total"` - Items []Resource `json:"items"` -} - -// NewResourceList instantiates a new ResourceList object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewResourceList(kind string, page int32, size int32, total int32, items []Resource) *ResourceList { - this := ResourceList{} - this.Kind = kind - this.Page = page - this.Size = size - this.Total = total - this.Items = items - return &this -} - -// NewResourceListWithDefaults instantiates a new ResourceList object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewResourceListWithDefaults() *ResourceList { - this := ResourceList{} - return &this -} - -// GetKind returns the Kind field value -func (o *ResourceList) GetKind() string { - if o == nil { - var ret string - return ret - } - - return o.Kind -} - -// GetKindOk returns a tuple with the Kind field value -// and a boolean to check if the value has been set. -func (o *ResourceList) GetKindOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Kind, true -} - -// SetKind sets field value -func (o *ResourceList) SetKind(v string) { - o.Kind = v -} - -// GetPage returns the Page field value -func (o *ResourceList) GetPage() int32 { - if o == nil { - var ret int32 - return ret - } - - return o.Page -} - -// GetPageOk returns a tuple with the Page field value -// and a boolean to check if the value has been set. -func (o *ResourceList) GetPageOk() (*int32, bool) { - if o == nil { - return nil, false - } - return &o.Page, true -} - -// SetPage sets field value -func (o *ResourceList) SetPage(v int32) { - o.Page = v -} - -// GetSize returns the Size field value -func (o *ResourceList) GetSize() int32 { - if o == nil { - var ret int32 - return ret - } - - return o.Size -} - -// GetSizeOk returns a tuple with the Size field value -// and a boolean to check if the value has been set. -func (o *ResourceList) GetSizeOk() (*int32, bool) { - if o == nil { - return nil, false - } - return &o.Size, true -} - -// SetSize sets field value -func (o *ResourceList) SetSize(v int32) { - o.Size = v -} - -// GetTotal returns the Total field value -func (o *ResourceList) GetTotal() int32 { - if o == nil { - var ret int32 - return ret - } - - return o.Total -} - -// GetTotalOk returns a tuple with the Total field value -// and a boolean to check if the value has been set. -func (o *ResourceList) GetTotalOk() (*int32, bool) { - if o == nil { - return nil, false - } - return &o.Total, true -} - -// SetTotal sets field value -func (o *ResourceList) SetTotal(v int32) { - o.Total = v -} - -// GetItems returns the Items field value -func (o *ResourceList) GetItems() []Resource { - if o == nil { - var ret []Resource - return ret - } - - return o.Items -} - -// GetItemsOk returns a tuple with the Items field value -// and a boolean to check if the value has been set. -func (o *ResourceList) GetItemsOk() ([]Resource, bool) { - if o == nil { - return nil, false - } - return o.Items, true -} - -// SetItems sets field value -func (o *ResourceList) SetItems(v []Resource) { - o.Items = v -} - -func (o ResourceList) MarshalJSON() ([]byte, error) { - toSerialize, err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o ResourceList) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - toSerialize["kind"] = o.Kind - toSerialize["page"] = o.Page - toSerialize["size"] = o.Size - toSerialize["total"] = o.Total - toSerialize["items"] = o.Items - return toSerialize, nil -} - -type NullableResourceList struct { - value *ResourceList - isSet bool -} - -func (v NullableResourceList) Get() *ResourceList { - return v.value -} - -func (v *NullableResourceList) Set(val *ResourceList) { - v.value = val - v.isSet = true -} - -func (v NullableResourceList) IsSet() bool { - return v.isSet -} - -func (v *NullableResourceList) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableResourceList(val *ResourceList) *NullableResourceList { - return &NullableResourceList{value: val, isSet: true} -} - -func (v NullableResourceList) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableResourceList) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} diff --git a/pkg/api/openapi/model_resource_list_all_of.go b/pkg/api/openapi/model_resource_list_all_of.go deleted file mode 100644 index 1e3bf9a0..00000000 --- a/pkg/api/openapi/model_resource_list_all_of.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -maestro Service API - -maestro Service API - -API version: 0.0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package openapi - -import ( - "encoding/json" -) - -// checks if the ResourceListAllOf type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &ResourceListAllOf{} - -// ResourceListAllOf struct for ResourceListAllOf -type ResourceListAllOf struct { - Items []Resource `json:"items,omitempty"` -} - -// NewResourceListAllOf instantiates a new ResourceListAllOf object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewResourceListAllOf() *ResourceListAllOf { - this := ResourceListAllOf{} - return &this -} - -// NewResourceListAllOfWithDefaults instantiates a new ResourceListAllOf object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewResourceListAllOfWithDefaults() *ResourceListAllOf { - this := ResourceListAllOf{} - return &this -} - -// GetItems returns the Items field value if set, zero value otherwise. -func (o *ResourceListAllOf) GetItems() []Resource { - if o == nil || IsNil(o.Items) { - var ret []Resource - return ret - } - return o.Items -} - -// GetItemsOk returns a tuple with the Items field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourceListAllOf) GetItemsOk() ([]Resource, bool) { - if o == nil || IsNil(o.Items) { - return nil, false - } - return o.Items, true -} - -// HasItems returns a boolean if a field has been set. -func (o *ResourceListAllOf) HasItems() bool { - if o != nil && !IsNil(o.Items) { - return true - } - - return false -} - -// SetItems gets a reference to the given []Resource and assigns it to the Items field. -func (o *ResourceListAllOf) SetItems(v []Resource) { - o.Items = v -} - -func (o ResourceListAllOf) MarshalJSON() ([]byte, error) { - toSerialize, err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o ResourceListAllOf) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - if !IsNil(o.Items) { - toSerialize["items"] = o.Items - } - return toSerialize, nil -} - -type NullableResourceListAllOf struct { - value *ResourceListAllOf - isSet bool -} - -func (v NullableResourceListAllOf) Get() *ResourceListAllOf { - return v.value -} - -func (v *NullableResourceListAllOf) Set(val *ResourceListAllOf) { - v.value = val - v.isSet = true -} - -func (v NullableResourceListAllOf) IsSet() bool { - return v.isSet -} - -func (v *NullableResourceListAllOf) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableResourceListAllOf(val *ResourceListAllOf) *NullableResourceListAllOf { - return &NullableResourceListAllOf{value: val, isSet: true} -} - -func (v NullableResourceListAllOf) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableResourceListAllOf) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} diff --git a/pkg/api/openapi/model_resource_patch_request.go b/pkg/api/openapi/model_resource_patch_request.go deleted file mode 100644 index a72ea467..00000000 --- a/pkg/api/openapi/model_resource_patch_request.go +++ /dev/null @@ -1,232 +0,0 @@ -/* -maestro Service API - -maestro Service API - -API version: 0.0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package openapi - -import ( - "encoding/json" -) - -// checks if the ResourcePatchRequest type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &ResourcePatchRequest{} - -// ResourcePatchRequest struct for ResourcePatchRequest -type ResourcePatchRequest struct { - Version *int32 `json:"version,omitempty"` - Manifest map[string]interface{} `json:"manifest,omitempty"` - DeleteOption map[string]interface{} `json:"delete_option,omitempty"` - UpdateStrategy map[string]interface{} `json:"update_strategy,omitempty"` -} - -// NewResourcePatchRequest instantiates a new ResourcePatchRequest object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewResourcePatchRequest() *ResourcePatchRequest { - this := ResourcePatchRequest{} - return &this -} - -// NewResourcePatchRequestWithDefaults instantiates a new ResourcePatchRequest object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewResourcePatchRequestWithDefaults() *ResourcePatchRequest { - this := ResourcePatchRequest{} - return &this -} - -// GetVersion returns the Version field value if set, zero value otherwise. -func (o *ResourcePatchRequest) GetVersion() int32 { - if o == nil || IsNil(o.Version) { - var ret int32 - return ret - } - return *o.Version -} - -// GetVersionOk returns a tuple with the Version field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourcePatchRequest) GetVersionOk() (*int32, bool) { - if o == nil || IsNil(o.Version) { - return nil, false - } - return o.Version, true -} - -// HasVersion returns a boolean if a field has been set. -func (o *ResourcePatchRequest) HasVersion() bool { - if o != nil && !IsNil(o.Version) { - return true - } - - return false -} - -// SetVersion gets a reference to the given int32 and assigns it to the Version field. -func (o *ResourcePatchRequest) SetVersion(v int32) { - o.Version = &v -} - -// GetManifest returns the Manifest field value if set, zero value otherwise. -func (o *ResourcePatchRequest) GetManifest() map[string]interface{} { - if o == nil || IsNil(o.Manifest) { - var ret map[string]interface{} - return ret - } - return o.Manifest -} - -// GetManifestOk returns a tuple with the Manifest field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourcePatchRequest) GetManifestOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.Manifest) { - return map[string]interface{}{}, false - } - return o.Manifest, true -} - -// HasManifest returns a boolean if a field has been set. -func (o *ResourcePatchRequest) HasManifest() bool { - if o != nil && !IsNil(o.Manifest) { - return true - } - - return false -} - -// SetManifest gets a reference to the given map[string]interface{} and assigns it to the Manifest field. -func (o *ResourcePatchRequest) SetManifest(v map[string]interface{}) { - o.Manifest = v -} - -// GetDeleteOption returns the DeleteOption field value if set, zero value otherwise. -func (o *ResourcePatchRequest) GetDeleteOption() map[string]interface{} { - if o == nil || IsNil(o.DeleteOption) { - var ret map[string]interface{} - return ret - } - return o.DeleteOption -} - -// GetDeleteOptionOk returns a tuple with the DeleteOption field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourcePatchRequest) GetDeleteOptionOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.DeleteOption) { - return map[string]interface{}{}, false - } - return o.DeleteOption, true -} - -// HasDeleteOption returns a boolean if a field has been set. -func (o *ResourcePatchRequest) HasDeleteOption() bool { - if o != nil && !IsNil(o.DeleteOption) { - return true - } - - return false -} - -// SetDeleteOption gets a reference to the given map[string]interface{} and assigns it to the DeleteOption field. -func (o *ResourcePatchRequest) SetDeleteOption(v map[string]interface{}) { - o.DeleteOption = v -} - -// GetUpdateStrategy returns the UpdateStrategy field value if set, zero value otherwise. -func (o *ResourcePatchRequest) GetUpdateStrategy() map[string]interface{} { - if o == nil || IsNil(o.UpdateStrategy) { - var ret map[string]interface{} - return ret - } - return o.UpdateStrategy -} - -// GetUpdateStrategyOk returns a tuple with the UpdateStrategy field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *ResourcePatchRequest) GetUpdateStrategyOk() (map[string]interface{}, bool) { - if o == nil || IsNil(o.UpdateStrategy) { - return map[string]interface{}{}, false - } - return o.UpdateStrategy, true -} - -// HasUpdateStrategy returns a boolean if a field has been set. -func (o *ResourcePatchRequest) HasUpdateStrategy() bool { - if o != nil && !IsNil(o.UpdateStrategy) { - return true - } - - return false -} - -// SetUpdateStrategy gets a reference to the given map[string]interface{} and assigns it to the UpdateStrategy field. -func (o *ResourcePatchRequest) SetUpdateStrategy(v map[string]interface{}) { - o.UpdateStrategy = v -} - -func (o ResourcePatchRequest) MarshalJSON() ([]byte, error) { - toSerialize, err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o ResourcePatchRequest) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - if !IsNil(o.Version) { - toSerialize["version"] = o.Version - } - if !IsNil(o.Manifest) { - toSerialize["manifest"] = o.Manifest - } - if !IsNil(o.DeleteOption) { - toSerialize["delete_option"] = o.DeleteOption - } - if !IsNil(o.UpdateStrategy) { - toSerialize["update_strategy"] = o.UpdateStrategy - } - return toSerialize, nil -} - -type NullableResourcePatchRequest struct { - value *ResourcePatchRequest - isSet bool -} - -func (v NullableResourcePatchRequest) Get() *ResourcePatchRequest { - return v.value -} - -func (v *NullableResourcePatchRequest) Set(val *ResourcePatchRequest) { - v.value = val - v.isSet = true -} - -func (v NullableResourcePatchRequest) IsSet() bool { - return v.isSet -} - -func (v *NullableResourcePatchRequest) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableResourcePatchRequest(val *ResourcePatchRequest) *NullableResourcePatchRequest { - return &NullableResourcePatchRequest{value: val, isSet: true} -} - -func (v NullableResourcePatchRequest) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableResourcePatchRequest) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} diff --git a/pkg/api/presenters/kind.go b/pkg/api/presenters/kind.go index a534f8b4..c4b0fc7b 100755 --- a/pkg/api/presenters/kind.go +++ b/pkg/api/presenters/kind.go @@ -14,9 +14,9 @@ func ObjectKind(i interface{}) *string { case api.ConsumerList, *api.ConsumerList, []api.Consumer, []*api.Consumer: result = "ConsumerList" case api.Resource, *api.Resource: - result = "Resource" + result = "ResourceBundle" case api.ResourceList, *api.ResourceList, []api.Resource, []*api.Resource: - result = "ResourceList" + result = "ResourceBundleList" case errors.ServiceError, *errors.ServiceError: result = "Error" } diff --git a/pkg/api/presenters/path.go b/pkg/api/presenters/path.go index c11120b1..f5aec10f 100755 --- a/pkg/api/presenters/path.go +++ b/pkg/api/presenters/path.go @@ -20,7 +20,7 @@ func ObjectPath(id string, obj interface{}) *string { func path(i interface{}) string { switch i.(type) { case api.Resource, *api.Resource: - return "resources" + return "resource-bundles" case api.Consumer, *api.Consumer: return "consumers" case errors.ServiceError, *errors.ServiceError: diff --git a/pkg/api/presenters/resource.go b/pkg/api/presenters/resource.go deleted file mode 100755 index 0663b312..00000000 --- a/pkg/api/presenters/resource.go +++ /dev/null @@ -1,70 +0,0 @@ -package presenters - -import ( - "gorm.io/datatypes" - - "github.com/openshift-online/maestro/pkg/api" - "github.com/openshift-online/maestro/pkg/api/openapi" - "github.com/openshift-online/maestro/pkg/constants" - "github.com/openshift-online/maestro/pkg/util" -) - -// ConvertResource converts a resource from the API to the openapi representation. -func ConvertResource(resource openapi.Resource) (*api.Resource, error) { - payload, err := ConvertResourceManifest(resource.Manifest, resource.GroupResource, resource.DeleteOption, resource.UpdateStrategy) - if err != nil { - return nil, err - } - return &api.Resource{ - Name: util.NilToEmptyString(resource.Name), - Meta: api.Meta{ - ID: util.NilToEmptyString(resource.Id), - }, - ConsumerName: util.NilToEmptyString(resource.ConsumerName), - Version: util.NilToEmptyInt32(resource.Version), - // Set the default source ID for RESTful API calls and do not allow modification - Source: constants.DefaultSourceID, - Type: api.ResourceTypeSingle, - Payload: payload, - }, nil -} - -// ConvertResourceManifest converts a resource manifest from the openapi representation to the API. -func ConvertResourceManifest(manifest, groupResource, deleteOption, updateStrategy map[string]interface{}) (datatypes.JSONMap, error) { - return api.EncodeManifest(manifest, groupResource, deleteOption, updateStrategy) -} - -// PresentResource converts a resource from the API to the openapi representation. -func PresentResource(resource *api.Resource) (*openapi.Resource, error) { - manifest, groupResource, deleteOption, updateStrategy, err := api.DecodeManifest(resource.Payload) - if err != nil { - return nil, err - } - status, err := api.DecodeStatus(resource.Status) - if err != nil { - return nil, err - } - reference := PresentReference(resource.ID, resource) - res := &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, - GroupResource: groupResource, - DeleteOption: deleteOption, - UpdateStrategy: updateStrategy, - Status: status, - } - - // set the deletedAt field if the resource has been marked as deleted - if !resource.DeletedAt.Time.IsZero() { - res.DeletedAt = openapi.PtrTime(resource.DeletedAt.Time) - } - - return res, nil -} diff --git a/pkg/api/presenters/resource_bundle.go b/pkg/api/presenters/resource_bundle.go index 737659e2..b83690b2 100644 --- a/pkg/api/presenters/resource_bundle.go +++ b/pkg/api/presenters/resource_bundle.go @@ -1,15 +1,13 @@ package presenters import ( - "fmt" - "github.com/openshift-online/maestro/pkg/api" "github.com/openshift-online/maestro/pkg/api/openapi" ) // PresentResourceBundle converts a resource from the API to the openapi representation. func PresentResourceBundle(resource *api.Resource) (*openapi.ResourceBundle, error) { - manifestBundle, err := api.DecodeManifestBundle(resource.Payload) + manifestWrapper, err := api.DecodeManifestBundle(resource.Payload) if err != nil { return nil, err } @@ -18,10 +16,11 @@ func PresentResourceBundle(resource *api.Resource) (*openapi.ResourceBundle, err return nil, err } - res := &openapi.ResourceBundle{ - Id: openapi.PtrString(resource.ID), - Kind: openapi.PtrString("ResourceBundle"), - Href: openapi.PtrString(fmt.Sprintf("%s/%s/%s", BasePath, "resource-bundles", resource.ID)), + reference := PresentReference(resource.ID, resource) + rb := &openapi.ResourceBundle{ + Id: reference.Id, + Kind: reference.Kind, + Href: reference.Href, Name: openapi.PtrString(resource.Name), ConsumerName: openapi.PtrString(resource.ConsumerName), Version: openapi.PtrInt32(resource.Version), @@ -30,17 +29,17 @@ func PresentResourceBundle(resource *api.Resource) (*openapi.ResourceBundle, err Status: status, } - if manifestBundle != nil { - res.Metadata = manifestBundle.Meta - res.Manifests = manifestBundle.Manifests - res.ManifestConfigs = manifestBundle.ManifestConfigs - res.DeleteOption = manifestBundle.DeleteOption + if manifestWrapper != nil { + rb.Metadata = manifestWrapper.Meta + rb.Manifests = manifestWrapper.Manifests + rb.ManifestConfigs = manifestWrapper.ManifestConfigs + rb.DeleteOption = manifestWrapper.DeleteOption } // set the deletedAt field if the resource has been marked as deleted if !resource.DeletedAt.Time.IsZero() { - res.DeletedAt = openapi.PtrTime(resource.DeletedAt.Time) + rb.DeletedAt = openapi.PtrTime(resource.DeletedAt.Time) } - return res, nil + return rb, nil } diff --git a/pkg/api/resource_bundle.go b/pkg/api/resource_bundle.go new file mode 100755 index 00000000..60f820ae --- /dev/null +++ b/pkg/api/resource_bundle.go @@ -0,0 +1,216 @@ +package api + +import ( + "encoding/json" + "fmt" + + cloudevents "github.com/cloudevents/sdk-go/v2" + cloudeventstypes "github.com/cloudevents/sdk-go/v2/types" + "gorm.io/datatypes" + + "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" + workpayload "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" + "open-cluster-management.io/sdk-go/pkg/cloudevents/work/source/codec" +) + +// ResourceBundleStatus defines resource bundle status +type ResourceBundleStatus struct { + ObservedVersion int32 + SequenceID string + *workpayload.ManifestBundleStatus +} + +// ManifestBundleWrapper is a wrapper used for openapi output that contains: +// * metadata - manifestwork metadata +// * manifests - resource manifests +// * manifest configs - manifest configs +// * delete option - delete option +type ManifestBundleWrapper struct { + Meta map[string]interface{} + Manifests []map[string]interface{} + ManifestConfigs []map[string]interface{} + DeleteOption map[string]interface{} +} + +// DecodeManifestBundle converts a CloudEvent JSONMap representation of a list of resource manifests +// into manifests and manifestconfigs that will be used in openapi output. +func DecodeManifestBundle(manifestBundle datatypes.JSONMap) (*ManifestBundleWrapper, error) { + if len(manifestBundle) == 0 { + return nil, nil + } + + evt, err := JSONMAPToCloudEvent(manifestBundle) + if err != nil { + return nil, fmt.Errorf("failed to convert resource manifest bundle to cloudevent: %v", err) + } + + metaData := map[string]any{} + extensions := evt.Extensions() + if meta, ok := extensions[codec.ExtensionWorkMeta]; ok { + metaJson, err := cloudeventstypes.ToString(meta) + if err != nil { + return nil, fmt.Errorf("failed to get work meta extension: %v", err) + } + + if err := json.Unmarshal([]byte(metaJson), &metaData); err != nil { + return nil, fmt.Errorf("failed to unmarshal work meta extension: %v", err) + } + } + + eventPayload := &workpayload.ManifestBundle{} + if err := evt.DataAs(eventPayload); err != nil { + return nil, fmt.Errorf("failed to decode cloudevent payload: %v", err) + } + + manifests := make([]map[string]interface{}, 0, len(eventPayload.Manifests)) + for _, manifest := range eventPayload.Manifests { + m := map[string]interface{}{} + if err := json.Unmarshal(manifest.Raw, &m); err != nil { + return nil, fmt.Errorf("failed to unmarshal manifest raw: %v", err) + } + manifests = append(manifests, m) + } + manifestConfigs := make([]map[string]interface{}, 0, len(eventPayload.ManifestConfigs)) + for _, manifestConfig := range eventPayload.ManifestConfigs { + mbytes, err := json.Marshal(manifestConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal manifest config: %v", err) + } + m := map[string]interface{}{} + if err := json.Unmarshal(mbytes, &m); err != nil { + return nil, fmt.Errorf("failed to unmarshal manifest config: %v", err) + } + manifestConfigs = append(manifestConfigs, m) + } + deleteOption := map[string]interface{}{} + if eventPayload.DeleteOption != nil { + dbytes, err := json.Marshal(eventPayload.DeleteOption) + if err != nil { + return nil, fmt.Errorf("failed to marshal delete option: %v", err) + } + if err := json.Unmarshal(dbytes, &deleteOption); err != nil { + return nil, fmt.Errorf("failed to unmarshal delete option: %v", err) + } + } + + return &ManifestBundleWrapper{ + Meta: metaData, + Manifests: manifests, + ManifestConfigs: manifestConfigs, + DeleteOption: deleteOption, + }, nil +} + +// DecodeBundleStatus converts a CloudEvent JSONMap representation of a resource bundle status +// into resource bundle status (map[string]interface{}) in openapi output. +func DecodeBundleStatus(status datatypes.JSONMap) (map[string]interface{}, error) { + if len(status) == 0 { + return nil, nil + } + + evt, err := JSONMAPToCloudEvent(status) + if err != nil { + return nil, fmt.Errorf("failed to convert resource bundle status to cloudevent: %v", err) + } + + evtExtensions := evt.Extensions() + resourceVersion, err := cloudeventstypes.ToInteger(evtExtensions[types.ExtensionResourceVersion]) + if err != nil { + return nil, fmt.Errorf("failed to get resourceversion extension: %v", err) + } + + sequenceID, err := cloudeventstypes.ToString(evtExtensions[types.ExtensionStatusUpdateSequenceID]) + if err != nil { + return nil, fmt.Errorf("failed to get sequenceid extension: %v", err) + } + + resourceBundleStatus := &ResourceBundleStatus{ + ObservedVersion: resourceVersion, + SequenceID: sequenceID, + ManifestBundleStatus: &workpayload.ManifestBundleStatus{}, + } + if err := evt.DataAs(resourceBundleStatus.ManifestBundleStatus); err != nil { + return nil, fmt.Errorf("failed to decode cloudevent payload: %v", err) + } + resourceBundleStatusJSON, err := json.Marshal(resourceBundleStatus) + if err != nil { + return nil, fmt.Errorf("failed to marshal resource status: %v", err) + } + resourceBundleStatusMap := make(map[string]interface{}) + if err := json.Unmarshal(resourceBundleStatusJSON, &resourceBundleStatusMap); err != nil { + return nil, fmt.Errorf("failed to unmarshal resource status: %v", err) + } + + return resourceBundleStatusMap, nil +} + +// JSONMAPToCloudEvent converts a JSONMap (resource manifest or status) to a CloudEvent +func JSONMAPToCloudEvent(jsonmap datatypes.JSONMap) (*cloudevents.Event, error) { + if len(jsonmap) == 0 { + return nil, fmt.Errorf("failed to convert empty jsonmap to cloudevent") + } + + var err error + var resJSON []byte + + if metadata, ok := jsonmap[codec.ExtensionWorkMeta]; ok { + // cloudevents require its extension value as string, so we need convert the metadata object + // to string back + + // ensure the original resource will be not changed + resCopy := datatypes.JSONMap{} + for key, value := range jsonmap { + resCopy[key] = value + } + + metaJson, err := json.Marshal(metadata) + if err != nil { + return nil, fmt.Errorf("failed to marshal metadata to JSON: %v", err) + } + + resCopy[codec.ExtensionWorkMeta] = string(metaJson) + + resJSON, err = resCopy.MarshalJSON() + if err != nil { + return nil, fmt.Errorf("failed to marshal JSONMAP to cloudevent JSON: %v", err) + } + } else { + resJSON, err = jsonmap.MarshalJSON() + if err != nil { + return nil, fmt.Errorf("failed to marshal JSONMAP to cloudevent JSON: %v", err) + } + } + + evt := &cloudevents.Event{} + if err := json.Unmarshal(resJSON, evt); err != nil { + return nil, fmt.Errorf("failed to unmarshal JSONMAP to cloudevent: %v", err) + } + + return evt, nil +} + +// CloudEventToJSONMap converts a CloudEvent to a JSONMap (resource manifest or status) +func CloudEventToJSONMap(evt *cloudevents.Event) (datatypes.JSONMap, error) { + evtJSON, err := json.Marshal(evt) + if err != nil { + return nil, fmt.Errorf("failed to marshal cloudevent to JSONMAP: %v", err) + } + + var res datatypes.JSONMap + if err := res.UnmarshalJSON(evtJSON); err != nil { + return nil, fmt.Errorf("failed to unmarshal cloudevent JSON to JSONMAP: %v", err) + } + + if metadata, ok := res[codec.ExtensionWorkMeta]; ok { + // cloudevents treat its extension value as string, so we need convert metadata extension + // to an object for supporting to query the resources with metadata + objectMeta := map[string]any{} + + if err := json.Unmarshal([]byte(fmt.Sprintf("%s", metadata)), &objectMeta); err != nil { + return nil, fmt.Errorf("failed to unmarshal metadata extension to object: %v", err) + } + res[codec.ExtensionWorkMeta] = objectMeta + } + + return res, nil +} diff --git a/pkg/api/resource_bundle_types_test.go b/pkg/api/resource_bundle_test.go similarity index 89% rename from pkg/api/resource_bundle_types_test.go rename to pkg/api/resource_bundle_test.go index ff298783..c34d4449 100644 --- a/pkg/api/resource_bundle_types_test.go +++ b/pkg/api/resource_bundle_test.go @@ -46,25 +46,25 @@ func TestDecodeManifestBundle(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { - gotManifestBundle, err := DecodeManifestBundle(c.input) + gotManifestBundleWrapper, err := DecodeManifestBundle(c.input) if err != nil { if err.Error() != c.expectedErrorMsg { t.Errorf("expected %#v but got: %#v", c.expectedErrorMsg, err) } return } - if gotManifestBundle != nil { - if !equality.Semantic.DeepEqual(c.expectedMetaData, gotManifestBundle.Meta) { - t.Errorf("expected metaData %#v but got: %#v", c.expectedMetaData, gotManifestBundle.Meta) + if gotManifestBundleWrapper != nil { + if !equality.Semantic.DeepEqual(c.expectedMetaData, gotManifestBundleWrapper.Meta) { + t.Errorf("expected metaData %#v but got: %#v", c.expectedMetaData, gotManifestBundleWrapper.Meta) } - if !equality.Semantic.DeepEqual(c.expectedManifests, gotManifestBundle.Manifests) { - t.Errorf("expected manifests %#v but got: %#v", c.expectedManifests, gotManifestBundle.Manifests) + if !equality.Semantic.DeepEqual(c.expectedManifests, gotManifestBundleWrapper.Manifests) { + t.Errorf("expected manifests %#v but got: %#v", c.expectedManifests, gotManifestBundleWrapper.Manifests) } - if !equality.Semantic.DeepEqual(c.expectedManifestConfigs, gotManifestBundle.ManifestConfigs) { - t.Errorf("expected manifestConfigs %#v but got: %#v", c.expectedManifestConfigs, gotManifestBundle.ManifestConfigs) + if !equality.Semantic.DeepEqual(c.expectedManifestConfigs, gotManifestBundleWrapper.ManifestConfigs) { + t.Errorf("expected manifestConfigs %#v but got: %#v", c.expectedManifestConfigs, gotManifestBundleWrapper.ManifestConfigs) } - if !equality.Semantic.DeepEqual(c.expectedDeleteOption, gotManifestBundle.DeleteOption) { - t.Errorf("expected deleteOption %#v but got: %#v", c.expectedDeleteOption, gotManifestBundle.DeleteOption) + if !equality.Semantic.DeepEqual(c.expectedDeleteOption, gotManifestBundleWrapper.DeleteOption) { + t.Errorf("expected deleteOption %#v but got: %#v", c.expectedDeleteOption, gotManifestBundleWrapper.DeleteOption) } } }) @@ -94,22 +94,40 @@ func TestDecodeBundleStatus(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { - got, err := DecodeBundleStatus(c.input) + gotBundleStatus, err := DecodeBundleStatus(c.input) if err != nil { if err.Error() != c.expectedErrorMsg { t.Errorf("expected %#v but got: %#v", c.expectedErrorMsg, err) } return } - gotBytes, err := json.Marshal(got) + gotBundleStatusBytes, err := json.Marshal(gotBundleStatus) if err != nil { t.Errorf("failed to marshal got resource bundle status: %v", err) } - if string(gotBytes) != c.expectedJSON { - t.Errorf("expected %s but got: %s", c.expectedJSON, string(gotBytes)) + if string(gotBundleStatusBytes) != c.expectedJSON { + t.Errorf("expected status %s but got status: %s", c.expectedJSON, string(gotBundleStatusBytes)) } }) } } + +func newJSONMap(t *testing.T, data string) datatypes.JSONMap { + jsonmap := map[string]interface{}{} + if err := json.Unmarshal([]byte(data), &jsonmap); err != nil { + t.Fatal(err) + } + + return jsonmap +} + +func newJSONMAPList(t *testing.T, data ...string) []map[string]any { + jsonmapList := []map[string]any{} + for _, d := range data { + jsonmapList = append(jsonmapList, newJSONMap(t, d)) + } + + return jsonmapList +} diff --git a/pkg/api/resource_bundle_types.go b/pkg/api/resource_bundle_types.go deleted file mode 100755 index 2c1d7e50..00000000 --- a/pkg/api/resource_bundle_types.go +++ /dev/null @@ -1,146 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - - cloudeventstypes "github.com/cloudevents/sdk-go/v2/types" - "gorm.io/datatypes" - - cetypes "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" - workpayload "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" - "open-cluster-management.io/sdk-go/pkg/cloudevents/work/source/codec" -) - -type ResourceBundleStatus struct { - ObservedVersion int32 - SequenceID string - *workpayload.ManifestBundleStatus -} - -// ManifestBundleWrapper is a wrapper used for openapi output that contains: -// * metadata - manifestwork metadata -// * manifests - resource manifests -// * manifest configs - manifest configs -// * delete option - delete option -type ManifestBundleWrapper struct { - Meta map[string]interface{} - Manifests []map[string]interface{} - ManifestConfigs []map[string]interface{} - DeleteOption map[string]interface{} -} - -// DecodeManifestBundle converts a CloudEvent JSONMap representation of a list of resource manifest -// into ManifestBundle that will be used in openapi output. -func DecodeManifestBundle(manifest datatypes.JSONMap) (*ManifestBundleWrapper, error) { - if len(manifest) == 0 { - return nil, nil - } - - evt, err := JSONMAPToCloudEvent(manifest) - if err != nil { - return nil, fmt.Errorf("failed to convert resource manifest bundle to cloudevent: %v", err) - } - - metaData := map[string]any{} - extensions := evt.Extensions() - if meta, ok := extensions[codec.ExtensionWorkMeta]; ok { - metaJson, err := cloudeventstypes.ToString(meta) - if err != nil { - return nil, fmt.Errorf("failed to get work meta extension: %v", err) - } - - if err := json.Unmarshal([]byte(metaJson), &metaData); err != nil { - return nil, fmt.Errorf("failed to unmarshal work meta extension: %v", err) - } - } - - eventPayload := &workpayload.ManifestBundle{} - if err := evt.DataAs(eventPayload); err != nil { - return nil, fmt.Errorf("failed to decode cloudevent payload as resource manifest bundle: %v", err) - } - - manifests := make([]map[string]interface{}, 0, len(eventPayload.Manifests)) - for _, manifest := range eventPayload.Manifests { - m := map[string]interface{}{} - if err := json.Unmarshal(manifest.Raw, &m); err != nil { - return nil, fmt.Errorf("failed to unmarshal manifest raw in bundle: %v", err) - } - manifests = append(manifests, m) - } - manifestConfigs := make([]map[string]interface{}, 0, len(eventPayload.ManifestConfigs)) - for _, manifestConfig := range eventPayload.ManifestConfigs { - mbytes, err := json.Marshal(manifestConfig) - if err != nil { - return nil, fmt.Errorf("failed to marshal manifest config in bundle: %v", err) - } - m := map[string]interface{}{} - if err := json.Unmarshal(mbytes, &m); err != nil { - return nil, fmt.Errorf("failed to unmarshal manifest config in bundle: %v", err) - } - manifestConfigs = append(manifestConfigs, m) - } - deleteOption := map[string]interface{}{} - if eventPayload.DeleteOption != nil { - dbytes, err := json.Marshal(eventPayload.DeleteOption) - if err != nil { - return nil, fmt.Errorf("failed to marshal delete option in bundle: %v", err) - } - if err := json.Unmarshal(dbytes, &deleteOption); err != nil { - return nil, fmt.Errorf("failed to unmarshal delete option in bundle: %v", err) - } - } - - return &ManifestBundleWrapper{ - Meta: metaData, - Manifests: manifests, - ManifestConfigs: manifestConfigs, - DeleteOption: deleteOption, - }, nil -} - -// DecodeBundleStatus converts a CloudEvent JSONMap representation of a resource bundle status -// into resource bundle status (map[string]interface{}) in openapi output. -func DecodeBundleStatus(status datatypes.JSONMap) (map[string]interface{}, error) { - if len(status) == 0 { - return nil, nil - } - - evt, err := JSONMAPToCloudEvent(status) - if err != nil { - return nil, fmt.Errorf("failed to convert resource bundle status to cloudevent: %v", err) - } - - evtExtensions := evt.Extensions() - resourceVersion, err := cloudeventstypes.ToInteger(evtExtensions[cetypes.ExtensionResourceVersion]) - if err != nil { - return nil, fmt.Errorf("failed to get resourceversion extension: %v", err) - } - - sequenceID, err := cloudeventstypes.ToString(evtExtensions[cetypes.ExtensionStatusUpdateSequenceID]) - if err != nil { - return nil, fmt.Errorf("failed to get sequenceid extension: %v", err) - } - - resourceBundleStatus := &ResourceBundleStatus{ - ObservedVersion: resourceVersion, - SequenceID: sequenceID, - } - - eventPayload := &workpayload.ManifestBundleStatus{} - if err := evt.DataAs(eventPayload); err != nil { - return nil, fmt.Errorf("failed to decode cloudevent data as resource bundle status: %v", err) - } - resourceBundleStatus.ManifestBundleStatus = eventPayload - - resourceBundleStatusJSON, err := json.Marshal(resourceBundleStatus) - if err != nil { - return nil, fmt.Errorf("failed to marshal resource bundle status to JSON: %v", err) - } - statusMap := make(map[string]interface{}) - if err := json.Unmarshal(resourceBundleStatusJSON, &statusMap); err != nil { - return nil, fmt.Errorf("failed to unmarshal resource bundle status JSON to map: %v", err) - } - - return statusMap, nil -} diff --git a/pkg/api/resource_types.go b/pkg/api/resource_types.go index d3eef734..7dde3656 100755 --- a/pkg/api/resource_types.go +++ b/pkg/api/resource_types.go @@ -1,32 +1,16 @@ package api import ( - "encoding/json" - "fmt" "strconv" - cloudevents "github.com/cloudevents/sdk-go/v2" - cloudeventstypes "github.com/cloudevents/sdk-go/v2/types" "gorm.io/datatypes" "gorm.io/gorm" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" ktypes "k8s.io/apimachinery/pkg/types" - - workv1 "open-cluster-management.io/api/work/v1" - cetypes "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" - workpayload "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" - "open-cluster-management.io/sdk-go/pkg/cloudevents/work/source/codec" ) type ResourceType string -const ( - ResourceTypeSingle ResourceType = "Single" - ResourceTypeBundle ResourceType = "Bundle" -) - type Resource struct { Meta Version int32 @@ -42,17 +26,6 @@ type Resource struct { Name string } -type ResourceStatus struct { - ContentStatus datatypes.JSONMap - ReconcileStatus *ReconcileStatus -} - -type ReconcileStatus struct { - ObservedVersion int32 - SequenceID string - Conditions []metav1.Condition -} - type ResourceList []*Resource type ResourceIndex map[string]*Resource @@ -90,286 +63,3 @@ func (d *Resource) GetResourceVersion() string { func (d *Resource) GetDeletionTimestamp() *metav1.Time { return &metav1.Time{Time: d.Meta.DeletedAt.Time} } - -type ResourcePatchRequest struct{} - -// JSONMAPToCloudEvent converts a JSONMap (resource manifest or status) to a CloudEvent -func JSONMAPToCloudEvent(res datatypes.JSONMap) (*cloudevents.Event, error) { - var err error - var resJSON []byte - - if metadata, ok := res[codec.ExtensionWorkMeta]; ok { - // cloudevents require its extension value as string, so we need convert the metadata object - // to string back - - // ensure the original resource will be not changed - resCopy := datatypes.JSONMap{} - for key, value := range res { - resCopy[key] = value - } - - metaJson, err := json.Marshal(metadata) - if err != nil { - return nil, fmt.Errorf("failed to marshal metadata to JSON: %v", err) - } - - resCopy[codec.ExtensionWorkMeta] = string(metaJson) - - resJSON, err = resCopy.MarshalJSON() - if err != nil { - return nil, fmt.Errorf("failed to marshal JSONMAP to cloudevent JSON: %v", err) - } - } else { - resJSON, err = res.MarshalJSON() - if err != nil { - return nil, fmt.Errorf("failed to marshal JSONMAP to cloudevent JSON: %v", err) - } - } - - evt := &cloudevents.Event{} - if err := json.Unmarshal(resJSON, evt); err != nil { - return nil, fmt.Errorf("failed to unmarshal JSONMAP to cloudevent: %v", err) - } - - return evt, nil -} - -// CloudEventToJSONMap converts a CloudEvent to a JSONMap (resource manifest or status) -func CloudEventToJSONMap(evt *cloudevents.Event) (datatypes.JSONMap, error) { - evtJSON, err := json.Marshal(evt) - if err != nil { - return nil, fmt.Errorf("failed to marshal cloudevent to JSONMAP: %v", err) - } - - var res datatypes.JSONMap - if err := res.UnmarshalJSON(evtJSON); err != nil { - return nil, fmt.Errorf("failed to unmarshal cloudevent JSON to JSONMAP: %v", err) - } - - if metadata, ok := res[codec.ExtensionWorkMeta]; ok { - // cloudevents treat its extension value as string, so we need convert metadata extension - // to an object for supporting to query the resources with metadata - objectMeta := map[string]any{} - - if err := json.Unmarshal([]byte(fmt.Sprintf("%s", metadata)), &objectMeta); err != nil { - return nil, fmt.Errorf("failed to unmarshal metadata extension to object: %v", err) - } - res[codec.ExtensionWorkMeta] = objectMeta - } - - return res, nil -} - -// EncodeManifest converts resource manifest, groupResource, deleteOption and updateStrategy (map[string]interface{}) into a CloudEvent JSONMap representation. -func EncodeManifest(manifest, groupResource, deleteOption, updateStrategy map[string]interface{}) (datatypes.JSONMap, error) { - if len(manifest) == 0 { - return nil, nil - } - - // default update strategy is ServerSideApply - upStrategy := &workv1.UpdateStrategy{ - Type: workv1.UpdateStrategyTypeServerSideApply, - } - if len(updateStrategy) != 0 { - updateStrategyBytes, err := json.Marshal(updateStrategy) - if err != nil { - return nil, fmt.Errorf("failed to marshal updateStrategy to json: %v", err) - } - err = json.Unmarshal(updateStrategyBytes, upStrategy) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json to updateStrategy: %v", err) - } - } - - // default delete option is Foreground - delOption := &workv1.DeleteOption{ - PropagationPolicy: workv1.DeletePropagationPolicyTypeForeground, - } - - // set delete option to Orphan if update strategy is ReadOnly - if upStrategy.Type == workv1.UpdateStrategyTypeReadOnly { - delOption = &workv1.DeleteOption{ - PropagationPolicy: workv1.DeletePropagationPolicyTypeOrphan, - } - } else { - if len(deleteOption) != 0 { - deleteOptionBytes, err := json.Marshal(deleteOption) - if err != nil { - return nil, fmt.Errorf("failed to marshal deleteOption to json: %v", err) - } - err = json.Unmarshal(deleteOptionBytes, delOption) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json to deleteOption: %v", err) - } - } - } - - // create a cloud event with the manifest as the data - evt := cetypes.NewEventBuilder("maestro", cetypes.CloudEventsType{}).NewEvent() - manifestBytes, err := json.Marshal(manifest) - if err != nil { - return nil, fmt.Errorf("failed to marshal manifest: %v", err) - } - unstructuredObj := &unstructured.Unstructured{ - Object: manifest, - } - - eventPayload := &workpayload.ManifestBundle{ - Manifests: []workv1.Manifest{ - { - RawExtension: runtime.RawExtension{Raw: manifestBytes}, - }, - }, - DeleteOption: delOption, - ManifestConfigs: []workv1.ManifestConfigOption{ - { - ResourceIdentifier: workv1.ResourceIdentifier{ - Group: groupResource["group"].(string), - Resource: groupResource["resource"].(string), - Name: unstructuredObj.GetName(), - Namespace: unstructuredObj.GetNamespace(), - }, - FeedbackRules: []workv1.FeedbackRule{ - { - Type: workv1.JSONPathsType, - JsonPaths: []workv1.JsonPath{ - { - Name: "status", - Path: ".status", - }, - }, - }, - }, - UpdateStrategy: upStrategy, - }, - }, - } - - if err := evt.SetData(cloudevents.ApplicationJSON, eventPayload); err != nil { - return nil, fmt.Errorf("failed to set cloud event data: %v", err) - } - - // convert cloudevent to JSONMap - manifest, err = CloudEventToJSONMap(&evt) - if err != nil { - return nil, fmt.Errorf("failed to convert cloudevent to resource manifest: %v", err) - } - - return manifest, nil -} - -// DecodeManifest converts a CloudEvent JSONMap representation of a resource manifest into resource -// manifest, groupResource, deleteOption and updateStrategy (map[string]interface{}) for openapi output. -func DecodeManifest(manifest datatypes.JSONMap) (map[string]interface{}, map[string]interface{}, map[string]interface{}, map[string]interface{}, error) { - if len(manifest) == 0 { - return nil, nil, nil, nil, nil - } - - evt, err := JSONMAPToCloudEvent(manifest) - if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to convert resource manifest to cloudevent: %v", err) - } - - eventPayload := &workpayload.ManifestBundle{} - if err := evt.DataAs(eventPayload); err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to decode cloudevent payload as resource manifest: %v", err) - } - - deleteOptionObj := map[string]interface{}{} - if eventPayload.DeleteOption != nil { - deleteOptionBytes, err := json.Marshal(eventPayload.DeleteOption) - if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to marshal deleteOption to json: %v", err) - } - if err := json.Unmarshal(deleteOptionBytes, &deleteOptionObj); err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to unmarshal deleteOption to cloudevent: %v", err) - } - } - manifestObj := map[string]interface{}{} - if len(eventPayload.Manifests) != 1 { - return nil, nil, nil, nil, fmt.Errorf("invalid number of manifests in the event payload: %d", len(eventPayload.Manifests)) - } - if err := json.Unmarshal(eventPayload.Manifests[0].Raw, &manifestObj); err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to unmarshal manifest raw to manifest: %v", err) - } - if len(eventPayload.ManifestConfigs) != 1 { - return nil, nil, nil, nil, fmt.Errorf("invalid number of manifestConfigs in the event payload: %d", len(eventPayload.ManifestConfigs)) - } - resourceGroup := map[string]interface{}{ - "group": eventPayload.ManifestConfigs[0].ResourceIdentifier.Group, - "resource": eventPayload.ManifestConfigs[0].ResourceIdentifier.Resource, - } - updateStrategyObj := map[string]interface{}{} - if eventPayload.ManifestConfigs[0].UpdateStrategy != nil { - updateStrategyBytes, err := json.Marshal(eventPayload.ManifestConfigs[0].UpdateStrategy) - if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to marshal updateStrategy to json: %v", err) - } - if err := json.Unmarshal(updateStrategyBytes, &updateStrategyObj); err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed to unmarshal updateStrategy json to object: %v", err) - } - } - - return manifestObj, resourceGroup, deleteOptionObj, updateStrategyObj, nil -} - -// DecodeStatus converts a CloudEvent JSONMap representation of a resource status -// into resource status (map[string]interface{}). -func DecodeStatus(status datatypes.JSONMap) (map[string]interface{}, error) { - if len(status) == 0 { - return nil, nil - } - - evt, err := JSONMAPToCloudEvent(status) - if err != nil { - return nil, fmt.Errorf("failed to convert resource status to cloudevent: %v", err) - } - - evtExtensions := evt.Extensions() - resourceVersion, err := cloudeventstypes.ToInteger(evtExtensions[cetypes.ExtensionResourceVersion]) - if err != nil { - return nil, fmt.Errorf("failed to get resourceversion extension: %v", err) - } - - sequenceID, err := cloudeventstypes.ToString(evtExtensions[cetypes.ExtensionStatusUpdateSequenceID]) - if err != nil { - return nil, fmt.Errorf("failed to get sequenceid extension: %v", err) - } - - resourceStatus := &ResourceStatus{ - ReconcileStatus: &ReconcileStatus{ - ObservedVersion: resourceVersion, - SequenceID: sequenceID, - }, - } - - eventPayload := &workpayload.ManifestBundleStatus{} - if err := evt.DataAs(eventPayload); err != nil { - return nil, fmt.Errorf("failed to decode cloudevent data as resource status: %v", err) - } - - if len(eventPayload.ResourceStatus) != 1 { - return nil, fmt.Errorf("invalid number of resource status in the event payload: %d", len(eventPayload.ResourceStatus)) - } - resourceStatus.ReconcileStatus.Conditions = eventPayload.ResourceStatus[0].Conditions - for _, value := range eventPayload.ResourceStatus[0].StatusFeedbacks.Values { - if value.Name == "status" { - contentStatus := make(map[string]interface{}) - if err := json.Unmarshal([]byte(*value.Value.JsonRaw), &contentStatus); err != nil { - return nil, fmt.Errorf("failed to convert status feedback value to content status: %v", err) - } - resourceStatus.ContentStatus = contentStatus - } - } - - resourceStatusJSON, err := json.Marshal(resourceStatus) - if err != nil { - return nil, fmt.Errorf("failed to marshal resource status to JSON: %v", err) - } - statusMap := make(map[string]interface{}) - if err := json.Unmarshal(resourceStatusJSON, &statusMap); err != nil { - return nil, fmt.Errorf("failed to unmarshal resource status JSON to object: %v", err) - } - - return statusMap, nil -} diff --git a/pkg/api/resource_types_test.go b/pkg/api/resource_types_test.go deleted file mode 100644 index f45dd779..00000000 --- a/pkg/api/resource_types_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package api - -import ( - "encoding/json" - "testing" - - "gorm.io/datatypes" - "k8s.io/apimachinery/pkg/api/equality" -) - -func TestEncodeManifest(t *testing.T) { - cases := []struct { - name string - manifest map[string]interface{} - groupResource map[string]interface{} - deleteOption map[string]interface{} - updateStrategy map[string]interface{} - expected datatypes.JSONMap - expectedErrorMsg string - }{ - { - name: "empty", - manifest: map[string]interface{}{}, - expected: datatypes.JSONMap{}, - }, - { - name: "valid", - manifest: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), - groupResource: newJSONMap(t, "{\"group\":\"\",\"resource\":\"configmaps\"}"), - expected: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"feedbackRules\":[{\"jsonPaths\":[{\"name\":\"status\",\"path\":\".status\"}],\"type\":\"JSONPaths\"}],\"resourceIdentifier\":{\"group\":\"\",\"name\":\"test\",\"namespace\":\"test\",\"resource\":\"configmaps\"},\"updateStrategy\":{\"type\":\"ServerSideApply\"}}],\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}]}}"), - }, - { - name: "valid", - deleteOption: newJSONMap(t, "{\"propagationPolicy\": \"Orphan\"}"), - manifest: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), - groupResource: newJSONMap(t, "{\"group\":\"\",\"resource\":\"configmaps\"}"), - updateStrategy: newJSONMap(t, "{\"type\": \"CreateOnly\"}"), - expected: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"deleteOption\":{\"propagationPolicy\":\"Orphan\"},\"manifestConfigs\":[{\"feedbackRules\":[{\"jsonPaths\":[{\"name\":\"status\",\"path\":\".status\"}],\"type\":\"JSONPaths\"}],\"resourceIdentifier\":{\"group\":\"\",\"name\":\"test\",\"namespace\":\"test\",\"resource\":\"configmaps\"},\"updateStrategy\":{\"type\":\"CreateOnly\"}}],\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}]}}"), - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - gotManifest, err := EncodeManifest(c.manifest, c.groupResource, c.deleteOption, c.updateStrategy) - 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) - } - }) - } -} - -func TestDecodeManifest(t *testing.T) { - cases := []struct { - name string - input datatypes.JSONMap - expectedManifest map[string]interface{} - expectedGroupResource map[string]interface{} - expectedDeleteOption map[string]interface{} - expectedUpdateStrategy map[string]interface{} - expectedErrorMsg string - }{ - { - name: "empty", - input: datatypes.JSONMap{}, - expectedManifest: nil, - expectedDeleteOption: nil, - expectedUpdateStrategy: nil, - expectedErrorMsg: "", - }, - { - name: "valid", - input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"deleteOption\":{\"propagationPolicy\":\"Orphan\"},\"manifestConfigs\":[{\"feedbackRules\":[{\"jsonPaths\":[{\"name\":\"status\",\"path\":\".status\"}],\"type\":\"JSONPaths\"}],\"resourceIdentifier\":{\"group\":\"\",\"name\":\"test\",\"namespace\":\"test\",\"resource\":\"configmaps\"},\"updateStrategy\":{\"type\":\"CreateOnly\"}}],\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}]}}"), - expectedManifest: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), - expectedGroupResource: newJSONMap(t, "{\"group\":\"\",\"resource\":\"configmaps\"}"), - expectedDeleteOption: newJSONMap(t, "{\"propagationPolicy\": \"Orphan\"}"), - expectedUpdateStrategy: newJSONMap(t, "{\"type\": \"CreateOnly\"}"), - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - gotManifest, gotGroupResource, 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.expectedManifest, gotManifest) { - t.Errorf("expected %#v but got: %#v", c.expectedManifest, gotManifest) - } - if !equality.Semantic.DeepDerivative(c.expectedGroupResource, gotGroupResource) { - t.Errorf("expected %#v but got: %#v", c.expectedGroupResource, gotGroupResource) - } - 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) - } - }) - } -} - -func TestDecodeStatus(t *testing.T) { - cases := []struct { - name string - input datatypes.JSONMap - expected map[string]interface{} - expectedErrorMsg string - }{ - { - name: "empty", - input: datatypes.JSONMap{}, - expected: nil, - expectedErrorMsg: "", - }, - { - name: "valid", - input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"id\":\"1f21fcbe-3e41-4639-ab8d-1713c578e4cd\",\"time\":\"2024-03-07T03:29:12.094854533Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.status.update_request\",\"source\":\"maestro-agent-59d9c485d9-7bvwb\",\"resourceid\":\"b9368296-3200-42ec-bfbb-f7d44a06c4e0\",\"sequenceid\":\"1765580430112722944\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"originalsource\":\"maestro\",\"resourceversion\":\"1\",\"data\":{\"conditions\":[{\"type\":\"Applied\",\"reason\":\"AppliedManifestWorkComplete\",\"status\":\"True\",\"message\":\"Apply manifest work complete\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"},{\"type\":\"Available\",\"reason\":\"ResourcesAvailable\",\"status\":\"True\",\"message\":\"All resources are available\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"}],\"resourceStatus\":[{\"conditions\":[{\"type\":\"Applied\",\"reason\":\"AppliedManifestComplete\",\"status\":\"True\",\"message\":\"Apply manifest complete\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"},{\"type\":\"Available\",\"reason\":\"ResourceAvailable\",\"status\":\"True\",\"message\":\"Resource is available\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"},{\"type\":\"StatusFeedbackSynced\",\"reason\":\"StatusFeedbackSynced\",\"status\":\"True\",\"message\":\"\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"}],\"resourceMeta\":{\"kind\":\"Deployment\",\"name\":\"nginx\",\"group\":\"apps\",\"ordinal\":1,\"version\":\"v1\",\"resource\":\"deployments\",\"namespace\":\"default\"},\"statusFeedback\":{\"values\":[{\"name\":\"status\",\"fieldValue\":{\"type\":\"JsonRaw\",\"jsonRaw\":\"{\\\"availableReplicas\\\":2,\\\"conditions\\\":[{\\\"lastTransitionTime\\\":\\\"2024-03-07T03:56:35Z\\\",\\\"lastUpdateTime\\\":\\\"2024-03-07T03:56:38Z\\\",\\\"message\\\":\\\"ReplicaSet \\\\\\\"nginx-5d6b548959\\\\\\\" has successfully progressed.\\\",\\\"reason\\\":\\\"NewReplicaSetAvailable\\\",\\\"status\\\":\\\"True\\\",\\\"type\\\":\\\"Progressing\\\"},{\\\"lastTransitionTime\\\":\\\"2024-03-07T03:58: 26Z\\\",\\\"lastUpdateTime\\\":\\\"2024-03-07T03:58:26Z\\\",\\\"message\\\":\\\"Deployment has minimum availability.\\\",\\\"reason\\\":\\\"MinimumReplicasAvailable\\\",\\\"status\\\":\\\"True\\\",\\\"type\\\":\\\"Available\\\"}],\\\"observedGeneration\\\":2,\\\"readyReplicas\\\":2,\\\"replicas\\\":2,\\\"updatedReplicas\\\":2}\"}}]}}]}}"), - expected: newJSONMap(t, "{\"ContentStatus\":{\"availableReplicas\":2,\"conditions\":[{\"lastTransitionTime\":\"2024-03-07T03:56:35Z\",\"lastUpdateTime\":\"2024-03-07T03:56:38Z\",\"message\":\"ReplicaSet \\\"nginx-5d6b548959\\\" has successfully progressed.\",\"reason\":\"NewReplicaSetAvailable\",\"status\":\"True\",\"type\":\"Progressing\"},{\"lastTransitionTime\":\"2024-03-07T03:58: 26Z\",\"lastUpdateTime\":\"2024-03-07T03:58:26Z\",\"message\":\"Deployment has minimum availability.\",\"reason\":\"MinimumReplicasAvailable\",\"status\":\"True\",\"type\":\"Available\"}],\"observedGeneration\":2,\"readyReplicas\":2,\"replicas\":2,\"updatedReplicas\":2},\"ReconcileStatus\":{\"Conditions\":[{\"lastTransitionTime\":\"2024-03-07T03:56:35Z\",\"message\":\"Apply manifest complete\",\"reason\":\"AppliedManifestComplete\",\"status\":\"True\",\"type\":\"Applied\"},{\"lastTransitionTime\":\"2024-03-07T03:56:35Z\",\"message\":\"Resource is available\",\"reason\":\"ResourceAvailable\",\"status\":\"True\",\"type\":\"Available\"},{\"lastTransitionTime\":\"2024-03-07T03:56:35Z\",\"message\":\"\",\"reason\":\"StatusFeedbackSynced\",\"status\":\"True\",\"type\":\"StatusFeedbackSynced\"}],\"ObservedVersion\":1,\"SequenceID\":\"1765580430112722944\"}}"), - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - gotManifest, err := DecodeStatus(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) - } - }) - } -} - -func newJSONMap(t *testing.T, data string) datatypes.JSONMap { - jsonmap := map[string]interface{}{} - if err := json.Unmarshal([]byte(data), &jsonmap); err != nil { - t.Fatal(err) - } - - return jsonmap -} - -func newJSONMAPList(t *testing.T, data ...string) []map[string]any { - jsonmapList := []map[string]any{} - for _, d := range data { - jsonmapList = append(jsonmapList, newJSONMap(t, d)) - } - - return jsonmapList -} diff --git a/pkg/client/cloudevents/codec.go b/pkg/client/cloudevents/codec.go index 51140704..75dd1117 100644 --- a/pkg/client/cloudevents/codec.go +++ b/pkg/client/cloudevents/codec.go @@ -20,6 +20,12 @@ type Codec struct { var _ cegeneric.Codec[*api.Resource] = &Codec{} +func NewCodec(sourceID string) *Codec { + return &Codec{ + sourceID: sourceID, + } +} + func (codec *Codec) EventDataType() cetypes.CloudEventsDataType { return workpayload.ManifestBundleEventDataType } diff --git a/pkg/client/cloudevents/source_client.go b/pkg/client/cloudevents/source_client.go index 3205fee7..6fa781c0 100644 --- a/pkg/client/cloudevents/source_client.go +++ b/pkg/client/cloudevents/source_client.go @@ -37,7 +37,7 @@ type SourceClientImpl struct { func NewSourceClient(sourceOptions *ceoptions.CloudEventsSourceOptions, resourceService services.ResourceService) (SourceClient, error) { ctx := context.Background() - codec := &Codec{sourceID: sourceOptions.SourceID} + codec := NewCodec(sourceOptions.SourceID) ceSourceClient, err := cegeneric.NewCloudEventSourceClient[*api.Resource](ctx, sourceOptions, resourceService, ResourceStatusHashGetter, codec) if err != nil { diff --git a/pkg/client/cloudevents/source_client_mock.go b/pkg/client/cloudevents/source_client_mock.go index d8566099..9d7030d2 100644 --- a/pkg/client/cloudevents/source_client_mock.go +++ b/pkg/client/cloudevents/source_client_mock.go @@ -2,13 +2,16 @@ package cloudevents import ( "context" - "encoding/json" "fmt" "github.com/bwmarrin/snowflake" + cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/openshift-online/maestro/pkg/api" "github.com/openshift-online/maestro/pkg/services" cegeneric "open-cluster-management.io/sdk-go/pkg/cloudevents/generic" + "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" + "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" + workpayload "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -27,6 +30,7 @@ func init() { // SourceClientMock is a mock implementation of the SourceClient interface type SourceClientMock struct { + agent string resources api.ResourceList ResourceService services.ResourceService } @@ -35,6 +39,7 @@ var _ SourceClient = &SourceClientMock{} func NewSourceClientMock(resourceService services.ResourceService) SourceClient { return &SourceClientMock{ + agent: "mock-agent", ResourceService: resourceService, } } @@ -45,29 +50,38 @@ func (s *SourceClientMock) OnCreate(ctx context.Context, id string) error { return fmt.Errorf("failed to get resource: %v", serviceErr) } - resourceStatus := &api.ResourceStatus{ - ReconcileStatus: &api.ReconcileStatus{ - ObservedVersion: resource.Version, - SequenceID: sequenceGenerator.Generate().String(), - Conditions: []metav1.Condition{ - { - Type: "Applied", - Status: "True", - LastTransitionTime: metav1.Now(), - }, + eventType := types.CloudEventsType{ + CloudEventsDataType: workpayload.ManifestBundleEventDataType, + SubResource: types.SubResourceStatus, + Action: types.EventAction("update_request"), + } + evt := types.NewEventBuilder(s.agent, eventType). + WithResourceID(resource.ID). + WithStatusUpdateSequenceID(sequenceGenerator.Generate().String()). + WithResourceVersion(int64(resource.Version)). + WithClusterName(resource.ConsumerName). + WithOriginalSource("maestro"). + NewEvent() + + manifestBundleStatus := &payload.ManifestBundleStatus{ + Conditions: []metav1.Condition{ + { + Type: "Applied", + Status: "True", + LastTransitionTime: metav1.Now(), }, }, } - - resourceStatusJSON, err := json.Marshal(resourceStatus) - if err != nil { - return fmt.Errorf("failed to marshal resource status: %v", err) + if err := evt.SetData(cloudevents.ApplicationJSON, manifestBundleStatus); err != nil { + return fmt.Errorf("failed to encode manifestwork status to a cloudevent: %v", err) } - err = json.Unmarshal(resourceStatusJSON, &resource.Status) + + status, err := api.CloudEventToJSONMap(&evt) if err != nil { - return fmt.Errorf("failed to unmarshal resource status: %v", err) + return fmt.Errorf("failed to convert resource status cloudevent to jsonmap: %v", err) } + resource.Status = status newResource, _, serviceErr := s.ResourceService.UpdateStatus(ctx, resource) if serviceErr != nil { return fmt.Errorf("failed to update resource status: %v", serviceErr) @@ -87,42 +101,42 @@ func (s *SourceClientMock) OnUpdate(ctx context.Context, id string) error { found := false for i, r := range s.resources { if r.ID == resource.ID { - resourceStatusJSON, err := json.Marshal(resource.Status) + evt, err := api.JSONMAPToCloudEvent(resource.Status) if err != nil { - return fmt.Errorf("failed to marshal resource status: %v", err) + return fmt.Errorf("failed to convert resource status to cloudevent: %v", err) } - resourceStatus := &api.ResourceStatus{} - if err := json.Unmarshal(resourceStatusJSON, resourceStatus); err != nil { - return fmt.Errorf("failed to unmarshal resource status: %v", err) - } - if resourceStatus.ReconcileStatus == nil { - resourceStatus.ReconcileStatus = &api.ReconcileStatus{} + + manifestBundleStatus := &workpayload.ManifestBundleStatus{} + if err := evt.DataAs(manifestBundleStatus); err != nil { + return fmt.Errorf("failed to decode cloudevent payload as resource status: %v", err) } - resourceStatus.ReconcileStatus.ObservedVersion = resource.Version - resourceStatus.ReconcileStatus.SequenceID = sequenceGenerator.Generate().String() + condition := metav1.Condition{ Type: "Updated", Status: "True", LastTransitionTime: metav1.Now(), } - if len(resourceStatus.ReconcileStatus.Conditions) == 0 { - resourceStatus.ReconcileStatus.Conditions = []metav1.Condition{condition} + if len(manifestBundleStatus.Conditions) == 0 { + manifestBundleStatus.Conditions = []metav1.Condition{condition} + } else { + manifestBundleStatus.Conditions = append(manifestBundleStatus.Conditions, condition) } - resourceStatus.ReconcileStatus.Conditions = append(resourceStatus.ReconcileStatus.Conditions, condition) - resourceStatusJSON, err = json.Marshal(resourceStatus) - if err != nil { - return fmt.Errorf("failed to marshal resource status: %v", err) + if err := evt.SetData(cloudevents.ApplicationJSON, manifestBundleStatus); err != nil { + return fmt.Errorf("failed to encode manifestwork status to a cloudevent: %v", err) } - err = json.Unmarshal(resourceStatusJSON, &resource.Status) + + status, err := api.CloudEventToJSONMap(evt) if err != nil { - return fmt.Errorf("failed to unmarshal resource status: %v", err) + return fmt.Errorf("failed to convert resource status cloudevent to jsonmap: %v", err) } + resource.Status = status newResource, _, serviceErr := s.ResourceService.UpdateStatus(ctx, resource) if serviceErr != nil { return fmt.Errorf("failed to update resource status: %v", serviceErr) } + s.resources[i] = newResource found = true break diff --git a/pkg/handlers/resource.go b/pkg/handlers/resource.go deleted file mode 100755 index 2e5735e3..00000000 --- a/pkg/handlers/resource.go +++ /dev/null @@ -1,265 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - - "github.com/openshift-online/maestro/pkg/api" - "github.com/openshift-online/maestro/pkg/api/openapi" - "github.com/openshift-online/maestro/pkg/api/presenters" - "github.com/openshift-online/maestro/pkg/errors" - "github.com/openshift-online/maestro/pkg/services" -) - -var _ RestHandler = resourceHandler{} - -type resourceHandler struct { - resource services.ResourceService - generic services.GenericService -} - -func NewResourceHandler(resource services.ResourceService, generic services.GenericService) *resourceHandler { - return &resourceHandler{ - resource: resource, - generic: generic, - } -} - -func (h resourceHandler) Create(w http.ResponseWriter, r *http.Request) { - var rs openapi.Resource - cfg := &handlerConfig{ - &rs, - []validate{ - validateEmpty(&rs, "Id", "id"), - validateNotEmpty(&rs, "ConsumerName", "consumer_name"), - validateNotEmpty(&rs, "Manifest", "manifest"), - validateNotEmpty(&rs, "GroupResource", "group_resource"), - validateDeleteOptionAndUpdateStrategy(&rs), - }, - func() (interface{}, *errors.ServiceError) { - ctx := r.Context() - resource, err := presenters.ConvertResource(rs) - if err != nil { - return nil, errors.GeneralError("failed to convert resource: %s", err) - } - resource, serviceErr := h.resource.Create(ctx, resource) - if serviceErr != nil { - return nil, serviceErr - } - res, err := presenters.PresentResource(resource) - if err != nil { - return nil, errors.GeneralError("failed to present resource: %s", err) - } - return res, nil - }, - handleError, - } - - handle(w, r, cfg, http.StatusCreated) -} - -func (h resourceHandler) Patch(w http.ResponseWriter, r *http.Request) { - var patch openapi.ResourcePatchRequest - - cfg := &handlerConfig{ - &patch, - []validate{ - validateNotEmpty(&patch, "Version", "version"), - validateNotEmpty(&patch, "Manifest", "manifest"), - }, - func() (interface{}, *errors.ServiceError) { - ctx := r.Context() - id := mux.Vars(r)["id"] - found, serviceErr := h.resource.Get(ctx, id) - if serviceErr != nil { - return nil, serviceErr - } - _, groupResource, deleteOption, updateStrategy, err := api.DecodeManifest(found.Payload) - if err != nil { - return nil, errors.GeneralError("failed to decode existing manifest: %s", err) - } - if patch.DeleteOption != nil { - deleteOption = patch.DeleteOption - } - if patch.UpdateStrategy != nil { - updateStrategy = patch.UpdateStrategy - } - payload, err := presenters.ConvertResourceManifest(patch.Manifest, groupResource, deleteOption, updateStrategy) - if err != nil { - return nil, errors.GeneralError("failed to convert resource manifest: %s", err) - } - resource, serviceErr := h.resource.Update(ctx, &api.Resource{ - Meta: api.Meta{ID: id}, - Version: *patch.Version, - Type: api.ResourceTypeSingle, - Payload: payload, - }) - if serviceErr != nil { - return nil, serviceErr - } - res, err := presenters.PresentResource(resource) - if err != nil { - return nil, errors.GeneralError("failed to present resource: %s", err) - } - return res, nil - }, - handleError, - } - - handle(w, r, cfg, http.StatusOK) -} - -func (h resourceHandler) List(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ - Action: func() (interface{}, *errors.ServiceError) { - ctx := r.Context() - - listArgs := services.NewListArguments(r.URL.Query()) - if listArgs.Search == "" { - listArgs.Search = fmt.Sprintf("type='%s'", api.ResourceTypeSingle) - } else { - listArgs.Search = fmt.Sprintf("%s and type='%s'", listArgs.Search, api.ResourceTypeSingle) - } - var resources []api.Resource - paging, serviceErr := h.generic.List(ctx, "username", listArgs, &resources) - if serviceErr != nil { - return nil, serviceErr - } - resourceList := openapi.ResourceList{ - Kind: *presenters.ObjectKind(resources), - Page: int32(paging.Page), - Size: int32(paging.Size), - Total: int32(paging.Total), - Items: []openapi.Resource{}, - } - - for _, resource := range resources { - converted, err := presenters.PresentResource(&resource) - if err != nil { - return nil, errors.GeneralError("failed to present resource: %s", err) - } - resourceList.Items = append(resourceList.Items, *converted) - } - if listArgs.Fields != nil { - filteredItems, err := presenters.SliceFilter(listArgs.Fields, resourceList.Items) - if err != nil { - return nil, err - } - return filteredItems, nil - } - return resourceList, nil - }, - } - - handleList(w, r, cfg) -} - -func (h resourceHandler) Get(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ - Action: func() (interface{}, *errors.ServiceError) { - id := mux.Vars(r)["id"] - ctx := r.Context() - resource, serviceErr := h.resource.Get(ctx, id) - if serviceErr != nil { - return nil, serviceErr - } - - res, err := presenters.PresentResource(resource) - if err != nil { - return nil, errors.GeneralError("failed to present resource: %s", err) - } - return res, nil - }, - } - - handleGet(w, r, cfg) -} - -// Resource Deletion Flow: -// 1. User requests deletion -// 2. Maestro marks resource as deleting, adds delete event to DB -// 3. Maestro handles delete event and sends CloudEvent to work-agent -// 4. Work-agent deletes resource, sends CloudEvent back to Maestro -// 5. Maestro deletes resource from DB -func (h resourceHandler) Delete(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ - Action: func() (interface{}, *errors.ServiceError) { - id := mux.Vars(r)["id"] - ctx := r.Context() - err := h.resource.MarkAsDeleting(ctx, id) - if err != nil { - return nil, err - } - return nil, nil - }, - } - handleDelete(w, r, cfg, http.StatusNoContent) -} - -func (h resourceHandler) GetBundle(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ - Action: func() (interface{}, *errors.ServiceError) { - id := mux.Vars(r)["id"] - ctx := r.Context() - resource, serviceErr := h.resource.Get(ctx, id) - if serviceErr != nil { - return nil, serviceErr - } - - resBundle, err := presenters.PresentResourceBundle(resource) - if err != nil { - return nil, errors.GeneralError("failed to present resource bundle: %s", err) - } - return resBundle, nil - }, - } - - handleGet(w, r, cfg) -} - -func (h resourceHandler) ListBundle(w http.ResponseWriter, r *http.Request) { - cfg := &handlerConfig{ - Action: func() (interface{}, *errors.ServiceError) { - ctx := r.Context() - - listArgs := services.NewListArguments(r.URL.Query()) - if listArgs.Search == "" { - listArgs.Search = fmt.Sprintf("type='%s'", api.ResourceTypeBundle) - } else { - listArgs.Search = fmt.Sprintf("%s and type='%s'", listArgs.Search, api.ResourceTypeBundle) - } - var resources []api.Resource - paging, serviceErr := h.resource.ListWithArgs(ctx, "username", listArgs, &resources) - if serviceErr != nil { - return nil, serviceErr - } - resourceBundleList := openapi.ResourceBundleList{ - Kind: "ResourceBundleList", - Page: int32(paging.Page), - Size: int32(paging.Size), - Total: int32(paging.Total), - Items: []openapi.ResourceBundle{}, - } - - for _, resource := range resources { - converted, err := presenters.PresentResourceBundle(&resource) - if err != nil { - return nil, errors.GeneralError("failed to present resource: %s", err) - } - resourceBundleList.Items = append(resourceBundleList.Items, *converted) - } - if listArgs.Fields != nil { - filteredItems, err := presenters.SliceFilter(listArgs.Fields, resourceBundleList.Items) - if err != nil { - return nil, err - } - return filteredItems, nil - } - return resourceBundleList, nil - }, - } - - handleList(w, r, cfg) -} diff --git a/pkg/handlers/resource_bundle.go b/pkg/handlers/resource_bundle.go new file mode 100644 index 00000000..57f98443 --- /dev/null +++ b/pkg/handlers/resource_bundle.go @@ -0,0 +1,119 @@ +package handlers + +import ( + "net/http" + + "github.com/gorilla/mux" + + "github.com/openshift-online/maestro/pkg/api" + "github.com/openshift-online/maestro/pkg/api/openapi" + "github.com/openshift-online/maestro/pkg/api/presenters" + "github.com/openshift-online/maestro/pkg/errors" + "github.com/openshift-online/maestro/pkg/services" +) + +var _ RestHandler = resourceBundleHandler{} + +type resourceBundleHandler struct { + resource services.ResourceService + generic services.GenericService +} + +func NewResourceBundleHandler(resource services.ResourceService, generic services.GenericService) *resourceBundleHandler { + return &resourceBundleHandler{ + resource: resource, + generic: generic, + } +} + +func (h resourceBundleHandler) Create(w http.ResponseWriter, r *http.Request) { + // not implemented + http.Error(w, "Not Implemented Yet", http.StatusNotImplemented) +} + +func (h resourceBundleHandler) Patch(w http.ResponseWriter, r *http.Request) { + // not implemented + http.Error(w, "Not Implemented Yet", http.StatusNotImplemented) +} + +func (h resourceBundleHandler) Get(w http.ResponseWriter, r *http.Request) { + cfg := &handlerConfig{ + Action: func() (interface{}, *errors.ServiceError) { + id := mux.Vars(r)["id"] + ctx := r.Context() + resource, serviceErr := h.resource.Get(ctx, id) + if serviceErr != nil { + return nil, serviceErr + } + + rb, err := presenters.PresentResourceBundle(resource) + if err != nil { + return nil, errors.GeneralError("failed to present resource bundle: %s", err) + } + return rb, nil + }, + } + + handleGet(w, r, cfg) +} + +func (h resourceBundleHandler) List(w http.ResponseWriter, r *http.Request) { + cfg := &handlerConfig{ + Action: func() (interface{}, *errors.ServiceError) { + ctx := r.Context() + + listArgs := services.NewListArguments(r.URL.Query()) + var resources []api.Resource + paging, serviceErr := h.resource.ListWithArgs(ctx, "username", listArgs, &resources) + if serviceErr != nil { + return nil, serviceErr + } + resourceBundleList := openapi.ResourceBundleList{ + Kind: *presenters.ObjectKind(resources), + Page: int32(paging.Page), + Size: int32(paging.Size), + Total: int32(paging.Total), + Items: []openapi.ResourceBundle{}, + } + + for _, resource := range resources { + converted, err := presenters.PresentResourceBundle(&resource) + if err != nil { + return nil, errors.GeneralError("failed to present resource bundle: %s", err) + } + resourceBundleList.Items = append(resourceBundleList.Items, *converted) + } + if listArgs.Fields != nil { + filteredItems, err := presenters.SliceFilter(listArgs.Fields, resourceBundleList.Items) + if err != nil { + return nil, err + } + return filteredItems, nil + } + return resourceBundleList, nil + }, + } + + handleList(w, r, cfg) +} + +// Resource Bundle Deletion Flow: +// 1. User requests deletion +// 2. Maestro marks resource bundle as deleting, adds delete event to DB +// 3. Maestro handles delete event and sends CloudEvent to work-agent +// 4. Work-agent deletes resource bundle, sends CloudEvent back to Maestro +// 5. Maestro deletes resource bundle from DB +func (h resourceBundleHandler) Delete(w http.ResponseWriter, r *http.Request) { + cfg := &handlerConfig{ + Action: func() (interface{}, *errors.ServiceError) { + id := mux.Vars(r)["id"] + ctx := r.Context() + err := h.resource.MarkAsDeleting(ctx, id) + if err != nil { + return nil, err + } + return nil, nil + }, + } + handleDelete(w, r, cfg, http.StatusNoContent) +} diff --git a/pkg/handlers/validation.go b/pkg/handlers/validation.go index 4367c525..48f6c6d6 100755 --- a/pkg/handlers/validation.go +++ b/pkg/handlers/validation.go @@ -3,7 +3,6 @@ package handlers import ( "reflect" - "github.com/openshift-online/maestro/pkg/api/openapi" "github.com/openshift-online/maestro/pkg/errors" ) @@ -38,24 +37,3 @@ func validateEmpty(i interface{}, fieldName string, field string) validate { return nil } } - -// validateDeleteOptionAndUpdateStrategy validates the delete option and update strategy -// for a resource, to ensure that update strategy ReadOnly is only allowed with delete option Orphan. -func validateDeleteOptionAndUpdateStrategy(rs *openapi.Resource) validate { - return func() *errors.ServiceError { - if rs.DeleteOption != nil && rs.UpdateStrategy != nil { - deleteType, ok := rs.DeleteOption["propagationPolicy"].(string) - if !ok { - return errors.Validation("invalid delete option") - } - updateStrategy, ok := rs.UpdateStrategy["type"].(string) - if !ok { - return errors.Validation("invalid update strategy") - } - if deleteType != "Orphan" && updateStrategy == "ReadOnly" { - return errors.Validation("update strategy ReadOnly is only allowed with delete option Orphan") - } - } - return nil - } -} diff --git a/pkg/services/resource.go b/pkg/services/resource.go index 40e2ee8e..5e63234c 100755 --- a/pkg/services/resource.go +++ b/pkg/services/resource.go @@ -75,8 +75,8 @@ func (s *sqlResourceService) Create(ctx context.Context, resource *api.Resource) return nil, errors.Validation("the name in the resource is invalid, %v", err) } } - if err := ValidateManifest(resource.Type, resource.Payload); err != nil { - return nil, errors.Validation("the manifest in the resource is invalid, %v", err) + if err := ValidateManifestBundle(resource.Payload); err != nil { + return nil, errors.Validation("the manifest bundle in the resource is invalid, %v", err) } resource, err := s.resourceDao.Create(ctx, resource) @@ -126,8 +126,8 @@ func (s *sqlResourceService) Update(ctx context.Context, resource *api.Resource) return found, nil } - if err := ValidateManifestUpdate(resource.Type, resource.Payload, found.Payload); err != nil { - return nil, errors.Validation("the new manifest in the resource is invalid, %v", err) + if err := ValidateManifestBundleUpdate(resource.Payload, found.Payload); err != nil { + return nil, errors.Validation("the new manifest bundle in the resource is invalid, %v", err) } // Increase the current resource version and update its manifest. diff --git a/pkg/services/resource_test.go b/pkg/services/resource_test.go index 5ca29375..7731d091 100755 --- a/pkg/services/resource_test.go +++ b/pkg/services/resource_test.go @@ -28,13 +28,12 @@ func TestResourceFindByConsumerID(t *testing.T) { resourceService := NewResourceService(dbmocks.NewMockAdvisoryLockFactory(), resourceDAO, events, nil) resources := api.ResourceList{ - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"test\",\"group\":\"\",\"resource\":\"configmaps\",\"namespace\":\"test\"}}]}}")}, - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeBundle, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"test\",\"group\":\"\",\"resource\":\"configmaps\",\"namespace\":\"test\"}}]}}")}, - &api.Resource{ConsumerName: Seismosaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"test\",\"group\":\"\",\"resource\":\"configmaps\",\"namespace\":\"test\"}}]}}")}, - &api.Resource{ConsumerName: Seismosaurus, Type: api.ResourceTypeBundle, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"e3eb7db1-b124-4a4d-8bb6-cc779c01b402\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, - &api.Resource{ConsumerName: Breviceratops, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"test\",\"group\":\"\",\"resource\":\"configmaps\",\"namespace\":\"test\"}}]}}")}, - &api.Resource{ConsumerName: Breviceratops, Type: api.ResourceTypeBundle, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Fukuisaurus, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Fukuisaurus, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Fukuisaurus, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Seismosaurus, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"e3eb7db1-b124-4a4d-8bb6-cc779c01b402\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Seismosaurus, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"e3eb7db1-b124-4a4d-8bb6-cc779c01b402\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Breviceratops, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, } for _, resource := range resources { _, err := resourceService.Create(context.Background(), resource) @@ -50,7 +49,7 @@ func TestResourceFindByConsumerID(t *testing.T) { breviceratops, err := resourceDAO.FindByConsumerName(context.Background(), Breviceratops) gm.Expect(err).To(gm.BeNil()) - gm.Expect(len(breviceratops)).To(gm.Equal(2)) + gm.Expect(len(breviceratops)).To(gm.Equal(1)) } func TestCreateInvalidResource(t *testing.T) { @@ -78,32 +77,32 @@ func TestResourceList(t *testing.T) { resourceService := NewResourceService(dbmocks.NewMockAdvisoryLockFactory(), resourceDAO, events, nil) resources := api.ResourceList{ - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"test\",\"group\":\"\",\"resource\":\"configmaps\",\"namespace\":\"test\"}}]}}")}, - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"test\",\"group\":\"\",\"resource\":\"configmaps\",\"namespace\":\"test\"}}]}}")}, - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeBundle, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, - &api.Resource{ConsumerName: Seismosaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"test\",\"group\":\"\",\"resource\":\"configmaps\",\"namespace\":\"test\"}}]}}")}, + &api.Resource{ConsumerName: Fukuisaurus, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Fukuisaurus, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Seismosaurus, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, } for _, resource := range resources { _, err := resourceService.Create(context.Background(), resource) gm.Expect(err).To(gm.BeNil()) } - resoruces, err := resourceService.List(types.ListOptions{ - ClusterName: Fukuisaurus, + resources, err := resourceService.List(types.ListOptions{ + ClusterName: Fukuisaurus, + CloudEventsDataType: payload.ManifestEventDataType, }) gm.Expect(err).To(gm.BeNil()) - gm.Expect(len(resoruces)).To(gm.Equal(3)) + gm.Expect(len(resources)).To(gm.Equal(2)) - resoruces, err = resourceService.List(types.ListOptions{ + resources, err = resourceService.List(types.ListOptions{ ClusterName: Seismosaurus, }) gm.Expect(err).To(gm.BeNil()) - gm.Expect(len(resoruces)).To(gm.Equal(1)) + gm.Expect(len(resources)).To(gm.Equal(1)) - resoruces, err = resourceService.List(types.ListOptions{ + resources, err = resourceService.List(types.ListOptions{ ClusterName: Seismosaurus, - CloudEventsDataType: payload.ManifestEventDataType, + CloudEventsDataType: payload.ManifestBundleEventDataType, }) gm.Expect(err).To(gm.BeNil()) - gm.Expect(len(resoruces)).To(gm.Equal(1)) + gm.Expect(len(resources)).To(gm.Equal(1)) } diff --git a/pkg/services/validation.go b/pkg/services/validation.go index cb6e4882..b2aed970 100644 --- a/pkg/services/validation.go +++ b/pkg/services/validation.go @@ -40,30 +40,18 @@ func ValidateConsumer(consumer *api.Consumer) error { return fmt.Errorf(errs.ToAggregate().Error()) } -func ValidateManifest(resType api.ResourceType, manifest datatypes.JSONMap) error { - switch resType { - case api.ResourceTypeSingle: - // TODO: validate the deleteOption and updateStrategy - obj, _, _, _, err := api.DecodeManifest(manifest) - if err != nil { - return fmt.Errorf("failed to decode manifest: %v", err) - } - return ValidateObject(obj) - case api.ResourceTypeBundle: - manifestBundle, err := api.DecodeManifestBundle(manifest) - if err != nil { - return fmt.Errorf("failed to decode manifest bundle: %v", err) - } - if manifestBundle == nil { - return fmt.Errorf("manifest bundle is empty") - } - for _, obj := range manifestBundle.Manifests { - if err := ValidateObject(obj); err != nil { - return err - } +func ValidateManifestBundle(manifestBundle datatypes.JSONMap) error { + manifestBundleWrapper, err := api.DecodeManifestBundle(manifestBundle) + if err != nil { + return fmt.Errorf("failed to decode manifest bundle: %v", err) + } + if manifestBundleWrapper == nil { + return fmt.Errorf("manifest bundle is empty") + } + for _, obj := range manifestBundleWrapper.Manifests { + if err := ValidateObject(obj); err != nil { + return err } - default: - return fmt.Errorf("unknown resource type: %s", resType) } return nil @@ -99,43 +87,28 @@ func ValidateObject(obj datatypes.JSONMap) error { return fmt.Errorf(errs.ToAggregate().Error()) } -func ValidateManifestUpdate(resType api.ResourceType, new, old datatypes.JSONMap) error { - switch resType { - case api.ResourceTypeSingle: - newObj, _, _, _, err := api.DecodeManifest(new) - if err != nil { - return fmt.Errorf("failed to decode new manifest: %v", err) - } - oldObj, _, _, _, err := api.DecodeManifest(old) - if err != nil { - return fmt.Errorf("failed to decode old manifest: %v", err) - } - return ValidateObjectUpdate(newObj, oldObj) - case api.ResourceTypeBundle: - newManifestBundle, err := api.DecodeManifestBundle(new) - if err != nil { - return fmt.Errorf("failed to decode new manifest bundle: %v", err) - } - if newManifestBundle == nil { - return fmt.Errorf("new manifest bundle is empty") - } - oldManifestBundle, err := api.DecodeManifestBundle(old) - if err != nil { - return fmt.Errorf("failed to decode old manifest bundle: %v", err) - } - if oldManifestBundle == nil { - return fmt.Errorf("old manifest bundle is empty") - } - if len(newManifestBundle.Manifests) != len(oldManifestBundle.Manifests) { - return fmt.Errorf("new and old manifest bundles have different number of objects") - } - for i := range newManifestBundle.Manifests { - if err := ValidateObjectUpdate(newManifestBundle.Manifests[i], oldManifestBundle.Manifests[i]); err != nil { - return err - } +func ValidateManifestBundleUpdate(new, old datatypes.JSONMap) error { + newManifestBundleWrapper, err := api.DecodeManifestBundle(new) + if err != nil { + return fmt.Errorf("failed to decode new manifest bundle: %v", err) + } + if newManifestBundleWrapper == nil { + return fmt.Errorf("new manifest bundle is empty") + } + oldManifestBundleWrapper, err := api.DecodeManifestBundle(old) + if err != nil { + return fmt.Errorf("failed to decode old manifest bundle: %v", err) + } + if oldManifestBundleWrapper == nil { + return fmt.Errorf("old manifest bundle is empty") + } + if len(newManifestBundleWrapper.Manifests) != len(oldManifestBundleWrapper.Manifests) { + return fmt.Errorf("new and old manifest have different number of objects") + } + for i := range newManifestBundleWrapper.Manifests { + if err := ValidateObjectUpdate(newManifestBundleWrapper.Manifests[i], oldManifestBundleWrapper.Manifests[i]); err != nil { + return err } - default: - return fmt.Errorf("unknown resource type: %s", resType) } return nil diff --git a/pkg/services/validation_test.go b/pkg/services/validation_test.go index 2624cd4d..e9da2e3e 100644 --- a/pkg/services/validation_test.go +++ b/pkg/services/validation_test.go @@ -86,46 +86,26 @@ func TestValidateResourceName(t *testing.T) { } } -func TestValidateNewManifest(t *testing.T) { +func TestValidateManifestBundle(t *testing.T) { cases := []struct { name string - resType api.ResourceType manifest datatypes.JSONMap expectedErrorMsg string }{ { - name: "validated single manifest", - resType: api.ResourceTypeSingle, - manifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - }, - { - name: "validated bundle manifest", - resType: api.ResourceTypeBundle, + name: "validated manifest bundle", manifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), }, { - name: "invalidated single manifest", - resType: api.ResourceTypeSingle, - manifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - expectedErrorMsg: "failed to decode manifest: invalid number of manifests in the event payload: 2", - }, - { - name: "invalidated bundle manifest", - resType: api.ResourceTypeBundle, + name: "invalidated manifest bundle", manifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - expectedErrorMsg: "manifest bundle is empty", - }, - { - name: "invalidated resource type", - resType: "invalid", - manifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - expectedErrorMsg: "unknown resource type: invalid", + expectedErrorMsg: "manifest is empty", }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - err := ValidateManifest(c.resType, c.manifest) + err := ValidateManifestBundle(c.manifest) if err != nil && err.Error() != c.expectedErrorMsg { t.Errorf("expected %#v but got: %#v", c.expectedErrorMsg, err) } @@ -205,52 +185,29 @@ func TestValidateNewObject(t *testing.T) { } } -func TestValidateUpdateManifest(t *testing.T) { +func TestValidateUpdateManifestBundle(t *testing.T) { cases := []struct { name string - resType api.ResourceType newPayload datatypes.JSONMap oldManifest datatypes.JSONMap expectedErrorMsg string }{ { - name: "validated single manifest", - resType: api.ResourceTypeSingle, - newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - }, - { - name: "validated bundle manifest", - resType: api.ResourceTypeBundle, + name: "validated manifest", newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), }, { - name: "invalidated single manifest", - resType: api.ResourceTypeSingle, - newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - expectedErrorMsg: "failed to decode old manifest: invalid number of manifests in the event payload: 0", - }, - { - name: "invalidated bundle manifest", - resType: api.ResourceTypeBundle, - newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - expectedErrorMsg: "new or old manifest bundle is empty", - }, - { - name: "invalidated resource type", - resType: "invalid", + name: "invalidated manifest", newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - expectedErrorMsg: "unknown resource type: invalid", + expectedErrorMsg: "new or old manifest is empty", }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - err := ValidateManifestUpdate(c.resType, c.newPayload, c.oldManifest) + err := ValidateManifestBundleUpdate(c.newPayload, c.oldManifest) if err != nil && err.Error() != c.expectedErrorMsg { t.Errorf("expected %#v but got: %#v", c.expectedErrorMsg, err) } diff --git a/test/e2e/pkg/consumer_test.go b/test/e2e/pkg/consumer_test.go index 536184b8..46f1214a 100644 --- a/test/e2e/pkg/consumer_test.go +++ b/test/e2e/pkg/consumer_test.go @@ -15,7 +15,6 @@ var _ = Describe("Consumers", Ordered, Label("e2e-tests-consumers"), func() { Context("Consumer CRUD Tests", func() { consumerA := openapi.Consumer{Name: openapi.PtrString(fmt.Sprintf("consumer-a-%s", rand.String(5)))} consumerB := openapi.Consumer{Name: openapi.PtrString(fmt.Sprintf("consumer-b-%s", rand.String(5)))} - resource := helper.NewAPIResource(*consumerB.Name, fmt.Sprintf("nginx-%s", rand.String(5)), 1) AfterAll(func() { // delete the consumer @@ -27,24 +26,18 @@ var _ = Describe("Consumers", Ordered, Label("e2e-tests-consumers"), func() { Expect(err.Error()).To(ContainSubstring("Not Found")) Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) - // delete the consumer associated with resource + // delete the consumer resp, err = apiClient.DefaultApi.ApiMaestroV1ConsumersIdDelete(ctx, *consumerB.Id).Execute() - Expect(err).To(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusForbidden)) // 403 forbid deletion - - // delete the resource on the consumer - resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resource.Id).Execute() - Expect(err).To(Succeed()) + Expect(err).NotTo(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) - // only if permanently delete the resource, the consumer can be deleted - resp, err = apiClient.DefaultApi.ApiMaestroV1ConsumersIdDelete(ctx, *consumerB.Id).Execute() - Expect(err).To(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusForbidden)) // 403 forbid deletion + _, resp, err = apiClient.DefaultApi.ApiMaestroV1ConsumersIdGet(ctx, *consumerB.Id).Execute() + Expect(err.Error()).To(ContainSubstring("Not Found")) + Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) }) It("create consumer", func() { - // create a consumer without resource + // create a consumer created, resp, err := apiClient.DefaultApi.ApiMaestroV1ConsumersPost(ctx).Consumer(consumerA).Execute() Expect(err).To(Succeed()) Expect(resp.StatusCode).To(Equal(http.StatusCreated)) @@ -56,36 +49,37 @@ var _ = Describe("Consumers", Ordered, Label("e2e-tests-consumers"), func() { Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(got).NotTo(BeNil()) - // create a consumer with resource + // create a consumer created, resp, err = apiClient.DefaultApi.ApiMaestroV1ConsumersPost(ctx).Consumer(consumerB).Execute() Expect(err).To(Succeed()) Expect(resp.StatusCode).To(Equal(http.StatusCreated)) Expect(*created.Id).NotTo(BeEmpty()) consumerB = *created - res, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(resource).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*res.Id).ShouldNot(BeEmpty()) - Expect(*res.Version).To(Equal(int32(1))) - resource = *res + got, resp, err = apiClient.DefaultApi.ApiMaestroV1ConsumersIdGet(ctx, *consumerB.Id).Execute() + Expect(err).To(Succeed()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + Expect(got).NotTo(BeNil()) }) - It("list consumer", func() { + It("list consumers", func() { consumerList, resp, err := apiClient.DefaultApi.ApiMaestroV1ConsumersGet(ctx).Execute() Expect(err).To(Succeed()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(consumerList).NotTo(BeNil()) - Expect(len(consumerList.Items) > 0).To(BeTrue()) - fmt.Printf("consumer list: %v\n", consumerList.Items) + Expect(len(consumerList.Items) >= 2).To(BeTrue()) - got := false + gotA, gotB := false, false for _, c := range consumerList.Items { if *c.Name == *consumerA.Name { - got = true + gotA = true + } + if *c.Name == *consumerB.Name { + gotB = true } } - Expect(got).To(BeTrue()) + Expect(gotA).To(BeTrue()) + Expect(gotB).To(BeTrue()) }) It("patch consumer", func() { diff --git a/test/e2e/pkg/grpc_test.go b/test/e2e/pkg/grpc_test.go index faa1e80a..4effd39e 100644 --- a/test/e2e/pkg/grpc_test.go +++ b/test/e2e/pkg/grpc_test.go @@ -26,240 +26,14 @@ import ( ) var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { - Context("GRPC Manifest Tests", func() { - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - resourceID := uuid.NewString() - resourceStatus := &api.ResourceStatus{ - ReconcileStatus: &api.ReconcileStatus{}, - } - - It("subscribe to resource status with grpc client", func() { - go func() { - subClient, err := grpcClient.Subscribe(ctx, &pbv1.SubscriptionRequest{Source: sourceID}) - if err != nil { - return - } - - for { - pvEvt, err := subClient.Recv() - if err == io.EOF { - return - } - if err != nil { - return - } - evt, err := binding.ToEvent(ctx, grpcprotocol.NewMessage(pvEvt)) - if err != nil { - continue - } - - evtExtensions := evt.Context.GetExtensions() - resID, err := cetypes.ToString(evtExtensions[types.ExtensionResourceID]) - if err != nil { - continue - } - - if resID != resourceID { - continue - } - - resourceVersion, err := cetypes.ToInteger(evtExtensions[types.ExtensionResourceVersion]) - if err != nil { - continue - } - resourceStatus.ReconcileStatus.ObservedVersion = resourceVersion - - manifestBundleStatus := &payload.ManifestBundleStatus{} - if err := evt.DataAs(manifestBundleStatus); err != nil { - continue - } - - if len(manifestBundleStatus.ResourceStatus) != 1 { - return - } - resourceStatus.ReconcileStatus.Conditions = manifestBundleStatus.ResourceStatus[0].Conditions - if meta.IsStatusConditionTrue(manifestBundleStatus.Conditions, common.ManifestsDeleted) { - deletedCondition := meta.FindStatusCondition(manifestBundleStatus.Conditions, common.ManifestsDeleted) - resourceStatus.ReconcileStatus.Conditions = append(resourceStatus.ReconcileStatus.Conditions, *deletedCondition) - } - for _, value := range manifestBundleStatus.ResourceStatus[0].StatusFeedbacks.Values { - if value.Name == "status" { - contentStatus := make(map[string]interface{}) - if err := json.Unmarshal([]byte(*value.Value.JsonRaw), &contentStatus); err != nil { - continue - } - resourceStatus.ContentStatus = contentStatus - } - } - } - }() - }) - - It("publish a resource spec using grpc client", func() { - evt := helper.NewEvent(sourceID, "create_request", agentTestOpts.consumerName, resourceID, deployName, 1, 1) - pbEvt := &pbv1.CloudEvent{} - err := grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) - Expect(err).To(BeNil(), "failed to convert spec from cloudevent to protobuf") - _, err = grpcClient.Publish(ctx, &pbv1.PublishRequest{Event: pbEvt}) - Expect(err).ShouldNot(HaveOccurred()) - }) - - It("Subscribe to the resource status using grpc client", func() { - Eventually(func() error { - if resourceStatus.ReconcileStatus == nil { - return fmt.Errorf("reconcile status is empty") - } - - if !meta.IsStatusConditionTrue(resourceStatus.ReconcileStatus.Conditions, "Applied") { - return fmt.Errorf("resource not applied") - } - - if !meta.IsStatusConditionTrue(resourceStatus.ReconcileStatus.Conditions, "Available") { - return fmt.Errorf("resource not Available") - } - - replicas, ok := resourceStatus.ContentStatus["replicas"] - if !ok { - return fmt.Errorf("replicas not found in content status") - } - - if replicas.(float64) != float64(1) { - return fmt.Errorf("unexpected replicas, expected 1, got %d", replicas) - } - - return nil - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - - It("get the nginx deployment from cluster", func() { - Eventually(func() error { - deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - return err - } - if *deploy.Spec.Replicas != 1 { - return fmt.Errorf("unexpected replicas, expected 1, got %d", *deploy.Spec.Replicas) - } - return nil - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - - It("get the resource with the maestro api", func() { - gotResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, resourceID).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*gotResource.Id).To(Equal(resourceID)) - Expect(*gotResource.Version).To(Equal(int32(1))) - }) - - It("publish a resource spec with update request using grpc client", func() { - evt := helper.NewEvent(sourceID, "update_request", agentTestOpts.consumerName, resourceID, deployName, 1, 2) - pbEvt := &pbv1.CloudEvent{} - err := grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) - Expect(err).To(BeNil(), "failed to convert spec from cloudevent to protobuf") - _, err = grpcClient.Publish(ctx, &pbv1.PublishRequest{Event: pbEvt}) - Expect(err).ShouldNot(HaveOccurred()) - }) - - It("Subscribe to the resource status using grpc client", func() { - Eventually(func() error { - if resourceStatus.ReconcileStatus == nil { - return fmt.Errorf("reconcile status is empty") - } - - if !meta.IsStatusConditionTrue(resourceStatus.ReconcileStatus.Conditions, "Applied") { - return fmt.Errorf("resource not applied") - } - - if !meta.IsStatusConditionTrue(resourceStatus.ReconcileStatus.Conditions, "Available") { - return fmt.Errorf("resource not Available") - } - - replicas, ok := resourceStatus.ContentStatus["replicas"] - if !ok { - return fmt.Errorf("replicas not found in content status") - } - - if replicas.(float64) != float64(2) { - return fmt.Errorf("unexpected replicas, expected 2, got %d", replicas) - } - - return nil - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - - It("get the nginx deployment from cluster", func() { - Eventually(func() error { - deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - return err - } - if *deploy.Spec.Replicas != 2 { - return fmt.Errorf("unexpected replicas, expected 2, got %d", *deploy.Spec.Replicas) - } - return nil - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - - It("get the resource with the maestro api", func() { - gotResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, resourceID).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*gotResource.Id).To(Equal(resourceID)) - Expect(*gotResource.Version).To(Equal(int32(2))) - }) - - It("publish a resource spec with delete request using grpc client", func() { - evt := helper.NewEvent(sourceID, "delete_request", agentTestOpts.consumerName, resourceID, deployName, 2, 2) - pbEvt := &pbv1.CloudEvent{} - err := grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) - Expect(err).To(BeNil(), "failed to convert spec from cloudevent to protobuf") - _, err = grpcClient.Publish(ctx, &pbv1.PublishRequest{Event: pbEvt}) - Expect(err).ShouldNot(HaveOccurred()) - }) - - It("Subscribe to the resource status using grpc client", func() { - Eventually(func() error { - if resourceStatus.ReconcileStatus == nil { - return fmt.Errorf("reconcile status is empty") - } - - if !meta.IsStatusConditionTrue(resourceStatus.ReconcileStatus.Conditions, common.ManifestsDeleted) { - return fmt.Errorf("resource not deleted") - } - - return nil - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - - It("get the nginx deployment from cluster", func() { - Eventually(func() error { - _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { - return nil - } - return err - } - return fmt.Errorf("nginx deployment still exists") - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - - It("get the resource with the maestro api", func() { - _, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, resourceID).Execute() - Expect(err).To(HaveOccurred(), "Expected 404 error") - Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) - }) - }) - - Context("GRPC Manifest Bundle Tests", func() { + Context("GRPC API Tests", func() { deployName := fmt.Sprintf("nginx-%s", rand.String(5)) resourceID := uuid.NewString() resourceBundleStatus := &api.ResourceBundleStatus{ ManifestBundleStatus: &payload.ManifestBundleStatus{}, } - It("subscribe to resource bundle status with grpc client", func() { + It("subscribe to resource status with grpc client", func() { go func() { subClient, err := grpcClient.Subscribe(ctx, &pbv1.SubscriptionRequest{Source: sourceID}) if err != nil { @@ -302,27 +76,28 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { }() }) - It("publish a resource bundle spec using grpc client", func() { - evt := helper.NewEvent(sourceID, "create_request", agentTestOpts.consumerName, resourceID, deployName, 1, 1) + It("publish a resource spec with grpc client", func() { + evt, err := helper.NewEvent(sourceID, "create_request", agentTestOpts.consumerName, resourceID, deployName, 1, 1) + Expect(err).ShouldNot(HaveOccurred()) pbEvt := &pbv1.CloudEvent{} - err := grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) + err = grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) Expect(err).To(BeNil(), "failed to convert spec from cloudevent to protobuf") _, err = grpcClient.Publish(ctx, &pbv1.PublishRequest{Event: pbEvt}) Expect(err).ShouldNot(HaveOccurred()) }) - It("Subscribe to the resource bundle status using grpc client", func() { + It("subscribe to the resource status with grpc client", func() { Eventually(func() error { if resourceBundleStatus.ManifestBundleStatus == nil { - return fmt.Errorf("resource bundle status is empty") + return fmt.Errorf("resource status is empty") } if !meta.IsStatusConditionTrue(resourceBundleStatus.ManifestBundleStatus.Conditions, "Applied") { - return fmt.Errorf("resource bundle not applied") + return fmt.Errorf("resource not applied") } if !meta.IsStatusConditionTrue(resourceBundleStatus.ManifestBundleStatus.Conditions, "Available") { - return fmt.Errorf("resource bundle not Available") + return fmt.Errorf("resource not Available") } if len(resourceBundleStatus.ManifestBundleStatus.ResourceStatus) != 1 { @@ -353,7 +128,7 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("get the nginx deployment from cluster", func() { + It("get the deployment from cluster", func() { Eventually(func() error { deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) if err != nil { @@ -366,35 +141,36 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("get the resource bundle with the maestro api", func() { - gotResourceBundle, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, resourceID).Execute() + It("get the resource via maestro api", func() { + gotResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, resourceID).Execute() Expect(err).ShouldNot(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*gotResourceBundle.Id).To(Equal(resourceID)) - Expect(*gotResourceBundle.Version).To(Equal(int32(1))) + Expect(*gotResource.Id).To(Equal(resourceID)) + Expect(*gotResource.Version).To(Equal(int32(1))) }) - It("publish a resource bundle spec with update request using grpc client", func() { - evt := helper.NewEvent(sourceID, "update_request", agentTestOpts.consumerName, resourceID, deployName, 1, 2) + It("publish a resource update with grpc client", func() { + evt, err := helper.NewEvent(sourceID, "update_request", agentTestOpts.consumerName, resourceID, deployName, 1, 2) + Expect(err).ShouldNot(HaveOccurred()) pbEvt := &pbv1.CloudEvent{} - err := grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) + err = grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) Expect(err).To(BeNil(), "failed to convert spec from cloudevent to protobuf") _, err = grpcClient.Publish(ctx, &pbv1.PublishRequest{Event: pbEvt}) Expect(err).ShouldNot(HaveOccurred()) }) - It("Subscribe to the resource bundle status using grpc client", func() { + It("subscribe to the resource status with grpc client", func() { Eventually(func() error { if resourceBundleStatus.ManifestBundleStatus == nil { - return fmt.Errorf("resource bundle status is empty") + return fmt.Errorf("resource status is empty") } if !meta.IsStatusConditionTrue(resourceBundleStatus.ManifestBundleStatus.Conditions, "Applied") { - return fmt.Errorf("resource bundle not applied") + return fmt.Errorf("resource not applied") } if !meta.IsStatusConditionTrue(resourceBundleStatus.ManifestBundleStatus.Conditions, "Available") { - return fmt.Errorf("resource bundle not Available") + return fmt.Errorf("resource not Available") } if len(resourceBundleStatus.ManifestBundleStatus.ResourceStatus) != 1 { @@ -425,7 +201,7 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("get the nginx deployment from cluster", func() { + It("get the deployment from cluster", func() { Eventually(func() error { deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) if err != nil { @@ -438,38 +214,39 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("get the resource bundle with the maestro api", func() { - gotResourceBundle, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, resourceID).Execute() + It("get the resource via maestro api", func() { + gotResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, resourceID).Execute() Expect(err).ShouldNot(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*gotResourceBundle.Id).To(Equal(resourceID)) - Expect(*gotResourceBundle.Version).To(Equal(int32(2))) + Expect(*gotResource.Id).To(Equal(resourceID)) + Expect(*gotResource.Version).To(Equal(int32(2))) }) - It("publish a resource bundle spec with delete request using grpc client", func() { - evt := helper.NewEvent(sourceID, "delete_request", agentTestOpts.consumerName, resourceID, deployName, 2, 2) + It("publish a resource delete with grpc client", func() { + evt, err := helper.NewEvent(sourceID, "delete_request", agentTestOpts.consumerName, resourceID, deployName, 2, 2) + Expect(err).ShouldNot(HaveOccurred()) pbEvt := &pbv1.CloudEvent{} - err := grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) + err = grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) Expect(err).To(BeNil(), "failed to convert spec from cloudevent to protobuf") _, err = grpcClient.Publish(ctx, &pbv1.PublishRequest{Event: pbEvt}) Expect(err).ShouldNot(HaveOccurred()) }) - It("Subscribe to the resource bundle status using grpc client", func() { + It("subscribe to the resource status with grpc client", func() { Eventually(func() error { if resourceBundleStatus.ManifestBundleStatus == nil { - return fmt.Errorf("resource bundle status is empty") + return fmt.Errorf("resource status is empty") } if !meta.IsStatusConditionTrue(resourceBundleStatus.ManifestBundleStatus.Conditions, common.ManifestsDeleted) { - return fmt.Errorf("resource bundle not applied") + return fmt.Errorf("resource is not deleted") } return nil }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("get the nginx deployment from cluster", func() { + It("get the deployment from cluster", func() { Eventually(func() error { _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) if err != nil { @@ -482,7 +259,7 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("get the resource with the maestro api", func() { + It("check the resource via maestro api", func() { _, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, resourceID).Execute() Expect(err).To(HaveOccurred(), "Expected 404 error") Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) diff --git a/test/e2e/pkg/resources_test.go b/test/e2e/pkg/resources_test.go index 9fca6f7f..f47772d2 100644 --- a/test/e2e/pkg/resources_test.go +++ b/test/e2e/pkg/resources_test.go @@ -1,38 +1,32 @@ package e2e_test import ( - "encoding/json" "fmt" "net/http" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" workv1 "open-cluster-management.io/api/work/v1" - "github.com/openshift-online/maestro/pkg/api" - "github.com/openshift-online/maestro/pkg/api/openapi" + "github.com/openshift-online/maestro/pkg/client/cloudevents/grpcsource" ) var _ = Describe("Resources", Ordered, Label("e2e-tests-resources"), func() { Context("Resource CRUD Tests", func() { + workName := fmt.Sprintf("work-%s", rand.String(5)) deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - var resource *openapi.Resource - It("post the nginx resource to the maestro api", func() { - res := helper.NewAPIResource(agentTestOpts.consumerName, deployName, 1) - var resp *http.Response - var err error - resource, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() + work := helper.NewManifestWork(workName, deployName, "default", 1) + var resourceID string + It("create a resource with source work client", func() { + _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).ShouldNot(BeEmpty()) - Expect(*resource.Version).To(Equal(int32(1))) Eventually(func() error { deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) @@ -46,161 +40,48 @@ var _ = Describe("Resources", Ordered, Label("e2e-tests-resources"), func() { }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("get the nginx resource from the maestro api", func() { - gotResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resource.Id).Execute() + It("get the resource via maestro api", func() { + search := fmt.Sprintf("consumer_name = '%s'", agentTestOpts.consumerName) + gotResourceList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() Expect(err).ShouldNot(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*gotResource.Id).To(Equal(*resource.Id)) - Expect(*gotResource.Version).To(Equal(*resource.Version)) + Expect(len(gotResourceList.Items)).To(Equal(1)) + resourceID = *gotResourceList.Items[0].Id }) - It("patch the nginx resource with the maestro api", func() { - newRes := helper.NewAPIResource(agentTestOpts.consumerName, deployName, 2) - patchedResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdPatch(ctx, *resource.Id). - ResourcePatchRequest(openapi.ResourcePatchRequest{Version: resource.Version, Manifest: newRes.Manifest}).Execute() + It("patch the resource with source work client", func() { + work, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workName, metav1.GetOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*patchedResource.Version).To(Equal(*resource.Version + 1)) - - Eventually(func() error { - deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - return err - } - if *deploy.Spec.Replicas != 2 { - return fmt.Errorf("unexpected replicas, expected 2, got %d", *deploy.Spec.Replicas) - } - return nil - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - - It("delete the nginx resource", func() { - resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resource.Id).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) - - Eventually(func() error { - _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { - return nil - } - return err - } - return fmt.Errorf("nginx deployment still exists") - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - }) - - Context("Resource Delete Option Tests", func() { - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - var resource *openapi.Resource - It("post the nginx resource to the maestro api", func() { - res := helper.NewAPIResource(agentTestOpts.consumerName, deployName, 1) - res.DeleteOption = map[string]interface{}{"propagationPolicy": "Orphan"} - var resp *http.Response - var err error - resource, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).ShouldNot(BeEmpty()) - - Eventually(func() error { - deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - return err - } - if *deploy.Spec.Replicas != 1 { - return fmt.Errorf("unexpected replicas, expected 1, got %d", *deploy.Spec.Replicas) - } - return nil - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - It("delete the nginx resource from the maestro api", func() { - resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resource.Id).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) - - // ensure the "nginx" deployment in the "default" namespace is not deleted - Consistently(func() error { - _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { - return fmt.Errorf("nginx deployment is deleted") - } - } - return nil - }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) - }) + newWork := work.DeepCopy() + newWork.Spec.Workload.Manifests = []workv1.Manifest{helper.NewManifest(deployName, "default", 2)} - It("delete the nginx deployment", func() { - err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Delete(ctx, deployName, metav1.DeleteOptions{}) + patchData, err := grpcsource.ToWorkPatch(work, newWork) Expect(err).ShouldNot(HaveOccurred()) - Eventually(func() error { - _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { - return nil - } - return err - } - return fmt.Errorf("nginx deployment still exists") - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - }) - - Context("Resource CreateOnly UpdateStrategy Tests", func() { - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - var resource *openapi.Resource - It("post the nginx resource to the maestro api with createOnly updateStrategy", func() { - res := helper.NewAPIResource(agentTestOpts.consumerName, deployName, 1) - res.UpdateStrategy = map[string]interface{}{"type": "CreateOnly"} - var resp *http.Response - var err error - resource, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() + _, err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Patch(ctx, workName, types.MergePatchType, patchData, metav1.PatchOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).ShouldNot(BeEmpty()) Eventually(func() error { deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) if err != nil { return err } - if *deploy.Spec.Replicas != 1 { - return fmt.Errorf("unexpected replicas, expected 1, got %d", *deploy.Spec.Replicas) + if *deploy.Spec.Replicas != 2 { + return fmt.Errorf("unexpected replicas, expected 2, got %d", *deploy.Spec.Replicas) } return nil }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - It("patch the nginx resource", func() { - newRes := helper.NewAPIResource(agentTestOpts.consumerName, deployName, 2) - patchedResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdPatch(ctx, *resource.Id). - ResourcePatchRequest(openapi.ResourcePatchRequest{Version: resource.Version, Manifest: newRes.Manifest}).Execute() + gotResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, resourceID).Execute() Expect(err).ShouldNot(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*patchedResource.Version).To(Equal(*resource.Version + 1)) - - // ensure the "nginx" deployment in the "default" namespace is not updated - Consistently(func() error { - deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - return nil - } - if *deploy.Spec.Replicas != 1 { - return fmt.Errorf("unexpected replicas, expected 1, got %d", *deploy.Spec.Replicas) - } - return nil - }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) + Expect(*gotResource.Version).To(Equal(int32(2))) }) - It("delete the nginx resource", func() { - resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resource.Id).Execute() + It("delete the resource with source work client", func() { + err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) Eventually(func() error { _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) @@ -212,101 +93,35 @@ var _ = Describe("Resources", Ordered, Label("e2e-tests-resources"), func() { } return fmt.Errorf("nginx deployment still exists") }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - }) - - Context("Resource ReadOnly UpdateStrategy Tests via restful api", func() { - var resource *openapi.Resource - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - It("create a nginx deployment in the target cluster", func() { - nginxDeploy := &appsv1.Deployment{} - err := json.Unmarshal([]byte(helper.NewResourceManifestJSON(deployName, 1)), nginxDeploy) - Expect(err).ShouldNot(HaveOccurred()) - _, err = agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Create(ctx, nginxDeploy, metav1.CreateOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - }) - It("post the resource to the maestro api with readonly updateStrategy", func() { - var resp *http.Response - var err error - // post the resource with readonly updateStrategy and foreground delete option should fail - invalidRes := helper.NewReadOnlyAPIResource(agentTestOpts.consumerName, deployName) - invalidRes.DeleteOption = map[string]interface{}{"propagationPolicy": "Foreground"} - resource, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(invalidRes).Execute() + _, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, resourceID).Execute() Expect(err).Should(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusBadRequest)) - - res := helper.NewReadOnlyAPIResource(agentTestOpts.consumerName, deployName) - resource, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).ShouldNot(BeEmpty()) + Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) }) - It("get the resource status back", func() { + It("check the resource deletion via maestro api", func() { Eventually(func() error { - res, _, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resource.Id).Execute() + search := fmt.Sprintf("consumer_name = '%s'", agentTestOpts.consumerName) + gotResourceList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() if err != nil { return err } - - // ensure the delete option is set to Orphan - deleteType, ok := res.DeleteOption["propagationPolicy"] - if !ok { - return fmt.Errorf("delete option is not set") - } - if deleteType != "Orphan" { - return fmt.Errorf("delete option is not Orphan") - } - - statusJSON, err := json.Marshal(res.Status) - if err != nil { - return err - } - - resourceStatus := &api.ResourceStatus{} - err = json.Unmarshal(statusJSON, resourceStatus) - if err != nil { - return err - } - - if resourceStatus.ContentStatus != nil { - conditions := resourceStatus.ContentStatus["conditions"].([]interface{}) - if len(conditions) > 0 { - return nil - } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected http code, got %d, expected %d", resp.StatusCode, http.StatusOK) } - - return fmt.Errorf("contentStatus should not be empty") - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - }) - - It("delete the readonly resource", func() { - resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resource.Id).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) - - err = agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Delete(ctx, deployName, metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - - Eventually(func() error { - _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { - return nil - } - return err + if len(gotResourceList.Items) != 0 { + return fmt.Errorf("expected no resources returned by maestro api") } - return fmt.Errorf("nginx deployment still exists") + return nil }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) }) - Context("Resource ReadOnly UpdateStrategy Tests via gRPC", func() { + Context("Resource ReadOnly Tests", func() { workName := "work-readonly-" + rand.String(5) secretName := "auth-" + rand.String(5) manifest := fmt.Sprintf("{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"name\":\"%s\",\"namespace\":\"default\"}}", secretName) - It("create a secret in the target cluster", func() { + It("create the secret in the target cluster", func() { _, err := agentTestOpts.kubeClientSet.CoreV1().Secrets("default").Create(ctx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: secretName, @@ -319,7 +134,7 @@ var _ = Describe("Resources", Ordered, Label("e2e-tests-resources"), func() { Expect(err).ShouldNot(HaveOccurred()) }) - It("post the resource bundle via gRPC client", func() { + It("post the resource with source work client", func() { work := &workv1.ManifestWork{ ObjectMeta: metav1.ObjectMeta{ Name: workName, @@ -359,23 +174,19 @@ var _ = Describe("Resources", Ordered, Label("e2e-tests-resources"), func() { }, }, } - Eventually(func() error { - _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{}) - return err - }, 5*time.Minute, 5*time.Second).ShouldNot(HaveOccurred()) - }) - It("get the resource via restful API", func() { - gotResourceBundleList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Execute() + _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(len(gotResourceBundleList.Items)).To(Equal(1)) - resourceBundle := gotResourceBundleList.Items[0] - Expect(resourceBundle.Metadata["creationTimestamp"]).ShouldNot(BeEmpty()) - gotResourceBundle, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, *resourceBundle.Id).Execute() + }) + + It("get the resource via maestro API", func() { + search := fmt.Sprintf("consumer_name = '%s'", agentTestOpts.consumerName) + gotResourceList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() Expect(err).ShouldNot(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(gotResourceBundle.Metadata["creationTimestamp"]).ShouldNot(BeEmpty()) + Expect(len(gotResourceList.Items)).To(Equal(1)) + resource := gotResourceList.Items[0] + Expect(resource.Metadata["creationTimestamp"]).ShouldNot(BeEmpty()) }) It("get the resource status back", func() { @@ -385,7 +196,7 @@ var _ = Describe("Resources", Ordered, Label("e2e-tests-resources"), func() { return err } if work.CreationTimestamp.Time.IsZero() { - return fmt.Errorf("work creationTimestamp is empty") + return fmt.Errorf("work creation timestamp is empty") } manifests := work.Status.ResourceStatus.Manifests @@ -397,11 +208,11 @@ var _ = Describe("Resources", Ordered, Label("e2e-tests-resources"), func() { return fmt.Errorf("the status feedback value %v is not expected", feedback[0]) } - return fmt.Errorf("manifests are empty") + return fmt.Errorf("work status manifests are empty") }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("delete the readonly resource", func() { + It("delete the readonly resource with source work client", func() { err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{}) Expect(err).ShouldNot(HaveOccurred()) @@ -419,5 +230,22 @@ var _ = Describe("Resources", Ordered, Label("e2e-tests-resources"), func() { return fmt.Errorf("auth secret still exists") }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) + + It("check the resource deletion via maestro api", func() { + Eventually(func() error { + search := fmt.Sprintf("consumer_name = '%s'", agentTestOpts.consumerName) + gotResourceList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected http code, got %d, expected %d", resp.StatusCode, http.StatusOK) + } + if len(gotResourceList.Items) != 0 { + return fmt.Errorf("expected no resources returned by maestro api") + } + return nil + }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) + }) }) }) diff --git a/test/e2e/pkg/serverside_test.go b/test/e2e/pkg/serverside_test.go index 0fa261ba..e0792506 100644 --- a/test/e2e/pkg/serverside_test.go +++ b/test/e2e/pkg/serverside_test.go @@ -1,7 +1,6 @@ package e2e_test import ( - "encoding/json" "fmt" "net/http" "time" @@ -9,11 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/openshift-online/maestro/pkg/api" - "github.com/openshift-online/maestro/pkg/api/openapi" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/rand" @@ -21,145 +16,176 @@ import ( workv1 "open-cluster-management.io/api/work/v1" ) -const sleepJob = ` -{ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "%s", - "namespace": "default" - }, - "spec": { - "template": { - "spec": { - "containers": [ - { - "name": "sleep", - "image": "busybox:1.36", - "command": [ - "/bin/sh", - "-c", - "sleep 10" - ] - } - ], - "restartPolicy": "Never" - } - }, - "backoffLimit": 4 - } -} -` - -var _ = Describe("Server Side Apply", Ordered, Label("e2e-tests-serverside-apply"), func() { - It("Apply a job with maestro", func() { - // The kube-apiserver will set a default selector and label on the Pod of Job if the job does not have - // spec.Selector, these fields are immutable, if we use update strategy to apply Job, it will report - // AppliedManifestFailed. The maestro uses the server side strategy to apply a resource with ManifestWork - // by default, this will avoid this. - manifest := map[string]interface{}{} - sleepJobName := fmt.Sprintf("sleep-%s", rand.String(5)) - err := json.Unmarshal([]byte(fmt.Sprintf(sleepJob, sleepJobName)), &manifest) - Expect(err).ShouldNot(HaveOccurred()) - - res := openapi.Resource{ - Manifest: manifest, - ConsumerName: &agentTestOpts.consumerName, - GroupResource: map[string]interface{}{ - "group": "batch", - "resource": "jobs", - }, - } +var _ = Describe("ServerSideApply", Ordered, Label("e2e-tests-ssa"), func() { + // Context("Resource ServerSideApply Tests", func() { + // // The kube-apiserver will set a default selector and label on the Pod of Job if the job does not have + // // spec.Selector, these fields are immutable, if we use update strategy to apply Job, it will report + // // AppliedManifestFailed. The maestro uses the server side strategy to apply a resource with ManifestWork + // // by default, this will avoid this. + // workName := "work-ssa-" + rand.String(5) + // sleepJobName := fmt.Sprintf("sleep-%s", rand.String(5)) + // manifest := fmt.Sprintf("{\"apiVersion\":\"batch/v1\",\"kind\":\"Job\",\"metadata\":{\"name\":\"%s\",\"namespace\":\"default\"},\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"sleep\",\"image\":\"busybox:1.36\",\"command\":[\"/bin/sh\",\"-c\",\"sleep 10\"]}],\"restartPolicy\":\"Never\"}},\"backoffLimit\":4}}", sleepJobName) + // It("create the resource with source work client", func() { + // work := &workv1.ManifestWork{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: workName, + // }, + // Spec: workv1.ManifestWorkSpec{ + // Workload: workv1.ManifestsTemplate{ + // Manifests: []workv1.Manifest{ + // { + // RawExtension: runtime.RawExtension{ + // Raw: []byte(manifest), + // }, + // }, + // }, + // }, + // ManifestConfigs: []workv1.ManifestConfigOption{ + // { + // ResourceIdentifier: workv1.ResourceIdentifier{ + // Group: "batch", + // Resource: "jobs", + // Name: sleepJobName, + // Namespace: "default", + // }, + // FeedbackRules: []workv1.FeedbackRule{ + // { + // Type: workv1.JSONPathsType, + // JsonPaths: []workv1.JsonPath{ + // { + // Name: "status", + // Path: ".status", + // }, + // }, + // }, + // }, + // UpdateStrategy: &workv1.UpdateStrategy{ + // Type: workv1.UpdateStrategyTypeServerSideApply, + // }, + // }, + // }, + // }, + // } - created, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*created.Id).ShouldNot(BeEmpty()) + // _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{}) + // Expect(err).ShouldNot(HaveOccurred()) + // }) - resourceID := *created.Id - Eventually(func() error { - found, _, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, resourceID).Execute() - if err != nil { - return err - } - - if found.Status == nil { - return fmt.Errorf("the resource %s status is nil", resourceID) - } - - statusJSON, err := json.Marshal(found.Status) - if err != nil { - return fmt.Errorf("failed to marshal status to JSON: %v", err) - } - resourceStatus := &api.ResourceStatus{} - if err := json.Unmarshal(statusJSON, resourceStatus); err != nil { - return fmt.Errorf("failed to unmarshal status JSON to ResourceStatus: %v", err) - } - - conditions := resourceStatus.ReconcileStatus.Conditions - - if meta.IsStatusConditionFalse(conditions, workv1.WorkApplied) { - return fmt.Errorf("unexpected condition %v for resource %s", conditions, resourceID) - } - - if meta.IsStatusConditionFalse(conditions, workv1.WorkAvailable) { - return fmt.Errorf("unexpected condition %v for resource %s", conditions, resourceID) - } - - if meta.IsStatusConditionFalse(conditions, "StatusFeedbackSynced") { - return fmt.Errorf("unexpected condition %v for resource %s", conditions, resourceID) - } - - return nil - }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - - // cleanup the job - resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, resourceID).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) - }) + // It("get the resource via maestro api", func() { + // search := fmt.Sprintf("consumer_name = '%s'", agentTestOpts.consumerName) + // gotResourceList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() + // Expect(err).ShouldNot(HaveOccurred()) + // Expect(resp.StatusCode).To(Equal(http.StatusOK)) + // Expect(len(gotResourceList.Items)).To(Equal(1)) + // resource := gotResourceList.Items[0] + // Expect(resource.Metadata["creationTimestamp"]).ShouldNot(BeEmpty()) + // }) - It("Apply a nested work with SSA", func() { - workName := fmt.Sprintf("ssa-work-%s", rand.String(5)) - nestedWorkName := fmt.Sprintf("nested-work-%s", rand.String(5)) - nestedWorkNamespace := "default" + // It("get the resource status back", func() { + // Eventually(func() error { + // work, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workName, metav1.GetOptions{}) + // if err != nil { + // return err + // } + // if work.CreationTimestamp.Time.IsZero() { + // return fmt.Errorf("work creationTimestamp is empty") + // } + + // conditions := work.Status.Conditions + // if meta.IsStatusConditionFalse(conditions, workv1.WorkApplied) { + // return fmt.Errorf("unexpected condition %v", conditions) + // } - work := NewNestedManifestWork(nestedWorkNamespace, workName, nestedWorkName) - Eventually(func() error { + // if meta.IsStatusConditionFalse(conditions, workv1.WorkAvailable) { + // return fmt.Errorf("unexpected condition %v", conditions) + // } + + // if meta.IsStatusConditionFalse(conditions, "StatusFeedbackSynced") { + // return fmt.Errorf("unexpected condition %v", conditions) + // } + + // return nil + // }, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred()) + // }) + + // It("delete the resource with source work client", func() { + // err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{}) + // Expect(err).ShouldNot(HaveOccurred()) + // }) + + // It("check the resource deletion via maestro api", func() { + // Eventually(func() error { + // search := fmt.Sprintf("consumer_name = '%s'", agentTestOpts.consumerName) + // gotResourceList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() + // if err != nil { + // return err + // } + // if resp.StatusCode != http.StatusOK { + // return fmt.Errorf("unexpected http code, got %d, expected %d", resp.StatusCode, http.StatusOK) + // } + // if len(gotResourceList.Items) != 0 { + // return fmt.Errorf("expected no resources returned by maestro api") + // } + // return nil + // }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) + // }) + // }) + + Context("Nested Work ServerSideApply Tests", func() { + workName := fmt.Sprintf("ssa-work-%s", rand.String(5)) + It("create a resource with nested work using SSA", func() { + nestedWorkName := fmt.Sprintf("nested-work-%s", rand.String(5)) + nestedWorkNamespace := "default" + work := newNestedManifestWork(workName, nestedWorkName, nestedWorkNamespace) _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{}) - return err - }, 5*time.Minute, 5*time.Second).ShouldNot(HaveOccurred()) + Expect(err).ShouldNot(HaveOccurred()) - // make sure the nested work is created - Eventually(func() error { - _, err := agentTestOpts.workClientSet.WorkV1().ManifestWorks(nestedWorkNamespace).Get(ctx, nestedWorkName, metav1.GetOptions{}) - if err != nil { + // make sure the nested work is created + Eventually(func() error { + _, err := agentTestOpts.workClientSet.WorkV1().ManifestWorks(nestedWorkNamespace).Get(ctx, nestedWorkName, metav1.GetOptions{}) return err - } + }, 30*time.Second, time.Second).ShouldNot(HaveOccurred()) - return nil - }, 30*time.Second, time.Second).ShouldNot(HaveOccurred()) + // make sure the nested work is not updated + Consistently(func() error { + nestedWork, err := agentTestOpts.workClientSet.WorkV1().ManifestWorks(nestedWorkNamespace).Get(ctx, nestedWorkName, metav1.GetOptions{}) + if err != nil { + return err + } - // make sure the nested work is not updated - Consistently(func() error { - nestedWork, err := agentTestOpts.workClientSet.WorkV1().ManifestWorks(nestedWorkNamespace).Get(ctx, nestedWorkName, metav1.GetOptions{}) - if err != nil { - return err - } + if nestedWork.Generation != 1 { + return fmt.Errorf("nested work generation is changed to %d", nestedWork.Generation) + } - if nestedWork.Generation != 1 { - return fmt.Errorf("nested work generation is changed to %d", nestedWork.Generation) - } + return nil + }, 1*time.Minute, 1*time.Second).Should(BeNil()) + }) - return nil - }, 1*time.Minute, 1*time.Second).Should(BeNil()) + It("delete the resource with source work client", func() { + err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + }) - err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{}) - Expect(err).ShouldNot(HaveOccurred()) + It("check the resource deletion via maestro api", func() { + Eventually(func() error { + search := fmt.Sprintf("consumer_name = '%s'", agentTestOpts.consumerName) + gotResourceList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected http code, got %d, expected %d", resp.StatusCode, http.StatusOK) + } + if len(gotResourceList.Items) != 0 { + return fmt.Errorf("expected no resources returned by maestro api") + } + return nil + }, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred()) + }) }) }) -func NewNestedManifestWork(nestedWorkNamespace, name, nestedWorkName string) *workv1.ManifestWork { +func newNestedManifestWork(workName, nestedWorkName, nestedWorkNamespace string) *workv1.ManifestWork { nestedWork := &workv1.ManifestWork{ TypeMeta: metav1.TypeMeta{ APIVersion: "work.open-cluster-management.io/v1", @@ -197,7 +223,7 @@ func NewNestedManifestWork(nestedWorkNamespace, name, nestedWorkName string) *wo return &workv1.ManifestWork{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: workName, }, Spec: workv1.ManifestWorkSpec{ Workload: workv1.ManifestsTemplate{ diff --git a/test/e2e/pkg/sourceclient_test.go b/test/e2e/pkg/sourceclient_test.go index 9f40225d..c97f2530 100644 --- a/test/e2e/pkg/sourceclient_test.go +++ b/test/e2e/pkg/sourceclient_test.go @@ -25,17 +25,15 @@ import ( "open-cluster-management.io/sdk-go/pkg/cloudevents/work/common" ) -var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source-work-client"), func() { +var _ = Describe("SourceWorkClient", Ordered, Label("e2e-tests-source-work-client"), func() { Context("Update an obsolete work", func() { var workName string BeforeEach(func() { workName = "work-" + rand.String(5) work := NewManifestWork(workName) - Eventually(func() error { - _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{}) - return err - }, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred()) + _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{}) + Expect(err).ShouldNot(HaveOccurred()) // wait for few seconds to ensure the creation is finished <-time.After(5 * time.Second) @@ -51,7 +49,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- }) - It("Should return an error when updating an obsolete work", func() { + It("should return error when updating an obsolete work", func() { By("update a work by work client") work, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workName, metav1.GetOptions{}) Expect(err).ShouldNot(HaveOccurred()) @@ -79,7 +77,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- }) }) - Context("Watch work status with gRPC source ManifestWork client", func() { + Context("Watch work status with source work client", func() { var watcherCtx context.Context var watcherCancel context.CancelFunc @@ -120,8 +118,8 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- watcherCancel() }) - It("The work status should be watched", func() { - By("create a work client for watch") + It("the work status should be watched", func() { + By("create a work watcher client") watcherClient, err := grpcsource.NewMaestroGRPCSourceWorkClient( watcherCtx, apiClient, @@ -130,12 +128,12 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- ) Expect(err).ShouldNot(HaveOccurred()) - By("start watching") + By("start status watching") watcher, err := watcherClient.ManifestWorks(agentTestOpts.consumerName).Watch(watcherCtx, metav1.ListOptions{}) Expect(err).ShouldNot(HaveOccurred()) result := StartWatch(watcherCtx, watcher) - By("create a work by work client") + By("create a work with source work client") workName := "work-" + rand.String(5) _, err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, NewManifestWork(workName), metav1.CreateOptions{}) Expect(err).ShouldNot(HaveOccurred()) @@ -143,7 +141,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- // wait for few seconds to ensure the creation is finished <-time.After(5 * time.Second) - By("update a work by work client") + By("update a work with source work client") work, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workName, metav1.GetOptions{}) Expect(err).ShouldNot(HaveOccurred()) @@ -158,7 +156,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- // wait for few seconds to ensure the work status is updated by agent <-time.After(5 * time.Second) - By("delete the work by work client") + By("delete the work with source work client") err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{}) Expect(err).ShouldNot(HaveOccurred()) @@ -167,7 +165,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("The watchers with different namespace", func() { + It("the watchers for different namespace", func() { watcherClient, err := grpcsource.NewMaestroGRPCSourceWorkClient( watcherCtx, apiClient, @@ -191,7 +189,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- Expect(err).ShouldNot(HaveOccurred()) otherConsumerWatcherResult := StartWatch(watcherCtx, otherConsumerWatcher) - By("create a work by work client") + By("create a work with source work client") workName := "work-" + rand.String(5) _, err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, NewManifestWork(workName), metav1.CreateOptions{}) Expect(err).ShouldNot(HaveOccurred()) @@ -199,7 +197,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- // wait for few seconds to ensure the creation is finished <-time.After(5 * time.Second) - By("delete the work by work client") + By("delete the work with source work client") err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{}) Expect(err).ShouldNot(HaveOccurred()) @@ -219,7 +217,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("The watchers with label selector", func() { + It("the watchers with label selector", func() { watcherClient, err := grpcsource.NewMaestroGRPCSourceWorkClient( watcherCtx, apiClient, @@ -228,14 +226,14 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- ) Expect(err).ShouldNot(HaveOccurred()) - By("start watching with label app=test") + By("start watching with label") watcher, err := watcherClient.ManifestWorks(agentTestOpts.consumerName).Watch(watcherCtx, metav1.ListOptions{ LabelSelector: "app=test", }) Expect(err).ShouldNot(HaveOccurred()) result := StartWatch(watcherCtx, watcher) - By("create a work by work client") + By("create a work with source work client") workName := "work-" + rand.String(5) work := NewManifestWorkWithLabels(workName, map[string]string{"app": "test"}) _, err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{}) @@ -244,7 +242,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- // wait for few seconds to ensure the creation is finished <-time.After(5 * time.Second) - By("delete the work by work client") + By("delete the work with source work client") err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{}) Expect(err).ShouldNot(HaveOccurred()) @@ -254,7 +252,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- }) }) - Context("List works with gRPC source ManifestWork client", func() { + Context("List works with source work client", func() { var workName string var prodWorkName string var testWorkAName string @@ -329,7 +327,7 @@ var _ = Describe("Source ManifestWork Client", Ordered, Label("e2e-tests-source- }, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred()) }) - It("List works with options", func() { + It("list works with options", func() { By("list all works") works, err := sourceWorkClient.ManifestWorks(metav1.NamespaceAll).List(ctx, metav1.ListOptions{}) Expect(err).ShouldNot(HaveOccurred()) diff --git a/test/e2e/pkg/spec_resync_test.go b/test/e2e/pkg/spec_resync_test.go index a6a7aa6a..0bc76e8c 100644 --- a/test/e2e/pkg/spec_resync_test.go +++ b/test/e2e/pkg/spec_resync_test.go @@ -7,28 +7,29 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/openshift-online/maestro/pkg/api/openapi" + "github.com/openshift-online/maestro/pkg/client/cloudevents/grpcsource" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" + workv1 "open-cluster-management.io/api/work/v1" ) var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-resync-restart"), func() { - Context("Resource resync resource spec after maestro agent restarts", func() { + Context("Resync resource spec after maestro agent restarts", func() { var maestroAgentReplicas int - var resourceA, resourceB, resourceC *openapi.Resource - deployA := fmt.Sprintf("nginx-%s", rand.String(5)) - deployB := fmt.Sprintf("nginx-%s", rand.String(5)) - deployC := fmt.Sprintf("nginx-%s", rand.String(5)) - It("post the nginx A resource to the maestro api", func() { - res := helper.NewAPIResource(agentTestOpts.consumerName, deployA, 1) - var resp *http.Response - var err error - resourceA, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() + deployA := fmt.Sprintf("nginx-a-%s", rand.String(5)) + workNameA := fmt.Sprintf("work-a-%s", rand.String(5)) + workA := helper.NewManifestWork(workNameA, deployA, "default", 1) + deployB := fmt.Sprintf("nginx-b-%s", rand.String(5)) + workNameB := fmt.Sprintf("work-b-%s", rand.String(5)) + workB := helper.NewManifestWork(workNameB, deployB, "default", 1) + deployC := fmt.Sprintf("nginx-c-%s", rand.String(5)) + workNameC := fmt.Sprintf("work-c-%s", rand.String(5)) + workC := helper.NewManifestWork(workNameC, deployC, "default", 1) + It("create resource A with source work client", func() { + _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, workA, metav1.CreateOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resourceA.Id).ShouldNot(BeEmpty()) Eventually(func() error { deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployA, metav1.GetOptions{}) @@ -36,20 +37,15 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res return err } if *deploy.Spec.Replicas != 1 { - return fmt.Errorf("unexpected replicas for nginx A deployment %s, expected 1, got %d", deployA, *deploy.Spec.Replicas) + return fmt.Errorf("unexpected replicas, expected 1, got %d", *deploy.Spec.Replicas) } return nil }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("post the nginx B resource to the maestro api", func() { - res := helper.NewAPIResource(agentTestOpts.consumerName, deployB, 1) - var resp *http.Response - var err error - resourceB, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resourceB.Id).ShouldNot(BeEmpty()) + It("create resource B with source work client", func() { + _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, workB, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) Eventually(func() error { deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployB, metav1.GetOptions{}) @@ -57,7 +53,7 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res return err } if *deploy.Spec.Replicas != 1 { - return fmt.Errorf("unexpected replicas for nginx B deployment %s, expected 1, got %d", deployB, *deploy.Spec.Replicas) + return fmt.Errorf("unexpected replicas, expected 1, got %d", *deploy.Spec.Replicas) } return nil }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) @@ -90,16 +86,21 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("patch the nginx A resource", func() { - newRes := helper.NewAPIResource(agentTestOpts.consumerName, deployA, 2) - patchedResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdPatch(ctx, *resourceA.Id). - ResourcePatchRequest(openapi.ResourcePatchRequest{Version: resourceA.Version, Manifest: newRes.Manifest}).Execute() + It("patch the resource A with source work client", func() { + workA, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workNameA, metav1.GetOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + newWorkA := workA.DeepCopy() + newWorkA.Spec.Workload.Manifests = []workv1.Manifest{helper.NewManifest(deployA, "default", 2)} + + patchData, err := grpcsource.ToWorkPatch(workA, newWorkA) + Expect(err).ShouldNot(HaveOccurred()) + + _, err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Patch(ctx, workNameA, types.MergePatchType, patchData, metav1.PatchOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*patchedResource.Version).To(Equal(*resourceA.Version + 1)) }) - It("ensure the nginx A resource is not updated", func() { + It("ensure the nginx A deployment is not updated", func() { Consistently(func() error { deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployA, metav1.GetOptions{}) if err != nil { @@ -112,13 +113,12 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("delete the nginx B resource", func() { - resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resourceB.Id).Execute() + It("delete the resource B with source work client", func() { + err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workNameB, metav1.DeleteOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) }) - It("ensure the nginx B resource is not deleted", func() { + It("ensure the nginx B deployment is not deleted", func() { Consistently(func() error { _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployB, metav1.GetOptions{}) if err != nil { @@ -130,17 +130,12 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("post the nginx C resource to the maestro api", func() { - res := helper.NewAPIResource(agentTestOpts.consumerName, deployC, 1) - var resp *http.Response - var err error - resourceC, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resourceC.Id).ShouldNot(BeEmpty()) + It("create resource C with source work client", func() { + _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, workC, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("ensure the nginx C resource is not created", func() { + It("ensure the nginx C deployment is not created", func() { Consistently(func() error { _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployC, metav1.GetOptions{}) if err == nil { @@ -181,7 +176,7 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("ensure the nginx A resource is updated", func() { + It("ensure the nginx A deployment is updated", func() { Eventually(func() error { deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployA, metav1.GetOptions{}) if err != nil { @@ -194,7 +189,7 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res }, 3*time.Minute, 3*time.Second).ShouldNot(HaveOccurred()) }) - It("ensure the nginx B resource is deleted", func() { + It("ensure the nginx B deployment is deleted", func() { Eventually(func() error { _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployB, metav1.GetOptions{}) if err != nil { @@ -207,7 +202,7 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("ensure the nginx C resource is created", func() { + It("ensure the nginx C deployment is created", func() { Eventually(func() error { deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployC, metav1.GetOptions{}) if err != nil { @@ -220,10 +215,12 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("delete the nginx A and nginx C resources", func() { - resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resourceA.Id).Execute() + It("delete the nginx A and nginx C resource", func() { + err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workNameA, metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + + err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workNameC, metav1.DeleteOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) Eventually(func() error { _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployA, metav1.GetOptions{}) @@ -236,10 +233,6 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res return fmt.Errorf("nginx A deployment %s still exists", deployA) }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resourceC.Id).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) - Eventually(func() error { _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployC, metav1.GetOptions{}) if err != nil { @@ -251,365 +244,22 @@ var _ = Describe("Spec Resync After Restart", Ordered, Label("e2e-tests-spec-res return fmt.Errorf("nginx C deployment %s still exists", deployC) }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) + + It("check the resource deletion via maestro api", func() { + Eventually(func() error { + search := fmt.Sprintf("consumer_name = '%s'", agentTestOpts.consumerName) + gotResourceList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected http code, got %d, expected %d", resp.StatusCode, http.StatusOK) + } + if len(gotResourceList.Items) != 0 { + return fmt.Errorf("expected no resources returned by maestro api") + } + return nil + }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) + }) }) }) - -// var _ = Describe("Spec Resync After Reconnect", Ordered, Label("e2e-tests-spec-resync-reconnect"), func() { -// Context("Resource resync resource spec after maestro agent reconnects", func() { -// var maestroServerReplicas, mqttReplicas int -// var resourceA, resourceB, resourceC *openapi.Resource -// deployA := fmt.Sprintf("nginx-%s", rand.String(5)) -// deployB := fmt.Sprintf("nginx-%s", rand.String(5)) -// deployC := fmt.Sprintf("nginx-%s", rand.String(5)) -// It("post the nginx A resource to the maestro api", func() { -// res := helper.NewAPIResource(agentTestOpts.consumerName, deployA, 1) -// var resp *http.Response -// var err error -// resourceA, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(resp.StatusCode).To(Equal(http.StatusCreated)) -// Expect(*resourceA.Id).ShouldNot(BeEmpty()) - -// Eventually(func() error { -// deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployA, metav1.GetOptions{}) -// if err != nil { -// return err -// } -// if *deploy.Spec.Replicas != 1 { -// return fmt.Errorf("unexpected replicas for nginx A deployment %s, expected 1, got %d", deployA, *deploy.Spec.Replicas) -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("post the nginx B resource to the maestro api", func() { -// res := helper.NewAPIResource(agentTestOpts.consumerName, deployB, 1) -// var resp *http.Response -// var err error -// resourceB, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(resp.StatusCode).To(Equal(http.StatusCreated)) -// Expect(*resourceB.Id).ShouldNot(BeEmpty()) - -// Eventually(func() error { -// deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployB, metav1.GetOptions{}) -// if err != nil { -// return err -// } -// if *deploy.Spec.Replicas != 1 { -// return fmt.Errorf("unexpected replicas for nginx B deployment %s, expected 1, got %d", deployB, *deploy.Spec.Replicas) -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("delete the grpc-broker service for agent", func() { -// err := serverTestOpts.kubeClientSet.CoreV1().Services(serverTestOpts.serverNamespace).Delete(ctx, "maestro-grpc-broker", metav1.DeleteOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// }) - -// It("delete the mqtt-broker service for agent", func() { -// err := serverTestOpts.kubeClientSet.CoreV1().Services(serverTestOpts.serverNamespace).Delete(ctx, "maestro-mqtt-agent", metav1.DeleteOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// }) - -// It("rollout maestro server", func() { -// deploy, err := serverTestOpts.kubeClientSet.AppsV1().Deployments(serverTestOpts.serverNamespace).Get(ctx, "maestro", metav1.GetOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// maestroServerReplicas = int(*deploy.Spec.Replicas) -// deploy, err = serverTestOpts.kubeClientSet.AppsV1().Deployments(serverTestOpts.serverNamespace).Patch(ctx, "maestro", types.MergePatchType, []byte(`{"spec":{"replicas":0}}`), metav1.PatchOptions{ -// FieldManager: "serverTestOpts.kubeClientSet", -// }) -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(*deploy.Spec.Replicas).To(Equal(int32(0))) - -// // ensure no running maestro server pods -// Eventually(func() error { -// pods, err := serverTestOpts.kubeClientSet.CoreV1().Pods(serverTestOpts.serverNamespace).List(ctx, metav1.ListOptions{ -// LabelSelector: "app=maestro", -// }) -// if err != nil { -// return err -// } -// if len(pods.Items) > 0 { -// return fmt.Errorf("maestro server pods still running") -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - -// // patch maestro server replicas to maestroServerReplicas -// deploy, err = serverTestOpts.kubeClientSet.AppsV1().Deployments(serverTestOpts.serverNamespace).Patch(ctx, "maestro", types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"replicas":%d}}`, maestroServerReplicas)), metav1.PatchOptions{ -// FieldManager: "serverTestOpts.kubeClientSet", -// }) -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(*deploy.Spec.Replicas).To(Equal(int32(maestroServerReplicas))) - -// // ensure maestro server pod is up and running -// Eventually(func() error { -// pods, err := serverTestOpts.kubeClientSet.CoreV1().Pods(serverTestOpts.serverNamespace).List(ctx, metav1.ListOptions{ -// LabelSelector: "app=maestro", -// }) -// if err != nil { -// return err -// } -// if len(pods.Items) != maestroServerReplicas { -// return fmt.Errorf("unexpected maestro server pod count, expected %d, got %d", maestroServerReplicas, len(pods.Items)) -// } -// for _, pod := range pods.Items { -// if pod.Status.Phase != "Running" { -// return fmt.Errorf("maestro server pod not in running state") -// } -// if pod.Status.ContainerStatuses[0].State.Running == nil { -// return fmt.Errorf("maestro server container not in running state") -// } -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("rollout the mqtt-broker", func() { -// deploy, err := serverTestOpts.kubeClientSet.AppsV1().Deployments(serverTestOpts.serverNamespace).Get(ctx, "maestro-mqtt", metav1.GetOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// mqttReplicas = int(*deploy.Spec.Replicas) -// deploy, err = serverTestOpts.kubeClientSet.AppsV1().Deployments(serverTestOpts.serverNamespace).Patch(ctx, "maestro-mqtt", types.MergePatchType, []byte(`{"spec":{"replicas":0}}`), metav1.PatchOptions{ -// FieldManager: "serverTestOpts.kubeClientSet", -// }) -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(*deploy.Spec.Replicas).To(Equal(int32(0))) - -// // ensure no running mqtt-broker pods -// Eventually(func() error { -// pods, err := serverTestOpts.kubeClientSet.CoreV1().Pods(serverTestOpts.serverNamespace).List(ctx, metav1.ListOptions{ -// LabelSelector: "name=maestro-mqtt", -// }) -// if err != nil { -// return err -// } -// if len(pods.Items) > 0 { -// return fmt.Errorf("maestro-mqtt pods still running") -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - -// // patch mqtt-broker replicas to mqttReplicas -// deploy, err = serverTestOpts.kubeClientSet.AppsV1().Deployments(serverTestOpts.serverNamespace).Patch(ctx, "maestro-mqtt", types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"replicas":%d}}`, mqttReplicas)), metav1.PatchOptions{ -// FieldManager: "serverTestOpts.kubeClientSet", -// }) -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(*deploy.Spec.Replicas).To(Equal(int32(mqttReplicas))) - -// // ensure mqtt-broker pod is up and running -// Eventually(func() error { -// pods, err := serverTestOpts.kubeClientSet.CoreV1().Pods(serverTestOpts.serverNamespace).List(ctx, metav1.ListOptions{ -// LabelSelector: "name=maestro-mqtt", -// }) -// if err != nil { -// return err -// } -// if len(pods.Items) != mqttReplicas { -// return fmt.Errorf("unexpected maestro-mqtt pod count, expected %d, got %d", mqttReplicas, len(pods.Items)) -// } -// for _, pod := range pods.Items { -// if pod.Status.Phase != "Running" { -// return fmt.Errorf("maestro-mqtt pod not in running state") -// } -// if pod.Status.ContainerStatuses[0].State.Running == nil { -// return fmt.Errorf("maestro-mqtt container not in running state") -// } -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("patch the nginx A resource", func() { -// newRes := helper.NewAPIResource(agentTestOpts.consumerName, deployA, 2) -// Eventually(func() error { -// patchedResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdPatch(ctx, *resourceA.Id). -// ResourcePatchRequest(openapi.ResourcePatchRequest{Version: resourceA.Version, Manifest: newRes.Manifest}).Execute() -// if err != nil { -// return err -// } -// if resp.StatusCode != http.StatusOK { -// return fmt.Errorf("unexpected status code, expected 200, got %d", resp.StatusCode) -// } -// if *patchedResource.Version != *resourceA.Version+1 { -// return fmt.Errorf("unexpected version, expected %d, got %d", *resourceA.Version+1, *patchedResource.Version) -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("ensure the nginx A resource is not updated", func() { -// Consistently(func() error { -// deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployA, metav1.GetOptions{}) -// if err != nil { -// return nil -// } -// if *deploy.Spec.Replicas != 1 { -// return fmt.Errorf("unexpected replicas for nginx A deployment %s, expected 1, got %d", deployA, *deploy.Spec.Replicas) -// } -// return nil -// }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("delete the nginx B resource", func() { -// resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resourceB.Id).Execute() -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) -// }) - -// It("ensure the nginx B resource is not deleted", func() { -// Consistently(func() error { -// _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployB, metav1.GetOptions{}) -// if err != nil { -// if errors.IsNotFound(err) { -// return fmt.Errorf("nginx B deployment %s is deleted", deployB) -// } -// } -// return nil -// }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("post the nginx C resource to the maestro api", func() { -// res := helper.NewAPIResource(agentTestOpts.consumerName, deployC, 1) -// var resp *http.Response -// var err error -// resourceC, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(resp.StatusCode).To(Equal(http.StatusCreated)) -// Expect(*resourceC.Id).ShouldNot(BeEmpty()) -// }) - -// It("ensure the nginx C resource is not created", func() { -// Consistently(func() error { -// _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployC, metav1.GetOptions{}) -// if err == nil { -// return fmt.Errorf("nginx C deployment %s is created", deployC) -// } -// return nil -// }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("recreate the mqtt-broker service for agent", func() { -// mqttAgentService := &corev1.Service{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: "maestro-mqtt-agent", -// Namespace: serverTestOpts.serverNamespace, -// }, -// Spec: corev1.ServiceSpec{ -// Selector: map[string]string{ -// "name": "maestro-mqtt", -// }, -// Ports: []corev1.ServicePort{ -// { -// Name: "mosquitto", -// Protocol: corev1.ProtocolTCP, -// Port: 1883, -// TargetPort: intstr.FromInt(1883), -// }, -// }, -// Type: corev1.ServiceTypeClusterIP, -// }, -// } - -// _, err := serverTestOpts.kubeClientSet.CoreV1().Services(serverTestOpts.serverNamespace).Create(ctx, mqttAgentService, metav1.CreateOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// }) - -// It("recreate the grpc-broker service for agent", func() { -// grpcBrokerService := &corev1.Service{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: "maestro-grpc-broker", -// Namespace: serverTestOpts.serverNamespace, -// }, -// Spec: corev1.ServiceSpec{ -// Selector: map[string]string{ -// "app": "maestro", -// }, -// Ports: []corev1.ServicePort{ -// { -// Name: "grpc-broker", -// Protocol: corev1.ProtocolTCP, -// Port: 8091, -// TargetPort: intstr.FromInt(8091), -// }, -// }, -// Type: corev1.ServiceTypeClusterIP, -// }, -// } -// _, err := serverTestOpts.kubeClientSet.CoreV1().Services(serverTestOpts.serverNamespace).Create(ctx, grpcBrokerService, metav1.CreateOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// }) - -// It("ensure the nginx A resource is updated", func() { -// Eventually(func() error { -// deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployA, metav1.GetOptions{}) -// if err != nil { -// return err -// } -// if *deploy.Spec.Replicas != 2 { -// return fmt.Errorf("unexpected replicas for nginx A deployment %s, expected 2, got %d", deployA, *deploy.Spec.Replicas) -// } -// return nil -// }, 3*time.Minute, 3*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("ensure the nginx B resource is deleted", func() { -// Eventually(func() error { -// _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployB, metav1.GetOptions{}) -// if err != nil { -// if errors.IsNotFound(err) { -// return nil -// } -// return err -// } -// return fmt.Errorf("nginx B deployment %s still exists", deployB) -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("ensure the nginx C resource is created", func() { -// Eventually(func() error { -// deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployC, metav1.GetOptions{}) -// if err != nil { -// return err -// } -// if *deploy.Spec.Replicas != 1 { -// return fmt.Errorf("unexpected replicas for nginx C deployment %s, expected 1, got %d", deployC, *deploy.Spec.Replicas) -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("delete the nginx A and nginx C resources", func() { -// resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resourceA.Id).Execute() -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) - -// Eventually(func() error { -// _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployA, metav1.GetOptions{}) -// if err != nil { -// if errors.IsNotFound(err) { -// return nil -// } -// return err -// } -// return fmt.Errorf("nginx A deployment %s still exists", deployA) -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - -// resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resourceC.Id).Execute() -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) - -// Eventually(func() error { -// _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployC, metav1.GetOptions{}) -// if err != nil { -// if errors.IsNotFound(err) { -// return nil -// } -// return err -// } -// return fmt.Errorf("nginx C deployment %s still exists", deployC) -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) -// }) -// }) -// }) diff --git a/test/e2e/pkg/status_resync_test.go b/test/e2e/pkg/status_resync_test.go index 171d3af5..cac11573 100644 --- a/test/e2e/pkg/status_resync_test.go +++ b/test/e2e/pkg/status_resync_test.go @@ -1,7 +1,6 @@ package e2e_test import ( - "encoding/json" "fmt" "net/http" "strings" @@ -9,7 +8,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/openshift-online/maestro/pkg/api/openapi" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,49 +16,48 @@ import ( ) var _ = Describe("Status Resync After Restart", Ordered, Label("e2e-tests-status-resync-restart"), func() { - Context("Resource resync resource status after maestro server restarts", func() { + Context("Resync resource status after maestro server restarts", func() { var maestroServerReplicas int - var resource *openapi.Resource - name := fmt.Sprintf("nginx-%s", rand.String(5)) - It("post the nginx resource with non-default service account to the maestro api", func() { - res := helper.NewAPIResourceWithSA(agentTestOpts.consumerName, name, name, 1) - var resp *http.Response - var err error - resource, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() + workName := fmt.Sprintf("work-%s", rand.String(5)) + deployName := fmt.Sprintf("nginx-%s", rand.String(5)) + work := helper.NewManifestWork(workName, deployName, deployName, 1) + It("create a resource with source work client", func() { + _, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{}) Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).ShouldNot(BeEmpty()) Eventually(func() error { - deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, name, metav1.GetOptions{}) + deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) if err != nil { return err } if *deploy.Spec.Replicas != 1 { - return fmt.Errorf("unexpected replicas for nginx deployment %s, expected 1, got %d", name, *deploy.Spec.Replicas) + return fmt.Errorf("unexpected replicas, expected 1, got %d", *deploy.Spec.Replicas) } return nil }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - gotResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resource.Id).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*gotResource.Id).To(Equal(*resource.Id)) - Expect(*gotResource.Version).To(Equal(*resource.Version)) - Eventually(func() error { - gotResource, _, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resource.Id).Execute() + work, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workName, metav1.GetOptions{}) if err != nil { return err } - statusJSON, err := json.Marshal(gotResource.Status) - if err != nil { - return err + if work.CreationTimestamp.Time.IsZero() { + return fmt.Errorf("work creationTimestamp is empty") } - if !strings.Contains(string(statusJSON), "error looking up service account default/nginx") { - return fmt.Errorf("unexpected status, expected error looking up service account default/nginx, got %s", string(statusJSON)) + + manifests := work.Status.ResourceStatus.Manifests + if len(manifests) > 0 && len(manifests[0].StatusFeedbacks.Values) != 0 { + feedback := manifests[0].StatusFeedbacks.Values + if feedback[0].Name == "status" { + feedbackRaw := *feedback[0].Value.JsonRaw + if strings.Contains(feedbackRaw, "error looking up service account default/nginx") { + return nil + } + } + } - return nil + + return fmt.Errorf("unexpected status, expected error looking up service account default/nginx") }, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred()) }) @@ -91,16 +88,16 @@ var _ = Describe("Status Resync After Restart", Ordered, Label("e2e-tests-status }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) - It("create serviceaccount for nginx deployment", func() { + It("create serviceaccount for deployment", func() { _, err := agentTestOpts.kubeClientSet.CoreV1().ServiceAccounts("default").Create(ctx, &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: deployName, }, }, metav1.CreateOptions{}) Expect(err).ShouldNot(HaveOccurred()) // delete the nginx deployment to tigger recreating - err = agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Delete(ctx, name, metav1.DeleteOptions{}) + err = agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Delete(ctx, deployName, metav1.DeleteOptions{}) Expect(err).ShouldNot(HaveOccurred()) }) @@ -137,31 +134,38 @@ var _ = Describe("Status Resync After Restart", Ordered, Label("e2e-tests-status It("ensure the resource status is resynced", func() { Eventually(func() error { - gotResource, _, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resource.Id).Execute() + work, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workName, metav1.GetOptions{}) if err != nil { return err } - if _, ok := gotResource.Status["ContentStatus"]; !ok { - return fmt.Errorf("unexpected status, expected contains ContentStatus, got %v", gotResource.Status) - } - statusJSON, err := json.Marshal(gotResource.Status) - if err != nil { - return err + if work.CreationTimestamp.Time.IsZero() { + return fmt.Errorf("work creationTimestamp is empty") } - if strings.Contains(string(statusJSON), "error looking up service account default/nginx") { - return fmt.Errorf("unexpected status, should not contain error looking up service account default/nginx, got %s", string(statusJSON)) + + manifests := work.Status.ResourceStatus.Manifests + if len(manifests) > 0 && len(manifests[0].StatusFeedbacks.Values) != 0 { + feedback := manifests[0].StatusFeedbacks.Values + if feedback[0].Name == "status" { + feedbackRaw := *feedback[0].Value.JsonRaw + if !strings.Contains(feedbackRaw, "error looking up service account default/nginx") { + return nil + } + } + } - return nil - }, 3*time.Minute, 3*time.Second).ShouldNot(HaveOccurred()) + + return fmt.Errorf("unexpected status") + }, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred()) }) - It("delete the nginx resource", func() { - resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resource.Id).Execute() - Expect(err).ShouldNot(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + It("delete the resource with source work client", func() { + // note: wait some time to ensure source work client is connected to the restarted maestro server + Eventually(func() error { + return sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{}) + }, 3*time.Minute, 3*time.Second).ShouldNot(HaveOccurred()) Eventually(func() error { - _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, name, metav1.GetOptions{}) + _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{}) if err != nil { if errors.IsNotFound(err) { return nil @@ -170,193 +174,26 @@ var _ = Describe("Status Resync After Restart", Ordered, Label("e2e-tests-status } return fmt.Errorf("nginx deployment still exists") }, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred()) + + err := agentTestOpts.kubeClientSet.CoreV1().ServiceAccounts("default").Delete(ctx, deployName, metav1.DeleteOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + }) + + It("check the resource deletion via maestro api", func() { + Eventually(func() error { + search := fmt.Sprintf("consumer_name = '%s'", agentTestOpts.consumerName) + gotResourceList, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected http code, got %d, expected %d", resp.StatusCode, http.StatusOK) + } + if len(gotResourceList.Items) != 0 { + return fmt.Errorf("expected no resources returned by maestro api") + } + return nil + }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) }) }) }) - -// var _ = Describe("Status Resync After Reconnect", Ordered, Label("e2e-tests-status-resync-reconnect"), func() { -// Context("Resource resync resource status after maestro server reconnects", func() { -// var mqttReplicas int -// var resource *openapi.Resource -// name := fmt.Sprintf("nginx-%s", rand.String(5)) -// It("post the nginx resource with non-default service account to the maestro api", func() { -// res := helper.NewAPIResourceWithSA(agentTestOpts.consumerName, name, name, 1) -// var resp *http.Response -// var err error -// resource, resp, err = apiClient.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(resp.StatusCode).To(Equal(http.StatusCreated)) -// Expect(*resource.Id).ShouldNot(BeEmpty()) - -// Eventually(func() error { -// deploy, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, name, metav1.GetOptions{}) -// if err != nil { -// return err -// } -// if *deploy.Spec.Replicas != 1 { -// return fmt.Errorf("unexpected replicas for nginx deployment %s, expected 1, got %d", name, *deploy.Spec.Replicas) -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - -// gotResource, resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resource.Id).Execute() -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(resp.StatusCode).To(Equal(http.StatusOK)) -// Expect(*gotResource.Id).To(Equal(*resource.Id)) -// Expect(*gotResource.Version).To(Equal(*resource.Version)) - -// Eventually(func() error { -// gotResource, _, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resource.Id).Execute() -// if err != nil { -// return err -// } -// statusJSON, err := json.Marshal(gotResource.Status) -// if err != nil { -// return err -// } -// if !strings.Contains(string(statusJSON), "error looking up service account default/nginx") { -// return fmt.Errorf("unexpected status, expected error looking up service account default/nginx, got %s", string(statusJSON)) -// } -// return nil -// }, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("delete the mqtt-broker service for server", func() { -// err := serverTestOpts.kubeClientSet.CoreV1().Services(serverTestOpts.serverNamespace).Delete(ctx, "maestro-mqtt-server", metav1.DeleteOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// }) - -// It("create serviceaccount for nginx deployment", func() { -// _, err := agentTestOpts.kubeClientSet.CoreV1().ServiceAccounts("default").Create(ctx, &corev1.ServiceAccount{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: name, -// }, -// }, metav1.CreateOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) - -// // delete the nginx deployment to tigger recreating -// err = agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Delete(ctx, name, metav1.DeleteOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// }) - -// It("Rollout the mqtt-broker", func() { -// deploy, err := serverTestOpts.kubeClientSet.AppsV1().Deployments(serverTestOpts.serverNamespace).Get(ctx, "maestro-mqtt", metav1.GetOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// mqttReplicas = int(*deploy.Spec.Replicas) -// deploy, err = serverTestOpts.kubeClientSet.AppsV1().Deployments(serverTestOpts.serverNamespace).Patch(ctx, "maestro-mqtt", types.MergePatchType, []byte(`{"spec":{"replicas":0}}`), metav1.PatchOptions{ -// FieldManager: "serverTestOpts.kubeClientSet", -// }) -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(*deploy.Spec.Replicas).To(Equal(int32(0))) - -// // ensure no running mqtt-broker pods -// Eventually(func() error { -// pods, err := serverTestOpts.kubeClientSet.CoreV1().Pods(serverTestOpts.serverNamespace).List(ctx, metav1.ListOptions{ -// LabelSelector: "name=maestro-mqtt", -// }) -// if err != nil { -// return err -// } -// if len(pods.Items) > 0 { -// return fmt.Errorf("maestro-mqtt pods still running") -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) - -// // patch mqtt-broker replicas to mqttReplicas -// deploy, err = serverTestOpts.kubeClientSet.AppsV1().Deployments(serverTestOpts.serverNamespace).Patch(ctx, "maestro-mqtt", types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"replicas":%d}}`, mqttReplicas)), metav1.PatchOptions{ -// FieldManager: "serverTestOpts.kubeClientSet", -// }) -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(*deploy.Spec.Replicas).To(Equal(int32(mqttReplicas))) - -// // ensure mqtt-broker pod is up and running -// Eventually(func() error { -// pods, err := serverTestOpts.kubeClientSet.CoreV1().Pods(serverTestOpts.serverNamespace).List(ctx, metav1.ListOptions{ -// LabelSelector: "name=maestro-mqtt", -// }) -// if err != nil { -// return err -// } -// if len(pods.Items) != mqttReplicas { -// return fmt.Errorf("unexpected maestro-mqtt pod count, expected %d, got %d", mqttReplicas, len(pods.Items)) -// } -// for _, pod := range pods.Items { -// if pod.Status.Phase != "Running" { -// return fmt.Errorf("maestro-mqtt pod not in running state") -// } -// if pod.Status.ContainerStatuses[0].State.Running == nil { -// return fmt.Errorf("maestro-mqtt container not in running state") -// } -// } -// return nil -// }, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("recreate the mqtt-broker service for server", func() { -// mqttServerService := &corev1.Service{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: "maestro-mqtt-server", -// Namespace: serverTestOpts.serverNamespace, -// }, -// Spec: corev1.ServiceSpec{ -// Selector: map[string]string{ -// "name": "maestro-mqtt", -// }, -// Ports: []corev1.ServicePort{ -// { -// Name: "mosquitto", -// Protocol: corev1.ProtocolTCP, -// Port: 1883, -// TargetPort: intstr.FromInt(1883), -// }, -// }, -// Type: corev1.ServiceTypeClusterIP, -// }, -// } - -// _, err := serverTestOpts.kubeClientSet.CoreV1().Services(serverTestOpts.serverNamespace).Create(ctx, mqttServerService, metav1.CreateOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// }) - -// It("ensure the resource status is resynced", func() { -// Eventually(func() error { -// gotResource, _, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resource.Id).Execute() -// if err != nil { -// return err -// } -// if _, ok := gotResource.Status["ContentStatus"]; !ok { -// return fmt.Errorf("unexpected status, expected contains ContentStatus, got %v", gotResource.Status) -// } -// statusJSON, err := json.Marshal(gotResource.Status) -// if err != nil { -// return err -// } -// if strings.Contains(string(statusJSON), "error looking up service account default/nginx") { -// return fmt.Errorf("unexpected status, should not contain error looking up service account default/nginx, got %s", string(statusJSON)) -// } -// return nil -// }, 3*time.Minute, 3*time.Second).ShouldNot(HaveOccurred()) -// }) - -// It("delete the nginx resource", func() { -// resp, err := apiClient.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resource.Id).Execute() -// Expect(err).ShouldNot(HaveOccurred()) -// Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) - -// Eventually(func() error { -// _, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, name, metav1.GetOptions{}) -// if err != nil { -// if errors.IsNotFound(err) { -// return nil -// } -// return err -// } -// return fmt.Errorf("nginx deployment still exists") -// }, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred()) - -// err = agentTestOpts.kubeClientSet.CoreV1().ServiceAccounts("default").Delete(ctx, name, metav1.DeleteOptions{}) -// Expect(err).ShouldNot(HaveOccurred()) -// }) -// }) -// }) diff --git a/test/factories.go b/test/factories.go index 5c34a03f..862c9f6e 100755 --- a/test/factories.go +++ b/test/factories.go @@ -11,7 +11,6 @@ import ( cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/openshift-online/maestro/pkg/api" - "github.com/openshift-online/maestro/pkg/api/openapi" "github.com/openshift-online/maestro/pkg/db" "golang.org/x/oauth2" "google.golang.org/grpc" @@ -31,7 +30,7 @@ import ( workpayload "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" ) -var testManifestJSON = ` +var manifestJSON = ` { "apiVersion": "apps/v1", "kind": "Deployment", @@ -66,119 +65,11 @@ var testManifestJSON = ` } ` -var testReadOnlyManifestJSON = ` -{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "name": "%s", - "namespace": "%s" - } -} -` - -// NewAPIResource creates an API resource with the given consumer name, deploy name, and replicas. -// It generates a deployment for nginx using the testManifestJSON template, giving it a random deploy -// name to avoid testing conflicts. -func (helper *Helper) NewAPIResource(consumerName, deployName string, replicas int) openapi.Resource { - sa := "default" // default service account - return helper.NewAPIResourceWithSA(consumerName, deployName, sa, replicas) -} - -// NewAPIResourceWithSA creates an API resource with the given consumer name, deploy name, service account, and replicas. -// It generates a nginx deployment using the testManifestJSON template, assigning a random deploy name to avoid testing conflicts. -func (helper *Helper) NewAPIResourceWithSA(consumerName, deployName, sa string, replicas int) openapi.Resource { +// NewManifestJSON creates a resource manifest in JSON format with the given deploy name and replicas. +// It generates a deployment for nginx using the manifestJSON template, assigning a random deploy name to avoid conflicts. +func (helper *Helper) NewManifestJSON(deployName, serviceAccount string, replicas int) string { namespace := "default" // default namespace - testManifest := map[string]interface{}{} - if err := json.Unmarshal([]byte(fmt.Sprintf(testManifestJSON, deployName, namespace, replicas, sa)), &testManifest); err != nil { - helper.T.Errorf("error unmarshalling manifest: %q", err) - } - - return openapi.Resource{ - ConsumerName: &consumerName, - Manifest: testManifest, - GroupResource: map[string]interface{}{ - "group": "apps", - "resource": "deployments", - }, - } -} - -// NewResourceManifestJSON creates a resource manifest in JSON format with the given deploy name and replicas. -// It generates a deployment for nginx using the testManifestJSON template, assigning a random deploy name to avoid -// testing conflicts. -func (helper *Helper) NewResourceManifestJSON(deployName string, replicas int) string { - namespace := "default" // default namespace - sa := "default" // default service account - return fmt.Sprintf(testManifestJSON, deployName, namespace, replicas, sa) -} - -// NewReadOnlyAPIResource creates an API resource with the given consumer name and deploy name. -// It generates a read-only deployment manifests for nginx using the testReadOnlyManifestJSON template, -// giving it a random deploy name to avoid testing conflicts. -func (helper *Helper) NewReadOnlyAPIResource(consumerName, deployName string) openapi.Resource { - namespace := "default" // default namespace - testManifest := map[string]interface{}{} - if err := json.Unmarshal([]byte(fmt.Sprintf(testReadOnlyManifestJSON, deployName, namespace)), &testManifest); err != nil { - helper.T.Errorf("error unmarshalling test manifest: %q", err) - } - - return openapi.Resource{ - Manifest: testManifest, - ConsumerName: &consumerName, - GroupResource: map[string]interface{}{ - "group": "apps", - "resource": "deployments", - }, - UpdateStrategy: map[string]interface{}{ - "type": "ReadOnly", - }, - } -} - -// NewReadOnlyResourceManifestJSON creates a resource with the given consumer name, deploy name, replicas, and resource version. -// It generates a deployment for nginx using the testManifestJSON template, assigning a random deploy name to avoid testing conflicts. -func (helper *Helper) NewResource(consumerName, deployName string, replicas int, resourceVersion int32) *api.Resource { - testResource := helper.NewAPIResource(consumerName, deployName, replicas) - testPayload, err := api.EncodeManifest(testResource.Manifest, testResource.GroupResource, testResource.DeleteOption, testResource.UpdateStrategy) - if err != nil { - helper.T.Errorf("error encoding manifest: %q", err) - } - - resource := &api.Resource{ - ConsumerName: consumerName, - Type: api.ResourceTypeSingle, - Payload: testPayload, - Version: resourceVersion, - } - - return resource -} - -// CreateResource creates a resource with the given consumer name, deploy name, and replicas. -// It generates a deployment for nginx using the testManifestJSON template, assigning a random deploy name to avoid testing conflicts. -func (helper *Helper) CreateResource(consumerName, deployName string, replicas int) *api.Resource { - resource := helper.NewResource(consumerName, deployName, replicas, 1) - resourceService := helper.Env().Services.Resources() - - res, err := resourceService.Create(context.Background(), resource) - if err != nil { - helper.T.Errorf("error creating resource: %q", err) - } - - return res -} - -// CreateResourceList generates a list of resources with the specified consumer name and count. -// Each resource gets a randomly generated deploy name for nginx deployments to avoid testing conflicts. -func (helper *Helper) CreateResourceList(consumerName string, count int) (resources []*api.Resource) { - for i := 1; i <= count; i++ { - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - resources = append(resources, helper.CreateResource(consumerName, deployName, 1)) - time.Sleep(10 * time.Millisecond) - } - - return resources + return fmt.Sprintf(manifestJSON, deployName, namespace, replicas, serviceAccount) } // EncodeManifestBundle converts resource manifest JSON into a CloudEvent JSONMap representation. @@ -204,7 +95,7 @@ func (helper *Helper) EncodeManifestBundle(manifestJSON, deployName, deployNames } source := "maestro" - // create a cloud event with the manifest as the data + // create a cloud event with the manifest bundle as the data evt := cetypes.NewEventBuilder(source, cetypes.CloudEventsType{}).NewEvent() eventPayload := &workpayload.ManifestBundle{ Manifests: []workv1.Manifest{ @@ -247,87 +138,174 @@ func (helper *Helper) EncodeManifestBundle(manifestJSON, deployName, deployNames // convert cloudevent to JSONMap manifestBundle, err := api.CloudEventToJSONMap(&evt) if err != nil { - return nil, fmt.Errorf("failed to convert cloudevent to resource manifest: %v", err) + return nil, fmt.Errorf("failed to convert cloudevent to resource manifest bundle: %v", err) } return manifestBundle, nil } -// NewResourceBundle creates a resource bundle with the given consumer name, deploy name, replicas, and resource version. -func (helper *Helper) NewResourceBundle(consumerName, deployName string, replicas int, resourceVersion int32) *api.Resource { +// NewResource creates a resource with the given consumer name, deploy name, replicas, and resource version. +func (helper *Helper) NewResource(consumerName, deployName, serviceAccount string, replicas int, resourceVersion int32) (*api.Resource, error) { namespace := "default" // default namespace - manifestJSON := helper.NewResourceManifestJSON(deployName, replicas) + manifestJSON := helper.NewManifestJSON(deployName, serviceAccount, replicas) payload, err := helper.EncodeManifestBundle(manifestJSON, deployName, namespace) if err != nil { - helper.T.Errorf("error encoding manifest bundle: %q", err) + return nil, err } resource := &api.Resource{ ConsumerName: consumerName, - Type: api.ResourceTypeBundle, Payload: payload, Version: resourceVersion, } - return resource + return resource, nil } -// CreateResourceBundle creates a resource bundle with the given consumer name, deploy name and replicas. -// It generates a deployment for nginx using the testManifestJSON template, assigning a random deploy name to avoid testing conflicts. -func (helper *Helper) CreateResourceBundle(consumerName, deployName string, replicas int) *api.Resource { - resourceBundle := helper.NewResourceBundle(consumerName, deployName, replicas, 1) +// CreateResource creates a resource with the given consumer name, deploy name and replicas. +// It generates a deployment for nginx using the manifestJSON template, assigning a random deploy name to avoid conflicts. +func (helper *Helper) CreateResource(consumerName, deployName, serviceAccount string, replicas int) (*api.Resource, error) { + resource, err := helper.NewResource(consumerName, deployName, serviceAccount, replicas, 1) + if err != nil { + return nil, err + } resourceService := helper.Env().Services.Resources() + res, svcErr := resourceService.Create(context.Background(), resource) + if svcErr != nil { + return nil, svcErr.AsError() + } - res, err := resourceService.Create(context.Background(), resourceBundle) + return res, nil +} + +// UpdateResource attempts to update a resource, resource ID must not be empty. +func (helper *Helper) UpdateResource(resource *api.Resource) (*api.Resource, error) { + resourceService := helper.Env().Services.Resources() + res, err := resourceService.Update(context.Background(), resource) if err != nil { - helper.T.Errorf("error creating resource bundle: %q", err) + return nil, err.AsError() } - return res + return res, nil } -// CreateResourceBundleList generates a list of resource bundles with the specified consumer name and count. -// Each resource gets a randomly generated deploy name for nginx deployments to avoid testing conflicts. -func (helper *Helper) CreateResourceBundleList(consumerName string, count int) (resourceBundles []*api.Resource) { - for i := 1; i <= count; i++ { +// CreateResourceList generates a list of resources with the specified consumer name and count. +// Each resource gets a randomly generated deploy name for nginx deployments to avoid conflicts. +func (helper *Helper) CreateResourceList(consumerName string, count int) ([]*api.Resource, error) { + resources := make([]*api.Resource, count) + for i := 0; i < count; i++ { deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - resourceBundles = append(resourceBundles, helper.CreateResourceBundle(consumerName, deployName, 1)) + resource, err := helper.CreateResource(consumerName, deployName, "default", 1) + if err != nil { + return resources, err + } + resources[i] = resource } - return resourceBundles + return resources, nil } -func (helper *Helper) CreateConsumer(name string) *api.Consumer { +// DeleteResource attempts to delete a resource and returns an error if it fails. +func (helper *Helper) DeleteResource(id string) error { + resourceService := helper.Env().Services.Resources() + if err := resourceService.MarkAsDeleting(context.Background(), id); err != nil { + return err.AsError() + } + + return nil +} + +// NewManifest creats a manifest with the given deploy name and replicas. +// It generates a deployment for nginx using the manifestJSON template, assigning random +// deploy name to avoid conflicts. +func (helper *Helper) NewManifest(deployName, serviceAccount string, replicas int) workv1.Manifest { + manifestJSON := helper.NewManifestJSON(deployName, serviceAccount, replicas) + return workv1.Manifest{ + RawExtension: runtime.RawExtension{ + Raw: []byte(manifestJSON), + }, + } +} + +// NewManifestWork creates a manifestwork with the given manifestwork name, deploy name and replicas. +// It generates a deployment for nginx using the manifestJSON template, assigning random manifestwork name +// and deploy name to avoid conflicts. +func (helper *Helper) NewManifestWork(workName, deployName, serviceAccount string, replicas int) *workv1.ManifestWork { + manifest := helper.NewManifest(deployName, serviceAccount, replicas) + return &workv1.ManifestWork{ + ObjectMeta: metav1.ObjectMeta{ + Name: workName, + }, + Spec: workv1.ManifestWorkSpec{ + Workload: workv1.ManifestsTemplate{ + Manifests: []workv1.Manifest{manifest}, + }, + ManifestConfigs: []workv1.ManifestConfigOption{ + { + ResourceIdentifier: workv1.ResourceIdentifier{ + Group: "apps", + Resource: "deployments", + Name: deployName, + Namespace: "default", + }, + FeedbackRules: []workv1.FeedbackRule{ + { + Type: workv1.JSONPathsType, + JsonPaths: []workv1.JsonPath{ + { + Name: "status", + Path: ".status", + }, + }, + }, + }, + UpdateStrategy: &workv1.UpdateStrategy{ + Type: workv1.UpdateStrategyTypeServerSideApply, + }, + }, + }, + }, + } + +} + +func (helper *Helper) CreateConsumer(name string) (*api.Consumer, error) { return helper.CreateConsumerWithLabels(name, nil) } -func (helper *Helper) CreateConsumerWithLabels(name string, labels map[string]string) *api.Consumer { +func (helper *Helper) CreateConsumerWithLabels(name string, labels map[string]string) (*api.Consumer, error) { consumerService := helper.Env().Services.Consumers() consumer, err := consumerService.Create(context.Background(), &api.Consumer{Name: name, Labels: db.EmptyMapToNilStringMap(&labels)}) if err != nil { - helper.T.Errorf("error creating consumer: %q", err) + return nil, err } - return consumer + + return consumer, nil } -func (helper *Helper) CreateConsumerList(count int) (consumers []*api.Consumer) { - for i := 1; i <= count; i++ { - consumers = append(consumers, helper.CreateConsumer(fmt.Sprintf("consumer-%d", i))) +func (helper *Helper) CreateConsumerList(count int) ([]*api.Consumer, error) { + consumers := make([]*api.Consumer, count) + for i := 0; i < count; i++ { + consumer, err := helper.CreateConsumer(fmt.Sprintf("consumer-%d", i)) + if err != nil { + return consumers, err + } + consumers[i] = consumer } - return consumers + return consumers, nil } // NewEvent creates a CloudEvent with the given source, action, consumer name, resource ID, deployment name, resource version, and replicas. -// It generates a nginx deployment using the testManifestJSON template, assigning a random deploy name to avoid testing conflicts. +// It generates a nginx deployment using the manifestJSON template, assigning a random deploy name to avoid conflicts. // If the action is "delete_request," the event includes a deletion timestamp. -func (helper *Helper) NewEvent(source, action, consumerName, resourceID, deployName string, resourceVersion int64, replicas int) *cloudevents.Event { +func (helper *Helper) NewEvent(source, action, consumerName, resourceID, deployName string, resourceVersion int64, replicas int) (*cloudevents.Event, error) { sa := "default" // default service account deployNamespace := "default" // default namespace - testManifest := map[string]interface{}{} - if err := json.Unmarshal([]byte(fmt.Sprintf(testManifestJSON, deployName, deployNamespace, replicas, sa)), &testManifest); err != nil { - helper.T.Errorf("error unmarshalling manifest: %q", err) + manifest := map[string]interface{}{} + if err := json.Unmarshal([]byte(fmt.Sprintf(manifestJSON, deployName, deployNamespace, replicas, sa)), &manifest); err != nil { + return nil, err } eventType := cetypes.CloudEventsType{ @@ -352,14 +330,14 @@ func (helper *Helper) NewEvent(source, action, consumerName, resourceID, deployN // if action is delete_request, no data is needed if action == "delete_request" { evt.SetData(cloudevents.ApplicationJSON, nil) - return &evt + return &evt, nil } eventPayload := &workpayload.ManifestBundle{ Manifests: []workv1.Manifest{ { RawExtension: runtime.RawExtension{ - Object: &unstructured.Unstructured{Object: testManifest}, + Object: &unstructured.Unstructured{Object: manifest}, }, }, }, @@ -393,10 +371,10 @@ func (helper *Helper) NewEvent(source, action, consumerName, resourceID, deployN } if err := evt.SetData(cloudevents.ApplicationJSON, eventPayload); err != nil { - helper.T.Errorf("failed to set cloud event data: %q", err) + return nil, err } - return &evt + return &evt, nil } func (helper *Helper) CreateGRPCAuthRule(ctx context.Context, kubeClient kubernetes.Interface, ruleName, resourceType, resourceID string, actions []string) error { diff --git a/test/grpc_codec.go b/test/grpc_codec.go deleted file mode 100644 index a38e11e2..00000000 --- a/test/grpc_codec.go +++ /dev/null @@ -1,135 +0,0 @@ -package test - -import ( - "encoding/json" - "fmt" - - cloudevents "github.com/cloudevents/sdk-go/v2" - cloudeventstypes "github.com/cloudevents/sdk-go/v2/types" - "github.com/openshift-online/maestro/pkg/api" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - workv1 "open-cluster-management.io/api/work/v1" - "open-cluster-management.io/sdk-go/pkg/cloudevents/generic" - "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" - "open-cluster-management.io/sdk-go/pkg/cloudevents/work/common" - "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" -) - -type ResourceCodec struct{} - -var _ generic.Codec[*api.Resource] = &ResourceCodec{} - -func (c *ResourceCodec) EventDataType() types.CloudEventsDataType { - return payload.ManifestBundleEventDataType -} - -// encode the kubernetes resource to a cloudevent format -func (c *ResourceCodec) Encode(source string, eventType types.CloudEventsType, resource *api.Resource) (*cloudevents.Event, error) { - if eventType.CloudEventsDataType != payload.ManifestBundleEventDataType { - return nil, fmt.Errorf("unsupported cloudevents data type %s", eventType.CloudEventsDataType) - } - - eventBuilder := types.NewEventBuilder(source, eventType). - WithResourceID(resource.ID). - WithResourceVersion(int64(resource.Version)). - WithClusterName(resource.ConsumerName) - - if !resource.GetDeletionTimestamp().IsZero() { - evt := eventBuilder.WithDeletionTimestamp(resource.GetDeletionTimestamp().Time).NewEvent() - return &evt, nil - } - - manifest, _, _, _, err := api.DecodeManifest(resource.Payload) - if err != nil { - return nil, fmt.Errorf("failed to decode manifest: %v", err) - } - - evt := eventBuilder.NewEvent() - - manifests := &payload.ManifestBundle{ - Manifests: []workv1.Manifest{ - { - RawExtension: runtime.RawExtension{ - Object: &unstructured.Unstructured{Object: manifest}, - }, - }, - }, - DeleteOption: &workv1.DeleteOption{ - PropagationPolicy: workv1.DeletePropagationPolicyTypeForeground, - }, - ManifestConfigs: []workv1.ManifestConfigOption{}, - } - if err := evt.SetData(cloudevents.ApplicationJSON, manifests); err != nil { - return nil, fmt.Errorf("failed to encode resource bundle to a cloudevent: %v", err) - } - - return &evt, nil -} - -func (c *ResourceCodec) Decode(evt *cloudevents.Event) (*api.Resource, error) { - eventType, err := types.ParseCloudEventsType(evt.Type()) - if err != nil { - return nil, fmt.Errorf("failed to parse cloud event type %s, %v", evt.Type(), err) - } - - if eventType.CloudEventsDataType != payload.ManifestBundleEventDataType { - return nil, fmt.Errorf("unsupported cloudevents data type %s", eventType.CloudEventsDataType) - } - - evtExtensions := evt.Context.GetExtensions() - - resourceID, err := cloudeventstypes.ToString(evtExtensions[types.ExtensionResourceID]) - if err != nil { - return nil, fmt.Errorf("failed to get resourceid extension: %v", err) - } - - resourceVersion, err := cloudeventstypes.ToInteger(evtExtensions[types.ExtensionResourceVersion]) - if err != nil { - return nil, fmt.Errorf("failed to get resourceversion extension: %v", err) - } - - clusterName, err := cloudeventstypes.ToString(evtExtensions[types.ExtensionClusterName]) - if err != nil { - return nil, fmt.Errorf("failed to get clustername extension: %v", err) - } - - manifestStatus := &payload.ManifestBundleStatus{} - if err := evt.DataAs(manifestStatus); err != nil { - return nil, fmt.Errorf("failed to unmarshal event data %s, %v", string(evt.Data()), err) - } - - resource := &api.Resource{ - Meta: api.Meta{ - ID: resourceID, - }, - Version: resourceVersion, - ConsumerName: clusterName, - } - - resourceStatus := &api.ResourceStatus{ - ReconcileStatus: &api.ReconcileStatus{ - ObservedVersion: resourceVersion, - }, - } - - if len(manifestStatus.ResourceStatus) > 0 { - resourceStatus.ReconcileStatus.Conditions = manifestStatus.ResourceStatus[0].Conditions - if meta.IsStatusConditionTrue(manifestStatus.Conditions, common.ManifestsDeleted) { - deletedCondition := meta.FindStatusCondition(manifestStatus.Conditions, common.ManifestsDeleted) - resourceStatus.ReconcileStatus.Conditions = append(resourceStatus.ReconcileStatus.Conditions, *deletedCondition) - } - } - - resourceStatusJSON, err := json.Marshal(resourceStatus) - if err != nil { - return nil, fmt.Errorf("failed to marshal resource status: %v", err) - } - err = json.Unmarshal(resourceStatusJSON, &resource.Status) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal resource status: %v", err) - } - - return resource, nil -} diff --git a/test/helper.go b/test/helper.go index 76b718c2..f6e0a55f 100755 --- a/test/helper.go +++ b/test/helper.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/openshift-online/maestro/pkg/client/cloudevents" "github.com/openshift-online/maestro/pkg/controllers" "github.com/openshift-online/maestro/pkg/dao" "github.com/openshift-online/maestro/pkg/dispatcher" @@ -327,15 +328,16 @@ func (helper *Helper) StartWorkAgent(ctx context.Context, clusterName string) { } func (helper *Helper) StartGRPCResourceSourceClient() { + source := "maestro" store := NewStore() grpcOptions := grpcoptions.NewGRPCOptions() grpcOptions.URL = fmt.Sprintf("%s:%s", helper.Env().Config.HTTPServer.Hostname, helper.Env().Config.GRPCServer.ServerBindPort) sourceClient, err := generic.NewCloudEventSourceClient[*api.Resource]( helper.Ctx, - grpcoptions.NewSourceOptions(grpcOptions, "maestro"), + grpcoptions.NewSourceOptions(grpcOptions, source), store, resourceStatusHashGetter, - &ResourceCodec{}, + cloudevents.NewCodec(source), ) if err != nil { diff --git a/test/integration/consumers_test.go b/test/integration/consumers_test.go index 284da9a1..5f0449b7 100644 --- a/test/integration/consumers_test.go +++ b/test/integration/consumers_test.go @@ -11,6 +11,7 @@ import ( "gopkg.in/resty.v1" "k8s.io/apimachinery/pkg/util/rand" + "github.com/openshift-online/maestro/pkg/api" "github.com/openshift-online/maestro/pkg/api/openapi" "github.com/openshift-online/maestro/test" ) @@ -30,7 +31,8 @@ func TestConsumerGet(t *testing.T) { Expect(err).To(HaveOccurred(), "Expected 404") Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) - consumer := h.CreateConsumerWithLabels("cluster-"+rand.String(5), map[string]string{"foo": "bar"}) + consumer, err := h.CreateConsumerWithLabels("cluster-"+rand.String(5), map[string]string{"foo": "bar"}) + Expect(err).NotTo(HaveOccurred()) found, resp, err := client.DefaultApi.ApiMaestroV1ConsumersIdGet(ctx, consumer.ID).Execute() Expect(err).NotTo(HaveOccurred()) @@ -102,7 +104,8 @@ func TestConsumerPatch(t *testing.T) { ctx := h.NewAuthenticatedContext(account) // create a consumer - consumer := h.CreateConsumer("brontosaurus") + consumer, err := h.CreateConsumer("brontosaurus") + Expect(err).NotTo(HaveOccurred()) assert := func(patched *openapi.Consumer, resp *http.Response, err error, name *string, labels *map[string]string) { Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) @@ -189,12 +192,9 @@ func TestConsumerDeleteForbidden(t *testing.T) { // attach resource to the consumer deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - res := h.NewAPIResource(*consumer.Name, deployName, 1) - resource, resp, err := client.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).To(Succeed()) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).ShouldNot(BeEmpty()) - Expect(*resource.Version).To(Equal(int32(1))) + res, err := h.CreateResource(*consumer.Name, deployName, "default", 1) + Expect(err).NotTo(HaveOccurred()) + Expect(res.ID).ShouldNot(BeEmpty()) // 403 forbid deletion resp, err = client.DefaultApi.ApiMaestroV1ConsumersIdDelete(ctx, *consumer.Id).Execute() @@ -202,9 +202,8 @@ func TestConsumerDeleteForbidden(t *testing.T) { Expect(resp.StatusCode).To(Equal(http.StatusForbidden)) // delete the resource - resp, err = client.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, *resource.Id).Execute() - Expect(err).To(Succeed()) - Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + err = h.DeleteResource(res.ID) + Expect(err).NotTo(HaveOccurred()) // still forbid deletion for the deleting resource resp, err = client.DefaultApi.ApiMaestroV1ConsumersIdDelete(ctx, *consumer.Id).Execute() @@ -225,7 +224,7 @@ func TestConsumerDeleting(t *testing.T) { consumer, resp, err := client.DefaultApi.ApiMaestroV1ConsumersPost(ctx).Consumer(openapi.Consumer{ Name: openapi.PtrString(consumerName), }).Execute() - Expect(err).To(Succeed()) + Expect(err).NotTo(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusCreated)) Expect(*consumer.Id).NotTo(BeEmpty()) consumerIdToName[*consumer.Id] = consumerName @@ -247,11 +246,9 @@ func TestConsumerDeleting(t *testing.T) { defer wg.Done() for i := 0; i < resourceNum; i++ { deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - res := h.NewAPIResource(name, deployName, 1) - resource, resp, err := client.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() + res, err := h.CreateResource(name, deployName, "default", 1) resourceChan <- &Result{ - resource: resource, - resp: resp, + resource: res, consumerName: name, consumerId: id, err: err, @@ -287,7 +284,7 @@ func TestConsumerDeleting(t *testing.T) { consumerErr := result.err search := fmt.Sprintf("consumer_name = '%s'", consumerName) - resourceList, resp, err := client.DefaultApi.ApiMaestroV1ResourcesGet(ctx).Search(search).Execute() + resourceList, resp, err := client.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(err).To(Succeed()) @@ -297,7 +294,7 @@ func TestConsumerDeleting(t *testing.T) { Expect(resourceList.Items).To(BeEmpty()) } else { // at least one resource on the consumer, the statusCode should be 403 or 500 - fmt.Printf("failed to delete consumer(%s), associated with resource(%d), statusCode: %d, err: %v \n", consumerName, len(resourceList.Items), consumerStatusCode, consumerErr) + fmt.Printf("failed to delete consumer(%s), associated with resource(%d), statusCode: %d, err: %v\n", consumerName, len(resourceList.Items), consumerStatusCode, consumerErr) Expect(resourceList.Items).NotTo(BeEmpty(), resourceList.Items) } } @@ -309,22 +306,23 @@ func TestConsumerDeleting(t *testing.T) { for i := 0; i < consumerNum*resourceNum*resourceCreatorNum; i++ { result := <-resourceChan - resourceStatusCode := result.resp.StatusCode + resource := result.resource + resourceErr := result.err resourceConsumerId := result.consumerId resourceConsumerName := result.consumerName // get the consumer consumer, resp, err := client.DefaultApi.ApiMaestroV1ConsumersIdGet(ctx, resourceConsumerId).Execute() - if resourceStatusCode == http.StatusCreated { - Expect(err).To(Succeed()) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*consumer.Id).To(Equal(resourceConsumerId)) - Expect(*consumer.Name).To(Equal(resourceConsumerName)) - } else { - fmt.Printf("failed to create resource on consumer(%s), statusCode: %d, err: %v \n", resourceConsumerName, resourceStatusCode, result.err) + if resourceErr != nil { + fmt.Printf("failed to create resource on consumer(%s): %v\n", resourceConsumerName, result.err) Expect(err.Error()).To(ContainSubstring("Not Found")) Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) + } else { + Expect(resource).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + Expect(*consumer.Id).To(Equal(resourceConsumerId)) + Expect(*consumer.Name).To(Equal(resourceConsumerName)) } } close(resourceChan) @@ -337,7 +335,8 @@ func TestConsumerPaging(t *testing.T) { ctx := h.NewAuthenticatedContext(account) // Paging - _ = h.CreateConsumerList(20) + _, err := h.CreateConsumerList(20) + Expect(err).NotTo(HaveOccurred()) list, _, err := client.DefaultApi.ApiMaestroV1ConsumersGet(ctx).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting consumer list: %v", err) @@ -357,7 +356,7 @@ func TestConsumerPaging(t *testing.T) { } type Result struct { - resource *openapi.Resource + resource *api.Resource consumerName string consumerId string resp *http.Response diff --git a/test/integration/controller_test.go b/test/integration/controller_test.go index e23ff156..918643b2 100755 --- a/test/integration/controller_test.go +++ b/test/integration/controller_test.go @@ -27,7 +27,8 @@ func TestControllerRacing(t *testing.T) { }() // start work agent so that grpc broker can work - consumer := h.CreateConsumer("cluster-" + rand.String(5)) + consumer, err := h.CreateConsumer("cluster-" + rand.String(5)) + Expect(err).NotTo(HaveOccurred()) h.StartWorkAgent(ctx, consumer.Name) eventDao := dao.NewEventDao(&h.Env().Database.SessionFactory) @@ -122,7 +123,8 @@ func TestControllerRacing(t *testing.T) { // wait for controller service starts time.Sleep(3 * time.Second) - resources := h.CreateResourceList(consumer.Name, 50) + resources, err := h.CreateResourceList(consumer.Name, 50) + Expect(err).NotTo(HaveOccurred()) // This is to check only 50 create events are processed. It waits for 5 seconds to ensure all events have been // processed by the controllers. @@ -162,7 +164,8 @@ func TestControllerReconcile(t *testing.T) { ctx, cancel := context.WithCancel(h.NewAuthenticatedContext(account)) // start work agent so that grpc broker can work - consumer := h.CreateConsumer("cluster-" + rand.String(5)) + consumer, err := h.CreateConsumer("cluster-" + rand.String(5)) + Expect(err).NotTo(HaveOccurred()) h.StartWorkAgent(ctx, consumer.Name) eventDao := dao.NewEventDao(&h.Env().Database.SessionFactory) @@ -224,7 +227,8 @@ func TestControllerReconcile(t *testing.T) { time.Sleep(time.Second) deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - resource := h.CreateResource(consumer.Name, deployName, 1) + resource, err := h.CreateResource(consumer.Name, deployName, "default", 1) + Expect(err).NotTo(HaveOccurred()) // Eventually, the event will be processed by the controller. Eventually(func() error { @@ -284,7 +288,8 @@ func TestControllerSync(t *testing.T) { ctx, cancel := context.WithCancel(h.NewAuthenticatedContext(account)) // start work agent so that grpc broker can work - consumer := h.CreateConsumer("cluster-" + rand.String(5)) + consumer, err := h.CreateConsumer("cluster-" + rand.String(5)) + Expect(err).NotTo(HaveOccurred()) h.StartWorkAgent(ctx, consumer.Name) // create two resources with resource dao diff --git a/test/integration/resource_test.go b/test/integration/resource_test.go index d0afbc06..c297dc8d 100755 --- a/test/integration/resource_test.go +++ b/test/integration/resource_test.go @@ -16,7 +16,6 @@ import ( . "github.com/onsi/gomega" prommodel "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" - "gopkg.in/resty.v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8stypes "k8s.io/apimachinery/pkg/types" @@ -28,380 +27,51 @@ import ( "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" "github.com/openshift-online/maestro/pkg/api" - "github.com/openshift-online/maestro/pkg/api/openapi" "github.com/openshift-online/maestro/pkg/dao" + "github.com/openshift-online/maestro/pkg/errors" "github.com/openshift-online/maestro/test" ) -func TestResourceGet(t *testing.T) { +func TestResourceBundleGet(t *testing.T) { h, client := test.RegisterIntegration(t) account := h.NewRandAccount() ctx := h.NewAuthenticatedContext(account) // 401 using no JWT token - _, _, err := client.DefaultApi.ApiMaestroV1ResourcesIdGet(context.Background(), "foo").Execute() + _, _, err := client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(context.Background(), "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 401 but got nil error") // GET responses per openapi spec: 200 and 404, - _, resp, err := client.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, "foo").Execute() + _, resp, err := client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, "foo").Execute() Expect(err).To(HaveOccurred(), "Expected 404") Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) - consumer := h.CreateConsumer("cluster-" + rand.String(5)) + consumer, err := h.CreateConsumer("cluster-" + rand.String(5)) + Expect(err).NotTo(HaveOccurred()) deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - resource := h.CreateResource(consumer.Name, deployName, 1) + resource, err := h.CreateResource(consumer.Name, deployName, "default", 1) + Expect(err).NotTo(HaveOccurred()) - res, resp, err := client.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, resource.ID).Execute() + res, resp, err := client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, resource.ID).Execute() Expect(err).NotTo(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) Expect(*res.Id).To(Equal(resource.ID), "found object does not match test object") - Expect(*res.Kind).To(Equal("Resource")) - Expect(*res.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resources/%s", resource.ID))) + Expect(*res.Name).To(Equal(resource.Name)) + Expect(*res.Kind).To(Equal("ResourceBundle")) + Expect(*res.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resource-bundles/%s", resource.ID))) Expect(*res.CreatedAt).To(BeTemporally("~", resource.CreatedAt)) Expect(*res.UpdatedAt).To(BeTemporally("~", resource.UpdatedAt)) Expect(*res.Version).To(Equal(resource.Version)) -} - -func TestResourcePost(t *testing.T) { - h, client := test.RegisterIntegration(t) - account := h.NewRandAccount() - ctx, cancel := context.WithCancel(h.NewAuthenticatedContext(account)) - defer func() { - cancel() - }() - - clusterName := "cluster-" + rand.String(5) - consumer := h.CreateConsumer(clusterName) - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - res := h.NewAPIResource(consumer.Name, deployName, 1) - h.StartControllerManager(ctx) - h.StartWorkAgent(ctx, consumer.Name) - clientHolder := h.WorkAgentHolder - agentWorkClient := clientHolder.ManifestWorks(consumer.Name) - // POST responses per openapi spec: 201, 400, 409, 500 - - // 201 Created - resource, resp, err := client.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") - Expect(*resource.Kind).To(Equal("Resource")) - Expect(*resource.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resources/%s", *resource.Id))) - - // 400 bad request. posting junk json is one way to trigger 400. - jwtToken := ctx.Value(openapi.ContextAccessToken) - restyResp, err := resty.R(). - SetHeader("Content-Type", "application/json"). - SetHeader("Authorization", fmt.Sprintf("Bearer %s", jwtToken)). - SetBody(`{ this is invalid }`). - Post(h.RestURL("/resources")) - - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(restyResp.StatusCode()).To(Equal(http.StatusBadRequest)) - - var work *workv1.ManifestWork - Eventually(func() error { - // ensure the work can be get by work client - work, err = agentWorkClient.Get(ctx, *resource.Id, metav1.GetOptions{}) - if err != nil { - return err - } - return nil - }, 10*time.Second, 1*time.Second).Should(Succeed()) - - Expect(work).NotTo(BeNil()) - Expect(work.Spec.Workload).NotTo(BeNil()) - Expect(len(work.Spec.Workload.Manifests)).To(Equal(1)) - manifest := map[string]interface{}{} - Expect(json.Unmarshal(work.Spec.Workload.Manifests[0].Raw, &manifest)).NotTo(HaveOccurred(), "Error unmarshalling manifest: %v", err) - Expect(manifest).To(Equal(res.Manifest)) - - statusFeedbackValue := `{"replicas":1,"availableReplicas":1,"readyReplicas":1,"updatedReplicas":1}` - newWorkStatus := workv1.ManifestWorkStatus{ - ResourceStatus: workv1.ManifestResourceStatus{ - Manifests: []workv1.ManifestCondition{ - { - Conditions: []metav1.Condition{ - { - Type: "Applied", - Status: metav1.ConditionTrue, - }, - }, - StatusFeedbacks: workv1.StatusFeedbackResult{ - Values: []workv1.FeedbackValue{ - { - Name: "status", - Value: workv1.FieldValue{ - Type: workv1.JsonRaw, - JsonRaw: &statusFeedbackValue, - }, - }, - }, - }, - }, - }, - }, - } - - // update the work status - Expect(updateWorkStatus(ctx, agentWorkClient, work, newWorkStatus)).NotTo(HaveOccurred()) - - var newRes *openapi.Resource - Eventually(func() error { - newRes, _, err = client.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resource.Id).Execute() - if err != nil { - return err - } - if newRes.Status == nil || len(newRes.Status) == 0 || - newRes.Status["ReconcileStatus"] == nil || newRes.Status["ContentStatus"] == nil { - return fmt.Errorf("resource status is empty") - } - return nil - }, 10*time.Second, 1*time.Second).Should(Succeed()) - - Expect(err).NotTo(HaveOccurred(), "Error getting resource: %v", err) - Expect(newRes.Version).To(Equal(resource.Version)) - Expect(newRes.Status["ReconcileStatus"]).NotTo(BeNil()) - reconcileStatus := newRes.Status["ReconcileStatus"].(map[string]interface{}) - observedVersion, ok := reconcileStatus["ObservedVersion"].(float64) - Expect(ok).To(BeTrue()) - Expect(int32(observedVersion)).To(Equal(*resource.Version)) - conditions := reconcileStatus["Conditions"].([]interface{}) - Expect(len(conditions)).To(Equal(1)) - condition := conditions[0].(map[string]interface{}) - Expect(condition["type"]).To(Equal("Applied")) - Expect(condition["status"]).To(Equal("True")) - - contentStatus := newRes.Status["ContentStatus"].(map[string]interface{}) - Expect(contentStatus["replicas"]).To(Equal(float64(1))) - Expect(contentStatus["availableReplicas"]).To(Equal(float64(1))) - Expect(contentStatus["readyReplicas"]).To(Equal(float64(1))) - Expect(contentStatus["updatedReplicas"]).To(Equal(float64(1))) - - if h.Broker != "grpc" { - time.Sleep(1 * time.Second) - families := getServerMetrics(t, "http://localhost:8080/metrics") - labels := []*prommodel.LabelPair{ - {Name: strPtr("source"), Value: strPtr("maestro")}, - {Name: strPtr("original_source"), Value: strPtr("none")}, - {Name: strPtr("cluster"), Value: strPtr(clusterName)}, - {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, - {Name: strPtr("subresource"), Value: strPtr(string(types.SubResourceSpec))}, - {Name: strPtr("action"), Value: strPtr("create_request")}, - } - checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 1.0) - labels = []*prommodel.LabelPair{ - {Name: strPtr("source"), Value: strPtr("maestro")}, - {Name: strPtr("cluster"), Value: strPtr(clusterName)}, - {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, - {Name: strPtr("subresource"), Value: strPtr(string(types.SubResourceSpec))}, - {Name: strPtr("action"), Value: strPtr("create_request")}, - } - checkServerCounterMetric(t, families, "cloudevents_received_total", labels, 1.0) - labels = []*prommodel.LabelPair{ - {Name: strPtr("source"), Value: strPtr(clusterName)}, - {Name: strPtr("original_source"), Value: strPtr("maestro")}, - {Name: strPtr("cluster"), Value: strPtr(clusterName)}, - {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, - {Name: strPtr("subresource"), Value: strPtr(string(types.SubResourceStatus))}, - {Name: strPtr("action"), Value: strPtr("update_request")}, - } - checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 1.0) - labels = []*prommodel.LabelPair{ - {Name: strPtr("source"), Value: strPtr(clusterName)}, - {Name: strPtr("cluster"), Value: strPtr(clusterName)}, - {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, - {Name: strPtr("subresource"), Value: strPtr(string(types.SubResourceStatus))}, - {Name: strPtr("action"), Value: strPtr("update_request")}, - } - checkServerCounterMetric(t, families, "cloudevents_received_total", labels, 1.0) + families := getServerMetrics(t, "http://localhost:8080/metrics") + labels := []*prommodel.LabelPair{ + {Name: strPtr("method"), Value: strPtr("GET")}, + {Name: strPtr("path"), Value: strPtr("/api/maestro/v1/resource-bundles/-")}, + {Name: strPtr("code"), Value: strPtr("200")}, } -} - -func TestResourcePostWithoutName(t *testing.T) { - h, client := test.RegisterIntegration(t) - account := h.NewRandAccount() - ctx, cancel := context.WithCancel(h.NewAuthenticatedContext(account)) - - clusterName := "cluster-" + rand.String(5) - consumer := h.CreateConsumer(clusterName) - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - res := h.NewAPIResource(consumer.Name, deployName, 1) - h.StartControllerManager(ctx) - resourceService := h.Env().Services.Resources() - // POST responses per openapi spec: 201, 400, 409, 500 - - // 201 Created - resource, resp, err := client.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") - Expect(*resource.Name).To(Equal(*resource.Id)) - - // 201 Created - resource, resp, err = client.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") - Expect(*resource.Name).To(Equal(*resource.Id)) - - Eventually(func() error { - newRes, err := resourceService.List(types.ListOptions{ClusterName: clusterName, CloudEventsDataType: payload.ManifestEventDataType}) - if err != nil { - return err - } - if len(newRes) != 2 { - return fmt.Errorf("should create two resources") - } - return nil - }, 10*time.Second, 1*time.Second).Should(Succeed()) - - // make sure controller manager and work agent are stopped - cancel() -} - -func TestResourcePostWithName(t *testing.T) { - h, client := test.RegisterIntegration(t) - account := h.NewRandAccount() - ctx, cancel := context.WithCancel(h.NewAuthenticatedContext(account)) - - clusterName := "cluster-" + rand.String(5) - consumer := h.CreateConsumer(clusterName) - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - res := h.NewAPIResource(consumer.Name, deployName, 1) - h.StartControllerManager(ctx) - // POST responses per openapi spec: 201, 400, 409, 500 - - // 201 Created - resourceName := "ngix" - res.Name = &resourceName - resource, resp, err := client.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(resp.StatusCode).To(Equal(http.StatusCreated)) - Expect(*resource.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") - Expect(*resource.Name).To(Equal(resourceName)) - - // 201 Created - _, _, err = client.DefaultApi.ApiMaestroV1ResourcesPost(ctx).Resource(res).Execute() - Expect(err).To(HaveOccurred()) - - // make sure controller manager and work agent are stopped - cancel() -} - -func TestResourcePatch(t *testing.T) { - h, client := test.RegisterIntegration(t) - account := h.NewRandAccount() - ctx, cancel := context.WithCancel(h.NewAuthenticatedContext(account)) - defer func() { - cancel() - }() - // use the consumer id as the consumer name - consumer := h.CreateConsumer("") - - h.StartControllerManager(ctx) - h.StartWorkAgent(ctx, consumer.ID) - clientHolder := h.WorkAgentHolder - agentWorkClient := clientHolder.ManifestWorks(consumer.ID) - - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - res := h.CreateResource(consumer.ID, deployName, 1) - Expect(res.Version).To(Equal(int32(1))) - - var work *workv1.ManifestWork - Eventually(func() error { - // ensure the work can be get by work client - var err error - work, err = agentWorkClient.Get(ctx, res.ID, metav1.GetOptions{}) - if err != nil { - return err - } - // add finalizer to the work - patchBytes, err := json.Marshal(map[string]interface{}{ - "metadata": map[string]interface{}{ - "uid": work.GetUID(), - "resourceVersion": work.GetResourceVersion(), - "finalizers": []string{"work-test-finalizer"}, - }, - }) - if err != nil { - return err - } - - _, err = agentWorkClient.Patch(ctx, work.Name, k8stypes.MergePatchType, patchBytes, metav1.PatchOptions{}) - return err - }, 20*time.Second, 2*time.Second).Should(Succeed()) - - // 200 OK - newRes := h.NewAPIResource(consumer.Name, deployName, 2) - resource, resp, err := client.DefaultApi.ApiMaestroV1ResourcesIdPatch(ctx, res.ID).ResourcePatchRequest(openapi.ResourcePatchRequest{Version: &res.Version, Manifest: newRes.Manifest}).Execute() - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*resource.Id).To(Equal(res.ID)) - Expect(*resource.CreatedAt).To(BeTemporally("~", res.CreatedAt)) - Expect(*resource.Kind).To(Equal("Resource")) - Expect(*resource.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resources/%s", *resource.Id))) - Expect(*resource.Version).To(Equal(res.Version + 1)) - Expect(resource.Manifest).To(Equal(map[string]interface{}(newRes.Manifest))) - - jwtToken := ctx.Value(openapi.ContextAccessToken) - // 500 server error. posting junk json is one way to trigger 500. - restyResp, err := resty.R(). - SetHeader("Content-Type", "application/json"). - SetHeader("Authorization", fmt.Sprintf("Bearer %s", jwtToken)). - SetBody(`{ this is invalid }`). - Patch(h.RestURL("/resources/foo")) - - Expect(err).NotTo(HaveOccurred(), "Error posting object: %v", err) - Expect(restyResp.StatusCode()).To(Equal(http.StatusBadRequest)) - - dao := dao.NewEventDao(&h.Env().Database.SessionFactory) - events, err := dao.All(ctx) - Expect(err).NotTo(HaveOccurred(), "Error getting events: %v", err) - Expect(len(events)).To(Equal(2), "expected Create and Update events") - Expect(contains(api.CreateEventType, events)).To(BeTrue()) - Expect(contains(api.UpdateEventType, events)).To(BeTrue()) - - // 409 conflict error. using an out of date resource version - _, resp, err = client.DefaultApi.ApiMaestroV1ResourcesIdPatch(ctx, res.ID).ResourcePatchRequest( - openapi.ResourcePatchRequest{Version: &res.Version, Manifest: newRes.Manifest}).Execute() - Expect(err).To(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusConflict)) - - Eventually(func() error { - // ensure the work can be get by work client - work, err = agentWorkClient.Get(ctx, *resource.Id, metav1.GetOptions{}) - if err != nil { - return err - } - - // ensure the work version is updated - if work.GetResourceVersion() != "2" { - return fmt.Errorf("unexpected work version %v", work.GetResourceVersion()) - } - - return nil - }, 10*time.Second, 1*time.Second).Should(Succeed()) - - Expect(work).NotTo(BeNil()) - Expect(work.Spec.Workload).NotTo(BeNil()) - Expect(len(work.Spec.Workload.Manifests)).To(Equal(1)) - manifest := map[string]interface{}{} - Expect(json.Unmarshal(work.Spec.Workload.Manifests[0].Raw, &manifest)).NotTo(HaveOccurred(), "Error unmarshalling manifest: %v", err) - Expect(manifest).To(Equal(newRes.Manifest)) - - // initialize resource deletion - _, err = client.DefaultApi.ApiMaestroV1ResourcesIdDelete(ctx, res.ID).Execute() - Expect(err).NotTo(HaveOccurred(), "Error deleting object: %v", err) - - // patch the deleting resource should return 409 conflict - _, resp, err = client.DefaultApi.ApiMaestroV1ResourcesIdPatch(ctx, res.ID).ResourcePatchRequest( - openapi.ResourcePatchRequest{Version: &res.Version, Manifest: newRes.Manifest}).Execute() - Expect(err).To(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusConflict)) + checkServerCounterMetric(t, families, "rest_api_inbound_request_count", labels, 1.0) } func TestResourcePaging(t *testing.T) { @@ -411,21 +81,22 @@ func TestResourcePaging(t *testing.T) { ctx := h.NewAuthenticatedContext(account) // Paging - consumer := h.CreateConsumer("cluster-" + rand.String(5)) - _ = h.CreateResourceList(consumer.Name, 20) - _ = h.CreateResourceBundleList(consumer.Name, 20) + consumer, err := h.CreateConsumer("cluster-" + rand.String(5)) + Expect(err).NotTo(HaveOccurred()) + _, err = h.CreateResourceList(consumer.Name, 20) + Expect(err).NotTo(HaveOccurred()) - list, _, err := client.DefaultApi.ApiMaestroV1ResourcesGet(ctx).Execute() + list, _, err := client.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting resource list: %v", err) - Expect(list.Kind).To(Equal("ResourceList")) + Expect(list.Kind).To(Equal("ResourceBundleList")) Expect(len(list.Items)).To(Equal(20)) Expect(list.Size).To(Equal(int32(20))) Expect(list.Total).To(Equal(int32(20))) Expect(list.Page).To(Equal(int32(1))) - list, _, err = client.DefaultApi.ApiMaestroV1ResourcesGet(ctx).Page(2).Size(5).Execute() + list, _, err = client.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Page(2).Size(5).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting resource list: %v", err) - Expect(list.Kind).To(Equal("ResourceList")) + Expect(list.Kind).To(Equal("ResourceBundleList")) Expect(len(list.Items)).To(Equal(5)) Expect(list.Size).To(Equal(int32(5))) Expect(list.Total).To(Equal(int32(20))) @@ -438,95 +109,30 @@ func TestResourceListSearch(t *testing.T) { account := h.NewRandAccount() ctx := h.NewAuthenticatedContext(account) - consumer := h.CreateConsumer("cluster-" + rand.String(5)) - resources := h.CreateResourceList(consumer.Name, 20) - - search := fmt.Sprintf("id in ('%s')", resources[0].ID) - list, _, err := client.DefaultApi.ApiMaestroV1ResourcesGet(ctx).Search(search).Execute() - Expect(list.Kind).To(Equal("ResourceList")) - Expect(err).NotTo(HaveOccurred(), "Error getting resource list: %v", err) - Expect(len(list.Items)).To(Equal(1)) - Expect(list.Total).To(Equal(int32(1))) - Expect(*list.Items[0].Id).To(Equal(resources[0].ID)) -} - -func TestResourceBundleGet(t *testing.T) { - h, client := test.RegisterIntegration(t) - - account := h.NewRandAccount() - ctx := h.NewAuthenticatedContext(account) - - // 401 using no JWT token - _, _, err := client.DefaultApi.ApiMaestroV1ResourcesIdGet(context.Background(), "foo").Execute() - Expect(err).To(HaveOccurred(), "Expected 401 but got nil error") - - // GET responses per openapi spec: 200 and 404, - _, resp, err := client.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, "foo").Execute() - Expect(err).To(HaveOccurred(), "Expected 404") - Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) - - consumer := h.CreateConsumer("cluster-" + rand.String(5)) - deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - resourceBundle := h.CreateResourceBundle(consumer.Name, deployName, 1) - - resBundle, resp, err := client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, resourceBundle.ID).Execute() + consumer, err := h.CreateConsumer("cluster-" + rand.String(5)) + Expect(err).NotTo(HaveOccurred()) + resources, err := h.CreateResourceList(consumer.Name, 20) Expect(err).NotTo(HaveOccurred()) - Expect(resp.StatusCode).To(Equal(http.StatusOK)) - - Expect(*resBundle.Id).To(Equal(resourceBundle.ID), "found object does not match test object") - Expect(*resBundle.Name).To(Equal(resourceBundle.Name)) - Expect(*resBundle.Kind).To(Equal("ResourceBundle")) - Expect(*resBundle.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resource-bundles/%s", resourceBundle.ID))) - Expect(*resBundle.CreatedAt).To(BeTemporally("~", resourceBundle.CreatedAt)) - Expect(*resBundle.UpdatedAt).To(BeTemporally("~", resourceBundle.UpdatedAt)) - Expect(*resBundle.Version).To(Equal(resourceBundle.Version)) - - families := getServerMetrics(t, "http://localhost:8080/metrics") - labels := []*prommodel.LabelPair{ - {Name: strPtr("method"), Value: strPtr("GET")}, - {Name: strPtr("path"), Value: strPtr("/api/maestro/v1/resource-bundles/-")}, - {Name: strPtr("code"), Value: strPtr("200")}, - } - checkServerCounterMetric(t, families, "rest_api_inbound_request_count", labels, 1.0) -} - -func TestResourceBundleListSearch(t *testing.T) { - h, client := test.RegisterIntegration(t) - - account := h.NewRandAccount() - ctx := h.NewAuthenticatedContext(account) - - consumer := h.CreateConsumer("cluster-" + rand.String(5)) - resourceBundles := h.CreateResourceBundleList(consumer.Name, 20) - _ = h.CreateResourceList(consumer.Name, 20) - search := fmt.Sprintf("name = '%s' and consumer_name = '%s'", resourceBundles[0].Name, consumer.Name) + search := fmt.Sprintf("name = '%s' and consumer_name = '%s'", resources[0].Name, consumer.Name) list, _, err := client.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() Expect(list.Kind).To(Equal("ResourceBundleList")) - Expect(err).NotTo(HaveOccurred(), "Error getting resource bundle list: %v", err) + Expect(err).NotTo(HaveOccurred(), "Error getting resource list: %v", err) Expect(len(list.Items)).To(Equal(1)) Expect(list.Total).To(Equal(int32(1))) - Expect(*list.Items[0].Id).To(Equal(resourceBundles[0].ID)) - Expect(*list.Items[0].Name).To(Equal(resourceBundles[0].Name)) + Expect(*list.Items[0].Id).To(Equal(resources[0].ID)) + Expect(*list.Items[0].Name).To(Equal(resources[0].Name)) search = fmt.Sprintf("consumer_name = '%s'", consumer.Name) list, _, err = client.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Search(search).Execute() Expect(list.Kind).To(Equal("ResourceBundleList")) - Expect(err).NotTo(HaveOccurred(), "Error getting resource bundle list: %v", err) + Expect(err).NotTo(HaveOccurred(), "Error getting resource list: %v", err) Expect(len(list.Items)).To(Equal(20)) Expect(list.Total).To(Equal(int32(20))) - - families := getServerMetrics(t, "http://localhost:8080/metrics") - labels := []*prommodel.LabelPair{ - {Name: strPtr("method"), Value: strPtr("GET")}, - {Name: strPtr("path"), Value: strPtr("/api/maestro/v1/resource-bundles")}, - {Name: strPtr("code"), Value: strPtr("200")}, - } - checkServerCounterMetric(t, families, "rest_api_inbound_request_count", labels, 2.0) } func TestUpdateResourceWithRacingRequests(t *testing.T) { - h, client := test.RegisterIntegration(t) + h, _ := test.RegisterIntegration(t) account := h.NewRandAccount() ctx := h.NewAuthenticatedContext(account) @@ -542,10 +148,14 @@ func TestUpdateResourceWithRacingRequests(t *testing.T) { rows.Close() time.Sleep(time.Second) - consumer := h.CreateConsumer("cluster-" + rand.String(5)) + consumer, err := h.CreateConsumer("cluster-" + rand.String(5)) + Expect(err).NotTo(HaveOccurred()) deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - res := h.CreateResource(consumer.Name, deployName, 1) - newRes := h.NewAPIResource(consumer.Name, deployName, 2) + resource, err := h.CreateResource(consumer.Name, deployName, "default", 1) + Expect(err).NotTo(HaveOccurred()) + newResource, err := h.NewResource(consumer.Name, deployName, "default", 2, resource.Version) + Expect(err).NotTo(HaveOccurred()) + newResource.ID = resource.ID // starts 20 threads to update this resource at the same time threads := 20 @@ -556,9 +166,8 @@ func TestUpdateResourceWithRacingRequests(t *testing.T) { for i := 0; i < threads; i++ { go func() { defer wg.Done() - _, resp, err := client.DefaultApi.ApiMaestroV1ResourcesIdPatch(ctx, res.ID).ResourcePatchRequest( - openapi.ResourcePatchRequest{Version: &res.Version, Manifest: newRes.Manifest}).Execute() - if err != nil && resp.StatusCode == http.StatusConflict { + _, err := h.UpdateResource(newResource) + if err != nil && strings.Contains(err.Error(), fmt.Sprintf("%d", errors.ErrorConflict)) { conflictRequests = conflictRequests + 1 } }() @@ -576,7 +185,7 @@ func TestUpdateResourceWithRacingRequests(t *testing.T) { updatedCount := 0 for _, e := range events { - if e.SourceID == res.ID && e.EventType == api.UpdateEventType { + if e.SourceID == resource.ID && e.EventType == api.UpdateEventType { updatedCount = updatedCount + 1 } } @@ -616,10 +225,12 @@ func TestResourceFromGRPC(t *testing.T) { }() // create a mock resource clusterName := "cluster-" + rand.String(5) - consumer := h.CreateConsumer(clusterName) + consumer, err := h.CreateConsumer(clusterName) + Expect(err).NotTo(HaveOccurred()) deployName := fmt.Sprintf("nginx-%s", rand.String(5)) - res := h.NewResource(consumer.Name, deployName, 1, 1) - res.ID = uuid.NewString() + resource, err := h.NewResource(consumer.Name, deployName, "default", 1, 1) + Expect(err).NotTo(HaveOccurred()) + resource.ID = uuid.NewString() h.StartControllerManager(ctx) h.StartWorkAgent(ctx, consumer.Name) @@ -628,35 +239,35 @@ func TestResourceFromGRPC(t *testing.T) { // use grpc client to create resource h.StartGRPCResourceSourceClient() - err := h.GRPCSourceClient.Publish(ctx, types.CloudEventsType{ + err = h.GRPCSourceClient.Publish(ctx, types.CloudEventsType{ CloudEventsDataType: payload.ManifestBundleEventDataType, SubResource: types.SubResourceSpec, Action: common.CreateRequestAction, - }, res) + }, resource) Expect(err).NotTo(HaveOccurred(), "Error publishing resource with grpc source client: %v", err) // for real case, the controller should have a mapping between resource (replicated) in maestro and resource (root) in kubernetes // so call subscribe method can return the resource // for testing, just list the resource via restful api. - resourceBundles, _, err := client.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Execute() + resources, _, err := client.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting object: %v", err) - Expect(resourceBundles.Items).NotTo(BeEmpty(), "Expected returned resource list is not empty") + Expect(resources.Items).NotTo(BeEmpty(), "Expected returned resource list is not empty") - resourceBundle, resp, err := client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, *resourceBundles.Items[0].Id).Execute() + res, resp, err := client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, *resources.Items[0].Id).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*resourceBundle.Id).To(Equal(res.ID)) - Expect(*resourceBundle.Kind).To(Equal("ResourceBundle")) - Expect(*resourceBundle.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resource-bundles/%s", *resourceBundle.Id))) - Expect(*resourceBundle.Version).To(Equal(int32(1))) + Expect(*res.Id).To(Equal(resource.ID)) + Expect(*res.Kind).To(Equal("ResourceBundle")) + Expect(*res.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resource-bundles/%s", *res.Id))) + Expect(*res.Version).To(Equal(int32(1))) // add the resource to the store - h.Store.Add(res) + h.Store.Add(resource) var work *workv1.ManifestWork Eventually(func() error { // ensure the work can be get by work client - work, err = agentWorkClient.Get(ctx, res.ID, metav1.GetOptions{}) + work, err = agentWorkClient.Get(ctx, resource.ID, metav1.GetOptions{}) if err != nil { return err } @@ -701,55 +312,58 @@ func TestResourceFromGRPC(t *testing.T) { Expect(updateWorkStatus(ctx, agentWorkClient, work, newWorkStatus)).NotTo(HaveOccurred()) Eventually(func() error { - newRes, err := h.Store.Get(res.ID) + foundResource, err := h.Store.Get(resource.ID) if err != nil { return err } - if newRes.Status == nil || len(newRes.Status) == 0 { + if foundResource.Status == nil || len(foundResource.Status) == 0 { return fmt.Errorf("resource status is empty") } - resourceStatusJSON, err := json.Marshal(newRes.Status) + evt, err := api.JSONMAPToCloudEvent(foundResource.Status) if err != nil { - return err + return fmt.Errorf("failed to convert jsonmap to cloudevent") } - resourceStatus := &api.ResourceStatus{} - if err := json.Unmarshal(resourceStatusJSON, resourceStatus); err != nil { - return err + + manifestStatus := &payload.ManifestBundleStatus{} + if err := evt.DataAs(manifestStatus); err != nil { + return fmt.Errorf("failed to unmarshal event payload: %v", err) } - if len(resourceStatus.ReconcileStatus.Conditions) == 0 { - return fmt.Errorf("resource status is empty") + resourceStatus := manifestStatus.ResourceStatus + if len(resourceStatus) != 1 { + return fmt.Errorf("unexpected length of resourceStatus") } - if !meta.IsStatusConditionTrue(resourceStatus.ReconcileStatus.Conditions, "Applied") { + if !meta.IsStatusConditionTrue(resourceStatus[0].Conditions, "Applied") { return fmt.Errorf("resource status is not applied") } return nil }, 10*time.Second, 1*time.Second).Should(Succeed()) - newRes := h.NewResource(consumer.Name, deployName, 2, 1) - newRes.ID = *resourceBundle.Id - newRes.Version = *resourceBundle.Version + newResource, err := h.NewResource(consumer.Name, deployName, "default", 2, 1) + Expect(err).NotTo(HaveOccurred()) + newResource.ID = *res.Id + newResource.Version = *res.Version err = h.GRPCSourceClient.Publish(ctx, types.CloudEventsType{ CloudEventsDataType: payload.ManifestBundleEventDataType, SubResource: types.SubResourceSpec, Action: common.UpdateRequestAction, - }, newRes) + }, newResource) Expect(err).NotTo(HaveOccurred(), "Error publishing resource with grpc source client: %v", err) - resourceBundle, resp, err = client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, newRes.ID).Execute() + res, resp, err = client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, newResource.ID).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*resourceBundle.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") - Expect(*resourceBundle.Kind).To(Equal("ResourceBundle")) - Expect(*resourceBundle.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resource-bundles/%s", *resourceBundle.Id))) - Expect(*resourceBundle.Version).To(Equal(int32(2))) + Expect(*res.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") + Expect(*res.Kind).To(Equal("ResourceBundle")) + Expect(*res.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resource-bundles/%s", *res.Id))) + Expect(*res.Version).To(Equal(int32(2))) Eventually(func() error { // ensure the work can be get by work client - work, err = agentWorkClient.Get(ctx, *resourceBundle.Id, metav1.GetOptions{}) + work, err = agentWorkClient.Get(ctx, *res.Id, metav1.GetOptions{}) if err != nil { return err } @@ -771,12 +385,12 @@ func TestResourceFromGRPC(t *testing.T) { CloudEventsDataType: payload.ManifestBundleEventDataType, SubResource: types.SubResourceSpec, Action: common.DeleteRequestAction, - }, newRes) + }, newResource) Expect(err).NotTo(HaveOccurred(), "Error publishing resource with grpc source client: %v", err) Eventually(func() error { // ensure the work can be get by work client - work, err = agentWorkClient.Get(ctx, newRes.ID, metav1.GetOptions{}) + work, err = agentWorkClient.Get(ctx, newResource.ID, metav1.GetOptions{}) if err != nil { return err } @@ -800,9 +414,9 @@ func TestResourceFromGRPC(t *testing.T) { Expect(updateWorkStatus(ctx, agentWorkClient, work, deletingWorkStatus)).NotTo(HaveOccurred()) Eventually(func() error { - resourceBundle, _, err = client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, newRes.ID).Execute() - if resourceBundle != nil { - return fmt.Errorf("resource %s is not deleted", newRes.ID) + res, _, err = client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, newResource.ID).Execute() + if res != nil { + return fmt.Errorf("resource %s is not deleted", newResource.ID) } return nil }, 10*time.Second, 1*time.Second).Should(Succeed()) @@ -842,39 +456,37 @@ func TestResourceFromGRPC(t *testing.T) { if h.Broker != "grpc" { labels = []*prommodel.LabelPair{ {Name: strPtr("source"), Value: strPtr("maestro")}, - {Name: strPtr("original_source"), Value: strPtr("none")}, {Name: strPtr("cluster"), Value: strPtr(clusterName)}, {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, {Name: strPtr("subresource"), Value: strPtr(string(types.SubResourceSpec))}, {Name: strPtr("action"), Value: strPtr("create_request")}, } - checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 2.0) + checkServerCounterMetric(t, families, "cloudevents_received_total", labels, 1.0) labels = []*prommodel.LabelPair{ {Name: strPtr("source"), Value: strPtr("maestro")}, - {Name: strPtr("original_source"), Value: strPtr("none")}, {Name: strPtr("cluster"), Value: strPtr(clusterName)}, {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, {Name: strPtr("subresource"), Value: strPtr(string(types.SubResourceSpec))}, {Name: strPtr("action"), Value: strPtr("update_request")}, } - checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 2.0) + checkServerCounterMetric(t, families, "cloudevents_received_total", labels, 1.0) labels = []*prommodel.LabelPair{ {Name: strPtr("source"), Value: strPtr("maestro")}, - {Name: strPtr("original_source"), Value: strPtr("none")}, {Name: strPtr("cluster"), Value: strPtr(clusterName)}, {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, {Name: strPtr("subresource"), Value: strPtr(string(types.SubResourceSpec))}, {Name: strPtr("action"), Value: strPtr("delete_request")}, } - checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 2.0) + checkServerCounterMetric(t, families, "cloudevents_received_total", labels, 1.0) labels = []*prommodel.LabelPair{ {Name: strPtr("source"), Value: strPtr(clusterName)}, + {Name: strPtr("original_source"), Value: strPtr("maestro")}, {Name: strPtr("cluster"), Value: strPtr(clusterName)}, {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, {Name: strPtr("subresource"), Value: strPtr(string(types.SubResourceStatus))}, {Name: strPtr("action"), Value: strPtr("update_request")}, } - checkServerCounterMetric(t, families, "cloudevents_received_total", labels, 3.0) + checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 2.0) } } @@ -906,15 +518,6 @@ func updateWorkStatus(ctx context.Context, workClient workv1client.ManifestWorkI return nil } -func contains(et api.EventType, events api.EventList) bool { - for _, e := range events { - if e.EventType == et { - return true - } - } - return false -} - func getServerMetrics(t *testing.T, url string) map[string]*prommodel.MetricFamily { // gather metrics from metrics server from url /metrics resp, err := http.Get(url) diff --git a/test/integration/status_dispatcher_test.go b/test/integration/status_dispatcher_test.go index 2c3d1236..4937b61a 100644 --- a/test/integration/status_dispatcher_test.go +++ b/test/integration/status_dispatcher_test.go @@ -29,8 +29,10 @@ func TestStatusDispatcher(t *testing.T) { // create 2 consumers consumer1 := "xyzzy" consumer2 := "thud" - _ = h.CreateConsumer(consumer1) - _ = h.CreateConsumer(consumer2) + _, err := h.CreateConsumer(consumer1) + Expect(err).NotTo(HaveOccurred()) + _, err = h.CreateConsumer(consumer2) + Expect(err).NotTo(HaveOccurred()) // should dispatch to all consumers for current instance Eventually(func() bool { @@ -40,7 +42,7 @@ func TestStatusDispatcher(t *testing.T) { // insert a new instance and healthcheck server will mark it as ready and then add it to the hash ring instanceDao := dao.NewInstanceDao(&h.Env().Database.SessionFactory) - _, err := instanceDao.Create(ctx, &api.ServerInstance{ + _, err = instanceDao.Create(ctx, &api.ServerInstance{ Meta: api.Meta{ ID: "instance1", },