github.com/argoproj/argo-cd@v1.8.7/controller/cache/info.go (about) 1 package cache 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/argoproj/gitops-engine/pkg/utils/kube" 8 "github.com/argoproj/gitops-engine/pkg/utils/text" 9 v1 "k8s.io/api/core/v1" 10 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 11 "k8s.io/apimachinery/pkg/runtime" 12 k8snode "k8s.io/kubernetes/pkg/util/node" 13 14 "github.com/argoproj/argo-cd/common" 15 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 16 "github.com/argoproj/argo-cd/util/resource" 17 ) 18 19 func populateNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) { 20 gvk := un.GroupVersionKind() 21 revision := resource.GetRevision(un) 22 if revision > 0 { 23 res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Revision", Value: fmt.Sprintf("Rev:%v", revision)}) 24 } 25 switch gvk.Group { 26 case "": 27 switch gvk.Kind { 28 case kube.PodKind: 29 populatePodInfo(un, res) 30 return 31 case kube.ServiceKind: 32 populateServiceInfo(un, res) 33 return 34 } 35 case "extensions", "networking.k8s.io": 36 switch gvk.Kind { 37 case kube.IngressKind: 38 populateIngressInfo(un, res) 39 return 40 } 41 case "networking.istio.io": 42 switch gvk.Kind { 43 case "VirtualService": 44 populateIstioVirtualServiceInfo(un, res) 45 return 46 } 47 } 48 49 for k, v := range un.GetAnnotations() { 50 if strings.HasPrefix(k, common.AnnotationKeyLinkPrefix) { 51 if res.NetworkingInfo == nil { 52 res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{} 53 } 54 res.NetworkingInfo.ExternalURLs = append(res.NetworkingInfo.ExternalURLs, v) 55 } 56 } 57 } 58 59 func getIngress(un *unstructured.Unstructured) []v1.LoadBalancerIngress { 60 ingress, ok, err := unstructured.NestedSlice(un.Object, "status", "loadBalancer", "ingress") 61 if !ok || err != nil { 62 return nil 63 } 64 res := make([]v1.LoadBalancerIngress, 0) 65 for _, item := range ingress { 66 if lbIngress, ok := item.(map[string]interface{}); ok { 67 if hostname := lbIngress["hostname"]; hostname != nil { 68 res = append(res, v1.LoadBalancerIngress{Hostname: fmt.Sprintf("%s", hostname)}) 69 } else if ip := lbIngress["ip"]; ip != nil { 70 res = append(res, v1.LoadBalancerIngress{IP: fmt.Sprintf("%s", ip)}) 71 } 72 } 73 } 74 return res 75 } 76 77 func populateServiceInfo(un *unstructured.Unstructured, res *ResourceInfo) { 78 targetLabels, _, _ := unstructured.NestedStringMap(un.Object, "spec", "selector") 79 ingress := make([]v1.LoadBalancerIngress, 0) 80 if serviceType, ok, err := unstructured.NestedString(un.Object, "spec", "type"); ok && err == nil && serviceType == string(v1.ServiceTypeLoadBalancer) { 81 ingress = getIngress(un) 82 } 83 res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetLabels: targetLabels, Ingress: ingress} 84 } 85 86 func populateIngressInfo(un *unstructured.Unstructured, res *ResourceInfo) { 87 ingress := getIngress(un) 88 targetsMap := make(map[v1alpha1.ResourceRef]bool) 89 if backend, ok, err := unstructured.NestedMap(un.Object, "spec", "backend"); ok && err == nil { 90 targetsMap[v1alpha1.ResourceRef{ 91 Group: "", 92 Kind: kube.ServiceKind, 93 Namespace: un.GetNamespace(), 94 Name: fmt.Sprintf("%s", backend["serviceName"]), 95 }] = true 96 } 97 urlsSet := make(map[string]bool) 98 if rules, ok, err := unstructured.NestedSlice(un.Object, "spec", "rules"); ok && err == nil { 99 for i := range rules { 100 rule, ok := rules[i].(map[string]interface{}) 101 if !ok { 102 continue 103 } 104 host := rule["host"] 105 if host == nil || host == "" { 106 for i := range ingress { 107 host = text.FirstNonEmpty(ingress[i].Hostname, ingress[i].IP) 108 if host != "" { 109 break 110 } 111 } 112 } 113 paths, ok, err := unstructured.NestedSlice(rule, "http", "paths") 114 if !ok || err != nil { 115 continue 116 } 117 for i := range paths { 118 path, ok := paths[i].(map[string]interface{}) 119 if !ok { 120 continue 121 } 122 123 if serviceName, ok, err := unstructured.NestedString(path, "backend", "serviceName"); ok && err == nil { 124 targetsMap[v1alpha1.ResourceRef{ 125 Group: "", 126 Kind: kube.ServiceKind, 127 Namespace: un.GetNamespace(), 128 Name: serviceName, 129 }] = true 130 } 131 132 stringPort := "http" 133 if tls, ok, err := unstructured.NestedSlice(un.Object, "spec", "tls"); ok && err == nil { 134 for i := range tls { 135 tlsline, ok := tls[i].(map[string]interface{}) 136 secretName := tlsline["secretName"] 137 if ok && secretName != nil { 138 stringPort = "https" 139 } 140 tlshost := tlsline["host"] 141 if tlshost == host { 142 stringPort = "https" 143 } 144 } 145 } 146 147 externalURL := fmt.Sprintf("%s://%s", stringPort, host) 148 149 subPath := "" 150 if nestedPath, ok, err := unstructured.NestedString(path, "path"); ok && err == nil { 151 subPath = strings.TrimSuffix(nestedPath, "*") 152 } 153 externalURL += subPath 154 urlsSet[externalURL] = true 155 } 156 } 157 } 158 targets := make([]v1alpha1.ResourceRef, 0) 159 for target := range targetsMap { 160 targets = append(targets, target) 161 } 162 163 var urls []string 164 if res.NetworkingInfo != nil { 165 urls = res.NetworkingInfo.ExternalURLs 166 } 167 for url := range urlsSet { 168 urls = append(urls, url) 169 } 170 res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets, Ingress: ingress, ExternalURLs: urls} 171 } 172 173 func populateIstioVirtualServiceInfo(un *unstructured.Unstructured, res *ResourceInfo) { 174 targetsMap := make(map[v1alpha1.ResourceRef]bool) 175 176 if rules, ok, err := unstructured.NestedSlice(un.Object, "spec", "http"); ok && err == nil { 177 for i := range rules { 178 rule, ok := rules[i].(map[string]interface{}) 179 if !ok { 180 continue 181 } 182 routes, ok, err := unstructured.NestedSlice(rule, "route") 183 if !ok || err != nil { 184 continue 185 } 186 for i := range routes { 187 route, ok := routes[i].(map[string]interface{}) 188 if !ok { 189 continue 190 } 191 192 if hostName, ok, err := unstructured.NestedString(route, "destination", "host"); ok && err == nil { 193 hostSplits := strings.Split(hostName, ".") 194 serviceName := hostSplits[0] 195 196 var namespace string 197 if len(hostSplits) >= 2 { 198 namespace = hostSplits[1] 199 } else { 200 namespace = un.GetNamespace() 201 } 202 203 targetsMap[v1alpha1.ResourceRef{ 204 Kind: kube.ServiceKind, 205 Name: serviceName, 206 Namespace: namespace, 207 }] = true 208 } 209 } 210 } 211 } 212 targets := make([]v1alpha1.ResourceRef, 0) 213 for target := range targetsMap { 214 targets = append(targets, target) 215 } 216 217 res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets} 218 } 219 220 func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) { 221 pod := v1.Pod{} 222 err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &pod) 223 if err != nil { 224 return 225 } 226 restarts := 0 227 totalContainers := len(pod.Spec.Containers) 228 readyContainers := 0 229 230 reason := string(pod.Status.Phase) 231 if pod.Status.Reason != "" { 232 reason = pod.Status.Reason 233 } 234 235 imagesSet := make(map[string]bool) 236 for _, container := range pod.Spec.InitContainers { 237 imagesSet[container.Image] = true 238 } 239 for _, container := range pod.Spec.Containers { 240 imagesSet[container.Image] = true 241 } 242 243 res.Images = nil 244 for image := range imagesSet { 245 res.Images = append(res.Images, image) 246 } 247 248 initializing := false 249 for i := range pod.Status.InitContainerStatuses { 250 container := pod.Status.InitContainerStatuses[i] 251 restarts += int(container.RestartCount) 252 switch { 253 case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0: 254 continue 255 case container.State.Terminated != nil: 256 // initialization is failed 257 if len(container.State.Terminated.Reason) == 0 { 258 if container.State.Terminated.Signal != 0 { 259 reason = fmt.Sprintf("Init:Signal:%d", container.State.Terminated.Signal) 260 } else { 261 reason = fmt.Sprintf("Init:ExitCode:%d", container.State.Terminated.ExitCode) 262 } 263 } else { 264 reason = "Init:" + container.State.Terminated.Reason 265 } 266 initializing = true 267 case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing": 268 reason = "Init:" + container.State.Waiting.Reason 269 initializing = true 270 default: 271 reason = fmt.Sprintf("Init:%d/%d", i, len(pod.Spec.InitContainers)) 272 initializing = true 273 } 274 break 275 } 276 if !initializing { 277 restarts = 0 278 hasRunning := false 279 for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- { 280 container := pod.Status.ContainerStatuses[i] 281 282 restarts += int(container.RestartCount) 283 if container.State.Waiting != nil && container.State.Waiting.Reason != "" { 284 reason = container.State.Waiting.Reason 285 } else if container.State.Terminated != nil && container.State.Terminated.Reason != "" { 286 reason = container.State.Terminated.Reason 287 } else if container.State.Terminated != nil && container.State.Terminated.Reason == "" { 288 if container.State.Terminated.Signal != 0 { 289 reason = fmt.Sprintf("Signal:%d", container.State.Terminated.Signal) 290 } else { 291 reason = fmt.Sprintf("ExitCode:%d", container.State.Terminated.ExitCode) 292 } 293 } else if container.Ready && container.State.Running != nil { 294 hasRunning = true 295 readyContainers++ 296 } 297 } 298 299 // change pod status back to "Running" if there is at least one container still reporting as "Running" status 300 if reason == "Completed" && hasRunning { 301 reason = "Running" 302 } 303 } 304 305 if pod.DeletionTimestamp != nil && pod.Status.Reason == k8snode.NodeUnreachablePodReason { 306 reason = "Unknown" 307 } else if pod.DeletionTimestamp != nil { 308 reason = "Terminating" 309 } 310 311 if reason != "" { 312 res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Status Reason", Value: reason}) 313 } 314 res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Containers", Value: fmt.Sprintf("%d/%d", readyContainers, totalContainers)}) 315 res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{Labels: un.GetLabels()} 316 }