11#include < userver/yaml_config/yaml_config.hpp>
22
33#include < fmt/format.h>
4- #include < boost/algorithm/string/predicate .hpp>
4+ #include < boost/filesystem/operations .hpp>
55
66#include < userver/formats/common/conversion_stack.hpp>
77#include < userver/formats/json/value.hpp>
88#include < userver/formats/json/value_builder.hpp>
99#include < userver/formats/yaml/serialize.hpp>
1010#include < userver/logging/log.hpp>
1111#include < userver/utils/string_to_duration.hpp>
12+ #include < userver/utils/text_light.hpp>
1213
1314USERVER_NAMESPACE_BEGIN
1415
@@ -26,52 +27,72 @@ std::string GetSubstitutionVarName(const formats::yaml::Value& value) {
2627 return value.As <std::string>().substr (1 );
2728}
2829
29- std::string GetFallbackName (std::string_view str) {
30- return std::string{str} + " #fallback" ;
31- }
32-
3330std::string GetEnvName (std::string_view str) {
3431 return std::string{str} + " #env" ;
3532}
3633
34+ std::string GetFileName (std::string_view str) {
35+ return std::string{str} + " #file" ;
36+ }
37+
38+ std::string GetFallbackName (std::string_view str) {
39+ return std::string{str} + " #fallback" ;
40+ }
41+
3742template <typename Field>
3843YamlConfig MakeMissingConfig (const YamlConfig& config, Field field) {
3944 const auto path = formats::common::MakeChildPath (config.GetPath (), field);
4045 return {formats::yaml::Value ()[path], {}};
4146}
4247
4348void AssertEnvMode (YamlConfig::Mode mode) {
44- if (mode != YamlConfig::Mode::kEnvAllowed ) {
49+ if (mode == YamlConfig::Mode::kSecure ) {
4550 throw std::runtime_error (
46- " YamlConfig was not constructed with Mode::kEnvAllowed but an attempt "
51+ " YamlConfig was not constructed with Mode::kEnvAllowed or "
52+ " Mode::kEnvAndFileAllowed but an attempt "
4753 " to read an environment variable was made" );
4854 }
4955}
5056
51- std::optional<formats::yaml::Value> GetFromEnvByKey (
52- std::string_view key, const formats::yaml::Value& yaml,
53- YamlConfig::Mode mode) {
54- const auto env_name = yaml[GetEnvName (key)];
55- if (!env_name.IsMissing ()) {
56- AssertEnvMode (mode);
57-
58- // NOLINTNEXTLINE(concurrency-mt-unsafe)
59- const auto * env_value = std::getenv (env_name.As <std::string>().c_str ());
60- if (env_value) {
61- LOG_INFO () << " using env value for '" << key << ' \' ' ;
62- return formats::yaml::FromString (env_value);
63- }
57+ void AssertFileMode (YamlConfig::Mode mode) {
58+ if (mode != YamlConfig::Mode::kEnvAndFileAllowed ) {
59+ throw std::runtime_error (
60+ " YamlConfig was not constructed with Mode::kEnvAndFileAllowed "
61+ " but an attempt to read a file was made" );
62+ }
63+ }
6464
65- const auto fallback_name = GetFallbackName (key);
66- if (yaml.HasMember (fallback_name)) {
67- LOG_INFO () << " using fallback value for '" << key << ' \' ' ;
68- return yaml[fallback_name];
69- }
65+ std::optional<formats::yaml::Value> GetFromEnvImpl (
66+ const formats::yaml::Value& env_name, YamlConfig::Mode mode) {
67+ if (env_name.IsMissing ()) {
68+ return {};
69+ }
70+
71+ AssertEnvMode (mode);
72+
73+ // NOLINTNEXTLINE(concurrency-mt-unsafe)
74+ const auto * env_value = std::getenv (env_name.As <std::string>().c_str ());
75+ if (env_value) {
76+ return formats::yaml::FromString (env_value);
7077 }
7178
7279 return {};
7380}
7481
82+ std::optional<formats::yaml::Value> GetFromFileImpl (
83+ const formats::yaml::Value& file_name, YamlConfig::Mode mode) {
84+ if (file_name.IsMissing ()) {
85+ return {};
86+ }
87+
88+ AssertFileMode (mode);
89+ const auto str_filename = file_name.As <std::string>();
90+ if (!boost::filesystem::exists (str_filename)) {
91+ return {};
92+ }
93+ return formats::yaml::blocking::FromFile (str_filename);
94+ }
95+
7596} // namespace
7697
7798YamlConfig::YamlConfig (formats::yaml::Value yaml,
@@ -83,54 +104,54 @@ YamlConfig::YamlConfig(formats::yaml::Value yaml,
83104const formats::yaml::Value& YamlConfig::Yaml () const { return yaml_; }
84105
85106YamlConfig YamlConfig::operator [](std::string_view key) const {
86- if (boost::algorithm::ends_with (key, " #env" )) {
87- auto env_value = GetFromEnvByKey (key, yaml_, mode_);
88- if (env_value) {
89- // Strip substitutions off to disallow nested substitutions
90- return YamlConfig{std::move (*env_value), {}, Mode::kSecure };
91- }
92-
93- // Avoid parsing #env as a string
107+ // TODO: fix the iterators and assert this case in TAXICOMMON-8973
108+ if (utils::text::EndsWith (key, " #env" ) ||
109+ utils::text::EndsWith (key, " #file" ) ||
110+ utils::text::EndsWith (key, " #fallback" )) {
94111 return MakeMissingConfig (*this , key);
95112 }
96113
97114 auto value = yaml_[key];
98115
99- if (IsSubstitution (value)) {
116+ const bool is_substitution = IsSubstitution (value);
117+ if (is_substitution) {
100118 const auto var_name = GetSubstitutionVarName (value);
101119
102120 auto var_data = config_vars_[var_name];
103121 if (!var_data.IsMissing ()) {
104122 // Strip substitutions off to disallow nested substitutions
105123 return YamlConfig{std::move (var_data), {}, Mode::kSecure };
106124 }
125+ }
107126
108- auto env_value = GetFromEnvByKey (key, yaml_, mode_);
109- if (env_value) {
110- // Strip substitutions off to disallow nested substitutions
111- return YamlConfig{std::move (*env_value), {}, Mode::kSecure };
112- }
127+ if (!value.IsMissing () && !is_substitution) {
128+ return YamlConfig{std::move (value), config_vars_, mode_};
129+ }
113130
131+ const auto env_name = yaml_[GetEnvName (key)];
132+ auto env_value = GetFromEnvImpl (env_name, mode_);
133+ if (env_value) {
134+ // Strip substitutions off to disallow nested substitutions
135+ return YamlConfig{std::move (*env_value), {}, Mode::kSecure };
136+ }
137+
138+ const auto file_name = yaml_[GetFileName (key)];
139+ auto file_value = GetFromFileImpl (file_name, mode_);
140+ if (file_value) {
141+ // Strip substitutions off to disallow nested substitutions
142+ return YamlConfig{std::move (*file_value), {}, Mode::kSecure };
143+ }
144+
145+ if (is_substitution || !env_name.IsMissing () || !file_name.IsMissing ()) {
114146 const auto fallback_name = GetFallbackName (key);
115147 if (yaml_.HasMember (fallback_name)) {
116148 LOG_INFO () << " using fallback value for '" << key << ' \' ' ;
117149 // Strip substitutions off to disallow nested substitutions
118150 return YamlConfig{yaml_[fallback_name], {}, Mode::kSecure };
119151 }
120-
121- // Avoid parsing $substitution as a string
122- return MakeMissingConfig (*this , key);
123- }
124-
125- if (value.IsMissing ()) {
126- auto env_value = GetFromEnvByKey (key, yaml_, mode_);
127- if (env_value) {
128- // Strip substitutions off to disallow nested substitutions
129- return YamlConfig{std::move (*env_value), {}, Mode::kSecure };
130- }
131152 }
132153
133- return YamlConfig{ std::move (value), config_vars_, mode_} ;
154+ return MakeMissingConfig (* this , key) ;
134155}
135156
136157YamlConfig YamlConfig::operator [](size_t index) const {
0 commit comments