• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (C) 2019 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include "build_api.h"
17 
18 #include <dirent.h>
19 #include <unistd.h>
20 
21 #include <chrono>
22 #include <set>
23 #include <string>
24 #include <thread>
25 
26 #include <android-base/strings.h>
27 #include <android-base/logging.h>
28 
29 #include "common/libs/utils/environment.h"
30 #include "common/libs/utils/files.h"
31 
32 namespace cuttlefish {
33 namespace {
34 
35 const std::string BUILD_API =
36     "https://www.googleapis.com/android/internal/build/v3";
37 
StatusIsTerminal(const std::string & status)38 bool StatusIsTerminal(const std::string& status) {
39   const static std::set<std::string> terminal_statuses = {
40     "abandoned",
41     "complete",
42     "error",
43     "ABANDONED",
44     "COMPLETE",
45     "ERROR",
46   };
47   return terminal_statuses.count(status) > 0;
48 }
49 
50 } // namespace
51 
Artifact(const Json::Value & json_artifact)52 Artifact::Artifact(const Json::Value& json_artifact) {
53   name = json_artifact["name"].asString();
54   size = std::stol(json_artifact["size"].asString());
55   last_modified_time = std::stol(json_artifact["lastModifiedTime"].asString());
56   md5 = json_artifact["md5"].asString();
57   content_type = json_artifact["contentType"].asString();
58   revision = json_artifact["revision"].asString();
59   creation_time = std::stol(json_artifact["creationTime"].asString());
60   crc32 = json_artifact["crc32"].asUInt();
61 }
62 
operator <<(std::ostream & out,const DeviceBuild & build)63 std::ostream& operator<<(std::ostream& out, const DeviceBuild& build) {
64   return out << "(id=\"" << build.id << "\", target=\"" << build.target << "\")";
65 }
66 
operator <<(std::ostream & out,const DirectoryBuild & build)67 std::ostream& operator<<(std::ostream& out, const DirectoryBuild& build) {
68   auto paths = android::base::Join(build.paths, ":");
69   return out << "(paths=\"" << paths << "\", target=\"" << build.target << "\")";
70 }
71 
operator <<(std::ostream & out,const Build & build)72 std::ostream& operator<<(std::ostream& out, const Build& build) {
73   std::visit([&out](auto&& arg) { out << arg; }, build);
74   return out;
75 }
76 
DirectoryBuild(const std::vector<std::string> & paths,const std::string & target)77 DirectoryBuild::DirectoryBuild(const std::vector<std::string>& paths,
78                                const std::string& target)
79     : paths(paths), target(target), id("eng") {
80   product = StringFromEnv("TARGET_PRODUCT", "");
81 }
82 
BuildApi(std::unique_ptr<CredentialSource> credential_source)83 BuildApi::BuildApi(std::unique_ptr<CredentialSource> credential_source)
84     : credential_source(std::move(credential_source)) {}
85 
Headers()86 std::vector<std::string> BuildApi::Headers() {
87   std::vector<std::string> headers;
88   if (credential_source) {
89     headers.push_back("Authorization:Bearer " + credential_source->Credential());
90   }
91   return headers;
92 }
93 
LatestBuildId(const std::string & branch,const std::string & target)94 std::string BuildApi::LatestBuildId(const std::string& branch,
95                                     const std::string& target) {
96   std::string url = BUILD_API + "/builds?branch=" + branch
97       + "&buildAttemptStatus=complete"
98       + "&buildType=submitted&maxResults=1&successful=true&target=" + target;
99   auto response = curl.DownloadToJson(url, Headers());
100   CHECK(!response.isMember("error")) << "Error fetching the latest build of \""
101       << target << "\" on \"" << branch << "\". Response was " << response;
102 
103   if (!response.isMember("builds") || response["builds"].size() != 1) {
104     LOG(WARNING) << "expected to receive 1 build for \"" << target << "\" on \""
105         << branch << "\", but received " << response["builds"].size()
106         << ". Full response was " << response;
107     return "";
108   }
109   return response["builds"][0]["buildId"].asString();
110 }
111 
BuildStatus(const DeviceBuild & build)112 std::string BuildApi::BuildStatus(const DeviceBuild& build) {
113   std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target;
114   auto response_json = curl.DownloadToJson(url, Headers());
115   CHECK(!response_json.isMember("error")) << "Error fetching the status of "
116       << "build " << build << ". Response was " << response_json;
117 
118   return response_json["buildAttemptStatus"].asString();
119 }
120 
ProductName(const DeviceBuild & build)121 std::string BuildApi::ProductName(const DeviceBuild& build) {
122   std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target;
123   auto response_json = curl.DownloadToJson(url, Headers());
124   CHECK(!response_json.isMember("error")) << "Error fetching the status of "
125       << "build " << build << ". Response was " << response_json;
126   CHECK(response_json.isMember("target")) << "Build was missing target field.";
127   return response_json["target"]["product"].asString();
128 }
129 
Artifacts(const DeviceBuild & build)130 std::vector<Artifact> BuildApi::Artifacts(const DeviceBuild& build) {
131   std::string page_token = "";
132   std::vector<Artifact> artifacts;
133   do {
134     std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target +
135                       "/attempts/latest/artifacts?maxResults=1000";
136     if (page_token != "") {
137       url += "&pageToken=" + page_token;
138     }
139     auto artifacts_json = curl.DownloadToJson(url, Headers());
140     CHECK(!artifacts_json.isMember("error"))
141         << "Error fetching the artifacts of " << build << ". Response was "
142         << artifacts_json;
143     if (artifacts_json.isMember("nextPageToken")) {
144       page_token = artifacts_json["nextPageToken"].asString();
145     } else {
146       page_token = "";
147     }
148     for (const auto& artifact_json : artifacts_json["artifacts"]) {
149       artifacts.emplace_back(artifact_json);
150     }
151   } while (page_token != "");
152   return artifacts;
153 }
154 
155 struct CloseDir {
operator ()cuttlefish::CloseDir156   void operator()(DIR* dir) {
157     closedir(dir);
158   }
159 };
160 
161 using UniqueDir = std::unique_ptr<DIR, CloseDir>;
162 
Artifacts(const DirectoryBuild & build)163 std::vector<Artifact> BuildApi::Artifacts(const DirectoryBuild& build) {
164   std::vector<Artifact> artifacts;
165   for (const auto& path : build.paths) {
166     auto dir = UniqueDir(opendir(path.c_str()));
167     CHECK(dir != nullptr) << "Could not read files from \"" << path << "\"";
168     for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
169       artifacts.emplace_back(std::string(entity->d_name));
170     }
171   }
172   return artifacts;
173 }
174 
ArtifactToFile(const DeviceBuild & build,const std::string & artifact,const std::string & path)175 bool BuildApi::ArtifactToFile(const DeviceBuild& build,
176                               const std::string& artifact,
177                               const std::string& path) {
178   std::string download_url_endpoint =
179       BUILD_API + "/builds/" + build.id + "/" + build.target +
180       "/attempts/latest/artifacts/" + artifact + "/url";
181   auto download_url_json =
182       curl.DownloadToJson(download_url_endpoint, Headers());
183   if (!download_url_json.isMember("signedUrl")) {
184     LOG(ERROR) << "URL endpoint did not have json path: " << download_url_json;
185     return false;
186   }
187   std::string url = download_url_json["signedUrl"].asString();
188   return curl.DownloadToFile(url, path);
189 }
190 
ArtifactToFile(const DirectoryBuild & build,const std::string & artifact,const std::string & destination)191 bool BuildApi::ArtifactToFile(const DirectoryBuild& build,
192                               const std::string& artifact,
193                               const std::string& destination) {
194   for (const auto& path : build.paths) {
195     auto source = path + "/" + artifact;
196     if (!FileExists(source)) {
197       continue;
198     }
199     unlink(destination.c_str());
200     if (symlink(source.c_str(), destination.c_str())) {
201       int error_num = errno;
202       LOG(ERROR) << "Could not create symlink from " << source << " to "
203                   << destination << ": " << strerror(error_num);
204       return false;
205     }
206     return true;
207   }
208   return false;
209 }
210 
ArgumentToBuild(BuildApi * build_api,const std::string & arg,const std::string & default_build_target,const std::chrono::seconds & retry_period)211 Build ArgumentToBuild(BuildApi* build_api, const std::string& arg,
212                       const std::string& default_build_target,
213                       const std::chrono::seconds& retry_period) {
214   if (arg.find(':') != std::string::npos) {
215     std::vector<std::string> dirs = android::base::Split(arg, ":");
216     std::string id = dirs.back();
217     dirs.pop_back();
218     return DirectoryBuild(dirs, id);
219   }
220   size_t slash_pos = arg.find('/');
221   if (slash_pos != std::string::npos
222         && arg.find('/', slash_pos + 1) != std::string::npos) {
223     LOG(FATAL) << "Build argument cannot have more than one '/' slash. Was at "
224         << slash_pos << " and " << arg.find('/', slash_pos + 1);
225   }
226   std::string build_target = slash_pos == std::string::npos
227       ? default_build_target : arg.substr(slash_pos + 1);
228   std::string branch_or_id = slash_pos == std::string::npos
229       ? arg: arg.substr(0, slash_pos);
230   std::string branch_latest_build_id =
231       build_api->LatestBuildId(branch_or_id, build_target);
232   std::string build_id = branch_or_id;
233   if (branch_latest_build_id != "") {
234     LOG(INFO) << "The latest good build on branch \"" << branch_or_id
235         << "\"with build target \"" << build_target
236         << "\" is \"" << branch_latest_build_id << "\"";
237     build_id = branch_latest_build_id;
238   }
239   DeviceBuild proposed_build = DeviceBuild(build_id, build_target);
240   std::string status = build_api->BuildStatus(proposed_build);
241   if (status == "") {
242     LOG(FATAL) << proposed_build << " is not a valid branch or build id.";
243   }
244   LOG(INFO) << "Status for build " << proposed_build << " is " << status;
245   while (retry_period != std::chrono::seconds::zero() && !StatusIsTerminal(status)) {
246     LOG(INFO) << "Status is \"" << status << "\". Waiting for " << retry_period.count()
247         << " seconds.";
248     std::this_thread::sleep_for(retry_period);
249     status = build_api->BuildStatus(proposed_build);
250   }
251   LOG(INFO) << "Status for build " << proposed_build << " is " << status;
252   proposed_build.product = build_api->ProductName(proposed_build);
253   return proposed_build;
254 }
255 
256 } // namespace cuttlefish
257