264 lines
9.0 KiB
Go
264 lines
9.0 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"database/sql"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
|
||
|
"dim/query"
|
||
|
"dim/types"
|
||
|
"github.com/lib/pq"
|
||
|
)
|
||
|
|
||
|
// ContainerCreate will create a new container.
|
||
|
func containerCreate(c *Context, req Request, res *Response) error {
|
||
|
subnet := types.Subnet{}
|
||
|
options := struct {
|
||
|
Attributes types.FieldMap `json:"attributes"`
|
||
|
DisallowChildren bool `json:"disallow_children"`
|
||
|
Layer3Domain string `json:"layer3domain"`
|
||
|
AllowOverlap bool `json:"allow_overlap"`
|
||
|
}{
|
||
|
Attributes: types.FieldMap{},
|
||
|
}
|
||
|
if err := req.ParseAtLeast(1, &subnet, &options); err != nil {
|
||
|
res.AddMessage(LevelError, "could not parse options: %s", err)
|
||
|
return nil
|
||
|
}
|
||
|
if options.Layer3Domain == "" {
|
||
|
res.AddMessage(LevelError, "layer3domain name is empty")
|
||
|
return nil
|
||
|
}
|
||
|
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 containers(layer3domain_id, subnet, created_by, modified_by, attributes)
|
||
|
values ($1, $2, $3, $3, $4)`, l3id, subnet.String(), c.username, attrs)
|
||
|
if err != nil {
|
||
|
res.AddMessage(LevelError, "could not create new container")
|
||
|
return fmt.Errorf("could not create new container '%s': %#v", subnet.String(), err)
|
||
|
}
|
||
|
res.AddMessage(LevelInfo, "created container '%s'", subnet.String())
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// ContainerDelete deletes a container.
|
||
|
func containerDelete(c *Context, req Request, res *Response) error {
|
||
|
subnet := types.Subnet{}
|
||
|
options := struct {
|
||
|
Layer3Domain string `json:"layer3domain"`
|
||
|
Recursive bool `json:"recursive"`
|
||
|
}{}
|
||
|
if err := req.ParseAtLeast(1, &subnet, &options); err != nil {
|
||
|
res.AddMessage(LevelError, "could not parse options: %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 {
|
||
|
if err == sql.ErrNoRows {
|
||
|
res.AddMessage(LevelError, "layer3domain '%s' does not exist", options.Layer3Domain)
|
||
|
return nil
|
||
|
}
|
||
|
res.AddMessage(LevelError, "could not fetch layer3domain '%s'", options.Layer3Domain)
|
||
|
return fmt.Errorf("could not fetch layer3domain '%s': %#v", options.Layer3Domain, err)
|
||
|
}
|
||
|
// TODO implement Recursive (delete all containers and IPs in this subnet)
|
||
|
_, err = c.tx.Exec(`delete from containers where subnet = $1 and layer3domain_id = $2`, subnet.String(), l3id)
|
||
|
if err != nil {
|
||
|
res.AddMessage(LevelError, "could not delete container '%s' in layer3domain '%s'", subnet.String(), options.Layer3Domain)
|
||
|
return fmt.Errorf("could not delete subnets for container '%s': %#v", subnet.String(), err)
|
||
|
}
|
||
|
res.AddMessage(LevelInfo, "deleted container '%s' in layer3domain '%s'", subnet.String(), options.Layer3Domain)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// ContainerList lists all containers with the requested fields.
|
||
|
func containerList(c *Context, req Request, res *Response) error {
|
||
|
options := struct {
|
||
|
Attributes types.FieldList `json:"attributes"`
|
||
|
Layer3Domain string `json:"layer3domain"`
|
||
|
IPBlock types.Subnet `json:"ipblock"`
|
||
|
IPVersion types.IPVersion `json:"version"`
|
||
|
Depth int `json:"depth"`
|
||
|
Status string `json:"status"` // TODO not supported yet
|
||
|
}{
|
||
|
Attributes: types.NewFieldList("subnet", "", "subnets"),
|
||
|
}
|
||
|
if err := req.ParseAtLeast(0, &options); err != nil {
|
||
|
res.AddMessage(LevelError, "could not parse options")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
rawQuery := `select cfl.layer3domain_id, cfl.subnet, cfl.parents, c.attributes, cfl.state
|
||
|
from containers_free_list cfl
|
||
|
left join containers c on cfl.layer3domain_id = c.layer3domain_id and cfl.subnet = c.subnet`
|
||
|
args := []interface{}{}
|
||
|
if options.Layer3Domain != "" {
|
||
|
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)
|
||
|
}
|
||
|
args = append(args, l3id)
|
||
|
rawQuery += " where layer3domain_id = $1"
|
||
|
}
|
||
|
|
||
|
rows, err := c.tx.Query(rawQuery, args...)
|
||
|
if err != nil {
|
||
|
res.AddMessage(LevelError, "could not fetch containers")
|
||
|
return fmt.Errorf("could not fetch container tree: %#v", err)
|
||
|
}
|
||
|
defer rows.Close()
|
||
|
type (
|
||
|
Container struct {
|
||
|
Status string `json:"status"`
|
||
|
Attributes json.RawMessage `json:"attributes"`
|
||
|
Containers map[string]Container `json:"containers"`
|
||
|
}
|
||
|
)
|
||
|
result := map[int]Container{}
|
||
|
for rows.Next() {
|
||
|
cont := Container{Containers: map[string]Container{}}
|
||
|
parents := &[]string{}
|
||
|
l3id := 0
|
||
|
subnet := ""
|
||
|
attr := sql.NullString{}
|
||
|
if err := rows.Scan(&l3id, &subnet, pq.Array(parents), &attr, &cont.Status); err != nil {
|
||
|
res.AddMessage(LevelError, "could not scan containers")
|
||
|
return fmt.Errorf("could not scan containers: %#v", err)
|
||
|
}
|
||
|
if _, found := result[l3id]; !found {
|
||
|
result[l3id] = Container{Status: "layer3domain", Containers: map[string]Container{}}
|
||
|
}
|
||
|
current := result[l3id]
|
||
|
for _, parent := range *parents {
|
||
|
if _, found := current.Containers[parent]; !found {
|
||
|
current.Containers[parent] = Container{Status: "container", Containers: map[string]Container{}}
|
||
|
}
|
||
|
current = current.Containers[parent]
|
||
|
}
|
||
|
if attr.Valid {
|
||
|
cont.Attributes = json.RawMessage(attr.String)
|
||
|
}
|
||
|
current.Containers[subnet] = cont
|
||
|
}
|
||
|
res.Result = result
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func containerGetAttr(c *Context, req Request, res *Response) error {
|
||
|
subnet := types.Subnet{}
|
||
|
options := struct {
|
||
|
Layer3Domain string
|
||
|
}{}
|
||
|
if err := req.ParseAtLeast(2, &subnet, &options); err != nil {
|
||
|
res.AddMessage(LevelError, "could not parse options: %s", err)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
result := json.RawMessage{}
|
||
|
selClause := query.FieldsToJSON("c", map[string]string{
|
||
|
"subnet": "c.subnet",
|
||
|
"modified_by": "c.modified_by",
|
||
|
"modified_at": "c.modified_at",
|
||
|
"created_by": "c.created_by",
|
||
|
"created_at": "c.created_at",
|
||
|
"layer3domain": "l.name",
|
||
|
})
|
||
|
queryStr := fmt.Sprintf(`select %s from containers c join layer3domains l
|
||
|
on c.layer3domain_id = l.id
|
||
|
where c.subnet = $1 and l.name = $2`, selClause)
|
||
|
err := c.tx.QueryRow(queryStr, subnet, options.Layer3Domain).Scan(&result)
|
||
|
if err != nil {
|
||
|
res.AddMessage(LevelError, "could not return result")
|
||
|
return fmt.Errorf("could not get container '%s': %s - query: %s", subnet, err, queryStr)
|
||
|
}
|
||
|
res.Result = result
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func containerSetAttr(c *Context, req Request, res *Response) error {
|
||
|
subnet := types.Subnet{}
|
||
|
attrs := types.FieldMap{}
|
||
|
options := struct {
|
||
|
Layer3Domain string
|
||
|
}{}
|
||
|
if err := req.ParseAtLeast(3, &subnet, &attrs, &options); err != nil {
|
||
|
res.AddMessage(LevelError, "could not parse options: %s", err)
|
||
|
return nil
|
||
|
}
|
||
|
if attrs.Size() == 0 {
|
||
|
res.AddMessage(LevelError, "no key/value pairs provided to update")
|
||
|
return nil
|
||
|
}
|
||
|
if options.Layer3Domain == "" {
|
||
|
res.AddMessage(LevelError, "layer3domain is required")
|
||
|
return nil
|
||
|
}
|
||
|
if attrs.Contains("layer3domain") {
|
||
|
res.AddMessage(LevelError, "can't change the layer3domain of a subnet")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// TODO this is ugly. Can we have better API somehow?
|
||
|
fieldMap := map[string]string{
|
||
|
"layer3domain_id": "",
|
||
|
"subnet": "",
|
||
|
"modified_by": "",
|
||
|
"modified_at": "",
|
||
|
"created_by": "",
|
||
|
"created_at": "",
|
||
|
}
|
||
|
if attrs.Contains("subnets") {
|
||
|
res.AddMessage(LevelError, "can not set subnets as attributes")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
l3name := options.Layer3Domain
|
||
|
l3id := 0
|
||
|
err := c.tx.QueryRow(`select id from layer3domains where name = $1`, l3name).Scan(&l3id)
|
||
|
if err != nil {
|
||
|
if err == sql.ErrNoRows {
|
||
|
res.AddMessage(LevelError, "layer3domain '%s' does not exist", l3name)
|
||
|
return nil
|
||
|
}
|
||
|
res.AddMessage(LevelError, "could not get layer3domain")
|
||
|
return fmt.Errorf("could not fetch layer3domain id for name '%s': %#v", l3name, err)
|
||
|
}
|
||
|
|
||
|
setClause, args, err := query.FieldMapToUpdate(attrs, fieldMap)
|
||
|
if err != nil {
|
||
|
res.AddMessage(LevelError, "could not encode requested attributes: %s", err)
|
||
|
return nil
|
||
|
}
|
||
|
queryStr := fmt.Sprintf("update containers p set %s where subnet = $%d::cidr and layer3domain_id = $%d returning subnet", setClause, len(args)+1, len(args)+2)
|
||
|
args = append(args, subnet) // don't forget to add the where clause parameter
|
||
|
args = append(args, l3id) // don't forget to add the layer3domain to the where clause
|
||
|
c.Debugf(LevelInfo, "query: %s - args: %#v", queryStr, args)
|
||
|
changed := ""
|
||
|
if err := c.tx.QueryRow(queryStr, args...).Scan(&changed); err != nil {
|
||
|
if err == sql.ErrNoRows {
|
||
|
res.AddMessage(LevelError, "subnet '%s' in layer3domain '%s' does not exist", subnet.String(), l3name)
|
||
|
return nil
|
||
|
}
|
||
|
res.AddMessage(LevelError, "could not set attributes")
|
||
|
c.Logf(LevelError, "could not set attributes on subnet '%s': %s - query: `%s` - args: `%#v`", subnet, err, queryStr, args)
|
||
|
return nil
|
||
|
}
|
||
|
return nil
|
||
|
}
|