github.com/secure-build/gitlab-runner@v12.5.0+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  	docker_helpers "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  	failedContainerIDs []string
    36  }
    37  
    38  func NewCacheContainerManager(ctx context.Context, logger debugLogger, cClient containerClient, helperImage *types.ImageInspect) CacheContainersManager {
    39  	return &cacheContainerManager{
    40  		ctx:             ctx,
    41  		logger:          logger,
    42  		containerClient: cClient,
    43  		helperImage:     helperImage,
    44  	}
    45  }
    46  
    47  func (m *cacheContainerManager) FindOrCleanExisting(containerName string, containerPath string) string {
    48  	inspected, err := m.containerClient.ContainerInspect(m.ctx, containerName)
    49  	if err != nil {
    50  		m.logger.Debugln(fmt.Sprintf("Error while inspecting %q container: %v", containerName, err))
    51  		return ""
    52  	}
    53  
    54  	// check if we have valid cache, if not remove the broken container
    55  	_, ok := inspected.Config.Volumes[containerPath]
    56  	if !ok {
    57  		m.logger.Debugln(fmt.Sprintf("Removing broken cache container for %q path", containerPath))
    58  		err = m.containerClient.RemoveContainer(m.ctx, inspected.ID)
    59  		m.logger.Debugln(fmt.Sprintf("Cache container for %q path removed with %v", containerPath, err))
    60  
    61  		return ""
    62  	}
    63  
    64  	return inspected.ID
    65  }
    66  
    67  func (m *cacheContainerManager) Create(containerName string, containerPath string) (string, error) {
    68  	containerID, err := m.createCacheContainer(containerName, containerPath)
    69  	if err != nil {
    70  		return "", err
    71  	}
    72  
    73  	err = m.startCacheContainer(containerID)
    74  	if err != nil {
    75  		return "", err
    76  	}
    77  
    78  	return containerID, nil
    79  }
    80  
    81  func (m *cacheContainerManager) createCacheContainer(containerName string, containerPath string) (string, error) {
    82  	config := &container.Config{
    83  		Image: m.helperImage.ID,
    84  		Cmd:   []string{"gitlab-runner-helper", "cache-init", containerPath},
    85  		Volumes: map[string]struct{}{
    86  			containerPath: {},
    87  		},
    88  	}
    89  	m.containerClient.LabelContainer(config, "cache", "cache.dir="+containerPath)
    90  
    91  	hostConfig := &container.HostConfig{
    92  		LogConfig: container.LogConfig{
    93  			Type: "json-file",
    94  		},
    95  	}
    96  
    97  	resp, err := m.containerClient.ContainerCreate(m.ctx, config, hostConfig, nil, containerName)
    98  	if err != nil {
    99  		if resp.ID != "" {
   100  			m.failedContainerIDs = append(m.failedContainerIDs, resp.ID)
   101  		}
   102  
   103  		return "", err
   104  	}
   105  
   106  	return resp.ID, nil
   107  }
   108  
   109  func (m *cacheContainerManager) startCacheContainer(containerID string) error {
   110  	m.logger.Debugln(fmt.Sprintf("Starting cache container %q...", containerID))
   111  	err := m.containerClient.ContainerStart(m.ctx, containerID, types.ContainerStartOptions{})
   112  	if err != nil {
   113  		m.failedContainerIDs = append(m.failedContainerIDs, containerID)
   114  
   115  		return err
   116  	}
   117  
   118  	m.logger.Debugln(fmt.Sprintf("Waiting for cache container %q...", containerID))
   119  	err = m.containerClient.WaitForContainer(containerID)
   120  	if err != nil {
   121  		m.failedContainerIDs = append(m.failedContainerIDs, containerID)
   122  
   123  		return err
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  func (m *cacheContainerManager) Cleanup(ctx context.Context, ids []string) chan bool {
   130  	done := make(chan bool, 1)
   131  
   132  	ids = append(m.failedContainerIDs, ids...)
   133  
   134  	go func() {
   135  		wg := new(sync.WaitGroup)
   136  		wg.Add(len(ids))
   137  		for _, id := range ids {
   138  			m.remove(ctx, wg, id)
   139  		}
   140  
   141  		wg.Wait()
   142  		done <- true
   143  	}()
   144  
   145  	return done
   146  }
   147  
   148  func (m *cacheContainerManager) remove(ctx context.Context, wg *sync.WaitGroup, id string) {
   149  	go func() {
   150  		err := m.containerClient.RemoveContainer(ctx, id)
   151  		if err != nil {
   152  			m.logger.Debugln(fmt.Sprintf("Error while removing the container: %v", err))
   153  		}
   154  		wg.Done()
   155  	}()
   156  }