github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/runtime/kubernetes/util.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); 2 // you may not use this file except in compliance with the License. 3 // You may obtain a copy of the License at 4 // 5 // https://www.apache.org/licenses/LICENSE-2.0 6 // 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 // Original source: github.com/micro/go-micro/v3/runtime/kubernetes/util.go 14 15 package kubernetes 16 17 import ( 18 "encoding/base64" 19 "encoding/json" 20 "fmt" 21 "strings" 22 23 "github.com/tickoalcantara12/micro/v3/service/logger" 24 "github.com/tickoalcantara12/micro/v3/service/runtime" 25 "github.com/tickoalcantara12/micro/v3/service/runtime/kubernetes/api" 26 "github.com/tickoalcantara12/micro/v3/service/runtime/kubernetes/client" 27 ) 28 29 // getServices queries kubernetes for services. It gets information from both the pods and the 30 // deployments 31 func (k *kubernetes) getServices(opts ...client.GetOption) ([]*runtime.Service, error) { 32 // get the deployments 33 depList := new(client.DeploymentList) 34 d := &client.Resource{ 35 Kind: "deployment", 36 Value: depList, 37 } 38 if err := k.client.Get(d, opts...); err != nil { 39 return nil, err 40 } 41 42 srvMap := make(map[string]*runtime.Service, len(depList.Items)) 43 44 // loop through the services and create a deployment for each 45 for _, kdep := range depList.Items { 46 srv := &runtime.Service{ 47 Name: kdep.Metadata.Labels["name"], 48 Version: kdep.Metadata.Labels["version"], 49 Source: kdep.Metadata.Labels["source"], 50 Metadata: kdep.Metadata.Annotations, 51 } 52 53 // this metadata was injected by the k8s runtime 54 delete(srv.Metadata, "name") 55 delete(srv.Metadata, "version") 56 delete(srv.Metadata, "source") 57 58 // parse out deployment status and inject into service metadata 59 if len(kdep.Status.Conditions) > 0 { 60 srv.Status = transformStatus(kdep.Status.Conditions[0].Type) 61 srv.Metadata["started"] = kdep.Status.Conditions[0].LastUpdateTime 62 } else { 63 srv.Status = runtime.Unknown 64 } 65 66 srvMap[resourceName(srv)] = srv 67 } 68 69 // get the pods from k8s 70 podList := new(client.PodList) 71 p := &client.Resource{ 72 Kind: "pod", 73 Value: podList, 74 } 75 if err := k.client.Get(p, opts...); err != nil { 76 logger.Errorf("Error fetching pods: %v", err) 77 return nil, nil 78 } 79 80 for _, item := range podList.Items { 81 // skip if we can't get the container 82 if len(item.Status.Containers) == 0 { 83 continue 84 } 85 86 // lookup the service in the map 87 key := resourceName(&runtime.Service{ 88 Name: item.Metadata.Labels["name"], 89 Version: item.Metadata.Labels["version"], 90 }) 91 srv, ok := srvMap[key] 92 if !ok { 93 continue 94 } 95 96 // use the pod status over the deployment status (contains more details) 97 srv.Status = transformStatus(item.Status.Phase) 98 99 // set start time 100 state := item.Status.Containers[0].State 101 if state.Running != nil { 102 srv.Metadata["started"] = state.Running.Started 103 } 104 105 // set status from waiting 106 if v := state.Waiting; v != nil { 107 srv.Status = runtime.Pending 108 } 109 } 110 111 // turn the map into an array 112 services := make([]*runtime.Service, 0, len(srvMap)) 113 for _, srv := range srvMap { 114 services = append(services, srv) 115 } 116 return services, nil 117 } 118 119 func (k *kubernetes) createCredentials(service *runtime.Service, options *runtime.CreateOptions) error { 120 if len(options.Secrets) == 0 { 121 return nil 122 } 123 124 data := make(map[string]string, len(options.Secrets)) 125 for key, value := range options.Secrets { 126 data[key] = base64.StdEncoding.EncodeToString([]byte(value)) 127 } 128 129 // construct the k8s secret object 130 secret := &client.Secret{ 131 Type: "Opaque", 132 Data: data, 133 Metadata: &client.Metadata{ 134 Name: resourceName(service), 135 Namespace: options.Namespace, 136 }, 137 } 138 139 // crete the secret in kubernetes 140 err := k.client.Create(&client.Resource{ 141 Kind: "secret", 142 Name: resourceName(service), 143 Value: secret, 144 }, client.CreateNamespace(options.Namespace)) 145 146 // ignore the error if the creds already exist 147 if err == nil || parseError(err).Reason == "AlreadyExists" { 148 return nil 149 } 150 151 if logger.V(logger.WarnLevel, logger.DefaultLogger) { 152 logger.Warnf("Error generating auth credentials for service: %v", err) 153 } 154 return err 155 } 156 157 func (k *kubernetes) deleteCredentials(service *runtime.Service, options *runtime.CreateOptions) error { 158 // construct the k8s secret object 159 secret := &client.Secret{ 160 Type: "Opaque", 161 Metadata: &client.Metadata{ 162 Name: resourceName(service), 163 Namespace: options.Namespace, 164 }, 165 } 166 167 // crete the secret in kubernetes 168 err := k.client.Delete(&client.Resource{ 169 Kind: "secret", 170 Name: resourceName(service), 171 Value: secret, 172 }, client.DeleteNamespace(options.Namespace)) 173 174 if err != nil && logger.V(logger.WarnLevel, logger.DefaultLogger) { 175 logger.Warnf("Error deleting auth credentials for service: %v", err) 176 } 177 178 return err 179 } 180 181 func resourceName(srv *runtime.Service) string { 182 return fmt.Sprintf("%v-%v", client.Format(srv.Name), client.Format(srv.Version)) 183 } 184 185 // transformStatus takes a deployment status (deploymentcondition.type) and transforms it into a 186 // runtime service status, e.g. containercreating => starting 187 func transformStatus(depStatus string) runtime.ServiceStatus { 188 switch strings.ToLower(depStatus) { 189 case "pending": 190 return runtime.Pending 191 case "containercreating": 192 return runtime.Starting 193 case "imagepullbackoff": 194 return runtime.Error 195 case "crashloopbackoff": 196 return runtime.Error 197 case "error": 198 return runtime.Error 199 case "running": 200 return runtime.Running 201 case "available": 202 return runtime.Running 203 case "succeeded": 204 return runtime.Stopped 205 case "failed": 206 return runtime.Error 207 case "waiting": 208 return runtime.Pending 209 case "terminated": 210 return runtime.Stopped 211 default: 212 return runtime.Unknown 213 } 214 } 215 216 func parseError(err error) *api.Status { 217 status := new(api.Status) 218 json.Unmarshal([]byte(err.Error()), &status) 219 return status 220 }