diff --git a/lib/puppet/face/config.rb b/lib/puppet/face/config.rb index 3100426af..d1ff86672 100644 --- a/lib/puppet/face/config.rb +++ b/lib/puppet/face/config.rb @@ -1,75 +1,78 @@ require 'puppet/face' require 'puppet/settings/ini_file' Puppet::Face.define(:config, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Interact with Puppet's configuration options." action(:print) do summary "Examine Puppet's current configuration settings." arguments "(all | [ ...]" returns <<-'EOT' A single value when called with one config setting, and a list of settings and values when called with multiple options or "all." EOT description <<-'EOT' Prints the value of a single configuration option or a list of configuration options. This action is an alternate interface to the information available with `puppet --configprint`. EOT notes <<-'EOT' By default, this action reads the configuration in agent mode. Use the '--run_mode' and '--environment' flags to examine other configuration domains. EOT examples <<-'EOT' Get puppet's runfile directory: $ puppet config print rundir Get a list of important directories from the master's config: $ puppet config print all --run_mode master | grep -E "(path|dir)" EOT when_invoked do |*args| args.pop args = [ "all" ] if args.empty? Puppet.settings[:configprint] = args.join(",") Puppet.settings.print_config_options nil end end action(:set) do summary "Set Puppet's configuration settings." arguments "[setting_name] [setting_value]" description <<-'EOT' Update values in the `puppet.conf` configuration file. EOT examples <<-'EOT' Set puppet's runfile directory: $ puppet config set rundir /var/run/puppet EOT - when_invoked do |*args| - name, value = args + option "--section SECTION_NAME" do + default_to { "main" } + summary "The section of the configuration file to change." + end + when_invoked do |name, value, options| file = Puppet::FileSystem::File.new(Puppet.settings.which_configuration_file) file.touch file.open(nil, 'r+') do |file| Puppet::Settings::IniFile.update(file) do |config| - config.set(name, value) + config.set(options[:section], name, value) end end nil end end end diff --git a/lib/puppet/settings/ini_file.rb b/lib/puppet/settings/ini_file.rb index 5e1aebf29..e99dce4ba 100644 --- a/lib/puppet/settings/ini_file.rb +++ b/lib/puppet/settings/ini_file.rb @@ -1,94 +1,110 @@ # @api private class Puppet::Settings::IniFile + DEFAULT_SECTION_NAME = "main" + def self.update(config_fh, &block) config = parse(config_fh) manipulator = Manipulator.new(config) yield manipulator config.write(config_fh) end def self.parse(config_fh) config = new config_fh.each_line do |line| case line when /^(\s*)\[(\w+)\](\s*)$/ config << SectionLine.new($1, $2, $3) when /^(\s*)(\w+)(\s*=\s*)(.*?)(\s*)$/ config << SettingLine.new($1, $2, $3, $4, $5) else config << Line.new(line) end end config end def initialize @lines = [] end def <<(line) @lines << line end def each(&block) @lines.each(&block) end - def setting(name) - @lines.find do |line| + def setting(section, name) + settings_in(section).find do |line| line.is_a?(SettingLine) && line.name == name end end + def settings_in(section) + section_lines = [] + current_section = DEFAULT_SECTION_NAME + @lines.each do |line| + if line.is_a?(SectionLine) + current_section = line.name + elsif current_section == section + section_lines << line + end + end + + section_lines + end + def write(fh) fh.truncate(0) fh.rewind @lines.each do |line| line.write(fh) end fh.flush end class Manipulator def initialize(config) @config = config end - def set(name, value) - setting = @config.setting(name) + def set(section, name, value) + setting = @config.setting(section, name) if setting setting.value = value else + @config << SectionLine.new("", section, "") @config << SettingLine.new("", name, "=", value, "") end end end Line = Struct.new(:text) do def write(fh) fh.puts(text) end end SettingLine = Struct.new(:prefix, :name, :infix, :value, :suffix) do def write(fh) fh.write(prefix) fh.write(name) fh.write(infix) fh.write(value) fh.puts(suffix) end end SectionLine = Struct.new(:prefix, :name, :suffix) do def write(fh) fh.write(prefix) fh.write("[") fh.write(name) fh.write("]") fh.puts(suffix) end end - end diff --git a/spec/unit/settings/ini_file_spec.rb b/spec/unit/settings/ini_file_spec.rb index d11e010fa..aa5beca18 100644 --- a/spec/unit/settings/ini_file_spec.rb +++ b/spec/unit/settings/ini_file_spec.rb @@ -1,63 +1,122 @@ require 'spec_helper' require 'stringio' require 'puppet/settings/ini_file' describe Puppet::Settings::IniFile do it "preserves the file when no changes are made" do original_config = <<-CONFIG # comment [section] name = value CONFIG config_fh = a_config_file_containing(original_config) Puppet::Settings::IniFile.update(config_fh) do; end expect(config_fh.string).to eq original_config end it "adds a set name and value to an empty file" do config_fh = a_config_file_containing("") Puppet::Settings::IniFile.update(config_fh) do |config| - config.set("name", "value") + config.set("the_section", "name", "value") end - expect(config_fh.string).to eq "name=value\n" + expect(config_fh.string).to eq "[the_section]\nname=value\n" end it "preserves comments when writing a new name and value" do config_fh = a_config_file_containing("# this is a comment") Puppet::Settings::IniFile.update(config_fh) do |config| - config.set("name", "value") + config.set("the_section", "name", "value") end - expect(config_fh.string).to eq "# this is a comment\nname=value\n" + expect(config_fh.string).to eq "# this is a comment\n[the_section]\nname=value\n" end it "updates existing names and values in place" do config_fh = a_config_file_containing(<<-CONFIG) # this is the preceeding comment [section] name = original value # this is the trailing comment CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| - config.set("name", "changed value") + config.set("section", "name", "changed value") end expect(config_fh.string).to eq <<-CONFIG # this is the preceeding comment [section] name = changed value # this is the trailing comment CONFIG end + it "updates only the value in the selected section" do + config_fh = a_config_file_containing(<<-CONFIG) + [other_section] + name = does not change + [section] + name = original value + CONFIG + + Puppet::Settings::IniFile.update(config_fh) do |config| + config.set("section", "name", "changed value") + end + + expect(config_fh.string).to eq <<-CONFIG + [other_section] + name = does not change + [section] + name = changed value + CONFIG + end + + it "considers settings outside a section to be in section 'main'" do + config_fh = a_config_file_containing(<<-CONFIG) + name = original value + CONFIG + + Puppet::Settings::IniFile.update(config_fh) do |config| + config.set("main", "name", "changed value") + end + + expect(config_fh.string).to eq <<-CONFIG + name = changed value + CONFIG + end + + it "finds settings when the section is split up" do + config_fh = a_config_file_containing(<<-CONFIG) + [section] + name = original value + [different] + name = other value + [section] + other_name = different original value + CONFIG + + Puppet::Settings::IniFile.update(config_fh) do |config| + config.set("section", "name", "changed value") + config.set("section", "other_name", "other changed value") + end + + expect(config_fh.string).to eq <<-CONFIG + [section] + name = changed value + [different] + name = other value + [section] + other_name = other changed value + CONFIG + end + def a_config_file_containing(text) StringIO.new(text) end end