Shep Playbook Plugin Implementation Plan
Not logged in

Overview

A playbook is Ansible's configuration, deployment, and orchestration language. It contains one or more plays, each targeting a set of hosts and containing tasks to execute. This plugin parses playbook YAML files and returns structured domain types for the host to interpret.

Reference: docs/ansible-schema/playbook.json


Decisions to Make

Question Options
Plugin name shep-plugin-playbook
Capability playbook (single capability)
Scope Parse only, no execution (execution is host/task concern)
Task representation Reference tasks.fbs domain types

Architecture

Relationship to Other Components

playbook.yml ──► playbook plugin ──► Playbook (plays, tasks, handlers)
                                           │
                                           ▼
                              Host orchestration loop
                                           │
                                           ▼
                        ┌──────────────────┴──────────────────┐
                        ▼                                     ▼
              inventory plugin                         task executor
              (resolve hosts)                          (run tasks)

The playbook plugin is parse-only. It transforms YAML into structured domain types. The host owns:


Domain Types

Playbook Structure (from schema analysis)

A playbook is an array of items, each being either:

  1. Play - targets hosts with tasks
  2. Import playbook - includes another playbook file

A Play contains:

FlatBuffers Schema Design

Domain types (flatbuffers/domain/playbook.fbs):

namespace shep.domain;

// A complete playbook
table Playbook {
  plays:[Play];
  imports:[PlaybookImport];
}

// Import another playbook
table PlaybookImport {
  path:string;           // file path to import
  name:string;           // optional name
  vars:[Var];            // vars to pass
  tags:[string];
  when:string;           // conditional
}

// A play targets hosts with tasks
table Play {
  name:string;
  hosts:string;          // host pattern (may contain commas, groups)

  // Task lists
  tasks:[Task];
  handlers:[Task];
  pre_tasks:[Task];
  post_tasks:[Task];

  // Roles
  roles:[RoleInclude];

  // Variables
  vars:[Var];
  vars_files:[string];

  // Privilege escalation
  become:bool;
  become_user:string;
  become_method:string;

  // Execution control
  gather_facts:bool = true;
  strategy:string;
  serial:string;         // can be int, percent, or list - store as string
  max_fail_percentage:int;
  any_errors_fatal:bool;

  // Connection
  connection:string;
  remote_user:string;
  port:int;

  // Filtering
  tags:[string];
  when:string;

  // Environment
  environment:[Var];
}

// Role inclusion in a play
table RoleInclude {
  role:string;           // role name or path
  name:string;           // optional display name
  vars:[Var];
  tags:[string];
  when:string;
  become:bool;
  become_user:string;
}

Capability types (flatbuffers/capabilities/playbook.fbs):

namespace shep.cap.playbook;

include "domain/playbook.fbs";
include "domain/task.fbs";

table PlaybookRequest {
  file_path:string;
  content:[ubyte];
}

table PlaybookResponse {
  playbook:shep.domain.Playbook;
  warnings:[string];
  error:string;
}

Dependencies

This plugin depends on the Task domain types from tasks.fbs. The playbook contains task lists, so we must define Task types first (or concurrently).

Dependency order:

  1. flatbuffers/domain/task.fbs (Task, Block types)
  2. flatbuffers/domain/playbook.fbs (imports task.fbs)
  3. flatbuffers/capabilities/playbook.fbs

Implementation Plan

Phase 1: Domain Schema Definition

Prerequisites: Task domain types must exist (see shep-plugin-tasks-plan.md)

Files to create:

flatbuffers/domain/playbook.fbs
flatbuffers/capabilities/playbook.fbs

Phase 2: Plugin Implementation

Files to create:

plugins/playbook/
├── main.go           # Plugin entry point
├── parser.go         # YAML parsing logic
└── parser_test.go    # Unit tests

Parser responsibilities:

  1. Parse playbook YAML array
  2. Distinguish plays from import_playbook directives
  3. Parse each play's tasks, handlers, pre_tasks, post_tasks
  4. Parse role inclusions
  5. Handle vars, vars_files
  6. Normalize become settings
  7. Build Playbook FlatBuffer

Phase 3: CLI Integration

Add playbook commands:

shep playbook show -f site.yml         # Show playbook structure
shep playbook validate -f site.yml     # Validate playbook syntax
shep p show -f site.yml                # Short alias

Output (show):

PLAYBOOK: site.yml

PLAY: Configure web servers
  hosts: webservers
  gather_facts: true
  become: true

  TASKS:
    - Install nginx (apt)
    - Configure nginx (template)
    - Start nginx (service)

  HANDLERS:
    - Restart nginx (service)

PLAY: Configure database
  hosts: dbservers
  ...

Phase 4: Testing

Test data needed:

testdata/playbook/
├── simple-play.yml           # Single play, few tasks
├── multi-play.yml            # Multiple plays
├── with-handlers.yml         # Handlers and notify
├── with-roles.yml            # Role inclusions
├── with-blocks.yml           # Block/rescue/always
├── import-playbook.yml       # Import directive
└── complex-vars.yml          # vars, vars_files, vars_prompt

Milestones

  1. [ ] M1: Task schemas exist — Prerequisite from tasks plugin
  2. [ ] M2: Playbook schemas — FlatBuffers compile, Go code generates
  3. [ ] M3: Plugin skeleton — Handshake and capability routing
  4. [ ] M4: Play parsing — Parse plays with hosts, tasks
  5. [ ] M5: Full parsing — Roles, handlers, vars, blocks
  6. [ ] M6: CLI integrationshep playbook show works
  7. [ ] M7: Tests passing — Unit and integration tests

Out of Scope


Notes

Import vs Include

Ansible distinguishes:

For the parser plugin, we only handle import_playbook since it's static. The host handles dynamic includes during execution.

Blocks

Blocks (block, rescue, always) are parsed as part of task lists. They contain nested task lists. The Task domain type must support blocks.

Serial and Strategy

These affect execution parallelism but are host concerns. The parser just captures the values; the host interprets them.