|
1 | 1 | package manager |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "bytes" |
5 | | - "encoding/json" |
6 | 4 | "fmt" |
7 | | - "os/exec" |
8 | 5 | "path/filepath" |
9 | 6 | "strings" |
10 | 7 | "time" |
11 | 8 |
|
12 | | - "github.com/docker/docker/api/types" |
13 | 9 | "github.com/sdslabs/beastv4/core" |
14 | 10 | "github.com/sdslabs/beastv4/core/cache" |
15 | 11 | "github.com/sdslabs/beastv4/core/config" |
@@ -236,238 +232,13 @@ func ProcessInstanceDeletionQueue() { |
236 | 232 | func CleanupOrphanedInstanceContainers() { |
237 | 233 | log.Debug("Checking for orphaned instance containers") |
238 | 234 |
|
239 | | - cleanupOrphanedOnServer(core.LOCALHOST) |
240 | | - cleanupOrphanedComposeInstancesOnServer(core.LOCALHOST) |
| 235 | + cr.CleanupOrphans() |
| 236 | + cr.CleanupOrphanedComposeInstances() |
241 | 237 |
|
242 | 238 | for host, server := range config.Cfg.AvailableServers { |
243 | 239 | if server.Active && host != core.LOCALHOST { |
244 | | - cleanupOrphanedOnServer(host) |
245 | | - cleanupOrphanedComposeInstancesOnServer(host) |
| 240 | + remoteManager.CleanupOrphanedOnServer(host) |
| 241 | + remoteManager.CleanupOrphanedComposeInstancesOnServer(host) |
246 | 242 | } |
247 | 243 | } |
248 | 244 | } |
249 | | - |
250 | | -func cleanupOrphanedOnServer(serverHost string) { |
251 | | - var containers []types.Container |
252 | | - var err error |
253 | | - |
254 | | - if serverHost == core.LOCALHOST { |
255 | | - containers, err = cr.SearchContainerByFilter(map[string]string{ |
256 | | - "label": "beast.instance=true", |
257 | | - }) |
258 | | - } else { |
259 | | - server := config.Cfg.AvailableServers[serverHost] |
260 | | - containers, err = remoteManager.SearchContainerByFilterRemote(map[string]string{ |
261 | | - "label": "beast.instance=true", |
262 | | - }, server) |
263 | | - } |
264 | | - |
265 | | - if err != nil { |
266 | | - log.Warnf("Failed to search for instance containers on %s: %v", serverHost, err) |
267 | | - return |
268 | | - } |
269 | | - |
270 | | - for _, container := range containers { |
271 | | - instanceID := container.Labels["beast.instance.id"] |
272 | | - if instanceID == "" { |
273 | | - for _, name := range container.Names { |
274 | | - name = strings.TrimPrefix(name, "/") |
275 | | - if strings.HasPrefix(name, "beast_instance_") { |
276 | | - parts := strings.Split(name, "_") |
277 | | - if len(parts) >= 4 { |
278 | | - instanceID = parts[len(parts)-1] |
279 | | - break |
280 | | - } |
281 | | - } |
282 | | - } |
283 | | - } |
284 | | - |
285 | | - if instanceID == "" { |
286 | | - continue |
287 | | - } |
288 | | - |
289 | | - _, err := cache.GetInstance(instanceID) |
290 | | - if err != nil { |
291 | | - containerName := "" |
292 | | - if len(container.Names) > 0 { |
293 | | - containerName = strings.TrimPrefix(container.Names[0], "/") |
294 | | - } |
295 | | - log.Infof("Removing orphaned instance container: %s (instance %s) on %s", containerName, instanceID, serverHost) |
296 | | - |
297 | | - if serverHost == core.LOCALHOST { |
298 | | - if err := cr.StopAndRemoveContainer(container.ID); err != nil { |
299 | | - log.Warnf("Failed to remove orphaned container %s: %v", container.ID[:12], err) |
300 | | - } |
301 | | - } else { |
302 | | - server := config.Cfg.AvailableServers[serverHost] |
303 | | - if err := remoteManager.StopAndRemoveContainerRemote(container.ID, server); err != nil { |
304 | | - log.Warnf("Failed to remove orphaned container %s on %s: %v", container.ID[:12], serverHost, err) |
305 | | - } |
306 | | - } |
307 | | - |
308 | | - cache.FreeContainerPortsOnHost(serverHost, container.ID) |
309 | | - } |
310 | | - } |
311 | | -} |
312 | | - |
313 | | -// cleanupOrphanedComposeInstancesOnServer finds and removes orphaned docker compose instance projects. |
314 | | -// Docker Compose containers don't have the beast.instance labels, but they have |
315 | | -// com.docker.compose.project labels with project names starting with "beast-instance-". |
316 | | -func cleanupOrphanedComposeInstancesOnServer(serverHost string) { |
317 | | - var projectNames []string |
318 | | - var err error |
319 | | - |
320 | | - if serverHost == core.LOCALHOST { |
321 | | - projectNames, err = getOrphanedComposeInstanceProjects() |
322 | | - } else { |
323 | | - server := config.Cfg.AvailableServers[serverHost] |
324 | | - projectNames, err = getOrphanedComposeInstanceProjectsRemote(server) |
325 | | - } |
326 | | - |
327 | | - if err != nil { |
328 | | - log.Warnf("Failed to get compose instance projects on %s: %v", serverHost, err) |
329 | | - return |
330 | | - } |
331 | | - |
332 | | - for _, projectName := range projectNames { |
333 | | - // Extract instance ID from project name: beast-instance-{encoded_challenge}-{instanceID} |
334 | | - parts := strings.Split(projectName, "-") |
335 | | - if len(parts) < 4 { |
336 | | - continue |
337 | | - } |
338 | | - instanceID := parts[len(parts)-1] |
339 | | - |
340 | | - // Check if instance still exists in cache |
341 | | - _, err := cache.GetInstance(instanceID) |
342 | | - if err != nil { |
343 | | - log.Infof("Removing orphaned compose instance project: %s (instance %s) on %s", projectName, instanceID, serverHost) |
344 | | - |
345 | | - if serverHost == core.LOCALHOST { |
346 | | - if err := composeDownProject(projectName); err != nil { |
347 | | - log.Warnf("Failed to remove orphaned compose project %s: %v", projectName, err) |
348 | | - } |
349 | | - } else { |
350 | | - server := config.Cfg.AvailableServers[serverHost] |
351 | | - if err := composeDownProjectRemote(projectName, server); err != nil { |
352 | | - log.Warnf("Failed to remove orphaned compose project %s on %s: %v", projectName, serverHost, err) |
353 | | - } |
354 | | - } |
355 | | - } |
356 | | - } |
357 | | -} |
358 | | - |
359 | | -// getOrphanedComposeInstanceProjects returns a list of docker compose project names |
360 | | -// that match the instance naming pattern (beast-instance-*) |
361 | | -func getOrphanedComposeInstanceProjects() ([]string, error) { |
362 | | - cmd := exec.Command("docker", "compose", "ls", "--format", "json") |
363 | | - var output bytes.Buffer |
364 | | - cmd.Stdout = &output |
365 | | - cmd.Stderr = &output |
366 | | - |
367 | | - if err := cmd.Run(); err != nil { |
368 | | - return nil, fmt.Errorf("docker compose ls failed: %v, output: %s", err, output.String()) |
369 | | - } |
370 | | - |
371 | | - type ComposeProject struct { |
372 | | - Name string `json:"Name"` |
373 | | - Status string `json:"Status"` |
374 | | - } |
375 | | - |
376 | | - var projects []ComposeProject |
377 | | - outputStr := strings.TrimSpace(output.String()) |
378 | | - if outputStr == "" { |
379 | | - return nil, nil |
380 | | - } |
381 | | - |
382 | | - if err := json.Unmarshal([]byte(outputStr), &projects); err != nil { |
383 | | - // Try parsing line by line (older docker compose versions) |
384 | | - for _, line := range strings.Split(outputStr, "\n") { |
385 | | - if strings.TrimSpace(line) == "" { |
386 | | - continue |
387 | | - } |
388 | | - var project ComposeProject |
389 | | - if err := json.Unmarshal([]byte(line), &project); err != nil { |
390 | | - continue |
391 | | - } |
392 | | - projects = append(projects, project) |
393 | | - } |
394 | | - } |
395 | | - |
396 | | - var instanceProjects []string |
397 | | - for _, project := range projects { |
398 | | - if strings.HasPrefix(project.Name, "beast-instance-") { |
399 | | - instanceProjects = append(instanceProjects, project.Name) |
400 | | - } |
401 | | - } |
402 | | - |
403 | | - return instanceProjects, nil |
404 | | -} |
405 | | - |
406 | | -// getOrphanedComposeInstanceProjectsRemote returns compose instance projects on a remote server |
407 | | -func getOrphanedComposeInstanceProjectsRemote(server config.AvailableServer) ([]string, error) { |
408 | | - output, err := remoteManager.RunCommandOnServer(server, "docker compose ls --format json") |
409 | | - if err != nil { |
410 | | - return nil, fmt.Errorf("docker compose ls failed on remote: %v", err) |
411 | | - } |
412 | | - |
413 | | - type ComposeProject struct { |
414 | | - Name string `json:"Name"` |
415 | | - Status string `json:"Status"` |
416 | | - } |
417 | | - |
418 | | - var projects []ComposeProject |
419 | | - outputStr := strings.TrimSpace(output) |
420 | | - if outputStr == "" { |
421 | | - return nil, nil |
422 | | - } |
423 | | - |
424 | | - if err := json.Unmarshal([]byte(outputStr), &projects); err != nil { |
425 | | - // Try parsing line by line |
426 | | - for _, line := range strings.Split(outputStr, "\n") { |
427 | | - if strings.TrimSpace(line) == "" { |
428 | | - continue |
429 | | - } |
430 | | - var project ComposeProject |
431 | | - if err := json.Unmarshal([]byte(line), &project); err != nil { |
432 | | - continue |
433 | | - } |
434 | | - projects = append(projects, project) |
435 | | - } |
436 | | - } |
437 | | - |
438 | | - var instanceProjects []string |
439 | | - for _, project := range projects { |
440 | | - if strings.HasPrefix(project.Name, "beast-instance-") { |
441 | | - instanceProjects = append(instanceProjects, project.Name) |
442 | | - } |
443 | | - } |
444 | | - |
445 | | - return instanceProjects, nil |
446 | | -} |
447 | | - |
448 | | -// composeDownProject removes a docker compose project by name |
449 | | -func composeDownProject(projectName string) error { |
450 | | - cmd := exec.Command("docker", "compose", "-p", projectName, "down", "--remove-orphans", "-v") |
451 | | - var output bytes.Buffer |
452 | | - cmd.Stdout = &output |
453 | | - cmd.Stderr = &output |
454 | | - |
455 | | - if err := cmd.Run(); err != nil { |
456 | | - return fmt.Errorf("docker compose down failed: %v, output: %s", err, output.String()) |
457 | | - } |
458 | | - |
459 | | - log.Debugf("Successfully removed compose project %s", projectName) |
460 | | - return nil |
461 | | -} |
462 | | - |
463 | | -// composeDownProjectRemote removes a docker compose project on a remote server |
464 | | -func composeDownProjectRemote(projectName string, server config.AvailableServer) error { |
465 | | - cmd := fmt.Sprintf("docker compose -p %s down --remove-orphans -v", projectName) |
466 | | - output, err := remoteManager.RunCommandOnServer(server, cmd) |
467 | | - if err != nil { |
468 | | - return fmt.Errorf("docker compose down failed on remote: %v, output: %s", err, output) |
469 | | - } |
470 | | - |
471 | | - log.Debugf("Successfully removed compose project %s on %s", projectName, server.Host) |
472 | | - return nil |
473 | | -} |
0 commit comments