Introduce the foundational types for the checker system: - CheckTarget, CheckPlan, Execution, CheckEvaluation model types - CheckerDefinition, CheckerOptions, ObservationSnapshot types - CheckRule, ObservationProvider, CheckAggregator interfaces - CheckerEngine interface for orchestrating check pipelines - Global checker and observation provider registries - WorstStatusAggregator for combining rule results - New error types, config option for max concurrency, and AutoFill constants for context-driven option resolution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
191 lines
6.3 KiB
Go
191 lines
6.3 KiB
Go
// This file is part of the happyDomain (R) project.
|
|
// Copyright (c) 2020-2024 happyDomain
|
|
// Authors: Pierre-Olivier Mercier, et al.
|
|
//
|
|
// This program is offered under a commercial and under the AGPL license.
|
|
// For commercial licensing, contact us at <contact@happydomain.org>.
|
|
//
|
|
// For AGPL licensing:
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package happydns
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// ZoneMeta holds the metadata associated to a Zone.
|
|
type ZoneMeta struct {
|
|
// Id is the Zone's identifier.
|
|
Id Identifier `json:"id" swaggertype:"string" binding:"required"`
|
|
|
|
// IdAuthor is the User's identifier for the current Zone.
|
|
IdAuthor Identifier `json:"id_author" swaggertype:"string" binding:"required"`
|
|
|
|
// ParentZone identifies the parental zone of this one.
|
|
ParentZone *Identifier `json:"parent,omitempty" swaggertype:"string"`
|
|
|
|
// DefaultTTL is the TTL to use when no TTL has been defined for a record in this Zone.
|
|
DefaultTTL uint32 `json:"default_ttl" binding:"required"`
|
|
|
|
// LastModified holds the time when the last modification has been made on this Zone.
|
|
LastModified time.Time `json:"last_modified" format:"date-time" binding:"required"`
|
|
|
|
// CommitMsg is a message defined by the User to give a label to this Zone revision.
|
|
CommitMsg *string `json:"commit_message,omitempty"`
|
|
|
|
// CommitDate is the time when the commit has been made.
|
|
CommitDate *time.Time `json:"commit_date,omitempty" format:"date-time"`
|
|
|
|
// Published indicates whether the Zone has already been published or not.
|
|
Published *time.Time `json:"published,omitempty" format:"date-time"`
|
|
}
|
|
|
|
// ZoneMessage is the intermediate struct for parsing zones.
|
|
type ZoneMessage struct {
|
|
ZoneMeta
|
|
Services map[Subdomain][]*ServiceMessage `json:"services"`
|
|
}
|
|
|
|
// Zone contains ZoneMeta + map of services by subdomains.
|
|
type Zone struct {
|
|
ZoneMeta
|
|
Services map[Subdomain][]*Service `json:"services" binding:"required"`
|
|
}
|
|
|
|
// DerivateNew creates a new Zone from the current one, by copying all fields.
|
|
func (z *Zone) DerivateNew() *Zone {
|
|
newZone := new(Zone)
|
|
|
|
newZone.ZoneMeta.ParentZone = &z.ZoneMeta.Id
|
|
newZone.ZoneMeta.IdAuthor = z.ZoneMeta.IdAuthor
|
|
newZone.ZoneMeta.DefaultTTL = z.ZoneMeta.DefaultTTL
|
|
newZone.ZoneMeta.LastModified = time.Now()
|
|
newZone.Services = map[Subdomain][]*Service{}
|
|
|
|
for subdomain, svcs := range z.Services {
|
|
newZone.Services[subdomain] = svcs
|
|
}
|
|
|
|
return newZone
|
|
}
|
|
|
|
func (zone *Zone) eraseService(subdomain Subdomain, old *Service, idx int, new *Service) error {
|
|
if new == nil {
|
|
// Disallow removing SOA
|
|
if subdomain == "" && old.Type == "abstract.Origin" {
|
|
return fmt.Errorf("You cannot delete this service. It is mandatory.")
|
|
}
|
|
|
|
if len(zone.Services[subdomain]) <= 1 {
|
|
delete(zone.Services, subdomain)
|
|
} else {
|
|
zone.Services[subdomain] = append(zone.Services[subdomain][:idx], zone.Services[subdomain][idx+1:]...)
|
|
}
|
|
} else {
|
|
zone.Services[subdomain][idx] = new
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EraseService overwrites the Service identified by the given id, under the given subdomain.
|
|
// The the new service is nil, it removes the existing Service instead of overwrite it.
|
|
func (zone *Zone) EraseService(subdomain Subdomain, id []byte, s *Service) error {
|
|
idx, svc := zone.FindSubdomainService(subdomain, id)
|
|
if svc == nil {
|
|
return fmt.Errorf("service not found")
|
|
}
|
|
|
|
return zone.eraseService(subdomain, svc, idx, s)
|
|
}
|
|
|
|
func (zone *Zone) EraseServiceWithoutMeta(subdomain Subdomain, id []byte, s ServiceBody) error {
|
|
idx, svc := zone.FindSubdomainService(subdomain, id)
|
|
if svc == nil {
|
|
return fmt.Errorf("service not found")
|
|
}
|
|
|
|
return zone.eraseService(subdomain, svc, idx, &Service{Service: s, ServiceMeta: svc.ServiceMeta})
|
|
}
|
|
|
|
// FindService finds the Service identified by the given id.
|
|
func (z *Zone) FindService(id []byte) (Subdomain, *Service) {
|
|
for subdomain := range z.Services {
|
|
if _, svc := z.FindSubdomainService(subdomain, id); svc != nil {
|
|
return subdomain, svc
|
|
}
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
// FindSubdomainService finds the Service identified by the given id, only under the given subdomain.
|
|
func (z *Zone) FindSubdomainService(subdomain Subdomain, id []byte) (int, *Service) {
|
|
if subdomain == "@" {
|
|
subdomain = ""
|
|
}
|
|
|
|
if services, ok := z.Services[subdomain]; ok {
|
|
for k, svc := range services {
|
|
if bytes.Equal(svc.Id, id) {
|
|
return k, svc
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1, nil
|
|
}
|
|
|
|
type ZoneServices struct {
|
|
Services []*Service `json:"services"`
|
|
}
|
|
|
|
// ZoneWithServicesCheckStatus wraps a Zone with the worst check status for each service.
|
|
type ZoneWithServicesCheckStatus struct {
|
|
*Zone
|
|
// ServicesCheckStatus holds the worst check status for each service,
|
|
// keyed by service identifier string. Nil/absent if no results exist yet.
|
|
ServicesCheckStatus map[string]*Status `json:"services_check_status,omitempty"`
|
|
}
|
|
|
|
type ZoneUsecase interface {
|
|
AddRecord(*Zone, string, Record) error
|
|
CreateZone(*Zone) error
|
|
DeleteRecord(*Zone, string, Record) error
|
|
DeleteZone(Identifier) error
|
|
DiffZones(*Domain, *Zone, Identifier) ([]*Correction, error)
|
|
FlattenZoneFile(*Domain, *Zone) (string, error)
|
|
GenerateRecords(*Domain, *Zone) ([]Record, error)
|
|
GetZone(Identifier) (*Zone, error)
|
|
GetZoneMeta(Identifier) (*ZoneMeta, error)
|
|
LoadZoneFromId(domain *Domain, id Identifier) (*Zone, error)
|
|
UpdateZone(Identifier, func(*Zone)) error
|
|
}
|
|
|
|
type ApplyZoneForm struct {
|
|
WantedCorrections []Identifier `json:"wantedCorrections" swaggertype:"array,string"`
|
|
CommitMsg string `json:"commitMessage"`
|
|
}
|
|
|
|
type PrepareZoneForm struct {
|
|
WantedCorrections []Identifier `json:"wantedCorrections" swaggertype:"array,string"`
|
|
}
|
|
|
|
type PrepareZoneResponse struct {
|
|
Corrections []*Correction `json:"corrections" binding:"required"`
|
|
NbDiffs int `json:"nbDiffs" binding:"required"`
|
|
}
|