github.com/grahambrereton-form3/tilt@v0.10.18/internal/k8s/container.go (about) 1 package k8s 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/pkg/errors" 9 v1 "k8s.io/api/core/v1" 10 11 "github.com/windmilleng/tilt/internal/container" 12 "github.com/windmilleng/tilt/pkg/logger" 13 ) 14 15 const ContainerIDPrefix = "docker://" 16 17 func WaitForContainerReady(ctx context.Context, client Client, pod *v1.Pod, ref container.RefSelector) (v1.ContainerStatus, error) { 18 cStatus, err := waitForContainerReadyHelper(pod, ref) 19 if err != nil { 20 return v1.ContainerStatus{}, err 21 } else if cStatus != (v1.ContainerStatus{}) { 22 return cStatus, nil 23 } 24 25 watch, err := client.WatchPod(ctx, pod) 26 if err != nil { 27 return v1.ContainerStatus{}, errors.Wrap(err, "WaitForContainerReady") 28 } 29 defer watch.Stop() 30 31 for { 32 select { 33 case <-ctx.Done(): 34 return v1.ContainerStatus{}, errors.Wrap(ctx.Err(), "WaitForContainerReady") 35 case event, ok := <-watch.ResultChan(): 36 if !ok { 37 return v1.ContainerStatus{}, fmt.Errorf("Container watch closed: %s", ref) 38 } 39 40 obj := event.Object 41 pod, ok := obj.(*v1.Pod) 42 if !ok { 43 logger.Get(ctx).Debugf("Unexpected watch notification: %T", obj) 44 continue 45 } 46 47 FixContainerStatusImages(pod) 48 49 cStatus, err := waitForContainerReadyHelper(pod, ref) 50 if err != nil { 51 return v1.ContainerStatus{}, err 52 } else if cStatus != (v1.ContainerStatus{}) { 53 return cStatus, nil 54 } 55 } 56 } 57 } 58 59 func waitForContainerReadyHelper(pod *v1.Pod, ref container.RefSelector) (v1.ContainerStatus, error) { 60 cStatus, err := ContainerMatching(pod, ref) 61 if err != nil { 62 return v1.ContainerStatus{}, errors.Wrap(err, "WaitForContainerReadyHelper") 63 } 64 65 unschedulable, msg := IsUnschedulable(pod.Status) 66 if unschedulable { 67 return v1.ContainerStatus{}, fmt.Errorf("Container will never be ready: %s", msg) 68 } 69 70 if IsContainerExited(pod.Status, cStatus) { 71 return v1.ContainerStatus{}, fmt.Errorf("Container will never be ready: %s", ref) 72 } 73 74 if !cStatus.Ready { 75 return v1.ContainerStatus{}, nil 76 } 77 78 return cStatus, nil 79 } 80 81 // If true, this means the container is gone and will never recover. 82 func IsContainerExited(pod v1.PodStatus, container v1.ContainerStatus) bool { 83 if pod.Phase == v1.PodSucceeded || pod.Phase == v1.PodFailed { 84 return true 85 } 86 87 if container.State.Terminated != nil { 88 return true 89 } 90 91 return false 92 } 93 94 // Returns the error message if the pod is unschedulable 95 func IsUnschedulable(pod v1.PodStatus) (bool, string) { 96 for _, cond := range pod.Conditions { 97 if cond.Reason == v1.PodReasonUnschedulable { 98 return true, cond.Message 99 } 100 } 101 return false, "" 102 } 103 104 // Kubernetes has a bug where the image ref in the container status 105 // can be wrong (though this does not mean the container is running 106 // unexpected code) 107 // 108 // Repro steps: 109 // 1) Create an image and give it two different tags (A and B) 110 // 2) Deploy Pods with both A and B in the pod spec 111 // 3) The PodStatus will choose A or B for both pods. 112 // 113 // More details here: 114 // https://github.com/kubernetes/kubernetes/issues/51017 115 // 116 // For Tilt, it's pretty important that the image tag is correct (for matching 117 // purposes). To work around this bug, we change the image reference in 118 // ContainerStatus to match the ContainerSpec. 119 func FixContainerStatusImages(pod *v1.Pod) { 120 refsByContainerName := make(map[string]string) 121 for _, c := range pod.Spec.Containers { 122 if c.Name != "" { 123 refsByContainerName[c.Name] = c.Image 124 } 125 } 126 for i, cs := range pod.Status.ContainerStatuses { 127 image, ok := refsByContainerName[cs.Name] 128 if !ok { 129 continue 130 } 131 132 cs.Image = image 133 pod.Status.ContainerStatuses[i] = cs 134 } 135 } 136 137 func ContainerMatching(pod *v1.Pod, ref container.RefSelector) (v1.ContainerStatus, error) { 138 for _, c := range pod.Status.ContainerStatuses { 139 cRef, err := container.ParseNamed(c.Image) 140 if err != nil { 141 return v1.ContainerStatus{}, errors.Wrap(err, "ContainerMatching") 142 } 143 144 if ref.Matches(cRef) { 145 return c, nil 146 } 147 } 148 return v1.ContainerStatus{}, nil 149 } 150 151 func ContainerIDFromContainerStatus(status v1.ContainerStatus) (container.ID, error) { 152 id := status.ContainerID 153 return NormalizeContainerID(id) 154 } 155 156 func NormalizeContainerID(id string) (container.ID, error) { 157 if id == "" { 158 return "", nil 159 } 160 161 components := strings.SplitN(id, "://", 2) 162 if len(components) != 2 { 163 return "", fmt.Errorf("Malformed container ID: %s", id) 164 } 165 return container.ID(components[1]), nil 166 } 167 168 func ContainerNameFromContainerStatus(status v1.ContainerStatus) container.Name { 169 return container.Name(status.Name) 170 } 171 172 func ContainerSpecOf(pod *v1.Pod, status v1.ContainerStatus) v1.Container { 173 for _, spec := range pod.Spec.Containers { 174 if spec.Name == status.Name { 175 return spec 176 } 177 } 178 return v1.Container{} 179 }