1 package binding
2
3 import (
4 "fmt"
5 "path/filepath"
6
7 "k8s.io/klog"
8
9 bindingApis "github.com/redhat-developer/service-binding-operator/apis"
10 bindingApi "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
11 specApi "github.com/redhat-developer/service-binding-operator/apis/spec/v1alpha3"
12
13 "github.com/redhat-developer/odo/pkg/project"
14
15 "github.com/devfile/library/v2/pkg/devfile/parser"
16 devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem"
17 "gopkg.in/yaml.v2"
18 appsv1 "k8s.io/api/apps/v1"
19 kerrors "k8s.io/apimachinery/pkg/api/errors"
20 "k8s.io/apimachinery/pkg/api/meta"
21 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
22 "k8s.io/apimachinery/pkg/runtime"
23
24 devfilev1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
25 parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
26
27 "github.com/redhat-developer/odo/pkg/api"
28 "github.com/redhat-developer/odo/pkg/binding/asker"
29 backendpkg "github.com/redhat-developer/odo/pkg/binding/backend"
30 "github.com/redhat-developer/odo/pkg/kclient"
31 "github.com/redhat-developer/odo/pkg/libdevfile"
32 clierrors "github.com/redhat-developer/odo/pkg/odo/cli/errors"
33 )
34
35 type BindingClient struct {
36
37 flagsBackend *backendpkg.FlagsBackend
38 interactiveBackend *backendpkg.InteractiveBackend
39
40
41 kubernetesClient kclient.ClientInterface
42 }
43
44 var _ Client = (*BindingClient)(nil)
45
46 func NewBindingClient(projectClient project.Client, kubernetesClient kclient.ClientInterface) *BindingClient {
47
48 askerClient := asker.NewSurveyAsker()
49 return &BindingClient{
50 flagsBackend: backendpkg.NewFlagsBackend(),
51 interactiveBackend: backendpkg.NewInteractiveBackend(askerClient, projectClient, kubernetesClient),
52 kubernetesClient: kubernetesClient,
53 }
54 }
55
56
57
58 func (o *BindingClient) GetFlags(flags map[string]string) map[string]string {
59 bindingFlags := map[string]string{}
60 for flag, value := range flags {
61 if flag == backendpkg.FLAG_NAME ||
62 flag == backendpkg.FLAG_WORKLOAD ||
63 flag == backendpkg.FLAG_SERVICE_NAMESPACE ||
64 flag == backendpkg.FLAG_SERVICE ||
65 flag == backendpkg.FLAG_BIND_AS_FILES ||
66 flag == backendpkg.FLAG_NAMING_STRATEGY {
67 bindingFlags[flag] = value
68 }
69 }
70 return bindingFlags
71 }
72
73 func (o *BindingClient) GetServiceInstances(namespace string) (map[string]unstructured.Unstructured, error) {
74 err := o.checkServiceBindingOperatorInstalled()
75 if err != nil {
76 return nil, err
77 }
78
79
80 bindableKind, err := o.kubernetesClient.GetBindableKinds()
81 if err != nil {
82 return nil, err
83 }
84
85
86 bindableKindRestMappings, err := o.kubernetesClient.GetBindableKindStatusRestMapping(bindableKind.Status)
87 if err != nil {
88 return nil, err
89 }
90
91 var bindableObjectMap = map[string]unstructured.Unstructured{}
92 for _, restMapping := range bindableKindRestMappings {
93
94
95 resources, err := o.kubernetesClient.ListDynamicResources(namespace, restMapping.Resource, "")
96 if err != nil {
97 if kerrors.IsNotFound(err) || kerrors.IsForbidden(err) {
98
99 klog.V(3).Infoln(err)
100 continue
101 }
102 return nil, err
103 }
104
105 for _, item := range resources.Items {
106
107 serviceName := fmt.Sprintf("%s (%s.%s)", item.GetName(), item.GetKind(), item.GroupVersionKind().Group)
108 bindableObjectMap[serviceName] = item
109 }
110
111 }
112
113 return bindableObjectMap, nil
114 }
115
116
117
118
119 func (o *BindingClient) GetBindingsFromDevfile(devfileObj parser.DevfileObj, context string) ([]api.ServiceBinding, error) {
120 result := []api.ServiceBinding{}
121 kubeComponents, err := devfileObj.Data.GetComponents(parsercommon.DevfileOptions{
122 ComponentOptions: parsercommon.ComponentOptions{
123 ComponentType: devfilev1alpha2.KubernetesComponentType,
124 },
125 })
126 if err != nil {
127 return nil, err
128 }
129 ocpComponents, err := devfileObj.Data.GetComponents(parsercommon.DevfileOptions{
130 ComponentOptions: parsercommon.ComponentOptions{
131 ComponentType: devfilev1alpha2.OpenshiftComponentType,
132 },
133 })
134 if err != nil {
135 return nil, err
136 }
137
138 allComponents := make([]devfilev1alpha2.Component, 0, len(kubeComponents)+len(ocpComponents))
139 allComponents = append(allComponents, kubeComponents...)
140 allComponents = append(allComponents, ocpComponents...)
141
142 var warning error
143 for _, component := range allComponents {
144 strCRD, err := libdevfile.GetK8sManifestsWithVariablesSubstituted(devfileObj, component.Name, context, devfilefs.DefaultFs{})
145 if err != nil {
146 return nil, err
147 }
148
149 u := unstructured.Unstructured{}
150 if err := yaml.Unmarshal([]byte(strCRD), &u.Object); err != nil {
151 return nil, err
152 }
153
154 switch u.GetObjectKind().GroupVersionKind() {
155 case bindingApi.GroupVersionKind:
156
157 var sbo bindingApi.ServiceBinding
158 err := kclient.ConvertUnstructuredToResource(u, &sbo)
159 if err != nil {
160 return nil, err
161 }
162
163 sb, err := kclient.APIServiceBindingFromBinding(sbo)
164 if err != nil {
165 return nil, err
166 }
167 sb.Status, err = o.getStatusFromBinding(sb.Name)
168 if err != nil {
169 warning = clierrors.NewWarning(kclient.NewNoConnectionError().Error(), err)
170 }
171
172 result = append(result, sb)
173
174 case specApi.GroupVersion.WithKind("ServiceBinding"):
175
176 var sbc specApi.ServiceBinding
177 err := kclient.ConvertUnstructuredToResource(u, &sbc)
178 if err != nil {
179 return nil, err
180 }
181
182 sb := kclient.APIServiceBindingFromSpec(sbc)
183 sb.Status, err = o.getStatusFromSpec(sb.Name)
184 if err != nil {
185 warning = clierrors.NewWarning(kclient.NewNoConnectionError().Error(), err)
186 }
187
188 result = append(result, sb)
189
190 }
191 }
192 return result, warning
193 }
194
195
196
197 func (o *BindingClient) GetBindingFromCluster(name string) (api.ServiceBinding, error) {
198 bindingSB, err := o.kubernetesClient.GetBindingServiceBinding(name)
199 if err == nil {
200 var sb api.ServiceBinding
201 sb, err = kclient.APIServiceBindingFromBinding(bindingSB)
202 if err != nil {
203 return api.ServiceBinding{}, err
204 }
205 sb.Status, err = o.getStatusFromBinding(bindingSB.Name)
206 if err != nil {
207 return api.ServiceBinding{}, err
208 }
209 return sb, nil
210 }
211 if err != nil && !kerrors.IsNotFound(err) {
212 return api.ServiceBinding{}, err
213 }
214
215 specSB, err := o.kubernetesClient.GetSpecServiceBinding(name)
216 if err == nil {
217 sb := kclient.APIServiceBindingFromSpec(specSB)
218 sb.Status, err = o.getStatusFromSpec(specSB.Name)
219 if err != nil {
220 return api.ServiceBinding{}, err
221 }
222 return sb, nil
223 }
224
225
226 if kerrors.IsNotFound(err) {
227 return api.ServiceBinding{}, fmt.Errorf("ServiceBinding %q not found", name)
228 }
229 return api.ServiceBinding{}, err
230 }
231
232
233
234 func (o *BindingClient) getStatusFromBinding(name string) (*api.ServiceBindingStatus, error) {
235 if o.kubernetesClient == nil {
236 return nil, nil
237 }
238 bindingSB, err := o.kubernetesClient.GetBindingServiceBinding(name)
239 if err != nil {
240 if kerrors.IsNotFound(err) {
241 return nil, nil
242 }
243 return nil, err
244 }
245
246 if injected := meta.IsStatusConditionTrue(bindingSB.Status.Conditions, bindingApis.InjectionReady); !injected {
247 return nil, nil
248 }
249
250 secretName := bindingSB.Status.Secret
251 secret, err := o.kubernetesClient.GetSecret(secretName, o.kubernetesClient.GetCurrentNamespace())
252 if err != nil {
253 return nil, err
254 }
255
256 bindings := make([]string, 0, len(secret.Data))
257 if bindingSB.Spec.BindAsFiles {
258 for k := range secret.Data {
259 bindingName := filepath.ToSlash(filepath.Join("${SERVICE_BINDING_ROOT}", name, k))
260 bindings = append(bindings, bindingName)
261 }
262 return &api.ServiceBindingStatus{
263 BindingFiles: bindings,
264 }, nil
265 }
266
267 for k := range secret.Data {
268 bindings = append(bindings, k)
269 }
270 return &api.ServiceBindingStatus{
271 BindingEnvVars: bindings,
272 }, nil
273 }
274
275
276
277 func (o *BindingClient) getStatusFromSpec(name string) (*api.ServiceBindingStatus, error) {
278 specSB, err := o.kubernetesClient.GetSpecServiceBinding(name)
279 if err != nil {
280 if kerrors.IsNotFound(err) {
281 return nil, nil
282 }
283 return nil, err
284 }
285
286 if injected := meta.IsStatusConditionTrue(specSB.Status.Conditions, bindingApis.InjectionReady); !injected {
287 return nil, nil
288 }
289
290 if specSB.Status.Binding == nil {
291 return nil, nil
292 }
293 secretName := specSB.Status.Binding.Name
294 secret, err := o.kubernetesClient.GetSecret(secretName, o.kubernetesClient.GetCurrentNamespace())
295 if err != nil {
296 return nil, err
297 }
298 bindingFiles := make([]string, 0, len(secret.Data))
299 bindingEnvVars := make([]string, 0, len(specSB.Spec.Env))
300 for k := range secret.Data {
301 bindingName := filepath.ToSlash(filepath.Join("${SERVICE_BINDING_ROOT}", name, k))
302 bindingFiles = append(bindingFiles, bindingName)
303 }
304 for _, env := range specSB.Spec.Env {
305 bindingEnvVars = append(bindingEnvVars, env.Name)
306 }
307 return &api.ServiceBindingStatus{
308 BindingFiles: bindingFiles,
309 BindingEnvVars: bindingEnvVars,
310 }, nil
311 }
312
313 func (o *BindingClient) checkServiceBindingOperatorInstalled() error {
314 isServiceBindingInstalled, err := o.kubernetesClient.IsServiceBindingSupported()
315 if err != nil {
316 return err
317 }
318 if !isServiceBindingInstalled {
319
320 return fmt.Errorf("Service Binding Operator is not installed on the cluster, please ensure it is installed before proceeding. " +
321 "See installation instructions: https://odo.dev/docs/command-reference/add-binding#installing-the-service-binding-operator")
322
323 }
324 return nil
325 }
326
327 func (o *BindingClient) CheckServiceBindingsInjectionDone(componentName string, appName string) (bool, error) {
328
329 deployment, err := o.kubernetesClient.GetOneDeployment(componentName, appName, true)
330 if err != nil {
331
332 if _, ok := err.(*kclient.DeploymentNotFoundError); ok {
333 return true, nil
334 }
335 return false, err
336 }
337 deploymentName := deployment.GetName()
338
339 specList, bindingList, err := o.kubernetesClient.ListServiceBindingsFromAllGroups()
340 if err != nil {
341
342 if runtime.IsNotRegisteredError(err) {
343 return true, nil
344 }
345 return false, err
346 }
347
348 for _, binding := range bindingList {
349 app := binding.Spec.Application
350 if app.Group != appsv1.SchemeGroupVersion.Group ||
351 app.Version != appsv1.SchemeGroupVersion.Version ||
352 (app.Kind != "Deployment" && app.Resource != "deployments") {
353 continue
354 }
355 if app.Name != deploymentName {
356 continue
357 }
358 if injected := meta.IsStatusConditionTrue(binding.Status.Conditions, bindingApis.InjectionReady); !injected {
359 return false, nil
360 }
361 }
362
363 for _, binding := range specList {
364 app := binding.Spec.Workload
365 if app.APIVersion != appsv1.SchemeGroupVersion.String() ||
366 app.Kind != "Deployment" {
367 continue
368 }
369 if app.Name != deploymentName {
370 continue
371 }
372 if injected := meta.IsStatusConditionTrue(binding.Status.Conditions, bindingApis.InjectionReady); !injected {
373 return false, nil
374 }
375 }
376
377 return true, nil
378 }
379
View as plain text