Skip to content

cloud-init: fix the generator looking for ds-identify at the wrong path - #1676

Open
dcasota wants to merge 1 commit into
vmware:5.0from
dcasota:fix/cloud-init-generator-libexecdir
Open

dcasota wants to merge 1 commit into
vmware:5.0from
dcasota:fix/cloud-init-generator-libexecdir

Conversation

@dcasota

@dcasota dcasota commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

On Photon OS 5.0 subrelease 92, cloud-init does not run at boot. The systemd generator terminates with exit status 3, no datasource is identified, and the six cloud-* units remain inactive although they are enabled. No systemd unit enters a failed state, so systemctl --failed reports no error.

The consequence is that instance configuration normally applied by cloud-init — SSH key injection, hostname assignment, user-data processing and network configuration — is not applied.

Affected releases

Release cloud-init Status
Photon OS 5.0, subrelease 92 (mainline SPECS/) 26.2-1, 26.2-2 Affected
Photon OS 5.0, subrelease 91 (SPECS/91/) 25.1.3-11.1 Not affected
Photon OS 5.0, subrelease 90 (SPECS/90/) 25.1.3-4.1.1 Not affected
Photon OS 4.0 25.1.3-3 Not affected
Photon OS dev 24.4-1 Not affected

cloud-init.spec carries %global build_if %{photon_subrelease} >= 92, so the package is built for subrelease 92 only. The other trees remain on cloud-init 25.1.3 or earlier, which installs its helper binaries under %{_libdir}, where the generator's expected path is correct.

This change raises the mainline package to 26.2-3.

Observed behaviour

Sep 09 21:20:46 (sd-exec-[614]: /usr/lib/systemd/system-generators/cloud-init-generator
                                failed with exit status 3.

$ systemctl is-active cloud-init.service
inactive

$ cloud-init status --long
status: not started
detail: Systemd generator may not have run yet.

Root cause

Two independent path mismatches, both introduced when the package moved to a meson build.

1. The systemd generator

The generator resolves ds-identify from a path selected by build variant in systemd/cloud-init-generator.tmpl:

{% if variant in ["almalinux", "azurelinux", "centos", "cloudlinux", "eurolinux", "fedora",
                  "miraclelinux", "openeuler", "OpenCloudOS", "openmandriva", "rhel", "rocky", "TencentOS", "virtuozzo"] %}
    dsidentify="/usr/libexec/cloud-init/ds-identify"
{% elif variant == "benchmark" %}
    dsidentify="/bin/true"
{% else %}
    dsidentify="/usr/lib/cloud-init/ds-identify"
{% endif %}

photon is not a member of the first list, so the template renders the else branch. The meson install places the binary in libexecdir:

$ grep -n dsidentify= /usr/lib/systemd/system-generators/cloud-init-generator
18:    dsidentify="/usr/lib/cloud-init/ds-identify"

$ rpm -ql cloud-init | grep ds-identify
/usr/libexec/cloud-init/ds-identify

Rendering the template directly confirms the branch selection:

$ python3 tools/render-template --variant photon systemd/cloud-init-generator.tmpl out
    dsidentify="/usr/lib/cloud-init/ds-identify"

$ python3 tools/render-template --variant rhel   systemd/cloud-init-generator.tmpl out
    dsidentify="/usr/libexec/cloud-init/ds-identify"

meson.build renders the generator without an explicit --variant, so tools/render-template uses its default of util.system_info()["variant"], which is photon on a Photon build host.

Reading systemd/cloud-init-generator.tmpl, an absent $dsidentify yields exit status 127, which matches none of the handled values 0, 1 or 2. Control reaches the fallback branch, which sets ret=3. No multi-user.target.wants symlink is created and neither the enabled nor the disabled marker is written under /run.

2. Distro.usr_lib_exec

cloudinit/distros/__init__.py:132 defines usr_lib_exec = "/usr/lib" as the default. cloudinit/distros/rhel.py:30 overrides it with /usr/libexec; cloudinit/distros/photon.py does not, so Photon inherits the default.

The attribute is consumed by cloudinit/config/cc_keys_to_console.py:36 and cloudinit/distros/__init__.py:1406. Both therefore resolve to /usr/lib/cloud-init, while the package installs:

/usr/libexec/cloud-init/write-ssh-key-fingerprints
/usr/libexec/cloud-init/hook-hotplug

This is the same mismatch as above, expressed in Python rather than in a rendered shell script.

Origin

Commit cd1c1fff9, "92: cloud-init, open-vm-tools: version upgrades", upgraded the package from 25.1.3-11 to 26.2-1 and changed the install method:

-%py3_install -- --init-system=systemd
+%{meson_install}

-%{_libdir}/%{name}/*
+%{_libexecdir}/%{name}/*

meson installs helper binaries to libexecdir. The %files section was updated accordingly; the generator template and photon.py were not.

The preceding package was internally consistent:

Version Installs ds-identify at Generator expects Consistent
25.1.3-11 /usr/lib/cloud-init/ /usr/lib/cloud-init/ Yes
26.2-1 /usr/libexec/cloud-init/ /usr/lib/cloud-init/ No
26.2-2 /usr/libexec/cloud-init/ /usr/lib/cloud-init/ No

26.2-2 does not address this. Its changelog entry covers the python3-configobj dependency, and no commit under SPECS/cloud-init/ has modified either path (git log -S returns no results for libexec/cloud-init or lib/cloud-init/ds-identify). Both RPMs were extracted and compared: the generator is identical in each, including line 18.

Changes

0011-generator-photon-uses-libexecdir.patch adds photon to the variant list that resolves ds-identify under libexecdir.

0012-photon-usr-lib-exec-is-libexecdir.patch sets usr_lib_exec = "/usr/libexec" in cloudinit/distros/photon.py, matching rhel.py.

Both align cloud-init's expectations with the location the package already installs to, rather than relocating the payload. Photon OS is an RPM distribution whose meson build installs helpers to libexecdir, consistent with the variants already present in that list.

Verification

Template rendering after the change is limited to the photon variant:

--variant photon  ->  /usr/libexec/cloud-init/ds-identify    (changed)
--variant rhel    ->  /usr/libexec/cloud-init/ds-identify    (unchanged)
--variant ubuntu  ->  /usr/lib/cloud-init/ds-identify        (unchanged)

Both patches apply to the released 26.2 source archive at --fuzz=0. support/spec-checker/check_spec.py exits 0.

cloud-init-26.2-3.ph5.noarch.rpm was built from this branch and extracted:

generator, line 18:          dsidentify="/usr/libexec/cloud-init/ds-identify"
cloudinit/distros/photon.py: usr_lib_exec = "/usr/libexec"
ds-identify installed at:    /usr/libexec/cloud-init/ds-identify

Runtime evidence

Four guest installations, produced by the same automated harness and differing only in the cloud-init version present on the installation media:

Guest cloud-init cloud-init status
k01 25.1.3-11 done
s02 25.1.3-11 done
c01 26.2-2 not started
c03 26.2-1 not started

Both guests running 25.1.3 complete successfully; both guests running 26.2 do not. c01 carries 26.2-2, confirming that the currently published build is affected.

Guest installation with 26.2-3

A minimal installation ISO carrying cloud-init-26.2-3.ph5 was built from this branch and installed. The automated row reports 37 checks, 18 pass, 0 fail.

26.2-1 / 26.2-2 26.2-3
status not started done
boot_status_code unknown enabled-by-generator
detail Systemd generator may not have run yet. DataSourceNone

boot_status_code: enabled-by-generator is written only when the generator runs to completion and creates the enable marker under /run. The journal confirms all four stages ran:

Starting Cloud-init: Local Stage (pre-network)...   Finished
Starting Cloud-init: Network Stage...              Finished
Starting Cloud-init: Config Stage...               Finished
Starting Cloud-init: Final Stage...                Finished
Cloud-init v. 26.2-3.ph5 finished at Thu, 10 Sep 2026 18:06:44 +0000

detail: DataSourceNone is expected on this guest and is not a regression. No cloud-init datasource is provisioned on it: the installer's kickstart is delivered through a separate guestinfo key, not through cloud-init's guestinfo.metadata / guestinfo.userdata, so ds-identify correctly identifies no datasource and the None entry configured in datasource_list by 0004-Add-default-DS-list-few-other-changes-to-cloud.cfg.patch is used. extended_status: degraded done reflects the single recoverable warning that accompanies it, Used fallback datasource.

Upstream

The same two changes are applicable to cloud-init upstream. canonical/cloud-init PR canonical/cloud-init#6874 (merged 2026-06-09) made the equivalent addition for Azure Linux. Issue canonical/cloud-init#6329 records the same generator failure for openEuler, where the maintainer's stated preference is that packaging retain /usr/libexec and the variant list be extended. Issue canonical/cloud-init#6588 describes the usr_lib_exec class of mismatch.

Photon OS support was added upstream in PR canonical/cloud-init#909 (2021), which did not include the generator template, although five RPM distributions merged in the same period did.

Not covered by this change

meson.build renders templates using the build host's variant, because tools/render-template is invoked without --variant and falls back to util.system_info()["variant"]. A build performed on a non-Photon host would therefore omit the Photon branches. This does not affect builds on Photon and is not addressed here.

Unrelated observation

cloud-init status reports last_update: Thu, 01 Jan 1970 00:00:10 +0000 on the verified guest. cloudinit/cmd/status.py:486 formats datetime.fromtimestamp(latest_event), where get_latest_event() takes the maximum stage start/finished value recorded in /run/cloud-init/status.json. Those values correspond to uptime rather than wall-clock time — the journal shows the same run finishing at Thu, 10 Sep 2026 18:06:44 +0000 after Up 10.65 seconds, and 00:00:10 matches the uptime. The system clock was correct throughout.

This is present independently of this change and is not addressed by it. Recorded here because it makes the timestamp in cloud-init status unreliable for diagnosis.

On Photon OS 5.0 subrelease 92, cloud-init does not run at boot. The systemd
generator terminates with exit status 3, no datasource is identified, and the
six cloud-* units remain inactive although they are enabled. No unit enters a
failed state, so systemctl --failed reports no error.

  Sep 09 21:20:46 (sd-exec-[614]: /usr/lib/systemd/system-generators/
                   cloud-init-generator failed with exit status 3.

Instance configuration normally applied by cloud-init - SSH key injection,
hostname assignment, user-data processing and network configuration - is
therefore not applied.

Two path mismatches are responsible, both introduced when the package moved to
a meson build.

1. systemd/cloud-init-generator.tmpl resolves ds-identify from a path selected
   by build variant. photon is not a member of the list that selects
   libexecdir, so the template renders the else branch:

     {% if variant in ["almalinux", ..., "rhel", "rocky", ...] %}
         dsidentify="/usr/libexec/cloud-init/ds-identify"
     {% else %}
         dsidentify="/usr/lib/cloud-init/ds-identify"

   while %{meson_install} places the binary in /usr/libexec/cloud-init.
   Rendering the template confirms the branch selection:

     --variant photon  ->  /usr/lib/cloud-init/ds-identify
     --variant rhel    ->  /usr/libexec/cloud-init/ds-identify

2. cloudinit/distros/photon.py does not override Distro.usr_lib_exec, which
   defaults to /usr/lib in cloudinit/distros/__init__.py:132. rhel.py:30 sets
   /usr/libexec. The attribute is read by cc_keys_to_console.py:36 and
   distros/__init__.py:1406, so both resolve to /usr/lib/cloud-init while the
   package installs write-ssh-key-fingerprints and hook-hotplug under
   /usr/libexec/cloud-init.

Both patches align cloud-init's expectations with the location the package
already installs to. Photon OS is an RPM distribution whose meson build
installs helpers to libexecdir, consistent with the variants already listed.

Introduced by cd1c1ff (25.1.3-11 -> 26.2-1), which changed %py3_install to
%{meson_install} and relocated the payload from %{_libdir}/%{name} to
%{_libexecdir}/%{name}. The %files section was updated accordingly; the
generator template and photon.py were not. 25.1.3-11 was internally
consistent: it installed ds-identify under /usr/lib/cloud-init and the
generator resolved the same path.

The currently published 26.2-2 is affected. Its changelog entry covers the
python3-configobj dependency and modifies neither path.

Affected releases: Photon OS 5.0 subrelease 92 only. cloud-init.spec carries
%global build_if %{photon_subrelease} >= 92, and SPECS/91 (25.1.3-11.1),
SPECS/90 (25.1.3-4.1.1), 4.0 (25.1.3-3) and dev (24.4-1) remain on releases
that install helpers under %{_libdir}, where the generator's expected path is
correct. This change raises the mainline package to 26.2-3.

Runtime evidence from four guest installations produced by the same automated
harness, differing only in the cloud-init version on the installation media:
25.1.3-11 reports "done" on both, 26.2-1 and 26.2-2 report "not started" on
both.

Change-Id: Icd2715fd307cd045865a081e1ef20298d878b3a7
Signed-off-by: Daniel Casota <dcasota@gmail.com>
@dcasota

dcasota commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Hi @prashant1221 , could you please review the PR? Thank you in advance for a short comment. Best wishes, Daniel

dcasota pushed a commit to dcasota/photonos-scripts that referenced this pull request Sep 11, 2026
`fix/cloud-init-generator-libexecdir` (dcasota/photon#32, vmware/photon#1676)
is now part of the branch set both installer variants are built from, so every
ISO the matrix builds carries cloud-init 26.2-3 rather than 26.2-2.

Without it the generator resolves ds-identify under /usr/lib while the meson
install puts it in /usr/libexec, exits 3, and every 26.2 guest reports
"status: not started" where a 25.1.3 guest reports "done".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant