• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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