auberge 0.1.4

CLI tool for managing self-hosted infrastructure with Ansible
# Implementation Plan: Add --force/-f Flag for CI/CD Automation

**Issue:** #16
**Objective:** Add `--force/-f` flag to skip interactive confirmation prompts for CI/CD and automation scenarios

## Design Decisions

### 1. Flag Behavior (clig.dev compliant)

- Use `-f`/`--force` following clig.dev convention for bypassing confirmations
- Warnings still display (security-critical information remains visible)
- Only interactive y/N prompts are skipped
- Exit code 0 when --force used (assumes user completed prerequisites)

### 2. Bootstrap IP Address Handling

- Add optional `--ip` flag to Bootstrap command
- When `--force` is used, `--ip` becomes required
- IP validation: Basic IPv4/IPv6 format check (fail fast on invalid format)
- Keep interactive prompt when --force is not used

### 3. Command Coverage

Apply --force flag to:

- `ansible run` - Skip 3 confirmation prompts (firewall, cloudflare, port 853)
- `ansible check` - Skip same prompts (calls run_ansible_run internally)
- `ansible bootstrap` - Skip bootstrap firewall prompt

### 4. Security Considerations

- Warnings remain visible even with --force
- User assumes responsibility for prerequisites when using --force
- No reduction in information provided, only automation of "yes" response
- Document security implications in help text and README

## File Layout

### Files to Modify

- `src/commands/ansible.rs` - Add flags to command definitions, update prompt logic
- `Cargo.toml` - Ensure `regex` crate available for IP validation

### Files to Create

- None (inline implementation)

## Class/Method/Function Structure

### Command Struct Updates

```rust
enum AnsibleCommands {
    Run {
        // existing fields...
        force: bool, // NEW
    },
    Check {
        // existing fields...
        force: bool, // NEW
    },
    Bootstrap {
        host: String,
        port: u16,
        ip: Option<String>, // NEW
        force: bool,        // NEW
    },
}
```

### Helper Functions

1. `validate_ip(ip: &str) -> Result<()>`
   - Validates IPv4/IPv6 format using regex
   - Returns descriptive error on invalid format

2. `prompt_for_ip(host_name: &str) -> Result<String>`
   - Extracted from existing inline prompt code
   - Handles interactive IP input

3. Updated function signatures:
   - `run_ansible_run(..., force: bool)` - NEW parameter
   - `run_ansible_check(..., force: bool)` - NEW parameter
   - `run_ansible_bootstrap(..., ip: Option<String>, force: bool)` - NEW parameters

## Function Pseudocode

### validate_ip(ip: &str) -> Result<()>

```
1. Define IPv4 regex: ^(\d{1,3}\.){3}\d{1,3}$
2. Define IPv6 regex: simplified pattern for hex segments
3. Check if IP matches either pattern
4. If match:
   - For IPv4: validate each octet is 0-255
   - Return Ok(())
5. If no match:
   - Return Err with message "Invalid IP format: {ip}"
```

### prompt_for_ip(host_name: &str) -> Result<String>

```
1. Print "Enter IP address for {host_name}: "
2. Flush stdout
3. Read line from stdin
4. Return trimmed input
5. Wrap errors with "Failed to read IP address"
```

### run_ansible_bootstrap(..., ip: Option<String>, force: bool)

```
1. Get host from inventory
2. Check bootstrap playbook exists
3. Determine IP address:
   - If ip.is_some():
     * Validate IP format (fail if invalid)
     * Use provided IP
   - Else if force:
     * Return error "--ip is required when using --force"
   - Else:
     * Call prompt_for_ip() (interactive)
4. Show bootstrap firewall warning if is_fresh_bootstrap
5. If !force and is_fresh_bootstrap:
   - Show interactive y/N prompt
   - Exit if user doesn't confirm
6. Else if force and is_fresh_bootstrap:
   - Show "🤖 Skipping confirmation (--force enabled)"
7. Run bootstrap playbook
8. Return success/failure
```

### run_ansible_run(..., force: bool)

```
1. Select host and playbook (existing logic)
2. Determine playbook type (bootstrap/apps/auberge)
3. For each applicable warning:
   - Show warning text (always)
   - If !force:
     * Show y/N prompt
     * Exit if user doesn't confirm
   - Else:
     * Show "🤖 Skipping confirmation (--force enabled)"
4. Run playbook (existing logic)
5. Return success/failure
```

## TDD Plan

### Unit Tests

Location: `src/commands/ansible.rs` (inline tests module)

1. **test_validate_ip_valid_ipv4**
   - Input: "192.168.1.1", "10.0.0.1", "172.16.0.1"
   - Expected: Ok(())

2. **test_validate_ip_valid_ipv6**
   - Input: "::1", "2001:db8::1", "fe80::1"
   - Expected: Ok(())

3. **test_validate_ip_invalid_format**
   - Input: "999.999.999.999", "not-an-ip", "192.168.1", "192.168.1.1.1"
   - Expected: Err with descriptive message

4. **test_validate_ip_edge_cases**
   - Input: "", " ", "localhost", "192.168.1.1 "
   - Expected: Err for empty/spaces/localhost, Ok for trimmed input

### Integration Tests

Run manually in test environment:

1. **Test: Run with --force (all prompts skipped)**
   ```bash
   ./auberge ansible run --host test --playbook playbooks/apps.yml --force
   ```
   Expected: Warnings display, no prompts, proceeds to execution

2. **Test: Bootstrap with --force and --ip**
   ```bash
   ./auberge ansible bootstrap test --ip 192.168.1.100 --force
   ```
   Expected: Warning displays, no prompts, uses provided IP

3. **Test: Bootstrap with --force without --ip (error)**
   ```bash
   ./auberge ansible bootstrap test --force
   ```
   Expected: Error "--ip is required when using --force"

4. **Test: Bootstrap with invalid --ip**
   ```bash
   ./auberge ansible bootstrap test --ip invalid-ip --force
   ```
   Expected: Error "Invalid IP format: invalid-ip"

5. **Test: Interactive mode unchanged**
   ```bash
   ./auberge ansible run --host test --playbook playbooks/bootstrap.yml
   ```
   Expected: Warnings + prompts displayed, waits for y/N input

6. **Test: Check with --force**
   ```bash
   ./auberge ansible check --host test --playbook playbooks/apps.yml --force
   ```
   Expected: Warnings display, no prompts, runs in check mode

## Commit Plan

### Phase 1: Foundation

#### Commit 1: `feat(cli): add force flag to ansible commands`

- Phase: Foundation
- Layers: backend
- Changes:
  - Add `force: bool` field to Run, Check, Bootstrap structs with clap attribute
  - Add help text: "Skip confirmation prompts (for CI/CD automation)"
  - Update function signatures to accept force parameter
  - Pass force through call chain (no behavioral changes yet)
- Tests:
  - Cargo build succeeds
  - `./auberge ansible run --help` shows --force/-f flag
  - `./auberge ansible bootstrap --help` shows --force/-f flag
  - Commands work without --force (existing behavior)

#### Commit 2: `feat(cli): add ip flag to bootstrap command`

- Phase: Foundation
- Layers: backend
- Changes:
  - Add `ip: Option<String>` to Bootstrap struct with clap attribute
  - Extract IP prompt logic into `prompt_for_ip()` helper function
  - Update run_ansible_bootstrap signature to accept ip parameter
  - Use provided IP if present, otherwise call prompt (existing behavior)
- Tests:
  - Cargo build succeeds
  - `./auberge ansible bootstrap --help` shows --ip flag
  - Bootstrap works with --ip flag (skips interactive prompt)
  - Bootstrap works without --ip flag (shows interactive prompt)

### Phase 2: Validation

#### Commit 3: `feat(cli): add ip address format validation`

- Phase: Validation
- Layers: backend
- Changes:
  - Add `regex` dependency to Cargo.toml (if not present)
  - Implement `validate_ip()` function with IPv4/IPv6 regex validation
  - Add unit tests in `#[cfg(test)]` module
- Tests:
  - Unit: test_validate_ip_valid_ipv4 (passes)
  - Unit: test_validate_ip_valid_ipv6 (passes)
  - Unit: test_validate_ip_invalid_format (passes)
  - Unit: test_validate_ip_edge_cases (passes)
  - Cargo test succeeds

#### Commit 4: `feat(cli): enforce ip requirement when force is used`

- Phase: Validation
- Layers: backend
- Changes:
  - Update run_ansible_bootstrap IP handling logic
  - If force && ip.is_none(): return error
  - If ip.is_some(): validate_ip() then use
  - If !force && ip.is_none(): prompt_for_ip()
- Tests:
  - Integration: `bootstrap --force --ip 192.168.1.1` (succeeds)
  - Integration: `bootstrap --force` (fails with clear error)
  - Integration: `bootstrap --ip invalid` (fails validation)
  - Integration: `bootstrap` (interactive prompt works)

### Phase 3: Implementation

#### Commit 5: `feat(cli): skip bootstrap firewall prompt with force flag`

- Phase: Implementation
- Layers: backend
- Changes:
  - Update lines 97-121 in run_ansible_run
  - Wrap prompt in `if !force { ... }` conditional
  - Add else branch showing "🤖 Skipping confirmation (--force enabled)"
  - Warning text still displays in both branches
- Tests:
  - Integration: `run --playbook bootstrap.yml --force` (no prompt)
  - Integration: `run --playbook bootstrap.yml` (shows prompt)
  - Verify warnings display in both cases

#### Commit 6: `feat(cli): skip cloudflare prompt with force flag`

- Phase: Implementation
- Layers: backend
- Changes:
  - Update lines 126-157 in run_ansible_run
  - Apply same conditional pattern as commit 5
  - Consistent messaging for skipped prompts
- Tests:
  - Integration: `run --playbook apps.yml --force` (skips cloudflare prompt)
  - Integration: `run --playbook auberge.yml --force` (skips cloudflare prompt)
  - Integration: `run --playbook apps.yml` (shows cloudflare prompt)
  - Verify warnings display in both cases

#### Commit 7: `feat(cli): skip port 853 firewall prompt with force flag`

- Phase: Implementation
- Layers: backend
- Changes:
  - Update lines 159-182 in run_ansible_run
  - Apply same conditional pattern
  - Complete implementation of all prompt skips
- Tests:
  - Integration: `run --playbook apps.yml --force` (all 3 prompts skipped)
  - Integration: `run --playbook auberge.yml --force` (all prompts skipped)
  - Verify all warnings still display
  - Verify command proceeds without user input

### Phase 4: Documentation

#### Commit 8: `docs(cli): document force flag usage and security notes`

- Phase: Documentation
- Layers: backend
- Changes:
  - Update README with --force flag examples
  - Add CI/CD automation section
  - Document security considerations
  - Add troubleshooting entry for "locked out after bootstrap"
- Tests:
  - Verify examples in README are accurate
  - Verify all commands shown in docs actually work

### Phase 5: Final Review

- Do a PR review of the changes in the branch as if you are another engineer reviewing
- Decide which PR recommendations to follow, apply recommended changes
- Iterate on tests to ensure everything is passing
- Remove any unnecessary comments in the code