diff --git a/main.go b/main.go index 0a057e9..46c271c 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,11 @@ func main() { s.Register("layer3domain_get_attr", layer3DomainGetAttr) s.Register("layer3domain_set_attr", layer3DomainSetAttr) s.Register("ipblock_create", ipblockCreate) + s.Register("ippool_create", PoolCreate) + s.Register("ippool_delete", PoolDelete) + s.Register("ippool_list", PoolList) + s.Register("ippool_get_attr", PoolGetAttr) + s.Register("ippool_set_attr", PoolSetAttr) s.Register("zone_create", zoneCreate) s.Register("zone_list", zoneList) diff --git a/pool.go b/pool.go new file mode 100644 index 0000000..a7490b7 --- /dev/null +++ b/pool.go @@ -0,0 +1,149 @@ +package main + +import ( + "fmt" + + "dim/query" + "dim/types" +) + +// PoolCreate will create a new pool. +func PoolCreate(c *Context, req Request, res *Response) error { + name := "" + options := struct { + Layer3Domain string `json:"layer3domain"` + VLAN uint `json:"vlan"` + Owner string `json:"owner"` + Attributes types.FieldMap `json:"attributes"` + }{ + Attributes: types.NewFieldMap(map[string]interface{}{}), + } + if err := req.ParseAtLeast(1, &name, &options); err != nil { + res.AddMessage(LevelError, "could not parse options: %s", err) + return nil + } + if name == "" { + res.AddMessage(LevelError, "name is empty") + return nil + } + if options.VLAN > 0 { + options.Attributes.Set("vlan", options.VLAN) + } + attrs, err := options.Attributes.MarshalJSON() + if err != nil { + res.AddMessage(LevelError, "could not encode attributes to json: %s", err) + return nil + } + + l3id := 0 + err = c.tx.QueryRow(`select id from layer3domains where name = $1`, options.Layer3Domain).Scan(&l3id) + if err != nil { + res.AddMessage(LevelError, "could not resolve layer3domain") + return fmt.Errorf("could not resolve layer3domain '%s': %#v", options.Layer3Domain, err) + } + + _, err = c.tx.Exec(`insert into pools(name, created_by, modified_by, attributes, layer3domain_id) + values ($1, $2, $2, $3, $4)`, name, c.username, attrs, l3id) + if err != nil { + res.AddMessage(LevelError, "could not create new layer3domain") + return fmt.Errorf("could not create new layer3domain '%s': %#v", name, err) + } + res.AddMessage(LevelInfo, "created pool '%s'", name) + return nil +} + +// PoolDelete deletes a pool. +func PoolDelete(c *Context, req Request, res *Response) error { + name := "" + options := struct { + Force bool `json:"force"` + DeleteSubnets bool `json:"delete_subnets"` + }{} + if err := req.ParseAtLeast(1, &name, &options); err != nil { + res.AddMessage(LevelError, "could not parse options: %s", err) + return nil + } + if name == "" { + res.AddMessage(LevelError, "name is empty") + return nil + } + + id := 0 + l3id := 0 + err := c.tx.QueryRow(`select id, layer3domain_id from pools where name = $1 for update`, name). + Scan(&id, &l3id) + if err != nil { + res.AddMessage(LevelError, "could not fetch pool information") // TODO implement sql.ErrNotFound + return fmt.Errorf("could not find pool '%s': %#v", name, err) + } + if options.Force && options.DeleteSubnets { + // TODO what else needs to be deleted? + _, err := c.tx.Exec(`delete from containers where id = $1 and layer3domain_id = $2`, id, l3id) + if err != nil { + res.AddMessage(LevelError, "could not delete subnets") + return fmt.Errorf("could not delete subnets for pool '%s': %#v", name, err) + } + } + if _, err := c.tx.Exec(`delete from pools where id = $1 and layer3domain_id = $2`, id, l3id); err != nil { + res.AddMessage(LevelError, "could not delete pool") + return fmt.Errorf("could not delete pool '%s': %#v", name, err) + } + res.AddMessage(LevelInfo, "deleted pool '%s'", name) + return nil +} + +// PoolList lists all pools with the requested fields. +func PoolList(c *Context, req Request, res *Response) error { + options := struct { + Attributes types.FieldList + }{ + Attributes: types.NewFieldList("name", "vlan", "subnets"), + } + if err := req.ParseAtLeast(0, &options); err != nil { + res.AddMessage(LevelError, "could not parse options") + return nil + } + + fieldMap := map[string]string{ + "name": "p.name", + "modified_by": "p.modified_by", + "modified_at": "p.modified_at", + "created_by": "p.created_by", + "created_at": "p.created_at", + "layer3domain": "l.name", + "subnets": "(jsonb_agg(c.network::text) filter (where c.network is not null))::text", + } + selClause := query.FieldListToSelect("p", options.Attributes, fieldMap) + from := "pools p" + groupBy := "p.name, p.modified_by, p.modified_at, p.created_by, p.created_at, p.attributes" + if options.Attributes.Contains("layer3domain") { + from += " join layer3domains l on p.layer3domain_id = l.id" + groupBy += ",l.name" + } + if options.Attributes.Contains("subnets") { + from += " left join containers c on p.id = c.pool_id and p.layer3domain_id = c.layer3domain_id" + } + qry := fmt.Sprintf("select %s from %s group by %s order by p.name", selClause, from, groupBy) + rows, err := c.tx.Query(qry) + if err != nil { + res.AddMessage(LevelError, "could not fetch pools") + return fmt.Errorf("could not fetch pools: %#v - query: %s", err, qry) + } + defer rows.Close() + res.Result, err = query.RowsToMap(rows) + if err != nil { + res.Result = nil + res.AddMessage(LevelError, "could not parse pools") + return fmt.Errorf("could not generate pool list: %#v", err) + } + + return nil +} + +func PoolGetAttr(c *Context, req Request, res *Response) error { + return nil +} + +func PoolSetAttr(c *Context, req Request, res *Response) error { + return nil +} diff --git a/types/fields.go b/types/fields.go index a67ff53..81a8e12 100644 --- a/types/fields.go +++ b/types/fields.go @@ -74,6 +74,10 @@ func NewFieldMap(fields map[string]interface{}) FieldMap { return FieldMap{fields: fields} } +func (fm *FieldMap) MarshalJSON() ([]byte, error) { + return json.Marshal(fm.fields) +} + // UnmarshalJSON implements the json decoding interface so that it can be used with // with the request parsing functions. func (fm *FieldMap) UnmarshalJSON(raw []byte) error { @@ -90,6 +94,11 @@ func (fm *FieldMap) UnmarshalJSON(raw []byte) error { return nil } +// Set adds a key to the field map. +func (fm FieldMap) Set(key string, val interface{}) { + fm.fields[key] = val +} + // Fields returns all key/value pairs. func (fm FieldMap) Fields() map[string]interface{} { return fm.fields