Fix ensureNamespace behavior failing on missing ns
[multicloud/k8s.git] / src / k8splugin / internal / app / client.go
1 /*
2 Copyright 2018 Intel Corporation.
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6     http://www.apache.org/licenses/LICENSE-2.0
7 Unless required by applicable law or agreed to in writing, software
8 distributed under the License is distributed on an "AS IS" BASIS,
9 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 See the License for the specific language governing permissions and
11 limitations under the License.
12 */
13
14 package app
15
16 import (
17         "os"
18         "strings"
19         "time"
20
21         "github.com/onap/multicloud-k8s/src/k8splugin/internal/connection"
22         "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
23         log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils"
24         "github.com/onap/multicloud-k8s/src/k8splugin/internal/plugin"
25
26         pkgerrors "github.com/pkg/errors"
27         "k8s.io/apimachinery/pkg/api/meta"
28         "k8s.io/apimachinery/pkg/runtime/schema"
29         "k8s.io/client-go/discovery/cached/disk"
30         "k8s.io/client-go/dynamic"
31         "k8s.io/client-go/kubernetes"
32         "k8s.io/client-go/restmapper"
33         "k8s.io/client-go/tools/clientcmd"
34 )
35
36 // KubernetesClient encapsulates the different clients' interfaces
37 // we need when interacting with a Kubernetes cluster
38 type KubernetesClient struct {
39         clientSet      kubernetes.Interface
40         dynamicClient  dynamic.Interface
41         discoverClient *disk.CachedDiscoveryClient
42         restMapper     meta.RESTMapper
43         instanceID     string
44 }
45
46 // getKubeConfig uses the connectivity client to get the kubeconfig based on the name
47 // of the cloudregion. This is written out to a file.
48 func (k *KubernetesClient) getKubeConfig(cloudregion string) (string, error) {
49
50         conn := connection.NewConnectionClient()
51         kubeConfigPath, err := conn.Download(cloudregion)
52         if err != nil {
53                 return "", pkgerrors.Wrap(err, "Downloading kubeconfig")
54         }
55
56         return kubeConfigPath, nil
57 }
58
59 // init loads the Kubernetes configuation values stored into the local configuration file
60 func (k *KubernetesClient) init(cloudregion string, iid string) error {
61         if cloudregion == "" {
62                 return pkgerrors.New("Cloudregion is empty")
63         }
64
65         if iid == "" {
66                 return pkgerrors.New("Instance ID is empty")
67         }
68
69         k.instanceID = iid
70
71         configPath, err := k.getKubeConfig(cloudregion)
72         if err != nil {
73                 return pkgerrors.Wrap(err, "Get kubeconfig file")
74         }
75
76         //Remove kubeconfigfile after the clients are created
77         defer os.Remove(configPath)
78
79         config, err := clientcmd.BuildConfigFromFlags("", configPath)
80         if err != nil {
81                 return pkgerrors.Wrap(err, "setConfig: Build config from flags raised an error")
82         }
83
84         k.clientSet, err = kubernetes.NewForConfig(config)
85         if err != nil {
86                 return err
87         }
88
89         k.dynamicClient, err = dynamic.NewForConfig(config)
90         if err != nil {
91                 return pkgerrors.Wrap(err, "Creating dynamic client")
92         }
93
94         k.discoverClient, err = disk.NewCachedDiscoveryClientForConfig(config, os.TempDir(), "", 10*time.Minute)
95         if err != nil {
96                 return pkgerrors.Wrap(err, "Creating discovery client")
97         }
98
99         k.restMapper = restmapper.NewDeferredDiscoveryRESTMapper(k.discoverClient)
100
101         return nil
102 }
103
104 func (k *KubernetesClient) ensureNamespace(namespace string) error {
105
106         pluginImpl, err := plugin.GetPluginByKind("Namespace")
107         if err != nil {
108                 return pkgerrors.Wrap(err, "Loading Namespace Plugin")
109         }
110
111         ns, err := pluginImpl.Get(helm.KubernetesResource{
112                 Name: namespace,
113                 GVK: schema.GroupVersionKind{
114                         Group:   "",
115                         Version: "v1",
116                         Kind:    "Namespace",
117                 },
118         }, namespace, k)
119
120         // Check for errors getting the namespace while ignoring errors where the namespace does not exist
121         // Error message when namespace does not exist: "namespaces "namespace-name" not found"
122         if err != nil && strings.Contains(err.Error(), "not found") == false {
123                 log.Error("Error checking for namespace", log.Fields{
124                         "error":     err,
125                         "namespace": namespace,
126                 })
127                 return pkgerrors.Wrap(err, "Error checking for namespace: "+namespace)
128         }
129
130         if ns == "" {
131                 log.Info("Creating Namespace", log.Fields{
132                         "namespace": namespace,
133                 })
134
135                 _, err = pluginImpl.Create("", namespace, k)
136                 if err != nil {
137                         log.Error("Error Creating Namespace", log.Fields{
138                                 "error":     err,
139                                 "namespace": namespace,
140                         })
141                         return pkgerrors.Wrap(err, "Error creating "+namespace+" namespace")
142                 }
143         }
144         return nil
145 }
146
147 func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate,
148         namespace string) (helm.KubernetesResource, error) {
149
150         if _, err := os.Stat(resTempl.FilePath); os.IsNotExist(err) {
151                 return helm.KubernetesResource{}, pkgerrors.New("File " + resTempl.FilePath + "does not exists")
152         }
153
154         log.Info("Processing Kubernetes Resource", log.Fields{
155                 "filepath": resTempl.FilePath,
156         })
157
158         pluginImpl, err := plugin.GetPluginByKind(resTempl.GVK.Kind)
159         if err != nil {
160                 return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error loading plugin")
161         }
162
163         createdResourceName, err := pluginImpl.Create(resTempl.FilePath, namespace, k)
164         if err != nil {
165                 log.Error("Error Creating Resource", log.Fields{
166                         "error":    err,
167                         "gvk":      resTempl.GVK,
168                         "filepath": resTempl.FilePath,
169                 })
170                 return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in plugin "+resTempl.GVK.Kind+" plugin")
171         }
172
173         log.Info("Created Kubernetes Resource", log.Fields{
174                 "resource": createdResourceName,
175                 "gvk":      resTempl.GVK,
176         })
177
178         return helm.KubernetesResource{
179                 GVK:  resTempl.GVK,
180                 Name: createdResourceName,
181         }, nil
182 }
183
184 func (k *KubernetesClient) createResources(sortedTemplates []helm.KubernetesResourceTemplate,
185         namespace string) ([]helm.KubernetesResource, error) {
186
187         err := k.ensureNamespace(namespace)
188         if err != nil {
189                 return nil, pkgerrors.Wrap(err, "Creating Namespace")
190         }
191
192         var createdResources []helm.KubernetesResource
193         for _, resTempl := range sortedTemplates {
194                 resCreated, err := k.createKind(resTempl, namespace)
195                 if err != nil {
196                         return nil, pkgerrors.Wrapf(err, "Error creating kind: %+v", resTempl.GVK)
197                 }
198                 createdResources = append(createdResources, resCreated)
199         }
200
201         return createdResources, nil
202 }
203
204 func (k *KubernetesClient) deleteKind(resource helm.KubernetesResource, namespace string) error {
205         log.Warn("Deleting Resource", log.Fields{
206                 "gvk":      resource.GVK,
207                 "resource": resource.Name,
208         })
209
210         pluginImpl, err := plugin.GetPluginByKind(resource.GVK.Kind)
211         if err != nil {
212                 return pkgerrors.Wrap(err, "Error loading plugin")
213         }
214
215         err = pluginImpl.Delete(resource, namespace, k)
216         if err != nil {
217                 return pkgerrors.Wrap(err, "Error deleting "+resource.Name)
218         }
219
220         return nil
221 }
222
223 func (k *KubernetesClient) deleteResources(resources []helm.KubernetesResource, namespace string) error {
224         //TODO: Investigate if deletion should be in a particular order
225         for _, res := range resources {
226                 err := k.deleteKind(res, namespace)
227                 if err != nil {
228                         return pkgerrors.Wrap(err, "Deleting resources")
229                 }
230         }
231
232         return nil
233 }
234
235 //GetMapper returns the RESTMapper that was created for this client
236 func (k *KubernetesClient) GetMapper() meta.RESTMapper {
237         return k.restMapper
238 }
239
240 //GetDynamicClient returns the dynamic client that is needed for
241 //unstructured REST calls to the apiserver
242 func (k *KubernetesClient) GetDynamicClient() dynamic.Interface {
243         return k.dynamicClient
244 }
245
246 // GetStandardClient returns the standard client that can be used to handle
247 // standard kubernetes kinds
248 func (k *KubernetesClient) GetStandardClient() kubernetes.Interface {
249         return k.clientSet
250 }
251
252 //GetInstanceID returns the instanceID that is injected into all the
253 //resources created by the plugin
254 func (k *KubernetesClient) GetInstanceID() string {
255         return k.instanceID
256 }