Files
algo/files/cloud-init
Florian KinderandGitHub 38e87c2f5a Fix Vultr startup script JSON serialization issues (#14853)
* Fix Vultr startup script JSON serialization error

The startup_script module was failing with "Object of type 'bytes' is not
JSON serializable" because the lookup('template', ...) was returning bytes
instead of a string.

Added | string filter to explicitly convert the template result to a
string, matching the pattern used by the DigitalOcean cloud provider.

Also simplified from multiline block format to inline format for
consistency with other cloud providers.

Fixes the error: "Object of type 'bytes' is not JSON serializable by the
'tagless' profile."

* Add string filters to template lookups in cloud-init base template

Added | string filters to lookup() calls in the cloud-init base template
to ensure consistent string handling across all cloud providers.

The Vultr startup_script module requires all values to be JSON-serializable
strings, and lookup() can return bytes in some contexts. This change ensures
that both the SSH config template lookup and the SSH public key file lookup
explicitly return strings.

This is a defensive fix that improves compatibility with strict JSON
serialization requirements in some Ansible modules, while remaining
backward compatible with existing cloud providers.

Related to: vultr.cloud.startup_script JSON serialization requirements

* Use two-step fact assignment for Vultr startup script

Changed to set the cloud-init script as a fact first, then reference
that fact in the startup_script module. This follows the pattern used
by the Linode provider and avoids JSON serialization issues with nested
template lookups.

This approach ensures the template is fully evaluated and stored as a
string before being passed to the vultr.cloud.startup_script module,
which then base64-encodes it for the API.

Related to: JSON serialization with "tagless" profile in Ansible 2.19+
2025-11-27 13:42:26 -05:00
..

Cloud-Init Files - Critical Format Requirements

⚠️ CRITICAL WARNING ⚠️

The files in this directory have STRICT FORMAT REQUIREMENTS that must not be changed by linters or automated formatting tools.

Cloud-Config Header Format

The first line of base.yml MUST be exactly:

#cloud-config

DO NOT CHANGE TO:

  • # cloud-config (space after #) - BREAKS CLOUD-INIT PARSING
  • Add YAML document start --- - NOT ALLOWED IN CLOUD-INIT

Why This Matters

Cloud-init's YAML parser expects the exact string #cloud-config as the first line. Any deviation causes:

  1. Complete parsing failure - All directives are skipped
  2. SSH configuration not applied - Servers remain on port 22 instead of 4160
  3. Deployment timeouts - Ansible cannot connect to configure the VPN
  4. DigitalOcean specific impact - Other providers may be more tolerant

Historical Context

  • Working: All versions before PR #14775 (August 2025)
  • Broken: PR #14775 "Apply ansible-lint improvements" added space by mistake
  • Fixed: PR #14801 restored correct format + added protections

See GitHub issue #14800 for full technical details.

Linter Configuration

These files are excluded from:

  • yamllint (.yamllint config)
  • ansible-lint (.ansible-lint config)

This prevents automated tools from "fixing" the format and breaking deployments.

Template Variables

The cloud-init files use Jinja2 templating:

  • {{ ssh_port }} - Configured SSH port (typically 4160)
  • {{ lookup('file', '{{ SSH_keys.public }}') }} - SSH public key

Editing Guidelines

  1. Never run automated formatters on these files
  2. Test immediately after any changes with real deployments
  3. Check yamllint warnings are expected (missing space in comment, missing ---)
  4. Verify first line remains exactly #cloud-config

References