-
Notifications
You must be signed in to change notification settings - Fork 206
Add URL-based build sources (GCS/HTTPS) to cvd fetch #2238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ | |
| #include "absl/strings/match.h" | ||
| #include "absl/strings/strip.h" | ||
|
|
||
| #include "cuttlefish/common/libs/utils/archive.h" | ||
| #include "cuttlefish/common/libs/utils/files.h" | ||
| #include "cuttlefish/host/commands/cvd/fetch/builds.h" | ||
| #include "cuttlefish/host/commands/cvd/fetch/de_android_sparse.h" | ||
|
|
@@ -64,10 +65,20 @@ Result<void> FetchArtifact::DownloadTo(std::string local_path) { | |
| fmt::format("{}/{}", fetch_build_context_.target_directory_, local_path); | ||
|
|
||
| if (downloaded_path_.empty()) { | ||
| std::string downloaded = | ||
| CF_EXPECT(fetch_build_context_.fetch_context_.build_api_.DownloadFile( | ||
| fetch_build_context_.build_, fetch_build_context_.target_directory_, | ||
| artifact_name_)); | ||
| std::string downloaded; | ||
| // URL builds are downloaded via UrlDownloader, bypassing the Android | ||
| // Build API which does not support arbitrary URLs. | ||
| if (auto* url_build = | ||
| std::get_if<UrlBuild>(&fetch_build_context_.build_)) { | ||
| downloaded = CF_EXPECT( | ||
| fetch_build_context_.fetch_context_.url_downloader_.Download( | ||
| url_build->url, fetch_build_context_.target_directory_)); | ||
| } else { | ||
| downloaded = CF_EXPECT( | ||
| fetch_build_context_.fetch_context_.build_api_.DownloadFile( | ||
| fetch_build_context_.build_, | ||
| fetch_build_context_.target_directory_, artifact_name_)); | ||
| } | ||
| size_t size = FileSize(downloaded); | ||
| std::string download_phase = fmt::format("Downloaded '{}'", artifact_name_); | ||
| fetch_build_context_.trace_.CompletePhase(download_phase, size); | ||
|
|
@@ -91,9 +102,19 @@ Result<void> FetchArtifact::DownloadTo(std::string local_path) { | |
|
|
||
| Result<ReadableZip*> FetchArtifact::AsZip() { | ||
| if (!zip_) { | ||
| zip_ = CF_EXPECT( | ||
| ::cuttlefish::OpenZip(fetch_build_context_.fetch_context_.build_api_, | ||
| fetch_build_context_.build_, artifact_name_)); | ||
| if (std::holds_alternative<UrlBuild>(fetch_build_context_.build_)) { | ||
| // URL builds cannot stream zip contents from a build API, so the | ||
| // artifact must be downloaded to disk first. | ||
| if (downloaded_path_.empty()) { | ||
| CF_EXPECT(Download()); | ||
| } | ||
| CF_EXPECT(zip_.has_value(), | ||
| "Downloaded URL artifact is not a zip file"); | ||
| } else { | ||
| zip_ = CF_EXPECT( | ||
| ::cuttlefish::OpenZip(fetch_build_context_.fetch_context_.build_api_, | ||
| fetch_build_context_.build_, artifact_name_)); | ||
| } | ||
| } | ||
| return &*zip_; | ||
| } | ||
|
|
@@ -104,6 +125,32 @@ Result<void> FetchArtifact::ExtractAll() { | |
| } | ||
|
|
||
| Result<void> FetchArtifact::ExtractAll(const std::string& local_path) { | ||
| // Non-zip archives (tar.gz, tgz) require downloading first and use the | ||
| // system archive utility for extraction. | ||
| if (absl::EndsWith(artifact_name_, ".tar.gz") || | ||
| absl::EndsWith(artifact_name_, ".tgz")) { | ||
|
Comment on lines
+128
to
+131
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: IIUC this adds supports for downloading tar.gz and tgz files in addition to zip files. This is somewhat unrelated to the general url downloading feature, so it could be in its own commit to make reviewing easier. |
||
| if (downloaded_path_.empty()) { | ||
| CF_EXPECT(Download()); | ||
| } | ||
| std::string extract_dir = fetch_build_context_.target_directory_; | ||
| if (!local_path.empty()) { | ||
| extract_dir = fmt::format("{}/{}", extract_dir, local_path); | ||
| CF_EXPECT(EnsureDirectoryExists(extract_dir, kRwxAllMode)); | ||
| } | ||
| std::vector<std::string> extracted = CF_EXPECT( | ||
| ExtractArchiveContents(downloaded_path_, extract_dir, | ||
| /* keep_archive= */ true)); | ||
| size_t total_size = 0; | ||
| for (const std::string& file : extracted) { | ||
| total_size += FileSize(file); | ||
| CF_EXPECT(fetch_build_context_.AddFileToConfig(file)); | ||
| } | ||
| std::string phase = fmt::format("Extracted '{}'", artifact_name_); | ||
| fetch_build_context_.trace_.CompletePhase(std::move(phase), total_size); | ||
| return {}; | ||
| } | ||
|
|
||
| // Zip extraction uses the streaming zip reader. | ||
| ReadableZip* zip = CF_EXPECT(AsZip()); | ||
| size_t entries = CF_EXPECT(zip->NumEntries()); | ||
| for (uint64_t i = 0; i < entries; i++) { | ||
|
|
@@ -181,8 +228,14 @@ FetchBuildContext::FetchBuildContext(FetchContext& fetch_context, | |
| const Build& FetchBuildContext::Build() const { return build_; } | ||
|
|
||
| std::string FetchBuildContext::GetBuildZipName(const std::string& name) const { | ||
| std::string product = | ||
| std::visit([](auto&& arg) { return arg.product; }, build_); | ||
| struct ProductVisitor { | ||
| std::string operator()(const DeviceBuild& b) const { return b.product; } | ||
| std::string operator()(const DirectoryBuild& b) const { | ||
| return b.product; | ||
| } | ||
| std::string operator()(const UrlBuild&) const { return "url"; } | ||
| }; | ||
| std::string product = std::visit(ProductVisitor{}, build_); | ||
| std::string id = std::get<0>(GetBuildIdAndTarget(build_)); | ||
| return product + "-" + name + "-" + id + ".zip"; | ||
| } | ||
|
|
@@ -233,10 +286,12 @@ std::ostream& operator<<(std::ostream& out, const FetchBuildContext& context) { | |
| } | ||
|
|
||
| FetchContext::FetchContext(BuildApi& build_api, | ||
| UrlDownloader& url_downloader, | ||
| const TargetDirectories& target_directories, | ||
| const Builds& builds, FetcherConfig& fetcher_config, | ||
| FetchTracer& tracer) | ||
| : build_api_(build_api), | ||
| url_downloader_(url_downloader), | ||
| target_directories_(target_directories), | ||
| builds_(builds), | ||
| fetcher_config_(fetcher_config), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,11 +28,16 @@ | |
| #include "cuttlefish/host/libs/config/file_source.h" | ||
| #include "cuttlefish/host/libs/web/android_build.h" | ||
| #include "cuttlefish/host/libs/web/build_api.h" | ||
| #include "cuttlefish/host/libs/web/url_downloader.h" | ||
| #include "cuttlefish/host/libs/zip/libzip_cc/archive.h" | ||
| #include "cuttlefish/result/result.h" | ||
|
|
||
| namespace cuttlefish { | ||
|
|
||
| // Manages downloading and extracting a single artifact from a build. | ||
| // Handles both Android Build API artifacts and URL-based artifacts | ||
| // transparently. Tracks whether the artifact has already been downloaded | ||
| // to avoid redundant fetches. | ||
| class FetchArtifact { | ||
| public: | ||
| Result<void> Download(); | ||
|
|
@@ -60,21 +65,20 @@ class FetchArtifact { | |
| std::optional<ReadableZip> zip_; | ||
| }; | ||
|
|
||
| /** | ||
| * Wraps standard download operations with cross-cutting concerns: | ||
| * - Tracing long-running operations with time used. | ||
| * - Tracking the source build of created files. | ||
| * - Placing files under the right target directory. | ||
| * - Desparsing images. | ||
| * | ||
| * File paths for return values and argument values are relative to the target | ||
| * directory. | ||
| * | ||
| * By hiding the target directory from direct access, IO operations are funneled | ||
| * through an instance of this type, which guarantees none of the cross-cutting | ||
| * concerns are missed. Additionally, this could be replaced with a fake | ||
| * implementation later to support unit testing the business logic. | ||
| */ | ||
| // Wraps standard download operations with cross-cutting concerns: | ||
| // - Tracing long-running operations with time used. | ||
| // - Tracking the source build of created files. | ||
| // - Placing files under the right target directory. | ||
| // - Desparsing images. | ||
| // | ||
| // File paths for return values and argument values are relative to the target | ||
| // directory. | ||
| // | ||
| // By hiding the target directory from direct access, IO operations are | ||
| // funneled through an instance of this type, which guarantees none of the | ||
| // cross-cutting concerns are missed. Additionally, this could be replaced | ||
| // with a fake implementation later to support unit testing the business | ||
| // logic. | ||
|
Comment on lines
+68
to
+81
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep the old comment format, changing it only adds noise to the review. |
||
| class FetchBuildContext { | ||
| public: | ||
| const Build& Build() const; | ||
|
|
@@ -107,14 +111,12 @@ class FetchBuildContext { | |
|
|
||
| std::ostream& operator<<(std::ostream&, const FetchBuildContext&); | ||
|
|
||
| /** | ||
| * References common state used by most download operations and produces | ||
| * `FetchBuildContext` instances. | ||
| */ | ||
| // References common state used by most download operations and produces | ||
| // FetchBuildContext instances for each configured build component. | ||
| class FetchContext { | ||
| public: | ||
| FetchContext(BuildApi&, const TargetDirectories&, const Builds&, | ||
| FetcherConfig&, FetchTracer&); | ||
| FetchContext(BuildApi&, UrlDownloader&, const TargetDirectories&, | ||
| const Builds&, FetcherConfig&, FetchTracer&); | ||
|
|
||
| std::optional<FetchBuildContext> DefaultBuild(); | ||
| std::optional<FetchBuildContext> SystemBuild(); | ||
|
|
@@ -130,6 +132,7 @@ class FetchContext { | |
| friend class FetchBuildContext; | ||
|
|
||
| BuildApi& build_api_; | ||
| UrlDownloader& url_downloader_; | ||
| const TargetDirectories& target_directories_; | ||
| const Builds& builds_; | ||
| FetcherConfig& fetcher_config_; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
::cuttlefish::OpenZipclass should handle http and https urls and probaly gs:// too. The full download should be avoided.