// Package protocol defines the messages agents and the master exchange over // their WebSocket connection. Every message is an Envelope whose Payload is // one of the payload types below, selected by Type. package protocol import ( "encoding/json" "fmt" ) // Message types sent from agent to master. const ( TypeHello = "hello" TypeSnapshot = "snapshot" TypeUpsert = "upsert" TypeRemove = "remove" ) // Envelope wraps every message on the wire. type Envelope struct { Type string `json:"type"` Payload json.RawMessage `json:"payload,omitempty"` } // Hello is the first message an agent sends on every connection. type Hello struct { AgentVersion string `json:"agent_version"` Hostname string `json:"hostname"` SwarmManager bool `json:"swarm_manager"` SwarmClusterID string `json:"swarm_cluster_id,omitempty"` } // Kind says how a service is deployed. type Kind string // Service kinds. const ( KindCompose Kind = "compose" KindSwarm Kind = "swarm" KindContainer Kind = "container" ) // Service is one deployable unit as an agent sees it. Which location fields // are set depends on Kind. type Service struct { Kind Kind `json:"kind"` ComposeProject string `json:"compose_project,omitempty"` ComposeService string `json:"compose_service,omitempty"` WorkingDir string `json:"working_dir,omitempty"` SwarmService string `json:"swarm_service,omitempty"` ContainerName string `json:"container_name,omitempty"` Image string `json:"image"` Digest string `json:"digest,omitempty"` Status string `json:"status"` Self bool `json:"self,omitempty"` } // Key identifies the service within one agent's report. It is stable across // restarts and is what Remove refers to. func (s Service) Key() string { switch s.Kind { case KindCompose: return "compose|" + s.ComposeProject + "|" + s.ComposeService case KindSwarm: return "swarm|" + s.SwarmService default: return "container|" + s.ContainerName } } // Snapshot is the agent's complete inventory. Sent after every Hello. type Snapshot struct { Services []Service `json:"services"` } // Upsert reports a service that appeared or changed. type Upsert struct { Service Service `json:"service"` } // Remove reports that the service with Key is gone. type Remove struct { Key string `json:"key"` } // Encode wraps payload in an Envelope of the given type. func Encode(typ string, payload any) (Envelope, error) { raw, err := json.Marshal(payload) if err != nil { return Envelope{}, fmt.Errorf("encode %s: %w", typ, err) } return Envelope{Type: typ, Payload: raw}, nil } // Decode unpacks env's payload into T. func Decode[T any](env Envelope) (T, error) { var v T if err := json.Unmarshal(env.Payload, &v); err != nil { return v, fmt.Errorf("decode %s: %w", env.Type, err) } return v, nil }