Pull an image from registry and extract to the local image directory. Fetches the OCI manifest, downloads each layer blob, and extracts the tar (gzipped) contents into a flat directory.
(image_ref: &str, tag: &str, image_path: &Path)
| 91 | /// Fetches the OCI manifest, downloads each layer blob, and extracts |
| 92 | /// the tar (gzipped) contents into a flat directory. |
| 93 | pub async fn pull_and_extract(image_ref: &str, tag: &str, image_path: &Path) -> Result<()> { |
| 94 | let (registry, repo) = parse_image_ref(image_ref)?; |
| 95 | let client = build_client()?; |
| 96 | |
| 97 | info!("pulling image {image_ref}:{tag}"); |
| 98 | |
| 99 | // Resolve authentication |
| 100 | let token = try_fetch_token(&client, ®istry, &repo).await; |
| 101 | |
| 102 | // Fetch manifest |
| 103 | let manifest = fetch_manifest(&client, ®istry, &repo, tag, token.as_deref()).await?; |
| 104 | |
| 105 | // Determine output directory |
| 106 | let output_dir = determine_output_dir(tag, image_path); |
| 107 | if output_dir.exists() { |
| 108 | bail!("image directory already exists: {}", output_dir.display()); |
| 109 | } |
| 110 | |
| 111 | // Extract into temp dir first, then rename atomically |
| 112 | let tmp_dir = image_path.join(format!(".tmp-pull-{tag}")); |
| 113 | if tmp_dir.exists() { |
| 114 | fs_err::remove_dir_all(&tmp_dir).context("failed to clean up stale temp dir")?; |
| 115 | } |
| 116 | fs_err::create_dir_all(&tmp_dir)?; |
| 117 | |
| 118 | let result = download_and_extract_layers( |
| 119 | &client, |
| 120 | ®istry, |
| 121 | &repo, |
| 122 | &manifest, |
| 123 | token.as_deref(), |
| 124 | &tmp_dir, |
| 125 | ) |
| 126 | .await; |
| 127 | |
| 128 | if let Err(e) = &result { |
| 129 | tracing::error!("pull failed, cleaning up temp dir: {e:#}"); |
| 130 | let _ = fs_err::remove_dir_all(&tmp_dir); |
| 131 | return result; |
| 132 | } |
| 133 | |
| 134 | // Verify metadata.json exists |
| 135 | if !tmp_dir.join("metadata.json").exists() { |
| 136 | let _ = fs_err::remove_dir_all(&tmp_dir); |
| 137 | bail!("pulled image does not contain metadata.json - not a valid dstack guest image"); |
| 138 | } |
| 139 | |
| 140 | // Move to final location |
| 141 | fs_err::rename(&tmp_dir, &output_dir).with_context(|| { |
| 142 | format!( |
| 143 | "failed to rename {} to {}", |
| 144 | tmp_dir.display(), |
| 145 | output_dir.display() |
| 146 | ) |
| 147 | })?; |
| 148 | |
| 149 | info!("image extracted to {}", output_dir.display()); |
| 150 | Ok(()) |
no test coverage detected