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/files.h"
30 
31 namespace cuttlefish {
32 namespace {
33 
34 const std::string BUILD_API =
35     "https://www.googleapis.com/android/internal/build/v3";
36 
StatusIsTerminal(const std::string & status)37 bool StatusIsTerminal(const std::string& status) {
38   const static std::set<std::string> terminal_statuses = {
39     "abandoned",
40     "complete",
41     "error",
42     "ABANDONED",
43     "COMPLETE",
44     "ERROR",
45   };
46   return terminal_statuses.count(status) > 0;
47 }
48 
49 } // namespace
50 
Artifact(const Json::Value & json_artifact)51 Artifact::Artifact(const Json::Value& json_artifact) {
52   name = json_artifact["name"].asString();
53   size = std::stol(json_artifact["size"].asString());
54   last_modified_time = std::stol(json_artifact["lastModifiedTime"].asString());
55   md5 = json_artifact["md5"].asString();
56   content_type = json_artifact["contentType"].asString();
57   revision = json_artifact["revision"].asString();
58   creation_time = std::stol(json_artifact["creationTime"].asString());
59   crc32 = json_artifact["crc32"].asUInt();
60 }
61 
operator <<(std::ostream & out,const DeviceBuild & build)62 std::ostream& operator<<(std::ostream& out, const DeviceBuild& build) {
63   return out << "(id=\"" << build.id << "\", target=\"" << build.target << "\")";
64 }
65 
operator <<(std::ostream & out,const DirectoryBuild & build)66 std::ostream& operator<<(std::ostream& out, const DirectoryBuild& build) {
67   auto paths = android::base::Join(build.paths, ":");
68   return out << "(paths=\"" << paths << "\", target=\"" << build.target << "\")";
69 }
70 
operator <<(std::ostream & out,const Build & build)71 std::ostream& operator<<(std::ostream& out, const Build& build) {
72   std::visit([&out](auto&& arg) { out << arg; }, build);
73   return out;
74 }
75 
DirectoryBuild(const std::vector<std::string> & paths,const std::string & target)76 DirectoryBuild::DirectoryBuild(const std::vector<std::string>& paths,
77                                const std::string& target)
78     : paths(paths), target(target), id("eng") {
79   product = getenv("TARGET_PRODUCT");
80 }
81 
BuildApi(std::unique_ptr<CredentialSource> credential_source)82 BuildApi::BuildApi(std::unique_ptr<CredentialSource> credential_source)
83     : credential_source(std::move(credential_source)) {}
84 
Headers()85 std::vector<std::string> BuildApi::Headers() {
86   std::vector<std::string> headers;
87   if (credential_source) {
88     headers.push_back("Authorization:Bearer " + credential_source->Credential());
89   }
90   return headers;
91 }
92 
LatestBuildId(const std::string & branch,const std::string & target)93 std::string BuildApi::LatestBuildId(const std::string& branch,
94                                     const std::string& target) {
95   std::string url = BUILD_API + "/builds?branch=" + branch
96       + "&buildAttemptStatus=complete"
97       + "&buildType=submitted&maxResults=1&successful=true&target=" + target;
98   auto response = curl.DownloadToJson(url, Headers());
99   CHECK(!response.isMember("error")) << "Error fetching the latest build of \""
100       << target << "\" on \"" << branch << "\". Response was " << response;
101 
102   if (!response.isMember("builds") || response["builds"].size() != 1) {
103     LOG(WARNING) << "expected to receive 1 build for \"" << target << "\" on \""
104         << branch << "\", but received " << response["builds"].size()
105         << ". Full response was " << response;
106     return "";
107   }
108   return response["builds"][0]["buildId"].asString();
109 }
110 
BuildStatus(const DeviceBuild & build)111 std::string BuildApi::BuildStatus(const DeviceBuild& build) {
112   std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target;
113   auto response_json = curl.DownloadToJson(url, Headers());
114   CHECK(!response_json.isMember("error")) << "Error fetching the status of "
115       << "build " << build << ". Response was " << response_json;
116 
117   return response_json["buildAttemptStatus"].asString();
118 }
119 
ProductName(const DeviceBuild & build)120 std::string BuildApi::ProductName(const DeviceBuild& build) {
121   std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target;
122   auto response_json = curl.DownloadToJson(url, Headers());
123   CHECK(!response_json.isMember("error")) << "Error fetching the status of "
124       << "build " << build << ". Response was " << response_json;
125   CHECK(response_json.isMember("target")) << "Build was missing target field.";
126   return response_json["target"]["product"].asString();
127 }
128 
Artifacts(const DeviceBuild & build)129 std::vector<Artifact> BuildApi::Artifacts(const DeviceBuild& build) {
130   std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target
131       + "/attempts/latest/artifacts?maxResults=1000";
132   auto artifacts_json = curl.DownloadToJson(url, Headers());
133   CHECK(!artifacts_json.isMember("error")) << "Error fetching the artifacts of "
134       << build << ". Response was " << artifacts_json;
135 
136   std::vector<Artifact> artifacts;
137   for (const auto& artifact_json : artifacts_json["artifacts"]) {
138     artifacts.emplace_back(artifact_json);
139   }
140   return artifacts;
141 }
142 
143 struct CloseDir {
operator ()cuttlefish::CloseDir144   void operator()(DIR* dir) {
145     closedir(dir);
146   }
147 };
148 
149 using UniqueDir = std::unique_ptr<DIR, CloseDir>;
150 
Artifacts(const DirectoryBuild & build)151 std::vector<Artifact> BuildApi::Artifacts(const DirectoryBuild& build) {
152   std::vector<Artifact> artifacts;
153   for (const auto& path : build.paths) {
154     auto dir = UniqueDir(opendir(path.c_str()));
155     CHECK(dir != nullptr) << "Could not read files from \"" << path << "\"";
156     for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
157       artifacts.emplace_back(std::string(entity->d_name));
158     }
159   }
160   return artifacts;
161 }
162 
ArtifactToFile(const DeviceBuild & build,const std::string & artifact,const std::string & path)163 bool BuildApi::ArtifactToFile(const DeviceBuild& build,
164                               const std::string& artifact,
165                               const std::string& path) {
166   std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target
167       + "/attempts/latest/artifacts/" + artifact + "?alt=media";
168   return curl.DownloadToFile(url, path, Headers());
169 }
170 
ArtifactToFile(const DirectoryBuild & build,const std::string & artifact,const std::string & destination)171 bool BuildApi::ArtifactToFile(const DirectoryBuild& build,
172                               const std::string& artifact,
173                               const std::string& destination) {
174   for (const auto& path : build.paths) {
175     auto source = path + "/" + artifact;
176     if (!FileExists(source)) {
177       continue;
178     }
179     unlink(destination.c_str());
180     if (symlink(source.c_str(), destination.c_str())) {
181       int error_num = errno;
182       LOG(ERROR) << "Could not create symlink from " << source << " to "
183                   << destination << ": " << strerror(error_num);
184       return false;
185     }
186     return true;
187   }
188   return false;
189 }
190 
ArgumentToBuild(BuildApi * build_api,const std::string & arg,const std::string & default_build_target,const std::chrono::seconds & retry_period)191 Build ArgumentToBuild(BuildApi* build_api, const std::string& arg,
192                       const std::string& default_build_target,
193                       const std::chrono::seconds& retry_period) {
194   if (arg.find(':') != std::string::npos) {
195     std::vector<std::string> dirs = android::base::Split(arg, ":");
196     std::string id = dirs.back();
197     dirs.pop_back();
198     return DirectoryBuild(dirs, id);
199   }
200   size_t slash_pos = arg.find('/');
201   if (slash_pos != std::string::npos
202         && arg.find('/', slash_pos + 1) != std::string::npos) {
203     LOG(FATAL) << "Build argument cannot have more than one '/' slash. Was at "
204         << slash_pos << " and " << arg.find('/', slash_pos + 1);
205   }
206   std::string build_target = slash_pos == std::string::npos
207       ? default_build_target : arg.substr(slash_pos + 1);
208   std::string branch_or_id = slash_pos == std::string::npos
209       ? arg: arg.substr(0, slash_pos);
210   std::string branch_latest_build_id =
211       build_api->LatestBuildId(branch_or_id, build_target);
212   std::string build_id = branch_or_id;
213   if (branch_latest_build_id != "") {
214     LOG(INFO) << "The latest good build on branch \"" << branch_or_id
215         << "\"with build target \"" << build_target
216         << "\" is \"" << branch_latest_build_id << "\"";
217     build_id = branch_latest_build_id;
218   }
219   DeviceBuild proposed_build = DeviceBuild(build_id, build_target);
220   std::string status = build_api->BuildStatus(proposed_build);
221   if (status == "") {
222     LOG(FATAL) << proposed_build << " is not a valid branch or build id.";
223   }
224   LOG(INFO) << "Status for build " << proposed_build << " is " << status;
225   while (retry_period != std::chrono::seconds::zero() && !StatusIsTerminal(status)) {
226     LOG(INFO) << "Status is \"" << status << "\". Waiting for " << retry_period.count()
227         << " seconds.";
228     std::this_thread::sleep_for(retry_period);
229     status = build_api->BuildStatus(proposed_build);
230   }
231   LOG(INFO) << "Status for build " << proposed_build << " is " << status;
232   proposed_build.product = build_api->ProductName(proposed_build);
233   return proposed_build;
234 }
235 
236 } // namespace cuttlefish
237