# # Copyright:: Copyright (c) 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_relative "../resource" require "chef-utils/dist" unless defined?(ChefUtils::Dist) class Chef class Resource # Sets the hostname and updates /etc/hosts on *nix systems # @since 14.0.0 class Hostname < Chef::Resource provides :hostname, target_mode: true description "Use the **hostname** resource to set the system's hostname, configure hostname and hosts config file, and re-run the Ohai hostname plugin so the hostname will be available in subsequent cookbooks." introduced "14.0" examples <<~DOC **Set the hostname using the IP address, as detected by Ohai**: ```ruby hostname 'example' ``` **Manually specify the hostname and IP address**: ```ruby hostname 'statically_configured_host' do hostname 'example' ipaddress '198.51.100.2' end ``` **Change the hostname of a Windows, Non-Domain joined node**: ```ruby hostname 'renaming a workgroup computer' do hostname 'Foo' end ``` **Change the hostname of a Windows, Domain-joined node (new in 17.2)**: ```ruby hostname 'renaming a domain-joined computer' do hostname 'Foo' domain_user "Domain\\Someone" domain_password 'SomePassword' end ``` DOC property :hostname, String, description: "An optional property to set the hostname if it differs from the resource block's name.", name_property: true property :fqdn, String, description: "An optional property to set the fqdn if it differs from the resource block's hostname.", introduced: "17.0" property :ipaddress, String, description: "The IP address to use when configuring the hosts file.", default: lazy { node["ipaddress"] }, default_description: "The node's IP address as determined by Ohai." property :aliases, [ Array, nil ], description: "An array of hostname aliases to use when configuring the hosts file.", default: nil # override compile_time property to be true by default property :compile_time, [ TrueClass, FalseClass ], description: "Determines whether or not the resource should be run at compile time.", default: true, desired_state: false property :windows_reboot, [ TrueClass, FalseClass ], description: "Determines whether or not Windows should be reboot after changing the hostname, as this is required for the change to take effect.", default: true property :domain_user, String, description: "A domain account specified in the form of DOMAIN\\user used when renaming a domain-joined device", introduced: "17.2" property :domain_password, String, description: "The password to accompany the domain_user parameter", sensitive: true, introduced: "17.2" action_class do def append_replacing_matching_lines(path, regex, string) text = TargetIO::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 { TargetIO::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" unless defined?(REXML::Document) config = REXML::Document.new(::File.read(WINDOWS_EC2_CONFIG)) # 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 def is_domain_joined? powershell_exec!("(Get-CIMInstance -Class Win32_ComputerSystem).PartofDomain").result end action :set, description: "Sets the node's hostname." do if !windows? ohai "reload hostname" do plugin "hostname" action :nothing end # set the hostname via /bin/hostname 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}" newline << " #{new_resource.fqdn}" unless new_resource.fqdn.to_s.empty? newline << " #{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 persist on a reboot case when darwin? # darwin 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 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[/[^\.]*/] 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 linux? case when ::TargetIO::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 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}\s*$/ } end when ::TargetIO::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) file "/etc/hostname" do atomic_update false if docker? content "#{new_resource.hostname}\n" owner "root" group node["root_group"] mode "0644" end when ::TargetIO::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 ::TargetIO::File.exist?("/etc/HOSTNAME") # SuSE/openSUSE uses /etc/HOSTNAME file "/etc/HOSTNAME" do content "#{new_resource.hostname}\n" owner "root" group node["root_group"] mode "0644" end when ::TargetIO::File.exist?("/etc/conf.d/hostname") # Gentoo 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 ::TargetIO::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}") file "/etc/myname" do content "#{new_resource.hostname}\n" owner "root" group node["root_group"] mode "0644" end when ::TargetIO::File.exist?("/usr/sbin/svccfg") # solaris 5.11 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 =~ %r{config/nodename\s+astring\s+#{new_resource.hostname}} } end execute "svcadm refresh" do command "svcadm refresh system/identity:node" action :nothing end 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 WINDOWS_EC2_CONFIG = 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml'.freeze raise "Windows hostnames cannot contain a period." if new_resource.hostname.include?(".") raise "Windows not supported in Target Mode" if ChefConfig::Config.target_mode? # suppress EC2 config service from setting our hostname if ::File.exist?(WINDOWS_EC2_CONFIG) 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 file WINDOWS_EC2_CONFIG do content xml_contents end end end unless Socket.gethostbyname(Socket.gethostname).first == new_resource.hostname if is_domain_joined? if new_resource.domain_user.nil? || new_resource.domain_password.nil? raise "The `domain_user` and `domain_password` properties are required to change the hostname of a domain-connected Windows system." else converge_by "set hostname to #{new_resource.hostname}" do powershell_exec! <<~EOH $user = #{new_resource.domain_user} $secure_password = #{new_resource.domain_password} | Convertto-SecureString -AsPlainText -Force $Credentials = New-Object System.Management.Automation.PSCredential -Argumentlist ($user, $secure_password) Rename-Computer -NewName #{new_resource.hostname} -DomainCredential $Credentials EOH end end else converge_by "set hostname to #{new_resource.hostname}" do powershell_exec!("Rename-Computer -NewName #{new_resource.hostname}") end end # reboot because $windows reboot "setting hostname" do reason "#{ChefUtils::Dist::Infra::PRODUCT} updated system hostname" only_if { new_resource.windows_reboot } action :request_reboot end end end end end end end