package main
import (
"bufio"
"os"
"shep/internal/fbs/shep"
"shep/internal/protocol"
)
func main() {
stdin := bufio.NewReader(os.Stdin)
stdout := os.Stdout
// Send handshake to host with declared capabilities
capabilities := []protocol.CapabilityInfo{
{Name: "playbook", Version: 1},
}
handshake := protocol.BuildHandshake("playbook", capabilities)
if err := protocol.WriteMessage(stdout, protocol.KindHandshake, handshake); err != nil {
os.Exit(1)
}
// Wait for handshake response
kind, data, err := protocol.ReadMessage(stdin)
if err != nil {
os.Exit(1)
}
if kind != protocol.KindHandshakeResponse {
os.Exit(1)
}
resp, err := protocol.ParseHandshakeResponse(data)
if err != nil {
os.Exit(1)
}
if resp.Status() != shep.StatusOk {
os.Exit(1)
}
// Main message loop - handle capability invocations
for {
kind, data, err := protocol.ReadMessage(stdin)
if err != nil {
break
}
switch kind {
case protocol.KindEnvelope:
handleEnvelope(stdout, data)
case protocol.KindShutdown:
return
}
}
}
func handleEnvelope(stdout *os.File, data []byte) {
env, err := protocol.ParseEnvelope(data)
if err != nil {
return
}
capability := string(env.Capability())
correlationID := string(env.CorrelationId())
payload := env.PayloadBytes()
// Route to capability handler
var responsePayload []byte
var responseErr string
switch capability {
case "playbook":
responsePayload, responseErr = handlePlaybook(stdout, payload)
default:
responseErr = "unknown capability: " + capability
}
// Send response envelope
response := protocol.BuildEnvelope(capability, correlationID, true, responseErr, responsePayload)
protocol.WriteMessage(stdout, protocol.KindEnvelope, response)
}
func handlePlaybook(stdout *os.File, payload []byte) ([]byte, string) {
// Parse the request
req, err := ParsePlaybookRequest(payload)
if err != nil {
return nil, "failed to parse request: " + err.Error()
}
// Log what we're doing
logMsg := protocol.BuildLog(shep.LogLevelInfo, "parsing playbook: "+req.FilePath)
protocol.WriteMessage(stdout, protocol.KindLog, logMsg)
// Get the content to parse
var content []byte
if len(req.Content) > 0 {
content = req.Content
} else if req.FilePath != "" {
content, err = os.ReadFile(req.FilePath)
if err != nil {
return BuildPlaybookErrorResponse("failed to read file: " + err.Error()), ""
}
} else {
return BuildPlaybookErrorResponse("no file path or content provided"), ""
}
// Parse the YAML playbook
playbook, warnings, err := ParseYAMLPlaybook(content)
if err != nil {
return BuildPlaybookErrorResponse("failed to parse playbook: " + err.Error()), ""
}
// Build and return the response
return BuildPlaybookResponse(playbook, warnings), ""
}
package main
import (
"encoding/json"
"fmt"
"sort"
"strings"
flatbuffers "github.com/google/flatbuffers/go"
"github.com/goccy/go-yaml"
fbscap "shep/internal/fbs/shep/cap/playbook"
fbsdomain "shep/internal/fbs/shep/domain"
)
// PlaybookRequest is the parsed request from the host.
type PlaybookRequest struct {
FilePath string
Content []byte
}
// ParsePlaybookRequest parses a FlatBuffer PlaybookRequest.
func ParsePlaybookRequest(data []byte) (*PlaybookRequest, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty request data")
}
req := fbscap.GetRootAsPlaybookRequest(data, 0)
return &PlaybookRequest{
FilePath: string(req.FilePath()),
Content: req.ContentBytes(),
}, nil
}
// Playbook represents a parsed playbook.
type Playbook struct {
Plays []Play
Imports []PlaybookImport
}
// PlaybookImport represents an import_playbook directive.
type PlaybookImport struct {
Path string
Name string
Vars map[string]string
Tags []string
When string
}
// Play represents a play in a playbook.
type Play struct {
Name string
Hosts string
Tasks []Task
Handlers []Task
PreTasks []Task
PostTasks []Task
Roles []RoleInclude
Vars map[string]string
VarsFiles []string
// Privilege escalation
Become bool
BecomeUser string
BecomeMethod string
// Execution control
GatherFacts bool
Strategy string
Serial string
MaxFailPercentage int
AnyErrorsFatal bool
// Connection
Connection string
RemoteUser string
Port int
// Filtering
Tags []string
When string
// Environment
Environment map[string]string
}
// RoleInclude represents a role inclusion.
type RoleInclude struct {
Role string
Name string
Vars map[string]string
Tags []string
When string
Become bool
BecomeUser string
}
// Task represents a task in a play.
type Task struct {
Name string
Module string
Args map[string]string
FreeFormArgs string
Action string
// Control flow
When string
Loop string
LoopControl string
Register string
Until string
Retries int
Delay int
ChangedWhen string
FailedWhen string
// Privilege escalation
Become bool
BecomeUser string
BecomeMethod string
BecomeExe string
BecomeFlags string
// Delegation
DelegateTo string
DelegateFacts bool
// Error handling
IgnoreErrors bool
IgnoreUnreachable bool
AnyErrorsFatal bool
// Notification
Notify []string
Listen []string
// Execution
Async int
Poll int
Throttle int
Timeout int
RunOnce bool
// Metadata
Tags []string
NoLog bool
Diff bool
CheckMode bool
// Environment
Environment map[string]string
Vars map[string]string
// Connection overrides
Connection string
RemoteUser string
Port int
// Block support
IsBlock bool
IsHandler bool
BlockTasks []Task
RescueTasks []Task
AlwaysTasks []Task
}
// knownTaskAttrs are the known task attributes (not module names).
var knownTaskAttrs = map[string]bool{
"name": true, "action": true, "args": true, "local_action": true,
"import_tasks": true, "include_tasks": true, "include_role": true, "import_role": true,
"when": true, "loop": true, "loop_control": true, "with_items": true,
"with_dict": true, "with_fileglob": true, "with_first_found": true,
"with_together": true, "with_subelements": true, "with_sequence": true,
"with_random_choice": true, "with_lines": true, "with_indexed_items": true,
"with_nested": true, "with_flattened": true, "register": true,
"until": true, "retries": true, "delay": true,
"changed_when": true, "failed_when": true,
"become": true, "become_user": true, "become_method": true,
"become_exe": true, "become_flags": true,
"delegate_to": true, "delegate_facts": true,
"ignore_errors": true, "ignore_unreachable": true, "any_errors_fatal": true,
"notify": true, "listen": true,
"async": true, "poll": true, "throttle": true, "timeout": true, "run_once": true,
"tags": true, "no_log": true, "diff": true, "check_mode": true,
"environment": true, "vars": true,
"connection": true, "remote_user": true, "port": true,
"block": true, "rescue": true, "always": true,
}
// ParseYAMLPlaybook parses an Ansible YAML playbook.
func ParseYAMLPlaybook(content []byte) (*Playbook, []string, error) {
var raw []map[string]any
if err := yaml.Unmarshal(content, &raw); err != nil {
return nil, nil, fmt.Errorf("yaml unmarshal: %w", err)
}
playbook := &Playbook{
Plays: []Play{},
Imports: []PlaybookImport{},
}
var warnings []string
for i, item := range raw {
// Check if this is an import_playbook directive
if importPath, ok := item["import_playbook"]; ok {
imp := parsePlaybookImport(item, importPath)
playbook.Imports = append(playbook.Imports, imp)
continue
}
// Otherwise it's a play
play, playWarnings := parsePlay(item, i)
warnings = append(warnings, playWarnings...)
playbook.Plays = append(playbook.Plays, play)
}
return playbook, warnings, nil
}
// ParseYAMLTasks parses a standalone Ansible task file (array of tasks).
// Task files are simpler than playbooks - they're just arrays of tasks
// without the play wrapper (no hosts, handlers, etc.).
func ParseYAMLTasks(content []byte) ([]Task, []string, error) {
var raw []map[string]any
if err := yaml.Unmarshal(content, &raw); err != nil {
return nil, nil, fmt.Errorf("yaml unmarshal: %w", err)
}
var tasks []Task
var warnings []string
for _, item := range raw {
task := parseTask(item, false)
tasks = append(tasks, task)
}
return tasks, warnings, nil
}
func parsePlaybookImport(item map[string]any, importPath any) PlaybookImport {
imp := PlaybookImport{
Path: toString(importPath),
Vars: map[string]string{},
}
if name, ok := item["name"]; ok {
imp.Name = toString(name)
}
if vars, ok := item["vars"]; ok {
imp.Vars = parseVars(vars)
}
if tags, ok := item["tags"]; ok {
imp.Tags = toStringSlice(tags)
}
if when, ok := item["when"]; ok {
imp.When = conditionToString(when)
}
return imp
}
func parsePlay(item map[string]any, index int) (Play, []string) {
var warnings []string
play := Play{
Vars: map[string]string{},
Environment: map[string]string{},
GatherFacts: true, // default
}
// Basic fields
if name, ok := item["name"]; ok {
play.Name = toString(name)
}
if hosts, ok := item["hosts"]; ok {
play.Hosts = toString(hosts)
} else {
warnings = append(warnings, fmt.Sprintf("play %d: missing required 'hosts' field", index))
}
// Tasks
if tasks, ok := item["tasks"]; ok {
play.Tasks = parseTasks(tasks, false)
}
if handlers, ok := item["handlers"]; ok {
play.Handlers = parseTasks(handlers, true)
}
if preTasks, ok := item["pre_tasks"]; ok {
play.PreTasks = parseTasks(preTasks, false)
}
if postTasks, ok := item["post_tasks"]; ok {
play.PostTasks = parseTasks(postTasks, false)
}
// Roles
if roles, ok := item["roles"]; ok {
play.Roles = parseRoles(roles)
}
// Variables
if vars, ok := item["vars"]; ok {
play.Vars = parseVars(vars)
}
if varsFiles, ok := item["vars_files"]; ok {
play.VarsFiles = toStringSlice(varsFiles)
}
// Privilege escalation
if become, ok := item["become"]; ok {
play.Become = toBool(become)
}
if becomeUser, ok := item["become_user"]; ok {
play.BecomeUser = toString(becomeUser)
}
if becomeMethod, ok := item["become_method"]; ok {
play.BecomeMethod = toString(becomeMethod)
}
// Execution control
if gatherFacts, ok := item["gather_facts"]; ok {
play.GatherFacts = toBool(gatherFacts)
}
if strategy, ok := item["strategy"]; ok {
play.Strategy = toString(strategy)
}
if serial, ok := item["serial"]; ok {
play.Serial = toString(serial)
}
if maxFail, ok := item["max_fail_percentage"]; ok {
play.MaxFailPercentage = toInt(maxFail)
}
if anyFatal, ok := item["any_errors_fatal"]; ok {
play.AnyErrorsFatal = toBool(anyFatal)
}
// Connection
if conn, ok := item["connection"]; ok {
play.Connection = toString(conn)
}
if user, ok := item["remote_user"]; ok {
play.RemoteUser = toString(user)
}
if port, ok := item["port"]; ok {
play.Port = toInt(port)
}
// Filtering
if tags, ok := item["tags"]; ok {
play.Tags = toStringSlice(tags)
}
if when, ok := item["when"]; ok {
play.When = conditionToString(when)
}
// Environment
if env, ok := item["environment"]; ok {
play.Environment = parseVars(env)
}
return play, warnings
}
func parseRoles(data any) []RoleInclude {
var roles []RoleInclude
rolesList, ok := data.([]any)
if !ok {
return roles
}
for _, r := range rolesList {
var role RoleInclude
switch v := r.(type) {
case string:
// Simple role name
role.Role = v
case map[string]any:
// Role with options
if name, ok := v["role"]; ok {
role.Role = toString(name)
} else if name, ok := v["name"]; ok {
role.Role = toString(name)
}
if displayName, ok := v["name"]; ok && role.Role != "" {
role.Name = toString(displayName)
}
if vars, ok := v["vars"]; ok {
role.Vars = parseVars(vars)
}
if tags, ok := v["tags"]; ok {
role.Tags = toStringSlice(tags)
}
if when, ok := v["when"]; ok {
role.When = conditionToString(when)
}
if become, ok := v["become"]; ok {
role.Become = toBool(become)
}
if becomeUser, ok := v["become_user"]; ok {
role.BecomeUser = toString(becomeUser)
}
}
if role.Role != "" {
roles = append(roles, role)
}
}
return roles
}
func parseTasks(data any, isHandler bool) []Task {
var tasks []Task
tasksList, ok := data.([]any)
if !ok {
return tasks
}
for _, t := range tasksList {
taskMap, ok := t.(map[string]any)
if !ok {
continue
}
task := parseTask(taskMap, isHandler)
tasks = append(tasks, task)
}
return tasks
}
func parseTask(data map[string]any, isHandler bool) Task {
task := Task{
Args: map[string]string{},
Vars: map[string]string{},
Environment: map[string]string{},
IsHandler: isHandler,
}
// Check if this is a block
if blockTasks, ok := data["block"]; ok {
task.IsBlock = true
task.BlockTasks = parseTasks(blockTasks, isHandler)
if rescue, ok := data["rescue"]; ok {
task.RescueTasks = parseTasks(rescue, isHandler)
}
if always, ok := data["always"]; ok {
task.AlwaysTasks = parseTasks(always, isHandler)
}
}
// Basic fields
if name, ok := data["name"]; ok {
task.Name = toString(name)
}
if action, ok := data["action"]; ok {
task.Action = toString(action)
}
// Handle local_action (runs on controller)
if localAction, ok := data["local_action"]; ok {
task.DelegateTo = "localhost"
switch v := localAction.(type) {
case string:
// Format: "module args" or "module: args" or "module key=value ..."
parts := strings.SplitN(v, " ", 2)
task.Module = parts[0]
if len(parts) > 1 {
task.FreeFormArgs = parts[1]
}
case map[string]any:
// Dict form with "module" key
if mod, ok := v["module"]; ok {
task.Module = toString(mod)
// Copy other keys as args
for k, val := range v {
if k != "module" {
task.Args[k] = varToString(val)
}
}
}
}
}
// Handle explicit args (can supplement module args)
if argsData, ok := data["args"]; ok {
argsMap := parseVars(argsData)
for k, v := range argsMap {
task.Args[k] = v
}
}
// Find the module (key that isn't a known attribute)
if !task.IsBlock && task.Module == "" {
for key, value := range data {
if !knownTaskAttrs[key] {
task.Module = key
// Parse module args
switch v := value.(type) {
case string:
task.FreeFormArgs = v
case map[string]any:
for k, val := range parseVars(v) {
task.Args[k] = val
}
}
break
}
}
}
// Control flow
if when, ok := data["when"]; ok {
task.When = conditionToString(when)
}
if loop, ok := data["loop"]; ok {
task.Loop = loopToString(loop)
}
// Handle with_* variants
for key, value := range data {
if strings.HasPrefix(key, "with_") {
task.Loop = fmt.Sprintf("%s: %s", key, loopToString(value))
break
}
}
if loopCtrl, ok := data["loop_control"]; ok {
task.LoopControl = toJSONString(loopCtrl)
}
if register, ok := data["register"]; ok {
task.Register = toString(register)
}
if until, ok := data["until"]; ok {
task.Until = conditionToString(until)
}
if retries, ok := data["retries"]; ok {
task.Retries = toInt(retries)
}
if delay, ok := data["delay"]; ok {
task.Delay = toInt(delay)
}
if changed, ok := data["changed_when"]; ok {
task.ChangedWhen = conditionToString(changed)
}
if failed, ok := data["failed_when"]; ok {
task.FailedWhen = conditionToString(failed)
}
// Privilege escalation
if become, ok := data["become"]; ok {
task.Become = toBool(become)
}
if becomeUser, ok := data["become_user"]; ok {
task.BecomeUser = toString(becomeUser)
}
if becomeMethod, ok := data["become_method"]; ok {
task.BecomeMethod = toString(becomeMethod)
}
if becomeExe, ok := data["become_exe"]; ok {
task.BecomeExe = toString(becomeExe)
}
if becomeFlags, ok := data["become_flags"]; ok {
task.BecomeFlags = toString(becomeFlags)
}
// Delegation
if delegateTo, ok := data["delegate_to"]; ok {
task.DelegateTo = toString(delegateTo)
}
if delegateFacts, ok := data["delegate_facts"]; ok {
task.DelegateFacts = toBool(delegateFacts)
}
// Error handling
if ignoreErrors, ok := data["ignore_errors"]; ok {
task.IgnoreErrors = toBool(ignoreErrors)
}
if ignoreUnreachable, ok := data["ignore_unreachable"]; ok {
task.IgnoreUnreachable = toBool(ignoreUnreachable)
}
if anyFatal, ok := data["any_errors_fatal"]; ok {
task.AnyErrorsFatal = toBool(anyFatal)
}
// Notification
if notify, ok := data["notify"]; ok {
task.Notify = toStringSlice(notify)
}
if listen, ok := data["listen"]; ok {
task.Listen = toStringSlice(listen)
}
// Execution
if async, ok := data["async"]; ok {
task.Async = toInt(async)
}
if poll, ok := data["poll"]; ok {
task.Poll = toInt(poll)
}
if throttle, ok := data["throttle"]; ok {
task.Throttle = toInt(throttle)
}
if timeout, ok := data["timeout"]; ok {
task.Timeout = toInt(timeout)
}
if runOnce, ok := data["run_once"]; ok {
task.RunOnce = toBool(runOnce)
}
// Metadata
if tags, ok := data["tags"]; ok {
task.Tags = toStringSlice(tags)
}
if noLog, ok := data["no_log"]; ok {
task.NoLog = toBool(noLog)
}
if diff, ok := data["diff"]; ok {
task.Diff = toBool(diff)
}
if checkMode, ok := data["check_mode"]; ok {
task.CheckMode = toBool(checkMode)
}
// Environment
if env, ok := data["environment"]; ok {
task.Environment = parseVars(env)
}
if vars, ok := data["vars"]; ok {
task.Vars = parseVars(vars)
}
// Connection
if conn, ok := data["connection"]; ok {
task.Connection = toString(conn)
}
if user, ok := data["remote_user"]; ok {
task.RemoteUser = toString(user)
}
if port, ok := data["port"]; ok {
task.Port = toInt(port)
}
return task
}
// Helper functions for type conversion
func toString(v any) string {
switch val := v.(type) {
case string:
return val
case int, int64, float64:
return fmt.Sprintf("%v", val)
case bool:
return fmt.Sprintf("%v", val)
case nil:
return ""
default:
return fmt.Sprintf("%v", val)
}
}
func toBool(v any) bool {
switch val := v.(type) {
case bool:
return val
case string:
return strings.ToLower(val) == "true" || val == "yes"
case int:
return val != 0
default:
return false
}
}
func toInt(v any) int {
switch val := v.(type) {
case int:
return val
case int64:
return int(val)
case uint64:
return int(val)
case float64:
return int(val)
case string:
var i int
fmt.Sscanf(val, "%d", &i)
return i
default:
return 0
}
}
func toStringSlice(v any) []string {
switch val := v.(type) {
case []any:
result := make([]string, 0, len(val))
for _, item := range val {
result = append(result, toString(item))
}
return result
case []string:
return val
case string:
return []string{val}
default:
return nil
}
}
func parseVars(data any) map[string]string {
result := map[string]string{}
varsMap, ok := data.(map[string]any)
if !ok {
return result
}
for k, v := range varsMap {
result[k] = varToString(v)
}
return result
}
func varToString(v any) string {
switch val := v.(type) {
case string:
return val
case int, int64, float64:
return fmt.Sprintf("%v", val)
case bool:
return fmt.Sprintf("%v", val)
case nil:
return ""
default:
// Complex types (lists, maps) are JSON-encoded
b, err := json.Marshal(val)
if err != nil {
return fmt.Sprintf("%v", val)
}
return string(b)
}
}
func conditionToString(v any) string {
switch val := v.(type) {
case string:
return val
case bool:
return fmt.Sprintf("%v", val)
case []any:
// List of conditions is AND-ed together
parts := make([]string, 0, len(val))
for _, item := range val {
parts = append(parts, toString(item))
}
return strings.Join(parts, " and ")
default:
return toString(v)
}
}
func loopToString(v any) string {
switch val := v.(type) {
case string:
return val
case []any:
b, _ := json.Marshal(val)
return string(b)
default:
return toJSONString(v)
}
}
func toJSONString(v any) string {
b, err := json.Marshal(v)
if err != nil {
return fmt.Sprintf("%v", v)
}
return string(b)
}
// FlatBuffer building functions
// BuildPlaybookResponse builds a FlatBuffer PlaybookResponse.
func BuildPlaybookResponse(playbook *Playbook, warnings []string) []byte {
builder := flatbuffers.NewBuilder(4096)
playbookOffset := buildPlaybook(builder, playbook)
// Build warnings vector
warningOffsets := make([]flatbuffers.UOffsetT, len(warnings))
for i, w := range warnings {
warningOffsets[i] = builder.CreateString(w)
}
fbscap.PlaybookResponseStartWarningsVector(builder, len(warningOffsets))
for i := len(warningOffsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(warningOffsets[i])
}
warningsVec := builder.EndVector(len(warningOffsets))
// Build response
fbscap.PlaybookResponseStart(builder)
fbscap.PlaybookResponseAddPlaybook(builder, playbookOffset)
fbscap.PlaybookResponseAddWarnings(builder, warningsVec)
responseOffset := fbscap.PlaybookResponseEnd(builder)
builder.Finish(responseOffset)
return builder.FinishedBytes()
}
// BuildPlaybookErrorResponse builds a FlatBuffer PlaybookResponse with an error.
func BuildPlaybookErrorResponse(errMsg string) []byte {
builder := flatbuffers.NewBuilder(256)
errOffset := builder.CreateString(errMsg)
fbscap.PlaybookResponseStart(builder)
fbscap.PlaybookResponseAddError(builder, errOffset)
responseOffset := fbscap.PlaybookResponseEnd(builder)
builder.Finish(responseOffset)
return builder.FinishedBytes()
}
func buildPlaybook(builder *flatbuffers.Builder, playbook *Playbook) flatbuffers.UOffsetT {
// Build plays
playOffsets := make([]flatbuffers.UOffsetT, len(playbook.Plays))
for i, play := range playbook.Plays {
playOffsets[i] = buildPlay(builder, &play)
}
fbsdomain.PlaybookStartPlaysVector(builder, len(playOffsets))
for i := len(playOffsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(playOffsets[i])
}
playsVec := builder.EndVector(len(playOffsets))
// Build imports
importOffsets := make([]flatbuffers.UOffsetT, len(playbook.Imports))
for i, imp := range playbook.Imports {
importOffsets[i] = buildPlaybookImport(builder, &imp)
}
fbsdomain.PlaybookStartImportsVector(builder, len(importOffsets))
for i := len(importOffsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(importOffsets[i])
}
importsVec := builder.EndVector(len(importOffsets))
// Build Playbook
fbsdomain.PlaybookStart(builder)
fbsdomain.PlaybookAddPlays(builder, playsVec)
fbsdomain.PlaybookAddImports(builder, importsVec)
return fbsdomain.PlaybookEnd(builder)
}
func buildPlaybookImport(builder *flatbuffers.Builder, imp *PlaybookImport) flatbuffers.UOffsetT {
pathOffset := builder.CreateString(imp.Path)
var nameOffset flatbuffers.UOffsetT
if imp.Name != "" {
nameOffset = builder.CreateString(imp.Name)
}
var whenOffset flatbuffers.UOffsetT
if imp.When != "" {
whenOffset = builder.CreateString(imp.When)
}
varsVec := buildVarsVector(builder, imp.Vars)
tagsVec := buildStringVector(builder, imp.Tags)
fbsdomain.PlaybookImportStart(builder)
fbsdomain.PlaybookImportAddPath(builder, pathOffset)
if imp.Name != "" {
fbsdomain.PlaybookImportAddName(builder, nameOffset)
}
fbsdomain.PlaybookImportAddVars(builder, varsVec)
fbsdomain.PlaybookImportAddTags(builder, tagsVec)
if imp.When != "" {
fbsdomain.PlaybookImportAddWhen(builder, whenOffset)
}
return fbsdomain.PlaybookImportEnd(builder)
}
func buildPlay(builder *flatbuffers.Builder, play *Play) flatbuffers.UOffsetT {
var nameOffset flatbuffers.UOffsetT
if play.Name != "" {
nameOffset = builder.CreateString(play.Name)
}
hostsOffset := builder.CreateString(play.Hosts)
// Build task lists
tasksVec := buildTasksVector(builder, play.Tasks)
handlersVec := buildTasksVector(builder, play.Handlers)
preTasksVec := buildTasksVector(builder, play.PreTasks)
postTasksVec := buildTasksVector(builder, play.PostTasks)
// Build roles
roleOffsets := make([]flatbuffers.UOffsetT, len(play.Roles))
for i, role := range play.Roles {
roleOffsets[i] = buildRoleInclude(builder, &role)
}
fbsdomain.PlayStartRolesVector(builder, len(roleOffsets))
for i := len(roleOffsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(roleOffsets[i])
}
rolesVec := builder.EndVector(len(roleOffsets))
// Variables
varsVec := buildVarsVector(builder, play.Vars)
varsFilesVec := buildStringVector(builder, play.VarsFiles)
// Privilege escalation
var becomeUserOffset, becomeMethodOffset flatbuffers.UOffsetT
if play.BecomeUser != "" {
becomeUserOffset = builder.CreateString(play.BecomeUser)
}
if play.BecomeMethod != "" {
becomeMethodOffset = builder.CreateString(play.BecomeMethod)
}
// Execution control
var strategyOffset, serialOffset flatbuffers.UOffsetT
if play.Strategy != "" {
strategyOffset = builder.CreateString(play.Strategy)
}
if play.Serial != "" {
serialOffset = builder.CreateString(play.Serial)
}
// Connection
var connectionOffset, remoteUserOffset flatbuffers.UOffsetT
if play.Connection != "" {
connectionOffset = builder.CreateString(play.Connection)
}
if play.RemoteUser != "" {
remoteUserOffset = builder.CreateString(play.RemoteUser)
}
// Filtering
tagsVec := buildStringVector(builder, play.Tags)
var whenOffset flatbuffers.UOffsetT
if play.When != "" {
whenOffset = builder.CreateString(play.When)
}
// Environment
envVec := buildVarsVector(builder, play.Environment)
// Build Play
fbsdomain.PlayStart(builder)
if play.Name != "" {
fbsdomain.PlayAddName(builder, nameOffset)
}
fbsdomain.PlayAddHosts(builder, hostsOffset)
fbsdomain.PlayAddTasks(builder, tasksVec)
fbsdomain.PlayAddHandlers(builder, handlersVec)
fbsdomain.PlayAddPreTasks(builder, preTasksVec)
fbsdomain.PlayAddPostTasks(builder, postTasksVec)
fbsdomain.PlayAddRoles(builder, rolesVec)
fbsdomain.PlayAddVars(builder, varsVec)
fbsdomain.PlayAddVarsFiles(builder, varsFilesVec)
fbsdomain.PlayAddBecome(builder, play.Become)
if play.BecomeUser != "" {
fbsdomain.PlayAddBecomeUser(builder, becomeUserOffset)
}
if play.BecomeMethod != "" {
fbsdomain.PlayAddBecomeMethod(builder, becomeMethodOffset)
}
fbsdomain.PlayAddGatherFacts(builder, play.GatherFacts)
if play.Strategy != "" {
fbsdomain.PlayAddStrategy(builder, strategyOffset)
}
if play.Serial != "" {
fbsdomain.PlayAddSerial(builder, serialOffset)
}
fbsdomain.PlayAddMaxFailPercentage(builder, int32(play.MaxFailPercentage))
fbsdomain.PlayAddAnyErrorsFatal(builder, play.AnyErrorsFatal)
if play.Connection != "" {
fbsdomain.PlayAddConnection(builder, connectionOffset)
}
if play.RemoteUser != "" {
fbsdomain.PlayAddRemoteUser(builder, remoteUserOffset)
}
fbsdomain.PlayAddPort(builder, int32(play.Port))
fbsdomain.PlayAddTags(builder, tagsVec)
if play.When != "" {
fbsdomain.PlayAddWhen(builder, whenOffset)
}
fbsdomain.PlayAddEnvironment(builder, envVec)
return fbsdomain.PlayEnd(builder)
}
func buildRoleInclude(builder *flatbuffers.Builder, role *RoleInclude) flatbuffers.UOffsetT {
roleOffset := builder.CreateString(role.Role)
var nameOffset flatbuffers.UOffsetT
if role.Name != "" {
nameOffset = builder.CreateString(role.Name)
}
varsVec := buildVarsVector(builder, role.Vars)
tagsVec := buildStringVector(builder, role.Tags)
var whenOffset flatbuffers.UOffsetT
if role.When != "" {
whenOffset = builder.CreateString(role.When)
}
var becomeUserOffset flatbuffers.UOffsetT
if role.BecomeUser != "" {
becomeUserOffset = builder.CreateString(role.BecomeUser)
}
fbsdomain.RoleIncludeStart(builder)
fbsdomain.RoleIncludeAddRole(builder, roleOffset)
if role.Name != "" {
fbsdomain.RoleIncludeAddName(builder, nameOffset)
}
fbsdomain.RoleIncludeAddVars(builder, varsVec)
fbsdomain.RoleIncludeAddTags(builder, tagsVec)
if role.When != "" {
fbsdomain.RoleIncludeAddWhen(builder, whenOffset)
}
fbsdomain.RoleIncludeAddBecome(builder, role.Become)
if role.BecomeUser != "" {
fbsdomain.RoleIncludeAddBecomeUser(builder, becomeUserOffset)
}
return fbsdomain.RoleIncludeEnd(builder)
}
func buildTasksVector(builder *flatbuffers.Builder, tasks []Task) flatbuffers.UOffsetT {
taskOffsets := make([]flatbuffers.UOffsetT, len(tasks))
for i, task := range tasks {
taskOffsets[i] = buildTask(builder, &task)
}
fbsdomain.PlayStartTasksVector(builder, len(taskOffsets))
for i := len(taskOffsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(taskOffsets[i])
}
return builder.EndVector(len(taskOffsets))
}
func buildTask(builder *flatbuffers.Builder, task *Task) flatbuffers.UOffsetT {
// Build strings first (must be done before starting the table)
var nameOffset, moduleOffset, freeFormArgsOffset, actionOffset flatbuffers.UOffsetT
if task.Name != "" {
nameOffset = builder.CreateString(task.Name)
}
if task.Module != "" {
moduleOffset = builder.CreateString(task.Module)
}
if task.FreeFormArgs != "" {
freeFormArgsOffset = builder.CreateString(task.FreeFormArgs)
}
if task.Action != "" {
actionOffset = builder.CreateString(task.Action)
}
// Control flow
var whenOffset, loopOffset, loopControlOffset, registerOffset flatbuffers.UOffsetT
var untilOffset, changedWhenOffset, failedWhenOffset flatbuffers.UOffsetT
if task.When != "" {
whenOffset = builder.CreateString(task.When)
}
if task.Loop != "" {
loopOffset = builder.CreateString(task.Loop)
}
if task.LoopControl != "" {
loopControlOffset = builder.CreateString(task.LoopControl)
}
if task.Register != "" {
registerOffset = builder.CreateString(task.Register)
}
if task.Until != "" {
untilOffset = builder.CreateString(task.Until)
}
if task.ChangedWhen != "" {
changedWhenOffset = builder.CreateString(task.ChangedWhen)
}
if task.FailedWhen != "" {
failedWhenOffset = builder.CreateString(task.FailedWhen)
}
// Privilege escalation
var becomeUserOffset, becomeMethodOffset, becomeExeOffset, becomeFlagsOffset flatbuffers.UOffsetT
if task.BecomeUser != "" {
becomeUserOffset = builder.CreateString(task.BecomeUser)
}
if task.BecomeMethod != "" {
becomeMethodOffset = builder.CreateString(task.BecomeMethod)
}
if task.BecomeExe != "" {
becomeExeOffset = builder.CreateString(task.BecomeExe)
}
if task.BecomeFlags != "" {
becomeFlagsOffset = builder.CreateString(task.BecomeFlags)
}
// Delegation
var delegateToOffset flatbuffers.UOffsetT
if task.DelegateTo != "" {
delegateToOffset = builder.CreateString(task.DelegateTo)
}
// Connection
var connectionOffset, remoteUserOffset flatbuffers.UOffsetT
if task.Connection != "" {
connectionOffset = builder.CreateString(task.Connection)
}
if task.RemoteUser != "" {
remoteUserOffset = builder.CreateString(task.RemoteUser)
}
// Build vectors
argsVec := buildVarsVector(builder, task.Args)
notifyVec := buildStringVector(builder, task.Notify)
listenVec := buildStringVector(builder, task.Listen)
tagsVec := buildStringVector(builder, task.Tags)
envVec := buildVarsVector(builder, task.Environment)
varsVec := buildVarsVector(builder, task.Vars)
// Build block tasks recursively
var blockTasksVec, rescueTasksVec, alwaysTasksVec flatbuffers.UOffsetT
if task.IsBlock {
blockTasksVec = buildTasksVectorForTask(builder, task.BlockTasks)
rescueTasksVec = buildTasksVectorForTask(builder, task.RescueTasks)
alwaysTasksVec = buildTasksVectorForTask(builder, task.AlwaysTasks)
}
// Build the Task
fbsdomain.TaskStart(builder)
if task.Name != "" {
fbsdomain.TaskAddName(builder, nameOffset)
}
if task.Module != "" {
fbsdomain.TaskAddModule(builder, moduleOffset)
}
fbsdomain.TaskAddArgs(builder, argsVec)
if task.FreeFormArgs != "" {
fbsdomain.TaskAddFreeFormArgs(builder, freeFormArgsOffset)
}
if task.Action != "" {
fbsdomain.TaskAddAction(builder, actionOffset)
}
// Control flow
if task.When != "" {
fbsdomain.TaskAddWhen(builder, whenOffset)
}
if task.Loop != "" {
fbsdomain.TaskAddLoop(builder, loopOffset)
}
if task.LoopControl != "" {
fbsdomain.TaskAddLoopControl(builder, loopControlOffset)
}
if task.Register != "" {
fbsdomain.TaskAddRegister(builder, registerOffset)
}
if task.Until != "" {
fbsdomain.TaskAddUntil(builder, untilOffset)
}
fbsdomain.TaskAddRetries(builder, int32(task.Retries))
fbsdomain.TaskAddDelay(builder, int32(task.Delay))
if task.ChangedWhen != "" {
fbsdomain.TaskAddChangedWhen(builder, changedWhenOffset)
}
if task.FailedWhen != "" {
fbsdomain.TaskAddFailedWhen(builder, failedWhenOffset)
}
// Privilege escalation
fbsdomain.TaskAddBecome(builder, task.Become)
if task.BecomeUser != "" {
fbsdomain.TaskAddBecomeUser(builder, becomeUserOffset)
}
if task.BecomeMethod != "" {
fbsdomain.TaskAddBecomeMethod(builder, becomeMethodOffset)
}
if task.BecomeExe != "" {
fbsdomain.TaskAddBecomeExe(builder, becomeExeOffset)
}
if task.BecomeFlags != "" {
fbsdomain.TaskAddBecomeFlags(builder, becomeFlagsOffset)
}
// Delegation
if task.DelegateTo != "" {
fbsdomain.TaskAddDelegateTo(builder, delegateToOffset)
}
fbsdomain.TaskAddDelegateFacts(builder, task.DelegateFacts)
// Error handling
fbsdomain.TaskAddIgnoreErrors(builder, task.IgnoreErrors)
fbsdomain.TaskAddIgnoreUnreachable(builder, task.IgnoreUnreachable)
fbsdomain.TaskAddAnyErrorsFatal(builder, task.AnyErrorsFatal)
// Notification
fbsdomain.TaskAddNotify(builder, notifyVec)
fbsdomain.TaskAddListen(builder, listenVec)
// Execution
fbsdomain.TaskAddAsync(builder, int32(task.Async))
fbsdomain.TaskAddPoll(builder, int32(task.Poll))
fbsdomain.TaskAddThrottle(builder, int32(task.Throttle))
fbsdomain.TaskAddTimeout(builder, int32(task.Timeout))
fbsdomain.TaskAddRunOnce(builder, task.RunOnce)
// Metadata
fbsdomain.TaskAddTags(builder, tagsVec)
fbsdomain.TaskAddNoLog(builder, task.NoLog)
fbsdomain.TaskAddDiff(builder, task.Diff)
fbsdomain.TaskAddCheckMode(builder, task.CheckMode)
// Environment
fbsdomain.TaskAddEnvironment(builder, envVec)
fbsdomain.TaskAddVars(builder, varsVec)
// Connection
if task.Connection != "" {
fbsdomain.TaskAddConnection(builder, connectionOffset)
}
if task.RemoteUser != "" {
fbsdomain.TaskAddRemoteUser(builder, remoteUserOffset)
}
fbsdomain.TaskAddPort(builder, int32(task.Port))
// Block support
fbsdomain.TaskAddIsBlock(builder, task.IsBlock)
fbsdomain.TaskAddIsHandler(builder, task.IsHandler)
if task.IsBlock {
fbsdomain.TaskAddBlockTasks(builder, blockTasksVec)
fbsdomain.TaskAddRescueTasks(builder, rescueTasksVec)
fbsdomain.TaskAddAlwaysTasks(builder, alwaysTasksVec)
}
return fbsdomain.TaskEnd(builder)
}
func buildTasksVectorForTask(builder *flatbuffers.Builder, tasks []Task) flatbuffers.UOffsetT {
taskOffsets := make([]flatbuffers.UOffsetT, len(tasks))
for i, task := range tasks {
taskOffsets[i] = buildTask(builder, &task)
}
fbsdomain.TaskStartBlockTasksVector(builder, len(taskOffsets))
for i := len(taskOffsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(taskOffsets[i])
}
return builder.EndVector(len(taskOffsets))
}
func buildVarsVector(builder *flatbuffers.Builder, vars map[string]string) flatbuffers.UOffsetT {
// Sort keys for deterministic output
keys := make([]string, 0, len(vars))
for k := range vars {
keys = append(keys, k)
}
sort.Strings(keys)
varOffsets := make([]flatbuffers.UOffsetT, len(keys))
for i, k := range keys {
varOffsets[i] = buildVar(builder, k, vars[k])
}
fbsdomain.PlayStartVarsVector(builder, len(varOffsets))
for i := len(varOffsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(varOffsets[i])
}
return builder.EndVector(len(varOffsets))
}
func buildVar(builder *flatbuffers.Builder, key, value string) flatbuffers.UOffsetT {
keyOffset := builder.CreateString(key)
valueOffset := builder.CreateString(value)
fbsdomain.VarStart(builder)
fbsdomain.VarAddKey(builder, keyOffset)
fbsdomain.VarAddValue(builder, valueOffset)
return fbsdomain.VarEnd(builder)
}
func buildStringVector(builder *flatbuffers.Builder, strs []string) flatbuffers.UOffsetT {
strOffsets := make([]flatbuffers.UOffsetT, len(strs))
for i, s := range strs {
strOffsets[i] = builder.CreateString(s)
}
builder.StartVector(4, len(strOffsets), 4)
for i := len(strOffsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(strOffsets[i])
}
return builder.EndVector(len(strOffsets))
}