diff options
author | Tim Smith <tsmith@chef.io> | 2018-02-19 09:30:17 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-19 09:30:17 -0800 |
commit | 9a2ba654492fd53f05f3f363badbebb26f7bf70f (patch) | |
tree | 744f8ca2d1c01baeb2a371eff61e6b30201e5174 | |
parent | ab46256c6119dd5a2394e424c2b20dcd1f3ccd47 (diff) | |
parent | e8f01e79670b43e9fa49955156de33154c741d2c (diff) | |
download | chef-9a2ba654492fd53f05f3f363badbebb26f7bf70f.tar.gz |
Merge pull request #6795 from chef/hostname_resource
Add hostname resource from chef_hostname cookbook
-rw-r--r-- | lib/chef/resource/hostname.rb | 252 | ||||
-rw-r--r-- | lib/chef/resources.rb | 1 | ||||
-rw-r--r-- | spec/unit/resource/hostname_spec.rb | 43 |
3 files changed, 296 insertions, 0 deletions
diff --git a/lib/chef/resource/hostname.rb b/lib/chef/resource/hostname.rb new file mode 100644 index 0000000000..16986c09a3 --- /dev/null +++ b/lib/chef/resource/hostname.rb @@ -0,0 +1,252 @@ +class Chef + class Resource + # Sets the hostname and updates /etc/hosts on *nix systems + # @since 14.0.0 + class Hostname < Chef::Resource + provides :hostname + resource_name :hostname + + description "Sets the systems hostname, ensures that reboot will preserve the hostname, and re-runs the ohai plugin so the hostname will be available in subsequent cookbooks." + introduced "14.0" + + property :hostname, + String, + description: "The hostname if different than the resource's name", + name_property: true + + property :compile_time, + [ TrueClass, FalseClass ], + description: "Should the resource run at compile time or not.", + default: true + + property :ipaddress, + String, + description: "The ip address to use when configuring the hosts file", + default: lazy { node["ipaddress"] } + + property :aliases, + [ Array, nil ], + description: "An array of hostname aliases to use when configuring the hosts file", + default: nil + + property :windows_reboot, + [ TrueClass, FalseClass ], + description: "Should Windows nodes be rebooted upon changing the name so it can take effect", + default: true + + action_class do + def append_replacing_matching_lines(path, regex, string) + text = IO.read(path).split("\n") + text.reject! { |s| s =~ regex } + text += [ string ] + file path do + content text.join("\n") + "\n" + owner "root" + group node["root_group"] + mode "0644" + not_if { IO.read(path).split("\n").include?(string) } + end + end + + # read in the xml file used by Ec2ConfigService and update the Ec2SetComputerName + # setting to disable updating the computer name so we don't revert our change on reboot + # @return [String] + def updated_ec2_config_xml + begin + require "rexml/document" + config_file = 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml' + config = REXML::Document.new(::File.read(config_file)) + # find an element named State with a sibling element whose value is Ec2SetComputerName + REXML::XPath.each(config, "//Plugin/State[../Name/text() = 'Ec2SetComputerName']") do |element| + element.text = "Disabled" + end + rescue + return "" + end + config.to_s + end + end + + action :set do + description "Sets the node's hostname" + + ohai "reload hostname" do + plugin "hostname" + action :nothing + end + + if node["platform_family"] != "windows" + # set the hostname via /bin/hostname + declare_resource(:execute, "set hostname to #{new_resource.hostname}") do + command "/bin/hostname #{new_resource.hostname}" + not_if { shell_out!("hostname").stdout.chomp == new_resource.hostname } + notifies :reload, "ohai[reload hostname]" + end + + # make sure node['fqdn'] resolves via /etc/hosts + unless new_resource.ipaddress.nil? + newline = "#{new_resource.ipaddress} #{new_resource.hostname}" + newline << " #{new_resource.aliases.join(" ")}" if new_resource.aliases && !new_resource.aliases.empty? + newline << " #{new_resource.hostname[/[^\.]*/]}" + r = append_replacing_matching_lines("/etc/hosts", /^#{new_resource.ipaddress}\s+|\s+#{new_resource.hostname}\s+/, newline) + r.atomic_update false if docker? + r.notifies :reload, "ohai[reload hostname]" + end + + # setup the hostname to perist on a reboot + case + when ::File.exist?("/usr/sbin/scutil") + # darwin + declare_resource(:execute, "set HostName via scutil") do + command "/usr/sbin/scutil --set HostName #{new_resource.hostname}" + not_if { shell_out!("/usr/sbin/scutil --get HostName").stdout.chomp == new_resource.hostname } + notifies :reload, "ohai[reload hostname]" + end + declare_resource(:execute, "set ComputerName via scutil") do + command "/usr/sbin/scutil --set ComputerName #{new_resource.hostname}" + not_if { shell_out!("/usr/sbin/scutil --get ComputerName").stdout.chomp == new_resource.hostname } + notifies :reload, "ohai[reload hostname]" + end + shortname = new_resource.hostname[/[^\.]*/] + declare_resource(:execute, "set LocalHostName via scutil") do + command "/usr/sbin/scutil --set LocalHostName #{shortname}" + not_if { shell_out!("/usr/sbin/scutil --get LocalHostName").stdout.chomp == shortname } + notifies :reload, "ohai[reload hostname]" + end + when node["os"] == "linux" + case + when ::File.exist?("/usr/bin/hostnamectl") && !docker? + # use hostnamectl whenever we find it on linux (as systemd takes over the world) + # this must come before other methods like /etc/hostname and /etc/sysconfig/network + declare_resource(:execute, "hostnamectl set-hostname #{new_resource.hostname}") do + notifies :reload, "ohai[reload hostname]" + not_if { shell_out!("hostnamectl status", { :returns => [0, 1] }).stdout =~ /Static hostname:\s+#{new_resource.hostname}/ } + end + when ::File.exist?("/etc/hostname") + # debian family uses /etc/hostname + # arch also uses /etc/hostname + # the "platform: iox_xr, platform_family: wrlinux, os: linux" platform also hits this + # the "platform: nexus, platform_family: wrlinux, os: linux" platform also hits this + # this is also fallback for any linux systemd host in a docker container (where /usr/bin/hostnamectl will fail) + declare_resource(:file, "/etc/hostname") do + atomic_update false if docker? + content "#{new_resource.hostname}\n" + owner "root" + group node["root_group"] + mode "0644" + end + when ::File.file?("/etc/sysconfig/network") + # older non-systemd RHEL/Fedora derived + append_replacing_matching_lines("/etc/sysconfig/network", /^HOSTNAME\s*=/, "HOSTNAME=#{new_resource.hostname}") + when ::File.exist?("/etc/HOSTNAME") + # SuSE/OpenSUSE uses /etc/HOSTNAME + declare_resource(:file, "/etc/HOSTNAME") do + content "#{new_resource.hostname}\n" + owner "root" + group node["root_group"] + mode "0644" + end + when ::File.exist?("/etc/conf.d/hostname") + # Gentoo + declare_resource(:file, "/etc/conf.d/hostname") do + content "hostname=\"#{new_resource.hostname}\"\n" + owner "root" + group node["root_group"] + mode "0644" + end + else + # This is a failsafe for all other linux distributions where we set the hostname + # via /etc/sysctl.conf on reboot. This may get into a fight with other cookbooks + # that manage sysctls on linux. + append_replacing_matching_lines("/etc/sysctl.conf", /^\s+kernel\.hostname\s+=/, "kernel.hostname=#{new_resource.hostname}") + end + when ::File.exist?("/etc/rc.conf") + # *BSD systems with /etc/rc.conf + /etc/myname + append_replacing_matching_lines("/etc/rc.conf", /^\s+hostname\s+=/, "hostname=#{new_resource.hostname}") + + declare_resource(:file, "/etc/myname") do + content "#{new_resource.hostname}\n" + owner "root" + group node["root_group"] + mode "0644" + end + when ::File.exist?("/etc/nodename") + # Solaris <= 5.10 systems prior to svccfg taking over this functionality (must come before svccfg handling) + declare_resource(:file, "/etc/nodename") do + content "#{new_resource.hostname}\n" + owner "root" + group node["root_group"] + mode "0644" + end + # Solaris also has /etc/inet/hosts (copypasta alert) + unless new_resource.ipaddress.nil? + newline = "#{new_resource.ipaddress} #{new_resource.hostname}" + newline << " #{new_resource.aliases.join(" ")}" if new_resource.aliases && !new_resource.aliases.empty? + newline << " #{new_resource.hostname[/[^\.]*/]}" + r = append_replacing_matching_lines("/etc/inet/hosts", /^#{new_resource.ipaddress}\s+|\s+#{new_resource.hostname}\s+/, newline) + r.notifies :reload, "ohai[reload hostname]" + end + when ::File.exist?("/usr/sbin/svccfg") + # Solaris >= 5.11 systems using svccfg (must come after /etc/nodename handling) + declare_resource(:execute, "svccfg -s system/identity:node setprop config/nodename=\'#{new_resource.hostname}\'") do + notifies :run, "execute[svcadm refresh]", :immediately + notifies :run, "execute[svcadm restart]", :immediately + not_if { shell_out!("svccfg -s system/identity:node listprop config/nodename").stdout.chomp =~ /config\/nodename\s+astring\s+#{new_resource.hostname}/ } + end + declare_resource(:execute, "svcadm refresh") do + command "svcadm refresh system/identity:node" + action :nothing + end + declare_resource(:execute, "svcadm restart") do + command "svcadm restart system/identity:node" + action :nothing + end + else + raise "Do not know how to set hostname on os #{node["os"]}, platform #{node["platform"]},"\ + "platform_version #{node["platform_version"]}, platform_family #{node["platform_family"]}" + end + + else # windows + + # suppress EC2 config service from setting our hostname + if ::File.exist?('C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml') + xml_contents = updated_ec2_config_xml + if xml_contents.empty? + Chef::Log.warn('Unable to properly parse and update C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml contents. Skipping file update.') + else + declare_resource(:file, 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml') do + content xml_contents + end + end + end + + # update via netdom + declare_resource(:powershell_script, "set hostname") do + code <<-EOH + $sysInfo = Get-WmiObject -Class Win32_ComputerSystem + $sysInfo.Rename("#{new_resource.hostname}") + EOH + notifies :request_reboot, "reboot[setting hostname]" + not_if { Socket.gethostbyname(Socket.gethostname).first == new_resource.hostname } + end + + # reboot because $windows + declare_resource(:reboot, "setting hostname") do + reason "chef setting hostname" + action :nothing + only_if { new_resource.windows_reboot } + end + end + end + + # this resource forces itself to run at compile_time + def after_created + if compile_time + Array(action).each do |action| + run_action(action) + end + end + end + end + end +end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index b580a01c29..37d1ca253f 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -41,6 +41,7 @@ require "chef/resource/gem_package" require "chef/resource/git" require "chef/resource/group" require "chef/resource/http_request" +require "chef/resource/hostname" require "chef/resource/homebrew_package" require "chef/resource/ifconfig" require "chef/resource/ksh" diff --git a/spec/unit/resource/hostname_spec.rb b/spec/unit/resource/hostname_spec.rb new file mode 100644 index 0000000000..33f944dbc9 --- /dev/null +++ b/spec/unit/resource/hostname_spec.rb @@ -0,0 +1,43 @@ +# +# Copyright:: Copyright 2018, Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed 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. +# + +require "spec_helper" + +describe Chef::Resource::Hostname do + + let(:resource) { Chef::Resource::Hostname.new("foo") } + + it "has a resource name of :hostname" do + expect(resource.resource_name).to eql(:hostname) + end + + it "has a default action of set" do + expect(resource.action).to eql([:set]) + end + + it "runs at compile_time by default" do + expect(resource.compile_time).to eql(true) + end + + it "reboots windows nodes by default" do + expect(resource.windows_reboot).to eql(true) + end + + it "the hostname property is the name property" do + expect(resource.hostname).to eql("foo") + end +end |