Skip to content

Commit

Permalink
feat: check for create_hostname_file key before writing /etc/hostname…
Browse files Browse the repository at this point in the history
… (SC-1588) (#4330)

Some users would like to be able to bring up their systems without
automatically creating /etc/hostname if it does not exist already.
On distros with systemd, the hostname/hostnamectl command can be
used to set the hostname transiently (which does not create
/etc/hostname).  To support this without changing previous behavior
(that cloud-init will set the hostname statically and create
/etc/hostname if it does not already exist), this change proposes a
new key preserve_etchostname_state which is true, sets the hostname
transiently and if not present or False, sets it statically.

This change does not affect BSD or OpenBSD (or any distros that
inherit their _write_hostname), as their hostname management does
not use an /etc/hostname file.

Co-authored-by: Catherine Redfield <catherine.redfield@canonical.com>
  • Loading branch information
catmsred and catmsred authored Sep 12, 2023
1 parent 0d9f149 commit be7f64d
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 9 deletions.
7 changes: 7 additions & 0 deletions cloudinit/config/cc_set_hostname.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
if hostname_fqdn is not None:
cloud.distro.set_option("prefer_fqdn_over_hostname", hostname_fqdn)

# Set create_hostname_file in distro
create_hostname_file = util.get_cfg_option_bool(
cfg, "create_hostname_file", None
)
if create_hostname_file is not None:
cloud.distro.set_option("create_hostname_file", create_hostname_file)

(hostname, fqdn, is_default) = util.get_hostname_fqdn(cfg, cloud)
# Check for previous successful invocation of set_hostname

Expand Down
7 changes: 7 additions & 0 deletions cloudinit/config/cc_update_hostname.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
if hostname_fqdn is not None:
cloud.distro.set_option("prefer_fqdn_over_hostname", hostname_fqdn)

# Set create_hostname_file in distro
create_hostname_file = util.get_cfg_option_bool(
cfg, "create_hostname_file", None
)
if create_hostname_file is not None:
cloud.distro.set_option("create_hostname_file", create_hostname_file)

(hostname, fqdn, is_default) = util.get_hostname_fqdn(cfg, cloud)
if is_default and hostname == "localhost":
# https://github.com/systemd/systemd/commit/d39079fcaa05e23540d2b1f0270fa31c22a7e9f1
Expand Down
10 changes: 10 additions & 0 deletions cloudinit/config/schemas/schema-cloud-config-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2576,6 +2576,11 @@
"prefer_fqdn_over_hostname": {
"type": "boolean",
"description": "If true, the fqdn will be used if it is set. If false, the hostname will be used. If unset, the result is distro-dependent"
},
"create_hostname_file": {
"type": "boolean",
"default": true,
"description": "If ``false``, the hostname file (e.g. /etc/hostname) will not be created if it does not exist. On systems that use systemd, setting create_hostname_file to ``false`` will set the hostname transiently. If ``true``, the hostname file will always be created and the hostname will be set statically on systemd systems. Default: ``true``"
}
}
},
Expand Down Expand Up @@ -3064,6 +3069,11 @@
"type": "boolean",
"default": null,
"description": "By default, it is distro-dependent whether cloud-init uses the short hostname or fully qualified domain name when both ``local-hostname` and ``fqdn`` are both present in instance metadata. When set ``true``, use fully qualified domain name if present as hostname instead of short hostname. When set ``false``, use ``hostname`` config value if present, otherwise fallback to ``fqdn``."
},
"create_hostname_file": {
"type": "boolean",
"default": true,
"description": "If ``false``, the hostname file (e.g. /etc/hostname) will not be created if it does not exist. On systems that use systemd, setting create_hostname_file to ``false`` will set the hostname transiently. If ``true``, the hostname file will always be created and the hostname will be set statically on systemd systems. Default: ``true``"
}
}
},
Expand Down
8 changes: 7 additions & 1 deletion cloudinit/distros/alpine.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,13 @@ def _write_hostname(self, hostname, filename):
# so lets see if we can read it first.
conf = self._read_hostname_conf(filename)
except IOError:
pass
create_hostname_file = util.get_cfg_option_bool(
self._cfg, "create_hostname_file", True
)
if create_hostname_file:
pass
else:
return
if not conf:
conf = HostnameConf("")
conf.set_hostname(hostname)
Expand Down
8 changes: 7 additions & 1 deletion cloudinit/distros/arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,13 @@ def _write_hostname(self, hostname, filename):
# so lets see if we can read it first.
conf = self._read_hostname_conf(filename)
except IOError:
pass
create_hostname_file = util.get_cfg_option_bool(
self._cfg, "create_hostname_file", True
)
if create_hostname_file:
pass
else:
return
if not conf:
conf = HostnameConf("")
conf.set_hostname(hostname)
Expand Down
8 changes: 7 additions & 1 deletion cloudinit/distros/debian.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,13 @@ def _write_hostname(self, hostname, filename):
# so lets see if we can read it first.
conf = self._read_hostname_conf(filename)
except IOError:
pass
create_hostname_file = util.get_cfg_option_bool(
self._cfg, "create_hostname_file", True
)
if create_hostname_file:
pass
else:
return
if not conf:
conf = HostnameConf("")
conf.set_hostname(hostname)
Expand Down
8 changes: 7 additions & 1 deletion cloudinit/distros/gentoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,13 @@ def _write_hostname(self, hostname, filename):
# so lets see if we can read it first.
conf = self._read_hostname_conf(filename)
except IOError:
pass
create_hostname_file = util.get_cfg_option_bool(
self._cfg, "create_hostname_file", True
)
if create_hostname_file:
pass
else:
return
if not conf:
conf = HostnameConf("")

Expand Down
20 changes: 18 additions & 2 deletions cloudinit/distros/opensuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,18 +221,34 @@ def _set_update_method(self):
self.update_method = "zypper"

def _write_hostname(self, hostname, filename):
create_hostname_file = util.get_cfg_option_bool(
self._cfg, "create_hostname_file", True
)
if self.uses_systemd() and filename.endswith("/previous-hostname"):
util.write_file(filename, hostname)
elif self.uses_systemd():
subp.subp(["hostnamectl", "set-hostname", str(hostname)])
if create_hostname_file:
subp.subp(["hostnamectl", "set-hostname", str(hostname)])
else:
subp.subp(
[
"hostnamectl",
"set-hostname",
"--transient",
str(hostname),
]
)
else:
conf = None
try:
# Try to update the previous one
# so lets see if we can read it first.
conf = self._read_hostname_conf(filename)
except IOError:
pass
if create_hostname_file:
pass
else:
return
if not conf:
conf = HostnameConf("")
conf.set_hostname(hostname)
Expand Down
18 changes: 16 additions & 2 deletions cloudinit/distros/photon.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,23 @@ def _write_hostname(self, hostname, filename):
if filename and filename.endswith("/previous-hostname"):
util.write_file(filename, hostname)
else:
ret, _out, err = self.exec_cmd(
["hostnamectl", "set-hostname", str(hostname)]
ret = None
create_hostname_file = util.get_cfg_option_bool(
self._cfg, "create_hostname_file", True
)
if create_hostname_file:
ret, _out, err = self.exec_cmd(
["hostnamectl", "set-hostname", str(hostname)]
)
else:
ret, _out, err = self.exec_cmd(
[
"hostnamectl",
"set-hostname",
"--transient",
str(hostname),
]
)
if ret:
LOG.warning(
(
Expand Down
15 changes: 14 additions & 1 deletion cloudinit/distros/rhel.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,20 @@ def _write_hostname(self, hostname, filename):
conf.set_hostname(hostname)
util.write_file(filename, str(conf), 0o644)
elif self.uses_systemd():
subp.subp(["hostnamectl", "set-hostname", str(hostname)])
create_hostname_file = util.get_cfg_option_bool(
self._cfg, "create_hostname_file", True
)
if create_hostname_file:
subp.subp(["hostnamectl", "set-hostname", str(hostname)])
else:
subp.subp(
[
"hostnamectl",
"set-hostname",
"--transient",
str(hostname),
]
)
else:
host_cfg = {
"HOSTNAME": hostname,
Expand Down
90 changes: 90 additions & 0 deletions tests/unittests/config/test_cc_set_hostname.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,5 +259,95 @@ def test_ignore_empty_previous_artifact_file(self):
contents = util.load_file("/etc/hostname")
self.assertEqual("blah", contents.strip())

def test_create_hostname_file_false(self):
cfg = {
"hostname": "foo",
"fqdn": "foo.blah.yahoo.com",
"create_hostname_file": False,
}
distro = self._fetch_distro("debian")
paths = helpers.Paths({"cloud_dir": self.tmp})
ds = None
cc = cloud.Cloud(ds, paths, {}, distro, None)
self.patchUtils(self.tmp)
cc_set_hostname.handle("cc_set_hostname", cfg, cc, [])
with self.assertRaises(FileNotFoundError):
util.load_file("/etc/hostname")

def test_create_hostname_file_false_arch(self):
cfg = {
"hostname": "foo",
"fqdn": "foo.blah.yahoo.com",
"create_hostname_file": False,
}
distro = self._fetch_distro("arch")
paths = helpers.Paths({"cloud_dir": self.tmp})
ds = None
cc = cloud.Cloud(ds, paths, {}, distro, None)
self.patchUtils(self.tmp)
cc_set_hostname.handle("cc_set_hostname", cfg, cc, [])
with self.assertRaises(FileNotFoundError):
util.load_file("/etc/hostname")

def test_create_hostname_file_false_alpine(self):
cfg = {
"hostname": "foo",
"fqdn": "foo.blah.yahoo.com",
"create_hostname_file": False,
}
distro = self._fetch_distro("alpine")
paths = helpers.Paths({"cloud_dir": self.tmp})
ds = None
cc = cloud.Cloud(ds, paths, {}, distro, None)
self.patchUtils(self.tmp)
cc_set_hostname.handle("cc_set_hostname", cfg, cc, [])
with self.assertRaises(FileNotFoundError):
util.load_file("/etc/hostname")

def test_create_hostname_file_false_gentoo(self):
cfg = {
"hostname": "foo",
"fqdn": "foo.blah.yahoo.com",
"create_hostname_file": False,
}
distro = self._fetch_distro("gentoo")
paths = helpers.Paths({"cloud_dir": self.tmp})
ds = None
cc = cloud.Cloud(ds, paths, {}, distro, None)
self.patchUtils(self.tmp)
cc_set_hostname.handle("cc_set_hostname", cfg, cc, [])
with self.assertRaises(FileNotFoundError):
util.load_file("/etc/hostname")

def test_create_hostname_file_false_photon(self):
cfg = {
"hostname": "foo",
"fqdn": "foo.blah.yahoo.com",
"create_hostname_file": False,
}
distro = self._fetch_distro("photon")
paths = helpers.Paths({"cloud_dir": self.tmp})
ds = None
cc = cloud.Cloud(ds, paths, {}, distro, None)
self.patchUtils(self.tmp)
cc_set_hostname.handle("cc_set_hostname", cfg, cc, [])
with self.assertRaises(FileNotFoundError):
util.load_file("/etc/hostname")

def test_create_hostname_file_false_rhel(self):
cfg = {
"hostname": "foo",
"fqdn": "foo.blah.yahoo.com",
"create_hostname_file": False,
}
distro = self._fetch_distro("rhel")
paths = helpers.Paths({"cloud_dir": self.tmp})
ds = None
cc = cloud.Cloud(ds, paths, {}, distro, None)
self.patchUtils(self.tmp)
cc_set_hostname.handle("cc_set_hostname", cfg, cc, [])
with self.assertRaises(FileNotFoundError):
util.load_file("/etc/hostname")


# vi: ts=4 expandtab
1 change: 1 addition & 0 deletions tools/.github-cla-signers
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ bmhughes
brianphaley
CalvoM
candlerb
catmsred
cawamata
cclauss
chifac08
Expand Down

0 comments on commit be7f64d

Please sign in to comment.