From 1857bdd05aaec9a4166042d7f5fe43ed523bfe22 Mon Sep 17 00:00:00 2001 From: Gibheer Date: Mon, 3 May 2021 22:40:27 +0200 Subject: [PATCH] add layer3domain create and ipblock create This is the first draft of creating layer3domains and ipblocks/containers. This allows some testing with different things, like list building for complex container output, but also how containers should behave. --- ipblock_create.go | 43 ++++++++++++++++++++++++++++++ layer3domain_create.go | 27 +++++++++++++++++++ main.go | 2 ++ types/ip.go | 60 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 ipblock_create.go create mode 100644 layer3domain_create.go create mode 100644 types/ip.go diff --git a/ipblock_create.go b/ipblock_create.go new file mode 100644 index 0000000..e149b55 --- /dev/null +++ b/ipblock_create.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + + "dim/types" +) + +type ( + IPBlockCreateOptions struct { + Attributes string `json:"attributes"` + Layer3Domain string `json:"layer3domain"` + AllowOverlap bool `json:"allow_overlap"` + } +) + +func ipblockCreate(c *Context, req Request, res *Response) error { + block := new(types.Subnet) + options := IPBlockCreateOptions{ + Attributes: "{}", + } + if err := req.ParseAtLeast(1, block, &options); err != nil { + res.AddMessage(LevelError, "could not parse parameters: %s", err) + return nil + } + + l3Id := 0 + err := c.tx.QueryRow(`select id from layer3domains where name = $1 for update`, options.Layer3Domain).Scan(&l3Id) + if err != nil { + res.AddMessage(LevelError, "could not get layer3domain") + return fmt.Errorf("could not resolve layer3domain '%s': %s", options.Layer3Domain, err) + } + + _, err = c.tx.Exec(`insert into containers(layer3domain_id, network, created_by, modified_by, attributes) + values ($1, $2, $3, $3, $4::jsonb)`, + l3Id, block, c.username, options.Attributes) + if err != nil { + res.AddMessage(LevelError, "could not create ip block") + return fmt.Errorf("could not create container '%s': %s", block, err) + } + + return nil +} diff --git a/layer3domain_create.go b/layer3domain_create.go new file mode 100644 index 0000000..8e478ba --- /dev/null +++ b/layer3domain_create.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" +) + +type ( + Layer3DomainCreateOptions string +) + +func layer3DomainCreate(c *Context, req Request, res *Response) error { + name := "" + options := Layer3DomainCreateOptions("{}") + if err := req.ParseAtLeast(1, &name, &options); err != nil { + res.AddMessage(LevelError, "could not parse parameter: %s", err) + return nil + } + + _, err := c.tx.Exec(`insert into layer3domains(name, attributes, created_by, modified_by) + values ($1, $2::jsonb, $3, $3)`, name, options, c.username) + // TODO handle unique constraint violation + if err != nil { + res.AddMessage(LevelError, "could not create layer3domain '%s'", name) + return fmt.Errorf("could not insert layer3domain '%s': %s", name, err) + } + return nil +} diff --git a/main.go b/main.go index f1a6da5..a092eb3 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,8 @@ func main() { log.Fatalf("could not create server instance: %s", err) return } + s.Register("layer3domain_create", layer3DomainCreate) + s.Register("ipblock_create", ipblockCreate) s.Register("zone_create", zoneCreate) s.Register("zone_list", zoneList) diff --git a/types/ip.go b/types/ip.go new file mode 100644 index 0000000..8756d0c --- /dev/null +++ b/types/ip.go @@ -0,0 +1,60 @@ +package types + +import ( + "bytes" + "database/sql/driver" + "fmt" + "net" +) + +type ( + // Subnet is used to parse a subnet parameter. + Subnet net.IPNet + // IP is used to parse an IP parameter. + IP net.IP +) + +// UnmarshalJSON parses a value into a subnet. +// +// It is also checked if the provided IP matches the network address +// of the subnet. +func (s *Subnet) UnmarshalJSON(in []byte) error { + in = bytes.Trim(in, `"`) + + ip, ipnet, err := net.ParseCIDR(string(in)) + if err != nil { + return fmt.Errorf("not a valid subnet") + } + if !ipnet.IP.Equal(ip) { + return fmt.Errorf("provided IP is not a subnet") + } + *s = Subnet(*ipnet) + return nil +} + +// String returns the string representation of the subnet. +// +// The subnet is returned as the subnet address and prefix separated by `/` +// as defined in RFC 4632 and RFC 4291. +func (s *Subnet) String() string { + return (*net.IPNet)(s).String() +} + +// Value implements the database Value interface. +// +// This function is needed so that a subnet can be inserted into +// the database without much casting. +func (s *Subnet) Value() (driver.Value, error) { + return s.String(), nil +} + +func (i *IP) UnmarshalJSON(in []byte) error { + in = bytes.Trim(in, `"`) + + ip := net.ParseIP(string(in)) + if ip == nil { + return fmt.Errorf("not a valid ip") + } + *i = IP(ip) + return nil +}