mirror of
https://github.com/facebook/sapling.git
synced 2024-10-05 14:28:17 +03:00
6f9369f38f
Summary: This sets up a new `TomlFileConfigSource` for `edenfs_cfgr.rc`. Note that this config will have priority over configs set by chef, but won't override user or command line configs. Reviewed By: xavierd Differential Revision: D50372549 fbshipit-source-id: 0ded035d88b2ece93f5aae7cdca6a911bee88c8a
253 lines
7.3 KiB
C++
253 lines
7.3 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This software may be used and distributed according to the terms of the
|
|
* GNU General Public License version 2.
|
|
*/
|
|
|
|
#include "eden/fs/config/EdenConfig.h"
|
|
|
|
#include <array>
|
|
#include <optional>
|
|
|
|
#include <boost/filesystem.hpp>
|
|
#include <folly/MapUtil.h>
|
|
#include <folly/String.h>
|
|
#include <folly/logging/xlog.h>
|
|
#include <thrift/lib/cpp/util/EnumUtils.h>
|
|
|
|
#include "eden/fs/utils/Bug.h"
|
|
#include "eden/fs/utils/EnumValue.h"
|
|
|
|
namespace facebook::eden {
|
|
|
|
namespace {
|
|
|
|
constexpr PathComponentPiece kDefaultUserIgnoreFile{".edenignore"};
|
|
constexpr PathComponentPiece kDefaultSystemIgnoreFile{"ignore"};
|
|
constexpr PathComponentPiece kDefaultEdenDirectory{".eden"};
|
|
|
|
std::pair<std::string_view, std::string_view> parseKey(
|
|
std::string_view fullKey) {
|
|
auto pos = fullKey.find(":");
|
|
if (pos == std::string::npos) {
|
|
EDEN_BUG() << "ConfigSetting key must contain a colon: " << fullKey;
|
|
}
|
|
|
|
std::string_view section{fullKey.data(), pos};
|
|
std::string_view key = fullKey.substr(pos + 1);
|
|
|
|
// Avoid use of locales. Standardize on - instead of _.
|
|
auto isConfigChar = [](char c) {
|
|
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') || c == '-';
|
|
};
|
|
|
|
for (char c : section) {
|
|
if (!isConfigChar(c)) {
|
|
EDEN_BUG() << "not a valid section name: " << fullKey;
|
|
}
|
|
}
|
|
|
|
return {section, key};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const AbsolutePath kUnspecifiedDefault{};
|
|
|
|
// TODO: move this to TestMount.h or something.
|
|
|
|
std::shared_ptr<EdenConfig> EdenConfig::createTestEdenConfig() {
|
|
ConfigVariables subst;
|
|
subst["HOME"] = "/tmp";
|
|
subst["USER"] = "testuser";
|
|
subst["USER_ID"] = "0";
|
|
|
|
return std::make_unique<EdenConfig>(
|
|
std::move(subst),
|
|
/*userHomePath=*/canonicalPath("/tmp"),
|
|
/*systemConfigDir=*/canonicalPath("/tmp"),
|
|
SourceVector{
|
|
std::make_shared<NullConfigSource>(ConfigSourceType::SystemConfig),
|
|
std::make_shared<NullConfigSource>(ConfigSourceType::Dynamic),
|
|
std::make_shared<NullConfigSource>(ConfigSourceType::UserConfig)});
|
|
}
|
|
|
|
std::string EdenConfig::toString(ConfigSourceType cs) const {
|
|
switch (cs) {
|
|
case ConfigSourceType::Default:
|
|
return "default";
|
|
case ConfigSourceType::Dynamic:
|
|
case ConfigSourceType::SystemConfig:
|
|
case ConfigSourceType::UserConfig:
|
|
if (const auto& source = configSources_[ConfigSettingBase::getIdx(cs)]) {
|
|
return source->getSourcePath();
|
|
} else {
|
|
return "";
|
|
}
|
|
case ConfigSourceType::CommandLine:
|
|
return "command-line";
|
|
}
|
|
throwf<std::invalid_argument>(
|
|
"invalid config source value: {}", enumValue(cs));
|
|
}
|
|
|
|
EdenConfigData EdenConfig::toThriftConfigData() const {
|
|
EdenConfigData result;
|
|
for (const auto& [sectionName, section] : configMap_) {
|
|
for (const auto& [key, setting] : section) {
|
|
auto keyName = fmt::format("{}:{}", sectionName, key);
|
|
auto& configValue = result.values_ref()[keyName];
|
|
configValue.parsedValue() = setting->getStringValue();
|
|
configValue.sourceType() = setting->getSourceType();
|
|
configValue.sourcePath() = toSourcePath(setting->getSourceType());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string EdenConfig::toSourcePath(ConfigSourceType cs) const {
|
|
if (const auto& source = configSources_[ConfigSettingBase::getIdx(cs)]) {
|
|
return source->getSourcePath();
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
EdenConfig::EdenConfig(
|
|
ConfigVariables substitutions,
|
|
AbsolutePathPiece userHomePath,
|
|
AbsolutePathPiece systemConfigDir,
|
|
SourceVector configSources)
|
|
: substitutions_{std::make_shared<ConfigVariables>(
|
|
std::move(substitutions))},
|
|
systemConfigDir_{std::move(systemConfigDir)} {
|
|
// Force set defaults that require passed arguments
|
|
edenDir.setValue(
|
|
userHomePath + kDefaultEdenDirectory, ConfigSourceType::Default, true);
|
|
userIgnoreFile.setValue(
|
|
userHomePath + kDefaultUserIgnoreFile, ConfigSourceType::Default, true);
|
|
systemIgnoreFile.setValue(
|
|
systemConfigDir_ + kDefaultSystemIgnoreFile,
|
|
ConfigSourceType::Default,
|
|
true);
|
|
|
|
for (auto& source : configSources) {
|
|
auto type = source->getSourceType();
|
|
auto index = ConfigSettingBase::getIdx(type);
|
|
XCHECK_NE(ConfigSourceType::Default, type)
|
|
<< "May not provide a ConfigSource of type Default. Default is prepopulated.";
|
|
XCHECK(!configSources_[index])
|
|
<< "Multiple ConfigSources of the same type ("
|
|
<< apache::thrift::util::enumNameSafe(type) << ") are disallowed.";
|
|
configSources_[index] = std::move(source);
|
|
}
|
|
|
|
reload();
|
|
}
|
|
|
|
EdenConfig::EdenConfig(const EdenConfig& source) {
|
|
substitutions_ = source.substitutions_;
|
|
configSources_ = source.configSources_;
|
|
|
|
// Copy each ConfigSetting from source.
|
|
for (const auto& [section, sectionMap] : source.configMap_) {
|
|
for (const auto& [key, value] : sectionMap) {
|
|
configMap_[section][key]->copyFrom(*value);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::optional<std::string> EdenConfig::getValueByFullKey(
|
|
std::string_view configKey) const {
|
|
// Throws if the config key is ill-formed.
|
|
auto [sectionKey, entryKey] = parseKey(configKey);
|
|
|
|
if (auto* entry = folly::get_ptr(
|
|
configMap_, std::string{sectionKey}, std::string{entryKey})) {
|
|
return (*entry)->getStringValue();
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
void EdenConfig::reload() {
|
|
for (const auto& source : configSources_) {
|
|
if (source) {
|
|
source->reload(*substitutions_, configMap_);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<const EdenConfig> EdenConfig::maybeReload() const {
|
|
std::shared_ptr<EdenConfig> newConfig;
|
|
|
|
for (const auto& source : configSources_) {
|
|
if (source) {
|
|
if (auto reason = source->shouldReload()) {
|
|
XLOGF(DBG3, "Reloading {} because {}", source->getSourcePath(), reason);
|
|
|
|
if (!newConfig) {
|
|
newConfig = std::make_shared<EdenConfig>(*this);
|
|
}
|
|
newConfig->clearAll(source->getSourceType());
|
|
source->reload(*newConfig->substitutions_, newConfig->configMap_);
|
|
}
|
|
}
|
|
}
|
|
|
|
return newConfig;
|
|
}
|
|
|
|
void EdenConfig::registerConfiguration(ConfigSettingBase* configSetting) {
|
|
std::string_view fullKeyStr = configSetting->getConfigKey();
|
|
auto [section, key] = parseKey(fullKeyStr);
|
|
|
|
auto& keyMap = configMap_[std::string{section}];
|
|
keyMap[std::string{key}] = configSetting;
|
|
}
|
|
|
|
const std::optional<AbsolutePath> EdenConfig::getClientCertificate() const {
|
|
// return the first cert path that exists
|
|
for (auto& cert : clientCertificateLocations.getValue()) {
|
|
auto certPath =
|
|
FieldConverter<AbsolutePath>{}.fromString(cert, *substitutions_);
|
|
|
|
if (certPath.hasError()) {
|
|
XLOG(
|
|
DBG2,
|
|
"Unable to resolve possible cert location, skipping : ",
|
|
certPath.error());
|
|
continue;
|
|
}
|
|
|
|
if (boost::filesystem::exists(certPath.value().value())) {
|
|
return certPath.value();
|
|
}
|
|
|
|
XLOG(
|
|
DBG2,
|
|
"Cert does not exist on disk, skipping : ",
|
|
cert,
|
|
" (resolved as: ",
|
|
certPath.value().value(),
|
|
")");
|
|
}
|
|
auto singleCertificateConfig = clientCertificate.getValue();
|
|
if (singleCertificateConfig != kUnspecifiedDefault) {
|
|
return singleCertificateConfig;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
void EdenConfig::clearAll(ConfigSourceType configSource) {
|
|
for (const auto& sectionEntry : configMap_) {
|
|
for (auto& keyEntry : sectionEntry.second) {
|
|
keyEntry.second->clearValue(configSource);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace facebook::eden
|