Config Actions: Add Common Framework for Repository-Wide Save and Deploy Handling - #498
Config Actions: Add Common Framework for Repository-Wide Save and Deploy Handling#498AKDRG wants to merge 7 commits into
Conversation
217ee4f to
9202c39
Compare
allenrobel
left a comment
There was a problem hiding this comment.
Code review
Review of the common config-actions framework. Findings posted inline; the switch-module wiring on top of this framework was reviewed separately on #499.
🤖 Generated with Claude Code
| resource_deploy_overrides=resource_overrides, | ||
| ) | ||
|
|
||
| if state in policy.read_only_states and not provided: |
There was a problem hiding this comment.
The read-only-state force-off only fires when config_actions is omitted entirely (not provided). If a task in gathered state supplies config_actions with only a non-write suboption — e.g. config_actions: {type: switch} — then provided is true, this branch is skipped, and Ansible's default fill-in leaves save/deploy at the policy defaults (True for five of the six shipped policies). validate_config_actions() doesn't catch it either: its read-only check only fires when save/deploy are explicitly named in the raw args (explicit_options & {"save", "deploy"}), not when they're merely default-filled.
So parse_config_actions() can return ConfigActions(deploy=True) in a read-only state with no error — a caller trusting actions.deploy would deploy during gathered. Suggest forcing save/deploy off in read-only states regardless of provided (unless explicitly set, which validation already rejects), and adding a test for the type-only-in-gathered case — the new suite only covers the omitted-dict and explicitly-named cases.
| policy=self.config_actions_policy, | ||
| backend=selected_backend, | ||
| ) | ||
| return controller.execute(actions, context) |
There was a problem hiding this comment.
The controller's skip paths (no_fabrics, ineligible, actions_disabled, no_targets) return status: "skipped" but nothing surfaces that to the user — no rest_send.warn(), and this facade returns the result unchanged. This is the same visibility gap fixed in this file in #491, where the switchless-fabric skip in execute_config_actions() was switched from logger.info() (invisible unless ND_LOGGING_CONFIG is set) to self.rest_send.warn(...).
The controller is transport-agnostic so it can't warn itself, which makes this facade the natural place: after controller.execute(), translate skipped steps into self.rest_send.warn(...) so integrations get the surfaced-skip behavior by default instead of each caller having to remember to do it.
| `type` accepts `resource`, `switch`, and `global`, per the contract in issue #368. The companion per-resource `deploy` key described in that | ||
| issue is not part of this fragment yet: it lives in each module's `config` suboptions and its interaction with `config_actions` (mutually | ||
| exclusive, or gated on `type == "resource"`) is still under discussion on #368. It will be added as a separate fragment once settled. | ||
| This compatibility wrapper delegates to the policy-based implementation in `config_actions.argument_spec`. |
There was a problem hiding this comment.
Two pieces of load-bearing docstring content are dropped here without being re-homed in config_actions/argument_spec.py:
- The allowlist rationale on
_select_options()("An allowlist (rather than an exclude list) is deliberate: options added to a shared fragment in the future must never silently appear in modules that composed the fragment before the addition") — the mechanics survive, but the reason it must stay an allowlist is now undocumented in both the wrapper and the new canonical implementation. - The note that the per-resource
deploykey's interaction withconfig_actions("mutually exclusive, or gated ontype == 'resource'") was still under discussion on Module Support for config_actions + resource level deploy #368. This PR implements one specific resolution (RESOURCE_CONFIG_ACTIONS.resource_interaction = "type_resource_gated") — Module Support for config_actions + resource level deploy #368 is still open, so it would be good to note the decision on that issue and carry the rationale intopolicies.py/argument_spec.pyrather than silently settling it.
|
|
||
| ## Raises | ||
|
|
||
| Exception |
There was a problem hiding this comment.
All four Protocol methods (save here, deploy_global L48, deploy_switches L59, deploy_resources L70) list a bare Exception under ## Raises. CLAUDE.md: "use ### ExceptionType subheadings listing conditions as a Markdown list".
|
|
||
| ## Raises | ||
|
|
||
| Exception |
There was a problem hiding this comment.
execute() (here) and _deploy() (L127) list a bare Exception under ## Raises — same format issue as backend.py. CLAUDE.md: "use ### ExceptionType subheadings listing conditions as a Markdown list".
Related Issue(s)
Related to #368
Proposed Changes
config_actionsframework for parsing, validating, planning, and executing save/deploy intent.config_actionsthrough module-family policiesconfig_actions: {}deploy=true/save=falserestrictions where requiredconfig_actions.type: resourceConfigActionsControllerand backend protocol so module-specific integrations can plug in their own save/deploy endpoint implementations without changing the common framework.ConfigActionsMixinfacade for orchestrators that need to execute normalized config action plans.nd_argument_specs.config_actions_spec()to delegate to the policy-based implementation while preserving compatibility with existing call patterns.Test Notes
Ran focused unit tests:
PYENV_VERSION=ndfclab PYTHONPATH=$PWD/collections python -m pytest -q tests/unit/module_utils/config_actions tests/unit/module_utils/orchestrators/test_config_actions_mixin.pyResult:
52 passed in 0.25sCisco Nexus Dashboard Version
N/A - common framework only. Module-specific endpoint integrations and live controller validation will be handled in follow-up PRs.
Related ND API Resource Category
Checklist