Skip to content

Commit

Permalink
fix: avoid deprecation logs for calling cli stages (#5770)
Browse files Browse the repository at this point in the history
Avoid deprecating command line calls to cloud-init boot stages.

When cloud-init's boot stages are invoked directly on the command line
it is typically an attempt to add supplemental configuration to a
system after initial boot has completed.

Although direct calls to cloud-init boot stages could lead to
misconfiguration if boot stages are called out of sequence
or customized /etc/cloud/cloud.cfg alters which config modules are
run during the boot stage, the support of such uses or custom
environments is out of scope for this feature. It remains a simple
tool to apply additional configuration via user-data onto an existing
system after initial boot.

At some point in the future, a new sub-command may be provided by
cloud-init tooling to allow for supplemental configuration of user-data
post-boot, in that event this --file feature will become deprecated in
favor of a cleaner/simpler alternative.

Fixes GH-5726
  • Loading branch information
blackboxsw authored Oct 4, 2024
1 parent 99f4ca4 commit 55de797
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 49 deletions.
40 changes: 7 additions & 33 deletions cloudinit/cmd/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,21 +98,6 @@ def print_exc(msg=""):
)


def log_ppid(distro, bootstage_name):
if distro.is_linux:
ppid = os.getppid()
if 1 != ppid and distro.uses_systemd():
lifecycle.deprecate(
deprecated=(
"Unsupported configuration: boot stage called "
f"by PID [{ppid}] outside of systemd"
),
deprecated_version="24.3",
extra_message=DEPRECATE_BOOT_STAGE_MESSAGE,
)
LOG.info("PID [%s] started cloud-init '%s'.", ppid, bootstage_name)


def welcome(action, msg=None):
if not msg:
msg = welcome_format(action)
Expand Down Expand Up @@ -394,7 +379,7 @@ def main_init(name, args):
# config applied. We send the welcome message now, as stderr/out have
# been redirected and log now configured.
welcome(name, msg=w_msg)
log_ppid(init.distro, bootstage_name)
LOG.info("PID [%s] started cloud-init '%s'.", os.getppid(), bootstage_name)

# re-play early log messages before logging was setup
for lvl, msg in early_logs:
Expand Down Expand Up @@ -662,7 +647,7 @@ def main_modules(action_name, args):

# now that logging is setup and stdout redirected, send welcome
welcome(name, msg=w_msg)
log_ppid(init.distro, bootstage_name)
LOG.info("PID [%s] started cloud-init '%s'.", os.getppid(), bootstage_name)

if name == "init":
lifecycle.deprecate(
Expand Down Expand Up @@ -953,17 +938,14 @@ def main(sysv_args=None):
"--debug",
"-d",
action="store_true",
help=(
"DEPRECATED: Show additional pre-action "
"logging (default: %(default)s)."
),
help="Show additional pre-action logging (default: %(default)s).",
default=False,
)
parser.add_argument(
"--force",
action="store_true",
help=(
"DEPRECATED: Force running even if no datasource is"
"Force running even if no datasource is"
" found (use at your own risk)."
),
dest="force",
Expand All @@ -986,10 +968,7 @@ def main(sysv_args=None):

# Each action and its sub-options (if any)
parser_init = subparsers.add_parser(
"init",
help=(
"DEPRECATED: Initialize cloud-init and perform initial modules."
),
"init", help="Initialize cloud-init and perform initial modules."
)
parser_init.add_argument(
"--local",
Expand All @@ -1012,8 +991,7 @@ def main(sysv_args=None):

# These settings are used for the 'config' and 'final' stages
parser_mod = subparsers.add_parser(
"modules",
help=("DEPRECATED: Activate modules using a given configuration key."),
"modules", help="Activate modules using a given configuration key."
)
extra_help = lifecycle.deprecate(
deprecated="`init`",
Expand Down Expand Up @@ -1044,11 +1022,7 @@ def main(sysv_args=None):

# This subcommand allows you to run a single module
parser_single = subparsers.add_parser(
"single",
help=(
"Manually run a single module. Useful for "
"testing during development."
),
"single", help="Run a single module."
)
parser_single.add_argument(
"--name",
Expand Down
15 changes: 1 addition & 14 deletions tests/integration_tests/reporting/test_webhook_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@

import pytest

from cloudinit import lifecycle
from tests.integration_tests.instances import IntegrationInstance
from tests.integration_tests.util import (
ASSETS_DIR,
get_feature_flag_value,
verify_clean_boot,
verify_clean_log,
)
Expand Down Expand Up @@ -53,18 +51,7 @@ def test_webhook_reporting(client: IntegrationInstance):
"cloud-init status --wait"
)
verify_clean_log(client.read_from_file("/var/log/cloud-init.log"))
version_boundary = get_feature_flag_value(
client, "DEPRECATION_INFO_BOUNDARY"
)
message = "Unsupported configuration: boot stage called by PID"
deprecation_messages = []
if lifecycle.should_log_deprecation("24.3", version_boundary):
deprecation_messages.append(message)
else:
assert client.execute(
f"grep 'INFO]: {message}' /var/log/cloud-init.log"
).stdout.strip(), f"Did not find expected log: {message}"
verify_clean_boot(client, require_deprecations=deprecation_messages)
verify_clean_boot(client)

server_output = client.read_from_file(
"/var/tmp/echo_server_output"
Expand Down
40 changes: 38 additions & 2 deletions tests/unittests/cmd/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,18 @@
)


EXTRA_CLOUD_CONFIG = """\
#cloud-config
write_files
- path: {tmpdir}/etc/blah.ini
content: override
"""


class TestMain:
@pytest.fixture(autouse=True)
def common_mocks(self, mocker):
mocker.patch("cloudinit.cmd.main.os.getppid", return_value=42)
mocker.patch("cloudinit.cmd.main.close_stdin")
mocker.patch(
"cloudinit.cmd.main.netinfo.debug_info",
Expand Down Expand Up @@ -68,11 +77,37 @@ def cloud_cfg(self, mocker, tmpdir, fake_filesystem):
write_file(cloud_cfg_file, safeyaml.dumps(cfg))
yield copy.deepcopy(cfg), cloud_cfg_file

def test_main_init_run_net_runs_modules(self, cloud_cfg, capsys, tmpdir):
@pytest.mark.parametrize(
"provide_file_arg,expected_file_content",
(
pytest.param(False, "blah", id="write_files_from_base_config"),
pytest.param(
True,
"override",
id="write_files_from_supplemental_file_arg",
),
),
)
def test_main_init_run_net_runs_modules(
self,
provide_file_arg,
expected_file_content,
cloud_cfg,
capsys,
tmpdir,
):
"""Modules like write_files are run in 'net' mode."""
if provide_file_arg:
supplemental_config_file = tmpdir.join("custom.yaml")
supplemental_config_file.write(
EXTRA_CLOUD_CONFIG.format(tmpdir=tmpdir)
)
files = [open(supplemental_config_file)]
else:
files = None
cmdargs = MyArgs(
debug=False,
files=None,
files=files,
force=False,
local=False,
reporter=None,
Expand All @@ -91,6 +126,7 @@ def test_main_init_run_net_runs_modules(self, cloud_cfg, capsys, tmpdir):
expected_logs = [
"network config is disabled by fallback", # apply_network_config
"my net debug info", # netinfo.debug_info
"PID [42] started cloud-init 'init'",
]
stderr = capsys.readouterr().err
for log in expected_logs:
Expand Down

0 comments on commit 55de797

Please sign in to comment.