gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+incompatible/executors/docker/internal/volumes/cache_container.go (about)

     1  package volumes
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/docker/docker/api/types"
     9  	"github.com/docker/docker/api/types/container"
    10  
    11  	"gitlab.com/gitlab-org/gitlab-runner/helpers/docker"
    12  )
    13  
    14  type containerClient interface {
    15  	docker_helpers.Client
    16  
    17  	LabelContainer(container *container.Config, containerType string, otherLabels ...string)
    18  	WaitForContainer(id string) error
    19  	RemoveContainer(ctx context.Context, id string) error
    20  }
    21  
    22  type CacheContainersManager interface {
    23  	FindOrCleanExisting(containerName string, containerPath string) string
    24  	Create(containerName string, containerPath string) (string, error)
    25  	Cleanup(ctx context.Context, ids []string) chan bool
    26  }
    27  
    28  type cacheContainerManager struct {
    29  	ctx    context.Context
    30  	logger debugLogger
    31  
    32  	containerClient containerClient
    33  
    34  	helperImage         *types.ImageInspect
    35  	outdatedHelperImage bool
    36  	failedContainerIDs  []string
    37  }
    38  
    39  func NewCacheContainerManager(ctx context.Context, logger debugLogger, cClient containerClient, helperImage *types.ImageInspect, outdatedHelperImage bool) CacheContainersManager {
    40  	return &cacheContainerManager{
    41  		ctx:                 ctx,
    42  		logger:              logger,
    43  		containerClient:     cClient,
    44  		helperImage:         helperImage,
    45  		outdatedHelperImage: outdatedHelperImage,
    46  	}
    47  }
    48  
    49  func (m *cacheContainerManager) FindOrCleanExisting(containerName string, containerPath string) string {
    50  	inspected, err := m.containerClient.ContainerInspect(m.ctx, containerName)
    51  	if err != nil {
    52  		m.logger.Debugln(fmt.Sprintf("Error while inspecting %q container: %v", containerName, err))
    53  		return ""
    54  	}
    55  
    56  	// check if we have valid cache, if not remove the broken container
    57  	_, ok := inspected.Config.Volumes[containerPath]
    58  	if !ok {
    59  		m.logger.Debugln(fmt.Sprintf("Removing broken cache container for %q path", containerPath))
    60  		err = m.containerClient.RemoveContainer(m.ctx, inspected.ID)
    61  		m.logger.Debugln(fmt.Sprintf("Cache container for %q path removed with %v", containerPath, err))
    62  
    63  		return ""
    64  	}
    65  
    66  	return inspected.ID
    67  }
    68  
    69  func (m *cacheContainerManager) Create(containerName string, containerPath string) (string, error) {
    70  	containerID, err := m.createCacheContainer(containerName, containerPath)
    71  	if err != nil {
    72  		return "", err
    73  	}
    74  
    75  	err = m.startCacheContainer(containerID)
    76  	if err != nil {
    77  		return "", err
    78  	}
    79  
    80  	return containerID, nil
    81  }
    82  
    83  func (m *cacheContainerManager) createCacheContainer(containerName string, containerPath string) (string, error) {
    84  	config := &container.Config{
    85  		Image: m.helperImage.ID,
    86  		Cmd:   m.getCacheCommand(containerPath),
    87  		Volumes: map[string]struct{}{
    88  			containerPath: {},
    89  		},
    90  	}
    91  	m.containerClient.LabelContainer(config, "cache", "cache.dir="+containerPath)
    92  
    93  	hostConfig := &container.HostConfig{
    94  		LogConfig: container.LogConfig{
    95  			Type: "json-file",
    96  		},
    97  	}
    98  
    99  	resp, err := m.containerClient.ContainerCreate(m.ctx, config, hostConfig, nil, containerName)
   100  	if err != nil {
   101  		if resp.ID != "" {
   102  			m.failedContainerIDs = append(m.failedContainerIDs, resp.ID)
   103  		}
   104  
   105  		return "", err
   106  	}
   107  
   108  	return resp.ID, nil
   109  }
   110  
   111  func (m *cacheContainerManager) getCacheCommand(containerPath string) []string {
   112  	// TODO: Remove in 12.0 to start using the command from `gitlab-runner-helper`
   113  	if m.outdatedHelperImage {
   114  		m.logger.Debugln("Falling back to old gitlab-runner-cache command")
   115  		return []string{"gitlab-runner-cache", containerPath}
   116  	}
   117  
   118  	return []string{"gitlab-runner-helper", "cache-init", containerPath}
   119  
   120  }
   121  
   122  func (m *cacheContainerManager) startCacheContainer(containerID string) error {
   123  	m.logger.Debugln(fmt.Sprintf("Starting cache container %q...", containerID))
   124  	err := m.containerClient.ContainerStart(m.ctx, containerID, types.ContainerStartOptions{})
   125  	if err != nil {
   126  		m.failedContainerIDs = append(m.failedContainerIDs, containerID)
   127  
   128  		return err
   129  	}
   130  
   131  	m.logger.Debugln(fmt.Sprintf("Waiting for cache container %q...", containerID))
   132  	err = m.containerClient.WaitForContainer(containerID)
   133  	if err != nil {
   134  		m.failedContainerIDs = append(m.failedContainerIDs, containerID)
   135  
   136  		return err
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  func (m *cacheContainerManager) Cleanup(ctx context.Context, ids []string) chan bool {
   143  	done := make(chan bool, 1)
   144  
   145  	ids = append(m.failedContainerIDs, ids...)
   146  
   147  	go func() {
   148  		wg := new(sync.WaitGroup)
   149  		wg.Add(len(ids))
   150  		for _, id := range ids {
   151  			m.remove(ctx, wg, id)
   152  		}
   153  
   154  		wg.Wait()
   155  		done <- true
   156  	}()
   157  
   158  	return done
   159  }
   160  
   161  func (m *cacheContainerManager) remove(ctx context.Context, wg *sync.WaitGroup, id string) {
   162  	go func() {
   163  		err := m.containerClient.RemoveContainer(ctx, id)
   164  		if err != nil {
   165  			m.logger.Debugln(fmt.Sprintf("Error while removing the container: %v", err))
   166  		}
   167  		wg.Done()
   168  	}()
   169  }