-
Notifications
You must be signed in to change notification settings - Fork 5.5k
config: logging a warning on use of deprecated proto fields #5760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
b2e47fc
3a717fa
de43d41
5c6adc7
08175d6
ad3bcf0
17dfc47
9abec4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| #include "envoy/json/json_object.h" | ||
| #include "envoy/type/percent.pb.h" | ||
|
|
||
| #include "common/common/fmt.h" | ||
| #include "common/common/hash.h" | ||
| #include "common/common/utility.h" | ||
| #include "common/json/json_loader.h" | ||
|
|
@@ -177,6 +178,51 @@ class MessageUtil { | |
| static void loadFromYaml(const std::string& yaml, Protobuf::Message& message); | ||
| static void loadFromFile(const std::string& path, Protobuf::Message& message); | ||
|
|
||
| static void checkForDeprecation(const Protobuf::Message& message, bool fatal) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: prefer |
||
| const Protobuf::Descriptor* descriptor = message.GetDescriptor(); | ||
| const Protobuf::Reflection* reflection = message.GetReflection(); | ||
| for (int i = 0; i < descriptor->field_count(); ++i) { | ||
| const auto* field = descriptor->field(i); | ||
|
|
||
| // If this field is not in use, continue. | ||
| if ((field->is_repeated() && reflection->FieldSize(message, field) == 0) || | ||
| (!field->is_repeated() && !reflection->HasField(message, field))) { | ||
| continue; | ||
| } | ||
|
|
||
| // If this field is deprecated, warn or throw an error. | ||
| if (field->options().deprecated()) { | ||
| std::string err = fmt::format( | ||
| "Using deprecated option '{}'. This configuration will be removed from Envoy soon. " | ||
| "Please see https://github.com/envoyproxy/envoy/blob/master/DEPRECATED.md for " | ||
| "details.", | ||
| field->name()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the tip - this is going to be even more useful! |
||
| if (fatal) { | ||
| throw ProtoValidationException(err, message); | ||
| } else { | ||
| ENVOY_LOG_MISC(warn, "{}", err); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use config logger?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which logger? The only other log macro in the util.cc used MISC so I didn't think any of the other log macros were suitable.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could wrap in a class to provide access to the config logger (or maybe there is a more direct way?).
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just make MessageUtil inherit Loggable and it should be good (it only provides static method)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, so you want it to inherit from Logger::LoggableLogger::Id::config Done, though I think ENVOY_LOG_MISC might make more sense, given that someone could put non-config related proto utilities in source/common/protobuf/utility.h That said the main uses of proto are config and gRPC which has its own directories, so it's fine for now. |
||
| } | ||
| } | ||
|
|
||
| // If this is a message, recurse to check for deprecated fields in the sub-message. | ||
| switch (field->cpp_type()) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just |
||
| case Protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { | ||
| if (field->is_repeated()) { | ||
| const int size = reflection->FieldSize(message, field); | ||
| for (int j = 0; j < size; ++j) { | ||
| checkForDeprecation(reflection->GetRepeatedMessage(message, field, j), fatal); | ||
| } | ||
| } else { | ||
| checkForDeprecation(reflection->GetMessage(message, field), fatal); | ||
| } | ||
| break; | ||
| } | ||
| default: | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Validate protoc-gen-validate constraints on a given protobuf. | ||
| * Note the corresponding `.pb.validate.h` for the message has to be included in the source file | ||
|
|
@@ -185,6 +231,9 @@ class MessageUtil { | |
| * @throw ProtoValidationException if the message does not satisfy its type constraints. | ||
| */ | ||
| template <class MessageType> static void validate(const MessageType& message) { | ||
| // Do a (non-fatal) deprecation check to log warnings about deprecated field use. | ||
| checkForDeprecation(message, false); | ||
|
|
||
| std::string err; | ||
| if (!Validate(message, &err)) { | ||
| throw ProtoValidationException(err, message); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,9 @@ | |
| #include "common/protobuf/protobuf.h" | ||
| #include "common/protobuf/utility.h" | ||
|
|
||
| #include "test/proto/deprecated.pb.h" | ||
| #include "test/test_common/environment.h" | ||
| #include "test/test_common/logging.h" | ||
| #include "test/test_common/utility.h" | ||
|
|
||
| #include "gtest/gtest.h" | ||
|
|
@@ -40,6 +42,65 @@ TEST(UtilityTest, DowncastAndValidate) { | |
| ProtoValidationException); | ||
| } | ||
|
|
||
| TEST(UtilityTest, ValidateDeprecatedFields) { | ||
| { | ||
| envoy::test::deprecation_test::Base base; | ||
| base.set_not_deprecated("foo"); | ||
| // Fatal checks for a non-deprecated field should cause no problem. | ||
| MessageUtil::checkForDeprecation(base, true); | ||
| } | ||
| { | ||
| envoy::test::deprecation_test::Base base; | ||
| base.set_is_deprecated("foo"); | ||
| // Non-fatal checks for a deprecated field should log rather than throw an exception. | ||
| EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'is_deprecated'.", | ||
| MessageUtil::checkForDeprecation(base, false)); | ||
| // Fatal checks for a deprecated field should result in an exception. | ||
| EXPECT_THROW_WITH_REGEX(MessageUtil::checkForDeprecation(base, true), ProtoValidationException, | ||
| "Using deprecated option 'is_deprecated'."); | ||
| } | ||
| { | ||
| envoy::test::deprecation_test::Base base; | ||
| base.mutable_deprecated_message(); | ||
| // Fatal checks for a present (unused) deprecated message should result in an exception. | ||
| EXPECT_THROW_WITH_REGEX(MessageUtil::checkForDeprecation(base, true), ProtoValidationException, | ||
| "Using deprecated option 'deprecated_message'."); | ||
| } | ||
|
|
||
| { | ||
| envoy::test::deprecation_test::Base base; | ||
| base.mutable_not_deprecated_message()->set_inner_not_deprecated("foo"); | ||
| // Non-fatal checks for a deprecated field shouldn't throw an exception. | ||
| MessageUtil::checkForDeprecation(base, false); | ||
|
|
||
| base.mutable_not_deprecated_message()->set_inner_deprecated("bar"); | ||
| // Fatal checks for a deprecated sub-message should result in an exception. | ||
| EXPECT_THROW_WITH_REGEX(MessageUtil::checkForDeprecation(base, true), ProtoValidationException, | ||
| "Using deprecated option 'inner_deprecated'."); | ||
| } | ||
|
|
||
| // Check that repeated sub-messages get validated. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: preference for making these all separate |
||
| { | ||
| envoy::test::deprecation_test::Base base; | ||
| base.add_repeated_message(); | ||
| base.add_repeated_message()->set_inner_deprecated("foo"); | ||
| base.add_repeated_message(); | ||
|
|
||
| // Fatal checks for a repeated deprecated sub-message should result in an exception. | ||
| EXPECT_THROW_WITH_REGEX(MessageUtil::checkForDeprecation(base, true), ProtoValidationException, | ||
| "Using deprecated option 'inner_deprecated'."); | ||
| } | ||
| // Check that deprecated repeated messages trigger | ||
| { | ||
| envoy::test::deprecation_test::Base base; | ||
| base.add_deprecated_repeated_message(); | ||
|
|
||
| // Fatal checks for a repeated deprecated sub-message should result in an exception. | ||
| EXPECT_THROW_WITH_REGEX(MessageUtil::checkForDeprecation(base, true), ProtoValidationException, | ||
| "Using deprecated option 'deprecated_repeated_message'."); | ||
| } | ||
| } | ||
|
|
||
| TEST(UtilityTest, LoadBinaryProtoFromFile) { | ||
| envoy::config::bootstrap::v2::Bootstrap bootstrap; | ||
| bootstrap.mutable_cluster_manager() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package envoy.test.deprecation_test; | ||
|
|
||
| message Base { | ||
| string not_deprecated = 1; | ||
| string is_deprecated = 2 [deprecated = true]; | ||
| message InnerMessage { | ||
| string inner_not_deprecated = 1; | ||
| string inner_deprecated = 2 [deprecated = true]; | ||
| } | ||
| InnerMessage deprecated_message = 3 [deprecated = true]; | ||
| InnerMessage not_deprecated_message = 4; | ||
| repeated InnerMessage repeated_message = 5; | ||
| repeated InnerMessage deprecated_repeated_message = 6 [deprecated = true]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move the impl to .cc