From 6cec48e1a7b0512405d28beccc353cf7fcdd2299 Mon Sep 17 00:00:00 2001 From: "L. Jezak" <110393214+ukff@users.noreply.github.com> Date: Tue, 16 Jul 2024 08:21:40 +0200 Subject: [PATCH] Handle Service Bindings in SM client and get Service Instances in API (#735) * wip * wip * wip * Rebase and sort imports * wip * Fix fetching structs fields * Fix ServiceBinding struct * GET Service Instances in API * CRUD Service Bindings * GET Service Plan by ID in SM client * GET Service Instance by ID in API * Revert temp changes --------- Co-authored-by: Marcin Szwed Co-authored-by: Ksawery Zietara --- go.mod | 2 +- go.sum | 12 +- internal/api/api.go | 70 ++++- internal/api/requests/converters.go | 18 ++ internal/api/requests/requests.go | 8 + internal/api/{vm => responses}/converters.go | 57 +++- .../api/{vm/vm.go => responses/responses.go} | 23 +- internal/cluster-object/secret_provider.go | 20 ++ internal/service-manager/client.go | 164 +++++++++- internal/service-manager/client_test.go | 297 +++++++++++++++++- .../testdata/service_bindings.json | 113 +++++++ .../CreateServiceBindingRequestPayload.go | 15 + .../service-manager/types/service_binding.go | 27 ++ .../service-manager/types/service_instance.go | 54 +++- 14 files changed, 849 insertions(+), 31 deletions(-) create mode 100644 internal/api/requests/converters.go create mode 100644 internal/api/requests/requests.go rename internal/api/{vm => responses}/converters.go (51%) rename internal/api/{vm/vm.go => responses/responses.go} (70%) create mode 100644 internal/service-manager/testdata/service_bindings.json create mode 100644 internal/service-manager/types/requests/CreateServiceBindingRequestPayload.go create mode 100644 internal/service-manager/types/service_binding.go diff --git a/go.mod b/go.mod index a902a9672..7cb1893d3 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.5 require ( github.com/go-logr/logr v1.4.2 + github.com/google/uuid v1.6.0 github.com/onsi/ginkgo/v2 v2.20.0 github.com/onsi/gomega v1.34.1 github.com/prometheus/client_golang v1.20.1 @@ -40,7 +41,6 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 321ac7f98..f4c902217 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -58,6 +59,8 @@ github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2 github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -71,6 +74,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= @@ -94,12 +98,13 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/vrischmann/envconfig v1.3.0 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk= github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -154,6 +159,7 @@ gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuB google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= @@ -163,6 +169,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= diff --git a/internal/api/api.go b/internal/api/api.go index 0a51db945..7d6581dd9 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -10,7 +10,10 @@ import ( "strings" "time" - "github.com/kyma-project/btp-manager/internal/api/vm" + "github.com/kyma-project/btp-manager/internal/service-manager/types" + + "github.com/kyma-project/btp-manager/internal/api/responses" + clusterobject "github.com/kyma-project/btp-manager/internal/cluster-object" servicemanager "github.com/kyma-project/btp-manager/internal/service-manager" ) @@ -24,7 +27,7 @@ type Config struct { type API struct { server *http.Server - serviceManager *servicemanager.Client + smClient *servicemanager.Client secretProvider *clusterobject.SecretProvider frontendFS http.FileSystem logger *slog.Logger @@ -39,7 +42,7 @@ func NewAPI(cfg Config, serviceManager *servicemanager.Client, secretProvider *c } return &API{ server: srv, - serviceManager: serviceManager, + smClient: serviceManager, secretProvider: secretProvider, frontendFS: fs, logger: slog.Default()} @@ -53,6 +56,8 @@ func (a *API) Start() { mux.HandleFunc("GET /api/service-instance/{id}", a.GetServiceInstance) mux.HandleFunc("GET /api/service-offerings/{namespace}/{name}", a.ListServiceOfferings) mux.HandleFunc("GET /api/service-offering/{id}", a.GetServiceOffering) + mux.HandleFunc("POST /api/service-bindings", a.CreateServiceBindings) + mux.HandleFunc("GET /api/service-binding/{id}", a.GetServiceBinding) mux.Handle("GET /", http.FileServer(a.frontendFS)) a.server.Handler = mux @@ -60,17 +65,17 @@ func (a *API) Start() { } func (a *API) CreateServiceInstance(writer http.ResponseWriter, request *http.Request) { - return + a.setupCors(writer, request) } func (a *API) GetServiceOffering(writer http.ResponseWriter, request *http.Request) { a.setupCors(writer, request) id := request.PathValue("id") - details, err := a.serviceManager.ServiceOfferingDetails(id) + details, err := a.smClient.ServiceOfferingDetails(id) if returnError(writer, err) { return } - response, err := json.Marshal(vm.ToServiceOfferingDetailsVM(details)) + response, err := json.Marshal(responses.ToServiceOfferingDetailsVM(details)) returnResponse(writer, response, err) } @@ -78,15 +83,15 @@ func (a *API) ListServiceOfferings(writer http.ResponseWriter, request *http.Req a.setupCors(writer, request) namespace := request.PathValue("namespace") name := request.PathValue("name") - err := a.serviceManager.SetForGivenSecret(context.Background(), name, namespace) + err := a.smClient.SetForGivenSecret(context.Background(), name, namespace) if returnError(writer, err) { return } - offerings, err := a.serviceManager.ServiceOfferings() + offerings, err := a.smClient.ServiceOfferings() if returnError(writer, err) { return } - response, err := json.Marshal(vm.ToServiceOfferingsVM(offerings)) + response, err := json.Marshal(responses.ToServiceOfferingsVM(offerings)) returnResponse(writer, response, err) } @@ -96,21 +101,62 @@ func (a *API) ListSecrets(writer http.ResponseWriter, request *http.Request) { if returnError(writer, err) { return } - response, err := json.Marshal(vm.ToSecretVM(*secrets)) + response, err := json.Marshal(responses.ToSecretVM(*secrets)) returnResponse(writer, response, err) } func (a *API) GetServiceInstance(writer http.ResponseWriter, request *http.Request) { a.setupCors(writer, request) - // not implemented in SM + id := request.PathValue("id") + si, err := a.smClient.ServiceInstance(id) + if returnError(writer, err) { + return + } + plan, err := a.smClient.ServicePlan(si.ServicePlanID) + if returnError(writer, err) { + return + } + response, err := json.Marshal(responses.ToServiceInstanceVM(si, plan)) + returnResponse(writer, response, err) } func (a *API) ListServiceInstances(writer http.ResponseWriter, request *http.Request) { a.setupCors(writer, request) - // will be taken from SM + sis, err := a.smClient.ServiceInstances() + if returnError(writer, err) { + return + } + response, err := json.Marshal(responses.ToServiceInstancesVM(sis)) + returnResponse(writer, response, err) +} + +func (a *API) CreateServiceBindings(writer http.ResponseWriter, request *http.Request) { + a.setupCors(writer, request) + var sb types.ServiceBinding + err := json.NewDecoder(request.Body).Decode(&sb) + if returnError(writer, err) { + return + } + _, err = a.smClient.CreateServiceBinding(&sb) + if returnError(writer, err) { + return + } + returnResponse(writer, []byte{}, err) +} + +func (a *API) GetServiceBinding(writer http.ResponseWriter, request *http.Request) { + a.setupCors(writer, request) + id := request.PathValue("id") + details, err := a.smClient.ServiceBinding(id) + if returnError(writer, err) { + return + } + response, err := json.Marshal(responses.ToServiceBindingVM(details)) + returnResponse(writer, response, err) } func (a *API) setupCors(writer http.ResponseWriter, request *http.Request) { + a.logger.Info(fmt.Sprintf("api call to -> %s as: %s", request.RequestURI, request.Method)) origin := request.Header.Get("Origin") origin = strings.ReplaceAll(origin, "\r", "") origin = strings.ReplaceAll(origin, "\n", "") diff --git a/internal/api/requests/converters.go b/internal/api/requests/converters.go new file mode 100644 index 000000000..550e08f20 --- /dev/null +++ b/internal/api/requests/converters.go @@ -0,0 +1,18 @@ +package requests + +import ( + "github.com/kyma-project/btp-manager/internal/service-manager/types/requests" +) + +func CreateServiceBindingVM(request CreateServiceBinding) requests.CreateServiceBindingRequestPayload { + payload := requests.CreateServiceBindingRequestPayload{ + Name: request.Name, + ServiceInstanceID: request.ServiceInstanceId, + Parameters: []byte(request.Parameters), + Labels: map[string][]string{}, + } + payload.Labels["_clusterid"] = append(payload.Labels["_clusterid"], payload.Name) + payload.Labels["_namespace"] = append(payload.Labels["_namespace"], request.Namespace) + payload.Labels["_k8sname"] = append(payload.Labels["_k8sname"], request.Name) + return payload +} diff --git a/internal/api/requests/requests.go b/internal/api/requests/requests.go new file mode 100644 index 000000000..dfc40abe3 --- /dev/null +++ b/internal/api/requests/requests.go @@ -0,0 +1,8 @@ +package requests + +type CreateServiceBinding struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + ServiceInstanceId string `json:"serviceInstanceId"` + Parameters string `json:"parameters"` +} diff --git a/internal/api/vm/converters.go b/internal/api/responses/converters.go similarity index 51% rename from internal/api/vm/converters.go rename to internal/api/responses/converters.go index 00f232d5b..4421be470 100644 --- a/internal/api/vm/converters.go +++ b/internal/api/responses/converters.go @@ -1,4 +1,4 @@ -package vm +package responses import ( "github.com/kyma-project/btp-manager/internal/service-manager/types" @@ -64,3 +64,58 @@ func ToServiceOfferingDetailsVM(details *types.ServiceOfferingDetails) ServiceOf return toReturn } + +func ToServiceInstancesVM(instances *types.ServiceInstances) ServiceInstances { + toReturn := ServiceInstances{ + NumItems: len(instances.Items), + Items: []ServiceInstance{}, + } + + for _, instance := range instances.Items { + namespace, _ := instance.ContextValueByFieldName(types.ServiceInstanceNamespace) + subaccountID, _ := instance.ContextValueByFieldName(types.ServiceInstanceSubaccountID) + clusterID, _ := instance.ContextValueByFieldName(types.ServiceInstanceClusterID) + instance := ServiceInstance{ + ID: instance.ID, + Name: instance.Name, + Namespace: namespace, + SubaccountID: subaccountID, + ClusterID: clusterID, + } + toReturn.Items = append(toReturn.Items, instance) + } + return toReturn +} + +func ToServiceInstanceVM(instance *types.ServiceInstance, plan *types.ServicePlan) ServiceInstance { + namespace, _ := instance.ContextValueByFieldName(types.ServiceInstanceNamespace) + subaccountID, _ := instance.ContextValueByFieldName(types.ServiceInstanceSubaccountID) + clusterID, _ := instance.ContextValueByFieldName(types.ServiceInstanceClusterID) + + return ServiceInstance{ + ID: instance.ID, + Name: instance.Name, + Namespace: namespace, + ServicePlanID: instance.ServicePlanID, + ServicePlanName: plan.Name, + SubaccountID: subaccountID, + ClusterID: clusterID, + } +} + +func ToServiceBindingsVM(bindings *types.ServiceBindings) ServiceBindings { + toReturn := ServiceBindings{ + Items: []ServiceBinding{}, + } + + for _, _ = range bindings.Items { + n := ServiceBinding{} + toReturn.Items = append(toReturn.Items, n) + } + + return toReturn +} + +func ToServiceBindingVM(binding *types.ServiceBinding) ServiceBindings { + return ServiceBindings{} +} diff --git a/internal/api/vm/vm.go b/internal/api/responses/responses.go similarity index 70% rename from internal/api/vm/vm.go rename to internal/api/responses/responses.go index 7b81a0e30..2b40958b2 100644 --- a/internal/api/vm/vm.go +++ b/internal/api/responses/responses.go @@ -1,4 +1,4 @@ -package vm +package responses type Secrets struct { Items []Secret `json:"items"` @@ -30,12 +30,18 @@ type ServiceOfferingMetadata struct { } type ServiceInstances struct { - Items []ServiceInstance `json:"items"` + NumItems int `json:"numItems"` + Items []ServiceInstance `json:"items"` } type ServiceInstance struct { - Name string `json:"name"` - Namespace string `json:"namespace"` + ID string `json:"id"` + Name string `json:"name"` + Namespace string `json:"namespace"` + ServicePlanID string `json:"servicePlanID"` + ServicePlanName string `json:"servicePlanName"` + SubaccountID string `json:"subaccountID"` + ClusterID string `json:"clusterID"` } type ServiceOfferingDetails struct { @@ -47,3 +53,12 @@ type ServiceOfferingPlan struct { Name string `json:"name"` Description string `json:"description"` } + +type ServiceBinding struct { + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +type ServiceBindings struct { + Items []ServiceBinding `json:"items"` +} diff --git a/internal/cluster-object/secret_provider.go b/internal/cluster-object/secret_provider.go index 107c9457f..728c36972 100644 --- a/internal/cluster-object/secret_provider.go +++ b/internal/cluster-object/secret_provider.go @@ -5,6 +5,8 @@ import ( "fmt" "log/slog" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/kyma-project/btp-manager/controllers" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -19,6 +21,7 @@ const ( type SecretProvider struct { client.Reader + client.Writer namespaceProvider *NamespaceProvider serviceInstanceProvider *ServiceInstanceProvider logger *slog.Logger @@ -158,3 +161,20 @@ func (p *SecretProvider) GetByNameAndNamespace(ctx context.Context, name, namesp return secret, nil } + +func (p *SecretProvider) CreateSecret(ctx context.Context, name, namespace string) (*corev1.Secret, error) { + p.logger.Info(fmt.Sprintf("creating \"%s\" secret in \"%s\" namespace", btpServiceOperatorSecretName, namespace)) + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + + if err := p.Writer.Create(ctx, secret); err != nil { + p.logger.Error(fmt.Sprintf("failed to create \"%s\" secret in \"%s\" namespace", btpServiceOperatorSecretName, namespace), "error", err) + return nil, err + } + + return secret, nil +} diff --git a/internal/service-manager/client.go b/internal/service-manager/client.go index 76d4879aa..7a8055580 100644 --- a/internal/service-manager/client.go +++ b/internal/service-manager/client.go @@ -27,7 +27,7 @@ const ( ServiceOfferingsPath = "/v1/service_offerings" ServicePlansPath = "/v1/service_plans" ServiceInstancesPath = "/v1/service_instances" - + ServiceBindingsPath = "/v1/service_bindings" // see https://help.sap.com/docs/service-manager/sap-service-manager/filtering-parameters-and-operators URLFieldQueryKey = "fieldQuery" servicePlansForServiceOfferingQueryFormat = "service_offering_id eq '%s'" @@ -297,7 +297,7 @@ func (c *Client) ServiceInstanceParameters(serviceInstanceID string) (map[string switch resp.StatusCode { case http.StatusOK: - return c.serviceInstanceParamsResponse(resp) + return c.paramsResponse(resp) default: return nil, c.errorResponse(resp) } @@ -365,7 +365,7 @@ func (c *Client) serviceInstanceResponse(resp *http.Response) (*types.ServiceIns return &siResp, nil } -func (c *Client) serviceInstanceParamsResponse(resp *http.Response) (map[string]string, error) { +func (c *Client) paramsResponse(resp *http.Response) (map[string]string, error) { body, err := c.readResponseBody(resp.Body) if err != nil { return nil, err @@ -412,6 +412,130 @@ func (c *Client) UpdateServiceInstance(si *types.ServiceInstanceUpdateRequest) ( } } +func (c *Client) ServiceBindings() (*types.ServiceBindings, error) { + req, err := http.NewRequest(http.MethodGet, c.smURL+ServiceBindingsPath, 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 serviceBindings types.ServiceBindings + if err := json.Unmarshal(body, &serviceBindings); err != nil { + return nil, err + } + + return &serviceBindings, nil +} + +func (c *Client) CreateServiceBinding(sb *types.ServiceBinding) (*types.ServiceBinding, error) { + reqBody, err := json.Marshal(sb) + if err != nil { + return nil, err + } + req, err := http.NewRequest(http.MethodPost, c.smURL+ServiceBindingsPath, io.NopCloser(bytes.NewReader(reqBody))) + 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.serviceBindingResponse(resp) + case http.StatusAccepted: + return nil, nil + default: + return nil, c.errorResponse(resp) + } +} + +func (c *Client) ServiceBinding(serviceBindingId string) (*types.ServiceBinding, error) { + req, err := http.NewRequest(http.MethodGet, c.smURL+ServiceBindingsPath+"/"+serviceBindingId, 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.serviceBindingResponse(resp) + default: + return nil, c.errorResponse(resp) + } +} + +func (c *Client) DeleteServiceBinding(serviceBindingId string) error { + req, err := http.NewRequest(http.MethodDelete, c.smURL+ServiceBindingsPath+"/"+serviceBindingId, 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) ServiceBindingParameters(serviceBindingId string) (map[string]string, error) { + req, err := http.NewRequest(http.MethodGet, c.smURL+ServiceBindingsPath+"/"+serviceBindingId+"/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.paramsResponse(resp) + default: + return nil, c.errorResponse(resp) + } +} + +func (c *Client) serviceBindingResponse(resp *http.Response) (*types.ServiceBinding, error) { + body, err := c.readResponseBody(resp.Body) + if err != nil { + return nil, err + } + + var sb types.ServiceBinding + if err := json.Unmarshal(body, &sb); err != nil { + return nil, err + } + + return &sb, nil +} + func (c *Client) errorResponse(resp *http.Response) error { body, err := c.readResponseBody(resp.Body) if err != nil { @@ -441,3 +565,37 @@ func (c *Client) validServiceInstanceUpdateRequestBody(si *types.ServiceInstance } return true } + +func (c *Client) ServicePlan(servicePlanID string) (*types.ServicePlan, error) { + req, err := http.NewRequest(http.MethodGet, c.smURL+ServicePlansPath+"/"+servicePlanID, 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.servicePlanResponse(resp) + default: + return nil, c.errorResponse(resp) + } +} + +func (c *Client) servicePlanResponse(resp *http.Response) (*types.ServicePlan, error) { + body, err := c.readResponseBody(resp.Body) + if err != nil { + return nil, err + } + + var plan types.ServicePlan + if err := json.Unmarshal(body, &plan); err != nil { + return nil, err + } + + return &plan, nil +} diff --git a/internal/service-manager/client_test.go b/internal/service-manager/client_test.go index d73ff7d0f..0fc1d6784 100644 --- a/internal/service-manager/client_test.go +++ b/internal/service-manager/client_test.go @@ -25,6 +25,7 @@ const ( serviceOfferingsJSONPath = "testdata/service_offerings.json" servicePlansJSONPath = "testdata/service_plans.json" serviceInstancesJSONPath = "testdata/service_instances.json" + serviceBindingsJSONPath = "testdata/service_bindings.json" ) func TestClient(t *testing.T) { @@ -45,6 +46,8 @@ func TestClient(t *testing.T) { require.NoError(t, err) defaultServiceInstances, err := getServiceInstancesFromJSON() require.NoError(t, err) + defaultServiceBindings, err := getServiceBindingsFromJSON() + require.NoError(t, err) t.Run("should get service offerings available for the default credentials", func(t *testing.T) { // given @@ -241,6 +244,100 @@ func TestClient(t *testing.T) { srv, err = initFakeServer() require.NoError(t, err) }) + + t.Run("should get all service bindings", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + + // when + sbs, err := smClient.ServiceBindings() + + // then + require.NoError(t, err) + assertEqualServiceBindings(t, defaultServiceBindings, sbs) + }) + + t.Run("should get service binding for given service binding ID", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + sbID := "550e8400-e29b-41d4-a716-446655440003" + expectedServiceBinding := getServiceBindingByID(defaultServiceBindings, sbID) + + // when + sb, err := smClient.ServiceBinding(sbID) + + // then + require.NoError(t, err) + assertEqualServiceBinding(t, *expectedServiceBinding, *sb) + }) + + t.Run("should create service binding", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + sbCreateRequest := &types.ServiceBinding{ + Common: types.Common{ + Name: "test-service-binding", + Labels: types.Labels{"test-label": []string{"test-value"}}, + }, + ServiceInstanceID: "test-service-instance-id", + Parameters: json.RawMessage(`{"test-parameter": "test-value"}`), + BindResource: json.RawMessage(`{"test-bind-resource": "test-value"}`), + } + + // when + sb, err := smClient.CreateServiceBinding(sbCreateRequest) + + // then + require.NoError(t, err) + assert.NotEmpty(t, sb.ID) + assert.Equal(t, sbCreateRequest.Name, sb.Name) + assert.Equal(t, sbCreateRequest.ServiceInstanceID, sb.ServiceInstanceID) + assert.Equal(t, sbCreateRequest.Labels, sb.Labels) + + var expectedParams, actualParams []byte + require.NoError(t, sbCreateRequest.Parameters.UnmarshalJSON(expectedParams)) + require.NoError(t, sb.Parameters.UnmarshalJSON(actualParams)) + assert.Equal(t, expectedParams, actualParams) + + var expectedBindResource, actualBindResource []byte + require.NoError(t, sbCreateRequest.Parameters.UnmarshalJSON(expectedBindResource)) + require.NoError(t, sb.Parameters.UnmarshalJSON(actualBindResource)) + assert.Equal(t, expectedBindResource, actualBindResource) + }) + + t.Run("should delete service binding", func(t *testing.T) { + // given + ctx := context.TODO() + smClient := servicemanager.NewClient(ctx, slog.Default(), secretProvider) + smClient.SetHTTPClient(httpClient) + smClient.SetSMURL(url) + sbID := "318a16c3-7c80-485f-b55c-918629012c9a" + + // when + err := smClient.DeleteServiceBinding(sbID) + + // then + require.NoError(t, err) + + // when + _, err = smClient.ServiceBinding(sbID) + + // then + require.Error(t, err) + + // revert server to default state + srv, err = initFakeServer() + require.NoError(t, err) + }) } func initFakeServer() (*httptest.Server, error) { @@ -258,6 +355,10 @@ func initFakeServer() (*httptest.Server, error) { 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) + mux.HandleFunc("GET /v1/service_bindings", smHandler.getServiceBindings) + mux.HandleFunc("GET /v1/service_bindings/{serviceBindingID}", smHandler.getServiceBinding) + mux.HandleFunc("POST /v1/service_bindings", smHandler.createServiceBinding) + mux.HandleFunc("DELETE /v1/service_bindings/{serviceBindingID}", smHandler.deleteServiceBinding) srv := httptest.NewUnstartedServer(mux) @@ -268,6 +369,7 @@ type fakeSMHandler struct { serviceOfferings *types.ServiceOfferings servicePlans *types.ServicePlans serviceInstances *types.ServiceInstances + serviceBindings *types.ServiceBindings } func newFakeSMHandler() (*fakeSMHandler, error) { @@ -285,7 +387,12 @@ func newFakeSMHandler() (*fakeSMHandler, error) { return nil, fmt.Errorf("while getting service instances from JSON: %w", err) } - return &fakeSMHandler{serviceOfferings: sos, servicePlans: plans, serviceInstances: sis}, nil + sbs, err := getServiceBindingsFromJSON() + if err != nil { + return nil, fmt.Errorf("while getting service bindings from JSON: %w", err) + + } + return &fakeSMHandler{serviceOfferings: sos, servicePlans: plans, serviceInstances: sis, serviceBindings: sbs}, nil } func getServiceOfferingsFromJSON() (*types.ServiceOfferings, error) { @@ -335,6 +442,22 @@ func getServiceInstancesFromJSON() (*types.ServiceInstances, error) { return &sis, nil } +func getServiceBindingsFromJSON() (*types.ServiceBindings, error) { + var sbs types.ServiceBindings + f, err := os.Open(serviceBindingsJSONPath) + 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(&sbs); err != nil { + return nil, fmt.Errorf("while decoding resources JSON: %w", err) + } + + return &sbs, nil +} + func (h *fakeSMHandler) getServiceOfferings(w http.ResponseWriter, r *http.Request) { data, err := json.Marshal(h.serviceOfferings) if err != nil { @@ -594,6 +717,126 @@ func (h *fakeSMHandler) deleteServiceInstance(w http.ResponseWriter, r *http.Req } } +func (h *fakeSMHandler) getServiceBindings(w http.ResponseWriter, r *http.Request) { + data, err := json.Marshal(h.serviceBindings) + if err != nil { + log.Println("error while marshalling service bindings 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 bindings data: %w", err) + return + } +} + +func (h *fakeSMHandler) getServiceBinding(w http.ResponseWriter, r *http.Request) { + sbID := r.PathValue("serviceBindingID") + if len(sbID) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + var err error + data := make([]byte, 0) + for _, sb := range h.serviceBindings.Items { + if sb.ID == sbID { + data, err = json.Marshal(sb) + if err != nil { + log.Println("error while marshalling service binding data: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + break + } + } + if len(data) == 0 { + errResp := types.ErrorResponse{ + ErrorType: "NotFound", + Description: "could not find such service_binding", + } + 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) + } + + if _, err = w.Write(data); err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println("error while writing service binding data: %w", err) + return + } +} + +func (h *fakeSMHandler) createServiceBinding(w http.ResponseWriter, r *http.Request) { + var sbCreateRequest types.ServiceBinding + err := json.NewDecoder(r.Body).Decode(&sbCreateRequest) + if err != nil { + log.Println("error while decoding request body into Service Binding struct: %w", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + sbCreateRequest.ID = uuid.New().String() + h.serviceBindings.Items = append(h.serviceBindings.Items, sbCreateRequest) + + data, err := json.Marshal(sbCreateRequest) + if err != nil { + log.Println("error while marshalling service binding: %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 binding data: %w", err) + return + } +} + +func (h *fakeSMHandler) deleteServiceBinding(w http.ResponseWriter, r *http.Request) { + sbID := r.PathValue("serviceBindingID") + if len(sbID) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + for i, sb := range h.serviceBindings.Items { + if sb.ID == sbID { + h.serviceBindings.Items = append(h.serviceBindings.Items[:i], h.serviceBindings.Items[i+1:]...) + w.WriteHeader(http.StatusOK) + return + } + } + w.WriteHeader(http.StatusNotFound) + + errResp := types.ErrorResponse{ + ErrorType: "NotFound", + Description: "could not find such service_binding", + } + + 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 + } +} + type fakeSecretProvider struct { secrets []*corev1.Secret } @@ -653,6 +896,15 @@ func getServiceInstanceByID(serviceInstances *types.ServiceInstances, serviceIns return nil } +func getServiceBindingByID(serviceBindings *types.ServiceBindings, serviceBindingID string) *types.ServiceBinding { + for _, sb := range serviceBindings.Items { + if sb.ID == serviceBindingID { + return &sb + } + } + return nil +} + func filterServicePlansByServiceOfferingID(servicePlans *types.ServicePlans, serviceOfferingID string) types.ServicePlans { var filteredSp types.ServicePlans for _, sp := range servicePlans.Items { @@ -741,3 +993,46 @@ func assertEqualServiceInstance(t *testing.T, expectedToCompare types.ServiceIns assert.Equal(t, expectedToCompare, actualToCompare) } + +func assertEqualServiceBindings(t *testing.T, expected, actual *types.ServiceBindings) { + assert.Len(t, actual.Items, len(expected.Items)) + for i := 0; i < len(expected.Items); i++ { + expectedToCompare, actualToCompare := expected.Items[i], actual.Items[i] + assertEqualServiceBinding(t, expectedToCompare, actualToCompare) + } +} + +func assertEqualServiceBinding(t *testing.T, expectedToCompare, actualToCompare types.ServiceBinding) { + var expectedBuff, actualBuff []byte + require.NoError(t, expectedToCompare.Credentials.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.Credentials.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.Credentials, actualToCompare.Credentials = nil, nil + + require.NoError(t, expectedToCompare.VolumeMounts.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.VolumeMounts.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.VolumeMounts, actualToCompare.VolumeMounts = nil, nil + + require.NoError(t, expectedToCompare.Endpoints.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.Endpoints.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.Endpoints, actualToCompare.Endpoints = 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.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.BindResource.UnmarshalJSON(expectedBuff)) + require.NoError(t, actualToCompare.BindResource.UnmarshalJSON(actualBuff)) + assert.Equal(t, expectedBuff, actualBuff) + expectedToCompare.BindResource, actualToCompare.BindResource = nil, nil + + assert.Equal(t, expectedToCompare, actualToCompare) +} diff --git a/internal/service-manager/testdata/service_bindings.json b/internal/service-manager/testdata/service_bindings.json new file mode 100644 index 000000000..9a9428b1c --- /dev/null +++ b/internal/service-manager/testdata/service_bindings.json @@ -0,0 +1,113 @@ +{ + "num_items": 4, + "items": [ + { + "id": "550e8400-e29b-41d4-a716-446655440003", + "ready": true, + "name": "service-binding", + "service_instance_id": "6ba7b810-9dad-11d1-80b4-00c04fd430cb", + "context": { + "account": "account" + }, + "credentials": { + "username": "user", + "password": "pass" + }, + "bind_resource": { + "app_guid": "550e8400-e29b-41d4-a716-446655440003", + "space_guid": "6ba7b810-9dad-11d1-80b4-00c04fd430cb" + }, + "created_at": "2023-08-25T15:40:20Z", + "updated_at": "2023-08-25T15:41:55Z", + "labels": { + "env": [ + "dev" + ], + "version": [ + "v1" + ] + } + }, + { + "id": "9e420bca-4cf2-4858-ade2-e5ef23cd756f", + "ready": true, + "name": "service-binding-2", + "service_instance_id": "a12c2ccb-9697-490a-9287-c480cef5c121", + "context": { + "account": "account-2" + }, + "credentials": { + "username": "user2", + "password": "pass2" + }, + "bind_resource": { + "app_guid": "9e420bca-4cf2-4858-ade2-e5ef23cd756f", + "space_guid": "a12c2ccb-9697-490a-9287-c480cef5c121" + }, + "created_at": "2023-08-25T15:42:20Z", + "updated_at": "2023-08-25T15:43:55Z", + "labels": { + "env": [ + "prod" + ], + "version": [ + "v2" + ] + } + }, + { + "id": "318a16c3-7c80-485f-b55c-918629012c9a", + "ready": true, + "name": "service-binding-3", + "service_instance_id": "2e3f7c7d-970a-43c0-927a-92d2f7ec64c5", + "context": { + "account": "account-3" + }, + "credentials": { + "username": "user3", + "password": "pass3" + }, + "bind_resource": { + "app_guid": "318a16c3-7c80-485f-b55c-918629012c9a", + "space_guid": "2e3f7c7d-970a-43c0-927a-92d2f7ec64c5" + }, + "created_at": "2023-08-25T15:44:20Z", + "updated_at": "2023-08-25T15:45:55Z", + "labels": { + "env": [ + "staging" + ], + "version": [ + "v3" + ] + } + }, + { + "id": "8e97d56b-9fc1-43db-9d2e-e52f8ce91046", + "ready": true, + "name": "service-binding-4", + "service_instance_id": "3fdbfbdd-2d79-4375-bcd3-9b257e82b6a4", + "context": { + "account": "account-4" + }, + "credentials": { + "username": "user3", + "password": "pass3" + }, + "bind_resource": { + "app_guid": "8e97d56b-9fc1-43db-9d2e-e52f8ce91046", + "space_guid": "3fdbfbdd-2d79-4375-bcd3-9b257e82b6a4" + }, + "created_at": "2023-08-25T15:44:20Z", + "updated_at": "2023-08-25T15:45:55Z", + "labels": { + "env": [ + "staging" + ], + "version": [ + "v3" + ] + } + } + ] +} diff --git a/internal/service-manager/types/requests/CreateServiceBindingRequestPayload.go b/internal/service-manager/types/requests/CreateServiceBindingRequestPayload.go new file mode 100644 index 000000000..9f1f0a6f1 --- /dev/null +++ b/internal/service-manager/types/requests/CreateServiceBindingRequestPayload.go @@ -0,0 +1,15 @@ +package requests + +import ( + "encoding/json" + + "github.com/kyma-project/btp-manager/internal/service-manager/types" +) + +type CreateServiceBindingRequestPayload struct { + Name string `json:"name"` + ServiceInstanceID string `json:"service_instance_id" yaml:"service_instance_id,omitempty"` + Parameters json.RawMessage `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Labels types.Labels `json:"labels,omitempty" yaml:"labels,omitempty"` + BindResource json.RawMessage `json:"bind_resource" yaml:"bind_resource"` +} diff --git a/internal/service-manager/types/service_binding.go b/internal/service-manager/types/service_binding.go new file mode 100644 index 000000000..d2a89e8f9 --- /dev/null +++ b/internal/service-manager/types/service_binding.go @@ -0,0 +1,27 @@ +package types + +import "encoding/json" + +type ServiceBindings struct { + Items []ServiceBinding `json:"items" yaml:"items"` +} + +type ServiceBinding struct { + Common + PagingSequence int64 `json:"-" yaml:"-"` + + Credentials json.RawMessage `json:"credentials,omitempty" yaml:"credentials,omitempty"` + + ServiceInstanceID string `json:"service_instance_id" yaml:"service_instance_id,omitempty"` + ServiceInstanceName string `json:"service_instance_name,omitempty" yaml:"service_instance_name,omitempty"` + + SyslogDrainURL string `json:"syslog_drain_url,omitempty" yaml:"syslog_drain_url,omitempty"` + RouteServiceURL string `json:"route_service_url,omitempty"` + VolumeMounts json.RawMessage `json:"-" yaml:"-"` + Endpoints json.RawMessage `json:"-" yaml:"-"` + Context json.RawMessage `json:"context,omitempty" yaml:"context,omitempty"` + Parameters json.RawMessage `json:"parameters,omitempty" yaml:"parameters,omitempty"` + BindResource json.RawMessage `json:"-" yaml:"-"` + + LastOperation *Operation `json:"last_operation,omitempty" yaml:"last_operation,omitempty"` +} diff --git a/internal/service-manager/types/service_instance.go b/internal/service-manager/types/service_instance.go index 23432d91d..fecb107b4 100644 --- a/internal/service-manager/types/service_instance.go +++ b/internal/service-manager/types/service_instance.go @@ -1,6 +1,15 @@ package types -import "encoding/json" +import ( + "encoding/json" + "fmt" +) + +const ( + ServiceInstanceClusterID = "clusterid" + ServiceInstanceSubaccountID = "subaccount_id" + ServiceInstanceNamespace = "namespace" +) type ServiceInstances struct { Items []ServiceInstance `json:"items" yaml:"items"` @@ -8,15 +17,17 @@ type ServiceInstances struct { 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"` + ServiceID string `json:"service_id,omitempty" yaml:"service_id,omitempty"` + ServicePlanID string `json:"service_plan_id,omitempty" yaml:"service_plan_id,omitempty"` + ServicePlanName string `json:"-" yaml:"-"` + 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:"-"` + MaintenanceInfo json.RawMessage `json:"maintenance_info,omitempty" yaml:"-"` + Context json.RawMessage `json:"context,omitempty" yaml:"context,omitempty"` + PreviousValues json.RawMessage `json:"-" yaml:"-"` + unmarshalledContext map[string]interface{} Ready bool `json:"ready" yaml:"ready"` Usable bool `json:"usable" yaml:"usable"` @@ -25,6 +36,35 @@ type ServiceInstance struct { LastOperation *Operation `json:"last_operation,omitempty" yaml:"last_operation,omitempty"` } +func (i *ServiceInstance) ContextValueByFieldName(fieldName string) (string, error) { + if i.Context == nil || len(i.Context) == 0 { + return "", nil + } + if err := i.unmarshalContext(); err != nil { + return "", err + } + val, ok := i.unmarshalledContext[fieldName] + if !ok { + return "not found", nil + } + + return fmt.Sprint(val), nil + +} + +func (i *ServiceInstance) unmarshalContext() error { + if i.unmarshalledContext != nil && len(i.unmarshalledContext) != 0 { + return nil + } + var context map[string]interface{} + if err := json.Unmarshal(i.Context, &context); err != nil { + return err + } + i.unmarshalledContext = context + + return nil +} + type ServiceInstanceUpdateRequest struct { ID *string `json:"id,omitempty" yaml:"id,omitempty"` Name *string `json:"name,omitempty" yaml:"name,omitempty"`