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