manager.go 4.42 KB
Newer Older
1
package volumes
2
3

import (
4
	"context"
5
	"errors"
6
7
	"fmt"
	"path/filepath"
8
9

	"gitlab.com/gitlab-org/gitlab-runner/executors/docker/internal/volumes/parser"
10
11
)

12
var ErrCacheVolumesDisabled = errors.New("cache volumes feature disabled")
Tomasz Maczukin's avatar
Tomasz Maczukin committed
13

14
type Manager interface {
15
	Create(volume string) error
16
	CreateTemporary(containerPath string) error
17
18
	Binds() []string
	ContainerIDs() []string
19
	Cleanup(ctx context.Context) chan bool
20
21
}

22
type ManagerConfig struct {
23
24
	CacheDir          string
	BaseContainerPath string
25
	UniqueName        string
26
	DisableCache      bool
27
28
}

29
30
type manager struct {
	config ManagerConfig
31
	logger debugLogger
32
	parser parser.Parser
33

34
	cacheContainersManager CacheContainersManager
35

36
37
38
	volumeBindings    []string
	cacheContainerIDs []string
	tmpContainerIDs   []string
Tomasz Maczukin's avatar
Tomasz Maczukin committed
39

40
	managedVolumes pathList
41
42
}

43
func NewManager(logger debugLogger, volumeParser parser.Parser, ccManager CacheContainersManager, config ManagerConfig) Manager {
44
	return &manager{
45
46
		config:                 config,
		logger:                 logger,
47
		parser:                 volumeParser,
48
49
50
51
52
		cacheContainersManager: ccManager,
		volumeBindings:         make([]string, 0),
		cacheContainerIDs:      make([]string, 0),
		tmpContainerIDs:        make([]string, 0),
		managedVolumes:         pathList{},
53
54
55
	}
}

56
func (m *manager) Create(volume string) error {
57
58
	if len(volume) < 1 {
		return nil
59
60
	}

61
62
63
64
	parsedVolume, err := m.parser.ParseVolume(volume)
	if err != nil {
		return err
	}
65

66
	switch parsedVolume.Len() {
67
	case 2:
68
		err = m.addHostVolume(parsedVolume)
69
	case 1:
70
		err = m.addCacheVolume(parsedVolume)
71
72
73
74
75
	}

	return err
}

76
77
func (m *manager) addHostVolume(volume *parser.Volume) error {
	volume.Destination = m.getAbsoluteContainerPath(volume.Destination)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
78

79
	err := m.managedVolumes.Add(volume.Destination)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
80
81
82
83
	if err != nil {
		return err
	}

84
	m.appendVolumeBind(volume)
85
86
87
88

	return nil
}

89
func (m *manager) getAbsoluteContainerPath(dir string) string {
90
	if filepath.IsAbs(dir) {
91
92
93
		return dir
	}

94
	return filepath.Join(m.config.BaseContainerPath, dir)
95
96
}

97
98
99
func (m *manager) appendVolumeBind(volume *parser.Volume) {
	m.logger.Debugln(fmt.Sprintf("Using host-based %q for %q...", volume.Source, volume.Destination))
	m.volumeBindings = append(m.volumeBindings, volume.Definition())
100
101
}

102
func (m *manager) addCacheVolume(volume *parser.Volume) error {
103
104
	// disable cache for automatic container cache,
	// but leave it for host volumes (they are shared on purpose)
105
	if m.config.DisableCache {
106
		m.logger.Debugln("Cache containers feature is disabled")
107

108
		return ErrCacheVolumesDisabled
109
110
	}

111
	if m.config.CacheDir != "" {
112
		return m.createHostBasedCacheVolume(volume.Destination)
113
114
	}

115
	_, err := m.createContainerBasedCacheVolume(volume.Destination)
116
117

	return err
118
119
}

120
121
122
123
124
125
126
127
func (m *manager) createHostBasedCacheVolume(containerPath string) error {
	containerPath = m.getAbsoluteContainerPath(containerPath)

	err := m.managedVolumes.Add(containerPath)
	if err != nil {
		return err
	}

128
	hostPath := filepath.Join(m.config.CacheDir, m.config.UniqueName, hashContainerPath(containerPath))
129
	hostPath, err = filepath.Abs(hostPath)
130
131
132
133
	if err != nil {
		return err
	}

134
135
136
137
	m.appendVolumeBind(&parser.Volume{
		Source:      hostPath,
		Destination: containerPath,
	})
138
139
140
141

	return nil
}

142
143
144
145
146
147
148
func (m *manager) createContainerBasedCacheVolume(containerPath string) (string, error) {
	containerPath = m.getAbsoluteContainerPath(containerPath)

	err := m.managedVolumes.Add(containerPath)
	if err != nil {
		return "", err
	}
149

150
	containerName := fmt.Sprintf("%s-cache-%s", m.config.UniqueName, hashContainerPath(containerPath))
151
	containerID := m.cacheContainersManager.FindOrCleanExisting(containerName, containerPath)
152
153
154
155
156

	// create new cache container for that project
	if containerID == "" {
		var err error

157
		containerID, err = m.cacheContainersManager.Create(containerName, containerPath)
158
		if err != nil {
159
			return "", err
160
161
162
163
		}
	}

	m.logger.Debugln(fmt.Sprintf("Using container %q as cache %q...", containerID, containerPath))
164
	m.cacheContainerIDs = append(m.cacheContainerIDs, containerID)
165

166
	return containerID, nil
167
168
}

169
170
171
172
173
174
175
176
177
178
179
func (m *manager) CreateTemporary(containerPath string) error {
	id, err := m.createContainerBasedCacheVolume(containerPath)
	if err != nil {
		return err
	}

	m.tmpContainerIDs = append(m.tmpContainerIDs, id)

	return nil
}

180
func (m *manager) Binds() []string {
181
	return m.volumeBindings
182
183
}

184
func (m *manager) ContainerIDs() []string {
185
	return m.cacheContainerIDs
186
187
}

188
func (m *manager) Cleanup(ctx context.Context) chan bool {
189
	return m.cacheContainersManager.Cleanup(ctx, m.tmpContainerIDs)
190
}