diff --git a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto index ebcc53d774d4..5ef01fd8a290 100644 --- a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto +++ b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.filters.http.ext_proc.v3; import "envoy/config/common/mutation_rules/v3/mutation_rules.proto"; +import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/grpc_service.proto"; import "envoy/extensions/filters/http/ext_proc/v3/processing_mode.proto"; import "envoy/type/matcher/v3/string.proto"; @@ -273,7 +274,7 @@ message ExtProcPerRoute { } // Overrides that may be set on a per-route basis -// [#next-free-field: 7] +// [#next-free-field: 8] message ExtProcOverrides { // Set a different processing mode for this route than the default. ProcessingMode processing_mode = 1; @@ -301,4 +302,10 @@ message ExtProcOverrides { // config used. It is the prerogative of the control plane to ensure this // most-specific config contains the correct final overrides. MetadataOptions metadata_options = 6; + + // Additional metadata to include into streams initiated to the ext_proc gRPC + // service. This can be used for scenarios in which additional ad hoc + // authorization headers (e.g. ``x-foo-bar: baz-key``) are to be injected or + // when a route needs to partially override inherited metadata. + repeated config.core.v3.HeaderValue grpc_initial_metadata = 7; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index cbea1657d1fd..cd0767926b3e 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -31,6 +31,15 @@ removed_config_or_runtime: removed ``envoy.reloadable_features.overload_manager_error_unknown_action`` and legacy code paths. new_features: +- area: ext_proc + change: | + Added + :ref:`grpc_initial_metadata ` + config API to allow extending inherited metadata from + :ref:`ExternalProcessor.grpc_service ` + and + :ref:`ExtProcOverrides.grpc_service ` + with the new or updated values. - area: aws_request_signing change: | Update ``aws_request_signing`` filter to support use as an upstream HTTP filter. This allows successful calculation of diff --git a/source/extensions/filters/http/ext_proc/BUILD b/source/extensions/filters/http/ext_proc/BUILD index 976de7b294b8..49ceffc502fe 100644 --- a/source/extensions/filters/http/ext_proc/BUILD +++ b/source/extensions/filters/http/ext_proc/BUILD @@ -35,6 +35,7 @@ envoy_cc_library( "//source/extensions/filters/http/common:pass_through_filter_lib", "@com_google_absl//absl/status", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/strings:string_view", "@envoy_api//envoy/config/common/mutation_rules/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/ext_proc/v3:pkg_cc_proto", diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 4b41dbdc57a2..88ac3e5a0d11 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -9,11 +9,13 @@ #include "source/extensions/filters/http/ext_proc/mutation_utils.h" #include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" namespace Envoy { namespace Extensions { namespace HttpFilters { namespace ExternalProcessing { +namespace { using envoy::config::common::mutation_rules::v3::HeaderMutationRules; using envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute; @@ -33,9 +35,114 @@ using Http::RequestTrailerMap; using Http::ResponseHeaderMap; using Http::ResponseTrailerMap; -static const std::string ErrorPrefix = "ext_proc_error"; -static const int DefaultImmediateStatus = 200; -static const std::string FilterName = "envoy.filters.http.ext_proc"; +constexpr absl::string_view ErrorPrefix = "ext_proc_error"; +constexpr int DefaultImmediateStatus = 200; +constexpr absl::string_view FilterName = "envoy.filters.http.ext_proc"; + +absl::optional initProcessingMode(const ExtProcPerRoute& config) { + if (!config.disabled() && config.has_overrides() && config.overrides().has_processing_mode()) { + return config.overrides().processing_mode(); + } + return absl::nullopt; +} + +absl::optional +initGrpcService(const ExtProcPerRoute& config) { + if (config.has_overrides() && config.overrides().has_grpc_service()) { + return config.overrides().grpc_service(); + } + return absl::nullopt; +} + +std::vector initNamespaces(const Protobuf::RepeatedPtrField& ns) { + std::vector namespaces; + for (const auto& single_ns : ns) { + namespaces.emplace_back(single_ns); + } + return namespaces; +} + +absl::optional> +initUntypedForwardingNamespaces(const ExtProcPerRoute& config) { + if (!config.has_overrides() || !config.overrides().has_metadata_options() || + !config.overrides().metadata_options().has_forwarding_namespaces()) { + return absl::nullopt; + } + + return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().untyped())}; +} + +absl::optional> +initTypedForwardingNamespaces(const ExtProcPerRoute& config) { + if (!config.has_overrides() || !config.overrides().has_metadata_options() || + !config.overrides().metadata_options().has_forwarding_namespaces()) { + return absl::nullopt; + } + + return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().typed())}; +} + +absl::optional> +initUntypedReceivingNamespaces(const ExtProcPerRoute& config) { + if (!config.has_overrides() || !config.overrides().has_metadata_options() || + !config.overrides().metadata_options().has_receiving_namespaces()) { + return absl::nullopt; + } + + return {initNamespaces(config.overrides().metadata_options().receiving_namespaces().untyped())}; +} + +absl::optional mergeProcessingMode(const FilterConfigPerRoute& less_specific, + const FilterConfigPerRoute& more_specific) { + if (more_specific.disabled()) { + return absl::nullopt; + } + return more_specific.processingMode().has_value() ? more_specific.processingMode() + : less_specific.processingMode(); +} + +// Replaces all entries with the same name or append one. +void mergeHeaderValues(std::vector& metadata, + const envoy::config::core::v3::HeaderValue& header) { + bool has_key = false; + for (auto& dest : metadata) { + if (dest.key() == header.key()) { + dest.CopyFrom(header); + has_key = true; + } + } + if (!has_key) { + metadata.emplace_back(header); + } +} + +std::vector +mergeGrpcInitialMetadata(const FilterConfigPerRoute& less_specific, + const FilterConfigPerRoute& more_specific) { + std::vector metadata(less_specific.grpcInitialMetadata()); + + for (const auto& header : more_specific.grpcInitialMetadata()) { + mergeHeaderValues(metadata, header); + } + + return metadata; +} + +// Replaces all entries with the same name or append one. +void mergeHeaderValuesField( + Protobuf::RepeatedPtrField<::envoy::config::core::v3::HeaderValue>& metadata, + const envoy::config::core::v3::HeaderValue& header) { + bool has_key = false; + for (auto& dest : metadata) { + if (dest.key() == header.key()) { + dest.CopyFrom(header); + has_key = true; + } + } + if (!has_key) { + metadata.Add()->CopyFrom(header); + } +} // Changes to headers are normally tested against the MutationRules supplied // with configuration. When writing an immediate response message, however, @@ -58,6 +165,19 @@ class ImmediateMutationChecker { std::unique_ptr rule_checker_; }; +const ImmediateMutationChecker& immediateResponseChecker() { + CONSTRUCT_ON_FIRST_USE(ImmediateMutationChecker); +} + +ProcessingMode allDisabledMode() { + ProcessingMode pm; + pm.set_request_header_mode(ProcessingMode::SKIP); + pm.set_response_header_mode(ProcessingMode::SKIP); + return pm; +} + +} // namespace + void ExtProcLoggingInfo::recordGrpcCall( std::chrono::microseconds latency, Grpc::Status::GrpcStatus call_status, ProcessorState::CallbackState callback_state, @@ -115,77 +235,11 @@ ExtProcLoggingInfo::grpcCalls(envoy::config::core::v3::TrafficDirection traffic_ : encoding_processor_grpc_calls_; } -std::vector -FilterConfigPerRoute::initNamespaces(const Protobuf::RepeatedPtrField& ns) { - if (ns.empty()) { - return {}; - } - - std::vector namespaces; - for (const auto& single_ns : ns) { - namespaces.emplace_back(single_ns); - } - return namespaces; -} - -absl::optional> -FilterConfigPerRoute::initUntypedForwardingNamespaces(const ExtProcPerRoute& config) { - if (!config.has_overrides() || !config.overrides().has_metadata_options() || - !config.overrides().metadata_options().has_forwarding_namespaces()) { - return absl::nullopt; - } - - return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().untyped())}; -} - -absl::optional> -FilterConfigPerRoute::initTypedForwardingNamespaces(const ExtProcPerRoute& config) { - if (!config.has_overrides() || !config.overrides().has_metadata_options() || - !config.overrides().metadata_options().has_forwarding_namespaces()) { - return absl::nullopt; - } - - return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().typed())}; -} - -absl::optional> -FilterConfigPerRoute::initUntypedReceivingNamespaces(const ExtProcPerRoute& config) { - if (!config.has_overrides() || !config.overrides().has_metadata_options() || - !config.overrides().metadata_options().has_receiving_namespaces()) { - return absl::nullopt; - } - - return {initNamespaces(config.overrides().metadata_options().receiving_namespaces().untyped())}; -} - -absl::optional -FilterConfigPerRoute::initProcessingMode(const ExtProcPerRoute& config) { - if (!config.disabled() && config.has_overrides() && config.overrides().has_processing_mode()) { - return config.overrides().processing_mode(); - } - return absl::nullopt; -} -absl::optional -FilterConfigPerRoute::initGrpcService(const ExtProcPerRoute& config) { - if (config.has_overrides() && config.overrides().has_grpc_service()) { - return config.overrides().grpc_service(); - } - return absl::nullopt; -} - -absl::optional -FilterConfigPerRoute::mergeProcessingMode(const FilterConfigPerRoute& less_specific, - const FilterConfigPerRoute& more_specific) { - if (more_specific.disabled()) { - return absl::nullopt; - } - return more_specific.processingMode().has_value() ? more_specific.processingMode() - : less_specific.processingMode(); -} - FilterConfigPerRoute::FilterConfigPerRoute(const ExtProcPerRoute& config) : disabled_(config.disabled()), processing_mode_(initProcessingMode(config)), grpc_service_(initGrpcService(config)), + grpc_initial_metadata_(config.overrides().grpc_initial_metadata().begin(), + config.overrides().grpc_initial_metadata().end()), untyped_forwarding_namespaces_(initUntypedForwardingNamespaces(config)), typed_forwarding_namespaces_(initTypedForwardingNamespaces(config)), untyped_receiving_namespaces_(initUntypedReceivingNamespaces(config)) {} @@ -196,6 +250,7 @@ FilterConfigPerRoute::FilterConfigPerRoute(const FilterConfigPerRoute& less_spec processing_mode_(mergeProcessingMode(less_specific, more_specific)), grpc_service_(more_specific.grpcService().has_value() ? more_specific.grpcService() : less_specific.grpcService()), + grpc_initial_metadata_(mergeGrpcInitialMetadata(less_specific, more_specific)), untyped_forwarding_namespaces_(more_specific.untypedForwardingMetadataNamespaces().has_value() ? more_specific.untypedForwardingMetadataNamespaces() : less_specific.untypedForwardingMetadataNamespaces()), @@ -994,10 +1049,6 @@ void Filter::onFinishProcessorCalls(Grpc::Status::GrpcStatus call_status) { encoding_state_.onFinishProcessorCall(call_status); } -static const ImmediateMutationChecker& immediateResponseChecker() { - CONSTRUCT_ON_FIRST_USE(ImmediateMutationChecker); -} - void Filter::sendImmediateResponse(const ImmediateResponse& response) { auto status_code = response.has_status() ? response.status().code() : DefaultImmediateStatus; if (!MutationUtils::isValidHttpStatus(status_code)) { @@ -1035,13 +1086,6 @@ void Filter::sendImmediateResponse(const ImmediateResponse& response) { mutate_headers, grpc_status, details); } -static ProcessingMode allDisabledMode() { - ProcessingMode pm; - pm.set_request_header_mode(ProcessingMode::SKIP); - pm.set_response_header_mode(ProcessingMode::SKIP); - return pm; -} - void Filter::mergePerRouteConfig() { if (route_config_merged_) { return; @@ -1088,6 +1132,16 @@ void Filter::mergePerRouteConfig() { grpc_service_ = *merged_config->grpcService(); config_with_hash_key_.setConfig(*merged_config->grpcService()); } + if (!merged_config->grpcInitialMetadata().empty()) { + ENVOY_LOG(trace, "Overriding grpc initial metadata from per-route configuration"); + envoy::config::core::v3::GrpcService config = config_with_hash_key_.config(); + auto ptr = config.mutable_initial_metadata(); + for (const auto& header : merged_config->grpcInitialMetadata()) { + ENVOY_LOG(trace, "Setting grpc initial metadata {} = {}", header.key(), header.value()); + mergeHeaderValuesField(*ptr, header); + } + config_with_hash_key_.setConfig(config); + } // For metadata namespaces, we only override the existing value if we have a // value from our merged config. We indicate a lack of value from the merged diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 78d97cbe9bab..ba90d5113cb2 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -247,6 +247,9 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig { const absl::optional& grpcService() const { return grpc_service_; } + const std::vector& grpcInitialMetadata() const { + return grpc_initial_metadata_; + } const absl::optional>& untypedForwardingMetadataNamespaces() const { @@ -260,31 +263,11 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig { } private: - absl::optional - initProcessingMode(const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - absl::optional - initGrpcService(const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - std::vector initNamespaces(const Protobuf::RepeatedPtrField& ns); - - absl::optional> initUntypedForwardingNamespaces( - const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - absl::optional> initTypedForwardingNamespaces( - const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - absl::optional> initUntypedReceivingNamespaces( - const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - absl::optional - mergeProcessingMode(const FilterConfigPerRoute& less_specific, - const FilterConfigPerRoute& more_specific); - const bool disabled_; const absl::optional processing_mode_; const absl::optional grpc_service_; + std::vector grpc_initial_metadata_; const absl::optional> untyped_forwarding_namespaces_; const absl::optional> typed_forwarding_namespaces_; @@ -321,6 +304,9 @@ class Filter : public Logger::Loggable, config->untypedReceivingMetadataNamespaces()) {} const FilterConfig& config() const { return *config_; } + const envoy::config::core::v3::GrpcService& grpc_service_config() const { + return config_with_hash_key_.config(); + } ExtProcFilterStats& stats() { return stats_; } ExtProcLoggingInfo* loggingInfo() { return logging_info_; } diff --git a/test/extensions/filters/http/ext_proc/config_test.cc b/test/extensions/filters/http/ext_proc/config_test.cc index fb982aee9b25..5c5196af6551 100644 --- a/test/extensions/filters/http/ext_proc/config_test.cc +++ b/test/extensions/filters/http/ext_proc/config_test.cc @@ -106,6 +106,23 @@ TEST(HttpExtProcConfigTest, CorrectConfigServerContext) { cb(filter_callback); } +TEST(HttpExtProcConfigTest, CorrectRouteMetadataOnlyConfig) { + std::string yaml = R"EOF( + overrides: + grpc_initial_metadata: + - key: "a" + value: "a" + )EOF"; + + ExternalProcessingFilterConfig factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + testing::NiceMock context; + Router::RouteSpecificFilterConfigConstSharedPtr cb = factory.createRouteSpecificFilterConfig( + *proto_config, context, context.messageValidationVisitor()); +} + } // namespace } // namespace ExternalProcessing } // namespace HttpFilters diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index d0becb1f2bab..b2c8eb1728ad 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -40,6 +40,7 @@ using envoy::service::ext_proc::v3::ProcessingResponse; using envoy::service::ext_proc::v3::TrailersResponse; using Extensions::HttpFilters::ExternalProcessing::HasNoHeader; using Extensions::HttpFilters::ExternalProcessing::HeaderProtosEqual; +using Extensions::HttpFilters::ExternalProcessing::makeHeaderValue; using Extensions::HttpFilters::ExternalProcessing::SingleHeaderValueIs; using Http::LowerCaseString; @@ -2580,6 +2581,43 @@ TEST_P(ExtProcIntegrationTest, PerRouteGrpcService) { EXPECT_THAT(response->headers(), SingleHeaderValueIs("x-response-processed", "1")); } +// Set up per-route configuration that extends original metadata. +TEST_P(ExtProcIntegrationTest, PerRouteGrpcMetadata) { + initializeConfig(); + + // Override metadata from route config. + config_helper_.addConfigModifier([this](HttpConnectionManager& cm) { + // Set up "/foo" so that it will use a different GrpcService + auto* vh = cm.mutable_route_config()->mutable_virtual_hosts()->Mutable(0); + auto* route = vh->mutable_routes()->Mutable(0); + route->mutable_match()->set_path("/foo"); + ExtProcPerRoute per_route; + *per_route.mutable_overrides()->mutable_grpc_initial_metadata()->Add() = + makeHeaderValue("b", "c"); + *per_route.mutable_overrides()->mutable_grpc_initial_metadata()->Add() = + makeHeaderValue("c", "c"); + setPerRouteConfig(route, per_route); + }); + + HttpIntegrationTest::initialize(); + + // Request that matches route directed to ext_proc_server_0 + auto response = + sendDownstreamRequest([](Http::RequestHeaderMap& headers) { headers.setPath("/foo"); }); + + processRequestHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); + EXPECT_EQ( + "c", + processor_stream_->headers().get(Http::LowerCaseString("b"))[0]->value().getStringView()); + EXPECT_EQ( + "c", + processor_stream_->headers().get(Http::LowerCaseString("c"))[0]->value().getStringView()); + handleUpstreamRequest(); + + processResponseHeadersMessage(*grpc_upstreams_[0], false, absl::nullopt); + verifyDownstreamResponse(*response, 200); +} + // Sending new timeout API in both downstream request and upstream response // handling path with header mutation. TEST_P(ExtProcIntegrationTest, RequestAndResponseMessageNewTimeoutWithHeaderMutation) { diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 907f52aef5c6..83d469b60331 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -2998,6 +2998,33 @@ TEST(OverrideTest, GrpcServiceNonOverride) { EXPECT_THAT(*merged_route.grpcService(), ProtoEq(cfg1.overrides().grpc_service())); } +// When merging two configurations, second metadata override only extends the first's one. +TEST(OverrideTest, GrpcMetadataOverride) { + ExtProcPerRoute cfg1; + cfg1.mutable_overrides()->mutable_grpc_initial_metadata()->Add()->CopyFrom( + makeHeaderValue("a", "a")); + cfg1.mutable_overrides()->mutable_grpc_initial_metadata()->Add()->CopyFrom( + makeHeaderValue("b", "b")); + + ExtProcPerRoute cfg2; + cfg2.mutable_overrides()->mutable_grpc_initial_metadata()->Add()->CopyFrom( + makeHeaderValue("b", "c")); + cfg2.mutable_overrides()->mutable_grpc_initial_metadata()->Add()->CopyFrom( + makeHeaderValue("c", "c")); + + FilterConfigPerRoute route1(cfg1); + FilterConfigPerRoute route2(cfg2); + FilterConfigPerRoute merged_route(route1, route2); + + ASSERT_TRUE(merged_route.grpcInitialMetadata().size() == 3); + EXPECT_THAT(merged_route.grpcInitialMetadata()[0], + ProtoEq(cfg1.overrides().grpc_initial_metadata()[0])); + EXPECT_THAT(merged_route.grpcInitialMetadata()[1], + ProtoEq(cfg2.overrides().grpc_initial_metadata()[0])); + EXPECT_THAT(merged_route.grpcInitialMetadata()[2], + ProtoEq(cfg2.overrides().grpc_initial_metadata()[1])); +} + // Verify that attempts to change headers that are not allowed to be changed // are ignored and a counter is incremented. TEST_F(HttpFilterTest, IgnoreInvalidHeaderMutations) { @@ -3853,6 +3880,63 @@ TEST_F(HttpFilter2Test, LastEncodeDataCallExceedsStreamBufferLimitWouldJustRaise conn_manager_->onData(fake_input, false); } +// Test that per route metadata override does override inherited grpc_service configuration. +TEST_F(HttpFilterTest, GrpcServiceMetadataOverride) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + initial_metadata: + - key: "a" + value: "a" + - key: "b" + value: "b" + )EOF"); + + // Route configuration overrides the grpc_service metadata. + ExtProcPerRoute route_proto; + *route_proto.mutable_overrides()->mutable_grpc_initial_metadata()->Add() = + makeHeaderValue("b", "c"); + *route_proto.mutable_overrides()->mutable_grpc_initial_metadata()->Add() = + makeHeaderValue("c", "c"); + FilterConfigPerRoute route_config(route_proto); + EXPECT_CALL(decoder_callbacks_, traversePerFilterConfig(_)) + .WillOnce( + testing::Invoke([&](std::function cb) { + cb(route_config); + })); + + // Build expected merged grpc_service configuration. + { + std::string expected_config = (R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + initial_metadata: + - key: "a" + value: "a" + - key: "b" + value: "c" + - key: "c" + value: "c" + )EOF"); + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor expected_proto{}; + TestUtility::loadFromYaml(expected_config, expected_proto); + final_expected_grpc_service_.emplace(expected_proto.grpc_service()); + config_with_hash_key_.setConfig(expected_proto.grpc_service()); + } + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, true)); + processRequestHeaders(false, absl::nullopt); + + const auto& meta = filter_->grpc_service_config().initial_metadata(); + EXPECT_EQ(meta[0].value(), "a"); // a = a inherited + EXPECT_EQ(meta[1].value(), "c"); // b = c overridden + EXPECT_EQ(meta[2].value(), "c"); // c = c added + + filter_->onDestroy(); +} + } // namespace } // namespace ExternalProcessing } // namespace HttpFilters diff --git a/test/extensions/filters/http/ext_proc/utils.cc b/test/extensions/filters/http/ext_proc/utils.cc index 0e322535c08a..e3723b8b728b 100644 --- a/test/extensions/filters/http/ext_proc/utils.cc +++ b/test/extensions/filters/http/ext_proc/utils.cc @@ -29,6 +29,14 @@ bool ExtProcTestUtility::headerProtosEqualIgnoreOrder( return TestUtility::headerMapEqualIgnoreOrder(expected, actual_headers); } +envoy::config::core::v3::HeaderValue makeHeaderValue(const std::string& key, + const std::string& value) { + envoy::config::core::v3::HeaderValue v; + v.set_key(key); + v.set_value(value); + return v; +} + } // namespace ExternalProcessing } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/ext_proc/utils.h b/test/extensions/filters/http/ext_proc/utils.h index ba88559b9c74..858e8e7cb01d 100644 --- a/test/extensions/filters/http/ext_proc/utils.h +++ b/test/extensions/filters/http/ext_proc/utils.h @@ -50,6 +50,8 @@ MATCHER_P2(SingleProtoHeaderValueIs, key, value, return false; } +envoy::config::core::v3::HeaderValue makeHeaderValue(const std::string& key, + const std::string& value); } // namespace ExternalProcessing } // namespace HttpFilters } // namespace Extensions