|
1 | | -use anyhow::Result; |
2 | | - |
3 | | -use crate::error::{self, TopgradeError}; |
4 | | -use crate::executor::CommandExt; |
5 | | -use crate::terminal::print_separator; |
6 | | -use crate::{execution_context::ExecutionContext, utils::require}; |
7 | | -use log::{debug, error, warn}; |
8 | | -use std::path::Path; |
9 | | -use std::process::Command; |
10 | | - |
11 | | -// A string found in the output of docker for containers that weren't found in |
12 | | -// the docker registry. We use this to gracefully handle and skip containers |
13 | | -// that cannot be pulled, likely because they don't exist in the registry in |
14 | | -// the first place. This happens e.g. when the user tags an image locally |
15 | | -// themselves or when using docker-compose. |
16 | | -const NONEXISTENT_REPO: &str = "repository does not exist"; |
17 | | - |
18 | | -/// Returns a Vector of all containers, with Strings in the format |
19 | | -/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG" |
20 | | -fn list_containers(crt: &Path) -> Result<Vec<String>> { |
21 | | - debug!( |
22 | | - "Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}\"' for containers", |
23 | | - crt.display() |
24 | | - ); |
25 | | - let output = Command::new(crt) |
26 | | - .args(&["image", "ls", "--format", "{{.Repository}}:{{.Tag}}"]) |
27 | | - .output()?; |
28 | | - let output_str = String::from_utf8(output.stdout)?; |
29 | | - |
30 | | - let mut retval = vec![]; |
31 | | - for line in output_str.lines() { |
32 | | - if line.starts_with("localhost") { |
33 | | - // Don't know how to update self-built containers |
34 | | - debug!("Skipping self-built container '{}'", line); |
35 | | - continue; |
36 | | - } |
37 | | - |
38 | | - if line.contains("<none>") { |
39 | | - // Bogus/dangling container or intermediate layer |
40 | | - debug!("Skipping bogus container '{}'", line); |
41 | | - continue; |
42 | | - } |
43 | | - |
44 | | - debug!("Using container '{}'", line); |
45 | | - retval.push(String::from(line)); |
46 | | - } |
47 | | - |
48 | | - Ok(retval) |
49 | | -} |
50 | | - |
51 | | -pub fn run_containers(ctx: &ExecutionContext) -> Result<()> { |
52 | | - // Prefer podman, fall back to docker if not present |
53 | | - let crt = require("podman").or_else(|_| require("docker"))?; |
54 | | - debug!("Using container runtime '{}'", crt.display()); |
55 | | - |
56 | | - print_separator("Containers"); |
57 | | - let mut success = true; |
58 | | - let containers = list_containers(&crt)?; |
59 | | - debug!("Containers to inspect: {:?}", containers); |
60 | | - |
61 | | - for container in containers.iter() { |
62 | | - debug!("Pulling container '{}'", container); |
63 | | - let args = vec!["pull", &container[..]]; |
64 | | - let mut exec = ctx.run_type().execute(&crt); |
65 | | - |
66 | | - if let Err(e) = exec.args(&args).check_run() { |
67 | | - error!("Pulling container '{}' failed: {}", container, e); |
68 | | - |
69 | | - // Find out if this is 'skippable' |
70 | | - // This is necessary e.g. for docker, because unlike podman docker doesn't tell from |
71 | | - // which repository a container originates (such as `docker.io`). This has the |
72 | | - // practical consequence that all containers, whether self-built, created by |
73 | | - // docker-compose or pulled from the docker hub, look exactly the same to us. We can |
74 | | - // only find out what went wrong by manually parsing the output of the command... |
75 | | - if match exec.check_output() { |
76 | | - Ok(s) => s.contains(NONEXISTENT_REPO), |
77 | | - Err(e) => match e.downcast_ref::<TopgradeError>() { |
78 | | - Some(TopgradeError::ProcessFailedWithOutput(_, stderr)) => stderr.contains(NONEXISTENT_REPO), |
79 | | - _ => false, |
80 | | - }, |
81 | | - } { |
82 | | - warn!("Skipping unknown container '{}'", container); |
83 | | - continue; |
84 | | - } |
85 | | - |
86 | | - success = false; |
87 | | - } |
88 | | - } |
89 | | - |
90 | | - if ctx.config().cleanup() { |
91 | | - // Remove dangling images |
92 | | - debug!("Removing dangling images"); |
93 | | - if let Err(e) = ctx.run_type().execute(&crt).args(&["image", "prune", "-f"]).check_run() { |
94 | | - error!("Removing dangling images failed: {}", e); |
95 | | - success = false; |
96 | | - } |
97 | | - } |
98 | | - |
99 | | - if success { |
100 | | - Ok(()) |
101 | | - } else { |
102 | | - Err(anyhow::anyhow!(error::StepFailed)) |
103 | | - } |
104 | | -} |
| 1 | +use anyhow::Result; |
| 2 | + |
| 3 | +use crate::error::{self, TopgradeError}; |
| 4 | +use crate::executor::CommandExt; |
| 5 | +use crate::terminal::print_separator; |
| 6 | +use crate::{execution_context::ExecutionContext, utils::require}; |
| 7 | +use log::{debug, error, warn}; |
| 8 | +use std::path::Path; |
| 9 | +use std::process::Command; |
| 10 | + |
| 11 | +// A string found in the output of docker for containers that weren't found in |
| 12 | +// the docker registry. We use this to gracefully handle and skip containers |
| 13 | +// that cannot be pulled, likely because they don't exist in the registry in |
| 14 | +// the first place. This happens e.g. when the user tags an image locally |
| 15 | +// themselves or when using docker-compose. |
| 16 | +const NONEXISTENT_REPO: &str = "repository does not exist"; |
| 17 | + |
| 18 | +/// Returns a Vector of all containers, with Strings in the format |
| 19 | +/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG" |
| 20 | +fn list_containers(crt: &Path) -> Result<Vec<String>> { |
| 21 | + debug!( |
| 22 | + "Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}\"' for containers", |
| 23 | + crt.display() |
| 24 | + ); |
| 25 | + let output = Command::new(crt) |
| 26 | + .args(&["image", "ls", "--format", "{{.Repository}}:{{.Tag}}"]) |
| 27 | + .output()?; |
| 28 | + let output_str = String::from_utf8(output.stdout)?; |
| 29 | + |
| 30 | + let mut retval = vec![]; |
| 31 | + for line in output_str.lines() { |
| 32 | + if line.starts_with("localhost") { |
| 33 | + // Don't know how to update self-built containers |
| 34 | + debug!("Skipping self-built container '{}'", line); |
| 35 | + continue; |
| 36 | + } |
| 37 | + |
| 38 | + if line.contains("<none>") { |
| 39 | + // Bogus/dangling container or intermediate layer |
| 40 | + debug!("Skipping bogus container '{}'", line); |
| 41 | + continue; |
| 42 | + } |
| 43 | + |
| 44 | + if line.starts_with("vsc-") { |
| 45 | + debug!("Skipping visual studio code dev container '{}'", line); |
| 46 | + continue; |
| 47 | + } |
| 48 | + |
| 49 | + debug!("Using container '{}'", line); |
| 50 | + retval.push(String::from(line)); |
| 51 | + } |
| 52 | + |
| 53 | + Ok(retval) |
| 54 | +} |
| 55 | + |
| 56 | +pub fn run_containers(ctx: &ExecutionContext) -> Result<()> { |
| 57 | + // Prefer podman, fall back to docker if not present |
| 58 | + let crt = require("podman").or_else(|_| require("docker"))?; |
| 59 | + debug!("Using container runtime '{}'", crt.display()); |
| 60 | + |
| 61 | + print_separator("Containers"); |
| 62 | + let mut success = true; |
| 63 | + let containers = list_containers(&crt)?; |
| 64 | + debug!("Containers to inspect: {:?}", containers); |
| 65 | + |
| 66 | + for container in containers.iter() { |
| 67 | + debug!("Pulling container '{}'", container); |
| 68 | + let args = vec!["pull", &container[..]]; |
| 69 | + let mut exec = ctx.run_type().execute(&crt); |
| 70 | + |
| 71 | + if let Err(e) = exec.args(&args).check_run() { |
| 72 | + error!("Pulling container '{}' failed: {}", container, e); |
| 73 | + |
| 74 | + // Find out if this is 'skippable' |
| 75 | + // This is necessary e.g. for docker, because unlike podman docker doesn't tell from |
| 76 | + // which repository a container originates (such as `docker.io`). This has the |
| 77 | + // practical consequence that all containers, whether self-built, created by |
| 78 | + // docker-compose or pulled from the docker hub, look exactly the same to us. We can |
| 79 | + // only find out what went wrong by manually parsing the output of the command... |
| 80 | + if match exec.check_output() { |
| 81 | + Ok(s) => s.contains(NONEXISTENT_REPO), |
| 82 | + Err(e) => match e.downcast_ref::<TopgradeError>() { |
| 83 | + Some(TopgradeError::ProcessFailedWithOutput(_, stderr)) => stderr.contains(NONEXISTENT_REPO), |
| 84 | + _ => false, |
| 85 | + }, |
| 86 | + } { |
| 87 | + warn!("Skipping unknown container '{}'", container); |
| 88 | + continue; |
| 89 | + } |
| 90 | + |
| 91 | + success = false; |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + if ctx.config().cleanup() { |
| 96 | + // Remove dangling images |
| 97 | + debug!("Removing dangling images"); |
| 98 | + if let Err(e) = ctx.run_type().execute(&crt).args(&["image", "prune", "-f"]).check_run() { |
| 99 | + error!("Removing dangling images failed: {}", e); |
| 100 | + success = false; |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + if success { |
| 105 | + Ok(()) |
| 106 | + } else { |
| 107 | + Err(anyhow::anyhow!(error::StepFailed)) |
| 108 | + } |
| 109 | +} |
0 commit comments