package main import ( "database/sql" "encoding/json" "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 pool") return fmt.Errorf("could not create new pool '%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 { name := "" if err := req.ParseAtLeast(1, &name); err != nil { res.AddMessage(LevelError, "could not parse name: %s", err) return nil } if name == "" { res.AddMessage(LevelError, "empty name was provided") return nil } result := json.RawMessage{} selClause := query.FieldsToJSON("p", 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", }) queryStr := fmt.Sprintf(`select %s from pools p join layer3domains l on p.layer3domain_id = l.id where p.name = $1`, selClause) err := c.tx.QueryRow(queryStr, name).Scan(&result) if err != nil { res.AddMessage(LevelError, "could not return result") return fmt.Errorf("could not get pool '%s': %s - query: %s", name, err, queryStr) } res.Result = result return nil } func PoolSetAttr(c *Context, req Request, res *Response) error { name := "" attrs := types.FieldMap{} if err := req.ParseAtLeast(2, &name, &attrs); err != nil { res.AddMessage(LevelError, "could not parse options: %s", err) return nil } if name == "" { res.AddMessage(LevelError, "empty name was provided") return nil } if attrs.Size() == 0 { res.AddMessage(LevelError, "no key/value pairs provided to update") return nil } // TODO this is ugly. Can we have better API somehow? fieldMap := map[string]string{ "name": "", "modified_by": "", "modified_at": "", "created_by": "", "created_at": "", "layer3domain_id": "", } if attrs.Contains("subnets") { res.AddMessage(LevelError, "can not set subnets as attributes") return nil } if attrs.Contains("layer3domain") { l3name := attrs.Fields()["layer3domain"] attrs.Delete("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) } attrs.Set("layer3domain_id", l3id) } 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 pools p set %s where name = $%d", setClause, len(args)+1) args = append(args, name) // don't forget to add the where clause parameter c.Logf(LevelInfo, "query: %s - args: %#v", queryStr, args) if _, err := c.tx.Exec(queryStr, args...); err != nil { res.AddMessage(LevelError, "could not set attributes") c.Logf(LevelError, "could not set attributes on layer3domain '%s': %s - query: `%s` - args: `%#v`", name, err, queryStr, args) return nil } return nil }