package models import ( "strings" "time" ) // AgentProfile represents a reusable configuration profile for agents. type AgentProfile struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description,omitempty"` Config AgentConfigMap `json:"config"` Version int `json:"version"` // Auto-incremented on each update ParentID string `json:"parent_id,omitempty"` // For profile inheritance CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` CreatedBy string `json:"created_by,omitempty"` UpdatedBy string `json:"updated_by,omitempty"` } // AgentConfigMap represents the key-value configuration overrides // (e.g., {"interval": "10s", "enable_docker": true}) type AgentConfigMap map[string]interface{} // AgentProfileAssignment maps an agent to a profile type AgentProfileAssignment struct { AgentID string `json:"agent_id"` ProfileID string `json:"profile_id"` ProfileVersion int `json:"profile_version"` // Version at time of assignment UpdatedAt time.Time `json:"updated_at"` AssignedBy string `json:"assigned_by,omitempty"` } // AgentProfileVersion represents a historical version of a profile. type AgentProfileVersion struct { ProfileID string `json:"profile_id"` Version int `json:"version"` Name string `json:"name"` Description string `json:"description,omitempty"` Config AgentConfigMap `json:"config"` ParentID string `json:"parent_id,omitempty"` CreatedAt time.Time `json:"created_at"` // When this version was created CreatedBy string `json:"created_by,omitempty"` ChangeNote string `json:"change_note,omitempty"` // Optional note about the change } // ProfileDeploymentStatus tracks which version an agent has received. type ProfileDeploymentStatus struct { AgentID string `json:"agent_id"` ProfileID string `json:"profile_id"` AssignedVersion int `json:"assigned_version"` // Version that should be deployed DeployedVersion int `json:"deployed_version"` // Version actually deployed LastDeployedAt time.Time `json:"last_deployed_at"` DeploymentStatus string `json:"deployment_status"` // "pending", "deployed", "failed" ErrorMessage string `json:"error_message,omitempty"` } // ProfileChangeLog represents an audit entry for profile changes. type ProfileChangeLog struct { ID string `json:"id"` ProfileID string `json:"profile_id"` ProfileName string `json:"profile_name"` Action string `json:"action"` // "create", "update", "delete", "assign", "unassign", "rollback" OldVersion int `json:"old_version,omitempty"` NewVersion int `json:"new_version,omitempty"` AgentID string `json:"agent_id,omitempty"` // For assign/unassign actions User string `json:"user,omitempty"` Timestamp time.Time `json:"timestamp"` Details string `json:"details,omitempty"` } const maxProfileInheritanceDepth = 64 // MergedConfig returns the effective configuration by merging parent configs. // Parent configs are applied first, then overridden by child configs. func (p *AgentProfile) MergedConfig(profiles []AgentProfile) AgentConfigMap { return p.mergedConfig(profiles, map[string]struct{}{}) } func (p *AgentProfile) mergedConfig(profiles []AgentProfile, _ map[string]struct{}) AgentConfigMap { // Build lookup map for parent resolution byID := make(map[string]*AgentProfile, len(profiles)) for i := range profiles { if profiles[i].ID != "" { byID[profiles[i].ID] = &profiles[i] } } // Walk the inheritance chain collecting configs from root to leaf chain := make([]*AgentProfile, 0, maxProfileInheritanceDepth) visited := make(map[string]struct{}, maxProfileInheritanceDepth) current := p for depth := 0; depth < maxProfileInheritanceDepth && current != nil; depth++ { chain = append(chain, current) if current.ID != "" { if _, seen := visited[current.ID]; seen { // Cycle detected; stop walking. break } visited[current.ID] = struct{}{} } parentID := strings.TrimSpace(current.ParentID) if parentID == "" { break } parent, ok := byID[parentID] if !ok { break } current = parent } // Merge: apply from root (end of chain) to leaf (start), so child overrides parent merged := make(AgentConfigMap) for i := len(chain) - 1; i >= 0; i-- { for k, v := range chain[i].Config { merged[k] = v } } return merged }