This is an automated email from the ASF dual-hosted git repository.

dmeden pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 13c4a9f6fc traffic_ctl: Display `YAML` format when `--records` option 
is set (instead of the legacy `records.config` style) (#11650)
13c4a9f6fc is described below

commit 13c4a9f6fc0e6acfe3c4d41e52a1d207a423b67b
Author: Damian Meden <[email protected]>
AuthorDate: Wed Aug 14 11:08:06 2024 +0200

    traffic_ctl: Display `YAML` format when `--records` option is set (instead 
of the legacy `records.config` style) (#11650)
    
    traffic_ctl: Display YAML format when --records option is set.
    It make sense to display YAML instead of the legacy format in case this
    out wants to be used as input for ATS.
    A new bunch of function are added only for traffic_ctl to build up yaml
    from the record's name.
    This code uses YAML::Emitter so we can add comments to the yaml doc,
    this is essential as we are including things like defaults values in
    Each field, this can only be accomplished with Emitters.
---
 doc/appendices/command-line/traffic_ctl.en.rst |   7 +
 include/records/RecYAMLDecoder.h               |   4 +-
 src/traffic_ctl/CMakeLists.txt                 |   2 +-
 src/traffic_ctl/CtrlPrinters.cc                |  21 ++-
 src/traffic_ctl/PrintUtils.cc                  | 206 +++++++++++++++++++++++++
 src/traffic_ctl/PrintUtils.h                   |  45 +++++-
 src/traffic_ctl/traffic_ctl.cc                 |   8 +-
 7 files changed, 277 insertions(+), 16 deletions(-)

diff --git a/doc/appendices/command-line/traffic_ctl.en.rst 
b/doc/appendices/command-line/traffic_ctl.en.rst
index b50b188486..fdc4e890dd 100644
--- a/doc/appendices/command-line/traffic_ctl.en.rst
+++ b/doc/appendices/command-line/traffic_ctl.en.rst
@@ -129,6 +129,13 @@ traffic_ctl alarm
 traffic_ctl config
 ------------------
 
+   Manipulate configuration records.
+
+   .. program:: traffic_ctl config
+   .. option:: --records
+
+   Display the config output in YAML format. This out can be used directly 
into ATS if needed.
+
 .. program:: traffic_ctl config
 .. option:: defaults [--records]
 
diff --git a/include/records/RecYAMLDecoder.h b/include/records/RecYAMLDecoder.h
index bbd4289675..0392ec356d 100644
--- a/include/records/RecYAMLDecoder.h
+++ b/include/records/RecYAMLDecoder.h
@@ -56,9 +56,9 @@ swoc::Errata RecYAMLConfigFileParse(const char *path, 
RecYAMLNodeHandler handler
 /// @brief This function parses the YAML root node ("records") and convert 
each field
 ///        into a record style object.
 ///
-/// As we keep the internal records without a change we should rebuild each 
records name
+/// As we keep the internal records without a change we should rebuild each 
record name
 /// from a YAML structure, this function parses the yaml and while walking 
down to the scalar
-/// node it builds the record name, this where the handler gets called.
+/// node it builds up the record name, once finishes the handler gets called.
 ///
 /// Example:
 ///
diff --git a/src/traffic_ctl/CMakeLists.txt b/src/traffic_ctl/CMakeLists.txt
index 6962f30d3a..671789b852 100644
--- a/src/traffic_ctl/CMakeLists.txt
+++ b/src/traffic_ctl/CMakeLists.txt
@@ -16,7 +16,7 @@
 #######################
 
 add_executable(
-  traffic_ctl traffic_ctl.cc CtrlCommands.cc CtrlPrinters.cc 
FileConfigCommand.cc
+  traffic_ctl traffic_ctl.cc CtrlCommands.cc CtrlPrinters.cc 
FileConfigCommand.cc PrintUtils.cc
               ${CMAKE_SOURCE_DIR}/src/shared/rpc/IPCSocketClient.cc
 )
 
diff --git a/src/traffic_ctl/CtrlPrinters.cc b/src/traffic_ctl/CtrlPrinters.cc
index 1e7073e036..860af4787c 100644
--- a/src/traffic_ctl/CtrlPrinters.cc
+++ b/src/traffic_ctl/CtrlPrinters.cc
@@ -105,6 +105,8 @@ RecordPrinter::write_output(YAML::Node const &result)
 {
   auto const &response = result.as<shared::rpc::RecordLookUpResponse>();
   std::string text;
+  // if yaml is needed
+  RecNameToYaml::RecInfoList recordList;
   for (auto &&recordInfo : response.recordList) {
     if (!recordInfo.registered) {
       std::cout << recordInfo.name
@@ -114,10 +116,13 @@ RecordPrinter::write_output(YAML::Node const &result)
     if (!_printAsRecords) {
       std::cout << recordInfo.name << ": " << recordInfo.currentValue << '\n';
     } else {
-      std::cout << swoc::bwprint(text, "{} {} {} {} # default: {}\n", 
rec_labelof(recordInfo.rclass), recordInfo.name,
-                                 recordInfo.dataType, recordInfo.currentValue, 
recordInfo.defaultValue);
+      recordList.push_back(std::make_tuple(recordInfo.name, 
recordInfo.currentValue, recordInfo.defaultValue));
     }
   }
+
+  if (_printAsRecords && recordList.size() > 0) {
+    std::cout << RecNameToYaml{recordList, WithoutDefaults}.string() << '\n';
+  }
   // we print errors if found.
   print_record_error_list(response.errorList);
 }
@@ -135,8 +140,11 @@ MetricRecordPrinter::write_output(YAML::Node const &result)
 void
 DiffConfigPrinter::write_output(YAML::Node const &result)
 {
-  std::string text;
   auto        response = result.as<shared::rpc::RecordLookUpResponse>();
+  std::string text;
+
+  // if yaml is needed
+  RecNameToYaml::RecInfoList recordList;
   for (auto &&recordInfo : response.recordList) {
     auto const &currentValue = recordInfo.currentValue;
     auto const &defaultValue = recordInfo.defaultValue;
@@ -147,11 +155,14 @@ DiffConfigPrinter::write_output(YAML::Node const &result)
         std::cout << swoc::bwprint(text, "\tCurrent Value: {}\n", 
currentValue);
         std::cout << swoc::bwprint(text, "\tDefault Value: {}\n", 
defaultValue);
       } else {
-        std::cout << swoc::bwprint(text, "{} {} {} {} # default: {}\n", 
rec_labelof(recordInfo.rclass), recordInfo.name,
-                                   recordInfo.dataType, 
recordInfo.currentValue, recordInfo.defaultValue);
+        recordList.push_back(std::make_tuple(recordInfo.name, 
recordInfo.currentValue, recordInfo.defaultValue));
       }
     }
   }
+
+  if (_printAsRecords && recordList.size() > 0) {
+    std::cout << RecNameToYaml{recordList, WithDefaults}.string() << '\n';
+  }
 }
 
//------------------------------------------------------------------------------------------------------------------------------------
 void
diff --git a/src/traffic_ctl/PrintUtils.cc b/src/traffic_ctl/PrintUtils.cc
new file mode 100644
index 0000000000..c0497f5a1c
--- /dev/null
+++ b/src/traffic_ctl/PrintUtils.cc
@@ -0,0 +1,206 @@
+/** @file
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "PrintUtils.h"
+#include <swoc/bwf_base.h>
+
+namespace
+{
+void
+YAML_begin_map(YAML::Emitter &doc)
+{
+  doc << YAML::BeginMap;
+}
+void
+YAML_end_map(YAML::Emitter &doc)
+{
+  doc << YAML::EndMap;
+}
+void
+YAML_add_key(YAML::Emitter &doc, std::string key)
+{
+  doc << YAML::Key << key;
+}
+void
+YAML_add_value(YAML::Emitter &doc, std::string value)
+{
+  doc << YAML::Value << value;
+}
+void
+YAML_add_comment(YAML::Emitter &doc, std::string comment, const char *fmt = 
"default: {}")
+{
+  std::string txt;
+  doc << YAML::Comment(swoc::bwprint(txt, fmt, comment));
+}
+void
+remove_legacy_config_prefix(swoc::TextView &rec_name)
+{
+  constexpr std::array<swoc::TextView, 3> Prefixes{"proxy.config.", 
"local.config.", "proxy.node."};
+
+  for (auto &prefix : Prefixes) {
+    if (rec_name.starts_with(prefix)) {
+      rec_name.remove_prefix(prefix.size());
+      break;
+    }
+  }
+}
+} // namespace
+
+RecNameToYaml::RecNameToYaml(RecInfoList records, bool p_include_defaults) : 
include_defaults(p_include_defaults)
+{
+  if (records.size() == 0) {
+    return;
+  }
+
+  RecList recs;
+
+  std::sort(records.begin(), records.end(), [](const auto &lhs, const auto 
&rhs) {
+    // by the name.
+    return std::get<0>(lhs) < std::get<0>(rhs);
+  });
+
+  // remove duplicated
+  records.erase(std::unique(records.begin(), records.end(),
+                            [](const auto &lhs, const auto &rhs) { return 
std::get<0>(lhs) == std::get<0>(rhs); }),
+                records.end());
+
+  // work from this list of records.
+  for (auto const &e : records) {
+    recs.push_back(std::make_pair(e, false));
+  }
+
+  build_yaml(recs);
+}
+
+RecNameToYaml::RecordsMatchTracker
+RecNameToYaml::find_all_keys_with_prefix(swoc::TextView prefix, 
RecNameToYaml::RecList &vars)
+{
+  RecordsMatchTracker ret;
+  for (auto &p : vars) {
+    if (p.second) {
+      continue;
+    }
+
+    swoc::TextView name{std::get<0>(p.first)};
+    remove_legacy_config_prefix(name);
+
+    // A var name could also start with the same prefix and not being a field
+    // like prefix=logfile and field=logfile_perm so we need to make sure we 
are either the end of the field
+    // or the end of the level(with another .)
+    if (!name.starts_with(prefix) || name.at(prefix.size()) != '.') {
+      continue;
+    }
+    ret.push_back({p.first, p.second});
+  }
+  return ret;
+}
+
+void
+RecNameToYaml::process_var_from_prefix(std::string prefix, 
RecNameToYaml::RecList &vars, bool &in_a_map)
+{
+  auto keys = find_all_keys_with_prefix(prefix, vars);
+
+  for (auto &[rec, proccesed] : keys) {
+    if (proccesed) {
+      continue;
+    }
+
+    swoc::TextView name{std::get<0>(rec)};
+    remove_legacy_config_prefix(name); // if no prefix to remove, it will 
ignore it.
+    name.remove_prefix(prefix.size() + 1);
+
+    auto const n = name.find(".");
+
+    if (n == std::string_view::npos) {
+      if (!in_a_map) {
+        YAML_begin_map(doc); // Value map.
+        in_a_map = true;
+      }
+
+      YAML_add_key(doc, std::string(name));
+
+      auto const &rec_current_value = std::get<1>(rec);
+      auto const &rec_default_value = std::get<2>(rec);
+      YAML_add_value(doc, rec_current_value);
+      if (include_defaults && !rec_default_value.empty()) {
+        YAML_add_comment(doc, rec_default_value);
+      }
+      proccesed = true;
+    } else {
+      auto key = std::string{name.substr(0, n).data(), name.substr(0, 
n).size()};
+      YAML_add_key(doc, key);
+
+      std::string nprefix;
+      nprefix.reserve(prefix.size() + 1 + key.size());
+      nprefix.append(prefix);
+      nprefix.append(".");
+      nprefix.append(key);
+
+      in_a_map = true; // no need for a map down the line if we will just add 
k,v.
+      YAML_begin_map(doc);
+      process_var_from_prefix(nprefix, vars, in_a_map);
+      YAML_end_map(doc);
+    }
+  }
+}
+
+void
+RecNameToYaml::build_yaml(RecNameToYaml::RecList vars)
+{
+  YAML_begin_map(doc);
+  YAML_add_key(doc, "records");
+  YAML_begin_map(doc); // content
+
+  // Just work from every passed records and walk down every field to build up
+  // each node from it.
+  // It may work just to use a single loop, but this seems easier to follow, 
it will
+  // not process the same records twice because once one is done, the 
processed mark
+  // will be set and then ignored.
+  for (auto &[rec, processed] : vars) {
+    if (processed) {
+      continue;
+    }
+
+    swoc::TextView name(std::get<0>(rec));
+    remove_legacy_config_prefix(name);
+    auto const  n = name.find('.');
+    std::string prefix{name.substr(0, n == std::string_view::npos ? 
name.size() : n)};
+    YAML_add_key(doc, prefix);
+    if (n == std::string_view::npos) {
+      auto const &rec_current_value = std::get<1>(rec);
+      auto const &rec_default_value = std::get<2>(rec);
+      // Just set this field right here and move on, no need to do it later.
+      YAML_add_value(doc, rec_current_value);
+      if (include_defaults && !rec_default_value.empty()) {
+        YAML_add_comment(doc, rec_default_value);
+      }
+      processed = true;
+      continue;
+    }
+    bool in_a_map = true;
+    YAML_begin_map(doc);
+    process_var_from_prefix(prefix, vars, in_a_map);
+    YAML_end_map(doc);
+  }
+
+  YAML_end_map(doc); // content
+  YAML_end_map(doc);
+}
diff --git a/src/traffic_ctl/PrintUtils.h b/src/traffic_ctl/PrintUtils.h
index c5991fe138..260ecd699a 100644
--- a/src/traffic_ctl/PrintUtils.h
+++ b/src/traffic_ctl/PrintUtils.h
@@ -19,8 +19,13 @@
 */
 #pragma once
 
+#include <vector>
+#include <utility>
+#include <yaml-cpp/yaml.h>
+#include <swoc/TextView.h>
+
 // Record access control, indexed by RecAccessT.
-static const char *
+[[maybe_unused]] static const char *
 rec_accessof(int rec_access)
 {
   switch (rec_access) {
@@ -33,7 +38,8 @@ rec_accessof(int rec_access)
     return "default";
   }
 }
-static const char *
+
+[[maybe_unused]] static const char *
 rec_updateof(int rec_updatetype)
 {
   switch (rec_updatetype) {
@@ -64,7 +70,8 @@ rec_checkof(int rec_checktype)
     return "none";
   }
 }
-static const char *
+
+[[maybe_unused]] static const char *
 rec_labelof(int rec_class)
 {
   switch (rec_class) {
@@ -76,7 +83,8 @@ rec_labelof(int rec_class)
     return "unknown";
   }
 }
-static const char *
+
+[[maybe_unused]] static const char *
 rec_sourceof(int rec_source)
 {
   switch (rec_source) {
@@ -92,3 +100,32 @@ rec_sourceof(int rec_source)
     return "unknown";
   }
 }
+
+[[maybe_unused]] static bool WithDefaults{true};
+[[maybe_unused]] static bool WithoutDefaults{false};
+
+class RecNameToYaml
+{
+  using RecordInfo          = std::tuple<std::string /*name*/, std::string 
/*value*/, std::string /*def_val*/>;
+  using RecList             = std::vector<std::pair<RecordInfo, bool /*keep 
track if a record was already converted.*/>>;
+  using RecordsMatchTracker = std::vector<std::pair<RecordInfo &, bool &>>;
+
+  RecordsMatchTracker find_all_keys_with_prefix(swoc::TextView prefix, RecList 
&vars);
+  void                process_var_from_prefix(std::string prefix, RecList 
&vars, bool &in_a_map);
+  void                build_yaml(RecList vars);
+
+public:
+  using RecInfoList = std::vector<RecordInfo>;
+  RecNameToYaml()   = default;
+  RecNameToYaml(RecInfoList recs, bool include_defaults);
+
+  std::string
+  string() const
+  {
+    return doc.good() ? doc.c_str() : "";
+  }
+
+private:
+  bool          include_defaults{false};
+  YAML::Emitter doc;
+};
diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc
index a58725e7da..9937cfbdc4 100644
--- a/src/traffic_ctl/traffic_ctl.cc
+++ b/src/traffic_ctl/traffic_ctl.cc
@@ -94,24 +94,24 @@ main([[maybe_unused]] int argc, const char **argv)
   // config commands
   config_command.add_command("defaults", "Show default information 
configuration values", [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config defaults [OPTIONS]")
-    .add_option("--records", "", "Emit output in records.config format");
+    .add_option("--records", "", "Emit output in YAML format");
   config_command
     .add_command("describe", "Show detailed information about configuration 
values", "", MORE_THAN_ONE_ARG_N,
                  [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config describe RECORD [RECORD ...]");
   config_command.add_command("diff", "Show non-default configuration values", 
[&]() { command->execute(); })
     .add_example_usage("traffic_ctl config diff [OPTIONS]")
-    .add_option("--records", "", "Emit output in records.config format");
+    .add_option("--records", "", "Emit output in YAML format");
   config_command.add_command("get", "Get one or more configuration values", 
"", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config get [OPTIONS] RECORD [RECORD ...]")
     .add_option("--cold", "-c",
                 "Save the value in a configuration file. This does not save 
the value in TS. Local file change only",
                 "TS_RECORD_YAML", MORE_THAN_ZERO_ARG_N)
-    .add_option("--records", "", "Emit output in records.config format");
+    .add_option("--records", "", "Emit output in YAML format");
   config_command
     .add_command("match", "Get configuration matching a regular expression", 
"", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config match [OPTIONS] REGEX [REGEX ...]")
-    .add_option("--records", "", "Emit output in records.config format");
+    .add_option("--records", "", "Emit output in YAML format");
   config_command.add_command("reload", "Request a configuration reload", [&]() 
{ command->execute(); })
     .add_example_usage("traffic_ctl config reload");
   config_command.add_command("status", "Check the configuration status", [&]() 
{ command->execute(); })

Reply via email to