dim/container.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
}