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 }