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