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 }