diff --git a/base/cvd/cuttlefish/common/libs/utils/BUILD.bazel b/base/cvd/cuttlefish/common/libs/utils/BUILD.bazel index 4eb9d268201..9fe920416cd 100644 --- a/base/cvd/cuttlefish/common/libs/utils/BUILD.bazel +++ b/base/cvd/cuttlefish/common/libs/utils/BUILD.bazel @@ -227,6 +227,15 @@ cf_cc_library( ], ) +cf_cc_library( + name = "is_google_corp", + srcs = ["is_google_corp.cpp"], + hdrs = ["is_google_corp.h"], + deps = [ + "//cuttlefish/common/libs/utils:files", + ], +) + cf_cc_library( name = "json", srcs = ["json.cpp"], diff --git a/base/cvd/cuttlefish/common/libs/utils/is_google_corp.cpp b/base/cvd/cuttlefish/common/libs/utils/is_google_corp.cpp new file mode 100644 index 00000000000..6e4769c1486 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/is_google_corp.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cuttlefish/common/libs/utils/is_google_corp.h" + +#include +#include + +#include "cuttlefish/common/libs/utils/files.h" + +namespace cuttlefish { +namespace { + +constexpr std::string_view kGoogleCorpDir = "/google"; + +} // namespace + +bool IsGoogleCorp() { return DirectoryExists(std::string(kGoogleCorpDir)); } + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/is_google_corp.h b/base/cvd/cuttlefish/common/libs/utils/is_google_corp.h new file mode 100644 index 00000000000..87fd6f82652 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/is_google_corp.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace cuttlefish { + +bool IsGoogleCorp(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel index 83389e9dc67..09402875ab0 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel @@ -142,6 +142,8 @@ cf_cc_library( "//cuttlefish/host/commands/cvd/cli:command_request", "//cuttlefish/host/commands/cvd/cli:types", "//cuttlefish/host/commands/cvd/cli/commands:command_handler", + "//cuttlefish/host/commands/cvd/fetch:auto_login", + "//cuttlefish/host/commands/cvd/fetch:build_api_flags", "//cuttlefish/host/commands/cvd/fetch:fetch_cvd", "//cuttlefish/host/commands/cvd/fetch:fetch_cvd_parser", "//cuttlefish/host/commands/cvd/utils", @@ -206,8 +208,8 @@ cf_cc_library( hdrs = ["login.h"], deps = [ "//cuttlefish/common/libs/utils:environment", - "//cuttlefish/common/libs/utils:files", "//cuttlefish/common/libs/utils:flag_parser", + "//cuttlefish/common/libs/utils:is_google_corp", "//cuttlefish/host/commands/cvd/cli:command_request", "//cuttlefish/host/commands/cvd/cli:types", "//cuttlefish/host/commands/cvd/cli/commands:command_handler", diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/fetch.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/fetch.cpp index 9d726e58e8d..5dd49c656aa 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/fetch.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/fetch.cpp @@ -31,6 +31,8 @@ #include "cuttlefish/host/commands/cvd/cli/command_request.h" #include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h" #include "cuttlefish/host/commands/cvd/cli/types.h" +#include "cuttlefish/host/commands/cvd/fetch/auto_login.h" +#include "cuttlefish/host/commands/cvd/fetch/build_api_flags.h" #include "cuttlefish/host/commands/cvd/fetch/fetch_cvd.h" #include "cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.h" #include "cuttlefish/host/commands/cvd/utils/common.h" @@ -50,32 +52,63 @@ class CvdFetchCommandHandler : public CvdCommandHandler { Result DetailedHelp(const CommandRequest& request) const override; }; +Result RunAutoLogin(const BuildApiFlags& build_api_flags) { + if (!CF_EXPECT(ShouldAutoLogin(build_api_flags))) { + return {}; + } + LOG(INFO) << "\nNo credentials detected on corp, running credential " + "workflow. Please follow prompts.\n"; + if (!CanRunAutoLogin()) { + LOG(INFO) << "\nUnable to detect necessary files for credentialing. Do " + "you need to run `gcert`?\n"; + return {}; + } + + CF_EXPECT(RunLogin()); + LOG(INFO) + << "\nLogin successful. This will persist across future executions."; + return {}; +} + +Result RunCacheCleanup(const BuildApiFlags& build_api_flags) { + if (!build_api_flags.enable_caching) { + return {}; + } + + VLOG(0) << "Running automatic cache cleanup"; + const std::string cache_directory = PerUserCacheDir(); + const PruneResult prune_result = + CF_EXPECTF(PruneCache(cache_directory, build_api_flags.max_cache_size_gb), + "Error pruning cache at {} to {}GB", cache_directory, + build_api_flags.max_cache_size_gb); + if (prune_result.before > prune_result.after) { + LOG(INFO) << fmt::format( + "Cache at \"{}\" pruned from ~{}GB to ~{}GB of {}GB max size", + cache_directory, prune_result.before, prune_result.after, + build_api_flags.max_cache_size_gb); + } + return {}; +} + Result CvdFetchCommandHandler::Handle(const CommandRequest& request) { CF_EXPECT(CanHandle(request)); std::vector args = request.SubcommandArguments(); const FetchFlags flags = CF_EXPECT(FetchFlags::Parse(args)); CF_EXPECT(EnsureDirectoryExists(flags.target_directory)); - GatherFetchStartMetrics(flags); + Result ensure_credentials_result = RunAutoLogin(flags.build_api_flags); + if (!ensure_credentials_result.ok()) { + LOG(INFO) << "Auto-login failed with the following error:\n" + << ensure_credentials_result.error() << "\n"; + LOG(INFO) << "Still running the fetch operation."; + } + GatherFetchStartMetrics(flags); std::string log_file = GetFetchLogsFileName(flags.target_directory); ScopedLogger logger(SeverityTarget::FromFile(log_file), ""); Result result = FetchCvdMain(flags); - if (flags.build_api_flags.enable_caching) { - VLOG(0) << "Running automatic cache cleanup"; - const std::string cache_directory = PerUserCacheDir(); - const PruneResult prune_result = CF_EXPECTF( - PruneCache(cache_directory, flags.build_api_flags.max_cache_size_gb), - "Error pruning cache at {} to {}GB", cache_directory, - flags.build_api_flags.max_cache_size_gb); - if (prune_result.before > prune_result.after) { - LOG(INFO) << fmt::format( - "Cache at \"{}\" pruned from ~{}GB to ~{}GB of {}GB max size", - cache_directory, prune_result.before, prune_result.after, - flags.build_api_flags.max_cache_size_gb); - } - } + CF_EXPECT(RunCacheCleanup(flags.build_api_flags)); if (result.ok()) { GatherFetchCompleteMetrics(flags.target_directory, *result); diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/login.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/login.cpp index 7bc73f1dbbb..94537f1e67f 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/login.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/login.cpp @@ -21,8 +21,8 @@ #include #include "cuttlefish/common/libs/utils/environment.h" -#include "cuttlefish/common/libs/utils/files.h" #include "cuttlefish/common/libs/utils/flag_parser.h" +#include "cuttlefish/common/libs/utils/is_google_corp.h" #include "cuttlefish/host/commands/cvd/cli/command_request.h" #include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h" #include "cuttlefish/host/commands/cvd/cli/types.h" @@ -95,7 +95,7 @@ class CvdLoginCommand : public CvdCommandHandler { Result DetailedHelp(const CommandRequest& request) const override { std::string google_appendix; - if (DirectoryExists("/google")) { + if (IsGoogleCorp()) { google_appendix = "\nIf running on corp, use the wrapper script in " "google3/cloud/android/login"; diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/BUILD.bazel b/base/cvd/cuttlefish/host/commands/cvd/fetch/BUILD.bazel index 7cc4528febb..778f65a9653 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/fetch/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/BUILD.bazel @@ -7,6 +7,26 @@ package( default_visibility = ["//:android_cuttlefish"], ) +cf_cc_library( + name = "auto_login", + srcs = [ + "auto_login.cc", + ], + hdrs = [ + "auto_login.h", + ], + deps = [ + "//cuttlefish/common/libs/utils:files", + "//cuttlefish/common/libs/utils:is_google_corp", + "//cuttlefish/common/libs/utils:subprocess", + "//cuttlefish/common/libs/utils:subprocess_managed_stdio", + "//cuttlefish/host/commands/cvd/fetch:build_api_credentials", + "//cuttlefish/host/commands/cvd/fetch:build_api_flags", + "//cuttlefish/host/libs/web:oauth2_consent", + "//cuttlefish/result", + ], +) + cf_cc_library( name = "build_api_credentials", srcs = [ @@ -16,6 +36,7 @@ cf_cc_library( "build_api_credentials.h", ], deps = [ + "//cuttlefish/common/libs/utils:environment", "//cuttlefish/common/libs/utils:files", "//cuttlefish/common/libs/utils:json", "//cuttlefish/host/commands/cvd/fetch:build_api_flags", diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/auto_login.cc b/base/cvd/cuttlefish/host/commands/cvd/fetch/auto_login.cc new file mode 100644 index 00000000000..f863a370b21 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/auto_login.cc @@ -0,0 +1,70 @@ +// +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cuttlefish/host/commands/cvd/fetch/auto_login.h" + +#include +#include + +#include "cuttlefish/common/libs/utils/files.h" +#include "cuttlefish/common/libs/utils/is_google_corp.h" +#include "cuttlefish/common/libs/utils/subprocess.h" +#include "cuttlefish/common/libs/utils/subprocess_managed_stdio.h" +#include "cuttlefish/host/commands/cvd/fetch/build_api_credentials.h" +#include "cuttlefish/host/commands/cvd/fetch/build_api_flags.h" +#include "cuttlefish/host/libs/web/oauth2_consent.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { +namespace { + +constexpr std::string_view kWrapperFilepath = + "/google/src/head/depot/google3/cloud/android/login/login.sh"; + +bool IsCredentialFlagPresent(const BuildApiFlags& flags) { + return !flags.credential_source.empty() || + flags.credential_flags.use_gce_metadata || + !flags.credential_flags.credential_filepath.empty() || + !flags.credential_flags.service_account_filepath.empty(); +} + +Result IsCredentialFilePresent() { + return FileExists(GetAcloudOauthFilepath()) || + !CF_EXPECT(GetCvdCredentialFilepaths()).empty(); +} + +} // namespace + +Result ShouldAutoLogin(const BuildApiFlags& flags) { + return !IsCredentialFlagPresent(flags) && + !CF_EXPECT(IsCredentialFilePresent(), + "Failed searching for credential files.") && + IsGoogleCorp(); +} + +bool CanRunAutoLogin() { return FileExists(std::string(kWrapperFilepath)); } + +Result RunLogin() { + Command login_command = + Command(std::string(kWrapperFilepath)).AddParameter("--ssh"); + std::string stderr; + const int exit_code = + RunWithManagedStdio(std::move(login_command), nullptr, nullptr, &stderr); + CF_EXPECTF(exit_code == 0, "Failure to automatically credential:\n{}", + stderr); + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/auto_login.h b/base/cvd/cuttlefish/host/commands/cvd/fetch/auto_login.h new file mode 100644 index 00000000000..1810ee2b75c --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/auto_login.h @@ -0,0 +1,29 @@ +// +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "cuttlefish/host/commands/cvd/fetch/build_api_flags.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +Result ShouldAutoLogin(const BuildApiFlags&); + +bool CanRunAutoLogin(); + +Result RunLogin(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/build_api_credentials.cc b/base/cvd/cuttlefish/host/commands/cvd/fetch/build_api_credentials.cc index ce77e42f20a..48a174b2f54 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/fetch/build_api_credentials.cc +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/build_api_credentials.cc @@ -21,6 +21,7 @@ #include "absl/log/log.h" +#include "cuttlefish/common/libs/utils/environment.h" #include "cuttlefish/common/libs/utils/files.h" #include "cuttlefish/common/libs/utils/json.h" #include "cuttlefish/host/commands/cvd/fetch/build_api_flags.h" @@ -147,4 +148,8 @@ Result> GetCredentialSourceFromFlags( flags.credential_flags.service_account_filepath)); } +std::string GetAcloudOauthFilepath() { + return StringFromEnv("HOME", ".") + "/.acloud_oauth2.dat"; +} + } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/build_api_credentials.h b/base/cvd/cuttlefish/host/commands/cvd/fetch/build_api_credentials.h index c744cb8ec96..f8772873115 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/fetch/build_api_credentials.h +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/build_api_credentials.h @@ -32,4 +32,6 @@ Result> GetCredentialSourceFromFlags( HttpClient& http_client, const BuildApiFlags& flags, const std::string& oauth_filepath); +std::string GetAcloudOauthFilepath(); + } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/downloaders.cc b/base/cvd/cuttlefish/host/commands/cvd/fetch/downloaders.cc index 86e4bf2357f..33364ce2854 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/fetch/downloaders.cc +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/downloaders.cc @@ -74,14 +74,11 @@ Result Downloaders::Create(const BuildApiFlags& flags, Result> cvd_creds = CredentialForScopes(*impl->curl_, scopes); - std::string oauth_filepath = - StringFromEnv("HOME", ".") + "/.acloud_oauth2.dat"; - impl->android_creds_ = cvd_creds.ok() && cvd_creds->get() ? std::move(*cvd_creds) - : CF_EXPECT(GetCredentialSourceFromFlags(*impl->retrying_http_client_, - flags, oauth_filepath)); + : CF_EXPECT(GetCredentialSourceFromFlags( + *impl->retrying_http_client_, flags, GetAcloudOauthFilepath())); impl->android_build_url_ = std::make_unique( flags.api_base_url, flags.api_key, flags.project_id); diff --git a/base/cvd/cuttlefish/host/libs/web/oauth2_consent.cpp b/base/cvd/cuttlefish/host/libs/web/oauth2_consent.cpp index 6f86f913e5a..7acdad17fa6 100644 --- a/base/cvd/cuttlefish/host/libs/web/oauth2_consent.cpp +++ b/base/cvd/cuttlefish/host/libs/web/oauth2_consent.cpp @@ -285,12 +285,9 @@ Result> Oauth2Login( Result> CredentialForScopes( HttpClient& http_client, const std::vector& scopes) { std::vector credential_paths = - CF_EXPECT(FindCvdDataFiles(kCredentials)); + CF_EXPECT(GetCvdCredentialFilepaths()); for (const std::string& credential_path : credential_paths) { - if (!absl::EndsWith(credential_path, ".json")) { - continue; - } Result> credential = CredentialForScopes(http_client, scopes, credential_path); if (credential.ok() && *credential != nullptr) { @@ -300,4 +297,16 @@ Result> CredentialForScopes( return CF_ERR("No credentials found."); } +Result> GetCvdCredentialFilepaths() { + std::vector credential_paths = + CF_EXPECT(FindCvdDataFiles(kCredentials)); + std::vector result; + for (const std::string& credential_path : credential_paths) { + if (absl::EndsWith(credential_path, ".json")) { + result.push_back(credential_path); + } + } + return result; +} + } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/oauth2_consent.h b/base/cvd/cuttlefish/host/libs/web/oauth2_consent.h index 8d4221d60b0..7c40f88f575 100644 --- a/base/cvd/cuttlefish/host/libs/web/oauth2_consent.h +++ b/base/cvd/cuttlefish/host/libs/web/oauth2_consent.h @@ -45,4 +45,6 @@ Result> Oauth2Login( Result> CredentialForScopes( HttpClient&, const std::vector&); +Result> GetCvdCredentialFilepaths(); + } // namespace cuttlefish