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  }