gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+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 "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 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 logger: logger, 43 containerClient: cClient, 44 helperImage: helperImage, 45 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 return "" 54 } 55 56 // check if we have valid cache, if not remove the broken container 57 _, ok := inspected.Config.Volumes[containerPath] 58 if !ok { 59 m.logger.Debugln(fmt.Sprintf("Removing broken cache container for %q path", containerPath)) 60 err = m.containerClient.RemoveContainer(m.ctx, inspected.ID) 61 m.logger.Debugln(fmt.Sprintf("Cache container for %q path removed with %v", containerPath, err)) 62 63 return "" 64 } 65 66 return inspected.ID 67 } 68 69 func (m *cacheContainerManager) Create(containerName string, containerPath string) (string, error) { 70 containerID, err := m.createCacheContainer(containerName, containerPath) 71 if err != nil { 72 return "", err 73 } 74 75 err = m.startCacheContainer(containerID) 76 if err != nil { 77 return "", err 78 } 79 80 return containerID, nil 81 } 82 83 func (m *cacheContainerManager) createCacheContainer(containerName string, containerPath string) (string, error) { 84 config := &container.Config{ 85 Image: m.helperImage.ID, 86 Cmd: m.getCacheCommand(containerPath), 87 Volumes: map[string]struct{}{ 88 containerPath: {}, 89 }, 90 } 91 m.containerClient.LabelContainer(config, "cache", "cache.dir="+containerPath) 92 93 hostConfig := &container.HostConfig{ 94 LogConfig: container.LogConfig{ 95 Type: "json-file", 96 }, 97 } 98 99 resp, err := m.containerClient.ContainerCreate(m.ctx, config, hostConfig, nil, containerName) 100 if err != nil { 101 if resp.ID != "" { 102 m.failedContainerIDs = append(m.failedContainerIDs, resp.ID) 103 } 104 105 return "", err 106 } 107 108 return resp.ID, nil 109 } 110 111 func (m *cacheContainerManager) getCacheCommand(containerPath string) []string { 112 // TODO: Remove in 12.0 to start using the command from `gitlab-runner-helper` 113 if m.outdatedHelperImage { 114 m.logger.Debugln("Falling back to old gitlab-runner-cache command") 115 return []string{"gitlab-runner-cache", containerPath} 116 } 117 118 return []string{"gitlab-runner-helper", "cache-init", containerPath} 119 120 } 121 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 return err 129 } 130 131 m.logger.Debugln(fmt.Sprintf("Waiting for cache container %q...", containerID)) 132 err = m.containerClient.WaitForContainer(containerID) 133 if err != nil { 134 m.failedContainerIDs = append(m.failedContainerIDs, containerID) 135 136 return err 137 } 138 139 return nil 140 } 141 142 func (m *cacheContainerManager) Cleanup(ctx context.Context, ids []string) chan bool { 143 done := make(chan bool, 1) 144 145 ids = append(m.failedContainerIDs, ids...) 146 147 go func() { 148 wg := new(sync.WaitGroup) 149 wg.Add(len(ids)) 150 for _, id := range ids { 151 m.remove(ctx, wg, id) 152 } 153 154 wg.Wait() 155 done <- true 156 }() 157 158 return done 159 } 160 161 func (m *cacheContainerManager) remove(ctx context.Context, wg *sync.WaitGroup, id string) { 162 go func() { 163 err := m.containerClient.RemoveContainer(ctx, id) 164 if err != nil { 165 m.logger.Debugln(fmt.Sprintf("Error while removing the container: %v", err)) 166 } 167 wg.Done() 168 }() 169 }