Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion base/cvd/cuttlefish/host/commands/cvd/fetch/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ cf_cc_library(
"//cuttlefish/host/libs/web:credential_source",
"//cuttlefish/host/libs/web:luci_build_api",
"//cuttlefish/host/libs/web:oauth2_consent",
"//cuttlefish/host/libs/web:url_downloader",
"//cuttlefish/host/libs/web/http_client",
"//cuttlefish/host/libs/web/http_client:curl_http_client",
"//cuttlefish/host/libs/web/http_client:retrying_http_client",
Expand Down Expand Up @@ -164,6 +165,7 @@ cf_cc_library(
"//cuttlefish/host/libs/web:android_build_api",
"//cuttlefish/host/libs/web:build_api",
"//cuttlefish/host/libs/web:build_api_zip",
"//cuttlefish/host/libs/web:url_downloader",
"//cuttlefish/host/libs/zip:zip_file",
"//cuttlefish/host/libs/zip/libzip_cc:archive",
"//cuttlefish/posix:strerror",
Expand Down Expand Up @@ -192,7 +194,6 @@ cf_cc_library(
"//cuttlefish/host/commands/cvd/fetch:fetch_tracer",
"//cuttlefish/host/commands/cvd/fetch:host_package",
"//cuttlefish/host/commands/cvd/fetch:host_tools_target",
"//cuttlefish/host/commands/cvd/fetch:substitute",
"//cuttlefish/host/commands/cvd/fetch:target_directories",
"//cuttlefish/host/commands/cvd/utils:common",
"//cuttlefish/host/libs/config:fetcher_config",
Expand All @@ -202,6 +203,7 @@ cf_cc_library(
"//cuttlefish/host/libs/web:android_build_string",
"//cuttlefish/host/libs/web:chrome_os_build_string",
"//cuttlefish/host/libs/web:luci_build_api",
"//cuttlefish/host/libs/web:url_downloader",
"//cuttlefish/host/libs/web/http_client:curl_global_init",
"//cuttlefish/host/libs/zip:zip_string",
"//cuttlefish/host/libs/zip/libzip_cc:archive",
Expand Down Expand Up @@ -266,6 +268,7 @@ cf_cc_library(
"//cuttlefish/host/libs/web:android_build",
"//cuttlefish/host/libs/web:android_build_api",
"//cuttlefish/host/libs/web:build_api",
"//cuttlefish/host/libs/web:url_downloader",
"//cuttlefish/result",
"//libbase",
"@abseil-cpp//absl/log",
Expand Down
8 changes: 8 additions & 0 deletions base/cvd/cuttlefish/host/commands/cvd/fetch/downloaders.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "cuttlefish/host/libs/web/http_client/retrying_http_client.h"
#include "cuttlefish/host/libs/web/luci_build_api.h"
#include "cuttlefish/host/libs/web/oauth2_consent.h"
#include "cuttlefish/host/libs/web/url_downloader.h"
#include "cuttlefish/result/result.h"

namespace cuttlefish {
Expand All @@ -50,6 +51,7 @@ struct Downloaders::Impl {
std::unique_ptr<CredentialSource> luci_credential_source_;
std::unique_ptr<CredentialSource> gsutil_credential_source_;
std::unique_ptr<LuciBuildApi> luci_build_api_;
std::unique_ptr<UrlDownloader> url_downloader_;
};

Downloaders::Downloaders(std::unique_ptr<Downloaders::Impl> impl)
Expand Down Expand Up @@ -114,6 +116,10 @@ Result<Downloaders> Downloaders::Create(const BuildApiFlags& flags,
*impl->retrying_http_client_, impl->luci_credential_source_.get(),
impl->gsutil_credential_source_.get());

// Create URL downloader with the same credential source used for GCS
impl->url_downloader_ = std::make_unique<UrlDownloader>(
*impl->retrying_http_client_, impl->gsutil_credential_source_.get());

return Downloaders(std::move(impl));
}

Expand All @@ -127,4 +133,6 @@ BuildApi& Downloaders::AndroidBuild() {

LuciBuildApi& Downloaders::Luci() { return *impl_->luci_build_api_; }

UrlDownloader& Downloaders::Url() { return *impl_->url_downloader_; }

} // namespace cuttlefish
2 changes: 2 additions & 0 deletions base/cvd/cuttlefish/host/commands/cvd/fetch/downloaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.h"
#include "cuttlefish/host/libs/web/build_api.h"
#include "cuttlefish/host/libs/web/luci_build_api.h"
#include "cuttlefish/host/libs/web/url_downloader.h"
#include "cuttlefish/result/result.h"

namespace cuttlefish {
Expand All @@ -36,6 +37,7 @@ class Downloaders {

BuildApi& AndroidBuild();
LuciBuildApi& Luci();
UrlDownloader& Url();

private:
struct Impl; // for pimpl
Expand Down
73 changes: 64 additions & 9 deletions base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand All @@ -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_)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ::cuttlefish::OpenZip class should handle http and https urls and probaly gs:// too. The full download should be avoided.

// 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_;
}
Expand All @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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++) {
Expand Down Expand Up @@ -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";
}
Expand Down Expand Up @@ -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),
Expand Down
45 changes: 24 additions & 21 deletions base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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;
Expand Down Expand Up @@ -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();
Expand All @@ -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_;
Expand Down
Loading