Files
jarl/internal/engine/inputs.go
T
adamandClaude Opus 5.5 89663e366b Disable zones instead of deleting them
A disabled zone stays in Jarl with its records, options and history,
still editable, but named doesn't load it; enabling it serves it as it is
then. Signed zones, and zones another view shares, can't be disabled.
jarl zone disable|enable, the zone page's Disable/Enable button.
Database schema 7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:45:55 +00:00

260 lines
9.8 KiB
Go

package engine
import (
"encoding/json"
"time"
"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"
OpBlocklistCreate = "blocklist.create"
OpBlocklistUpdate = "blocklist.update"
OpBlocklistDelete = "blocklist.delete"
OpBlockRuleCreate = "blockrule.create"
OpBlockRuleUpdate = "blockrule.update"
OpBlockRuleDelete = "blockrule.delete"
OpBlockingPause = "blocking.pause"
// 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,
OpBlocklistCreate, OpBlocklistUpdate, OpBlocklistDelete, OpBlockRuleCreate, OpBlockRuleUpdate, OpBlockRuleDelete, OpBlockingPause}
// 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"`
// Disabled keeps the zone in Jarl but out of named's configuration.
Disabled *bool `json:"disabled,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"`
}
// BlocklistInput creates or updates a blocklist. Views are given by name
// (Views) or ID (ViewIDs); an empty list means every view that offers
// recursion.
type BlocklistInput struct {
Name *string `json:"name,omitempty"`
URL *string `json:"url,omitempty"`
Views *[]string `json:"views,omitempty"`
ViewIDs *[]int64 `json:"view_ids,omitempty"`
Disabled *bool `json:"disabled,omitempty"`
Comment *string `json:"comment,omitempty"`
}
// BlockRuleInput creates or updates a block or allow rule. Views as for
// BlocklistInput.
type BlockRuleInput struct {
Name *string `json:"name,omitempty"`
Action *string `json:"action,omitempty"` // block (default) or allow
Exact *bool `json:"exact,omitempty"` // only the name, not the names below it
Views *[]string `json:"views,omitempty"`
ViewIDs *[]int64 `json:"view_ids,omitempty"`
Comment *string `json:"comment,omitempty"`
}
// BlockingPauseInput pauses blocking until a time, or resumes it (null).
type BlockingPauseInput struct {
Until *time.Time `json:"until"`
}
// MaxBlockingPause is the longest pause: blocking shouldn't stay off by
// mistake (disable lists to stop using them).
const MaxBlockingPause = 24 * time.Hour