1 //
2 // Copyright 2022 gRPC authors.
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
17 #include "src/cpp/ext/gcp/observability_config.h"
18
19 #include <grpc/slice.h>
20 #include <grpc/status.h>
21 #include <grpc/support/port_platform.h>
22 #include <stddef.h>
23
24 #include <algorithm>
25 #include <utility>
26
27 #include "absl/status/status.h"
28 #include "absl/strings/match.h"
29 #include "absl/strings/str_cat.h"
30 #include "absl/strings/str_split.h"
31 #include "absl/strings/string_view.h"
32 #include "absl/types/optional.h"
33 #include "src/core/lib/iomgr/error.h"
34 #include "src/core/lib/slice/slice_internal.h"
35 #include "src/core/lib/transport/error_utils.h"
36 #include "src/core/util/env.h"
37 #include "src/core/util/json/json.h"
38 #include "src/core/util/json/json_reader.h"
39 #include "src/core/util/load_file.h"
40 #include "src/core/util/status_helper.h"
41 #include "src/core/util/validation_errors.h"
42
43 namespace grpc {
44 namespace internal {
45
46 namespace {
47
48 // Loads the contents of the file pointed by env var
49 // GRPC_GCP_OBSERVABILITY_CONFIG_FILE. If unset, falls back to the contents of
50 // GRPC_GCP_OBSERVABILITY_CONFIG.
GetGcpObservabilityConfigContents()51 absl::StatusOr<std::string> GetGcpObservabilityConfigContents() {
52 // First, try GRPC_GCP_OBSERVABILITY_CONFIG_FILE
53 std::string contents_str;
54 auto path = grpc_core::GetEnv("GRPC_GCP_OBSERVABILITY_CONFIG_FILE");
55 if (path.has_value() && !path.value().empty()) {
56 auto contents = grpc_core::LoadFile(*path, /*add_null_terminator=*/true);
57 if (!contents.ok()) {
58 return absl::FailedPreconditionError(absl::StrCat(
59 "error loading file ", *path, ": ", contents.status().ToString()));
60 }
61 return std::string(contents->as_string_view());
62 }
63 // Next, try GRPC_GCP_OBSERVABILITY_CONFIG env var.
64 auto env_config = grpc_core::GetEnv("GRPC_GCP_OBSERVABILITY_CONFIG");
65 if (env_config.has_value() && !env_config.value().empty()) {
66 return std::move(*env_config);
67 }
68 // No observability config found.
69 return absl::FailedPreconditionError(
70 "Environment variables GRPC_GCP_OBSERVABILITY_CONFIG_FILE or "
71 "GRPC_GCP_OBSERVABILITY_CONFIG "
72 "not defined");
73 }
74
75 // Tries to get the GCP Project ID from environment variables, or returns an
76 // empty string if not found.
GetProjectIdFromGcpEnvVar()77 std::string GetProjectIdFromGcpEnvVar() {
78 // First check GCP_PROJECT
79 absl::optional<std::string> project_id = grpc_core::GetEnv("GCP_PROJECT");
80 if (project_id.has_value() && !project_id->empty()) {
81 return project_id.value();
82 }
83 // Next, try GCLOUD_PROJECT
84 project_id = grpc_core::GetEnv("GCLOUD_PROJECT");
85 if (project_id.has_value() && !project_id->empty()) {
86 return project_id.value();
87 }
88 // Lastly, try GOOGLE_CLOUD_PROJECT
89 project_id = grpc_core::GetEnv("GOOGLE_CLOUD_PROJECT");
90 if (project_id.has_value() && !project_id->empty()) {
91 return project_id.value();
92 }
93 return "";
94 }
95
96 } // namespace
97
98 //
99 // GcpObservabilityConfig::CloudLogging::RpcEventConfiguration
100 //
101
102 const grpc_core::JsonLoaderInterface*
JsonLoader(const grpc_core::JsonArgs &)103 GcpObservabilityConfig::CloudLogging::RpcEventConfiguration::JsonLoader(
104 const grpc_core::JsonArgs&) {
105 static const auto* loader =
106 grpc_core::JsonObjectLoader<RpcEventConfiguration>()
107 .OptionalField("methods", &RpcEventConfiguration::qualified_methods)
108 .OptionalField("exclude", &RpcEventConfiguration::exclude)
109 .OptionalField("max_metadata_bytes",
110 &RpcEventConfiguration::max_metadata_bytes)
111 .OptionalField("max_message_bytes",
112 &RpcEventConfiguration::max_message_bytes)
113 .Finish();
114 return loader;
115 }
116
JsonPostLoad(const grpc_core::Json &,const grpc_core::JsonArgs &,grpc_core::ValidationErrors * errors)117 void GcpObservabilityConfig::CloudLogging::RpcEventConfiguration::JsonPostLoad(
118 const grpc_core::Json& /* json */, const grpc_core::JsonArgs& /* args */,
119 grpc_core::ValidationErrors* errors) {
120 grpc_core::ValidationErrors::ScopedField methods_field(errors, ".methods");
121 parsed_methods.reserve(qualified_methods.size());
122 for (size_t i = 0; i < qualified_methods.size(); ++i) {
123 grpc_core::ValidationErrors::ScopedField methods_index(
124 errors, absl::StrCat("[", i, "]"));
125 std::vector<absl::string_view> parts =
126 absl::StrSplit(qualified_methods[i], '/', absl::SkipEmpty());
127 if (parts.size() > 2) {
128 errors->AddError("methods[] can have at most a single '/'");
129 continue;
130 } else if (parts.empty()) {
131 errors->AddError("Empty configuration");
132 continue;
133 } else if (parts.size() == 1) {
134 if (parts[0] != "*") {
135 errors->AddError("Illegal methods[] configuration");
136 continue;
137 }
138 if (exclude) {
139 errors->AddError(
140 "Wildcard match '*' not allowed when 'exclude' is set");
141 continue;
142 }
143 parsed_methods.push_back(ParsedMethod{parts[0], ""});
144 } else {
145 // parts.size() == 2
146 if (absl::StrContains(parts[0], '*')) {
147 errors->AddError("Configuration of type '*/method' not allowed");
148 continue;
149 }
150 if (absl::StrContains(parts[1], '*') && parts[1].size() != 1) {
151 errors->AddError("Wildcard specified for method in incorrect manner");
152 continue;
153 }
154 parsed_methods.push_back(ParsedMethod{parts[0], parts[1]});
155 }
156 }
157 }
158
ReadFromEnv()159 absl::StatusOr<GcpObservabilityConfig> GcpObservabilityConfig::ReadFromEnv() {
160 auto config_contents = GetGcpObservabilityConfigContents();
161 if (!config_contents.ok()) {
162 return config_contents.status();
163 }
164 auto config_json = grpc_core::JsonParse(*config_contents);
165 if (!config_json.ok()) {
166 return config_json.status();
167 }
168 auto config = grpc_core::LoadFromJson<GcpObservabilityConfig>(*config_json);
169 if (!config.ok()) {
170 return config.status();
171 }
172 if (config->project_id.empty()) {
173 // Get project ID from GCP environment variables since project ID was not
174 // set it in the GCP observability config.
175 config->project_id = GetProjectIdFromGcpEnvVar();
176 if (config->project_id.empty()) {
177 // Could not find project ID from GCP environment variables either.
178 return absl::FailedPreconditionError("GCP Project ID not found.");
179 }
180 }
181 return config;
182 }
183
184 } // namespace internal
185 } // namespace grpc
186