Skip to content

Commit 1fcb3bf

Browse files
committed
In GCE, derive defaults from metadata.
Cloud infrastructure often users to deploy instances with associated metadata, for purposes like associating data such as configuration, or parameters describing instance purposes. This provides a natural means for feature flag deployment. We introduce a new defaults binary that runs in GCE instances, which will eventually be deployed with the `cuttlefish-integration` package, and runs at boot. The binary can load the defaults file with content derived from metadata verbatim. If for example because of permissions the metadata cannot be initialized with the desired content, then the binary can be also invoked with an optional flag that looks for a particular preexisting key-value pair in the instance metadata, and if true, initializes the defaults file with statically defined content. This key-value pair could represent known read-only values like a Cloud project name, which for example could denote a separate project dedicated for testing purposes.
1 parent 6b4de25 commit 1fcb3bf

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
load("//cuttlefish/bazel:rules.bzl", "cf_cc_binary")
2+
3+
package(
4+
default_visibility = ["//:android_cuttlefish"],
5+
)
6+
7+
cf_cc_binary(
8+
name = "defaults",
9+
srcs = [
10+
"defaults.cpp",
11+
],
12+
deps = [
13+
"//cuttlefish/common/libs/key_equals_value",
14+
"//cuttlefish/common/libs/utils:result",
15+
"//cuttlefish/host/libs/web/http_client:curl_http_client",
16+
"//cuttlefish/host/libs/web/http_client:http_string",
17+
"//libbase",
18+
"@abseil-cpp//absl/cleanup:cleanup",
19+
"@abseil-cpp//absl/flags:flag",
20+
"@abseil-cpp//absl/flags:parse",
21+
"@abseil-cpp//absl/strings:str_format",
22+
],
23+
)
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright (C) 2025 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 <string.h>
17+
#include <unistd.h>
18+
19+
#include <string_view>
20+
21+
#include <android-base/logging.h>
22+
#include <android-base/macros.h>
23+
#include "absl/cleanup/cleanup.h"
24+
#include "absl/flags/flag.h"
25+
#include "absl/flags/parse.h"
26+
#include "absl/strings/str_format.h"
27+
28+
#include "cuttlefish/common/libs/key_equals_value/key_equals_value.h"
29+
#include "cuttlefish/host/libs/web/http_client/curl_http_client.h"
30+
#include "cuttlefish/host/libs/web/http_client/http_string.h"
31+
32+
ABSL_FLAG(std::string, filename, "/usr/lib/cuttlefish-common/etc/cf_defaults", "Output filename.");
33+
ABSL_FLAG(std::optional<std::string>, static_defaults_when, std::nullopt,
34+
"Specify a key-value pair as \"<key>=<value>\". "
35+
"The key should be a metadata path, e.g., 'project/project-id'. "
36+
"If found in GCE metadata, then use statically defined defaults.");
37+
38+
namespace cuttlefish {
39+
namespace {
40+
41+
Result<std::string> MetadataValue(std::string_view key) {
42+
std::string url = absl::StrFormat(
43+
"http://metadata.google.internal/computeMetadata/v1/%s", key);
44+
const auto client = CurlHttpClient();
45+
CF_EXPECT(client.get());
46+
auto response =
47+
CF_EXPECT(HttpGetToString(*client, url, {"Metadata-Flavor: Google"}));
48+
CF_EXPECT(response.HttpSuccess());
49+
return response.data;
50+
}
51+
52+
Result<std::pair<std::string, std::string>> ParseKeyValueFlag(
53+
const std::string &flag) {
54+
std::map<std::string, std::string> kvs = CF_EXPECT(
55+
ParseKeyEqualsValue(flag));
56+
CF_EXPECT(kvs.size() == 1);
57+
return *(kvs.begin());
58+
}
59+
60+
/*
61+
* Use static defaults when the flag is specified with a key-value pair
62+
* that exists in GCE metadata.
63+
*
64+
* For example, if the flag value is `project/project-id=testing`,
65+
* static defaults will only be used if running in the `testing` project.
66+
*/
67+
Result<bool> UseStaticDefaults(const std::optional<std::string> &flag) {
68+
if (!flag.has_value()) {
69+
LOG(INFO) << "Will not use static defaults.";
70+
return false;
71+
}
72+
73+
/* Is the flag value well-formed? */
74+
LOG(INFO) << "Looking for metadata value from flag " << flag.value();
75+
Result<std::pair<std::string, std::string>> kv =
76+
CF_EXPECT(ParseKeyValueFlag(flag.value()),
77+
"Couldn't parse key-value pair to find in metadata, "
78+
"got: " << flag.value());
79+
80+
/* Does the key exist? If so, then get the value. */
81+
std::string actual =
82+
CF_EXPECT(MetadataValue(kv->first),
83+
"Couldn't get value at metadata path " << kv->first);
84+
std::string expected = kv->second;
85+
86+
/* Is the value expected? */
87+
std::string err = absl::StrFormat(
88+
"Metadata value for %s unexpected, got: '%s', expected '%s'",
89+
kv->first, actual, expected);
90+
return CF_EXPECT_EQ(actual, expected, err);
91+
}
92+
93+
Result<std::map<std::string, std::string>> DefaultsFromMetadata() {
94+
std::string data =
95+
CF_EXPECT(MetadataValue("instance/attributes/cf-defaults"));
96+
return ParseKeyEqualsValue(data);
97+
}
98+
99+
std::map<std::string, std::string> StaticDefaults() {
100+
return {
101+
{"use_cvdalloc", "true"},
102+
};
103+
}
104+
105+
} // namespace
106+
107+
Result<int> DefaultsMain(int argc, char *argv[]) {
108+
std::vector<char *> args = absl::ParseCommandLine(argc, argv);
109+
110+
std::string filename = absl::GetFlag(FLAGS_filename);
111+
LOG(INFO) << "Writing to " << filename;
112+
113+
if (CF_EXPECT(UseStaticDefaults(absl::GetFlag(FLAGS_static_defaults_when)))) {
114+
std::map<std::string, std::string> m = StaticDefaults();
115+
CF_EXPECT(WriteKeyEqualsValue(m, filename));
116+
return 0;
117+
}
118+
119+
Result<std::map<std::string, std::string>> m = DefaultsFromMetadata();
120+
if (!m.ok()) {
121+
LOG(INFO) << "Couldn't get defaults from metadata.";
122+
// Not necessarily an error, so don't report it.
123+
return 0;
124+
}
125+
126+
CF_EXPECT(WriteKeyEqualsValue(*m, filename));
127+
return 0;
128+
}
129+
130+
} // namespace cuttlefish
131+
132+
int main(int argc, char *argv[]) {
133+
auto res = cuttlefish::DefaultsMain(argc, argv);
134+
if (!res.ok()) {
135+
LOG(ERROR) << "defaults failed: \n" << res.error().FormatForEnv();
136+
abort();
137+
}
138+
139+
return *res;
140+
}

base/cvd/cuttlefish/package/BUILD.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,12 @@ package_files(
132132
},
133133
visibility = ["//:android_cuttlefish"],
134134
)
135+
136+
package_files(
137+
name = "defaults",
138+
executable = "cuttlefish-integration/bin/defaults",
139+
package_file_to_src = {
140+
"cuttlefish-integration/bin/defaults": "//cuttlefish/host/commands/defaults",
141+
},
142+
visibility = ["//:android_cuttlefish"],
143+
)

0 commit comments

Comments
 (0)