1
#include "source/extensions/formatter/file_content/config.h"
2

            
3
#include "envoy/extensions/formatter/file_content/v3/file_content.pb.h"
4
#include "envoy/registry/registry.h"
5

            
6
#include "source/common/common/utility.h"
7
#include "source/common/config/datasource.h"
8
#include "source/common/formatter/substitution_format_utility.h"
9

            
10
namespace Envoy {
11
namespace Extensions {
12
namespace Formatter {
13

            
14
namespace {
15

            
16
constexpr absl::string_view FileContentCommand = "FILE_CONTENT";
17

            
18
/**
19
 * FormatterProvider backed by a DataSourceProvider with file watching.
20
 * The DataSourceProvider automatically re-reads the file when it changes on disk.
21
 */
22
class FileContentFormatterProvider : public Envoy::Formatter::FormatterProvider {
23
public:
24
  FileContentFormatterProvider(
25
      Config::DataSource::DataSourceProviderSharedPtr<std::string> provider)
26
10
      : provider_(std::move(provider)) {}
27

            
28
  absl::optional<std::string> format(const Envoy::Formatter::Context&,
29
13
                                     const StreamInfo::StreamInfo&) const override {
30
13
    const auto data = provider_->data();
31
13
    if (!data) {
32
      return absl::nullopt;
33
    }
34
13
    return *data;
35
13
  }
36

            
37
  Protobuf::Value formatValue(const Envoy::Formatter::Context& context,
38
1
                              const StreamInfo::StreamInfo& stream_info) const override {
39
1
    Protobuf::Value val;
40
1
    const auto opt = format(context, stream_info);
41
1
    if (opt.has_value()) {
42
1
      val.set_string_value(*opt);
43
1
    }
44
1
    return val;
45
1
  }
46

            
47
private:
48
  Config::DataSource::DataSourceProviderSharedPtr<std::string> provider_;
49
};
50

            
51
/**
52
 * CommandParser that handles the %FILE_CONTENT(/path/to/file)% or
53
 * %FILE_CONTENT(/path/to/file:/path/to/watch)% command.
54
 * Creates a DataSourceProvider with file watching for each parsed file path.
55
 * When a watch directory is specified, changes in that directory trigger a re-read of the file.
56
 */
57
class FileContentCommandParser : public Envoy::Formatter::CommandParser {
58
public:
59
  explicit FileContentCommandParser(Server::Configuration::ServerFactoryContext& server_context)
60
12
      : server_context_(server_context) {}
61

            
62
  Envoy::Formatter::FormatterProviderPtr parse(absl::string_view command,
63
                                               absl::string_view subcommand,
64
13
                                               absl::optional<size_t> max_length) const override {
65
13
    if (command != FileContentCommand) {
66
1
      return nullptr;
67
1
    }
68

            
69
    // This formatter creates thread locals which can only happen on the main thread.
70
12
    ASSERT_IS_MAIN_OR_TEST_THREAD();
71

            
72
12
    envoy::config::core::v3::DataSource source;
73
    // Split subcommand on ':' to extract filename and optional watch directory.
74
    // Format: /path/to/file or /path/to/file:/path/to/watch
75
12
    const auto parts = StringUtil::splitToken(subcommand, ":", /*keep_empty_string=*/false);
76
12
    if (parts.empty() || parts.size() > 2) {
77
1
      throw EnvoyException(fmt::format(
78
1
          "FILE_CONTENT: expected format 'path' or 'path:watch_directory', got '{}'", subcommand));
79
1
    }
80
11
    source.set_filename(std::string(parts[0]));
81
11
    if (parts.size() == 2) {
82
1
      source.mutable_watched_directory()->set_path(std::string(parts[1]));
83
1
    }
84
11
    const Config::DataSource::ProviderOptions options{.allow_empty = true, .modify_watch = true};
85

            
86
11
    auto provider = THROW_OR_RETURN_VALUE(
87
11
        Config::DataSource::DataSourceProvider<std::string>::create(
88
11
            source, server_context_.mainThreadDispatcher(), server_context_.threadLocal(),
89
11
            server_context_.api(),
90
11
            [max_length](absl::string_view data) -> absl::StatusOr<std::shared_ptr<std::string>> {
91
11
              auto result = std::make_shared<std::string>(data);
92
11
              Envoy::Formatter::SubstitutionFormatUtils::truncate(*result, max_length);
93
11
              return result;
94
11
            },
95
11
            options),
96
11
        Config::DataSource::DataSourceProviderPtr<std::string>);
97

            
98
11
    return std::make_unique<FileContentFormatterProvider>(
99
11
        Config::DataSource::DataSourceProviderSharedPtr<std::string>(std::move(provider)));
100
12
  }
101

            
102
private:
103
  Server::Configuration::ServerFactoryContext& server_context_;
104
};
105

            
106
} // namespace
107

            
108
Envoy::Formatter::CommandParserPtr FileContentFormatterFactory::createCommandParserFromProto(
109
12
    const Protobuf::Message&, Server::Configuration::GenericFactoryContext& context) {
110
12
  return std::make_unique<FileContentCommandParser>(context.serverFactoryContext());
111
12
}
112

            
113
17
ProtobufTypes::MessagePtr FileContentFormatterFactory::createEmptyConfigProto() {
114
17
  return std::make_unique<envoy::extensions::formatter::file_content::v3::FileContent>();
115
17
}
116

            
117
10
std::string FileContentFormatterFactory::name() const { return "envoy.formatter.file_content"; }
118

            
119
REGISTER_FACTORY(FileContentFormatterFactory, Envoy::Formatter::CommandParserFactory);
120

            
121
} // namespace Formatter
122
} // namespace Extensions
123
} // namespace Envoy