1 package service
2
3 import (
4 "fmt"
5 "strings"
6
7 "github.com/redhat-developer/odo/pkg/libdevfile"
8 "github.com/redhat-developer/odo/pkg/log"
9
10 devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
11 "github.com/devfile/library/v2/pkg/devfile/parser"
12 devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem"
13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15 "k8s.io/klog"
16
17 "github.com/redhat-developer/odo/pkg/kclient"
18 odolabels "github.com/redhat-developer/odo/pkg/labels"
19
20 olm "github.com/operator-framework/api/pkg/operators/v1alpha1"
21 )
22
23
24 const LinkLabel = "app.kubernetes.io/link-name"
25
26
27 const ServiceLabel = "app.kubernetes.io/service-name"
28
29
30 const ServiceKind = "app.kubernetes.io/service-kind"
31
32
33 func IsLinkSecret(labels map[string]string) bool {
34 _, hasLinkLabel := labels[LinkLabel]
35 _, hasServiceLabel := labels[ServiceLabel]
36 _, hasServiceKindLabel := labels[ServiceKind]
37 return hasLinkLabel && hasServiceLabel && hasServiceKindLabel
38 }
39
40
41
42
43 func DeleteOperatorService(client kclient.ClientInterface, serviceName string) error {
44 kind, name, err := SplitServiceKindName(serviceName)
45 if err != nil {
46 return fmt.Errorf("refer %q to see list of running services: %w", serviceName, err)
47 }
48
49 csv, err := client.GetCSVWithCR(kind)
50 if err != nil {
51 return err
52 }
53
54 if csv == nil {
55 return fmt.Errorf("unable to find any Operator providing the service %q", kind)
56 }
57
58 crs := client.GetCustomResourcesFromCSV(csv)
59 var cr *olm.CRDDescription
60
61 for _, c := range *crs {
62 customResource := c
63 if customResource.Kind == kind {
64 cr = &customResource
65 break
66 }
67 }
68
69 return client.DeleteDynamicResource(name, kclient.GetGVRFromCR(cr), false)
70 }
71
72
73
74 func ListOperatorServices(client kclient.ClientInterface) ([]unstructured.Unstructured, []string, error) {
75 klog.V(4).Info("Getting list of services")
76
77
78 csvs, err := client.ListClusterServiceVersions()
79 if err != nil {
80 return nil, nil, err
81 }
82
83 if err != nil {
84 return nil, nil, fmt.Errorf("unable to list operator backed services: %w", err)
85 }
86
87 var allCRInstances []unstructured.Unstructured
88 var failedListingCR []string
89
90
91 for _, csv := range csvs.Items {
92 clusterServiceVersion := csv
93 klog.V(4).Infof("Getting services started from operator: %s", clusterServiceVersion.Name)
94 customResources := client.GetCustomResourcesFromCSV(&clusterServiceVersion)
95
96
97 var instances []unstructured.Unstructured
98 for _, cr := range *customResources {
99 customResource := cr
100
101 list, err := GetCRInstances(client, &customResource)
102 if err != nil {
103 crName := strings.Join([]string{csv.Name, cr.Kind}, "/")
104 klog.V(4).Infof("Failed to list instances of %q with error: %s", crName, err.Error())
105 failedListingCR = append(failedListingCR, crName)
106 continue
107 }
108
109 if len(list.Items) > 0 {
110 instances = append(instances, list.Items...)
111 }
112 }
113
114
115 allCRInstances = append(allCRInstances, instances...)
116 }
117
118 return allCRInstances, failedListingCR, nil
119 }
120
121
122
123 func GetCRInstances(client kclient.ClientInterface, customResource *olm.CRDDescription) (*unstructured.UnstructuredList, error) {
124 klog.V(4).Infof("Getting instances of: %s\n", customResource.Name)
125
126 instances, err := client.ListDynamicResources("", kclient.GetGVRFromCR(customResource), "")
127 if err != nil {
128 return nil, err
129 }
130
131 return instances, nil
132 }
133
134
135
136 func SplitServiceKindName(serviceName string) (string, string, error) {
137 sn := strings.SplitN(serviceName, "/", 2)
138 if len(sn) != 2 || sn[0] == "" || sn[1] == "" {
139 return "", "", fmt.Errorf("couldn't split %q into exactly two", serviceName)
140 }
141
142 kind := sn[0]
143 name := sn[1]
144
145 return kind, name, nil
146 }
147
148
149 func PushKubernetesResources(client kclient.ClientInterface, devfileObj parser.DevfileObj, k8sComponents []devfile.Component, labels map[string]string, annotations map[string]string, context, mode string, reference metav1.OwnerReference) error {
150
151 csvSupported, err := client.IsCSVSupported()
152 if err != nil {
153 return err
154 }
155
156 var deployed map[string]DeployedInfo
157
158 if csvSupported {
159 deployed, err = ListDeployedServices(client, labels)
160 if err != nil {
161 return err
162 }
163
164 for key, deployedResource := range deployed {
165 if deployedResource.isLinkResource {
166 delete(deployed, key)
167 }
168 }
169 }
170
171
172 for _, c := range k8sComponents {
173 uList, er := libdevfile.GetK8sComponentAsUnstructuredList(devfileObj, c.Name, context, devfilefs.DefaultFs{})
174 if er != nil {
175 return er
176 }
177 for _, u := range uList {
178 var found bool
179 currentOwnerReferences := u.GetOwnerReferences()
180 for _, ref := range currentOwnerReferences {
181 if ref.UID == reference.UID {
182 found = true
183 break
184 }
185 }
186 if !found {
187 currentOwnerReferences = append(currentOwnerReferences, reference)
188 u.SetOwnerReferences(currentOwnerReferences)
189 }
190 er = PushKubernetesResource(client, u, labels, annotations, mode)
191 if er != nil {
192 return er
193 }
194 if csvSupported {
195 delete(deployed, u.GetKind()+"/"+u.GetName())
196 }
197 }
198 }
199
200 if csvSupported {
201 for key, val := range deployed {
202 if isLinkResource(val.Kind) {
203 continue
204 }
205 err = DeleteOperatorService(client, key)
206 if err != nil {
207 return err
208
209 }
210 }
211 }
212
213 return nil
214 }
215
216
217
218 func PushKubernetesResource(client kclient.ClientInterface, u unstructured.Unstructured, labels map[string]string, annotations map[string]string, mode string) error {
219 sboSupported, err := client.IsServiceBindingSupported()
220 if err != nil {
221 return err
222 }
223
224
225 if isLinkResource(u.GetKind()) && mode == odolabels.ComponentDevMode && !sboSupported {
226
227 return pushLinksWithoutOperator(client, u, labels)
228 }
229
230
231 u.SetLabels(mergeMaps(u.GetLabels(), labels))
232
233
234 u.SetAnnotations(mergeMaps(u.GetAnnotations(), annotations))
235
236 _, err = updateOperatorService(client, u)
237 return err
238 }
239
240 func mergeMaps(maps ...map[string]string) map[string]string {
241 mergedMaps := map[string]string{}
242
243 for _, l := range maps {
244 for k, v := range l {
245 mergedMaps[k] = v
246 }
247 }
248
249 return mergedMaps
250 }
251
252
253 type DeployedInfo struct {
254 Kind string
255 Name string
256 isLinkResource bool
257 }
258
259 func ListDeployedServices(client kclient.ClientInterface, labels map[string]string) (map[string]DeployedInfo, error) {
260 deployed := map[string]DeployedInfo{}
261
262 deployedServices, _, err := ListOperatorServices(client)
263 if err != nil {
264
265 return nil, err
266 }
267 for _, svc := range deployedServices {
268 name := svc.GetName()
269 kind := svc.GetKind()
270 deployedLabels := svc.GetLabels()
271 if odolabels.IsManagedByOdo(deployedLabels) && odolabels.GetComponentName(deployedLabels) == odolabels.GetComponentName(labels) {
272 deployed[kind+"/"+name] = DeployedInfo{
273 Kind: kind,
274 Name: name,
275 isLinkResource: isLinkResource(kind),
276 }
277 }
278 }
279
280 return deployed, nil
281 }
282
283 func isLinkResource(kind string) bool {
284 return kind == "ServiceBinding"
285 }
286
287
288
289 func updateOperatorService(client kclient.ClientInterface, u unstructured.Unstructured) (bool, error) {
290
291
292 updated, err := client.PatchDynamicResource(u)
293 if err != nil {
294 return false, err
295 }
296
297 if updated {
298 createSpinner := log.Spinnerf("Creating resource %s/%s", u.GetKind(), u.GetName())
299 createSpinner.End(true)
300 }
301 return updated, err
302 }
303
View as plain text