515 lines
13 KiB
Go
515 lines
13 KiB
Go
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
|
|
package resource
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"gopkg.in/yaml.v3"
|
|
"io"
|
|
"net/url"
|
|
"regexp"
|
|
_ "strconv"
|
|
"strings"
|
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
|
"decl/internal/codec"
|
|
"decl/internal/data"
|
|
"decl/internal/folio"
|
|
"decl/internal/command"
|
|
)
|
|
|
|
const (
|
|
NetworkRouteTypeName TypeName = "route"
|
|
)
|
|
|
|
func init() {
|
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"route"}, func(u *url.URL) (n data.Resource) {
|
|
n = NewNetworkRoute()
|
|
if u != nil {
|
|
if err := folio.CastParsedURI(u).ConstructResource(n); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return n
|
|
})
|
|
}
|
|
|
|
type NetworkRouteType string
|
|
|
|
const (
|
|
NetworkRouteTypeUnicast = "unicast"
|
|
NetworkRouteTypeLocal = "local"
|
|
NetworkRouteTypeBroadcast = "broadcast"
|
|
NetworkRouteTypeMulticast = "multicast"
|
|
NetworkRouteTypeThrow = "throw"
|
|
NetworkRouteTypeUnreachable = "unreachable"
|
|
NetworkRouteTypeProhibit = "prohibit"
|
|
NetworkRouteTypeBlackhole = "blackhole"
|
|
NetworkRouteTypeNat = "nat"
|
|
)
|
|
|
|
type NetworkRouteTableId string
|
|
|
|
const (
|
|
NetworkRouteTableLocal = "local"
|
|
NetworkRouteTableMain = "main"
|
|
NetworkRouteTableDefault = "default"
|
|
NetworkRouteTableAll = "all"
|
|
)
|
|
|
|
var NetworkRouteNumber = regexp.MustCompile(`^[0-9]+$`)
|
|
|
|
type NetworkRouteScope string
|
|
|
|
const (
|
|
NetworkRouteScopeHost = "host"
|
|
NetworkRouteScopeLink = "link"
|
|
NetworkRouteScopeGlobal = "global"
|
|
)
|
|
|
|
type NetworkRouteProto string
|
|
|
|
const (
|
|
NetworkRouteProtoKernel = "kernel"
|
|
NetworkRouteProtoBoot = "boot"
|
|
NetworkRouteProtoStatic = "static"
|
|
)
|
|
|
|
// Manage the state of network routes
|
|
type NetworkRoute struct {
|
|
*Common `json:",inline" yaml:",inline"`
|
|
stater machine.Stater `json:"-" yaml:"-"`
|
|
Id string
|
|
To string `json:"to" yaml:"to"`
|
|
Interface string `json:"interface" yaml:"interface"`
|
|
Gateway string `json:"gateway" yaml:"gateway"`
|
|
Metric uint `json:"metric" yaml:"metric"`
|
|
Rtid NetworkRouteTableId `json:"rtid" yaml:"rtid"`
|
|
RouteType NetworkRouteType `json:"routetype" yaml:"routetype"`
|
|
Scope NetworkRouteScope `json:"scope" yaml:"scope"`
|
|
Proto NetworkRouteProto `json:"proto" yaml:"proto"`
|
|
|
|
CreateCommand *command.Command `yaml:"-" json:"-"`
|
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
|
|
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
|
}
|
|
|
|
func NewNetworkRoute() *NetworkRoute {
|
|
n := &NetworkRoute{Rtid: NetworkRouteTableMain}
|
|
n.Common = NewCommon(NetworkRouteTypeName, false)
|
|
n.Common.NormalizePath = n.NormalizePath
|
|
n.CreateCommand, n.ReadCommand, n.UpdateCommand, n.DeleteCommand = n.NewCRUD()
|
|
return n
|
|
}
|
|
|
|
func (n *NetworkRoute) Init(u data.URIParser) (err error) {
|
|
if u == nil {
|
|
u = folio.URI(n.URI()).Parse()
|
|
}
|
|
return n.SetParsedURI(u)
|
|
}
|
|
|
|
func (n *NetworkRoute) NormalizePath() error {
|
|
return nil
|
|
}
|
|
|
|
func (n *NetworkRoute) SetResourceMapper(resources data.ResourceMapper) {
|
|
n.Resources = resources
|
|
}
|
|
|
|
func (n *NetworkRoute) Clone() data.Resource {
|
|
newn := &NetworkRoute {
|
|
Common: n.Common,
|
|
Id: n.Id,
|
|
To: n.To,
|
|
Interface: n.Interface,
|
|
Gateway: n.Gateway,
|
|
Metric: n.Metric,
|
|
Rtid: n.Rtid,
|
|
RouteType: n.RouteType,
|
|
Scope: n.Scope,
|
|
Proto: n.Proto,
|
|
}
|
|
newn.CreateCommand, newn.ReadCommand, newn.UpdateCommand, newn.DeleteCommand = n.NewCRUD()
|
|
return newn
|
|
}
|
|
|
|
func (n *NetworkRoute) StateMachine() machine.Stater {
|
|
if n.stater == nil {
|
|
n.stater = StorageMachine(n)
|
|
}
|
|
return n.stater
|
|
}
|
|
|
|
func (n *NetworkRoute) Notify(m *machine.EventMessage) {
|
|
ctx := context.Background()
|
|
switch m.On {
|
|
case machine.ENTERSTATEEVENT:
|
|
switch m.Dest {
|
|
case "start_create":
|
|
if e := n.Create(ctx); e == nil {
|
|
if triggerErr := n.stater.Trigger("created"); triggerErr == nil {
|
|
return
|
|
}
|
|
}
|
|
n.Common.State = "absent"
|
|
case "present":
|
|
n.Common.State = "present"
|
|
}
|
|
case machine.EXITSTATEEVENT:
|
|
}
|
|
}
|
|
|
|
func (n *NetworkRoute) Create(ctx context.Context) error {
|
|
_, err := n.CreateCommand.Execute(n)
|
|
if n.CreateCommand.Extractor != nil {
|
|
if err != nil {
|
|
return n.CreateCommand.Extractor([]byte(err.Error()), n)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *NetworkRoute) Update(ctx context.Context) error {
|
|
return n.Create(ctx)
|
|
}
|
|
|
|
func (n *NetworkRoute) Delete(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (n *NetworkRoute) URI() string {
|
|
return fmt.Sprintf("route://%s", n.Id)
|
|
}
|
|
|
|
func (n *NetworkRoute) Validate() error {
|
|
return fmt.Errorf("failed")
|
|
}
|
|
|
|
func (n *NetworkRoute) Apply() error {
|
|
switch n.Common.State {
|
|
case "absent":
|
|
case "present":
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *NetworkRoute) Load(docData []byte, f codec.Format) (err error) {
|
|
err = f.StringDecoder(string(docData)).Decode(n)
|
|
return
|
|
}
|
|
|
|
func (n *NetworkRoute) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
|
|
err = f.Decoder(r).Decode(n)
|
|
return
|
|
}
|
|
|
|
func (n *NetworkRoute) LoadString(docData string, f codec.Format) (err error) {
|
|
err = f.StringDecoder(docData).Decode(n)
|
|
return
|
|
}
|
|
|
|
func (n *NetworkRoute) LoadDecl(yamlResourceDeclaration string) error {
|
|
return n.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
|
}
|
|
|
|
func (n *NetworkRoute) ResolveId(ctx context.Context) string {
|
|
uri := fmt.Sprintf("%s?gateway=%s&interface=%s&rtid=%s&metric=%d&type=%s&scope=%s",
|
|
n.To, n.Gateway, n.Interface, n.Rtid, n.Metric, n.RouteType, n.Scope)
|
|
fmt.Printf("%#v\n", uri)
|
|
n.Id = hex.EncodeToString([]byte(uri))
|
|
return n.Id
|
|
}
|
|
|
|
func (n *NetworkRoute) Read(ctx context.Context) ([]byte, error) {
|
|
out, err := n.ReadCommand.Execute(n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exErr := n.ReadCommand.Extractor(out, n)
|
|
if exErr != nil {
|
|
return nil, exErr
|
|
}
|
|
|
|
/*
|
|
var cmdArgs []string = make([]string, 17)
|
|
cmdArgs[0] = "route"
|
|
cmdArgs[1] = "show"
|
|
if len(n.To) > 0 {
|
|
cmdArgs = append(cmdArgs, "to", n.To)
|
|
}
|
|
if len(n.Rtid) > 0 {
|
|
cmdArgs = append(cmdArgs, "table", string(n.Rtid))
|
|
}
|
|
if len(n.Gateway) > 0 {
|
|
cmdArgs = append(cmdArgs, "via", n.Gateway)
|
|
}
|
|
if len(n.Proto) > 0 {
|
|
cmdArgs = append(cmdArgs, "protocol", string(n.Proto))
|
|
}
|
|
if len(n.Scope) > 0 {
|
|
cmdArgs = append(cmdArgs, "scope", string(n.Scope))
|
|
}
|
|
if len(n.RouteType) > 0 {
|
|
cmdArgs = append(cmdArgs, "type", string(n.RouteType))
|
|
}
|
|
if len(n.Interface) > 0 {
|
|
cmdArgs = append(cmdArgs, "dev", n.Interface)
|
|
}
|
|
|
|
readRouteTable, errRouteList := exec.Command("ip", cmdArgs...).Output()
|
|
if errRouteList != nil {
|
|
return nil, errRouteList
|
|
}
|
|
routes := strings.Split(string(readRouteTable), "\n")
|
|
if len(routes) == 1 {
|
|
fields := strings.Split(routes[0], " ")
|
|
numberOfFields := len(fields)
|
|
if numberOfFields > 1 {
|
|
n.To = fields[0]
|
|
for i := 1; i < numberOfFields; i += 2 {
|
|
n.SetField(fields[i], fields[i+1])
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
n.ResolveId(ctx)
|
|
return yaml.Marshal(n)
|
|
}
|
|
|
|
func (n *NetworkRoute) SetField(name string, value string) {
|
|
switch name {
|
|
case "to":
|
|
n.To = value
|
|
case "table":
|
|
n.Rtid = NetworkRouteTableId(value)
|
|
case "via":
|
|
n.Gateway = value
|
|
case "protocol":
|
|
n.Proto = NetworkRouteProto(value)
|
|
case "scope":
|
|
n.Scope = NetworkRouteScope(value)
|
|
case "type":
|
|
n.RouteType = NetworkRouteType(value)
|
|
case "dev":
|
|
n.Interface = value
|
|
}
|
|
}
|
|
|
|
func (n *NetworkRoute) GetField(name string) string {
|
|
switch name {
|
|
case "to":
|
|
return n.To
|
|
case "table":
|
|
return string(n.Rtid)
|
|
case "via":
|
|
return n.Gateway
|
|
case "protocol":
|
|
return string(n.Proto)
|
|
case "scope":
|
|
return string(n.Scope)
|
|
case "type":
|
|
return string(n.RouteType)
|
|
case "dev":
|
|
return n.Interface
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (n *NetworkRoute) Type() string { return "route" }
|
|
|
|
func (n *NetworkRouteType) UnmarshalValue(value string) error {
|
|
switch value {
|
|
case string(NetworkRouteTypeUnicast), string(NetworkRouteTypeLocal), string(NetworkRouteTypeBroadcast), string(NetworkRouteTypeMulticast), string(NetworkRouteTypeThrow), string(NetworkRouteTypeUnreachable), string(NetworkRouteTypeProhibit), string(NetworkRouteTypeBlackhole), string(NetworkRouteTypeNat):
|
|
*n = NetworkRouteType(value)
|
|
return nil
|
|
default:
|
|
return errors.New("invalid NetworkRouteType value")
|
|
}
|
|
}
|
|
|
|
func (n *NetworkRouteType) UnmarshalJSON(data []byte) error {
|
|
var s string
|
|
if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil {
|
|
return unmarshalRouteTypeErr
|
|
}
|
|
return n.UnmarshalValue(s)
|
|
}
|
|
|
|
func (n *NetworkRouteType) UnmarshalYAML(value *yaml.Node) error {
|
|
var s string
|
|
if err := value.Decode(&s); err != nil {
|
|
return err
|
|
}
|
|
return n.UnmarshalValue(s)
|
|
}
|
|
|
|
func (n *NetworkRouteTableId) UnmarshalValue(value string) error {
|
|
switch value {
|
|
case NetworkRouteNumber.FindString(value):
|
|
fallthrough
|
|
case string(NetworkRouteTableLocal), string(NetworkRouteTableMain), string(NetworkRouteTableDefault), string(NetworkRouteTableAll):
|
|
*n = NetworkRouteTableId(value)
|
|
return nil
|
|
default:
|
|
return errors.New("invalid NetworkRouteTableId value")
|
|
}
|
|
}
|
|
|
|
func (n *NetworkRouteTableId) UnmarshalJSON(data []byte) error {
|
|
var s string
|
|
if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil {
|
|
return unmarshalRouteTypeErr
|
|
}
|
|
return n.UnmarshalValue(s)
|
|
}
|
|
|
|
func (n *NetworkRouteTableId) UnmarshalYAML(value *yaml.Node) error {
|
|
var s string
|
|
if err := value.Decode(&s); err != nil {
|
|
return err
|
|
}
|
|
return n.UnmarshalValue(s)
|
|
}
|
|
|
|
func (n *NetworkRouteScope) UnmarshalValue(value string) error {
|
|
switch value {
|
|
case NetworkRouteNumber.FindString(value):
|
|
fallthrough
|
|
case string(NetworkRouteScopeHost), string(NetworkRouteScopeLink), string(NetworkRouteScopeGlobal):
|
|
*n = NetworkRouteScope(value)
|
|
return nil
|
|
default:
|
|
return errors.New("invalid NetworkRouteScope value")
|
|
}
|
|
}
|
|
|
|
func (n *NetworkRouteScope) UnmarshalJSON(data []byte) error {
|
|
var s string
|
|
if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil {
|
|
return unmarshalRouteTypeErr
|
|
}
|
|
return n.UnmarshalValue(s)
|
|
}
|
|
|
|
func (n *NetworkRouteScope) UnmarshalYAML(value *yaml.Node) error {
|
|
var s string
|
|
if err := value.Decode(&s); err != nil {
|
|
return err
|
|
}
|
|
return n.UnmarshalValue(s)
|
|
}
|
|
|
|
func (n *NetworkRouteProto) UnmarshalValue(value string) error {
|
|
switch value {
|
|
case NetworkRouteNumber.FindString(value):
|
|
fallthrough
|
|
case string(NetworkRouteProtoKernel), string(NetworkRouteProtoBoot), string(NetworkRouteProtoStatic):
|
|
*n = NetworkRouteProto(value)
|
|
return nil
|
|
default:
|
|
return errors.New("invalid NetworkRouteProto value")
|
|
}
|
|
}
|
|
|
|
func (n *NetworkRouteProto) UnmarshalJSON(data []byte) error {
|
|
var s string
|
|
if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil {
|
|
return unmarshalRouteTypeErr
|
|
}
|
|
return n.UnmarshalValue(s)
|
|
}
|
|
|
|
func (n *NetworkRouteProto) UnmarshalYAML(value *yaml.Node) error {
|
|
var s string
|
|
if err := value.Decode(&s); err != nil {
|
|
return err
|
|
}
|
|
return n.UnmarshalValue(s)
|
|
}
|
|
|
|
func (n *NetworkRoute) UnmarshalJSON(data []byte) error {
|
|
if err := json.Unmarshal(data, n); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
|
|
func (n *NetworkRoute) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) {
|
|
return NewNetworkRouteCreateCommand(), NewNetworkRouteReadCommand(), NewNetworkRouteUpdateCommand(), NewNetworkRouteDeleteCommand()
|
|
}
|
|
|
|
func NewNetworkRouteCreateCommand() *command.Command {
|
|
c := command.NewCommand()
|
|
c.Path = "ip"
|
|
c.Args = []command.CommandArg{
|
|
command.CommandArg("route"),
|
|
command.CommandArg("add"),
|
|
command.CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
|
|
command.CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
|
|
command.CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
|
|
command.CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
|
|
command.CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
|
|
command.CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
|
|
command.CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
|
|
command.CommandArg("{{ if .Metric }}metric {{ .Metric }}{{ end }}"),
|
|
}
|
|
return c
|
|
}
|
|
|
|
func NewNetworkRouteReadCommand() *command.Command {
|
|
c := command.NewCommand()
|
|
c.Path = "ip"
|
|
c.Args = []command.CommandArg{
|
|
command.CommandArg("route"),
|
|
command.CommandArg("show"),
|
|
command.CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
|
|
command.CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
|
|
command.CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
|
|
command.CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
|
|
command.CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
|
|
command.CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
|
|
command.CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
|
|
}
|
|
c.Extractor = func(out []byte, target any) error {
|
|
n := target.(*NetworkRoute)
|
|
routes := strings.Split(string(out), "\n")
|
|
if len(routes) == 1 {
|
|
fields := strings.Split(routes[0], " ")
|
|
numberOfFields := len(fields)
|
|
if numberOfFields > 1 {
|
|
n.To = fields[0]
|
|
for i := 1; i < numberOfFields; i += 2 {
|
|
n.SetField(fields[i], fields[i + 1])
|
|
}
|
|
}
|
|
n.Common.State = "present"
|
|
} else {
|
|
n.Common.State = "absent"
|
|
}
|
|
return nil
|
|
}
|
|
return c
|
|
}
|
|
|
|
func NewNetworkRouteUpdateCommand() *command.Command {
|
|
c := command.NewCommand()
|
|
c.Path = "ip"
|
|
c.Args = []command.CommandArg{
|
|
command.CommandArg("del"),
|
|
command.CommandArg("{{ .Name }}"),
|
|
}
|
|
return c
|
|
}
|
|
|
|
func NewNetworkRouteDeleteCommand() *command.Command {
|
|
return nil
|
|
}
|