Skip to content

refactor: playbooks file output - #63

Merged
loadinglucian merged 4 commits into
mainfrom
refactor/playbooks-file-output
Nov 2, 2025
Merged

refactor: playbooks file output#63
loadinglucian merged 4 commits into
mainfrom
refactor/playbooks-file-output

Conversation

@loadinglucian

@loadinglucian loadinglucian commented Nov 2, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes

    • More reliable playbook execution using file-based YAML output, improved parsing and clearer runtime error reporting.
    • Enhanced permission detection and distro-aware operations to reduce failures across server types.
    • Clearer separation of progress output for improved diagnostics.
  • Style

    • Cosmetic console spacing and more explicit progress messages for readability.
  • Documentation

    • Expanded playbook guidelines with structured templates, idempotency patterns, validation guidance, distro handling, and output/error conventions.

Playbooks now write YAML output to DEPLOYER_OUTPUT_FILE instead of
stdout, allowing progress messages to be displayed separately from
structured data. This improves UX by showing real-time progress while
keeping YAML parsing reliable.

Changes:
- PlaybookHelpersTrait generates temp file and passes as env var
- Display playbook stdout as progress messages
- Read YAML from file after execution and clean up
- Update server-info.sh to write to DEPLOYER_OUTPUT_FILE
- Refactor server-info.sh to use main() function pattern
- Add error checking for file write operations
Document the new playbook pattern where YAML output is written to
DEPLOYER_OUTPUT_FILE instead of stdout. This allows progress messages
to be displayed separately from structured data.

Updates:
- Add Structure section with main() function pattern
- Document DEPLOYER_OUTPUT_FILE environment variable
- Update Output section with file-based pattern
- Add error handling patterns for file writes
- Update complete example to match new pattern
@coderabbitai

coderabbitai Bot commented Nov 2, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

The PR restructures playbook guidelines, changes playbook output to write parsable YAML to a designated remote file, updates the trait to read/parse that remote YAML tempfile, and modifies the server-info playbook to validate environment, improve permission handling, rename a function, and write progress + YAML to DEPLOYER_OUTPUT_FILE. A cosmetic blank line was added to server info command output.

Changes

Cohort / File(s) Summary
Playbook guidelines
\.cursor/rules/06-playbooks.mdc
Restructured playbook guidance: introduces a Bash template (shebang, set -o pipefail, validation, main()/helpers), formalizes DEPLOYER_* env vars, replaces stdout-only YAML guidance with "Return parsable YAML output" to a file, details distro handling, idempotency, and error-handling patterns.
Playbook execution (PHP trait)
app/Traits/PlaybookHelpersTrait.php
Use a unique remote temp file and inject DEPLOYER_OUTPUT_FILE into playbook env; capture and stream remote stdout/stderr lines as progress; after run, cat then rm the remote output file and parse YAML from its contents; improved error messages and explicit handling for empty or unparsable YAML.
Server info playbook
playbooks/server-info.sh
Add export DEBIAN_FRONTEND=noninteractive; require and validate DEPLOYER_OUTPUT_FILE; rename get_listening_ports()get_listening_services(); refine permission detection (`root
Console output (cosmetic)
app/Console/Server/ServerInfoCommand.php
Insert a blank line in console output after retrieving server info for visual separation.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as ServerInfoCommand
    participant Trait as PlaybookHelpersTrait
    participant Remote as Remote Host
    participant Script as server-info.sh

    User->>CLI: request server info
    CLI->>Trait: executePlaybook(playbook, vars)
    activate Trait
    Trait->>Trait: create remote temp file path (DEPLOYER_OUTPUT_FILE)
    Trait->>Remote: ssh/run playbook with DEPLOYER_OUTPUT_FILE + env
    activate Remote
    Remote->>Script: start server-info.sh
    activate Script
    Script->>Script: validate DEPLOYER_OUTPUT_FILE
    Script->>Script: detect distro & permissions
    Script->>Script: discover services (get_listening_services)
    Script->>Remote: write YAML lines to DEPLOYER_OUTPUT_FILE (with checks)
    Script->>Remote: emit progress to stdout/stderr
    Script-->>Remote: exit with code
    deactivate Script
    Remote->>Trait: stream stdout/stderr lines + exit code
    deactivate Remote
    Trait->>Remote: cat DEPLOYER_OUTPUT_FILE
    activate Remote
    Remote->>Trait: return YAML content
    deactivate Remote
    Trait->>Remote: rm DEPLOYER_OUTPUT_FILE
    Trait->>Trait: parse YAML content
    Trait->>CLI: return parsed array or failure
    deactivate Trait
    CLI->>User: print a blank line and formatted info
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • app/Traits/PlaybookHelpersTrait.php — verify remote temp file lifecycle, race conditions, streaming behavior, and all error/exception branches (empty content, parse failures).
  • playbooks/server-info.sh — validate DEPLOYER_OUTPUT_FILE checks, per-line write error handling, permission detection and sudo behavior, and idempotency of service detection.
  • Integration — ensure stdout/stderr progress lines don’t contaminate YAML output and remote cleanup occurs on all exit paths.

Possibly related PRs

  • bigpixelrocket/deployer-php#62 — overlaps changes to playbooks/server-info.sh and app/Traits/PlaybookHelpersTrait.php, affecting playbook execution/output handling.

Poem

🐰
I hopped a temp path through the night,
Wrote YAML safe and tidy, tucked up tight,
I checked permissions, named each service bright,
Progress hummed aloud, output filed just right,
Now playbooks nap until the next delight.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "refactor: playbooks file output" directly aligns with the primary architectural change across the changeset. The core modification involves shifting how playbooks output results from in-band YAML (stdout) to file-based output via DEPLOYER_OUTPUT_FILE. This change is consistently implemented across multiple files including the playbook guidelines documentation, the trait that executes playbooks, and the actual playbook script. The title is concise, specific, and clear enough that a teammate scanning the repository history would immediately understand that this PR refactors the output mechanism for playbooks to use file-based delivery.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/playbooks-file-output

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
playbooks/server-info.sh (1)

68-70: Keep sudo non-interactive and single-shot

run_cmd still relies on the [[ … ]] && "$@" || sudo "$@" pattern. When a root-level call fails we end up invoking sudo anyway, and without -n the command can block waiting for a password if a PTY gets allocated or the sudoers policy differs from the initial probe. That violates the “never prompt” rule and makes failures harder to diagnose. Please branch explicitly and force sudo -n so we fail fast while remaining non-interactive.

 run_cmd() {
-	[[ $DEPLOYER_PERMS == 'root' ]] && "$@" || sudo "$@"
+	if [[ $DEPLOYER_PERMS == 'root' ]]; then
+		"$@"
+	else
+		sudo -n "$@"
+	fi
 }

As per coding guidelines

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4e176 and 3477629.

📒 Files selected for processing (4)
  • .cursor/rules/06-playbooks.mdc (3 hunks)
  • app/Console/Server/ServerInfoCommand.php (1 hunks)
  • app/Traits/PlaybookHelpersTrait.php (3 hunks)
  • playbooks/server-info.sh (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use methods: inline if a method is called only once
Cache computed values: initialize expensive calculations in the constructor
Avoid method call overhead: prefer direct property access when appropriate

**/*.php: Adhere to PSR-12, enable strict_types, and leverage PHP 8.x features (union types, match, attributes, readonly)
Use explicit return types, including generic-like annotations where applicable (e.g., Collection<int, User>)
Prefer Symfony components (e.g., Filesystem, Process) over native PHP functions for testability
Always import classes via use statements; only use root FQDNs for core exceptions (e.g., \InvalidArgumentException, \RuntimeException). Do not use inline FQDNs for non-root namespaces
Create objects via $container->build(ClassName::class) everywhere except DTOs, value objects, and pure data structures
Use minimalist DocBlocks documenting description, parameters, and return types for classes and functions
Follow the specified comment structure with section headers/subheaders and spacing; remove obsolete comments with removed code

Files:

  • app/Console/Server/ServerInfoCommand.php
  • app/Traits/PlaybookHelpersTrait.php
**/*Command.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

**/*Command.php: Commands handle user interaction (I/O), orchestration, styling, error formatting, and prompts using SymfonyStyle
Commands must not contain business logic; delegate to Services
Commands must not invoke other commands (no proxy commands)

Files:

  • app/Console/Server/ServerInfoCommand.php
**/*{Command,Service}.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

All dependencies should be expressed in constructor signatures; avoid circular dependencies

Files:

  • app/Console/Server/ServerInfoCommand.php
🧠 Learnings (3)
📚 Learning: 2025-10-24T19:59:22.873Z
Learnt from: CR
Repo: bigpixelrocket/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-10-24T19:59:22.873Z
Learning: Applies to **/*Command.php : Commands handle user interaction (I/O), orchestration, styling, error formatting, and prompts using SymfonyStyle

Applied to files:

  • app/Traits/PlaybookHelpersTrait.php
📚 Learning: 2025-10-24T20:03:23.947Z
Learnt from: CR
Repo: bigpixelrocket/deployer-php PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2025-10-24T20:03:23.947Z
Learning: Applies to **/*.{md,mdc} : Target rule density per file: Project-specific (Playbooks) 100–140 lines

Applied to files:

  • .cursor/rules/06-playbooks.mdc
📚 Learning: 2025-10-24T19:59:22.873Z
Learnt from: CR
Repo: bigpixelrocket/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-10-24T19:59:22.873Z
Learning: Applies to bin/deployer : Application entry point is bin/deployer

Applied to files:

  • .cursor/rules/06-playbooks.mdc
🧬 Code graph analysis (2)
app/Console/Server/ServerInfoCommand.php (2)
app/Services/IOService.php (1)
  • writeln (463-469)
app/Traits/ServerInfoTrait.php (1)
  • displayServerInfo (40-80)
app/Traits/PlaybookHelpersTrait.php (2)
app/Services/IOService.php (3)
  • writeln (463-469)
  • error (498-501)
  • promptSpin (439-452)
app/Services/SSHService.php (1)
  • executeCommand (69-86)
🔇 Additional comments (2)
app/Console/Server/ServerInfoCommand.php (1)

72-73: Output spacing reads better

The extra blank line keeps the server info section visually separated from the preceding status checks. Nicely done.

app/Traits/PlaybookHelpersTrait.php (1)

104-147: Robust remote YAML retrieval

Appreciate how the new block wraps the remote cat/remove inside a spinner, checks for empty payloads, and surfaces parse errors with the raw YAML. This makes diagnosing playbook failures much easier.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
playbooks/server-info.sh (2)

90-90: Remove redundant DEBIAN_FRONTEND export.

Line 16 already exports DEBIAN_FRONTEND=noninteractive globally. The re-export at line 90 is unnecessary and adds noise.

	case $distro in
		debian)
-			export DEBIAN_FRONTEND=noninteractive
			run_cmd apt-get update -q 2> /dev/null

109-115: Clarify the intent of the 'else' branches in ss/netstat command construction.

The 'else' branches (lines 114–115, 138–139) fallback to running ss/netstat without sudo when DEPLOYER_PERMS is neither 'root' nor 'sudo'. However, due to the early return at line 84 when perms=='none', these branches should theoretically never execute in normal flow. If this is intentional as a safety fallback, a comment would improve clarity.

	if [[ $DEPLOYER_PERMS == 'root' ]]; then
		cmd='ss'
	elif [[ $DEPLOYER_PERMS == 'sudo' ]]; then
		cmd='sudo ss'
	else
+		# Fallback: attempt without sudo (limited output but non-blocking)
		cmd='ss'
	fi

Also applies to: 133-139

.cursor/rules/06-playbooks.mdc (1)

177-191: Helper functions section documents the standard run_cmd() pattern, but lacks guard for 'none' case.

The documentation correctly explains the run_cmd() helper and -n flag behavior. However, both documentation and actual implementation (server-info.sh line 69-74) leave the DEPLOYER_PERMS='none' case implicit. Consider adding an explicit guard as mentioned in the server-info.sh review.

To align documentation with defensive coding:

 run_cmd() {
+    if [[ $DEPLOYER_PERMS == 'none' ]]; then
+        echo "Error: run_cmd called without elevated permissions" >&2
+        exit 1
+    fi
     if [[ $DEPLOYER_PERMS == 'root' ]]; then
         "$@"
     else
-        sudo -n "$@"
+        sudo -n "$@"  # -n ensures non-interactive operation
     fi
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3477629 and be265e3.

📒 Files selected for processing (2)
  • .cursor/rules/06-playbooks.mdc (3 hunks)
  • playbooks/server-info.sh (5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-24T19:59:22.873Z
Learnt from: CR
Repo: bigpixelrocket/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-10-24T19:59:22.873Z
Learning: Applies to bin/deployer : Application entry point is bin/deployer

Applied to files:

  • .cursor/rules/06-playbooks.mdc
🔇 Additional comments (9)
playbooks/server-info.sh (4)

179-202: Robust YAML output flow with proper error handling.

The output sequence correctly writes the YAML header, appends service lines with per-write error checks, and handles the empty services case with an explicit placeholder. The pattern prevents partial/corrupt output and aligns with documentation guidelines.


105-105: Function rename is internally consistent.

The rename from get_listening_ports() to get_listening_services() is applied at both definition (line 105) and call site (line 195). The new name better reflects its actual behavior of discovering listening services, not just ports.

Also applies to: 195-195


167-173: Progress messages are clear and consistent with guidelines.

The use of checkmark indicators and ellipsis syntax aligns with the documentation patterns and provides good user feedback during script execution.


69-74: ****

The code is not fragile. The early return guard in ensure_tools() at line 84 ([[ $perms == 'none' ]] && return 0) is sufficient and properly structured. This guard exits the function before the case statement that contains all run_cmd() invocations (lines 91–92, 95–96). Since run_cmd() is only called within ensure_tools() and after this guard, it is structurally guaranteed to never execute when DEPLOYER_PERMS='none'. The suggested defensive check in run_cmd() is unnecessary and would obscure the actual control flow logic.

Likely an incorrect or invalid review comment.

.cursor/rules/06-playbooks.mdc (5)

20-66: Structure section is well-defined with clear template pattern.

The standardized playbook structure using main() function, mandatory set -o pipefail, DEBIAN_FRONTEND export, and DEPLOYER_OUTPUT_FILE validation provides a solid foundation. The template clearly shows YAML output with error handling.


67-77: Environment variables guidance correctly distinguishes detection vs. provisioning playbooks.

The documentation accurately separates validation concerns: detection playbooks (like server-info.sh) validate only DEPLOYER_OUTPUT_FILE, while provisioning playbooks validate DEPLOYER_DISTRO and DEPLOYER_PERMS. This aligns with the actual server-info.sh implementation.


121-142: Idempotency guidance is clear with practical examples of correct and incorrect patterns.

The CORRECT examples (existence checks before resource creation/modification) and WRONG examples (useradd duplication, file append duplication) provide clear guardrails. server-info.sh's ensure_tools() appropriately uses command -v checks before installation (line 85).


144-175: Error handling section provides clear guidance on validation vs. runtime errors, and emphasizes file write checks.

The distinction between stdout (validation errors) and stderr (runtime errors), combined with the set -o pipefail pattern and explicit YAML write checks, is well-explained and properly implemented in server-info.sh.


227-299: Complete example is comprehensive and properly templates all documented patterns.

The example integrates validation, helper functions, distro-specific branching, idempotency checks, and proper YAML output with error handling. The reference to playbooks/server-info.sh grounds the documentation in actual implementation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
playbooks/server-info.sh (1)

162-186: YAML format will parse correctly—consider inline format for clarity.

The format ports:\n {} is valid YAML and will parse correctly with Symfony's Yaml parser because the explicit {} syntax is unambiguous. Symfony Yaml Component recognizes explicit empty mapping {} and parses it as an empty array.

However, the inline format ports: {} is the recommended approach per Symfony documentation for clarity and consistency. Consider standardizing to ports: {} (single line, line 165) for better readability and adherence to conventional YAML formatting practices.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be265e3 and f850e3e.

📒 Files selected for processing (1)
  • playbooks/server-info.sh (7 hunks)
🔇 Additional comments (3)
playbooks/server-info.sh (3)

1-22: Header, environment setup, and validation are solid. Environment variable validation before use prevents silent failures downstream. The documentation clearly specifies the YAML output contract.


68-98: run_cmd() and ensure_tools() logic is correct. DEPLOYER_PERMS is set before early returns, and the script's execution order (line 157 calls ensure_tools() before line 178 calls get_listening_services()) ensures the variable is always initialized before use.


104-178: get_listening_services() function and service output logic look correct. Process substitution, sort/uniq deduplication, and per-service error checking are all sound. Regex patterns for parsing ss/netstat output are appropriate.

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