cache_container.go 4.54 KB
Newer Older
1
2
3
package volumes

import (
4
	"context"
5
	"fmt"
6
	"sync"
7
8
9
10

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"

11
	"gitlab.com/gitlab-org/gitlab-runner/helpers/docker"
12
13
14
)

type containerClient interface {
15
16
	docker_helpers.Client

17
18
	LabelContainer(container *container.Config, containerType string, otherLabels ...string)
	WaitForContainer(id string) error
19
	RemoveContainer(ctx context.Context, id string) error
20
21
}

22
23
24
type CacheContainersManager interface {
	FindOrCleanExisting(containerName string, containerPath string) string
	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
35
	helperImage         *types.ImageInspect
	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
43
		logger:              logger,
		containerClient:     cClient,
44
45
		helperImage:         helperImage,
		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
54
55
		return ""
	}

Tomasz Maczukin's avatar
Tomasz Maczukin committed
56
	// check if we have valid cache, if not remove the broken container
57
58
59
	_, ok := inspected.Config.Volumes[containerPath]
	if !ok {
		m.logger.Debugln(fmt.Sprintf("Removing broken cache container for %q path", containerPath))
60
		err = m.containerClient.RemoveContainer(m.ctx, inspected.ID)
61
62
63
64
65
66
67
68
		m.logger.Debugln(fmt.Sprintf("Cache container for %q path removed with %v", containerPath, err))

		return ""
	}

	return inspected.ID
}

69
func (m *cacheContainerManager) Create(containerName string, containerPath string) (string, error) {
70
71
72
73
74
75
76
77
78
79
80
81
82
	containerID, err := m.createCacheContainer(containerName, containerPath)
	if err != nil {
		return "", err
	}

	err = m.startCacheContainer(containerID)
	if err != nil {
		return "", err
	}

	return containerID, nil
}

83
func (m *cacheContainerManager) createCacheContainer(containerName string, containerPath string) (string, error) {
84
	config := &container.Config{
85
86
		Image: m.helperImage.ID,
		Cmd:   m.getCacheCommand(containerPath),
87
88
89
90
91
92
93
94
95
96
97
98
		Volumes: map[string]struct{}{
			containerPath: {},
		},
	}
	m.containerClient.LabelContainer(config, "cache", "cache.dir="+containerPath)

	hostConfig := &container.HostConfig{
		LogConfig: container.LogConfig{
			Type: "json-file",
		},
	}

99
	resp, err := m.containerClient.ContainerCreate(m.ctx, config, hostConfig, nil, containerName)
100
101
	if err != nil {
		if resp.ID != "" {
102
			m.failedContainerIDs = append(m.failedContainerIDs, resp.ID)
103
104
105
106
107
108
109
110
		}

		return "", err
	}

	return resp.ID, nil
}

111
func (m *cacheContainerManager) getCacheCommand(containerPath string) []string {
112
113
114
115
116
117
118
119
120
121
	// TODO: Remove in 12.0 to start using the command from `gitlab-runner-helper`
	if m.outdatedHelperImage {
		m.logger.Debugln("Falling back to old gitlab-runner-cache command")
		return []string{"gitlab-runner-cache", containerPath}
	}

	return []string{"gitlab-runner-helper", "cache-init", containerPath}

}

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
129
130
131
132
133

		return err
	}

	m.logger.Debugln(fmt.Sprintf("Waiting for cache container %q...", containerID))
	err = m.containerClient.WaitForContainer(containerID)
	if err != nil {
134
		m.failedContainerIDs = append(m.failedContainerIDs, containerID)
135
136
137
138
139
140

		return err
	}

	return nil
}
141

142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
func (m *cacheContainerManager) Cleanup(ctx context.Context, ids []string) chan bool {
	done := make(chan bool, 1)

	ids = append(m.failedContainerIDs, ids...)

	go func() {
		wg := new(sync.WaitGroup)
		wg.Add(len(ids))
		for _, id := range ids {
			m.remove(ctx, wg, id)
		}

		wg.Wait()
		done <- true
	}()

	return done
}

func (m *cacheContainerManager) remove(ctx context.Context, wg *sync.WaitGroup, id string) {
	go func() {
		err := m.containerClient.RemoveContainer(ctx, id)
		if err != nil {
			m.logger.Debugln(fmt.Sprintf("Error while removing the container: %v", err))
		}
		wg.Done()
	}()
169
}