forked from redhat-developer/odo
/
service.go
362 lines (303 loc) · 11.7 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
package service
import (
"encoding/json"
"fmt"
"strings"
appsv1 "github.com/openshift/api/apps/v1"
"sort"
"github.com/pkg/errors"
applabels "github.com/redhat-developer/odo/pkg/application/labels"
componentlabels "github.com/redhat-developer/odo/pkg/component/labels"
"github.com/redhat-developer/odo/pkg/occlient"
"github.com/redhat-developer/odo/pkg/util"
)
const provisionedAndBoundStatus = "ProvisionedAndBound"
const provisionedAndLinkedStatus = "ProvisionedAndLinked"
// ServiceInfo holds all important information about one service
type ServiceInfo struct {
Name string
Type string
Status string
}
type ServiceClass struct {
Name string
Bindable bool
ShortDescription string
LongDescription string
Tags []string
VersionsAvailable []string
ServiceBrokerName string
}
type ServicePlanParameter struct {
Name string
HasDefaultValue bool
DefaultValue interface{}
Type string
Required bool
}
type ServicePlans struct {
Name string
DisplayName string
Description string
Parameters []ServicePlanParameter
}
// ListCatalog lists all the available service types
func ListCatalog(client *occlient.Client) ([]occlient.Service, error) {
clusterServiceClasses, err := client.GetClusterServiceClassExternalNamesAndPlans()
if err != nil {
return nil, errors.Wrapf(err, "unable to get cluster serviceClassExternalName")
}
// Sorting service classes alphabetically
// Reference: https://golang.org/pkg/sort/#example_Slice
sort.Slice(clusterServiceClasses, func(i, j int) bool {
return clusterServiceClasses[i].Name < clusterServiceClasses[j].Name
})
return clusterServiceClasses, nil
}
// Search searches for the services
func Search(client *occlient.Client, name string) ([]occlient.Service, error) {
var result []occlient.Service
serviceList, err := ListCatalog(client)
if err != nil {
return nil, errors.Wrap(err, "unable to list services")
}
// do a partial search in all the services
for _, service := range serviceList {
if strings.Contains(service.Name, name) {
result = append(result, service)
}
}
return result, nil
}
// CreateService creates new service from serviceCatalog
func CreateService(client *occlient.Client, serviceName string, serviceType string, servicePlan string, parameters []string, applicationName string) error {
labels := componentlabels.GetLabels(serviceName, applicationName, true)
// save service type as label
labels[componentlabels.ComponentTypeLabel] = serviceType
mapOfParameters := util.ConvertKeyValueStringToMap(parameters)
err := client.CreateServiceInstance(serviceName, serviceType, servicePlan, mapOfParameters, labels)
if err != nil {
return errors.Wrap(err, "unable to create service instance")
}
return nil
}
// DeleteService will delete the service with the provided `name`
func DeleteService(client *occlient.Client, name string, applicationName string) error {
labels := componentlabels.GetLabels(name, applicationName, false)
err := client.DeleteServiceInstance(labels)
if err != nil {
return errors.Wrapf(err, "unable to retrieve list of services")
}
return nil
}
// List lists all the deployed services
func List(client *occlient.Client, applicationName string) ([]ServiceInfo, error) {
labels := map[string]string{
applabels.ApplicationLabel: applicationName,
}
//since, service is associated with application, it consist of application label as well
// which we can give as a selector
applicationSelector := util.ConvertLabelsToSelector(labels)
// get service instance list based on given selector
serviceInstanceList, err := client.GetServiceInstanceList(applicationSelector)
if err != nil {
return nil, errors.Wrapf(err, "unable to list services")
}
var services []ServiceInfo
// Iterate through serviceInstanceList and add to service
for _, elem := range serviceInstanceList {
services = append(services, ServiceInfo{Name: elem.Labels[componentlabels.ComponentLabel], Type: elem.Labels[componentlabels.ComponentTypeLabel], Status: elem.Status.Conditions[0].Reason})
}
return services, nil
}
// ListWithDetailedStatus lists all the deployed services and additionally provides a "smart" status for each one of them
// The smart status takes into account how Services are used in odo.
// So when a secret has been created as a result of the created ServiceBinding, we set the appropriate status
// Same for when the secret has been "linked" into the deploymentconfig
func ListWithDetailedStatus(client *occlient.Client, applicationName string) ([]ServiceInfo, error) {
services, err := List(client, applicationName)
if err != nil {
return nil, err
}
// retrieve secrets in order to set status
secrets, err := client.ListSecrets("")
if err != nil {
return nil, errors.Wrapf(err, "unable to list secrets as part of the bindings check")
}
// use the standard selector to retrieve DeploymentConfigs
// these are used in order to update the status of a service
// because if a DeploymentConfig contains a secret with the service name
// then it has been successfully linked
labels := map[string]string{
applabels.ApplicationLabel: applicationName,
}
applicationSelector := util.ConvertLabelsToSelector(labels)
deploymentConfigs, err := client.GetDeploymentConfigsFromSelector(applicationSelector)
if err != nil {
return nil, err
}
// go through each service and see if there is a secret that has been created
// if so, update the status of the service
for i, service := range services {
for _, secret := range secrets {
if secret.Name == service.Name {
// this is the default status when the secret exists
services[i].Status = provisionedAndBoundStatus
// if we find that the dc contains a link to the secret
// we update the status to be even more specific
updateStatusIfMatchingDeploymentExists(deploymentConfigs, secret.Name, services, i)
break
}
}
}
return services, nil
}
func updateStatusIfMatchingDeploymentExists(dcs []appsv1.DeploymentConfig, secretName string,
services []ServiceInfo, index int) {
for _, dc := range dcs {
foundMatchingSecret := false
for _, env := range dc.Spec.Template.Spec.Containers[0].EnvFrom {
if env.SecretRef.Name == secretName {
services[index].Status = provisionedAndLinkedStatus
}
foundMatchingSecret = true
break
}
if foundMatchingSecret {
break
}
}
}
// GetSvcByType returns the matching (by type) service or nil of there are no matches
func GetSvcByType(client *occlient.Client, serviceType string) (*occlient.Service, error) {
catalogList, err := ListCatalog(client)
if err != nil {
return nil, errors.Wrapf(err, "unable to list catalog")
}
for _, supported := range catalogList {
if serviceType == supported.Name {
return &supported, nil
}
}
return nil, nil
}
// SvcExists Checks whether a service with the given name exists in the current application or not
// serviceName is the service name to perform check for
// The first returned parameter is a bool indicating if a service with the given name already exists or not
// The second returned parameter is the error that might occurs while execution
func SvcExists(client *occlient.Client, serviceName, applicationName string) (bool, error) {
serviceList, err := List(client, applicationName)
if err != nil {
return false, errors.Wrap(err, "unable to get the service list")
}
for _, service := range serviceList {
if service.Name == serviceName {
return true, nil
}
}
return false, nil
}
// GetServiceClassAndPlans returns the service class details with the associated plans
// serviceName is the name of the service class
// the first parameter returned is the ServiceClass object
// the second parameter returned is the array of ServicePlans associated with the service class
func GetServiceClassAndPlans(client *occlient.Client, serviceName string) (ServiceClass, []ServicePlans, error) {
result, err := client.GetClusterServiceClass(serviceName)
if err != nil {
return ServiceClass{}, nil, errors.Wrap(err, "unable to get the given service")
}
var meta map[string]interface{}
err = json.Unmarshal(result.Spec.ExternalMetadata.Raw, &meta)
if err != nil {
return ServiceClass{}, nil, errors.Wrap(err, "unable to unmarshal data the given service")
}
service := ServiceClass{
Name: result.Spec.ExternalName,
Bindable: result.Spec.Bindable,
ShortDescription: result.Spec.Description,
Tags: result.Spec.Tags,
ServiceBrokerName: result.Spec.ClusterServiceBrokerName,
}
if val, ok := meta["longDescription"]; ok {
service.LongDescription = val.(string)
}
if val, ok := meta["dependencies"]; ok {
versions := fmt.Sprint(val)
versions = strings.Replace(versions, "[", "", -1)
versions = strings.Replace(versions, "]", "", -1)
service.VersionsAvailable = strings.Split(versions, " ")
}
// get the plans according to the service name
planResults, err := client.GetClusterPlansFromServiceName(result.Name)
if err != nil {
return ServiceClass{}, nil, errors.Wrap(err, "unable to get plans for the given service")
}
var plans []ServicePlans
for _, result := range planResults {
plan := ServicePlans{
Name: result.Spec.ExternalName,
Description: result.Spec.Description,
}
// get the display name from the external meta data
var externalMetaData map[string]interface{}
err = json.Unmarshal(result.Spec.ExternalMetadata.Raw, &externalMetaData)
if err != nil {
return ServiceClass{}, nil, errors.Wrap(err, "unable to unmarshal data the given service")
}
if val, ok := externalMetaData["displayName"]; ok {
plan.DisplayName = val.(string)
}
// get the create parameters
var createParameter map[string]interface{}
err = json.Unmarshal(result.Spec.ServiceInstanceCreateParameterSchema.Raw, &createParameter)
if err != nil {
return ServiceClass{}, nil, errors.Wrap(err, "unable to unmarshal data the given service")
}
// record the values specified in the "required" field
// these parameters are not trully required from a user perspective
// since some of the parameters might have default values
requiredParameterNames := []string{}
if val, ok := createParameter["required"]; ok {
required := fmt.Sprint(val)
required = strings.Replace(required, "[", "", -1)
required = strings.Replace(required, "]", "", -1)
requiredParameterNames = strings.Split(required, " ")
}
// set the optional properties by inspecting additionalProperties
// and if it's true, then
if val, ok := createParameter["properties"]; ok {
propertiesMap, ok := val.(map[string]interface{})
if ok {
allServicePlanParameters := make([]ServicePlanParameter, 0, len(propertiesMap))
for propertyName, propertyValues := range propertiesMap {
servicePlanParameter := ServicePlanParameter{
Name: propertyName,
}
// we set the Required flag is the name of parameter
// is one of the parameters indicated as required
for _, name := range requiredParameterNames {
if name == propertyName {
servicePlanParameter.Required = true
}
}
propertyValuesMap, ok := propertyValues.(map[string]interface{})
if ok {
propertyDefaultValue, hasDefaultValue := propertyValuesMap["default"]
if hasDefaultValue {
servicePlanParameter.HasDefaultValue = true
servicePlanParameter.DefaultValue = propertyDefaultValue
}
if propertyType, ok := propertyValuesMap["type"]; ok {
// the type of the "type" value is always a string, so we just convert it
servicePlanParameter.Type = fmt.Sprintf("%v", propertyType)
}
}
allServicePlanParameters = append(allServicePlanParameters, servicePlanParameter)
}
plan.Parameters = allServicePlanParameters
}
}
plans = append(plans, plan)
}
return service, plans, nil
}