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

     1  package volumes
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"gitlab.com/gitlab-org/gitlab-runner/executors/docker/internal/volumes/parser"
     9  )
    10  
    11  var ErrCacheVolumesDisabled = errors.New("cache volumes feature disabled")
    12  
    13  type Manager interface {
    14  	Create(volume string) error
    15  	CreateTemporary(containerPath string) error
    16  	Binds() []string
    17  	ContainerIDs() []string
    18  	Cleanup(ctx context.Context) chan bool
    19  }
    20  
    21  type ManagerConfig struct {
    22  	CacheDir          string
    23  	BaseContainerPath string
    24  	UniqueName        string
    25  	DisableCache      bool
    26  }
    27  
    28  type manager struct {
    29  	config ManagerConfig
    30  	logger debugLogger
    31  	parser parser.Parser
    32  
    33  	cacheContainersManager CacheContainersManager
    34  
    35  	volumeBindings    []string
    36  	cacheContainerIDs []string
    37  	tmpContainerIDs   []string
    38  
    39  	managedVolumes pathList
    40  }
    41  
    42  func NewManager(logger debugLogger, volumeParser parser.Parser, ccManager CacheContainersManager, config ManagerConfig) Manager {
    43  	return &manager{
    44  		config:                 config,
    45  		logger:                 logger,
    46  		parser:                 volumeParser,
    47  		cacheContainersManager: ccManager,
    48  		volumeBindings:         make([]string, 0),
    49  		cacheContainerIDs:      make([]string, 0),
    50  		tmpContainerIDs:        make([]string, 0),
    51  		managedVolumes:         pathList{},
    52  	}
    53  }
    54  
    55  func (m *manager) Create(volume string) error {
    56  	if len(volume) < 1 {
    57  		return nil
    58  	}
    59  
    60  	parsedVolume, err := m.parser.ParseVolume(volume)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	switch parsedVolume.Len() {
    66  	case 2:
    67  		err = m.addHostVolume(parsedVolume)
    68  	case 1:
    69  		err = m.addCacheVolume(parsedVolume)
    70  	}
    71  
    72  	return err
    73  }
    74  
    75  func (m *manager) addHostVolume(volume *parser.Volume) error {
    76  	var err error
    77  
    78  	volume.Destination, err = m.getAbsoluteContainerPath(volume.Destination)
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	err = m.managedVolumes.Add(volume.Destination)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	m.appendVolumeBind(volume)
    89  
    90  	return nil
    91  }
    92  
    93  func (m *manager) getAbsoluteContainerPath(dir string) (string, error) {
    94  	if m.parser.Path().IsRoot(dir) {
    95  		return "", errDirectoryIsRootPath
    96  	}
    97  
    98  	if m.parser.Path().IsAbs(dir) {
    99  		return dir, nil
   100  	}
   101  
   102  	return m.parser.Path().Join(m.config.BaseContainerPath, dir), nil
   103  }
   104  
   105  func (m *manager) appendVolumeBind(volume *parser.Volume) {
   106  	m.logger.Debugln(fmt.Sprintf("Using host-based %q for %q...", volume.Source, volume.Destination))
   107  
   108  	m.volumeBindings = append(m.volumeBindings, volume.Definition())
   109  }
   110  
   111  func (m *manager) addCacheVolume(volume *parser.Volume) error {
   112  	// disable cache for automatic container cache,
   113  	// but leave it for host volumes (they are shared on purpose)
   114  	if m.config.DisableCache {
   115  		m.logger.Debugln("Cache containers feature is disabled")
   116  
   117  		return ErrCacheVolumesDisabled
   118  	}
   119  
   120  	if m.config.CacheDir != "" {
   121  		return m.createHostBasedCacheVolume(volume.Destination)
   122  	}
   123  
   124  	_, err := m.createContainerBasedCacheVolume(volume.Destination)
   125  
   126  	return err
   127  }
   128  
   129  func (m *manager) createHostBasedCacheVolume(containerPath string) error {
   130  	var err error
   131  
   132  	containerPath, err = m.getAbsoluteContainerPath(containerPath)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	err = m.managedVolumes.Add(containerPath)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	hostPath := m.parser.Path().Join(m.config.CacheDir, m.config.UniqueName, hashContainerPath(containerPath))
   143  
   144  	m.appendVolumeBind(&parser.Volume{
   145  		Source:      hostPath,
   146  		Destination: containerPath,
   147  	})
   148  
   149  	return nil
   150  }
   151  
   152  func (m *manager) createContainerBasedCacheVolume(containerPath string) (string, error) {
   153  	containerPath, err := m.getAbsoluteContainerPath(containerPath)
   154  	if err != nil {
   155  		return "", err
   156  	}
   157  
   158  	err = m.managedVolumes.Add(containerPath)
   159  	if err != nil {
   160  		return "", err
   161  	}
   162  
   163  	containerName := fmt.Sprintf("%s-cache-%s", m.config.UniqueName, hashContainerPath(containerPath))
   164  	containerID := m.cacheContainersManager.FindOrCleanExisting(containerName, containerPath)
   165  
   166  	// create new cache container for that project
   167  	if containerID == "" {
   168  		var err error
   169  
   170  		containerID, err = m.cacheContainersManager.Create(containerName, containerPath)
   171  		if err != nil {
   172  			return "", err
   173  		}
   174  	}
   175  
   176  	m.logger.Debugln(fmt.Sprintf("Using container %q as cache %q...", containerID, containerPath))
   177  	m.cacheContainerIDs = append(m.cacheContainerIDs, containerID)
   178  
   179  	return containerID, nil
   180  }
   181  
   182  func (m *manager) CreateTemporary(containerPath string) error {
   183  	id, err := m.createContainerBasedCacheVolume(containerPath)
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	m.tmpContainerIDs = append(m.tmpContainerIDs, id)
   189  
   190  	return nil
   191  }
   192  
   193  func (m *manager) Binds() []string {
   194  	return m.volumeBindings
   195  }
   196  
   197  func (m *manager) ContainerIDs() []string {
   198  	return m.cacheContainerIDs
   199  }
   200  
   201  func (m *manager) Cleanup(ctx context.Context) chan bool {
   202  	return m.cacheContainersManager.Cleanup(ctx, m.tmpContainerIDs)
   203  }