Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7e2f2d4
Add support for anonymous "auth"
sghf Mar 26, 2026
1bf7d79
Add GET /apps endpoint
sghf Apr 1, 2026
67bc163
Add GET /apps/{app_id} endpoint
sghf Apr 1, 2026
119ce73
Check if App exists before registering
sghf Apr 1, 2026
6fdf56f
Add GET /apps/{app_id}/am_i_responsible endpoint
sghf Apr 1, 2026
cd29c80
Add GetAppResponsibles endpoint
sghf Apr 1, 2026
994d3ca
Add GetAppPublications endpoint
sghf Apr 2, 2026
8ef41b9
Fix variables assignments
sghf Apr 2, 2026
7a21592
Add PostApps endpoint
sghf Apr 13, 2026
fe98d3c
Add DELETE /apps/{app_id} endpoint
sghf Apr 13, 2026
cd4781e
Add query builder and schema generator
sghf Apr 14, 2026
ac11c60
Add orderby and groupby parameters and adapt handlers
sghf Apr 14, 2026
57bddc7
Add GET /disks and /disks/{id} endpoints
sghf Apr 14, 2026
3136625
Add GET /nodes, /nodes/{id}, and /nodes/{id}/uuid endpoints
sghf Apr 14, 2026
74f96f6
Add GET /services and /services/{id} endpoints
sghf Apr 15, 2026
dbb635c
Add GET /services_instances and /services_instances/{id} endpoints
sghf Apr 15, 2026
b45c565
Add GET /services_instances_status_log endpoint
sghf Apr 15, 2026
627e80a
Add GET /arrays endpoint
sghf Apr 15, 2026
b9949b6
Add GET /nodes/hbas and /nodes/{id}/hbas endpoints
sghf Apr 15, 2026
794ce84
Add GET /nodes/{id}/disks endpoint
sghf Apr 15, 2026
3ab0bad
Add GET /nodes/{id}/interfaces endpoint
sghf Apr 15, 2026
5bc9067
Add POST /apps/{id} endpoint and refactor get and delete
sghf Apr 15, 2026
8e09f39
Move query builder to cdb
sghf Apr 20, 2026
f15bd0e
Add /missing /tags/* endpoints
sghf Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
462 changes: 457 additions & 5 deletions cdb/db_apps.go

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions cdb/db_arrays.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cdb

import (
"context"
"fmt"

"github.com/opensvc/oc3/schema"
)

func buildArraysQuery(selectExprs []string) (string, []any) {
q := From(schema.TStorArray).
RawSelect(selectExprs...).
Where(schema.StorArrayID, ">", 0)

query, args, err := q.Build()
if err != nil {
panic(fmt.Sprintf("buildArraysQuery: %v", err))
}
return query, args
}

func (oDb *DB) GetArrays(ctx context.Context, p ListParams) ([]map[string]any, error) {
query, args := buildArraysQuery(p.SelectExprs)
if gb := p.GroupByClause(""); gb != "" {
query += " " + gb
}
query += " " + p.OrderByClause("stor_array.array_name, stor_array.id")
query, args = appendLimitOffset(query, args, p.Limit, p.Offset)

rows, err := oDb.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("getArrays: %w", err)
}
defer func() { _ = rows.Close() }()

return scanRowsToMaps(rows, p.Props, p.TypeHints)
}
5 changes: 3 additions & 2 deletions cdb/db_auth_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ type DBAuthNode struct {
Nodename string
UUID string
NodeID string
Updated string
}

// return the list of auth_node rows matching the given node_id
func (oDb *DB) AuthNodesByNodeID(ctx context.Context, nodeID string) ([]DBAuthNode, error) {
defer logDuration("AuthNodesByNodeID", time.Now())
const query = `SELECT id, nodename, uuid, node_id FROM auth_node WHERE node_id = ?`
const query = `SELECT id, nodename, uuid, node_id, COALESCE(updated, '') FROM auth_node WHERE node_id = ?`
rows, err := oDb.DB.QueryContext(ctx, query, nodeID)
if err != nil {
return nil, fmt.Errorf("AuthNodesByNodeID: %w", err)
Expand All @@ -28,7 +29,7 @@ func (oDb *DB) AuthNodesByNodeID(ctx context.Context, nodeID string) ([]DBAuthNo
for rows.Next() {
var r DBAuthNode
var nodename, uid, nid sql.NullString
if err := rows.Scan(&r.ID, &nodename, &uid, &nid); err != nil {
if err := rows.Scan(&r.ID, &nodename, &uid, &nid, &r.Updated); err != nil {
return nil, fmt.Errorf("AuthNodesByNodeID scan: %w", err)
}
r.Nodename = nodename.String
Expand Down
101 changes: 101 additions & 0 deletions cdb/db_disks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package cdb

import (
"context"
"fmt"

"github.com/opensvc/oc3/schema"
)

func buildDisksQuery(groups []string, isManager bool, selectExprs []string) (string, []any) {
q := From(schema.TDiskinfo).
LeftJoin(schema.TSvcdisks, schema.TNodes, schema.TServices, schema.TApps).
RawSelect(selectExprs...)

if !isManager {
cleanGroups := cleanGroups(groups)
if len(cleanGroups) == 0 {
q = q.WhereRaw("1=0")
} else {
args := make([]any, len(cleanGroups))
for i, g := range cleanGroups {
args[i] = g
}
q = q.WhereRaw(
"diskinfo.disk_id IN ("+
"SELECT sd.disk_id FROM svcdisks sd"+
" JOIN nodes n ON sd.node_id = n.node_id"+
" JOIN apps a ON n.app = a.app"+
" JOIN apps_responsibles ar ON ar.app_id = a.id"+
" JOIN auth_group ag ON ag.id = ar.group_id"+
" WHERE ag.role IN ("+Placeholders(len(cleanGroups))+")"+
")",
args...,
)
}
} else {
q = q.Where(schema.DiskinfoID, ">", 0)
}

query, args, err := q.Build()
if err != nil {
// schema relations are static; a build error here is a programming mistake
panic(fmt.Sprintf("buildDisksQuery: %v", err))
}
return query, args
}

func (oDb *DB) GetDisk(ctx context.Context, diskID string, p ListParams) ([]map[string]any, error) {
query, args := buildDisksQuery(p.Groups, p.IsManager, p.SelectExprs)
query += " AND diskinfo.disk_id = ?"
args = append(args, diskID)
if gb := p.GroupByClause(""); gb != "" {
query += " " + gb
}
query += " " + p.OrderByClause("diskinfo.disk_id, diskinfo.disk_group")
query, args = appendLimitOffset(query, args, p.Limit, p.Offset)

rows, err := oDb.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("getDisk: %w", err)
}
defer func() { _ = rows.Close() }()

return scanRowsToMaps(rows, p.Props, p.TypeHints)
}

func (oDb *DB) GetNodeDisks(ctx context.Context, nodeID string, p ListParams) ([]map[string]any, error) {
query, args := buildDisksQuery(p.Groups, p.IsManager, p.SelectExprs)
query += " AND svcdisks.node_id = ?"
args = append(args, nodeID)
if gb := p.GroupByClause(""); gb != "" {
query += " " + gb
}
query += " " + p.OrderByClause("diskinfo.disk_id, diskinfo.disk_group")
query, args = appendLimitOffset(query, args, p.Limit, p.Offset)

rows, err := oDb.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("getNodeDisks: %w", err)
}
defer func() { _ = rows.Close() }()

return scanRowsToMaps(rows, p.Props, p.TypeHints)
}

func (oDb *DB) GetDisks(ctx context.Context, p ListParams) ([]map[string]any, error) {
query, args := buildDisksQuery(p.Groups, p.IsManager, p.SelectExprs)
if gb := p.GroupByClause(""); gb != "" {
query += " " + gb
}
query += " " + p.OrderByClause("diskinfo.disk_id, diskinfo.disk_group")
query, args = appendLimitOffset(query, args, p.Limit, p.Offset)

rows, err := oDb.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("getDisks: %w", err)
}
defer func() { _ = rows.Close() }()

return scanRowsToMaps(rows, p.Props, p.TypeHints)
}
79 changes: 79 additions & 0 deletions cdb/db_hbas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cdb

import (
"context"
"fmt"

"github.com/opensvc/oc3/schema"
)

func buildHbasQuery(groups []string, isManager bool, selectExprs []string) (string, []any) {
q := From(schema.TNodeHBA).
RawSelect(selectExprs...)

if !isManager {
cleanGroups := cleanGroups(groups)
if len(cleanGroups) == 0 {
q = q.WhereRaw("1=0")
} else {
args := make([]any, len(cleanGroups))
for i, g := range cleanGroups {
args[i] = g
}
q = q.WhereRaw(
"node_hba.node_id IN ("+
"SELECT n.node_id FROM nodes n"+
" JOIN apps a ON n.app = a.app"+
" JOIN apps_responsibles ar ON ar.app_id = a.id"+
" JOIN auth_group ag ON ag.id = ar.group_id"+
" WHERE ag.role IN ("+Placeholders(len(cleanGroups))+")"+
")",
args...,
)
}
} else {
q = q.Where(schema.NodeHBAID, ">", 0)
}

query, args, err := q.Build()
if err != nil {
panic(fmt.Sprintf("buildHbasQuery: %v", err))
}
return query, args
}

func (oDb *DB) GetHbas(ctx context.Context, p ListParams) ([]map[string]any, error) {
query, args := buildHbasQuery(p.Groups, p.IsManager, p.SelectExprs)
if gb := p.GroupByClause(""); gb != "" {
query += " " + gb
}
query += " " + p.OrderByClause("node_hba.node_id, node_hba.hba_id")
query, args = appendLimitOffset(query, args, p.Limit, p.Offset)

rows, err := oDb.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("getHbas: %w", err)
}
defer func() { _ = rows.Close() }()

return scanRowsToMaps(rows, p.Props, p.TypeHints)
}

func (oDb *DB) GetNodeHbas(ctx context.Context, nodeID string, p ListParams) ([]map[string]any, error) {
query, args := buildHbasQuery(p.Groups, p.IsManager, p.SelectExprs)
query += " AND node_hba.node_id = ?"
args = append(args, nodeID)
if gb := p.GroupByClause(""); gb != "" {
query += " " + gb
}
query += " " + p.OrderByClause("node_hba.hba_id")
query, args = appendLimitOffset(query, args, p.Limit, p.Offset)

rows, err := oDb.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("getNodeHbas: %w", err)
}
defer func() { _ = rows.Close() }()

return scanRowsToMaps(rows, p.Props, p.TypeHints)
}
71 changes: 71 additions & 0 deletions cdb/db_node_interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cdb

import (
"context"
"fmt"
"strings"

"github.com/opensvc/oc3/schema"
)

func buildNodeInterfacesQuery(nodeID string, p ListParams) (string, []any) {
q := From(schema.TNodeIP).
RawSelect(p.SelectExprs...).
Where(schema.NodeIPNodeID, "=", nodeID)

// LEFT JOIN nodes if needed.
for _, prop := range p.Props {
if strings.HasPrefix(prop, "nodes.") {
q = q.LeftJoin(schema.TNodes)
break
}
}

if !p.IsManager {
cleanGroups := cleanGroups(p.Groups)
if len(cleanGroups) == 0 {
q = q.WhereRaw("1=0")
} else {
args := make([]any, len(cleanGroups))
for i, g := range cleanGroups {
args[i] = g
}
q = q.WhereRaw(
"node_ip.node_id IN ("+
"SELECT n.node_id FROM nodes n"+
" JOIN apps a ON n.app = a.app"+
" JOIN apps_responsibles ar ON ar.app_id = a.id"+
" JOIN auth_group ag ON ag.id = ar.group_id"+
" WHERE ag.role IN ("+Placeholders(len(cleanGroups))+")"+
")",
args...,
)
}
}

query, args, err := q.Build()
if err != nil {
panic(fmt.Sprintf("buildNodeInterfacesQuery: %v", err))
}
return query, args
}

func (oDb *DB) GetNodeInterfaces(ctx context.Context, nodeID string, p ListParams) ([]map[string]any, error) {
query, args := buildNodeInterfacesQuery(nodeID, p)
query += " " + p.GroupByClause("node_ip.intf") + " " + p.OrderByClause("node_ip.intf")
query, args = appendLimitOffset(query, args, p.Limit, p.Offset)

rows, err := oDb.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("getNodeInterfaces: %w", err)
}
defer func() { _ = rows.Close() }()

// Use nested output when cross-table props (containing a dot) are requested.
for _, prop := range p.Props {
if strings.Contains(prop, ".") {
return scanRowsToNestedMaps(rows, p.Props, "node_ip")
}
}
return scanRowsToMaps(rows, p.Props, p.TypeHints)
}
68 changes: 68 additions & 0 deletions cdb/db_nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/google/uuid"

"github.com/opensvc/oc3/schema"
"github.com/opensvc/oc3/util/logkey"
)

Expand Down Expand Up @@ -49,6 +50,73 @@ func (n *DBNode) String() string {
return fmt.Sprintf("node: {nodename: %s, node_id: %s, cluster_id: %s, app: %s}", n.Nodename, n.NodeID, n.ClusterID, n.App)
}

func buildNodesQuery(groups []string, isManager bool, selectExprs []string) (string, []any) {
q := From(schema.TNodes).
RawSelect(selectExprs...)

if !isManager {
cleanGroups := cleanGroups(groups)
if len(cleanGroups) == 0 {
q = q.WhereRaw("1=0")
} else {
args := make([]any, len(cleanGroups))
for i, g := range cleanGroups {
args[i] = g
}
q = q.WhereRaw(
"nodes.app IN ("+
"SELECT a.app FROM apps a"+
" JOIN apps_responsibles ar ON ar.app_id = a.id"+
" JOIN auth_group ag ON ag.id = ar.group_id"+
" WHERE ag.role IN ("+Placeholders(len(cleanGroups))+")"+
")",
args...,
)
}
} else {
q = q.Where(schema.NodesID, ">", 0)
}

query, args, err := q.Build()
if err != nil {
panic(fmt.Sprintf("buildNodesQuery: %v", err))
}
return query, args
}

func (oDb *DB) GetNodes(ctx context.Context, p ListParams) ([]map[string]any, error) {
query, args := buildNodesQuery(p.Groups, p.IsManager, p.SelectExprs)
if gb := p.GroupByClause(""); gb != "" {
query += " " + gb
}
query += " " + p.OrderByClause("nodes.nodename")
query, args = appendLimitOffset(query, args, p.Limit, p.Offset)

rows, err := oDb.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("getNodes: %w", err)
}
defer func() { _ = rows.Close() }()

return scanRowsToMaps(rows, p.Props, p.TypeHints)
}

// GetNode fetches a single node by node_id or nodename.
func (oDb *DB) GetNode(ctx context.Context, nodeID string, p ListParams) ([]map[string]any, error) {
query, args := buildNodesQuery(p.Groups, p.IsManager, p.SelectExprs)
query += " AND (nodes.node_id = ? OR nodes.nodename = ?)"
args = append(args, nodeID, nodeID)
query, args = appendLimitOffset(query, args, p.Limit, p.Offset)

rows, err := oDb.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("getNode: %w", err)
}
defer func() { _ = rows.Close() }()

return scanRowsToMaps(rows, p.Props, p.TypeHints)
}

func (oDb *DB) NodeByNodeID(ctx context.Context, nodeID string) (*DBNode, error) {
defer logDuration("nodeByNodeID", time.Now())
if nodeID == "" {
Expand Down
Loading
Loading