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