github.com/secure-build/gitlab-runner@v12.5.0+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  	default:
    71  		err = fmt.Errorf("unsupported volume definition %s", volume)
    72  	}
    73  
    74  	return err
    75  }
    76  
    77  func (m *manager) addHostVolume(volume *parser.Volume) error {
    78  	var err error
    79  
    80  	volume.Destination, err = m.getAbsoluteContainerPath(volume.Destination)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	err = m.managedVolumes.Add(volume.Destination)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	m.appendVolumeBind(volume)
    91  
    92  	return nil
    93  }
    94  
    95  func (m *manager) getAbsoluteContainerPath(dir string) (string, error) {
    96  	if m.parser.Path().IsRoot(dir) {
    97  		return "", errDirectoryIsRootPath
    98  	}
    99  
   100  	if m.parser.Path().IsAbs(dir) {
   101  		return dir, nil
   102  	}
   103  
   104  	return m.parser.Path().Join(m.config.BaseContainerPath, dir), nil
   105  }
   106  
   107  func (m *manager) appendVolumeBind(volume *parser.Volume) {
   108  	m.logger.Debugln(fmt.Sprintf("Using host-based %q for %q...", volume.Source, volume.Destination))
   109  
   110  	m.volumeBindings = append(m.volumeBindings, volume.Definition())
   111  }
   112  
   113  func (m *manager) addCacheVolume(volume *parser.Volume) error {
   114  	// disable cache for automatic container cache,
   115  	// but leave it for host volumes (they are shared on purpose)
   116  	if m.config.DisableCache {
   117  		m.logger.Debugln("Cache containers feature is disabled")
   118  
   119  		return ErrCacheVolumesDisabled
   120  	}
   121  
   122  	if m.config.CacheDir != "" {
   123  		return m.createHostBasedCacheVolume(volume.Destination)
   124  	}
   125  
   126  	_, err := m.createContainerBasedCacheVolume(volume.Destination)
   127  
   128  	return err
   129  }
   130  
   131  func (m *manager) createHostBasedCacheVolume(containerPath string) error {
   132  	var err error
   133  
   134  	containerPath, err = m.getAbsoluteContainerPath(containerPath)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	err = m.managedVolumes.Add(containerPath)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	hostPath := m.parser.Path().Join(m.config.CacheDir, m.config.UniqueName, hashContainerPath(containerPath))
   145  
   146  	m.appendVolumeBind(&parser.Volume{
   147  		Source:      hostPath,
   148  		Destination: containerPath,
   149  	})
   150  
   151  	return nil
   152  }
   153  
   154  func (m *manager) createContainerBasedCacheVolume(containerPath string) (string, error) {
   155  	containerPath, err := m.getAbsoluteContainerPath(containerPath)
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  
   160  	err = m.managedVolumes.Add(containerPath)
   161  	if err != nil {
   162  		return "", err
   163  	}
   164  
   165  	containerName := fmt.Sprintf("%s-cache-%s", m.config.UniqueName, hashContainerPath(containerPath))
   166  	containerID := m.cacheContainersManager.FindOrCleanExisting(containerName, containerPath)
   167  
   168  	// create new cache container for that project
   169  	if containerID == "" {
   170  		var err error
   171  
   172  		containerID, err = m.cacheContainersManager.Create(containerName, containerPath)
   173  		if err != nil {
   174  			return "", err
   175  		}
   176  	}
   177  
   178  	m.logger.Debugln(fmt.Sprintf("Using container %q as cache %q...", containerID, containerPath))
   179  	m.cacheContainerIDs = append(m.cacheContainerIDs, containerID)
   180  
   181  	return containerID, nil
   182  }
   183  
   184  func (m *manager) CreateTemporary(containerPath string) error {
   185  	id, err := m.createContainerBasedCacheVolume(containerPath)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	m.tmpContainerIDs = append(m.tmpContainerIDs, id)
   191  
   192  	return nil
   193  }
   194  
   195  func (m *manager) Binds() []string {
   196  	return m.volumeBindings
   197  }
   198  
   199  func (m *manager) ContainerIDs() []string {
   200  	return m.cacheContainerIDs
   201  }
   202  
   203  func (m *manager) Cleanup(ctx context.Context) chan bool {
   204  	return m.cacheContainersManager.Cleanup(ctx, m.tmpContainerIDs)
   205  }