github.com/grahambrereton-form3/tilt@v0.10.18/internal/k8s/fake_client.go (about)

     1  package k8s
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io"
     7  	"io/ioutil"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/docker/distribution/reference"
    12  	"github.com/google/uuid"
    13  	"github.com/pkg/errors"
    14  	v1 "k8s.io/api/core/v1"
    15  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    16  	"k8s.io/apimachinery/pkg/labels"
    17  	"k8s.io/apimachinery/pkg/watch"
    18  
    19  	"github.com/windmilleng/tilt/internal/container"
    20  	"github.com/windmilleng/tilt/pkg/logger"
    21  )
    22  
    23  // A magic constant. If the docker client returns this constant, we always match
    24  // even if the container doesn't have the correct image name.
    25  const MagicTestContainerID = "tilt-testcontainer"
    26  
    27  var _ Client = &FakeK8sClient{}
    28  
    29  // For keying PodLogsByPodAndContainer
    30  type PodAndCName struct {
    31  	PID   PodID
    32  	CName container.Name
    33  }
    34  
    35  type FakeK8sClient struct {
    36  	FakePortForwardClient
    37  
    38  	Yaml string
    39  	Lb   LoadBalancerSpec
    40  
    41  	DeletedYaml string
    42  	DeleteError error
    43  
    44  	LastPodQueryNamespace Namespace
    45  	LastPodQueryImage     reference.NamedTagged
    46  
    47  	PodLogsByPodAndContainer map[PodAndCName]BufferCloser
    48  	LastPodLogStartTime      time.Time
    49  	ContainerLogsError       error
    50  
    51  	podWatcherMu sync.Mutex
    52  	podWatches   []fakePodWatch
    53  
    54  	serviceWatcherMu sync.Mutex
    55  	serviceWatches   []fakeServiceWatch
    56  
    57  	eventsCh       chan *v1.Event
    58  	EventsWatchErr error
    59  
    60  	UpsertError      error
    61  	LastUpsertResult []K8sEntity
    62  
    63  	Runtime  container.Runtime
    64  	Registry container.Registry
    65  
    66  	entityByName            map[string]K8sEntity
    67  	getByReferenceCallCount int
    68  
    69  	ExecCalls  []ExecCall
    70  	ExecErrors []error
    71  }
    72  
    73  type ExecCall struct {
    74  	PID   PodID
    75  	CName container.Name
    76  	Ns    Namespace
    77  	Cmd   []string
    78  	Stdin []byte
    79  }
    80  
    81  type fakeServiceWatch struct {
    82  	ls labels.Selector
    83  	ch chan *v1.Service
    84  }
    85  
    86  type fakePodWatch struct {
    87  	ls labels.Selector
    88  	ch chan *v1.Pod
    89  }
    90  
    91  func (c *FakeK8sClient) EmitService(ls labels.Selector, s *v1.Service) {
    92  	c.podWatcherMu.Lock()
    93  	defer c.podWatcherMu.Unlock()
    94  	for _, w := range c.serviceWatches {
    95  		if SelectorEqual(ls, w.ls) {
    96  			w.ch <- s
    97  		}
    98  	}
    99  }
   100  
   101  func (c *FakeK8sClient) WatchServices(ctx context.Context, ls labels.Selector) (<-chan *v1.Service, error) {
   102  	c.serviceWatcherMu.Lock()
   103  	ch := make(chan *v1.Service, 20)
   104  	c.serviceWatches = append(c.serviceWatches, fakeServiceWatch{ls, ch})
   105  	c.serviceWatcherMu.Unlock()
   106  
   107  	go func() {
   108  		// when ctx is canceled, remove the label selector from the list of watched label selectors
   109  		<-ctx.Done()
   110  		c.serviceWatcherMu.Lock()
   111  		var newWatches []fakeServiceWatch
   112  		for _, e := range c.serviceWatches {
   113  			if !SelectorEqual(e.ls, ls) {
   114  				newWatches = append(newWatches, e)
   115  			}
   116  		}
   117  		c.serviceWatches = newWatches
   118  		c.serviceWatcherMu.Unlock()
   119  	}()
   120  	return ch, nil
   121  }
   122  
   123  func (c *FakeK8sClient) WatchEvents(ctx context.Context) (<-chan *v1.Event, error) {
   124  	if c.EventsWatchErr != nil {
   125  		err := c.EventsWatchErr
   126  		c.EventsWatchErr = nil
   127  		return nil, err
   128  	}
   129  
   130  	return c.eventsCh, nil
   131  }
   132  
   133  func (c *FakeK8sClient) EmitEvent(ctx context.Context, evt *v1.Event) {
   134  	c.eventsCh <- evt
   135  }
   136  
   137  func (c *FakeK8sClient) WatchedSelectors() []labels.Selector {
   138  	c.podWatcherMu.Lock()
   139  	defer c.podWatcherMu.Unlock()
   140  	var ret []labels.Selector
   141  	for _, w := range c.podWatches {
   142  		ret = append(ret, w.ls)
   143  	}
   144  	return ret
   145  }
   146  
   147  func (c *FakeK8sClient) EmitPod(ls labels.Selector, p *v1.Pod) {
   148  	c.podWatcherMu.Lock()
   149  	defer c.podWatcherMu.Unlock()
   150  	for _, w := range c.podWatches {
   151  		if SelectorEqual(ls, w.ls) {
   152  			w.ch <- p
   153  		}
   154  	}
   155  }
   156  
   157  func (c *FakeK8sClient) WatchPods(ctx context.Context, ls labels.Selector) (<-chan *v1.Pod, error) {
   158  	c.podWatcherMu.Lock()
   159  	ch := make(chan *v1.Pod, 20)
   160  	c.podWatches = append(c.podWatches, fakePodWatch{ls, ch})
   161  	c.podWatcherMu.Unlock()
   162  
   163  	go func() {
   164  		// when ctx is canceled, remove the label selector from the list of watched label selectors
   165  		<-ctx.Done()
   166  		c.podWatcherMu.Lock()
   167  		var newWatches []fakePodWatch
   168  		for _, e := range c.podWatches {
   169  			if !SelectorEqual(e.ls, ls) {
   170  				newWatches = append(newWatches, e)
   171  			}
   172  		}
   173  		c.podWatches = newWatches
   174  		c.podWatcherMu.Unlock()
   175  	}()
   176  	return ch, nil
   177  }
   178  
   179  func NewFakeK8sClient() *FakeK8sClient {
   180  	return &FakeK8sClient{
   181  		PodLogsByPodAndContainer: make(map[PodAndCName]BufferCloser),
   182  		eventsCh:                 make(chan *v1.Event, 10),
   183  	}
   184  }
   185  
   186  func (c *FakeK8sClient) TearDown() {
   187  	if c.eventsCh != nil {
   188  		close(c.eventsCh)
   189  	}
   190  }
   191  
   192  func (c *FakeK8sClient) ConnectedToCluster(ctx context.Context) error {
   193  	return nil
   194  }
   195  
   196  func (c *FakeK8sClient) Upsert(ctx context.Context, entities []K8sEntity) ([]K8sEntity, error) {
   197  	if c.UpsertError != nil {
   198  		return nil, c.UpsertError
   199  	}
   200  	yaml, err := SerializeSpecYAML(entities)
   201  	if err != nil {
   202  		return nil, errors.Wrap(err, "kubectl apply")
   203  	}
   204  	c.Yaml = yaml
   205  
   206  	result := make([]K8sEntity, 0, len(entities))
   207  
   208  	for _, e := range entities {
   209  		clone := e.DeepCopy()
   210  		err = SetUID(&clone, uuid.New().String())
   211  		if err != nil {
   212  			return nil, errors.Wrap(err, "Upsert: generating UUID")
   213  		}
   214  		result = append(result, clone)
   215  	}
   216  
   217  	c.LastUpsertResult = result
   218  	return result, nil
   219  }
   220  
   221  func (c *FakeK8sClient) Delete(ctx context.Context, entities []K8sEntity) error {
   222  	if c.DeleteError != nil {
   223  		err := c.DeleteError
   224  		c.DeleteError = nil
   225  		return err
   226  	}
   227  
   228  	yaml, err := SerializeSpecYAML(entities)
   229  	if err != nil {
   230  		return errors.Wrap(err, "kubectl delete")
   231  	}
   232  	c.DeletedYaml = yaml
   233  	return nil
   234  }
   235  
   236  func (c *FakeK8sClient) InjectEntityByName(entities ...K8sEntity) {
   237  	if c.entityByName == nil {
   238  		c.entityByName = make(map[string]K8sEntity)
   239  	}
   240  	for _, entity := range entities {
   241  		c.entityByName[entity.Name()] = entity
   242  	}
   243  }
   244  
   245  func (c *FakeK8sClient) GetByReference(ctx context.Context, ref v1.ObjectReference) (K8sEntity, error) {
   246  	c.getByReferenceCallCount++
   247  	resp, ok := c.entityByName[ref.Name]
   248  	if !ok {
   249  		logger.Get(ctx).Infof("FakeK8sClient.GetByReference: resource not found: %s", ref.Name)
   250  		return K8sEntity{}, apierrors.NewNotFound(v1.Resource(ref.Kind), ref.Name)
   251  	}
   252  	return resp, nil
   253  }
   254  
   255  func (c *FakeK8sClient) WatchPod(ctx context.Context, pod *v1.Pod) (watch.Interface, error) {
   256  	return watch.NewEmptyWatch(), nil
   257  }
   258  
   259  func (c *FakeK8sClient) SetLogsForPodContainer(pID PodID, cName container.Name, logs string) {
   260  	c.PodLogsByPodAndContainer[PodAndCName{pID, cName}] = BufferCloser{Buffer: bytes.NewBufferString(logs)}
   261  }
   262  
   263  func (c *FakeK8sClient) ContainerLogs(ctx context.Context, pID PodID, cName container.Name, n Namespace, startTime time.Time) (io.ReadCloser, error) {
   264  	if c.ContainerLogsError != nil {
   265  		return nil, c.ContainerLogsError
   266  	}
   267  
   268  	// If we have specific logs for this pod/container combo, return those
   269  	c.LastPodLogStartTime = startTime
   270  	if buf, ok := c.PodLogsByPodAndContainer[PodAndCName{pID, cName}]; ok {
   271  		return buf, nil
   272  	}
   273  
   274  	return BufferCloser{Buffer: bytes.NewBuffer(nil)}, nil
   275  }
   276  
   277  func (c *FakeK8sClient) PodByID(ctx context.Context, pID PodID, n Namespace) (*v1.Pod, error) {
   278  	return nil, nil
   279  }
   280  
   281  func FakePodStatus(image reference.NamedTagged, phase string) v1.PodStatus {
   282  	return v1.PodStatus{
   283  		Phase: v1.PodPhase(phase),
   284  		ContainerStatuses: []v1.ContainerStatus{
   285  			{
   286  				Name:        "main",
   287  				ContainerID: "docker://" + MagicTestContainerID,
   288  				Image:       image.String(),
   289  				Ready:       true,
   290  			},
   291  			{
   292  				Name:        "tilt-synclet",
   293  				ContainerID: "docker://tilt-testsynclet",
   294  				// can't use the constants in synclet because that would create a dep cycle
   295  				Image: "gcr.io/windmill-public-containers/tilt-synclet:latest",
   296  				Ready: true,
   297  			},
   298  		},
   299  	}
   300  }
   301  
   302  func FakePodSpec(image reference.NamedTagged) v1.PodSpec {
   303  	return v1.PodSpec{
   304  		Containers: []v1.Container{
   305  			{
   306  				Name:  "main",
   307  				Image: image.String(),
   308  				Ports: []v1.ContainerPort{
   309  					{
   310  						ContainerPort: 8080,
   311  					},
   312  				},
   313  			},
   314  			{
   315  				Name:  "tilt-synclet",
   316  				Image: "gcr.io/windmill-public-containers/tilt-synclet:latest",
   317  			},
   318  		},
   319  	}
   320  }
   321  
   322  func (c *FakeK8sClient) applyWasCalled() bool {
   323  	return c.Yaml != ""
   324  }
   325  
   326  func (c *FakeK8sClient) CreatePortForwarder(ctx context.Context, namespace Namespace, podID PodID, optionalLocalPort, remotePort int, host string) (PortForwarder, error) {
   327  	pfc := &(c.FakePortForwardClient)
   328  	return pfc.CreatePortForwarder(ctx, namespace, podID, optionalLocalPort, remotePort, host)
   329  }
   330  
   331  func (c *FakeK8sClient) ContainerRuntime(ctx context.Context) container.Runtime {
   332  	if c.Runtime != "" {
   333  		return c.Runtime
   334  	}
   335  	return container.RuntimeDocker
   336  }
   337  
   338  func (c *FakeK8sClient) PrivateRegistry(ctx context.Context) container.Registry {
   339  	return c.Registry
   340  }
   341  
   342  func (c *FakeK8sClient) Exec(ctx context.Context, podID PodID, cName container.Name, n Namespace, cmd []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
   343  	var stdinBytes []byte
   344  	var err error
   345  	if stdin != nil {
   346  		stdinBytes, err = ioutil.ReadAll(stdin)
   347  		if err != nil {
   348  			return errors.Wrap(err, "reading Exec stdin")
   349  		}
   350  	}
   351  
   352  	c.ExecCalls = append(c.ExecCalls, ExecCall{
   353  		PID:   podID,
   354  		CName: cName,
   355  		Ns:    n,
   356  		Cmd:   cmd,
   357  		Stdin: stdinBytes,
   358  	})
   359  
   360  	if len(c.ExecErrors) > 0 {
   361  		err = c.ExecErrors[0]
   362  		c.ExecErrors = c.ExecErrors[1:]
   363  		return err
   364  	}
   365  	return nil
   366  }
   367  
   368  type BufferCloser struct {
   369  	*bytes.Buffer
   370  }
   371  
   372  func (b BufferCloser) Close() error {
   373  	return nil
   374  }
   375  
   376  var _ io.ReadCloser = BufferCloser{}
   377  
   378  type FakePortForwarder struct {
   379  	localPort int
   380  	ctx       context.Context
   381  	Done      chan error
   382  }
   383  
   384  func (pf FakePortForwarder) LocalPort() int {
   385  	return pf.localPort
   386  }
   387  
   388  func (pf FakePortForwarder) ForwardPorts() error {
   389  	select {
   390  	case <-pf.ctx.Done():
   391  		return pf.ctx.Err()
   392  	case <-pf.Done:
   393  		return nil
   394  	}
   395  }
   396  
   397  type FakePortForwardClient struct {
   398  	CreatePortForwardCallCount int
   399  	LastForwardPortPodID       PodID
   400  	LastForwardPortRemotePort  int
   401  	LastForwardPortHost        string
   402  	LastForwarder              FakePortForwarder
   403  	LastForwardContext         context.Context
   404  }
   405  
   406  func (c *FakePortForwardClient) CreatePortForwarder(ctx context.Context, namespace Namespace, podID PodID, optionalLocalPort, remotePort int, host string) (PortForwarder, error) {
   407  	c.CreatePortForwardCallCount++
   408  	c.LastForwardContext = ctx
   409  	c.LastForwardPortPodID = podID
   410  	c.LastForwardPortRemotePort = remotePort
   411  	c.LastForwardPortHost = host
   412  
   413  	result := FakePortForwarder{
   414  		localPort: optionalLocalPort,
   415  		ctx:       ctx,
   416  		Done:      make(chan error),
   417  	}
   418  	c.LastForwarder = result
   419  	return result, nil
   420  }