A disabled record stays in Jarl with its ID, TTL and comment but is left out of the zone; the zone file lists it as a comment. Web UI toggle, jarl record disable|enable, record.disable|enable operations and API endpoints. Database schema 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
216 lines
8.0 KiB
Go
216 lines
8.0 KiB
Go
package engine
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"jarl/internal/model"
|
|
)
|
|
|
|
// Operation kinds.
|
|
const (
|
|
OpZoneCreate = "zone.create"
|
|
OpZoneUpdate = "zone.update"
|
|
OpZoneDelete = "zone.delete"
|
|
OpRecordCreate = "record.create"
|
|
OpRecordUpdate = "record.update"
|
|
OpRecordDelete = "record.delete"
|
|
OpRRSetReplace = "rrset.replace"
|
|
OpViewCreate = "view.create"
|
|
OpViewUpdate = "view.update"
|
|
OpViewDelete = "view.delete"
|
|
OpViewReorder = "view.reorder"
|
|
OpACLCreate = "acl.create"
|
|
OpACLUpdate = "acl.update"
|
|
OpACLDelete = "acl.delete"
|
|
OpTSIGCreate = "tsig.create"
|
|
OpTSIGUpdate = "tsig.update"
|
|
OpTSIGDelete = "tsig.delete"
|
|
OpOptionCreate = "option.create"
|
|
OpOptionUpdate = "option.update"
|
|
OpOptionDelete = "option.delete"
|
|
OpSettingsUpdate = "settings.update"
|
|
|
|
// Requests only: disabling and enabling records, by ID or by match
|
|
// (zone, name, type and optionally data), become record.update
|
|
// operations.
|
|
OpRecordDisable = "record.disable"
|
|
OpRecordEnable = "record.enable"
|
|
)
|
|
|
|
// OpKinds lists all operation kinds.
|
|
var OpKinds = []string{OpZoneCreate, OpZoneUpdate, OpZoneDelete, OpRecordCreate, OpRecordUpdate, OpRecordDelete, OpRRSetReplace,
|
|
OpViewCreate, OpViewUpdate, OpViewDelete, OpViewReorder, OpACLCreate, OpACLUpdate, OpACLDelete,
|
|
OpTSIGCreate, OpTSIGUpdate, OpTSIGDelete, OpOptionCreate, OpOptionUpdate, OpOptionDelete, OpSettingsUpdate}
|
|
|
|
// Request is an operation as submitted by a client. Requests are expanded
|
|
// into one or more stored operations (for example an A record with "ptr"
|
|
// also creates the matching PTR record).
|
|
type Request struct {
|
|
Op string `json:"op"`
|
|
// ID is the object to update or delete.
|
|
ID int64 `json:"id,omitempty"`
|
|
// BaseVersion, if set, makes the request fail when the object's
|
|
// version differs (optimistic locking). Otherwise the version at
|
|
// staging time is recorded.
|
|
BaseVersion int `json:"base_version,omitempty"`
|
|
Data json.RawMessage `json:"data,omitempty"`
|
|
}
|
|
|
|
// NewRequest builds a request with data marshaled from an input struct.
|
|
func NewRequest(op string, id int64, data any) Request {
|
|
r := Request{Op: op, ID: id}
|
|
if data != nil {
|
|
b, err := json.Marshal(data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
r.Data = b
|
|
}
|
|
return r
|
|
}
|
|
|
|
// SOAInput sets SOA fields of a primary zone.
|
|
type SOAInput struct {
|
|
MName *string `json:"mname,omitempty"`
|
|
RName *string `json:"rname,omitempty"` // mailbox name or email address
|
|
Serial *uint32 `json:"serial,omitempty"`
|
|
Refresh *uint32 `json:"refresh,omitempty"`
|
|
Retry *uint32 `json:"retry,omitempty"`
|
|
Expire *uint32 `json:"expire,omitempty"`
|
|
Minimum *uint32 `json:"minimum,omitempty"`
|
|
}
|
|
|
|
// ZoneInput creates or updates a zone.
|
|
type ZoneInput struct {
|
|
// Name is the zone name. For create, a CIDR prefix such as
|
|
// "192.168.1.0/24" creates the matching reverse zone.
|
|
Name *string `json:"name,omitempty"`
|
|
ViewID *int64 `json:"view_id,omitempty"`
|
|
View *string `json:"view,omitempty"` // view name, alternative to view_id
|
|
Type *string `json:"type,omitempty"`
|
|
InView *string `json:"in_view,omitempty"`
|
|
File *string `json:"file,omitempty"`
|
|
SOA *SOAInput `json:"soa,omitempty"`
|
|
DefaultTTL *uint32 `json:"default_ttl,omitempty"`
|
|
SerialPolicy *string `json:"serial_policy,omitempty"`
|
|
// DNSSECPolicy turns signing on ("default" or a custom policy's
|
|
// name), starts turning it off ("insecure") or, once that has
|
|
// finished, off ("").
|
|
DNSSECPolicy *string `json:"dnssec_policy,omitempty"`
|
|
Comment *string `json:"comment,omitempty"`
|
|
|
|
// Create only: name servers for the apex NS records (default: the
|
|
// configured default name servers), additional records, and a zone to
|
|
// copy records from.
|
|
Nameservers []string `json:"nameservers,omitempty"`
|
|
Records []RecordInput `json:"records,omitempty"`
|
|
CloneFrom int64 `json:"clone_from,omitempty"`
|
|
// Options (create only) are zone options to set, e.g. primaries for a
|
|
// secondary zone or forwarders for a forward zone.
|
|
Options []ZoneOption `json:"options,omitempty"`
|
|
}
|
|
|
|
// ZoneOption is an option given when creating a zone.
|
|
type ZoneOption struct {
|
|
Name string `json:"name"`
|
|
Value string `json:"value"`
|
|
Comment string `json:"comment,omitempty"`
|
|
}
|
|
|
|
// RecordInput creates, updates or (by match) deletes a record.
|
|
type RecordInput struct {
|
|
ZoneID int64 `json:"zone_id,omitempty"`
|
|
Zone string `json:"zone,omitempty"` // zone name, alternative to zone_id
|
|
View string `json:"view,omitempty"` // view of Zone
|
|
Name *string `json:"name,omitempty"`
|
|
Type *string `json:"type,omitempty"`
|
|
TTL model.Opt[uint32] `json:"ttl,omitzero"` // null: use the zone default
|
|
Data *string `json:"data,omitempty"`
|
|
Comment *string `json:"comment,omitempty"`
|
|
// Disabled keeps the record in Jarl but out of the zone.
|
|
Disabled *bool `json:"disabled,omitempty"`
|
|
// PTR also creates (or deletes, disables or enables) the matching PTR
|
|
// record in the reverse zone, for A and AAAA records.
|
|
PTR bool `json:"ptr,omitempty"`
|
|
// MissingOK makes a delete-by-match succeed when nothing matches.
|
|
MissingOK bool `json:"missing_ok,omitempty"`
|
|
}
|
|
|
|
// RRSetInput replaces all records of one name and type.
|
|
type RRSetInput struct {
|
|
ZoneID int64 `json:"zone_id,omitempty"`
|
|
Zone string `json:"zone,omitempty"`
|
|
View string `json:"view,omitempty"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
TTL model.Opt[uint32] `json:"ttl,omitzero"`
|
|
Data []string `json:"data"`
|
|
Comment *string `json:"comment,omitempty"`
|
|
// NewIDs are allocated for records that need to be created.
|
|
NewIDs []int64 `json:"new_ids,omitempty"`
|
|
}
|
|
|
|
// ViewInput creates or updates a view.
|
|
type ViewInput struct {
|
|
Name *string `json:"name,omitempty"`
|
|
MatchClients *[]string `json:"match_clients,omitempty"`
|
|
MatchDestinations *[]string `json:"match_destinations,omitempty"`
|
|
MatchRecursiveOnly model.Opt[bool] `json:"match_recursive_only,omitzero"`
|
|
Comment *string `json:"comment,omitempty"`
|
|
Position *int `json:"position,omitempty"`
|
|
// AdoptZones (create) moves all zones that are not in a view into the
|
|
// new view; named requires every zone to be in a view once views
|
|
// are used.
|
|
AdoptZones bool `json:"adopt_zones,omitempty"`
|
|
// Cascade (delete) also deletes the view's zones.
|
|
Cascade bool `json:"cascade,omitempty"`
|
|
}
|
|
|
|
// ReorderInput sets the order of views.
|
|
type ReorderInput struct {
|
|
IDs []int64 `json:"ids"`
|
|
}
|
|
|
|
// ACLInput creates or updates an ACL.
|
|
type ACLInput struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Elements *[]string `json:"elements,omitempty"`
|
|
Comment *string `json:"comment,omitempty"`
|
|
}
|
|
|
|
// TSIGInput creates or updates a TSIG key. A missing secret on create is
|
|
// generated.
|
|
type TSIGInput struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Algorithm *string `json:"algorithm,omitempty"`
|
|
Secret *string `json:"secret,omitempty"`
|
|
Comment *string `json:"comment,omitempty"`
|
|
}
|
|
|
|
// OptionInput creates or updates an option.
|
|
type OptionInput struct {
|
|
Scope *string `json:"scope,omitempty"` // options, view, zone, toplevel
|
|
ScopeID *int64 `json:"scope_id,omitempty"` // view or zone ID
|
|
Name *string `json:"name,omitempty"`
|
|
Value *string `json:"value,omitempty"`
|
|
Comment *string `json:"comment,omitempty"`
|
|
Position *int `json:"position,omitempty"`
|
|
}
|
|
|
|
// SettingsInput updates Jarl's settings.
|
|
type SettingsInput struct {
|
|
DefaultTTL *uint32 `json:"default_ttl,omitempty"`
|
|
Nameservers *[]string `json:"nameservers,omitempty"`
|
|
RName *string `json:"rname,omitempty"`
|
|
Refresh *uint32 `json:"refresh,omitempty"`
|
|
Retry *uint32 `json:"retry,omitempty"`
|
|
Expire *uint32 `json:"expire,omitempty"`
|
|
NegativeTTL *uint32 `json:"negative_ttl,omitempty"`
|
|
SerialPolicy *string `json:"serial_policy,omitempty"`
|
|
}
|
|
|
|
// DeleteInput carries options for delete operations.
|
|
type DeleteInput struct {
|
|
Cascade bool `json:"cascade,omitempty"`
|
|
}
|