Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Version history
* admin: the admin server can now be accessed via HTTP/2 (prior knowledge).
* buffer: fix vulnerabilities when allocation fails.
* config: added support of using google.protobuf.Any in opaque configs for extensions.
* config: logging warnings when deprecated fields are in use.
* config: removed deprecated --v2-config-only from command line config.
* config: removed deprecated_v1 sds_config from :ref:`Bootstrap config <config_overview_v2_bootstrap>`.
* config: removed REST_LEGACY as a valid :ref:`ApiType <envoy_api_field_core.ApiConfigSource.api_type>`.
Expand Down
40 changes: 40 additions & 0 deletions source/common/protobuf/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,46 @@ void MessageUtil::loadFromFile(const std::string& path, Protobuf::Message& messa
}
}

void MessageUtil::checkForDeprecation(const Protobuf::Message& message, bool warn_only) {
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->full_name());
if (warn_only) {
ENVOY_LOG_MISC(warn, "{}", err);
} else {
throw ProtoValidationException(err, message);
}
}

// If this is a message, recurse to check for deprecated fields in the sub-message.
if (field->cpp_type() == 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), warn_only);
}
} else {
checkForDeprecation(reflection->GetMessage(message, field), warn_only);
}
}
}
}

std::string MessageUtil::getJsonStringFromMessage(const Protobuf::Message& message,
const bool pretty_print,
const bool always_print_primitive_fields) {
Expand Down
12 changes: 12 additions & 0 deletions source/common/protobuf/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,15 @@ class MessageUtil {
static void loadFromYaml(const std::string& yaml, Protobuf::Message& message);
static void loadFromFile(const std::string& path, Protobuf::Message& message, Api::Api& api);

/**
* Checks for use of deprecated fields in message and all sub-messages.
* @param message message to validate.
* @param warn_only if true, logs a warning rather than throwing an exception if deprecated fields
* are in use.
* @throw ProtoValidationException if deprecated fields are used and warn_only is false.
*/
static void checkForDeprecation(const Protobuf::Message& message, bool warn_only);

/**
* 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
Expand All @@ -186,6 +195,9 @@ class MessageUtil {
* @throw ProtoValidationException if the message does not satisfy its type constraints.
*/
template <class MessageType> static void validate(const MessageType& message) {
// Log warnings if deprecated fields are in use.
checkForDeprecation(message, true);

std::string err;
if (!Validate(message, &err)) {
throw ProtoValidationException(err, message);
Expand Down
2 changes: 2 additions & 0 deletions test/common/protobuf/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ envoy_cc_test(
deps = [
"//source/common/protobuf:utility_lib",
"//source/common/stats:isolated_store_lib",
"//test/proto:deprecated_proto_cc",
"//test/test_common:environment_lib",
"//test/test_common:logging_lib",
"//test/test_common:utility_lib",
"@envoy_api//envoy/config/bootstrap/v2:bootstrap_cc",
],
Expand Down
68 changes: 68 additions & 0 deletions test/common/protobuf/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
#include "common/protobuf/utility.h"
#include "common/stats/isolated_store_impl.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"
Expand Down Expand Up @@ -338,6 +340,72 @@ TEST(DurationUtilTest, OutOfRange) {
}
}

TEST(DeprecatedFields, NoErrorWhenDeprecatedFieldsUnused) {
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);
}

TEST(DeprecatedFields, IndividualFieldDeprecated) {
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 'envoy.test.deprecation_test.Base.is_deprecated'.",
MessageUtil::checkForDeprecation(base, true));
// Fatal checks for a deprecated field should result in an exception.
EXPECT_THROW_WITH_REGEX(
MessageUtil::checkForDeprecation(base, false), ProtoValidationException,
"Using deprecated option 'envoy.test.deprecation_test.Base.is_deprecated'.");
}

TEST(DeprecatedFields, MessageDeprecated) {
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, false), ProtoValidationException,
"Using deprecated option 'envoy.test.deprecation_test.Base.deprecated_message'.");
}

TEST(DeprecatedFields, InnerMessageDeprecated) {
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, true);

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, false), ProtoValidationException,
"Using deprecated option "
"'envoy.test.deprecation_test.Base.InnerMessage.inner_deprecated'.");
}

// Check that repeated sub-messages get validated.
TEST(DeprecatedFields, SubMessageDeprecated) {
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, false), ProtoValidationException,
"Using deprecated option "
"'envoy.test.deprecation_test.Base.InnerMessage.inner_deprecated'.");
}

// Check that deprecated repeated messages trigger
TEST(DeprecatedFields, RepeatedMessageDeprecated) {
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, false), ProtoValidationException,
"Using deprecated option 'envoy.test.deprecation_test.Base.deprecated_repeated_message'.");
}

class TimestampUtilTest : public ::testing::Test, public ::testing::WithParamInterface<int64_t> {};

TEST_P(TimestampUtilTest, SystemClockToTimestampTest) {
Expand Down
5 changes: 5 additions & 0 deletions test/proto/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ envoy_package()

exports_files(["bookstore.proto"])

envoy_proto_library(
name = "deprecated_proto",
srcs = [":deprecated.proto"],
)

envoy_proto_library(
name = "helloworld_proto",
srcs = [":helloworld.proto"],
Expand Down
16 changes: 16 additions & 0 deletions test/proto/deprecated.proto
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];
}