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