From 1578c07cefb02bd02f2e6213630ffd2a93776f8e Mon Sep 17 00:00:00 2001 From: Marcin Szwed Date: Fri, 28 Jun 2024 12:48:56 +0200 Subject: [PATCH] Handle Service Instances in SM client (#742) * rm internal/ from gitignore * Add Operation struct * Add ServiceInstance struct * ServiceInstances path const * Change the field name in collections of SM objects to Items * GET Service Instances * GET Service Instance by ID * Add SI, Error structs for SM responses * POST Service Instance * Refactor http response body reading * DELETE Service Instance * PATCH Service Instance * GET Service Instance params * Add fake SI JSONs for tests * GET Service Instances test * GET Service Instance by ID test * POST Service Instance test * Refactor SM client unit test to use typed structs instead of generic maps * PATCH Service Instance test * DELETE Service Instance by ID test --- .gitignore | 1 - internal/api/vm/converters.go | 6 +- internal/service-manager/client.go | 222 ++++++- internal/service-manager/client_test.go | 583 +++++++++++++++--- .../testdata/service_instances.json | 221 +++++++ internal/service-manager/types/error.go | 39 ++ internal/service-manager/types/operation.go | 42 ++ .../service-manager/types/service_instance.go | 35 ++ .../service-manager/types/service_offering.go | 2 +- .../service-manager/types/service_plan.go | 2 +- 10 files changed, 1053 insertions(+), 100 deletions(-) create mode 100644 internal/service-manager/testdata/service_instances.json create mode 100644 internal/service-manager/types/error.go create mode 100644 internal/service-manager/types/operation.go create mode 100644 internal/service-manager/types/service_instance.go diff --git a/.gitignore b/.gitignore index c68d56062..dbff347d9 100644 --- a/.gitignore +++ b/.gitignore @@ -221,7 +221,6 @@ scripts/run.bat scripts/**/*.js scripts/**/*.js.map coverage/ -internal/ **/.DS_Store .settings **/.vs diff --git a/internal/api/vm/converters.go b/internal/api/vm/converters.go index 4dd4536b5..00f232d5b 100644 --- a/internal/api/vm/converters.go +++ b/internal/api/vm/converters.go @@ -22,11 +22,11 @@ func ToSecretVM(list v1.SecretList) Secrets { func ToServiceOfferingsVM(offerings *types.ServiceOfferings) ServiceOfferings { toReturn := ServiceOfferings{ - NumItems: len(offerings.ServiceOfferings), + NumItems: len(offerings.Items), Items: []ServiceOffering{}, } - for _, offering := range offerings.ServiceOfferings { + for _, offering := range offerings.Items { imageUrl, _ := offering.MetadataValueByFieldName(types.ServiceOfferingImageUrl) displayName, _ := offering.MetadataValueByFieldName(types.ServiceOfferingDisplayName) supportUrl, _ := offering.MetadataValueByFieldName(types.ServiceOfferingSupportURL) @@ -55,7 +55,7 @@ func ToServiceOfferingDetailsVM(details *types.ServiceOfferingDetails) ServiceOf toReturn.LongDescription, _ = details.MetadataValueByFieldName(types.ServiceOfferingLongDescription) - for _, plan := range details.ServicePlans.ServicePlans { + for _, plan := range details.ServicePlans.Items { toReturn.Plans = append(toReturn.Plans, ServiceOfferingPlan{ Name: plan.Name, Description: plan.Description, diff --git a/internal/service-manager/client.go b/internal/service-manager/client.go index 5b96ab2ef..76d4879aa 100644 --- a/internal/service-manager/client.go +++ b/internal/service-manager/client.go @@ -1,6 +1,7 @@ package servicemanager import ( + "bytes" "context" "fmt" "io" @@ -19,11 +20,13 @@ import ( ) const ( - componentName = "ServiceManagerClient" - defaultSecret = "sap-btp-service-operator" - defaultNamespace = "kyma-system" + componentName = "ServiceManagerClient" + defaultSecret = "sap-btp-service-operator" + defaultNamespace = "kyma-system" + ServiceOfferingsPath = "/v1/service_offerings" ServicePlansPath = "/v1/service_plans" + ServiceInstancesPath = "/v1/service_instances" // see https://help.sap.com/docs/service-manager/sap-service-manager/filtering-parameters-and-operators URLFieldQueryKey = "fieldQuery" @@ -152,8 +155,7 @@ func (c *Client) ServiceOfferings() (*types.ServiceOfferings, error) { return nil, err } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := c.readResponseBody(resp.Body) if err != nil { return nil, err } @@ -195,8 +197,7 @@ func (c *Client) serviceOfferingByID(serviceOfferingID string) (*types.ServiceOf return nil, err } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := c.readResponseBody(resp.Body) if err != nil { return nil, err } @@ -224,8 +225,7 @@ func (c *Client) servicePlansForServiceOffering(serviceOfferingID string) (*type return nil, err } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := c.readResponseBody(resp.Body) if err != nil { return nil, err } @@ -237,3 +237,207 @@ func (c *Client) servicePlansForServiceOffering(serviceOfferingID string) (*type return &plans, nil } + +func (c *Client) ServiceInstances() (*types.ServiceInstances, error) { + req, err := http.NewRequest(http.MethodGet, c.smURL+ServiceInstancesPath, nil) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + + body, err := c.readResponseBody(resp.Body) + if err != nil { + return nil, err + } + var serviceInstances types.ServiceInstances + if err := json.Unmarshal(body, &serviceInstances); err != nil { + return nil, err + } + + return &serviceInstances, nil +} + +func (c *Client) ServiceInstance(serviceInstanceID string) (*types.ServiceInstance, error) { + req, err := http.NewRequest(http.MethodGet, c.smURL+ServiceInstancesPath+"/"+serviceInstanceID, nil) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + + switch resp.StatusCode { + case http.StatusOK: + return c.serviceInstanceResponse(resp) + default: + return nil, c.errorResponse(resp) + } + +} + +func (c *Client) ServiceInstanceParameters(serviceInstanceID string) (map[string]string, error) { + req, err := http.NewRequest(http.MethodGet, c.smURL+ServiceInstancesPath+"/"+serviceInstanceID+"/parameters", nil) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + + switch resp.StatusCode { + case http.StatusOK: + return c.serviceInstanceParamsResponse(resp) + default: + return nil, c.errorResponse(resp) + } +} + +func (c *Client) CreateServiceInstance(si *types.ServiceInstance) (*types.ServiceInstance, error) { + requestBody, err := json.Marshal(si) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, c.smURL+ServiceInstancesPath, bytes.NewBuffer(requestBody)) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + + switch resp.StatusCode { + case http.StatusCreated: + return c.serviceInstanceResponse(resp) + case http.StatusAccepted: + return nil, nil + default: + return nil, c.errorResponse(resp) + } +} + +func (c *Client) DeleteServiceInstance(serviceInstanceID string) error { + req, err := http.NewRequest(http.MethodDelete, c.smURL+ServiceInstancesPath+"/"+serviceInstanceID, nil) + if err != nil { + return err + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return err + } + + switch resp.StatusCode { + case http.StatusOK: + fallthrough + case http.StatusAccepted: + return nil + default: + return c.errorResponse(resp) + } +} + +func (c *Client) serviceInstanceResponse(resp *http.Response) (*types.ServiceInstance, error) { + body, err := c.readResponseBody(resp.Body) + if err != nil { + return nil, err + } + + var siResp types.ServiceInstance + if err := json.Unmarshal(body, &siResp); err != nil { + return nil, err + } + + return &siResp, nil +} + +func (c *Client) serviceInstanceParamsResponse(resp *http.Response) (map[string]string, error) { + body, err := c.readResponseBody(resp.Body) + if err != nil { + return nil, err + } + + var params map[string]string + if err := json.Unmarshal(body, params); err != nil { + return nil, err + } + + return params, nil +} + +func (c *Client) UpdateServiceInstance(si *types.ServiceInstanceUpdateRequest) (*types.ServiceInstance, error) { + id := *si.ID + si.ID = nil + + if !c.validServiceInstanceUpdateRequestBody(si) { + return nil, fmt.Errorf("invalid request body - share fields must be updated alone") + } + + requestBody, err := json.Marshal(si) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPatch, c.smURL+ServiceInstancesPath+"/"+id, bytes.NewBuffer(requestBody)) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + + switch resp.StatusCode { + case http.StatusOK: + return c.serviceInstanceResponse(resp) + case http.StatusAccepted: + return nil, nil + default: + return nil, c.errorResponse(resp) + } +} + +func (c *Client) errorResponse(resp *http.Response) error { + body, err := c.readResponseBody(resp.Body) + if err != nil { + return err + } + + var errResp types.ErrorResponse + if err := json.Unmarshal(body, &errResp); err != nil { + return err + } + + return fmt.Errorf("error: %s", errResp.Error()) +} + +func (c *Client) readResponseBody(respBody io.ReadCloser) ([]byte, error) { + defer respBody.Close() + bodyInBytes, err := io.ReadAll(respBody) + if err != nil { + return nil, err + } + return bodyInBytes, nil +} + +func (c *Client) validServiceInstanceUpdateRequestBody(si *types.ServiceInstanceUpdateRequest) bool { + if si.Shared != nil { + return si.ID == nil && si.Name == nil && si.ServicePlanID == nil && si.Parameters == nil && len(si.Labels) == 0 + } + return true +} diff --git a/internal/service-manager/client_test.go b/internal/service-manager/client_test.go index 82a880f5c..d73ff7d0f 100644 --- a/internal/service-manager/client_test.go +++ b/internal/service-manager/client_test.go @@ -12,6 +12,7 @@ import ( "strings" "testing" + "github.com/google/uuid" servicemanager "github.com/kyma-project/btp-manager/internal/service-manager" "github.com/kyma-project/btp-manager/internal/service-manager/types" "github.com/stretchr/testify/assert" @@ -23,6 +24,7 @@ import ( const ( serviceOfferingsJSONPath = "testdata/service_offerings.json" servicePlansJSONPath = "testdata/service_plans.json" + serviceInstancesJSONPath = "testdata/service_instances.json" ) func TestClient(t *testing.T) { @@ -37,8 +39,12 @@ func TestClient(t *testing.T) { httpClient := srv.Client() url := srv.URL - allServiceOfferings := getAllServiceOfferingsFromJSON(t) - allServicePlans := getAllServicePlansFromJSON(t) + defaultServiceOfferings, err := getServiceOfferingsFromJSON() + require.NoError(t, err) + defaultServicePlans, err := getServicePlansFromJSON() + require.NoError(t, err) + defaultServiceInstances, err := getServiceInstancesFromJSON() + require.NoError(t, err) t.Run("should get service offerings available for the default credentials", func(t *testing.T) { // given @@ -60,8 +66,7 @@ func TestClient(t *testing.T) { // then require.NoError(t, err) - assert.Len(t, sos.ServiceOfferings, 4) - assert.ElementsMatch(t, allServiceOfferings.ServiceOfferings, sos.ServiceOfferings) + assertEqualServiceOfferings(t, defaultServiceOfferings, sos) }) t.Run("should get service offering details and plans for given service offering ID", func(t *testing.T) { @@ -71,17 +76,170 @@ func TestClient(t *testing.T) { smClient.SetHTTPClient(httpClient) smClient.SetSMURL(url) soID := "fc26622b-aeb2-4f3c-95da-8eb337a26883" - expectedServiceOffering := getServiceOfferingByID(allServiceOfferings, soID) - filteredServicePlans := filterServicePlansByServiceOfferingID(allServicePlans, soID) + expectedServiceOffering := getServiceOfferingByID(defaultServiceOfferings, soID) + filteredServicePlans := filterServicePlansByServiceOfferingID(defaultServicePlans, soID) // when sod, err := smClient.ServiceOfferingDetails(soID) // then require.NoError(t, err) - assert.Len(t, sod.ServicePlans.ServicePlans, 3) - assert.Equal(t, expectedServiceOffering, sod.ServiceOffering) - assert.ElementsMatch(t, filteredServicePlans.ServicePlans, sod.ServicePlans.ServicePlans) + assert.Len(t, sod.ServicePlans.Items, 3) + assertEqualServiceOffering(t, *expectedServiceOffering, sod.ServiceOffering) + assertEqualServicePlans(t, &filteredServicePlans, &sod.ServicePlans) + }) + + t.Run("should get all service instances", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + + // when + sis, err := smClient.ServiceInstances() + + // then + require.NoError(t, err) + assertEqualServiceInstances(t, defaultServiceInstances, sis) + }) + + t.Run("should get service instance for given service instance ID", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + siID := "c7a604e8-f289-4f61-841f-c6519db8daf2" + expectedServiceInstance := getServiceInstanceByID(defaultServiceInstances, siID) + + // when + si, err := smClient.ServiceInstance(siID) + + // then + require.NoError(t, err) + assertEqualServiceInstance(t, *expectedServiceInstance, *si) + }) + + t.Run("should create service instance", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + siCreateRequest := &types.ServiceInstance{ + Common: types.Common{ + Name: "test-service-instance", + Labels: types.Labels{"test-label": []string{"test-value"}}, + }, + ServicePlanID: "test-service-plan-id", + Parameters: json.RawMessage(`{"test-parameter": "test-value"}`), + } + + // when + si, err := smClient.CreateServiceInstance(siCreateRequest) + + // then + require.NoError(t, err) + assert.NotEmpty(t, si.ID) + assert.Equal(t, siCreateRequest.Name, si.Name) + assert.Equal(t, siCreateRequest.ServicePlanID, si.ServicePlanID) + assert.Equal(t, siCreateRequest.Labels, si.Labels) + + var expectedParams, actualParams []byte + require.NoError(t, siCreateRequest.Parameters.UnmarshalJSON(expectedParams)) + require.NoError(t, si.Parameters.UnmarshalJSON(actualParams)) + assert.Equal(t, expectedParams, actualParams) + }) + + t.Run("should update service instance except shared field", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + siID := "f9ffbaa4-739a-4a16-ad02-6f2f17a830c5" + siUpdatedName := "updated-service-instance" + siUpdatedServicePlanID := "updated-service-plan-id" + siUpdatedParameters := json.RawMessage(`{"updated-parameter": "updated-value"}`) + siUpdatedLabels := []types.LabelChange{{Operation: types.AddLabelOperation, Key: "updated-label", Values: []string{"updated-value"}}} + siUpdateRequest := &types.ServiceInstanceUpdateRequest{ + ID: &siID, + Name: &siUpdatedName, + ServicePlanID: &siUpdatedServicePlanID, + Parameters: &siUpdatedParameters, + Labels: siUpdatedLabels, + } + + // when + si, err := smClient.UpdateServiceInstance(siUpdateRequest) + + // then + require.NoError(t, err) + assert.Equal(t, siID, si.ID) + assert.Equal(t, siUpdatedName, si.Name) + assert.Equal(t, siUpdatedServicePlanID, si.ServicePlanID) + assert.Contains(t, si.Labels, siUpdatedLabels[0].Key) + + var expectedParams, actualParams []byte + require.NoError(t, siUpdatedParameters.UnmarshalJSON(expectedParams)) + require.NoError(t, si.Parameters.UnmarshalJSON(actualParams)) + assert.Equal(t, expectedParams, actualParams) + + // revert server to default state + srv, err = initFakeServer() + require.NoError(t, err) + }) + + t.Run("should update only the shared field in a service instance", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + siID := "df28885c-7c5f-46f0-bb75-0ae2dc85ac41" + siUpdatedShared := true + siUpdateRequest := &types.ServiceInstanceUpdateRequest{ + ID: &siID, + Shared: &siUpdatedShared, + } + + // when + si, err := smClient.UpdateServiceInstance(siUpdateRequest) + + // then + require.NoError(t, err) + assert.Equal(t, siID, si.ID) + assert.Equal(t, siUpdatedShared, si.Shared) + + // revert server to default state + srv, err = initFakeServer() + require.NoError(t, err) + }) + + t.Run("delete service instance", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + siID := "a7e240d6-e348-4fc0-a54c-7b7bfe9b9da6" + + // when + err := smClient.DeleteServiceInstance(siID) + + // then + require.NoError(t, err) + + // when + _, err = smClient.ServiceInstance(siID) + + // then + require.Error(t, err) + + // revert server to default state + srv, err = initFakeServer() + require.NoError(t, err) }) } @@ -95,6 +253,11 @@ func initFakeServer() (*httptest.Server, error) { mux.HandleFunc("GET /v1/service_offerings", smHandler.getServiceOfferings) mux.HandleFunc("GET /v1/service_offerings/{serviceOfferingID}", smHandler.getServiceOffering) mux.HandleFunc("GET /v1/service_plans", smHandler.getServicePlans) + mux.HandleFunc("GET /v1/service_instances", smHandler.getServiceInstances) + mux.HandleFunc("GET /v1/service_instances/{serviceInstanceID}", smHandler.getServiceInstance) + mux.HandleFunc("POST /v1/service_instances", smHandler.createServiceInstance) + mux.HandleFunc("PATCH /v1/service_instances/{serviceInstanceID}", smHandler.updateServiceInstance) + mux.HandleFunc("DELETE /v1/service_instances/{serviceInstanceID}", smHandler.deleteServiceInstance) srv := httptest.NewUnstartedServer(mux) @@ -102,36 +265,74 @@ func initFakeServer() (*httptest.Server, error) { } type fakeSMHandler struct { - serviceOfferings map[string]interface{} - servicePlans map[string]interface{} + serviceOfferings *types.ServiceOfferings + servicePlans *types.ServicePlans + serviceInstances *types.ServiceInstances } func newFakeSMHandler() (*fakeSMHandler, error) { - sos, err := getResourcesFromJSONFile(serviceOfferingsJSONPath) + sos, err := getServiceOfferingsFromJSON() if err != nil { - return nil, fmt.Errorf("while getting service offerings from JSON file: %w", err) + return nil, fmt.Errorf("while getting service offerings from JSON: %w", err) + } - plans, err := getResourcesFromJSONFile(servicePlansJSONPath) + plans, err := getServicePlansFromJSON() if err != nil { - return nil, fmt.Errorf("while getting service plans from JSON file: %w", err) + return nil, fmt.Errorf("while getting service plans from JSON: %w", err) } + sis, err := getServiceInstancesFromJSON() + if err != nil { + return nil, fmt.Errorf("while getting service instances from JSON: %w", err) - return &fakeSMHandler{serviceOfferings: sos, servicePlans: plans}, nil + } + return &fakeSMHandler{serviceOfferings: sos, servicePlans: plans, serviceInstances: sis}, nil } -func getResourcesFromJSONFile(jsonFilePath string) (map[string]interface{}, error) { - var buf map[string]interface{} - f, err := os.Open(jsonFilePath) +func getServiceOfferingsFromJSON() (*types.ServiceOfferings, error) { + var sos types.ServiceOfferings + f, err := os.Open(serviceOfferingsJSONPath) defer f.Close() if err != nil { return nil, fmt.Errorf("while reading resources from JSON file: %w", err) } d := json.NewDecoder(f) - if err := d.Decode(&buf); err != nil { + if err := d.Decode(&sos); err != nil { return nil, fmt.Errorf("while decoding resources JSON: %w", err) } - return buf, nil + return &sos, nil +} + +func getServicePlansFromJSON() (*types.ServicePlans, error) { + var sps types.ServicePlans + f, err := os.Open(servicePlansJSONPath) + defer f.Close() + if err != nil { + return nil, fmt.Errorf("while reading resources from JSON file: %w", err) + } + + d := json.NewDecoder(f) + if err := d.Decode(&sps); err != nil { + return nil, fmt.Errorf("while decoding resources JSON: %w", err) + } + + return &sps, nil +} + +func getServiceInstancesFromJSON() (*types.ServiceInstances, error) { + var sis types.ServiceInstances + f, err := os.Open(serviceInstancesJSONPath) + defer f.Close() + if err != nil { + return nil, fmt.Errorf("while reading resources from JSON file: %w", err) + } + + d := json.NewDecoder(f) + if err := d.Decode(&sis); err != nil { + return nil, fmt.Errorf("while decoding resources JSON: %w", err) + } + + return &sis, nil } func (h *fakeSMHandler) getServiceOfferings(w http.ResponseWriter, r *http.Request) { @@ -144,7 +345,7 @@ func (h *fakeSMHandler) getServiceOfferings(w http.ResponseWriter, r *http.Reque w.WriteHeader(http.StatusOK) if _, err = w.Write(data); err != nil { - w.WriteHeader(http.StatusBadRequest) + w.WriteHeader(http.StatusInternalServerError) log.Println("error while writing service offerings data: %w", err) return } @@ -157,22 +358,9 @@ func (h *fakeSMHandler) getServiceOffering(w http.ResponseWriter, r *http.Reques return } - data, err := json.Marshal(h.serviceOfferings) - if err != nil { - log.Println("error while marshalling service offerings data: %w", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - var sos types.ServiceOfferings - if err := json.Unmarshal(data, &sos); err != nil { - log.Println("error while unmarshalling service offerings data to struct: %w", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - data = make([]byte, 0) - for _, so := range sos.ServiceOfferings { + var err error + data := make([]byte, 0) + for _, so := range h.serviceOfferings.Items { if so.ID == soID { data, err = json.Marshal(so) if err != nil { @@ -187,7 +375,7 @@ func (h *fakeSMHandler) getServiceOffering(w http.ResponseWriter, r *http.Reques } if _, err = w.Write(data); err != nil { - w.WriteHeader(http.StatusBadRequest) + w.WriteHeader(http.StatusInternalServerError) log.Println("error while writing service offerings data: %w", err) return } @@ -202,41 +390,206 @@ func (h *fakeSMHandler) getServicePlans(w http.ResponseWriter, r *http.Request) IDFilter = strings.Trim(fields[2], "'") } - data, err := json.Marshal(h.servicePlans) + var responseSps types.ServicePlans + if len(IDFilter) != 0 { + var filteredSps types.ServicePlans + for _, sp := range h.servicePlans.Items { + if sp.ServiceOfferingID == IDFilter { + filteredSps.Items = append(filteredSps.Items, sp) + } + } + responseSps = filteredSps + } + + data, err := json.Marshal(responseSps) if err != nil { log.Println("error while marshalling service plans data: %w", err) w.WriteHeader(http.StatusInternalServerError) return } - var responseSps types.ServicePlans - if err := json.Unmarshal(data, &responseSps); err != nil { - log.Println("error while unmarshalling service offerings data to struct: %w", err) + if _, err = w.Write(data); err != nil { w.WriteHeader(http.StatusInternalServerError) + log.Println("error while writing service plans data: %w", err) return } +} - if len(IDFilter) != 0 { - var filteredSps types.ServicePlans - for _, sp := range responseSps.ServicePlans { - if sp.ServiceOfferingID == IDFilter { - filteredSps.ServicePlans = append(filteredSps.ServicePlans, sp) +func (h *fakeSMHandler) getServiceInstances(w http.ResponseWriter, r *http.Request) { + data, err := json.Marshal(h.serviceInstances) + if err != nil { + log.Println("error while marshalling service instances data: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + if _, err = w.Write(data); err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println("error while writing service instances data: %w", err) + return + } +} + +func (h *fakeSMHandler) getServiceInstance(w http.ResponseWriter, r *http.Request) { + siID := r.PathValue("serviceInstanceID") + if len(siID) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + var err error + data := make([]byte, 0) + for _, si := range h.serviceInstances.Items { + if si.ID == siID { + data, err = json.Marshal(si) + if err != nil { + log.Println("error while marshalling service instance data: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return } + w.WriteHeader(http.StatusOK) + break } - responseSps = filteredSps + } + if len(data) == 0 { + errResp := types.ErrorResponse{ + ErrorType: "NotFound", + Description: "could not find such service_instance", + } + data, err = json.Marshal(errResp) + if err != nil { + log.Println("error while marshalling error response: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusNotFound) } - data = make([]byte, 0) - data, err = json.Marshal(responseSps) + if _, err = w.Write(data); err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println("error while writing service instance data: %w", err) + return + } +} + +func (h *fakeSMHandler) createServiceInstance(w http.ResponseWriter, r *http.Request) { + var siCreateRequest types.ServiceInstance + err := json.NewDecoder(r.Body).Decode(&siCreateRequest) if err != nil { - log.Println("error while marshalling service plans data: %w", err) + log.Println("error while decoding request body into Service Instance struct: %w", err) w.WriteHeader(http.StatusInternalServerError) return } + siCreateRequest.ID = uuid.New().String() + h.serviceInstances.Items = append(h.serviceInstances.Items, siCreateRequest) + + data, err := json.Marshal(siCreateRequest) + if err != nil { + log.Println("error while marshalling service instance: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusCreated) if _, err = w.Write(data); err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println("error while writing service instance data: %w", err) + return + } +} + +func (h *fakeSMHandler) updateServiceInstance(w http.ResponseWriter, r *http.Request) { + siID := r.PathValue("serviceInstanceID") + if len(siID) == 0 { w.WriteHeader(http.StatusBadRequest) - log.Println("error while writing service plans data: %w", err) + return + } + + var siUpdateRequest types.ServiceInstanceUpdateRequest + err := json.NewDecoder(r.Body).Decode(&siUpdateRequest) + if err != nil { + log.Println("error while decoding request body into ServiceInstanceUpdateRequest struct: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + var siUpdateResponse *types.ServiceInstance + for i, si := range h.serviceInstances.Items { + if si.ID == siID { + siUpdateResponse = &h.serviceInstances.Items[i] + if siUpdateRequest.Name != nil { + h.serviceInstances.Items[i].Name = *siUpdateRequest.Name + } + if siUpdateRequest.ServicePlanID != nil { + h.serviceInstances.Items[i].ServicePlanID = *siUpdateRequest.ServicePlanID + } + if siUpdateRequest.Shared != nil { + h.serviceInstances.Items[i].Shared = *siUpdateRequest.Shared + } + if siUpdateRequest.Parameters != nil { + h.serviceInstances.Items[i].Parameters = *siUpdateRequest.Parameters + } + if len(siUpdateRequest.Labels) != 0 { + for _, labelChange := range siUpdateRequest.Labels { + if labelChange.Operation == types.AddLabelOperation { + h.serviceInstances.Items[i].Labels[labelChange.Key] = labelChange.Values + } else if labelChange.Operation == types.RemoveLabelOperation { + delete(h.serviceInstances.Items[i].Labels, labelChange.Key) + } + } + } + break + } + } + + data, err := json.Marshal(siUpdateResponse) + if err != nil { + log.Println("error while marshalling service instance: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + if _, err = w.Write(data); err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println("error while writing service instance data: %w", err) + return + } +} + +func (h *fakeSMHandler) deleteServiceInstance(w http.ResponseWriter, r *http.Request) { + siID := r.PathValue("serviceInstanceID") + if len(siID) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + for i, si := range h.serviceInstances.Items { + if si.ID == siID { + h.serviceInstances.Items = append(h.serviceInstances.Items[:i], h.serviceInstances.Items[i+1:]...) + w.WriteHeader(http.StatusOK) + return + } + } + w.WriteHeader(http.StatusNotFound) + + errResp := types.ErrorResponse{ + ErrorType: "NotFound", + Description: "could not find such service_instance", + } + + data, err := json.Marshal(errResp) + if err != nil { + log.Println("error while marshalling error response: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + if _, err = w.Write(data); err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println("error while writing error response data: %w", err) return } } @@ -282,49 +635,109 @@ func defaultSecret() *corev1.Secret { } } -func getAllServiceOfferingsFromJSON(t *testing.T) types.ServiceOfferings { - var allSos types.ServiceOfferings - soJSON, err := getResourcesFromJSONFile(serviceOfferingsJSONPath) - require.NoError(t, err) - - soBytes, err := json.Marshal(soJSON) - require.NoError(t, err) +func getServiceOfferingByID(serviceOfferings *types.ServiceOfferings, serviceOfferingID string) *types.ServiceOffering { + for _, so := range serviceOfferings.Items { + if so.ID == serviceOfferingID { + return &so + } + } + return nil +} - err = json.Unmarshal(soBytes, &allSos) - require.NoError(t, err) +func getServiceInstanceByID(serviceInstances *types.ServiceInstances, serviceInstanceID string) *types.ServiceInstance { + for _, si := range serviceInstances.Items { + if si.ID == serviceInstanceID { + return &si + } + } + return nil +} - return allSos +func filterServicePlansByServiceOfferingID(servicePlans *types.ServicePlans, serviceOfferingID string) types.ServicePlans { + var filteredSp types.ServicePlans + for _, sp := range servicePlans.Items { + if sp.ServiceOfferingID == serviceOfferingID { + filteredSp.Items = append(filteredSp.Items, sp) + } + } + return filteredSp } -func getAllServicePlansFromJSON(t *testing.T) types.ServicePlans { - var allSp types.ServicePlans - spJSON, err := getResourcesFromJSONFile(servicePlansJSONPath) - require.NoError(t, err) +func assertEqualServiceOfferings(t *testing.T, expected, actual *types.ServiceOfferings) { + assert.Len(t, actual.Items, len(expected.Items)) + for i := 0; i < len(expected.Items); i++ { + expectedToCompare, actualToCompare := expected.Items[i], actual.Items[i] + assertEqualServiceOffering(t, expectedToCompare, actualToCompare) + } +} - spBytes, err := json.Marshal(spJSON) - require.NoError(t, err) +func assertEqualServiceOffering(t *testing.T, expectedToCompare types.ServiceOffering, actualToCompare types.ServiceOffering) { + var expectedBuff, actualBuff []byte + require.NoError(t, expectedToCompare.Metadata.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.Metadata.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.Metadata, actualToCompare.Metadata = nil, nil - err = json.Unmarshal(spBytes, &allSp) - require.NoError(t, err) + require.NoError(t, expectedToCompare.Tags.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.Tags.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.Tags, actualToCompare.Tags = nil, nil - return allSp + assert.Equal(t, expectedToCompare, actualToCompare) } -func getServiceOfferingByID(serviceOfferings types.ServiceOfferings, serviceOfferingID string) types.ServiceOffering { - for _, so := range serviceOfferings.ServiceOfferings { - if so.ID == serviceOfferingID { - return so - } +func assertEqualServicePlans(t *testing.T, expected, actual *types.ServicePlans) { + assert.Len(t, actual.Items, len(expected.Items)) + for i := 0; i < len(expected.Items); i++ { + expectedToCompare, actualToCompare := expected.Items[i], actual.Items[i] + assertEqualServicePlan(t, expectedToCompare, actualToCompare) } - return types.ServiceOffering{} } -func filterServicePlansByServiceOfferingID(servicePlans types.ServicePlans, serviceOfferingID string) types.ServicePlans { - var filteredSp types.ServicePlans - for _, sp := range servicePlans.ServicePlans { - if sp.ServiceOfferingID == serviceOfferingID { - filteredSp.ServicePlans = append(filteredSp.ServicePlans, sp) - } +func assertEqualServicePlan(t *testing.T, expectedToCompare types.ServicePlan, actualToCompare types.ServicePlan) { + var expectedBuff, actualBuff []byte + require.NoError(t, expectedToCompare.Metadata.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.Metadata.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.Metadata, actualToCompare.Metadata = nil, nil + + require.NoError(t, expectedToCompare.Schemas.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.Schemas.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.Schemas, actualToCompare.Schemas = nil, nil + + assert.Equal(t, expectedToCompare, actualToCompare) +} + +func assertEqualServiceInstances(t *testing.T, expected, actual *types.ServiceInstances) { + assert.Len(t, actual.Items, len(expected.Items)) + for i := 0; i < len(expected.Items); i++ { + expectedToCompare, actualToCompare := expected.Items[i], actual.Items[i] + assertEqualServiceInstance(t, expectedToCompare, actualToCompare) } - return filteredSp +} + +func assertEqualServiceInstance(t *testing.T, expectedToCompare types.ServiceInstance, actualToCompare types.ServiceInstance) { + var expectedBuff, actualBuff []byte + require.NoError(t, expectedToCompare.Parameters.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.Parameters.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.Parameters, actualToCompare.Parameters = nil, nil + + require.NoError(t, expectedToCompare.MaintenanceInfo.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.MaintenanceInfo.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.MaintenanceInfo, actualToCompare.MaintenanceInfo = nil, nil + + require.NoError(t, expectedToCompare.Context.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.Context.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.Context, actualToCompare.Context = nil, nil + + require.NoError(t, expectedToCompare.PreviousValues.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.PreviousValues.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.PreviousValues, actualToCompare.PreviousValues = nil, nil + + assert.Equal(t, expectedToCompare, actualToCompare) } diff --git a/internal/service-manager/testdata/service_instances.json b/internal/service-manager/testdata/service_instances.json new file mode 100644 index 000000000..2ef482248 --- /dev/null +++ b/internal/service-manager/testdata/service_instances.json @@ -0,0 +1,221 @@ +{ + "num_items": 4, + "items": [ + { + "id": "f9ffbaa4-739a-4a16-ad02-6f2f17a830c5", + "ready": true, + "last_operation": { + "id": "74d0ae91-90c3-4fc5-819b-d9105d923ca6", + "ready": true, + "type": "create", + "state": "succeeded", + "resource_id": "f9ffbaa4-739a-4a16-ad02-6f2f17a830c5", + "resource_type": "/v1/service_instances", + "platform_id": "service-manager", + "correlation_id": "2c24d3c1-46b7-4e3a-8d55-1543c97c46b0", + "reschedule": false, + "reschedule_timestamp": "0001-01-01T00:00:00Z", + "deletion_scheduled": "0001-01-01T00:00:00Z", + "created_at": "2023-08-25T15:00:10Z", + "updated_at": "2023-08-25T15:01:22Z" + }, + "name": "si-test-1", + "service_plan_id": "4036790e-5ef3-4cf7-bb16-476053477a9a", + "platform_id": "3b55cceb-f517-4620-9fa8-8185af2f151a", + "context": { + "clusterid": "59c7efc0-d6bc-4d07-87cf-9bd049534afe", + "namespace": "kyma-system", + "global_account_id": "345e83e9-4e4e-45c7-b9fb-2994834eac99", + "subdomain": "test-subdomain", + "origin": "kubernetes", + "zone_id": "a4bdee5b-2bc4-4a44-915b-196ae18c7f29", + "subaccount_id": "a4bdee5b-2bc4-4a44-915b-196ae18c7f29", + "region": "test-region", + "env_type": "kubernetes", + "env_provider": "kyma", + "crm_customer_id": "", + "platform": "test-platform", + "license_type": "TEST", + "instance_name": "si-test-1" + }, + "usable": true, + "subaccount_id": "a4bdee5b-2bc4-4a44-915b-196ae18c7f29", + "protected": null, + "created_at": "2023-08-25T15:00:10Z", + "updated_at": "2023-08-25T15:01:22Z", + "labels": { + "_k8sname": [ + "si-test-1" + ], + "operated_by": [ + "3b55cceb-f517-4620-9fa8-8185af2f151a" + ], + "subaccount_id": [ + "a4bdee5b-2bc4-4a44-915b-196ae18c7f29" + ] + } + }, + { + "id": "df28885c-7c5f-46f0-bb75-0ae2dc85ac41", + "ready": true, + "last_operation": { + "id": "e48b58a7-3ff5-4429-8156-43f2e4816b25", + "ready": true, + "type": "create", + "state": "succeeded", + "resource_id": "df28885c-7c5f-46f0-bb75-0ae2dc85ac41", + "resource_type": "/v1/service_instances", + "platform_id": "service-manager", + "correlation_id": "e5eacd64-3d82-43d2-910f-cf134d1c17fa", + "reschedule": false, + "reschedule_timestamp": "0001-01-01T00:00:00Z", + "deletion_scheduled": "0001-01-01T00:00:00Z", + "created_at": "2023-08-25T15:10:45Z", + "updated_at": "2023-08-25T15:12:18Z" + }, + "name": "si-test-2", + "service_plan_id": "4036790e-5ef3-4cf7-bb16-476053477a9a", + "platform_id": "e265dc19-77a9-4633-a6b2-6d93b4c7a424", + "context": { + "clusterid": "5dc40d3c-1839-4173-9743-d5b4f36d9d7b", + "namespace": "kyma-system", + "global_account_id": "b8617fdb-c2fd-4d35-960f-e28562fdce7a", + "subdomain": "test-subdomain", + "origin": "kubernetes", + "zone_id": "5ef574ba-5fb3-493f-839c-48b787f2b710", + "subaccount_id": "5ef574ba-5fb3-493f-839c-48b787f2b710", + "region": "test-region", + "env_type": "kubernetes", + "env_provider": "kyma", + "crm_customer_id": "", + "platform": "test-platform", + "license_type": "TEST", + "instance_name": "si-test-2" + }, + "usable": true, + "subaccount_id": "5ef574ba-5fb3-493f-839c-48b787f2b710", + "protected": null, + "created_at": "2023-08-25T15:10:45Z", + "updated_at": "2023-08-25T15:12:18Z", + "labels": { + "_k8sname": [ + "si-test-2" + ], + "operated_by": [ + "e265dc19-77a9-4633-a6b2-6d93b4c7a424" + ], + "subaccount_id": [ + "5ef574ba-5fb3-493f-839c-48b787f2b710" + ] + } + }, + { + "id": "a7e240d6-e348-4fc0-a54c-7b7bfe9b9da6", + "ready": true, + "last_operation": { + "id": "d95a6a1c-448d-4d19-93ab-088ec884b929", + "ready": true, + "type": "create", + "state": "succeeded", + "resource_id": "a7e240d6-e348-4fc0-a54c-7b7bfe9b9da6", + "resource_type": "/v1/service_instances", + "platform_id": "service-manager", + "correlation_id": "7b775679-4b31-4cf0-a546-83ce5a1f421f", + "reschedule": false, + "reschedule_timestamp": "0001-01-01T00:00:00Z", + "deletion_scheduled": "0001-01-01T00:00:00Z", + "created_at": "2023-08-25T15:25:30Z", + "updated_at": "2023-08-25T15:26:45Z" + }, + "name": "si-test-3", + "service_plan_id": "4036790e-5ef3-4cf7-bb16-476053477a9a", + "platform_id": "dbec3ec7-57d4-4f1e-8544-d4d687a1f3eb", + "context": { + "clusterid": "4f6ee6a5-9c28-4e50-8b91-708345e1b607", + "namespace": "kyma-system", + "global_account_id": "22f9b6a6-1f65-4435-bd3e-1f2c44de4d45", + "subdomain": "test-subdomain", + "origin": "kubernetes", + "zone_id": "73b7f0df-6376-4115-8e45-a0e005c0f5d2", + "subaccount_id": "73b7f0df-6376-4115-8e45-a0e005c0f5d2", + "region": "test-region", + "env_type": "kubernetes", + "env_provider": "kyma", + "crm_customer_id": "", + "platform": "test-platform", + "license_type": "TEST", + "instance_name": "si-test-3" + }, + "usable": true, + "subaccount_id": "73b7f0df-6376-4115-8e45-a0e005c0f5d2", + "protected": null, + "created_at": "2023-08-25T15:25:30Z", + "updated_at": "2023-08-25T15:26:45Z", + "labels": { + "_k8sname": [ + "si-test-3" + ], + "operated_by": [ + "dbec3ec7-57d4-4f1e-8544-d4d687a1f3eb" + ], + "subaccount_id": [ + "73b7f0df-6376-4115-8e45-a0e005c0f5d2" + ] + } + }, + { + "id": "c7a604e8-f289-4f61-841f-c6519db8daf2", + "ready": true, + "last_operation": { + "id": "cfdee5e0-b0a1-464e-8398-07d07ee33e9b", + "ready": true, + "type": "create", + "state": "succeeded", + "resource_id": "c7a604e8-f289-4f61-841f-c6519db8daf2", + "resource_type": "/v1/service_instances", + "platform_id": "service-manager", + "correlation_id": "f5c3e14f-9e3f-4e31-bab6-1c0c8b5c054b", + "reschedule": false, + "reschedule_timestamp": "0001-01-01T00:00:00Z", + "deletion_scheduled": "0001-01-01T00:00:00Z", + "created_at": "2023-08-25T15:40:20Z", + "updated_at": "2023-08-25T15:41:55Z" + }, + "name": "si-test-4", + "service_plan_id": "4036790e-5ef3-4cf7-bb16-476053477a9a", + "platform_id": "37b6a457-8e6c-4139-987b-75f0f5a35217", + "context": { + "clusterid": "8e0b4ad1-4fa0-4f7f-a6a7-3db2ac0779e2", + "namespace": "kyma-system", + "global_account_id": "91b1140a-1e2b-41e9-ba38-2d26f3fc6224", + "subdomain": "test-subdomain", + "origin": "kubernetes", + "zone_id": "ad4e88f7-e9cc-4346-944a-d9e0dc42a038", + "subaccount_id": "ad4e88f7-e9cc-4346-944a-d9e0dc42a038", + "region": "test-region", + "env_type": "kubernetes", + "env_provider": "kyma", + "crm_customer_id": "", + "platform": "test-platform", + "license_type": "TEST", + "instance_name": "si-test-4" + }, + "usable": true, + "subaccount_id": "ad4e88f7-e9cc-4346-944a-d9e0dc42a038", + "protected": null, + "created_at": "2023-08-25T15:40:20Z", + "updated_at": "2023-08-25T15:41:55Z", + "labels": { + "_k8sname": [ + "si-test-4" + ], + "operated_by": [ + "37b6a457-8e6c-4139-987b-75f0f5a35217" + ], + "subaccount_id": [ + "ad4e88f7-e9cc-4346-944a-d9e0dc42a038" + ] + } + } + ] +} \ No newline at end of file diff --git a/internal/service-manager/types/error.go b/internal/service-manager/types/error.go new file mode 100644 index 000000000..a93a5ff0a --- /dev/null +++ b/internal/service-manager/types/error.go @@ -0,0 +1,39 @@ +package types + +import ( + "fmt" +) + +type ErrorResponse struct { + ErrorType string `json:"error,omitempty"` + Description string `json:"description,omitempty"` + BrokerError *BrokerError `json:"broker_error,omitempty"` +} + +func (e *ErrorResponse) Error() string { + if e.BrokerError != nil { + return e.BrokerError.Error() + } + + return e.Description +} + +type BrokerError struct { + StatusCode int + ErrorMessage *string + Description *string + ResponseError error +} + +func (e *BrokerError) Error() string { + var message, description string + + if e.ErrorMessage != nil { + message = *e.ErrorMessage + } + if e.Description != nil { + description = *e.Description + } + + return fmt.Sprintf("BrokerError:%s, Status: %d, Description: %s", message, e.StatusCode, description) +} diff --git a/internal/service-manager/types/operation.go b/internal/service-manager/types/operation.go new file mode 100644 index 000000000..4175d3c51 --- /dev/null +++ b/internal/service-manager/types/operation.go @@ -0,0 +1,42 @@ +package types + +import "encoding/json" + +const ( + AddLabelOperation = "add" + RemoveLabelOperation = "remove" +) + +type LabelChange struct { + Operation string `json:"op"` + Key string `json:"key"` + Values []string `json:"values"` +} + +type OperationCategory string + +const ( + CREATE OperationCategory = "create" + UPDATE OperationCategory = "update" + DELETE OperationCategory = "delete" +) + +type OperationState string + +const ( + PENDING OperationState = "pending" + SUCCEEDED OperationState = "succeeded" + INPROGRESS OperationState = "in progress" + FAILED OperationState = "failed" +) + +const ResourceOperationsURL = "/operations" + +type Operation struct { + Common + Type OperationCategory `json:"type,omitempty" yaml:"type,omitempty"` + State OperationState `json:"state,omitempty" yaml:"state,omitempty"` + ResourceID string `json:"resource_id,omitempty" yaml:"resource_id,omitempty"` + ResourceType string `json:"resource_type,omitempty" yaml:"resource_type,omitempty"` + Errors json.RawMessage `json:"errors,omitempty" yaml:"errors,omitempty"` +} diff --git a/internal/service-manager/types/service_instance.go b/internal/service-manager/types/service_instance.go new file mode 100644 index 000000000..23432d91d --- /dev/null +++ b/internal/service-manager/types/service_instance.go @@ -0,0 +1,35 @@ +package types + +import "encoding/json" + +type ServiceInstances struct { + Items []ServiceInstance `json:"items" yaml:"items"` +} + +type ServiceInstance struct { + Common + ServiceID string `json:"service_id,omitempty" yaml:"service_id,omitempty"` + ServicePlanID string `json:"service_plan_id,omitempty" yaml:"service_plan_id,omitempty"` + PlatformID string `json:"platform_id,omitempty" yaml:"platform_id,omitempty"` + + Parameters json.RawMessage `json:"parameters,omitempty" yaml:"parameters,omitempty"` + + MaintenanceInfo json.RawMessage `json:"maintenance_info,omitempty" yaml:"-"` + Context json.RawMessage `json:"context,omitempty" yaml:"context,omitempty"` + PreviousValues json.RawMessage `json:"-" yaml:"-"` + + Ready bool `json:"ready" yaml:"ready"` + Usable bool `json:"usable" yaml:"usable"` + Shared bool `json:"shared,omitempty" yaml:"shared,omitempty"` + + LastOperation *Operation `json:"last_operation,omitempty" yaml:"last_operation,omitempty"` +} + +type ServiceInstanceUpdateRequest struct { + ID *string `json:"id,omitempty" yaml:"id,omitempty"` + Name *string `json:"name,omitempty" yaml:"name,omitempty"` + ServicePlanID *string `json:"service_plan_id,omitempty" yaml:"service_plan_id,omitempty"` + Shared *bool `json:"shared,omitempty" yaml:"shared,omitempty"` + Parameters *json.RawMessage `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Labels []LabelChange `json:"labels,omitempty" yaml:"labels,omitempty"` +} diff --git a/internal/service-manager/types/service_offering.go b/internal/service-manager/types/service_offering.go index e8f5eeb34..e5d13766f 100644 --- a/internal/service-manager/types/service_offering.go +++ b/internal/service-manager/types/service_offering.go @@ -23,7 +23,7 @@ type ServiceOfferingDetails struct { } type ServiceOfferings struct { - ServiceOfferings []ServiceOffering `json:"items" yaml:"items"` + Items []ServiceOffering `json:"items" yaml:"items"` } type ServiceOffering struct { diff --git a/internal/service-manager/types/service_plan.go b/internal/service-manager/types/service_plan.go index 7d6dea865..51c605b92 100644 --- a/internal/service-manager/types/service_plan.go +++ b/internal/service-manager/types/service_plan.go @@ -5,7 +5,7 @@ import ( ) type ServicePlans struct { - ServicePlans []ServicePlan `json:"items" yaml:"items"` + Items []ServicePlan `json:"items" yaml:"items"` } type ServicePlan struct {