• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (C) 2022 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 "host/commands/test_gce_driver/gce_api.h"
17 
18 #include <uuid.h>
19 
20 #include <sstream>
21 #include <string>
22 
23 #include <android-base/logging.h>
24 #include <android-base/strings.h>
25 
26 #include "host/libs/web/credential_source.h"
27 
28 namespace cuttlefish {
29 
OptStringMember(const Json::Value & jn,const std::string & name)30 std::optional<std::string> OptStringMember(const Json::Value& jn,
31                                            const std::string& name) {
32   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::stringValue) {
33     return {};
34   }
35   return jn[name].asString();
36 }
37 
OptObjMember(const Json::Value & jn,const std::string & name)38 const Json::Value* OptObjMember(const Json::Value& jn,
39                                 const std::string& name) {
40   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::objectValue) {
41     return nullptr;
42   }
43   return &(jn[name]);
44 }
45 
OptArrayMember(const Json::Value & jn,const std::string & name)46 const Json::Value* OptArrayMember(const Json::Value& jn,
47                                   const std::string& name) {
48   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::arrayValue) {
49     return nullptr;
50   }
51   return &(jn[name]);
52 }
53 
EnsureObjMember(Json::Value & jn,const std::string & name)54 Json::Value& EnsureObjMember(Json::Value& jn, const std::string& name) {
55   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::objectValue) {
56     jn[name] = Json::Value(Json::ValueType::objectValue);
57   }
58   return jn[name];
59 }
60 
EnsureArrayMember(Json::Value & jn,const std::string & name)61 Json::Value& EnsureArrayMember(Json::Value& jn, const std::string& name) {
62   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::arrayValue) {
63     jn[name] = Json::Value(Json::ValueType::arrayValue);
64   }
65   return jn[name];
66 }
67 
GceInstanceDisk(const Json::Value & json)68 GceInstanceDisk::GceInstanceDisk(const Json::Value& json) : data_(json){};
69 
EphemeralBootDisk()70 GceInstanceDisk GceInstanceDisk::EphemeralBootDisk() {
71   Json::Value initial_json(Json::ValueType::objectValue);
72   initial_json["type"] = "PERSISTENT";
73   initial_json["boot"] = true;
74   initial_json["mode"] = "READ_WRITE";
75   initial_json["autoDelete"] = true;
76   return GceInstanceDisk(initial_json);
77 }
78 
79 constexpr char kGceDiskInitParams[] = "initializeParams";
80 constexpr char kGceDiskName[] = "diskName";
Name() const81 std::optional<std::string> GceInstanceDisk::Name() const {
82   const auto& init_params = OptObjMember(data_, kGceDiskInitParams);
83   if (!init_params) {
84     return {};
85   }
86   return OptStringMember(*init_params, kGceDiskName);
87 }
Name(const std::string & source)88 GceInstanceDisk& GceInstanceDisk::Name(const std::string& source) & {
89   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskName] = source;
90   return *this;
91 }
Name(const std::string & source)92 GceInstanceDisk GceInstanceDisk::Name(const std::string& source) && {
93   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskName] = source;
94   return *this;
95 }
96 
97 constexpr char kGceDiskSourceImage[] = "sourceImage";
SourceImage() const98 std::optional<std::string> GceInstanceDisk::SourceImage() const {
99   const auto& init_params = OptObjMember(data_, kGceDiskInitParams);
100   if (!init_params) {
101     return {};
102   }
103   return OptStringMember(*init_params, kGceDiskSourceImage);
104 }
SourceImage(const std::string & source)105 GceInstanceDisk& GceInstanceDisk::SourceImage(const std::string& source) & {
106   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSourceImage] = source;
107   return *this;
108 }
SourceImage(const std::string & source)109 GceInstanceDisk GceInstanceDisk::SourceImage(const std::string& source) && {
110   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSourceImage] = source;
111   return *this;
112 }
113 
114 constexpr char kGceDiskSizeGb[] = "diskSizeGb";
SizeGb(uint64_t size)115 GceInstanceDisk& GceInstanceDisk::SizeGb(uint64_t size) & {
116   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSizeGb] = size;
117   return *this;
118 }
SizeGb(uint64_t size)119 GceInstanceDisk GceInstanceDisk::SizeGb(uint64_t size) && {
120   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSizeGb] = size;
121   return *this;
122 }
123 
AsJson() const124 const Json::Value& GceInstanceDisk::AsJson() const { return data_; }
125 
GceNetworkInterface(const Json::Value & data)126 GceNetworkInterface::GceNetworkInterface(const Json::Value& data)
127     : data_(data) {}
128 
129 constexpr char kNetwork[] = "network";
130 constexpr char kGceNetworkAccessConfigs[] = "accessConfigs";
Default()131 GceNetworkInterface GceNetworkInterface::Default() {
132   Json::Value json{Json::ValueType::objectValue};
133   json[kNetwork] = "global/networks/default";
134   Json::Value accessConfig{Json::ValueType::objectValue};
135   accessConfig["type"] = "ONE_TO_ONE_NAT";
136   accessConfig["name"] = "External NAT";
137   EnsureArrayMember(json, kGceNetworkAccessConfigs).append(accessConfig);
138   return GceNetworkInterface(json);
139 }
140 
Network() const141 std::optional<std::string> GceNetworkInterface::Network() const {
142   return OptStringMember(data_, kNetwork);
143 }
Network(const std::string & network)144 GceNetworkInterface& GceNetworkInterface::Network(
145     const std::string& network) & {
146   data_[kNetwork] = network;
147   return *this;
148 }
Network(const std::string & network)149 GceNetworkInterface GceNetworkInterface::Network(
150     const std::string& network) && {
151   data_[kNetwork] = network;
152   return *this;
153 }
154 
155 constexpr char kSubnetwork[] = "subnetwork";
Subnetwork() const156 std::optional<std::string> GceNetworkInterface::Subnetwork() const {
157   return OptStringMember(data_, kSubnetwork);
158 }
Subnetwork(const std::string & subnetwork)159 GceNetworkInterface& GceNetworkInterface::Subnetwork(
160     const std::string& subnetwork) & {
161   data_[kSubnetwork] = subnetwork;
162   return *this;
163 }
Subnetwork(const std::string & subnetwork)164 GceNetworkInterface GceNetworkInterface::Subnetwork(
165     const std::string& subnetwork) && {
166   data_[kSubnetwork] = subnetwork;
167   return *this;
168 }
169 
170 constexpr char kGceNetworkExternalIp[] = "natIP";
ExternalIp() const171 std::optional<std::string> GceNetworkInterface::ExternalIp() const {
172   auto accessConfigs = OptArrayMember(data_, kGceNetworkAccessConfigs);
173   if (!accessConfigs || accessConfigs->size() < 1) {
174     return {};
175   }
176   if ((*accessConfigs)[0].type() != Json::ValueType::objectValue) {
177     return {};
178   }
179   return OptStringMember((*accessConfigs)[0], kGceNetworkExternalIp);
180 }
181 
182 constexpr char kGceNetworkInternalIp[] = "networkIP";
InternalIp() const183 std::optional<std::string> GceNetworkInterface::InternalIp() const {
184   return OptStringMember(data_, kGceNetworkInternalIp);
185 }
186 
AsJson() const187 const Json::Value& GceNetworkInterface::AsJson() const { return data_; }
188 
GceInstanceInfo(const Json::Value & json)189 GceInstanceInfo::GceInstanceInfo(const Json::Value& json) : data_(json) {}
190 
191 constexpr char kGceZone[] = "zone";
Zone() const192 std::optional<std::string> GceInstanceInfo::Zone() const {
193   return OptStringMember(data_, kGceZone);
194 }
Zone(const std::string & zone)195 GceInstanceInfo& GceInstanceInfo::Zone(const std::string& zone) & {
196   data_[kGceZone] = zone;
197   return *this;
198 }
Zone(const std::string & zone)199 GceInstanceInfo GceInstanceInfo::Zone(const std::string& zone) && {
200   data_[kGceZone] = zone;
201   return *this;
202 }
203 
204 constexpr char kGceName[] = "name";
Name() const205 std::optional<std::string> GceInstanceInfo::Name() const {
206   return OptStringMember(data_, kGceName);
207 }
Name(const std::string & name)208 GceInstanceInfo& GceInstanceInfo::Name(const std::string& name) & {
209   data_[kGceName] = name;
210   return *this;
211 }
Name(const std::string & name)212 GceInstanceInfo GceInstanceInfo::Name(const std::string& name) && {
213   data_[kGceName] = name;
214   return *this;
215 }
216 
217 constexpr char kGceMachineType[] = "machineType";
MachineType() const218 std::optional<std::string> GceInstanceInfo::MachineType() const {
219   return OptStringMember(data_, kGceMachineType);
220 }
MachineType(const std::string & type)221 GceInstanceInfo& GceInstanceInfo::MachineType(const std::string& type) & {
222   data_[kGceMachineType] = type;
223   return *this;
224 }
MachineType(const std::string & type)225 GceInstanceInfo GceInstanceInfo::MachineType(const std::string& type) && {
226   data_[kGceMachineType] = type;
227   return *this;
228 }
229 
230 constexpr char kGceDisks[] = "disks";
AddDisk(const GceInstanceDisk & disk)231 GceInstanceInfo& GceInstanceInfo::AddDisk(const GceInstanceDisk& disk) & {
232   EnsureArrayMember(data_, kGceDisks).append(disk.AsJson());
233   return *this;
234 }
AddDisk(const GceInstanceDisk & disk)235 GceInstanceInfo GceInstanceInfo::AddDisk(const GceInstanceDisk& disk) && {
236   EnsureArrayMember(data_, kGceDisks).append(disk.AsJson());
237   return *this;
238 }
239 
240 constexpr char kGceNetworkInterfaces[] = "networkInterfaces";
AddNetworkInterface(const GceNetworkInterface & net)241 GceInstanceInfo& GceInstanceInfo::AddNetworkInterface(
242     const GceNetworkInterface& net) & {
243   EnsureArrayMember(data_, kGceNetworkInterfaces).append(net.AsJson());
244   return *this;
245 }
AddNetworkInterface(const GceNetworkInterface & net)246 GceInstanceInfo GceInstanceInfo::AddNetworkInterface(
247     const GceNetworkInterface& net) && {
248   EnsureArrayMember(data_, kGceNetworkInterfaces).append(net.AsJson());
249   return *this;
250 }
NetworkInterfaces() const251 std::vector<GceNetworkInterface> GceInstanceInfo::NetworkInterfaces() const {
252   auto jsonNetworkInterfaces = OptArrayMember(data_, kGceNetworkInterfaces);
253   if (!jsonNetworkInterfaces) {
254     return {};
255   }
256   std::vector<GceNetworkInterface> interfaces;
257   for (const Json::Value& jsonNetworkInterface : *jsonNetworkInterfaces) {
258     interfaces.push_back(GceNetworkInterface(jsonNetworkInterface));
259   }
260   return interfaces;
261 }
262 
263 constexpr char kGceMetadata[] = "metadata";
264 constexpr char kGceMetadataItems[] = "items";
265 constexpr char kGceMetadataKey[] = "key";
266 constexpr char kGceMetadataValue[] = "value";
AddMetadata(const std::string & key,const std::string & value)267 GceInstanceInfo& GceInstanceInfo::AddMetadata(const std::string& key,
268                                               const std::string& value) & {
269   Json::Value item{Json::ValueType::objectValue};
270   item[kGceMetadataKey] = key;
271   item[kGceMetadataValue] = value;
272   auto& metadata = EnsureObjMember(data_, kGceMetadata);
273   EnsureArrayMember(metadata, kGceMetadataItems).append(item);
274   return *this;
275 }
AddMetadata(const std::string & key,const std::string & value)276 GceInstanceInfo GceInstanceInfo::AddMetadata(const std::string& key,
277                                              const std::string& value) && {
278   Json::Value item{Json::ValueType::objectValue};
279   item[kGceMetadataKey] = key;
280   item[kGceMetadataValue] = value;
281   auto& metadata = EnsureObjMember(data_, kGceMetadata);
282   EnsureArrayMember(metadata, kGceMetadataItems).append(item);
283   return *this;
284 }
285 
286 constexpr char kGceServiceAccounts[] = "serviceAccounts";
287 constexpr char kGceScopes[] = "scopes";
AddScope(const std::string & scope)288 GceInstanceInfo& GceInstanceInfo::AddScope(const std::string& scope) & {
289   auto& serviceAccounts = EnsureArrayMember(data_, kGceServiceAccounts);
290   if (serviceAccounts.size() == 0) {
291     serviceAccounts.append(Json::Value(Json::ValueType::objectValue));
292   }
293   serviceAccounts[0]["email"] = "default";
294   auto& scopes = EnsureArrayMember(serviceAccounts[0], kGceScopes);
295   scopes.append(scope);
296   return *this;
297 }
AddScope(const std::string & scope)298 GceInstanceInfo GceInstanceInfo::AddScope(const std::string& scope) && {
299   auto& serviceAccounts = EnsureArrayMember(data_, kGceServiceAccounts);
300   if (serviceAccounts.size() == 0) {
301     serviceAccounts.append(Json::Value(Json::ValueType::objectValue));
302   }
303   serviceAccounts[0]["email"] = "default";
304   auto& scopes = EnsureArrayMember(serviceAccounts[0], kGceScopes);
305   scopes.append(scope);
306   return *this;
307 }
308 
AsJson() const309 const Json::Value& GceInstanceInfo::AsJson() const { return data_; }
310 
GceApi(HttpClient & http_client,CredentialSource & credentials,const std::string & project)311 GceApi::GceApi(HttpClient& http_client, CredentialSource& credentials,
312                const std::string& project)
313     : http_client_(http_client), credentials_(credentials), project_(project) {}
314 
Headers()315 Result<std::vector<std::string>> GceApi::Headers() {
316   return {{
317       "Authorization:Bearer " + CF_EXPECT(credentials_.Credential()),
318       "Content-Type: application/json",
319   }};
320 }
321 
322 class GceApi::Operation::Impl {
323  public:
Impl(GceApi & gce_api,std::function<Result<Json::Value> ()> initial_request)324   Impl(GceApi& gce_api, std::function<Result<Json::Value>()> initial_request)
325       : gce_api_(gce_api), initial_request_(std::move(initial_request)) {
326     operation_future_ = std::async([this]() { return Run(); });
327   }
328 
Run()329   Result<bool> Run() {
330     auto initial_response =
331         CF_EXPECT(initial_request_(), "Initial request failed: ");
332 
333     auto url = OptStringMember(initial_response, "selfLink");
334     if (!url) {
335       return CF_ERR("Operation " << initial_response
336                                  << " was missing `selfLink` field.");
337     }
338     url = *url + "/wait";
339     running_ = true;
340 
341     while (running_) {
342       auto response = CF_EXPECT(gce_api_.http_client_.PostToJson(
343           *url, std::string{""}, CF_EXPECT(gce_api_.Headers())));
344       const auto& json = response.data;
345       Json::Value errors;
346       if (auto j_error = OptObjMember(json, "error"); j_error) {
347         if (auto j_errors = OptArrayMember(*j_error, "errors"); j_errors) {
348           errors = j_errors->size() > 0 ? *j_errors : Json::Value();
349         }
350       }
351       Json::Value warnings;
352       if (auto j_warnings = OptArrayMember(json, "warnings"); j_warnings) {
353         warnings = j_warnings->size() > 0 ? *j_warnings : Json::Value();
354       }
355       LOG(DEBUG) << "Requested operation status at \"" << *url
356                  << "\", received " << json;
357       if (!response.HttpSuccess() || errors != Json::Value()) {
358         return CF_ERR("Error accessing \"" << *url << "\". Errors: " << errors
359                                            << ", Warnings: " << warnings);
360       }
361       if (!json.isMember("status") ||
362           json["status"].type() != Json::ValueType::stringValue) {
363         return CF_ERR(json << " \"status\" field invalid");
364       }
365       if (json["status"] == "DONE") {
366         return true;
367       }
368     }
369     return false;
370   }
371 
372  private:
373   GceApi& gce_api_;
374   std::function<Result<Json::Value>()> initial_request_;
375   bool running_;
376   std::future<Result<bool>> operation_future_;
377   friend class GceApi::Operation;
378 };
379 
Operation(std::unique_ptr<GceApi::Operation::Impl> impl)380 GceApi::Operation::Operation(std::unique_ptr<GceApi::Operation::Impl> impl)
381     : impl_(std::move(impl)) {}
382 
383 GceApi::Operation::~Operation() = default;
384 
StopWaiting()385 void GceApi::Operation::StopWaiting() { impl_->running_ = false; }
386 
Future()387 std::future<Result<bool>>& GceApi::Operation::Future() {
388   return impl_->operation_future_;
389 }
390 
RandomUuid()391 static std::string RandomUuid() {
392   uuid_t uuid;
393   uuid_generate_random(uuid);
394   std::string uuid_str = "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx";
395   uuid_unparse(uuid, uuid_str.data());
396   return uuid_str;
397 }
398 
399 // GCE gives back full URLs for zones, but it only wants the last part in
400 // requests
SanitizeZone(const std::string & zone)401 static std::string SanitizeZone(const std::string& zone) {
402   auto last_slash = zone.rfind("/");
403   if (last_slash == std::string::npos) {
404     return zone;
405   }
406   return zone.substr(last_slash + 1);
407 }
408 
Get(const GceInstanceInfo & instance)409 std::future<Result<GceInstanceInfo>> GceApi::Get(
410     const GceInstanceInfo& instance) {
411   auto name = instance.Name();
412   if (!name) {
413     auto task = [json = instance.AsJson()]() -> Result<GceInstanceInfo> {
414       return CF_ERR("Missing a name for \"" << json << "\"");
415     };
416     return std::async(std::launch::deferred, task);
417   }
418   auto zone = instance.Zone();
419   if (!zone) {
420     auto task = [json = instance.AsJson()]() -> Result<GceInstanceInfo> {
421       return CF_ERR("Missing a zone for \"" << json << "\"");
422     };
423     return std::async(std::launch::deferred, task);
424   }
425   return Get(*zone, *name);
426 }
427 
Get(const std::string & zone,const std::string & name)428 std::future<Result<GceInstanceInfo>> GceApi::Get(const std::string& zone,
429                                                  const std::string& name) {
430   std::stringstream url;
431   url << "https://compute.googleapis.com/compute/v1";
432   url << "/projects/" << http_client_.UrlEscape(project_);
433   url << "/zones/" << http_client_.UrlEscape(SanitizeZone(zone));
434   url << "/instances/" << http_client_.UrlEscape(name);
435   auto task = [this, url = url.str()]() -> Result<GceInstanceInfo> {
436     auto response =
437         CF_EXPECT(http_client_.DownloadToJson(url, CF_EXPECT(Headers())));
438     if (!response.HttpSuccess()) {
439       return CF_ERR("Failed to get instance info, received "
440                     << response.data << " with code " << response.http_code);
441     }
442     return GceInstanceInfo(response.data);
443   };
444   return std::async(task);
445 }
446 
Insert(const Json::Value & request)447 GceApi::Operation GceApi::Insert(const Json::Value& request) {
448   if (!request.isMember("zone") ||
449       request["zone"].type() != Json::ValueType::stringValue) {
450     auto task = [request]() -> Result<Json::Value> {
451       return CF_ERR("Missing a zone for \"" << request << "\"");
452     };
453     return Operation(
454         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
455   }
456   auto zone = request["zone"].asString();
457   Json::Value requestNoZone = request;
458   requestNoZone.removeMember("zone");
459   std::stringstream url;
460   url << "https://compute.googleapis.com/compute/v1";
461   url << "/projects/" << http_client_.UrlEscape(project_);
462   url << "/zones/" << http_client_.UrlEscape(SanitizeZone(zone));
463   url << "/instances";
464   url << "?requestId=" << RandomUuid();  // Avoid duplication on request retry
465   auto task = [this, requestNoZone, url = url.str()]() -> Result<Json::Value> {
466     auto response = CF_EXPECT(
467         http_client_.PostToJson(url, requestNoZone, CF_EXPECT(Headers())));
468     if (!response.HttpSuccess()) {
469       return CF_ERR("Failed to create instance: "
470                     << response.data << ". Sent request " << requestNoZone);
471     }
472     return response.data;
473   };
474   return Operation(
475       std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
476 }
477 
Insert(const GceInstanceInfo & request)478 GceApi::Operation GceApi::Insert(const GceInstanceInfo& request) {
479   return Insert(request.AsJson());
480 }
481 
Reset(const std::string & zone,const std::string & name)482 GceApi::Operation GceApi::Reset(const std::string& zone,
483                                 const std::string& name) {
484   std::stringstream url;
485   url << "https://compute.googleapis.com/compute/v1";
486   url << "/projects/" << http_client_.UrlEscape(project_);
487   url << "/zones/" << http_client_.UrlEscape(SanitizeZone(zone));
488   url << "/instances/" << http_client_.UrlEscape(name);
489   url << "/reset";
490   url << "?requestId=" << RandomUuid();  // Avoid duplication on request retry
491   auto task = [this, url = url.str()]() -> Result<Json::Value> {
492     auto response = CF_EXPECT(
493         http_client_.PostToJson(url, Json::Value(), CF_EXPECT(Headers())));
494     if (!response.HttpSuccess()) {
495       return CF_ERR("Failed to create instance: " << response.data);
496     }
497     return response.data;
498   };
499   return Operation(
500       std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
501 }
502 
Reset(const GceInstanceInfo & instance)503 GceApi::Operation GceApi::Reset(const GceInstanceInfo& instance) {
504   auto name = instance.Name();
505   if (!name) {
506     auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
507       return CF_ERR("Missing a name for \"" << json << "\"");
508     };
509     return Operation(
510         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
511   }
512   auto zone = instance.Zone();
513   if (!zone) {
514     auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
515       return CF_ERR("Missing a zone for \"" << json << "\"");
516     };
517     return Operation(
518         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
519   }
520   return Reset(*zone, *name);
521 }
522 
Delete(const std::string & zone,const std::string & name)523 GceApi::Operation GceApi::Delete(const std::string& zone,
524                                  const std::string& name) {
525   std::stringstream url;
526   url << "https://compute.googleapis.com/compute/v1";
527   url << "/projects/" << http_client_.UrlEscape(project_);
528   url << "/zones/" << http_client_.UrlEscape(SanitizeZone(zone));
529   url << "/instances/" << http_client_.UrlEscape(name);
530   url << "?requestId=" << RandomUuid();  // Avoid duplication on request retry
531   auto task = [this, url = url.str()]() -> Result<Json::Value> {
532     auto response =
533         CF_EXPECT(http_client_.DeleteToJson(url, CF_EXPECT(Headers())));
534     if (!response.HttpSuccess()) {
535       return CF_ERR("Failed to delete instance: " << response.data);
536     }
537     return response.data;
538   };
539   return Operation(
540       std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
541 }
542 
Delete(const GceInstanceInfo & instance)543 GceApi::Operation GceApi::Delete(const GceInstanceInfo& instance) {
544   auto name = instance.Name();
545   if (!name) {
546     auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
547       return CF_ERR("Missing a name for \"" << json << "\"");
548     };
549     return Operation(
550         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
551   }
552   auto zone = instance.Zone();
553   if (!zone) {
554     auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
555       return CF_ERR("Missing a zone for \"" << json << "\"");
556     };
557     return Operation(
558         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
559   }
560   return Delete(*zone, *name);
561 }
562 
563 }  // namespace cuttlefish
564