From 7e2f2d4f4d93c48062217f96411757f71d719ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Thu, 26 Mar 2026 18:58:40 +0100 Subject: [PATCH 01/24] Add support for anonymous "auth" --- cmd/server.go | 1 + server/api.yaml | 1 + server/codegen_server_gen.go | 24 +++++++++---------- server/handlers/middleware.go | 18 ++++++++++++-- server/handlers/post_auth_node.go | 34 +++++++++++++++++---------- xauth/anon-register.go | 39 +++++++++++++++++++++++++++++++ 6 files changed, 91 insertions(+), 26 deletions(-) create mode 100644 xauth/anon-register.go diff --git a/cmd/server.go b/cmd/server.go index 7371b87..e73f525 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -53,6 +53,7 @@ func (t *server) docMiddleware() echo.MiddlewareFunc { func (t *server) authMiddleware(publicPath, publicPrefix []string) echo.MiddlewareFunc { return handlers.AuthMiddleware(union.New( xauth.NewPublicStrategy(publicPath, publicPrefix), + xauth.NewAnonRegister(), xauth.NewBasicWeb2py(t.db, viper.GetString("w2p_hmac")), xauth.NewBasicNode(t.db), )) diff --git a/server/api.yaml b/server/api.yaml index 0bd8cd1..4068ca7 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -48,6 +48,7 @@ paths: 500: $ref: '#/components/responses/500' security: + - { } - basicAuth: [ ] - bearerAuth: [ ] tags: diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 30e21ec..8709544 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -661,18 +661,18 @@ var swaggerSpec = []string{ "ZVX13BFu9eEZvqwn2llGohNM56DsbcQinVLt1YaJXPKGevhBt9995aIoB0WLA/6q2+/2ecf1vU6QHpWZ", "nuvGSZ/aBqw+hLG0CIYJ17Yz2gIKywzpTElmcP+o++bn2iKJe+abfOOr52sdT7dKtA8SYJ4HswlB8j3E", "/Roj15QBK7e6iz+2rAqLYGtjPURbFDJej9RRlSYPgX1YW46iCHJc6BRCeaIWsEdE82q7jvavRplcTUtE", - "zZBxubQRLFc3lDGbAXF1MyMJxdiS4ORa/IZOcD5pe/flODhz/FIpVAS9SKhYxgLhcz3DOq2PQ/PBO0BW", - "b5gPvZYluvLogAu/AyTvPa6ZHldHfJiz7CxM3ldh5cxJegvDMSliLX1jttic3k+im9OXk9XmG3yN2Zjc", - "TyJk5kdF2LrKV5f3QICcllNYyBEare7DxjQGpjSypOpOyxhY0thZ39jBvLHbMQrqlL5NKJjtAsE8MgyG", - "L0Hw/IPA/A4hkOrxGqevaRnRbunxp3T8fp38sU6y6fASmovbqqpf1z5nJ9mwUWi0BwJRRBOIaZzZ3Fte", - "uoNnnBhrk/8mzcFcjN59+Tln5iGmgNAGewIkPxNz+VlidLbc/U/cQUsiYA8B0PjQFXCggNVqcMwWUQTW", - "JkWaTlkM3vCrra1NQzFPbfrOkmH9CFs2XJXAaFT/hey324uCwHy8+AGvPdz/sco3qjzg3aG/yYjcb3wb", - "WEd7sO2YvpfRe4u8stGoYR5XSF/mi+dfRn+L8cLUJdRsU0LNzgV0uNf0O9ymfA4fVTzNcymdZqfC+YR2", - "e8qyOXwpmvM8Un5p6VbKLqtjq9ZdfBfjsfvO82TZ+uO/fhntV9rMi1Eqo1KV/uGSBsOVGrhz367HzJEG", - "OopLsdOrm5d+4Ed62JPEIT3v3aMYV/U66EUn0uapmJL/VBeXpkv8qO1Gy24YoiMO3C70aDa5XNi4ybDn", - "N4drvzRWWeNwk0xw+GvY33d3K4cVR8G+T8BAZUQm56PLEqc4c+c+D8/46Yns6V5aV3NHaUSJE3//hPS9", - "rCe9FONgH7pfN23cWAm6pr+OyUQuWUUa8MT/1Es/LdVX3H9Mlq/VIXIxkql0tyZuZl6z1PD7OCpMyge8", - "2+Ozm9n/AwAA//94ygbJ8y4AAA==", + "zZChXEo5shEvVzfuQSMmrm5mJKQYW5KdvIvf0CHOLW3vvpwIZ45lKoWKoBcJFctYIHyux1in+HFoRHgH", + "yOoN87nXskRXTh3w4neA5MDHNdPj6ogPc5adheH7KqyfOUlvYT4mRaylb4wXm9P7YXRz+nK42nyDLzMb", + "k/thhMz8qCBbV/zqCh+IkdNyEAs5QqPbfdibxsCURpZUDWoZBkt6O+t7O5j3do1A2CYK6qy+TSiY7QLB", + "PDIMhi9B8PyDwPwOIZDq8Rqnr2kZ0W7p8ad0/H6d/LFOsun8EhqN26qq39g+ZyfZsFFotAcCUUQTiGmi", + "2dxbXrqDZ5wYa5P/Js3BXIzefflFZ+YhpoDQBnsCJD8Tc/lZYnS23P1P3EFLImAPAdD41hVwoIDVanDM", + "FlEE1iZFmk5ZDN7wq62tTUMxT236zpJ5/QhbNlyVwGha/4Xst9u7gsCIvPgNrz3f/7HKN6o84N2hv8mU", + "3G98HlhHe7DtpL7z9P2T8spGo4Z5XCF9mS+efxn9LcYLU5dQs00JNTsX0OFe0+9wm/I5fFTxNM+ldJqd", + "CucT2u0py+bwpWjO80j5saVbKbusjq1ad/FdjMfuU8+TZeuP//pltF9pMy9GqYxKVfqHSxoMV2rgzn2+", + "HjNHGugoLsVOr25e+oEf6WFPEof0vHePYlzV66AXnUibp2JK/lPdXZou8aO2Gy27ZIiOOHDB0KPZ5H5h", + "4zLDnt8crv3YWGWNw00yweGvYX/f3a0cVhwF+z4BA5URmZyPLkuc4syd+zw846cnsqd7aV3NHaURJU78", + "FRTS97Ke9FKMg33oft20cWkl6Jr+RiYTuWQVacAT/1Mv/bRUX3H/MVm+VofIxUim0l2cuJl5zVLD7+Oo", + "MCkf8G6Pz25m/w8AAP//exehkvYuAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/handlers/middleware.go b/server/handlers/middleware.go index 43ccff6..fc3612c 100644 --- a/server/handlers/middleware.go +++ b/server/handlers/middleware.go @@ -21,8 +21,9 @@ const ( ) const ( - AuthModeUser = "user" - AuthModeNode = "node" + AuthModeUser = "user" + AuthModeNode = "node" + AuthModeAnonRegister = "anon_register" ) // AuthMiddleware returns auth middleware that authenticate requests from strategies. @@ -37,6 +38,9 @@ func AuthMiddleware(strategies union.Union) echo.MiddlewareFunc { } ext := user.GetExtensions() + if authMode := ext.Get(xauth.XAuthMode); authMode != "" { + c.Set(XAuthMode, authMode) + } if nodeID := ext.Get(xauth.XNodeID); nodeID != "" { c.Set(XNodeID, nodeID) c.Set(XAuthMode, AuthModeNode) @@ -95,3 +99,13 @@ func IsAuthByNode(c echo.Context) bool { authMode, ok := c.Get(XAuthMode).(string) return ok && authMode == AuthModeNode } + +func IsAuthByUser(c echo.Context) bool { + authMode, ok := c.Get(XAuthMode).(string) + return ok && authMode == AuthModeUser +} + +func IsAuthByAnonRegister(c echo.Context) bool { + authMode, ok := c.Get(XAuthMode).(string) + return ok && authMode == AuthModeAnonRegister +} diff --git a/server/handlers/post_auth_node.go b/server/handlers/post_auth_node.go index be69e5f..cbbe626 100644 --- a/server/handlers/post_auth_node.go +++ b/server/handlers/post_auth_node.go @@ -31,12 +31,14 @@ func (a *Api) PostAuthNode(c echo.Context) error { var app string var userID int64 + var teamResponsible string if body.App != nil { app = *body.App } - if !IsAuthByNode(c) { + switch { + case IsAuthByUser(c): // User auth user := UserInfoFromContext(c) if user == nil { @@ -68,13 +70,27 @@ func (a *Api) PostAuthNode(c echo.Context) error { return JSONProblemf(c, http.StatusForbidden, "you are not responsible for the '%s' app", app) } } - } else { - // Node auth - // Check in config that refuse_anon_register is not set to true - allowAnonRegister := viper.GetBool("server.allow_anon_register") - if !allowAnonRegister { + + teamResponsible, _, err = odb.UserDefaultGroup(ctx, userID) + if err != nil { + log.Error("failed to find default group", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot find default group") + } + case IsAuthByNode(c), IsAuthByAnonRegister(c): + if !viper.GetBool("server.allow_anon_register") { return JSONProblemf(c, http.StatusForbidden, "anonymous node registration is disabled") } + if app == "" && IsAuthByNode(c) { + if s, ok := c.Get(XApp).(string); ok { + app = s + } + } + if app == "" { + return JSONProblemf(c, http.StatusBadRequest, "missing app") + } + teamResponsible = "Everybody" + default: + return JSONProblemf(c, http.StatusUnauthorized, "missing authentication") } var ( @@ -92,12 +108,6 @@ func (a *Api) PostAuthNode(c echo.Context) error { nodeID = node.NodeID } else { // Node does not exist: create it with a new node_id - teamResponsible, _, err := odb.UserDefaultGroup(ctx, userID) - if err != nil { - log.Error("failed to find default group", logkey.Error, err) - return JSONProblemf(c, http.StatusInternalServerError, "cannot find default group") - } - nodeID = uuid.New().String() if err := odb.InsertNode(ctx, nodename, teamResponsible, app, nodeID); err != nil { log.Error("failed to insert node", logkey.Error, err) diff --git a/xauth/anon-register.go b/xauth/anon-register.go new file mode 100644 index 0000000..292ad4e --- /dev/null +++ b/xauth/anon-register.go @@ -0,0 +1,39 @@ +package xauth + +import ( + "context" + "net/http" + + "github.com/shaj13/go-guardian/v2/auth" + "github.com/spf13/viper" +) + +const ( + XAuthMode = "auth_mode" + AuthModeAnonRegister = "anon_register" +) + +type anonRegister struct{} + +func NewAnonRegister() auth.Strategy { + return &anonRegister{} +} + +func (a *anonRegister) Authenticate(_ context.Context, r *http.Request) (auth.Info, error) { + if r.Method != http.MethodPost { + return nil, ErrPrivatePath + } + if r.URL.Path != "/api/auth/node" { + return nil, ErrPrivatePath + } + if !viper.GetBool("server.allow_anon_register") { + return nil, ErrPrivatePath + } + if r.Header.Get("Authorization") != "" { + return nil, ErrPrivatePath + } + + ext := make(auth.Extensions) + ext.Set(XAuthMode, AuthModeAnonRegister) + return auth.NewUserInfo("anon-register", "", nil, ext), nil +} From 1bf7d79172b3c2e4357465d6a271300225128663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 1 Apr 2026 15:19:55 +0200 Subject: [PATCH 02/24] Add GET /apps endpoint --- cdb/db_apps.go | 82 ++++++++++++++++++++-- server/api.yaml | 25 +++++++ server/codegen_server_gen.go | 114 +++++++++++++++++++++++-------- server/codegen_type_gen.go | 18 +++++ server/handlers/get_apps.go | 41 +++++++++++ server/handlers/props_mapping.go | 3 + 6 files changed, 248 insertions(+), 35 deletions(-) create mode 100644 server/handlers/get_apps.go diff --git a/cdb/db_apps.go b/cdb/db_apps.go index b325e45..9208be1 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -8,12 +8,84 @@ import ( ) type ( - DBApp struct { - id int64 - app string + App struct { + ID int64 `json:"id"` + App string `json:"app"` + Updated string `json:"updated"` + AppDomain string `json:"app_domain"` + AppTeamOps string `json:"app_team_ops"` + Description string `json:"description"` } ) +func (oDb *DB) GetApps(ctx context.Context, groups []string, isManager bool, limit, offset int) ([]App, error) { + query := ` + SELECT DISTINCT apps.id, apps.app, apps.updated, apps.app_domain, apps.app_team_ops, apps.description + FROM apps + ` + args := make([]any, 0) + + if !isManager { + cleanGroups := cleanGroups(groups) + query += ` + JOIN apps_responsibles ON apps.id = apps_responsibles.app_id + JOIN auth_group ON apps_responsibles.group_id = auth_group.id + ` + if len(cleanGroups) == 0 { + query += " WHERE 1=0" + } else { + query += " WHERE auth_group.role IN (" + Placeholders(len(cleanGroups)) + ")" + for _, g := range cleanGroups { + args = append(args, g) + } + } + } else { + query += " WHERE apps.id > 0" + } + + query += " ORDER BY apps.app, apps.id" + query, args = appendLimitOffset(query, args, limit, offset) + + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("getApps: %w", err) + } + defer func() { _ = rows.Close() }() + + apps := make([]App, 0) + for rows.Next() { + var ( + item App + updated sql.NullString + appDomain sql.NullString + appTeamOps sql.NullString + description sql.NullString + ) + if err := rows.Scan(&item.ID, &item.App, &updated, &appDomain, &appTeamOps, &description); err != nil { + return nil, fmt.Errorf("getApps scan: %w", err) + } + if updated.Valid { + item.Updated = updated.String + } + if appDomain.Valid { + item.AppDomain = appDomain.String + } + if appTeamOps.Valid { + item.AppTeamOps = appTeamOps.String + } + if description.Valid { + item.Description = description.String + } + apps = append(apps, item) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("getApps rows: %w", err) + } + + return apps, nil +} + // AuthGroupIdsForNode is the oc3 implementation of oc2 node_responsibles(node_id): // // q = db.nodes.node_id == node_id @@ -94,7 +166,7 @@ func (oDb *DB) ResponsibleAppsForNode(ctx context.Context, nodeID string) (apps return } -func (oDb *DB) appFromAppName(ctx context.Context, app string) (bool, *DBApp, error) { +func (oDb *DB) appFromAppName(ctx context.Context, app string) (bool, *App, error) { const query = "SELECT id, app FROM apps WHERE app = ?" var ( foundID int64 @@ -110,7 +182,7 @@ func (oDb *DB) appFromAppName(ctx context.Context, app string) (bool, *DBApp, er case err != nil: return false, nil, err default: - return true, &DBApp{id: foundID, app: foundApp}, nil + return true, &App{ID: foundID, App: foundApp}, nil } } diff --git a/server/api.yaml b/server/api.yaml index 4068ca7..c4d3efc 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -8,6 +8,31 @@ info: version: 1.0.3 paths: + /apps: + get: + operationId: GetApps + description: List available apps + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /auth/node: post: description: | diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 8709544..e95f360 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -21,6 +21,9 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { + // (GET /apps) + GetApps(ctx echo.Context, params GetAppsParams) error + // (POST /auth/node) PostAuthNode(ctx echo.Context) error @@ -72,6 +75,56 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// GetApps converts echo context to params. +func (w *ServerInterfaceWrapper) GetApps(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetAppsParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetApps(ctx, params) + return err +} + // PostAuthNode converts echo context to params. func (w *ServerInterfaceWrapper) PostAuthNode(ctx echo.Context) error { var err error @@ -622,6 +675,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.GET(baseURL+"/apps", wrapper.GetApps) router.POST(baseURL+"/auth/node", wrapper.PostAuthNode) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_modulesets", wrapper.GetNodeComplianceCandidateModulesets) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_rulesets", wrapper.GetNodeComplianceCandidateRulesets) @@ -643,36 +697,36 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "MCkf8G6Pz25m/w8AAP//exehkvYuAAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 60afe6e..fef2c91 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -96,6 +96,24 @@ type N409 = Problem // N500 defines model for 500. type N500 = Problem +// GetAppsParams defines parameters for GetApps. +type GetAppsParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` +} + // PostAuthNodeJSONBody defines parameters for PostAuthNode. type PostAuthNodeJSONBody struct { App *string `json:"app,omitempty"` diff --git a/server/handlers/get_apps.go b/server/handlers/get_apps.go new file mode 100644 index 0000000..636dd0d --- /dev/null +++ b/server/handlers/get_apps.go @@ -0,0 +1,41 @@ +package serverhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetApps handles GET /apps +func (a *Api) GetApps(c echo.Context, params server.GetAppsParams) error { + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["app"]) + if err != nil { + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log := echolog.GetLogHandler(c, "GetApps") + odb := a.getODB() + ctx := c.Request().Context() + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", "limit", query.Page.Limit, "offset", query.Page.Offset, "props", query.Props, "meta", query.WithMeta, "stats", query.WithStats, "is_manager", isManager) + + apps, err := odb.GetApps(ctx, groups, isManager, query.Page.Limit, query.Page.Offset) + if err != nil { + log.Error("cannot get apps", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get apps") + } + + filteredItems, err := filterItemsFields(apps, query.Props) + if err != nil { + log.Error("cannot project app props", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot project app props") + } + + return c.JSON(http.StatusOK, newListResponse(filteredItems, propsMapping["app"], query)) +} diff --git a/server/handlers/props_mapping.go b/server/handlers/props_mapping.go index 858f146..94e2128 100644 --- a/server/handlers/props_mapping.go +++ b/server/handlers/props_mapping.go @@ -6,6 +6,9 @@ type propMapping struct { } var propsMapping = map[string]propMapping{ + "app": { + Available: []string{"id", "app", "updated", "app_domain", "app_team_ops", "description"}, + }, "tag": { Available: []string{"id", "tag_name", "tag_created", "tag_exclude", "tag_data", "tag_id"}, Blacklist: map[string]struct{}{"id": {}}, From 67bc1636f6aafc3c2a72532fb5bbeb7f2be9aac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 1 Apr 2026 15:35:41 +0200 Subject: [PATCH 03/24] Add GET /apps/{app_id} endpoint --- cdb/db_apps.go | 104 +++++++++++++++++++++++++---------- server/api.yaml | 29 ++++++++++ server/codegen_server_gen.go | 94 +++++++++++++++++++++---------- server/codegen_type_gen.go | 6 ++ server/handlers/get_app.go | 44 +++++++++++++++ 5 files changed, 218 insertions(+), 59 deletions(-) create mode 100644 server/handlers/get_app.go diff --git a/cdb/db_apps.go b/cdb/db_apps.go index 9208be1..d9bf0b9 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "strconv" ) type ( @@ -18,7 +19,40 @@ type ( } ) -func (oDb *DB) GetApps(ctx context.Context, groups []string, isManager bool, limit, offset int) ([]App, error) { +func scanApps(rows *sql.Rows) ([]App, error) { + apps := make([]App, 0) + for rows.Next() { + var ( + item App + updated sql.NullString + appDomain sql.NullString + appTeamOps sql.NullString + description sql.NullString + ) + if err := rows.Scan(&item.ID, &item.App, &updated, &appDomain, &appTeamOps, &description); err != nil { + return nil, fmt.Errorf("scanApps: %w", err) + } + if updated.Valid { + item.Updated = updated.String + } + if appDomain.Valid { + item.AppDomain = appDomain.String + } + if appTeamOps.Valid { + item.AppTeamOps = appTeamOps.String + } + if description.Valid { + item.Description = description.String + } + apps = append(apps, item) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("scanApps rows: %w", err) + } + return apps, nil +} + +func buildAppsQuery(groups []string, isManager bool) (string, []any) { query := ` SELECT DISTINCT apps.id, apps.app, apps.updated, apps.app_domain, apps.app_team_ops, apps.description FROM apps @@ -43,6 +77,11 @@ func (oDb *DB) GetApps(ctx context.Context, groups []string, isManager bool, lim query += " WHERE apps.id > 0" } + return query, args +} + +func (oDb *DB) GetApps(ctx context.Context, groups []string, isManager bool, limit, offset int) ([]App, error) { + query, args := buildAppsQuery(groups, isManager) query += " ORDER BY apps.app, apps.id" query, args = appendLimitOffset(query, args, limit, offset) @@ -52,38 +91,45 @@ func (oDb *DB) GetApps(ctx context.Context, groups []string, isManager bool, lim } defer func() { _ = rows.Close() }() - apps := make([]App, 0) - for rows.Next() { - var ( - item App - updated sql.NullString - appDomain sql.NullString - appTeamOps sql.NullString - description sql.NullString - ) - if err := rows.Scan(&item.ID, &item.App, &updated, &appDomain, &appTeamOps, &description); err != nil { - return nil, fmt.Errorf("getApps scan: %w", err) - } - if updated.Valid { - item.Updated = updated.String - } - if appDomain.Valid { - item.AppDomain = appDomain.String - } - if appTeamOps.Valid { - item.AppTeamOps = appTeamOps.String - } - if description.Valid { - item.Description = description.String - } - apps = append(apps, item) + apps, err := scanApps(rows) + if err != nil { + return nil, fmt.Errorf("getApps: %w", err) } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("getApps rows: %w", err) + return apps, nil +} + +func (oDb *DB) GetApp(ctx context.Context, appIDOrName string, groups []string, isManager bool) (*App, error) { + query, args := buildAppsQuery(groups, isManager) + + if id, err := strconv.ParseInt(appIDOrName, 10, 64); err == nil { + query += " AND apps.id = ?" + args = append(args, id) + } else { + query += " AND apps.app = ?" + args = append(args, appIDOrName) } + query += " ORDER BY apps.app, apps.id LIMIT 2" - return apps, nil + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("getApp: %w", err) + } + defer func() { _ = rows.Close() }() + + apps, err := scanApps(rows) + if err != nil { + return nil, fmt.Errorf("getApp: %w", err) + } + + switch len(apps) { + case 0: + return nil, nil + case 1: + return &apps[0], nil + default: + return nil, fmt.Errorf("getApp: multiple apps found for %q", appIDOrName) + } } // AuthGroupIdsForNode is the oc3 implementation of oc2 node_responsibles(node_id): diff --git a/server/api.yaml b/server/api.yaml index c4d3efc..f12b4ac 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -33,6 +33,35 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /apps/{app_id}: + get: + operationId: GetApp + description: Display app properties + parameters: + - in: path + name: app_id + required: true + description: App record id or app code + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /auth/node: post: description: | diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index e95f360..8c57f5a 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -24,6 +24,9 @@ type ServerInterface interface { // (GET /apps) GetApps(ctx echo.Context, params GetAppsParams) error + // (GET /apps/{app_id}) + GetApp(ctx echo.Context, appId string, params GetAppParams) error + // (POST /auth/node) PostAuthNode(ctx echo.Context) error @@ -125,6 +128,35 @@ func (w *ServerInterfaceWrapper) GetApps(ctx echo.Context) error { return err } +// GetApp converts echo context to params. +func (w *ServerInterfaceWrapper) GetApp(ctx echo.Context) error { + var err error + // ------------- Path parameter "app_id" ------------- + var appId string + + err = runtime.BindStyledParameterWithOptions("simple", "app_id", ctx.Param("app_id"), &appId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter app_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetAppParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetApp(ctx, appId, params) + return err +} + // PostAuthNode converts echo context to params. func (w *ServerInterfaceWrapper) PostAuthNode(ctx echo.Context) error { var err error @@ -676,6 +708,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL } router.GET(baseURL+"/apps", wrapper.GetApps) + router.GET(baseURL+"/apps/:app_id", wrapper.GetApp) router.POST(baseURL+"/auth/node", wrapper.PostAuthNode) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_modulesets", wrapper.GetNodeComplianceCandidateModulesets) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_rulesets", wrapper.GetNodeComplianceCandidateRulesets) @@ -697,36 +730,37 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xaX3PbNhL/KhjcPeokpXEfqjc3aTK5cxyf7N492JoMRC4ltCTAAEvXOo+++80CJEWJ", - "kETZqRynftKIXGJ/2P3tP4L3PNJZrhUotHx0z3NhRAYIxv2T6kLg/KMF/BDT/xhsZGSOUis+4h/eMp0w", - "nAPLdFykYAF5j0u6lQuc8x5XIgM+4pkF/Cxj3uMGvhTSQMxHaArocRvNIRO0NC5yErVopJrx5bJXKj/X", - "MexWrnQMYb1056F6x3s3bXZt2Txwy/8uwCzOZCaxrfqKLC3uZFZkTBXZFAxBAYVGgmWomQEsjOqzIctA", - "KMuUZikt1a8wfqHVVyDdTd6EFEMiihT56Mdhj2dSkS4+GvYqrFIhzMA0wX4EFAEzqSgtYmAZoIgFCiaV", - "NxrYXCsLffaLEtMUYjZdsFJrn/1qgSUitcC0YUPaks4keooBCpZISONtuyEJ3sm+n5KEHNcCffm7zJ2y", - "RBqLtWVLf7ttRIWx2myDoP3CQYt2NuiF0bltgztlqbRIYHKjczBYOl2WlpaKgYjmHmYsI3pMCbPYhjV3", - "ajrZ6xIFBhC90QqNTq2zjoNhpVYrTxMZIW5iIfSC3XBLC95w9jsseizSCoVUUs3ccxZSiBDi5jZjaVGq", - "CNmtSAuwLNKFQrttZ271nTtbUlh6Irp9nQyH9ENIQDliiDxPZSQI+OA3S9u9b6z3dwMJH/G/DVbJc+Dv", - "2sGF0dMUMq9l3WA/i5iN4UsBFvmyx0+Gr46h9VclCpxrI/8HsVf7+hhq32kzlXEMyus8OYbOc43snS5U", - "uc+fjqHzjVZJKiPn0R+Pw6MPCsEokbJLMLdg2C/GaJ9Cyodp7TNpscrNq1BymG6FTCn5fs6rVCMRMhuI", - "lDpPCWPEgv670GtI1gmsx6sodTriWBJakV6s6W4/VV7R09/AG7FMaPFD0KVV5Wzr0XXSD2DQKNLQrRA+", - "Muy4zB5t41L6pV+t4FPCR9dt9KuVNtBvt9ojrLlxYbLs+UK5h301e3yurFqYa7+/SUBRxdeWRRDuMFTO", - "5kUm1D8MiJi4yOAuT4VykcJsDpFMZET1DefSMh1FhTGgIiir8Y3Kvb7+jeK9QOlqYnYIQphvwVjpg3Id", - "c+MG3IksT+m5YX/Yf7VXWfVoWx/FJ0SFkbi4JDN7VVNhZXRa4LxODfSMu7rSNUfMCfAUhAFTSft/77TJ", - "BPIR/+d/r6qq55ZwdzfX8GU90c4zEt3GdA7K3kYs0inVXm2YyCVvmIe/6g/7r10U5aDo5oi/7g/7Q95z", - "fa/byEDkPlxnod6KCMXqzMOcrFvOOJdTq83fA576680x5DrM1JXIYK1xojjqJu/b7O7yZdvY/QEfQJ3F", - "fZu1nGw0Jz98xaKylrsCleXTvxplLLRQjWxAQk1KO0c1yHw9oZ03CXs9ob2hmJFTeU02PqFlBtSkDNws", - "R9GobYBDY5hJi2CYcEMfo0dAYWkKlwjWCXWhLZLucz8iGt97/azjxUEW3SifeR6sRQTJd6D3e1JELRnI", - "Ea3e9FD3r4OtQ30TbVHIeD9SJ1UmjBDYTf6cRhHkuNZn7mYRCa16tX2yPz2KnY6P3QlK7Cq5SQ6zg/vy", - "fcLSqUylUBEMIqFiGQuEz/VLkO1J8D0gqx9YvTWxLNEVqQMsfg9IBH5TK31TLfFxpfLwnNl4u9I9R73k", - "2Efn2LNyjA8RoTErbU42MTClkSXVeFOGwZbJwPrJAFaTwVdK051CwRwWCOaRYTB+CYLnHwTmewiBVM/2", - "kL6WZSR7IOPPaPnjkvyxJOk6/YZerLRNVb/vf84k6dgoNNoDgSiiOcQ0D3dny0t38IwTY+3y76Q5WG1j", - "cF+eBy49xBQQ2mDfAu2fidX+WWJ0tp3+b91CWyLgCAHQOCkNECjgtRocs0UUgbVJkaYLFoN3/G5va9Mw", - "zFO7vrdlXj/Flg93JTCa1r8h/z3sXUFgRF4/AW7P9z/s4kaVBzwdhl2m5GHjcGmf7KtDJ/UneTe0I690", - "GjXM4wrpy3zx/MvodzFemLqEmkNKqHlwAR0fNf2ODymf40cVT/NcSqd5UOF8Qr89ZdkcvxTNVR4pj+r6", - "lbHL6tiqdZd/iNnMHRQ+8bHTN2H9ypp5MU1lVJrSX9x1sgl37uOHGXOigY7iSsxeDjb/kgebdH1wj2JW", - "1esgi95Km6diQfypvnxbbOFRm0bbPlFFJxz4PNWj6fJ1auNTmCO/Odx72FhljZMumeDk2/C/7+52DitO", - "gv0xBwOVE5lcjS5bSHHu1n0ezPjTE9nTvbSu5o7SiRLn/gMmsve2nvRKzIJ96HFp2vjkKUhN/z0vE7lk", - "lWiAif+pb/1pqb7S/nWyfG0OkYupTKX7cGKy9Jalht/HUWFSPuL9AV9Olv8PAAD//6lI+mE0MQAA", + "H4sIAAAAAAAC/+xaW3PbNhb+KxjsPmolpUkfqjc3aTLZzcUrp7sPjscDAYcSWhJAgEPXWo/++w4AkqIk", + "UBcrlePUTxqRBzi379wA3lGuC6MVKHR0dEcNs6wABBv+SXXOcPbeAb4V/r8Ax600KLWiI/r2FdEZwRmQ", + "QosyBwdIe1T6V4bhjPaoYgXQES0c4LUUtEctfCmlBUFHaEvoUcdnUDC/Nc6NJ3VopZrSxaJXMf+gBWxn", + "rrSANF//5r58xzuVtttUtvdU+d8l2Pk7WUjcZP3JW5rdyqIsiCqLCVgvCii0EhxBTSxgaVWfDEkBTDmi", + "NMn9Vv1axi9+96WQ4SVtiyQgY2WOdPTjsEcLqTwvOhr2almlQpiCbQv7HpAlzKR4XgogBSATDBmRKhoN", + "nNHKQZ/8otgkB0Emc1Jx7ZNfHZCM5Q6ItmToVdKFxAgxQEYyCbno0sZT0L3s+zHLvOM2hL74XZrALJPW", + "YWPZyt9BDV5ap22XCDpunLTo3gY9t9q4TeHOSC4demGM1QYsVk6XlaWlIsD4LIopJPfLFLPzLllNYLOX", + "vS6QYUKil1qh1bkL1gliOKnV0tMejCDasnjpGflMnd/wMyW/w7xHuFbIpJJqGtY5yIEjiLaaQjqUiiO5", + "YXkJjnBdKnRdmoXdt2q28GEZgRj0ejEc+h8vCagADGZMLjnzgg9+c17du9Z+f7eQ0RH922CZPAfxrRuc", + "Wz3JoYhcVg32MxNkDF9KcEgXPfpi+OwUXH9VrMSZtvJ/ICLb56dg+1rbiRQCVOT54hQ8P2gkr3WpKj1/", + "OgXPl1plueTBoz+eBkdvFYJVLCcXYG/Akl+s1TGFVIv93u+kwzo3L0MpyHTDZO6T77WpU41EKFwiUpo8", + "xaxlc/8/hF6LsklgPVpHaeAhhPTSsvx8hffmquqJnvwG0YhVQhP3kS6vK+cmH90k/YQMGlmeepWSzxt2", + "XGWPTeP69Ot/tYKPGR1dbkq/3GlN+m6rHWHNtQdXi14slDvQ16An5sq6hbmM+l0lGNV43bAIwi2mytms", + "LJj6hwUmPBYJ3JqcqRApxBngMpPc1zecSUc056W1oDhU1fizMpFf/7OivUTpasscJEjJfAPWyRiUqzK3", + "XsAtK0zu1w37w/6znczqpZv8fHwCL63E+YU3c2Q1YU7ysxJnTWrwa8LTJa8ZovECT4BZsDV1/Pda24Ih", + "HdF//vdTXfXCFuHt+h6xrGc6eEZiUEwbUO6GE65zX3u1JcxI2jIPfdYf9p+HKDKg/MsRfd4f9oe0F/re", + "oMiAmRiu01Rv5QFFmsxDAm3YzgaX+1abvgE8i8/bY8hlGqlLksFK4+TjaD/62GbvT1+1jfsviAG0N3ls", + "sxZXa83JD1+xqKzkrkRl+fivVhlLbdRINvBEbUgHR7XAfHnlNW8D9vLK64Zs6p1KG7DRK79NQM/gjhlz", + "LcWiE0avpDM5m3v8tBrEDiRtAmktBxlDLHBtBZGCBNgbwjvHySjbYSPdgdg91vfrGafDwVU7tt3BnujB", + "wFDibBAGe5+atUsgYQxT6RAsYeEEgPgloLCyTagKq5g41w497w/RwTY24j9rMT/IxGu9lDHJxsSLFGFz", + "t6NeNJSJgrExqByKh1Vhm7y/Lm1ZSrFb0kBVVY+UsOtYO+McDK4MHbsQ97zVuO+i/ekodAY87g9Qj64K", + "m95hbnBXHS4tAstcMsVhwJkSUjCE6+ZErLsivgEkzYLlEZojma5BnUDxG0AP4JcN05f1Fu+XLA8voK2j", + "toOT1lPBvX/BfVed6aSA0MrU62OuAKI0kqyedasw6BgTXRwTYTkmfqU0vVco2MMCwR4ZBuOnIHj8QWC/", + "hxDI9XQH6Bta4mkPRPw7v/1pQX4sSPY9Ckmdsm2aqrn8ecwg2bNRaLUHDJHxGQiC+gC0PHUHjzgxNi7/", + "TpqDpRqDu+pyeBFFzAEhMfWD15+wpf4ks7rohv+rsFFHBJwgAFrX5gkAJbzWCEdcyTk4l5V5PicCouO3", + "e1vblmEe2vW9jnn9DDd8uC2B+Wn9G/Lf/c4KEiPy6tnR5nz/wzZs1HkgwmG4z5Q8bN007qJ9duik/iBn", + "Q1vyyl6jhj2ukD7NF4+/jH4X44VtSqg9pITaexfQ8UnT7/iQ8jk+qnjax1I67b0K5wP67SHL5vipaC7z", + "SHVv26+NXVXHjVp38QebTsOt8QPfQX4T1q+tacpJLnllyvhw2zU33IYvYaYkkCY6ik9s+nTL/Ze85fbP", + "B3fIpnvdciOb1rfc8w4c7brjXn6vjIE4ca8dpdnnXrv1XdTTxfbx/o/d3dZhJVCQP2ZgoXYikcvRpQMU", + "H8K+jwMZf3oie7hD63ruqJwocRa/ZvP27upJP7Fpsg89LUxb378loRk/7ibMSFKTJpD4n+bVn5bqa+5f", + "J8s35mCGTWQuw4cTV4toWd/wxzgqbU5HtD+gi6vF/wMAAP//demk9kEzAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index fef2c91..0340a86 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -114,6 +114,12 @@ type GetAppsParams struct { Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` } +// GetAppParams defines parameters for GetApp. +type GetAppParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` +} + // PostAuthNodeJSONBody defines parameters for PostAuthNode. type PostAuthNodeJSONBody struct { App *string `json:"app,omitempty"` diff --git a/server/handlers/get_app.go b/server/handlers/get_app.go new file mode 100644 index 0000000..e3891ec --- /dev/null +++ b/server/handlers/get_app.go @@ -0,0 +1,44 @@ +package serverhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetApp handles GET /apps/{app_id} +func (a *Api) GetApp(c echo.Context, appId string, params server.GetAppParams) error { + props, err := buildProps(params.Props, propsMapping["app"]) + if err != nil { + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log := echolog.GetLogHandler(c, "GetApp") + odb := a.getODB() + ctx := c.Request().Context() + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", "app_id", appId, "props", props, "is_manager", isManager) + + app, err := odb.GetApp(ctx, appId, groups, isManager) + if err != nil { + log.Error("cannot get app", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get app") + } + if app == nil { + return JSONProblemf(c, http.StatusNotFound, "app %s not found", appId) + } + + filteredItem, err := filterItemFields(app, props) + if err != nil { + log.Error("cannot project app props", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot project app props") + } + + return c.JSON(http.StatusOK, filteredItem) +} From 119ce7318cb298648958bdad6fc9bcac4a817fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 1 Apr 2026 15:42:43 +0200 Subject: [PATCH 04/24] Check if App exists before registering --- cdb/db_apps.go | 8 ++++++++ server/handlers/post_auth_node.go | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/cdb/db_apps.go b/cdb/db_apps.go index d9bf0b9..e752a3f 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -232,6 +232,14 @@ func (oDb *DB) appFromAppName(ctx context.Context, app string) (bool, *App, erro } } +func (oDb *DB) AppExists(ctx context.Context, app string) (bool, error) { + ok, _, err := oDb.appFromAppName(ctx, app) + if err != nil { + return false, fmt.Errorf("appExists: %w", err) + } + return ok, nil +} + func (oDb *DB) isAppAllowedForNodeID(ctx context.Context, nodeID, app string) (bool, error) { // TODO: apps_responsibles PRIMARY KEY (app_id, group_id) const query = "" + diff --git a/server/handlers/post_auth_node.go b/server/handlers/post_auth_node.go index cbbe626..cb501f1 100644 --- a/server/handlers/post_auth_node.go +++ b/server/handlers/post_auth_node.go @@ -93,6 +93,13 @@ func (a *Api) PostAuthNode(c echo.Context) error { return JSONProblemf(c, http.StatusUnauthorized, "missing authentication") } + if ok, err := odb.AppExists(ctx, app); err != nil { + log.Error("failed to verify app existence", "app", app, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot verify app") + } else if !ok { + return JSONProblemf(c, http.StatusBadRequest, "unknown app %s", app) + } + var ( nodeID string nodename = body.Nodename From 6fdf56feffa7a103cfde63f491037681a6c46b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 1 Apr 2026 16:36:06 +0200 Subject: [PATCH 05/24] Add GET /apps/{app_id}/am_i_responsible endpoint --- cdb/db_apps.go | 28 +++++++ server/api.yaml | 32 ++++++++ server/codegen_server_gen.go | 87 +++++++++++++-------- server/handlers/get_app_am_i_responsible.go | 45 +++++++++++ 4 files changed, 161 insertions(+), 31 deletions(-) create mode 100644 server/handlers/get_app_am_i_responsible.go diff --git a/cdb/db_apps.go b/cdb/db_apps.go index e752a3f..db2e3fd 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -240,6 +240,34 @@ func (oDb *DB) AppExists(ctx context.Context, app string) (bool, error) { return ok, nil } +func (oDb *DB) AppResponsible(ctx context.Context, appIDOrName string, groups []string, isManager bool, nodeID string) (bool, error) { + if isManager { + return true, nil + } + + targetApp, err := oDb.GetApp(ctx, appIDOrName, nil, true) + if err != nil { + return false, fmt.Errorf("appResponsible: %w", err) + } + if targetApp == nil { + return false, nil + } + + if nodeID != "" { + nodeAppID, ok, err := oDb.AppIDFromNodeID(ctx, nodeID) + if err != nil { + return false, fmt.Errorf("appResponsible: %w", err) + } + return ok && nodeAppID == targetApp.ID, nil + } + + visibleApp, err := oDb.GetApp(ctx, appIDOrName, groups, false) + if err != nil { + return false, fmt.Errorf("appResponsible: %w", err) + } + return visibleApp != nil, nil +} + func (oDb *DB) isAppAllowedForNodeID(ctx context.Context, nodeID, app string) (bool, error) { // TODO: apps_responsibles PRIMARY KEY (app_id, group_id) const query = "" + diff --git a/server/api.yaml b/server/api.yaml index f12b4ac..da1bcb1 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -62,6 +62,38 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /apps/{app_id}/am_i_responsible: + get: + operationId: GetAppAmIResponsible + description: Return true if the requester is responsible for this application code + parameters: + - in: path + name: app_id + required: true + description: App record id or app code + schema: + type: string + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + type: boolean + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /auth/node: post: description: | diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 8c57f5a..f232a21 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -27,6 +27,9 @@ type ServerInterface interface { // (GET /apps/{app_id}) GetApp(ctx echo.Context, appId string, params GetAppParams) error + // (GET /apps/{app_id}/am_i_responsible) + GetAppAmIResponsible(ctx echo.Context, appId string) error + // (POST /auth/node) PostAuthNode(ctx echo.Context) error @@ -157,6 +160,26 @@ func (w *ServerInterfaceWrapper) GetApp(ctx echo.Context) error { return err } +// GetAppAmIResponsible converts echo context to params. +func (w *ServerInterfaceWrapper) GetAppAmIResponsible(ctx echo.Context) error { + var err error + // ------------- Path parameter "app_id" ------------- + var appId string + + err = runtime.BindStyledParameterWithOptions("simple", "app_id", ctx.Param("app_id"), &appId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter app_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetAppAmIResponsible(ctx, appId) + return err +} + // PostAuthNode converts echo context to params. func (w *ServerInterfaceWrapper) PostAuthNode(ctx echo.Context) error { var err error @@ -709,6 +732,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/apps", wrapper.GetApps) router.GET(baseURL+"/apps/:app_id", wrapper.GetApp) + router.GET(baseURL+"/apps/:app_id/am_i_responsible", wrapper.GetAppAmIResponsible) router.POST(baseURL+"/auth/node", wrapper.PostAuthNode) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_modulesets", wrapper.GetNodeComplianceCandidateModulesets) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_rulesets", wrapper.GetNodeComplianceCandidateRulesets) @@ -730,37 +754,38 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/handlers/get_app_am_i_responsible.go b/server/handlers/get_app_am_i_responsible.go new file mode 100644 index 0000000..1e9f03d --- /dev/null +++ b/server/handlers/get_app_am_i_responsible.go @@ -0,0 +1,45 @@ +package serverhandlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetAppAmIResponsible handles GET /apps/{app_id}/am_i_responsible +func (a *Api) GetAppAmIResponsible(c echo.Context, appId string) error { + log := echolog.GetLogHandler(c, "GetAppAmIResponsible") + odb := a.getODB() + ctx, cancel := context.WithTimeout(c.Request().Context(), a.SyncTimeout) + defer cancel() + + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + nodeID, _ := c.Get(XNodeID).(string) + + log.Info("called", "app_id", appId, "is_manager", isManager) + + app, err := odb.GetApp(ctx, appId, nil, true) + if err != nil { + log.Error("cannot resolve app", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve app %s", appId) + } + if app == nil { + return JSONProblemf(c, http.StatusNotFound, "app %s not found", appId) + } + + responsible, err := odb.AppResponsible(ctx, appId, groups, isManager, nodeID) + if err != nil { + log.Error("cannot check app responsibility", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot check responsibility for app %s", appId) + } + if !responsible { + return JSONProblemf(c, http.StatusForbidden, "you are not responsible for app %s", appId) + } + + return c.JSON(http.StatusOK, true) +} From cd29c807a03a93349066378a7efc1b04602ff98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 1 Apr 2026 17:32:49 +0200 Subject: [PATCH 06/24] Add GetAppResponsibles endpoint --- cdb/db_apps.go | 71 ++++++++++++++ server/api.yaml | 33 +++++++ server/codegen_server_gen.go | 124 ++++++++++++++++++------ server/codegen_type_gen.go | 18 ++++ server/handlers/get_app_responsibles.go | 50 ++++++++++ server/handlers/props_mapping.go | 3 + 6 files changed, 268 insertions(+), 31 deletions(-) create mode 100644 server/handlers/get_app_responsibles.go diff --git a/cdb/db_apps.go b/cdb/db_apps.go index db2e3fd..1077963 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -17,6 +17,13 @@ type ( AppTeamOps string `json:"app_team_ops"` Description string `json:"description"` } + + AuthGroup struct { + ID int64 `json:"id"` + Role string `json:"role"` + Privilege bool `json:"privilege"` + Description string `json:"description"` + } ) func scanApps(rows *sql.Rows) ([]App, error) { @@ -268,6 +275,70 @@ func (oDb *DB) AppResponsible(ctx context.Context, appIDOrName string, groups [] return visibleApp != nil, nil } +func (oDb *DB) GetAppResponsibles(ctx context.Context, appIDOrName string, groups []string, isManager bool, limit, offset int) ([]AuthGroup, error) { + targetApp, err := oDb.GetApp(ctx, appIDOrName, nil, true) + if err != nil { + return nil, fmt.Errorf("getAppResponsibles: %w", err) + } + if targetApp == nil { + return nil, nil + } + + if !isManager { + visibleApp, err := oDb.GetApp(ctx, appIDOrName, groups, false) + if err != nil { + return nil, fmt.Errorf("getAppResponsibles: %w", err) + } + if visibleApp == nil { + return []AuthGroup{}, nil + } + } + + query := ` + SELECT auth_group.id, auth_group.role, auth_group.privilege, auth_group.description + FROM auth_group + JOIN apps_responsibles ON auth_group.id = apps_responsibles.group_id + WHERE apps_responsibles.app_id = ? + ORDER BY auth_group.role, auth_group.id + ` + args := []any{targetApp.ID} + query, args = appendLimitOffset(query, args, limit, offset) + + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("getAppResponsibles: %w", err) + } + defer func() { _ = rows.Close() }() + + items := make([]AuthGroup, 0) + for rows.Next() { + var ( + item AuthGroup + role sql.NullString + privilege sql.NullString + description sql.NullString + ) + if err := rows.Scan(&item.ID, &role, &privilege, &description); err != nil { + return nil, fmt.Errorf("getAppResponsibles scan: %w", err) + } + if role.Valid { + item.Role = role.String + } + if privilege.Valid { + item.Privilege = privilege.String == "T" + } + if description.Valid { + item.Description = description.String + } + items = append(items, item) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("getAppResponsibles rows: %w", err) + } + + return items, nil +} + func (oDb *DB) isAppAllowedForNodeID(ctx context.Context, nodeID, app string) (bool, error) { // TODO: apps_responsibles PRIMARY KEY (app_id, group_id) const query = "" + diff --git a/server/api.yaml b/server/api.yaml index da1bcb1..0b1ffb2 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -94,6 +94,39 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /apps/{app_id}/responsibles: + get: + operationId: GetAppResponsibles + description: List an application code responsible groups + parameters: + - in: path + name: app_id + required: true + description: App record id or app code + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /auth/node: post: description: | diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index f232a21..1243176 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -30,6 +30,9 @@ type ServerInterface interface { // (GET /apps/{app_id}/am_i_responsible) GetAppAmIResponsible(ctx echo.Context, appId string) error + // (GET /apps/{app_id}/responsibles) + GetAppResponsibles(ctx echo.Context, appId string, params GetAppResponsiblesParams) error + // (POST /auth/node) PostAuthNode(ctx echo.Context) error @@ -180,6 +183,63 @@ func (w *ServerInterfaceWrapper) GetAppAmIResponsible(ctx echo.Context) error { return err } +// GetAppResponsibles converts echo context to params. +func (w *ServerInterfaceWrapper) GetAppResponsibles(ctx echo.Context) error { + var err error + // ------------- Path parameter "app_id" ------------- + var appId string + + err = runtime.BindStyledParameterWithOptions("simple", "app_id", ctx.Param("app_id"), &appId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter app_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetAppResponsiblesParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetAppResponsibles(ctx, appId, params) + return err +} + // PostAuthNode converts echo context to params. func (w *ServerInterfaceWrapper) PostAuthNode(ctx echo.Context) error { var err error @@ -733,6 +793,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/apps", wrapper.GetApps) router.GET(baseURL+"/apps/:app_id", wrapper.GetApp) router.GET(baseURL+"/apps/:app_id/am_i_responsible", wrapper.GetAppAmIResponsible) + router.GET(baseURL+"/apps/:app_id/responsibles", wrapper.GetAppResponsibles) router.POST(baseURL+"/auth/node", wrapper.PostAuthNode) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_modulesets", wrapper.GetNodeComplianceCandidateModulesets) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_rulesets", wrapper.GetNodeComplianceCandidateRulesets) @@ -754,38 +815,39 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "YSUd0f6ALi+X/w8AAP//dEzMENs4AAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 0340a86..299797d 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -120,6 +120,24 @@ type GetAppParams struct { Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` } +// GetAppResponsiblesParams defines parameters for GetAppResponsibles. +type GetAppResponsiblesParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` +} + // PostAuthNodeJSONBody defines parameters for PostAuthNode. type PostAuthNodeJSONBody struct { App *string `json:"app,omitempty"` diff --git a/server/handlers/get_app_responsibles.go b/server/handlers/get_app_responsibles.go new file mode 100644 index 0000000..e21b12d --- /dev/null +++ b/server/handlers/get_app_responsibles.go @@ -0,0 +1,50 @@ +package serverhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetAppResponsibles handles GET /apps/{app_id}/responsibles +func (a *Api) GetAppResponsibles(c echo.Context, appId string, params server.GetAppResponsiblesParams) error { + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["auth_group"]) + if err != nil { + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log := echolog.GetLogHandler(c, "GetAppResponsibles") + odb := a.getODB() + ctx := c.Request().Context() + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", "app_id", appId, "limit", query.Page.Limit, "offset", query.Page.Offset, "props", query.Props, "is_manager", isManager) + + app, err := odb.GetApp(ctx, appId, nil, true) + if err != nil { + log.Error("cannot resolve app", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve app %s", appId) + } + if app == nil { + return JSONProblemf(c, http.StatusNotFound, "app %s not found", appId) + } + + items, err := odb.GetAppResponsibles(ctx, appId, groups, isManager, query.Page.Limit, query.Page.Offset) + if err != nil { + log.Error("cannot get app responsibles", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get responsibles for app %s", appId) + } + + filteredItems, err := filterItemsFields(items, query.Props) + if err != nil { + log.Error("cannot project group props", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot project group props") + } + + return c.JSON(http.StatusOK, newListResponse(filteredItems, propsMapping["auth_group"], query)) +} diff --git a/server/handlers/props_mapping.go b/server/handlers/props_mapping.go index 94e2128..f18ef52 100644 --- a/server/handlers/props_mapping.go +++ b/server/handlers/props_mapping.go @@ -9,6 +9,9 @@ var propsMapping = map[string]propMapping{ "app": { Available: []string{"id", "app", "updated", "app_domain", "app_team_ops", "description"}, }, + "auth_group": { + Available: []string{"id", "role", "privilege", "description"}, + }, "tag": { Available: []string{"id", "tag_name", "tag_created", "tag_exclude", "tag_data", "tag_id"}, Blacklist: map[string]struct{}{"id": {}}, From 994d3ca13e11e0ae002b3e096f3e1bc392d30fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Thu, 2 Apr 2026 10:50:10 +0200 Subject: [PATCH 07/24] Add GetAppPublications endpoint --- cdb/db_apps.go | 64 ++++++++++++ server/api.yaml | 33 ++++++ server/codegen_server_gen.go | 127 ++++++++++++++++++------ server/codegen_type_gen.go | 18 ++++ server/handlers/get_app_publications.go | 50 ++++++++++ 5 files changed, 259 insertions(+), 33 deletions(-) create mode 100644 server/handlers/get_app_publications.go diff --git a/cdb/db_apps.go b/cdb/db_apps.go index 1077963..b00c56d 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -339,6 +339,70 @@ func (oDb *DB) GetAppResponsibles(ctx context.Context, appIDOrName string, group return items, nil } +func (oDb *DB) GetAppPublications(ctx context.Context, appIDOrName string, groups []string, isManager bool, limit, offset int) ([]AuthGroup, error) { + targetApp, err := oDb.GetApp(ctx, appIDOrName, nil, true) + if err != nil { + return nil, fmt.Errorf("getAppPublications: %w", err) + } + if targetApp == nil { + return nil, nil + } + + if !isManager { + visibleApp, err := oDb.GetApp(ctx, appIDOrName, groups, false) + if err != nil { + return nil, fmt.Errorf("getAppPublications: %w", err) + } + if visibleApp == nil { + return []AuthGroup{}, nil + } + } + + query := ` + SELECT auth_group.id, auth_group.role, auth_group.privilege, auth_group.description + FROM auth_group + JOIN apps_publications ON auth_group.id = apps_publications.group_id + WHERE apps_publications.app_id = ? + ORDER BY auth_group.role, auth_group.id + ` + args := []any{targetApp.ID} + query, args = appendLimitOffset(query, args, limit, offset) + + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("getAppPublications: %w", err) + } + defer func() { _ = rows.Close() }() + + items := make([]AuthGroup, 0) + for rows.Next() { + var ( + item AuthGroup + role sql.NullString + privilege sql.NullString + description sql.NullString + ) + if err := rows.Scan(&item.ID, &role, &privilege, &description); err != nil { + return nil, fmt.Errorf("getAppPublications scan: %w", err) + } + if role.Valid { + item.Role = role.String + } + if privilege.Valid { + item.Privilege = privilege.String == "T" + } + if description.Valid { + item.Description = description.String + } + items = append(items, item) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("getAppPublications rows: %w", err) + } + + return items, nil +} + func (oDb *DB) isAppAllowedForNodeID(ctx context.Context, nodeID, app string) (bool, error) { // TODO: apps_responsibles PRIMARY KEY (app_id, group_id) const query = "" + diff --git a/server/api.yaml b/server/api.yaml index 0b1ffb2..010043d 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -127,6 +127,39 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /apps/{app_id}/publications: + get: + operationId: GetAppPublications + description: List an application code publication groups + parameters: + - in: path + name: app_id + required: true + description: App record id or app code + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /auth/node: post: description: | diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 1243176..c4a2c9a 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -30,6 +30,9 @@ type ServerInterface interface { // (GET /apps/{app_id}/am_i_responsible) GetAppAmIResponsible(ctx echo.Context, appId string) error + // (GET /apps/{app_id}/publications) + GetAppPublications(ctx echo.Context, appId string, params GetAppPublicationsParams) error + // (GET /apps/{app_id}/responsibles) GetAppResponsibles(ctx echo.Context, appId string, params GetAppResponsiblesParams) error @@ -183,6 +186,63 @@ func (w *ServerInterfaceWrapper) GetAppAmIResponsible(ctx echo.Context) error { return err } +// GetAppPublications converts echo context to params. +func (w *ServerInterfaceWrapper) GetAppPublications(ctx echo.Context) error { + var err error + // ------------- Path parameter "app_id" ------------- + var appId string + + err = runtime.BindStyledParameterWithOptions("simple", "app_id", ctx.Param("app_id"), &appId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter app_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetAppPublicationsParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetAppPublications(ctx, appId, params) + return err +} + // GetAppResponsibles converts echo context to params. func (w *ServerInterfaceWrapper) GetAppResponsibles(ctx echo.Context) error { var err error @@ -793,6 +853,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/apps", wrapper.GetApps) router.GET(baseURL+"/apps/:app_id", wrapper.GetApp) router.GET(baseURL+"/apps/:app_id/am_i_responsible", wrapper.GetAppAmIResponsible) + router.GET(baseURL+"/apps/:app_id/publications", wrapper.GetAppPublications) router.GET(baseURL+"/apps/:app_id/responsibles", wrapper.GetAppResponsibles) router.POST(baseURL+"/auth/node", wrapper.PostAuthNode) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_modulesets", wrapper.GetNodeComplianceCandidateModulesets) @@ -815,39 +876,39 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "YSUd0f6ALi+X/w8AAP//dEzMENs4AAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 299797d..a6c7748 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -120,6 +120,24 @@ type GetAppParams struct { Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` } +// GetAppPublicationsParams defines parameters for GetAppPublications. +type GetAppPublicationsParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` +} + // GetAppResponsiblesParams defines parameters for GetAppResponsibles. type GetAppResponsiblesParams struct { // Props A list of properties to include in each data dictionnary. diff --git a/server/handlers/get_app_publications.go b/server/handlers/get_app_publications.go new file mode 100644 index 0000000..0bce1ba --- /dev/null +++ b/server/handlers/get_app_publications.go @@ -0,0 +1,50 @@ +package serverhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetAppPublications handles GET /apps/{app_id}/publications +func (a *Api) GetAppPublications(c echo.Context, appId string, params server.GetAppPublicationsParams) error { + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["auth_group"]) + if err != nil { + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log := echolog.GetLogHandler(c, "GetAppPublications") + odb := a.getODB() + ctx := c.Request().Context() + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", "app_id", appId, "limit", query.Page.Limit, "offset", query.Page.Offset, "props", query.Props, "is_manager", isManager) + + app, err := odb.GetApp(ctx, appId, nil, true) + if err != nil { + log.Error("cannot resolve app", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve app %s", appId) + } + if app == nil { + return JSONProblemf(c, http.StatusNotFound, "app %s not found", appId) + } + + items, err := odb.GetAppPublications(ctx, appId, groups, isManager, query.Page.Limit, query.Page.Offset) + if err != nil { + log.Error("cannot get app publications", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get publications for app %s", appId) + } + + filteredItems, err := filterItemsFields(items, query.Props) + if err != nil { + log.Error("cannot project group props", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot project group props") + } + + return c.JSON(http.StatusOK, newListResponse(filteredItems, propsMapping["auth_group"], query)) +} From 8ef41b98ac082731e45cf0925681da5067843bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Thu, 2 Apr 2026 14:22:22 +0200 Subject: [PATCH 08/24] Fix variables assignments --- cdb/db_object.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cdb/db_object.go b/cdb/db_object.go index 519fcca..0e9e177 100644 --- a/cdb/db_object.go +++ b/cdb/db_object.go @@ -142,9 +142,9 @@ func (oDb *DB) ObjectFromID(ctx context.Context, svcID string) (*DBObject, error case err != nil: return nil, err default: - o.Frozen = placement.String + o.Frozen = frozen.String o.Placement = placement.String - o.Provisioned = placement.String + o.Provisioned = provisioned.String return &o, nil } } From 7a2159254c7cb5e9a95d2ca7b838c939872561fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Mon, 13 Apr 2026 15:18:33 +0200 Subject: [PATCH 09/24] Add PostApps endpoint --- cdb/db_apps.go | 65 ++++++++++++++++++ server/api.yaml | 43 ++++++++++++ server/codegen_server_gen.go | 84 ++++++++++++++--------- server/codegen_type_gen.go | 11 +++ server/handlers/post_apps.go | 128 +++++++++++++++++++++++++++++++++++ 5 files changed, 298 insertions(+), 33 deletions(-) create mode 100644 server/handlers/post_apps.go diff --git a/cdb/db_apps.go b/cdb/db_apps.go index b00c56d..e95d54d 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -507,6 +507,71 @@ func (oDb *DB) AppIDFromObjectOrNodeIDs(ctx context.Context, nodeID, objectID st return oDb.AppIDFromNodeID(ctx, nodeID) } +// AppQuotaExceeded returns true if the user has reached their app creation quota. +// A quota of 0 or NULL means unlimited. +func (oDb *DB) AppQuotaExceeded(ctx context.Context, userID int64) (bool, error) { + var quota sql.NullInt64 + err := oDb.DB.QueryRowContext(ctx, + "SELECT quota_app FROM auth_user WHERE id = ?", userID, + ).Scan("a) + switch { + case errors.Is(err, sql.ErrNoRows): + return false, nil + case err != nil: + return false, fmt.Errorf("appQuotaExceeded: %w", err) + case !quota.Valid || quota.Int64 == 0: + return false, nil + } + + var count int64 + err = oDb.DB.QueryRowContext(ctx, + `SELECT COUNT(DISTINCT apps_responsibles.app_id) + FROM apps_responsibles + JOIN auth_membership ON apps_responsibles.group_id = auth_membership.group_id + WHERE auth_membership.user_id = ?`, userID, + ).Scan(&count) + if err != nil { + return false, fmt.Errorf("appQuotaExceeded count: %w", err) + } + return count >= quota.Int64, nil +} + +func (oDb *DB) InsertApp(ctx context.Context, app, description, appDomain, appTeamOps string) (*App, error) { + const query = `INSERT INTO apps (app, description, app_domain, app_team_ops) VALUES (?, ?, ?, ?)` + result, err := oDb.DB.ExecContext(ctx, query, app, + sql.NullString{String: description, Valid: description != ""}, + sql.NullString{String: appDomain, Valid: appDomain != ""}, + sql.NullString{String: appTeamOps, Valid: appTeamOps != ""}, + ) + if err != nil { + return nil, fmt.Errorf("insertApp: %w", err) + } + id, err := result.LastInsertId() + if err != nil { + return nil, fmt.Errorf("insertApp lastInsertId: %w", err) + } + oDb.SetChange("apps") + return &App{ID: id, App: app, Description: description, AppDomain: appDomain, AppTeamOps: appTeamOps}, nil +} + +func (oDb *DB) InsertAppResponsible(ctx context.Context, appID, groupID int64) error { + const query = `INSERT INTO apps_responsibles (app_id, group_id) VALUES (?, ?)` + if _, err := oDb.DB.ExecContext(ctx, query, appID, groupID); err != nil { + return fmt.Errorf("insertAppResponsible: %w", err) + } + oDb.SetChange("apps_responsibles") + return nil +} + +func (oDb *DB) InsertAppPublication(ctx context.Context, appID, groupID int64) error { + const query = `INSERT INTO apps_publications (app_id, group_id) VALUES (?, ?)` + if _, err := oDb.DB.ExecContext(ctx, query, appID, groupID); err != nil { + return fmt.Errorf("insertAppPublication: %w", err) + } + oDb.SetChange("apps_publications") + return nil +} + func (oDb *DB) AppsWithoutResponsible(ctx context.Context) (apps []string, err error) { const query = `SELECT apps.app FROM apps diff --git a/server/api.yaml b/server/api.yaml index 010043d..5fd4732 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -33,6 +33,49 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + post: + operationId: PostApps + description: Create an application code + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - app + properties: + app: + type: string + description: + type: string + app_domain: + type: string + app_team_ops: + type: string + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + 400: + $ref: '#/components/responses/400' + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 409: + $ref: '#/components/responses/409' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /apps/{app_id}: get: operationId: GetApp diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index c4a2c9a..7bd4a01 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -24,6 +24,9 @@ type ServerInterface interface { // (GET /apps) GetApps(ctx echo.Context, params GetAppsParams) error + // (POST /apps) + PostApps(ctx echo.Context) error + // (GET /apps/{app_id}) GetApp(ctx echo.Context, appId string, params GetAppParams) error @@ -137,6 +140,19 @@ func (w *ServerInterfaceWrapper) GetApps(ctx echo.Context) error { return err } +// PostApps converts echo context to params. +func (w *ServerInterfaceWrapper) PostApps(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PostApps(ctx) + return err +} + // GetApp converts echo context to params. func (w *ServerInterfaceWrapper) GetApp(ctx echo.Context) error { var err error @@ -851,6 +867,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL } router.GET(baseURL+"/apps", wrapper.GetApps) + router.POST(baseURL+"/apps", wrapper.PostApps) router.GET(baseURL+"/apps/:app_id", wrapper.GetApp) router.GET(baseURL+"/apps/:app_id/am_i_responsible", wrapper.GetAppAmIResponsible) router.GET(baseURL+"/apps/:app_id/publications", wrapper.GetAppPublications) @@ -876,39 +893,40 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index a6c7748..e966461 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -114,6 +114,14 @@ type GetAppsParams struct { Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` } +// PostAppsJSONBody defines parameters for PostApps. +type PostAppsJSONBody struct { + App string `json:"app"` + AppDomain *string `json:"app_domain,omitempty"` + AppTeamOps *string `json:"app_team_ops,omitempty"` + Description *string `json:"description,omitempty"` +} + // GetAppParams defines parameters for GetApp. type GetAppParams struct { // Props A list of properties to include in each data dictionnary. @@ -282,6 +290,9 @@ type GetTagNodesParams struct { Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` } +// PostAppsJSONRequestBody defines body for PostApps for application/json ContentType. +type PostAppsJSONRequestBody PostAppsJSONBody + // PostAuthNodeJSONRequestBody defines body for PostAuthNode for application/json ContentType. type PostAuthNodeJSONRequestBody PostAuthNodeJSONBody diff --git a/server/handlers/post_apps.go b/server/handlers/post_apps.go new file mode 100644 index 0000000..a5cc375 --- /dev/null +++ b/server/handlers/post_apps.go @@ -0,0 +1,128 @@ +package serverhandlers + +import ( + "context" + "net/http" + "strconv" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" + "github.com/opensvc/oc3/xauth" +) + +// PostApps handles POST /apps +func (a *Api) PostApps(c echo.Context) error { + log := echolog.GetLogHandler(c, "PostApps") + odb := a.getODB() + ctx, cancel := context.WithTimeout(c.Request().Context(), a.SyncTimeout) + defer cancel() + + if !IsAuthByUser(c) { + return JSONProblemf(c, http.StatusUnauthorized, "user authentication required") + } + + if !IsManager(c) { + return JSONProblemf(c, http.StatusForbidden, "AppManager privilege required") + } + + var body server.PostAppsJSONRequestBody + if err := c.Bind(&body); err != nil { + log.Error("invalid request body", logkey.Error, err) + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + if body.App == "" { + return JSONProblemf(c, http.StatusBadRequest, "missing required field: app") + } + + log.Info("called", "app", body.App) + + exists, err := odb.AppExists(ctx, body.App) + if err != nil { + log.Error("cannot check app existence", "app", body.App, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot check app existence") + } + if exists { + return JSONProblemf(c, http.StatusConflict, "app %s already exists", body.App) + } + + user := UserInfoFromContext(c) + if user == nil { + return JSONProblemf(c, http.StatusUnauthorized, "missing user context") + } + userIDStr := user.GetExtensions().Get(xauth.XUserID) + userID, err := strconv.ParseInt(userIDStr, 10, 64) + if err != nil { + return JSONProblemf(c, http.StatusBadRequest, "invalid user id") + } + + exceeded, err := odb.AppQuotaExceeded(ctx, userID) + if err != nil { + log.Error("cannot check app quota", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot check app quota") + } + if exceeded { + return JSONProblemf(c, http.StatusForbidden, "app quota exceeded") + } + + groupID, ok, err := odb.UserDefaultGroupID(ctx, userID) + if err != nil { + log.Error("cannot find default group", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot find default group") + } + if !ok { + return JSONProblemf(c, http.StatusInternalServerError, "user has no default group") + } + + var description, appDomain, appTeamOps string + if body.Description != nil { + description = *body.Description + } + if body.AppDomain != nil { + appDomain = *body.AppDomain + } + if body.AppTeamOps != nil { + appTeamOps = *body.AppTeamOps + } + + app, err := odb.InsertApp(ctx, body.App, description, appDomain, appTeamOps) + if err != nil { + log.Error("cannot insert app", "app", body.App, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot create app") + } + + if err := odb.InsertAppResponsible(ctx, app.ID, groupID); err != nil { + log.Error("cannot insert app responsible", "app", body.App, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot set app responsible") + } + + if err := odb.InsertAppPublication(ctx, app.ID, groupID); err != nil { + log.Error("cannot insert app publication", "app", body.App, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot set app publication") + } + + if err := odb.Session.NotifyChanges(ctx); err != nil { + log.Error("cannot notify changes", logkey.Error, err) + } + + userEmail, _ := c.Get(XUserEmail).(string) + logErr := odb.Log(ctx, cdb.LogEntry{ + Action: "apps.create", + User: userEmail, + Fmt: "app %(app)s created. data %(data)s", + Dict: map[string]any{ + "app": app.App, + "data": body, + }, + Level: "info", + }) + if logErr != nil { + log.Error("cannot write audit log", logkey.Error, logErr) + } + + return c.JSON(http.StatusOK, app) +} From fe98d3c9f39c326457bbc2431924e4dfac8055b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Mon, 13 Apr 2026 16:34:27 +0200 Subject: [PATCH 10/24] Add DELETE /apps/{app_id} endpoint --- cdb/db_apps.go | 43 ++++++++++++++ server/api.yaml | 32 +++++++++++ server/codegen_server_gen.go | 91 ++++++++++++++++++----------- server/handlers/delete_apps.go | 101 +++++++++++++++++++++++++++++++++ 4 files changed, 234 insertions(+), 33 deletions(-) create mode 100644 server/handlers/delete_apps.go diff --git a/cdb/db_apps.go b/cdb/db_apps.go index e95d54d..8662a0d 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -572,6 +572,49 @@ func (oDb *DB) InsertAppPublication(ctx context.Context, appID, groupID int64) e return nil } +func (oDb *DB) AppUsageCounts(ctx context.Context, app string) (nodesCount, servicesCount int64, err error) { + const nodeQuery = `SELECT COUNT(*) FROM nodes WHERE app = ?` + if err = oDb.DB.QueryRowContext(ctx, nodeQuery, app).Scan(&nodesCount); err != nil { + err = fmt.Errorf("appUsageCounts nodes: %w", err) + return + } + + const serviceQuery = `SELECT COUNT(*) FROM services WHERE svc_app = ?` + if err = oDb.DB.QueryRowContext(ctx, serviceQuery, app).Scan(&servicesCount); err != nil { + err = fmt.Errorf("appUsageCounts services: %w", err) + return + } + + return +} + +func (oDb *DB) DeleteApp(ctx context.Context, appID int64) error { + const query = `DELETE FROM apps WHERE id = ?` + if _, err := oDb.DB.ExecContext(ctx, query, appID); err != nil { + return fmt.Errorf("deleteApp: %w", err) + } + oDb.SetChange("apps") + return nil +} + +func (oDb *DB) DeleteAppResponsibles(ctx context.Context, appID int64) error { + const query = `DELETE FROM apps_responsibles WHERE app_id = ?` + if _, err := oDb.DB.ExecContext(ctx, query, appID); err != nil { + return fmt.Errorf("deleteAppResponsibles: %w", err) + } + oDb.SetChange("apps_responsibles") + return nil +} + +func (oDb *DB) DeleteAppPublications(ctx context.Context, appID int64) error { + const query = `DELETE FROM apps_publications WHERE app_id = ?` + if _, err := oDb.DB.ExecContext(ctx, query, appID); err != nil { + return fmt.Errorf("deleteAppPublications: %w", err) + } + oDb.SetChange("apps_publications") + return nil +} + func (oDb *DB) AppsWithoutResponsible(ctx context.Context) (apps []string, err error) { const query = `SELECT apps.app FROM apps diff --git a/server/api.yaml b/server/api.yaml index 5fd4732..75705db 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -104,6 +104,38 @@ paths: security: - basicAuth: [ ] - bearerAuth: [ ] + delete: + operationId: DeleteApps + description: Delete an application code + parameters: + - in: path + name: app_id + required: true + description: App record id or app code + schema: + type: string + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 404: + $ref: '#/components/responses/404' + 409: + $ref: '#/components/responses/409' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] /apps/{app_id}/am_i_responsible: get: diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 7bd4a01..450d78a 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -27,6 +27,9 @@ type ServerInterface interface { // (POST /apps) PostApps(ctx echo.Context) error + // (DELETE /apps/{app_id}) + DeleteApps(ctx echo.Context, appId string) error + // (GET /apps/{app_id}) GetApp(ctx echo.Context, appId string, params GetAppParams) error @@ -153,6 +156,26 @@ func (w *ServerInterfaceWrapper) PostApps(ctx echo.Context) error { return err } +// DeleteApps converts echo context to params. +func (w *ServerInterfaceWrapper) DeleteApps(ctx echo.Context) error { + var err error + // ------------- Path parameter "app_id" ------------- + var appId string + + err = runtime.BindStyledParameterWithOptions("simple", "app_id", ctx.Param("app_id"), &appId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter app_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.DeleteApps(ctx, appId) + return err +} + // GetApp converts echo context to params. func (w *ServerInterfaceWrapper) GetApp(ctx echo.Context) error { var err error @@ -868,6 +891,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/apps", wrapper.GetApps) router.POST(baseURL+"/apps", wrapper.PostApps) + router.DELETE(baseURL+"/apps/:app_id", wrapper.DeleteApps) router.GET(baseURL+"/apps/:app_id", wrapper.GetApp) router.GET(baseURL+"/apps/:app_id/am_i_responsible", wrapper.GetAppAmIResponsible) router.GET(baseURL+"/apps/:app_id/publications", wrapper.GetAppPublications) @@ -894,39 +918,40 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", + "E0EAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/handlers/delete_apps.go b/server/handlers/delete_apps.go new file mode 100644 index 0000000..835839d --- /dev/null +++ b/server/handlers/delete_apps.go @@ -0,0 +1,101 @@ +package serverhandlers + +import ( + "context" + "database/sql" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// DeleteApps handles DELETE /apps/{app_id} +func (a *Api) DeleteApps(c echo.Context, appId string) error { + log := echolog.GetLogHandler(c, "DeleteApps") + ctx, cancel := context.WithTimeout(c.Request().Context(), a.SyncTimeout) + defer cancel() + + if !IsAuthByUser(c) { + return JSONProblemf(c, http.StatusUnauthorized, "user authentication required") + } + + if !IsManager(c) { + return JSONProblemf(c, http.StatusForbidden, "AppManager privilege required") + } + + odb := cdb.New(a.DB) + odb.CreateSession(a.Ev) + + log.Info("called", "app_id", appId) + + app, err := odb.GetApp(ctx, appId, nil, true) + if err != nil { + log.Error("cannot get app", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get app") + } + if app == nil { + return JSONProblemf(c, http.StatusNotFound, "app %s not found", appId) + } + + nodesCount, servicesCount, err := odb.AppUsageCounts(ctx, app.App) + if err != nil { + log.Error("cannot count app usage", "app_id", appId, "app", app.App, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot count app usage") + } + if nodesCount+servicesCount > 0 { + return JSONProblemf(c, http.StatusConflict, "this app code cannot be deleted. used by %d nodes and %d services", nodesCount, servicesCount) + } + + markSuccess, endTx, err := odb.BeginTxWithControl(ctx, log, &sql.TxOptions{}) + if err != nil { + log.Error("cannot start transaction", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot delete app") + } + defer endTx() + + if err := odb.DeleteApp(ctx, app.ID); err != nil { + log.Error("cannot delete app", "app_id", appId, "app", app.App, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot delete app") + } + if err := odb.DeleteAppResponsibles(ctx, app.ID); err != nil { + log.Error("cannot delete app responsibles", "app_id", appId, "app", app.App, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot delete app responsibles") + } + if err := odb.DeleteAppPublications(ctx, app.ID); err != nil { + log.Error("cannot delete app publications", "app_id", appId, "app", app.App, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot delete app publications") + } + + userEmail, _ := c.Get(XUserEmail).(string) + if err := odb.Log(ctx, cdb.LogEntry{ + Action: "apps.delete", + User: userEmail, + Fmt: "app %(app)s deleted", + Dict: map[string]any{ + "app": app.App, + }, + Level: "info", + }); err != nil { + log.Error("cannot write audit log", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot write audit log") + } + + markSuccess() + + if err := odb.Session.NotifyTableChangeWithData(ctx, "apps", map[string]any{"id": app.ID}); err != nil { + log.Error("cannot notify apps change", logkey.Error, err) + } + if err := odb.Session.NotifyTableChangeWithData(ctx, "apps_responsibles", nil); err != nil { + log.Error("cannot notify apps_responsibles change", logkey.Error, err) + } + if err := odb.Session.NotifyTableChangeWithData(ctx, "apps_publications", nil); err != nil { + log.Error("cannot notify apps_publications change", logkey.Error, err) + } + + return c.JSON(http.StatusOK, map[string]string{ + "info": "app " + app.App + " deleted", + }) +} From cd4781ef95f8c363d91b0f2fed889e20efdcb1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Tue, 14 Apr 2026 15:42:00 +0200 Subject: [PATCH 11/24] Add query builder and schema generator --- cdb/list_params.go | 34 + cdb/util.go | 100 ++ qb/qb.go | 312 +++++ schema/gen/main.go | 216 +++ schema/relations.go | 19 + schema/schema.go | 21 + schema/tables.go | 3174 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 3876 insertions(+) create mode 100644 cdb/list_params.go create mode 100644 qb/qb.go create mode 100644 schema/gen/main.go create mode 100644 schema/relations.go create mode 100644 schema/schema.go create mode 100644 schema/tables.go diff --git a/cdb/list_params.go b/cdb/list_params.go new file mode 100644 index 0000000..2ba6418 --- /dev/null +++ b/cdb/list_params.go @@ -0,0 +1,34 @@ +package cdb + +import "strings" + +// ListParams bundles the standard query parameters shared by all list endpoints. +// Groups and IsManager encode the caller's access control context. +type ListParams struct { + Groups []string + IsManager bool + Limit int + Offset int + Props []string + SelectExprs []string + TypeHints map[string]string // Used by scanRowsToMaps to convert []byte driver values to the correct type + OrderBy []string + GroupBy []string +} + +func (p ListParams) OrderByClause(defaultClause string) string { + if len(p.OrderBy) == 0 { + return "ORDER BY " + defaultClause + } + return "ORDER BY " + strings.Join(p.OrderBy, ", ") +} + +func (p ListParams) GroupByClause(defaultClause string) string { + if len(p.GroupBy) > 0 { + return "GROUP BY " + strings.Join(p.GroupBy, ", ") + } + if defaultClause != "" { + return "GROUP BY " + defaultClause + } + return "" +} diff --git a/cdb/util.go b/cdb/util.go index 918b994..29845c7 100644 --- a/cdb/util.go +++ b/cdb/util.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "log/slog" + "strconv" + "strings" "time" ) @@ -31,6 +33,104 @@ func checkRow[T any](err error, valid bool, value T) (T, bool, error) { } } +// converts a raw value to the Go type indicated by kind. +func convertTyped(v any, kind string) any { + b, ok := v.([]byte) + if !ok { + return v + } + s := string(b) + switch kind { + case "int64": + n, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return s + } + return n + case "float64": + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return s + } + return f + default: + return s + } +} + +// Scans SQL rows into a slice of maps keyed by prop name. +func scanRowsToMaps(rows interface { + Next() bool + Scan(...any) error + Err() error +}, props []string, typeHints ...map[string]string) ([]map[string]any, error) { + var hints map[string]string + if len(typeHints) > 0 { + hints = typeHints[0] + } + results := make([]map[string]any, 0) + for rows.Next() { + ptrs := make([]any, len(props)) + vals := make([]any, len(props)) + for i := range ptrs { + ptrs[i] = &vals[i] + } + if err := rows.Scan(ptrs...); err != nil { + return nil, fmt.Errorf("scanRowsToMaps: %w", err) + } + row := make(map[string]any, len(props)) + for i, prop := range props { + row[prop] = convertTyped(vals[i], hints[prop]) + } + results = append(results, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("scanRowsToMaps rows: %w", err) + } + return results, nil +} + +// Scans SQL rows into a slice of nested maps. +func scanRowsToNestedMaps(rows interface { + Next() bool + Scan(...any) error + Err() error +}, props []string, primaryTable string) ([]map[string]any, error) { + results := make([]map[string]any, 0) + for rows.Next() { + ptrs := make([]any, len(props)) + vals := make([]any, len(props)) + for i := range ptrs { + ptrs[i] = &vals[i] + } + if err := rows.Scan(ptrs...); err != nil { + return nil, fmt.Errorf("scanRowsToNestedMaps: %w", err) + } + row := make(map[string]any) + for i, prop := range props { + var val any + if b, ok := vals[i].([]byte); ok { + val = string(b) + } else { + val = vals[i] + } + table, col, found := strings.Cut(prop, ".") + if !found { + table, col = primaryTable, prop + } + if row[table] == nil { + row[table] = make(map[string]any) + } + row[table].(map[string]any)[col] = val + } + results = append(results, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("scanRowsToNestedMaps rows: %w", err) + } + return results, nil +} + func appendLimitOffset(query string, args []any, limit, offset int) (string, []any) { if limit > 0 { query += " LIMIT ? OFFSET ?" diff --git a/qb/qb.go b/qb/qb.go new file mode 100644 index 0000000..a4c81d0 --- /dev/null +++ b/qb/qb.go @@ -0,0 +1,312 @@ +// Package qb provides a lightweight SQL query builder with automatic JOIN +// resolution based on the FK relationships declared in schema/relations.go. +// +// # Basic usage +// +// sql, args, err := qb.From(schema.TApps). +// Select(schema.AppsID, schema.AppsApp). +// Where(schema.AppsID, ">", 0). +// Build() +// +// # Automatic JOIN resolution +// +// When a selected column belongs to a table other than the FROM table, the +// builder resolves the required JOINs automatically by walking the Ref chain +// declared in schema/relations.go: +// +// sql, args, err := qb.From(schema.TAuthGroup). +// Select(schema.AuthGroupID, schema.AuthGroupRole). +// Via(schema.TAppsResponsibles). // disambiguates: apps_responsibles vs apps_publications +// Where(schema.AppsResponsiblesAppID, "=", appID). +// Build() +// // → SELECT auth_group.id, auth_group.role +// // FROM auth_group +// // JOIN apps_responsibles ON apps_responsibles.group_id = auth_group.id +// // WHERE apps_responsibles.app_id = ? +// +// # Ambiguity and Via() +// +// When multiple FK paths connect the FROM table to a needed table, Build() +// returns an error. Use Via() to specify the intermediate table(s) that +// disambiguate the path: +// +// // Error: both apps_responsibles and apps_publications connect apps to auth_group +// qb.From(schema.TApps).Select(schema.AuthGroupRole).Build() +// +// // OK: path is explicit +// qb.From(schema.TApps).Select(schema.AuthGroupRole).Via(schema.TAppsResponsibles).Build() +package qb + +import ( + "fmt" + "strings" + + "github.com/opensvc/oc3/schema" +) + +// joinStep describes one JOIN clause to add to the query. +type joinStep struct { + newTable *schema.Table // table being joined + left *schema.Col // left side of the ON condition + right *schema.Col // right side of the ON condition + leftJoin bool // true → LEFT OUTER JOIN, false → INNER JOIN +} + +func (j joinStep) sql() string { + keyword := "JOIN" + if j.leftJoin { + keyword = "LEFT JOIN" + } + return fmt.Sprintf("%s %s ON %s = %s", keyword, j.newTable.Name, j.left.Qualified(), j.right.Qualified()) +} + +// condition holds a WHERE clause fragment and its bound arguments. +type condition struct { + col *schema.Col // used for JOIN resolution; nil for raw conditions + expr string + args []any +} + +// Query builds a SQL SELECT statement. +type Query struct { + from *schema.Table + selects []*schema.Col + rawSelects []string // raw SQL expressions (e.g. "COALESCE(apps.updated, '')") + via []*schema.Table // explicit intermediate tables for join disambiguation + leftJoins []*schema.Table // tables that must be joined with LEFT JOIN + wheres []condition + distinct bool +} + +// From starts a new query against the given table. +func From(t *schema.Table) *Query { + return &Query{from: t} +} + +// Distinct adds the DISTINCT keyword to the SELECT clause. +func (q *Query) Distinct() *Query { + q.distinct = true + return q +} + +// Select adds columns to the SELECT clause. +// Columns from tables other than the FROM table trigger automatic JOIN resolution. +func (q *Query) Select(cols ...*schema.Col) *Query { + q.selects = append(q.selects, cols...) + return q +} + +// RawSelect adds raw SQL expressions to the SELECT clause (e.g. "COALESCE(apps.updated, ”)"). +// These are emitted verbatim and do not participate in JOIN resolution. +// Use Select() for typed column references whenever possible. +func (q *Query) RawSelect(exprs ...string) *Query { + q.rawSelects = append(q.rawSelects, exprs...) + return q +} + +// Via declares intermediate tables required to resolve ambiguous join paths. +// Use it when Build() returns an ambiguity error. +func (q *Query) Via(tables ...*schema.Table) *Query { + q.via = append(q.via, tables...) + return q +} + +// LeftJoin declares tables that must be joined with LEFT JOIN instead of INNER JOIN. +// Use it when rows from the FROM table should be preserved even if the joined +// table has no matching row (optional relationship). +// These tables are also added to the set of needed tables, so Via() is not required +// unless the path is ambiguous. +func (q *Query) LeftJoin(tables ...*schema.Table) *Query { + q.leftJoins = append(q.leftJoins, tables...) + q.via = append(q.via, tables...) + return q +} + +// Where adds a "col op ?" condition to the WHERE clause. +// If the column belongs to a table not yet reachable, add it via Via(). +func (q *Query) Where(col *schema.Col, op string, val any) *Query { + q.wheres = append(q.wheres, condition{ + col: col, + expr: fmt.Sprintf("%s %s ?", col.Qualified(), op), + args: []any{val}, + }) + return q +} + +// WhereRaw adds a raw SQL condition to the WHERE clause. +// The expression is emitted verbatim; args are bound in order. +// Use for subqueries or expressions that cannot be expressed with Where/WhereIn. +func (q *Query) WhereRaw(expr string, args ...any) *Query { + q.wheres = append(q.wheres, condition{expr: expr, args: args}) + return q +} + +// WhereIn adds a "col IN (?,...)" condition. Produces "1=0" for an empty slice. +func (q *Query) WhereIn(col *schema.Col, vals []string) *Query { + if len(vals) == 0 { + q.wheres = append(q.wheres, condition{expr: "1=0"}) + return q + } + ph := strings.Repeat("?,", len(vals)) + args := make([]any, len(vals)) + for i, v := range vals { + args[i] = v + } + q.wheres = append(q.wheres, condition{ + col: col, + expr: fmt.Sprintf("%s IN (%s)", col.Qualified(), ph[:len(ph)-1]), + args: args, + }) + return q +} + +// Build assembles the SQL query and its bound arguments. +// Returns an error if a required JOIN path is ambiguous or unreachable. +func (q *Query) Build() (string, []any, error) { + if len(q.selects) == 0 && len(q.rawSelects) == 0 { + return "", nil, fmt.Errorf("qb: no columns selected") + } + + // Collect tables that need to be joined (SELECT + WHERE columns + Via hints). + needed := map[*schema.Table]bool{} + for _, col := range q.selects { + if col.T != q.from { + needed[col.T] = true + } + } + for _, w := range q.wheres { + if w.col != nil && w.col.T != q.from { + needed[w.col.T] = true + } + } + for _, t := range q.via { + if t != q.from { + needed[t] = true + } + } + + // Build the LEFT JOIN set for resolveJoins. + leftJoinSet := make(map[*schema.Table]bool, len(q.leftJoins)) + for _, t := range q.leftJoins { + leftJoinSet[t] = true + } + + joins, err := resolveJoins(q.from, needed, leftJoinSet) + if err != nil { + return "", nil, err + } + + // SELECT + fields := make([]string, 0, len(q.selects)+len(q.rawSelects)) + for _, c := range q.selects { + fields = append(fields, c.Qualified()) + } + fields = append(fields, q.rawSelects...) + + keyword := "SELECT" + if q.distinct { + keyword = "SELECT DISTINCT" + } + sb := &strings.Builder{} + fmt.Fprintf(sb, "%s %s\nFROM %s", keyword, strings.Join(fields, ", "), q.from.Name) + + for _, j := range joins { + fmt.Fprintf(sb, "\n%s", j.sql()) + } + + var args []any + if len(q.wheres) > 0 { + parts := make([]string, len(q.wheres)) + for i, w := range q.wheres { + parts[i] = w.expr + args = append(args, w.args...) + } + fmt.Fprintf(sb, "\nWHERE %s", strings.Join(parts, "\n AND ")) + } + + return sb.String(), args, nil +} + +// resolveJoins computes the ordered list of JOIN steps needed to reach all +// tables in `needed` starting from `from`. +// +// It supports FK traversal in both directions: +// - parent → child : col.Ref.T is joined, col.T is needed +// → JOIN col.T ON col.Qualified() = col.Ref.Qualified() +// - child → parent : col.T is joined, col.Ref.T is needed +// → JOIN col.Ref.T ON col.Ref.Qualified() = col.Qualified() +// +// If multiple FK paths lead to the same needed table in the same iteration, +// an ambiguity error is returned. Use Via() to add intermediate tables to +// `needed` and force a specific path. +func resolveJoins(from *schema.Table, needed map[*schema.Table]bool, leftJoinSet map[*schema.Table]bool) ([]joinStep, error) { + if len(needed) == 0 { + return nil, nil + } + + joined := map[*schema.Table]bool{from: true} + remaining := make(map[*schema.Table]bool, len(needed)) + for t := range needed { + remaining[t] = true + } + + var steps []joinStep + + for len(remaining) > 0 { + // Collect all candidates found in this pass (detect ambiguity). + type candidate struct { + step joinStep + } + found := map[*schema.Table][]joinStep{} + + for _, col := range schema.AllCols { + if col.Ref == nil { + continue + } + // Direction 1: parent already joined → join child + if joined[col.Ref.T] && !joined[col.T] && remaining[col.T] { + found[col.T] = append(found[col.T], joinStep{ + newTable: col.T, + left: col, + right: col.Ref, + leftJoin: leftJoinSet[col.T], + }) + } + // Direction 2: child already joined → join parent + if joined[col.T] && !joined[col.Ref.T] && remaining[col.Ref.T] { + found[col.Ref.T] = append(found[col.Ref.T], joinStep{ + newTable: col.Ref.T, + left: col.Ref, + right: col, + leftJoin: leftJoinSet[col.Ref.T], + }) + } + } + + if len(found) == 0 { + names := make([]string, 0, len(remaining)) + for t := range remaining { + names = append(names, fmt.Sprintf("%q", t.Name)) + } + return nil, fmt.Errorf("qb: no FK path from %q to [%s]; add the intermediate table(s) with Via()", + from.Name, strings.Join(names, ", ")) + } + + for table, candidates := range found { + if len(candidates) > 1 { + paths := make([]string, len(candidates)) + for i, c := range candidates { + paths[i] = fmt.Sprintf("%s.%s", c.left.T.Name, c.left.Name) + } + return nil, fmt.Errorf("qb: ambiguous join path to %q from %q (candidates: %s); use Via() to pick one", + table.Name, from.Name, strings.Join(paths, ", ")) + } + step := candidates[0] + joined[table] = true + delete(remaining, table) + steps = append(steps, step) + } + } + + return steps, nil +} diff --git a/schema/gen/main.go b/schema/gen/main.go new file mode 100644 index 0000000..e67540e --- /dev/null +++ b/schema/gen/main.go @@ -0,0 +1,216 @@ +// schema/gen generates the schema package from a columns.txt export. +// +// # Generate columns.txt from MariaDB +// +// mysql -u -p -e " +// SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE +// FROM information_schema.COLUMNS +// WHERE TABLE_SCHEMA = '' +// ORDER BY TABLE_NAME, ORDINAL_POSITION;" > columns.txt +// +// # Regenerate schema/tables.go +// +// go run ./schema/gen columns.txt schema/tables.go +// +// # Exclude tables +// +// Pass --exclude with a file listing tables to skip (one per line, # for comments): +// +// go run ./schema/gen --exclude exclude.txt columns.txt schema/tables.go +package main + +import ( + "bufio" + "flag" + "fmt" + "os" + "strings" + "unicode" +) + +type column struct { + table string + name string + dbType string + nullable bool +} + +func main() { + excludeFile := flag.String("exclude", "", "file listing tables to exclude (one per line, # for comments)") + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "usage: schema/gen [--exclude exclude.txt] ") + flag.PrintDefaults() + } + flag.Parse() + + if flag.NArg() != 2 { + flag.Usage() + os.Exit(1) + } + + excluded := loadExcludeList(*excludeFile) + cols, tables := parse(flag.Arg(0), excluded) + + f, err := os.Create(flag.Arg(1)) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer f.Close() + + w := bufio.NewWriter(f) + emit(w, cols, tables) + w.Flush() +} + +// loadExcludeList reads a file of table names to exclude (one per line, # for comments). +// Returns an empty map if path is empty. +func loadExcludeList(path string) map[string]bool { + excluded := map[string]bool{} + if path == "" { + return excluded + } + f, err := os.Open(path) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + excluded[line] = true + } + return excluded +} + +// parse reads columns.txt and returns columns + ordered table list (no views, no excluded tables). +func parse(path string, excluded map[string]bool) ([]column, []string) { + f, err := os.Open(path) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer f.Close() + + var cols []column + seen := map[string]bool{} + var tables []string + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, "\t") + if len(parts) < 4 { + continue + } + tbl := parts[0] + col := parts[1] + typ := parts[2] + nullable := parts[3] == "YES" + + // skip header, views, and explicitly excluded tables + if tbl == "TABLE_NAME" || strings.HasPrefix(tbl, "v_") || excluded[tbl] { + continue + } + + if !seen[tbl] { + seen[tbl] = true + tables = append(tables, tbl) + } + cols = append(cols, column{tbl, col, typ, nullable}) + } + return cols, tables +} + +func emit(w *bufio.Writer, cols []column, tables []string) { + fmt.Fprintln(w, "// Code generated by schema/gen. DO NOT EDIT.") + fmt.Fprintln(w, "// Regenerate with: go run ./schema/gen columns.txt schema/tables.go") + fmt.Fprintln(w, "") + fmt.Fprintln(w, "package schema") + fmt.Fprintln(w, "") + + // Table vars + fmt.Fprintln(w, "// Tables") + fmt.Fprintln(w, "var (") + for _, t := range tables { + fmt.Fprintf(w, "\t%s = &Table{Name: %q}\n", tableVar(t), t) + } + fmt.Fprintln(w, ")") + fmt.Fprintln(w, "") + + // Column vars, grouped by table + for _, t := range tables { + fmt.Fprintf(w, "// Columns of %s\n", t) + fmt.Fprintln(w, "var (") + for _, c := range cols { + if c.table != t { + continue + } + fmt.Fprintf(w, "\t%s = &Col{T: %s, Name: %q, Nullable: %v}\n", + colVar(c.table, c.name), tableVar(t), c.name, c.nullable) + } + fmt.Fprintln(w, ")") + fmt.Fprintln(w, "") + } + + // AllCols registry — used by the query builder for join resolution + fmt.Fprintln(w, "// AllCols is the full column registry used for join resolution.") + fmt.Fprintln(w, "var AllCols = []*Col{") + for _, c := range cols { + fmt.Fprintf(w, "\t%s,\n", colVar(c.table, c.name)) + } + fmt.Fprintln(w, "}") +} + +// tableVar returns the Go variable name for a table: "nodes" → "TNodes". +func tableVar(table string) string { + return "T" + pascal(table) +} + +// colVar returns the Go variable name for a column: ("nodes","node_id") → "NodesNodeID". +func colVar(table, col string) string { + return pascal(table) + pascal(col) +} + +// pascal converts snake_case to PascalCase, with common acronym handling. +func pascal(s string) string { + parts := strings.Split(s, "_") + var b strings.Builder + for _, p := range parts { + if len(p) == 0 { + continue + } + up := strings.ToUpper(p) + if acronyms[up] { + b.WriteString(up) + } else { + runes := []rune(p) + runes[0] = unicode.ToUpper(runes[0]) + b.WriteString(string(runes)) + } + } + return b.String() +} + +// acronyms lists abbreviations that should stay fully uppercase. +var acronyms = map[string]bool{ + "ID": true, + "IP": true, + "URL": true, + "CPU": true, + "MEM": true, + "OS": true, + "FK": true, + "MD5": true, + "HBA": true, + "SAN": true, + "DRP": true, + "DG": true, + "PW": true, + "HW": true, +} diff --git a/schema/relations.go b/schema/relations.go new file mode 100644 index 0000000..1d0e0d7 --- /dev/null +++ b/schema/relations.go @@ -0,0 +1,19 @@ +package schema + +func init() { + AppsPublicationsAppID.Ref = AppsID + AppsPublicationsGroupID.Ref = AuthGroupID + AppsResponsiblesAppID.Ref = AppsID + AppsResponsiblesGroupID.Ref = AuthGroupID + + SvcdisksDiskID.Ref = DiskinfoDiskID + SvcdisksNodeID.Ref = NodesNodeID + SvcdisksSvcID.Ref = ServicesSvcID + + ServicesSvcApp.Ref = AppsApp + + NodeIPNodeID.Ref = NodesNodeID + + SvcmonSvcID.Ref = ServicesSvcID + SvcmonLogSvcID.Ref = ServicesSvcID +} diff --git a/schema/schema.go b/schema/schema.go new file mode 100644 index 0000000..893390c --- /dev/null +++ b/schema/schema.go @@ -0,0 +1,21 @@ +package schema + +// Table represents a database table. +type Table struct { + Name string +} + +// Col represents a column in a table. +type Col struct { + T *Table + Name string + Nullable bool + // Ref is set in relations.go for FK relationships. + // It points to the referenced column (e.g. NodesAppID.Ref = AppsID). + Ref *Col +} + +// Qualified returns "table.column" for use in SQL queries. +func (c *Col) Qualified() string { + return c.T.Name + "." + c.Name +} diff --git a/schema/tables.go b/schema/tables.go new file mode 100644 index 0000000..718207c --- /dev/null +++ b/schema/tables.go @@ -0,0 +1,3174 @@ +// Code generated by schema/gen. DO NOT EDIT. +// Regenerate with: go run ./schema/gen columns.txt schema/tables.go + +package schema + +// Tables +var ( + TActionQueue = &Table{Name: "action_queue"} + TAlerts = &Table{Name: "alerts"} + TAlertsSent = &Table{Name: "alerts_sent"} + TApps = &Table{Name: "apps"} + TAppsImport = &Table{Name: "apps_import"} + TAppsPublications = &Table{Name: "apps_publications"} + TAppsResponsibles = &Table{Name: "apps_responsibles"} + TAuthEvent = &Table{Name: "auth_event"} + TAuthFilters = &Table{Name: "auth_filters"} + TAuthGroup = &Table{Name: "auth_group"} + TAuthMembership = &Table{Name: "auth_membership"} + TAuthNode = &Table{Name: "auth_node"} + TAuthPermission = &Table{Name: "auth_permission"} + TAuthUser = &Table{Name: "auth_user"} + TBActionErrors = &Table{Name: "b_action_errors"} + TBAppsOld = &Table{Name: "b_apps_old"} + TCharts = &Table{Name: "charts"} + TChartTeamPublication = &Table{Name: "chart_team_publication"} + TChartTeamResponsible = &Table{Name: "chart_team_responsible"} + TChecksDefaults = &Table{Name: "checks_defaults"} + TChecksLive = &Table{Name: "checks_live"} + TChecksSettings = &Table{Name: "checks_settings"} + TClusters = &Table{Name: "clusters"} + TCompLog = &Table{Name: "comp_log"} + TCompLogDaily = &Table{Name: "comp_log_daily"} + TCompModuleset = &Table{Name: "comp_moduleset"} + TCompModulesetsServices = &Table{Name: "comp_modulesets_services"} + TCompModulesetModules = &Table{Name: "comp_moduleset_modules"} + TCompModulesetModuleset = &Table{Name: "comp_moduleset_moduleset"} + TCompModulesetRuleset = &Table{Name: "comp_moduleset_ruleset"} + TCompModulesetTeamPublication = &Table{Name: "comp_moduleset_team_publication"} + TCompModulesetTeamResponsible = &Table{Name: "comp_moduleset_team_responsible"} + TCompModStatus = &Table{Name: "comp_mod_status"} + TCompNodeModuleset = &Table{Name: "comp_node_moduleset"} + TCompNodeStatus = &Table{Name: "comp_node_status"} + TCompRulesets = &Table{Name: "comp_rulesets"} + TCompRulesetsChains = &Table{Name: "comp_rulesets_chains"} + TCompRulesetsFiltersets = &Table{Name: "comp_rulesets_filtersets"} + TCompRulesetsNodes = &Table{Name: "comp_rulesets_nodes"} + TCompRulesetsRulesets = &Table{Name: "comp_rulesets_rulesets"} + TCompRulesetsServices = &Table{Name: "comp_rulesets_services"} + TCompRulesetsVariables = &Table{Name: "comp_rulesets_variables"} + TCompRulesetTeamPublication = &Table{Name: "comp_ruleset_team_publication"} + TCompRulesetTeamResponsible = &Table{Name: "comp_ruleset_team_responsible"} + TCompRunRuleset = &Table{Name: "comp_run_ruleset"} + TCompStatus = &Table{Name: "comp_status"} + TCompSvcStatus = &Table{Name: "comp_svc_status"} + TDashboard = &Table{Name: "dashboard"} + TDashboardEvents = &Table{Name: "dashboard_events"} + TDashboardRef = &Table{Name: "dashboard_ref"} + TDigit = &Table{Name: "digit"} + TDiskinfo = &Table{Name: "diskinfo"} + TDiskBlacklist = &Table{Name: "disk_blacklist"} + TDockerRegistries = &Table{Name: "docker_registries"} + TDockerRegistriesPublications = &Table{Name: "docker_registries_publications"} + TDockerRegistriesResponsibles = &Table{Name: "docker_registries_responsibles"} + TDockerRepositories = &Table{Name: "docker_repositories"} + TDockerTags = &Table{Name: "docker_tags"} + TDrpprojects = &Table{Name: "drpprojects"} + TDrpservices = &Table{Name: "drpservices"} + TFeedQueue = &Table{Name: "feed_queue"} + TFeedQueueStats = &Table{Name: "feed_queue_stats"} + TFilters = &Table{Name: "filters"} + TForms = &Table{Name: "forms"} + TFormsRevisions = &Table{Name: "forms_revisions"} + TFormsStore = &Table{Name: "forms_store"} + TFormsTeamPublication = &Table{Name: "forms_team_publication"} + TFormsTeamResponsible = &Table{Name: "forms_team_responsible"} + TFormOutputResults = &Table{Name: "form_output_results"} + TFsetCache = &Table{Name: "fset_cache"} + TGenFilters = &Table{Name: "gen_filters"} + TGenFiltersets = &Table{Name: "gen_filtersets"} + TGenFiltersetsFilters = &Table{Name: "gen_filtersets_filters"} + TGenFiltersetCheckThreshold = &Table{Name: "gen_filterset_check_threshold"} + TGenFiltersetTeamResponsible = &Table{Name: "gen_filterset_team_responsible"} + TGenFiltersetUser = &Table{Name: "gen_filterset_user"} + TGroupHiddenMenuEntries = &Table{Name: "group_hidden_menu_entries"} + THbmon = &Table{Name: "hbmon"} + THbmonLog = &Table{Name: "hbmon_log"} + THbmonLogLast = &Table{Name: "hbmon_log_last"} + TImTypes = &Table{Name: "im_types"} + TLifecycleOS = &Table{Name: "lifecycle_os"} + TLinks = &Table{Name: "links"} + TLog = &Table{Name: "log"} + TMetrics = &Table{Name: "metrics"} + TMetricTeamPublication = &Table{Name: "metric_team_publication"} + TNetworks = &Table{Name: "networks"} + TNetworkSegments = &Table{Name: "network_segments"} + TNetworkSegmentResponsibles = &Table{Name: "network_segment_responsibles"} + TNodes = &Table{Name: "nodes"} + TNodeGroups = &Table{Name: "node_groups"} + TNodeHBA = &Table{Name: "node_hba"} + TNodeHW = &Table{Name: "node_hw"} + TNodeIP = &Table{Name: "node_ip"} + TNodePW = &Table{Name: "node_pw"} + TNodeTags = &Table{Name: "node_tags"} + TNodeUsers = &Table{Name: "node_users"} + TObsolescence = &Table{Name: "obsolescence"} + TOc3Scheduler = &Table{Name: "oc3_scheduler"} + TPackages = &Table{Name: "packages"} + TPatches = &Table{Name: "patches"} + TPkgSigProvider = &Table{Name: "pkg_sig_provider"} + TProvTemplates = &Table{Name: "prov_templates"} + TProvTemplateTeamPublication = &Table{Name: "prov_template_team_publication"} + TProvTemplateTeamResponsible = &Table{Name: "prov_template_team_responsible"} + TReplicationStatus = &Table{Name: "replication_status"} + TReports = &Table{Name: "reports"} + TReportsUser = &Table{Name: "reports_user"} + TReportTeamPublication = &Table{Name: "report_team_publication"} + TReportTeamResponsible = &Table{Name: "report_team_responsible"} + TResinfo = &Table{Name: "resinfo"} + TResmon = &Table{Name: "resmon"} + TResmonLog = &Table{Name: "resmon_log"} + TResmonLogLast = &Table{Name: "resmon_log_last"} + TSafe = &Table{Name: "safe"} + TSafeLog = &Table{Name: "safe_log"} + TSafeTeamPublication = &Table{Name: "safe_team_publication"} + TSafeTeamResponsible = &Table{Name: "safe_team_responsible"} + TSANZone = &Table{Name: "san_zone"} + TSANZoneAlias = &Table{Name: "san_zone_alias"} + TSaves = &Table{Name: "saves"} + TSavesLast = &Table{Name: "saves_last"} + TSchedulerRun = &Table{Name: "scheduler_run"} + TSchedulerTask = &Table{Name: "scheduler_task"} + TSchedulerTaskDeps = &Table{Name: "scheduler_task_deps"} + TSchedulerWorker = &Table{Name: "scheduler_worker"} + TServices = &Table{Name: "services"} + TServicesLog = &Table{Name: "services_log"} + TServicesLogLast = &Table{Name: "services_log_last"} + TServicesTest = &Table{Name: "services_test"} + TServiceIds = &Table{Name: "service_ids"} + TStatsCompare = &Table{Name: "stats_compare"} + TStatsCompareFset = &Table{Name: "stats_compare_fset"} + TStatsCompareUser = &Table{Name: "stats_compare_user"} + TStatDay = &Table{Name: "stat_day"} + TStatDayDiskApp = &Table{Name: "stat_day_disk_app"} + TStatDayDiskAppDG = &Table{Name: "stat_day_disk_app_dg"} + TStatDayDiskArray = &Table{Name: "stat_day_disk_array"} + TStatDayDiskArrayDG = &Table{Name: "stat_day_disk_array_dg"} + TStatDaySvc = &Table{Name: "stat_day_svc"} + TStorArray = &Table{Name: "stor_array"} + TStorArrayDG = &Table{Name: "stor_array_dg"} + TStorArrayDGQuota = &Table{Name: "stor_array_dg_quota"} + TStorArrayProxy = &Table{Name: "stor_array_proxy"} + TStorArrayTgtid = &Table{Name: "stor_array_tgtid"} + TStorZone = &Table{Name: "stor_zone"} + TSvcactions = &Table{Name: "svcactions"} + TSvcdisks = &Table{Name: "svcdisks"} + TSvcmon = &Table{Name: "svcmon"} + TSvcmonLog = &Table{Name: "svcmon_log"} + TSvcmonLogAck = &Table{Name: "svcmon_log_ack"} + TSvcmonLogLast = &Table{Name: "svcmon_log_last"} + TSvcTags = &Table{Name: "svc_tags"} + TSwitches = &Table{Name: "switches"} + TSysrepAllow = &Table{Name: "sysrep_allow"} + TSysrepChanging = &Table{Name: "sysrep_changing"} + TSysrepSecure = &Table{Name: "sysrep_secure"} + TTableModified = &Table{Name: "table_modified"} + TTags = &Table{Name: "tags"} + TTmp = &Table{Name: "tmp"} + TTmpmd5 = &Table{Name: "tmpmd5"} + TUserLog = &Table{Name: "user_log"} + TUserPrefs = &Table{Name: "user_prefs"} + TUInc = &Table{Name: "u_inc"} + TWikiPages = &Table{Name: "wiki_pages"} + TWorkflows = &Table{Name: "workflows"} +) + +// Columns of action_queue +var ( + ActionQueueID = &Col{T: TActionQueue, Name: "id", Nullable: false} + ActionQueueStatus = &Col{T: TActionQueue, Name: "status", Nullable: true} + ActionQueueCommand = &Col{T: TActionQueue, Name: "command", Nullable: false} + ActionQueueDateQueued = &Col{T: TActionQueue, Name: "date_queued", Nullable: false} + ActionQueueDateDequeued = &Col{T: TActionQueue, Name: "date_dequeued", Nullable: false} + ActionQueueRet = &Col{T: TActionQueue, Name: "ret", Nullable: true} + ActionQueueStdout = &Col{T: TActionQueue, Name: "stdout", Nullable: true} + ActionQueueStderr = &Col{T: TActionQueue, Name: "stderr", Nullable: true} + ActionQueueActionType = &Col{T: TActionQueue, Name: "action_type", Nullable: true} + ActionQueueUserID = &Col{T: TActionQueue, Name: "user_id", Nullable: true} + ActionQueueFormID = &Col{T: TActionQueue, Name: "form_id", Nullable: true} + ActionQueueConnectTo = &Col{T: TActionQueue, Name: "connect_to", Nullable: true} + ActionQueueNodeID = &Col{T: TActionQueue, Name: "node_id", Nullable: true} + ActionQueueSvcID = &Col{T: TActionQueue, Name: "svc_id", Nullable: true} +) + +// Columns of alerts +var ( + AlertsID = &Col{T: TAlerts, Name: "id", Nullable: false} + AlertsSentAt = &Col{T: TAlerts, Name: "sent_at", Nullable: true} + AlertsSentTo = &Col{T: TAlerts, Name: "sent_to", Nullable: false} + AlertsBody = &Col{T: TAlerts, Name: "body", Nullable: true} + AlertsSubject = &Col{T: TAlerts, Name: "subject", Nullable: false} + AlertsSendAt = &Col{T: TAlerts, Name: "send_at", Nullable: false} + AlertsCreatedAt = &Col{T: TAlerts, Name: "created_at", Nullable: false} + AlertsActionID = &Col{T: TAlerts, Name: "action_id", Nullable: true} + AlertsAppID = &Col{T: TAlerts, Name: "app_id", Nullable: true} + AlertsDomain = &Col{T: TAlerts, Name: "domain", Nullable: true} + AlertsActionIds = &Col{T: TAlerts, Name: "action_ids", Nullable: true} +) + +// Columns of alerts_sent +var ( + AlertsSentID = &Col{T: TAlertsSent, Name: "id", Nullable: false} + AlertsSentAlertID = &Col{T: TAlertsSent, Name: "alert_id", Nullable: false} + AlertsSentMsgType = &Col{T: TAlertsSent, Name: "msg_type", Nullable: false} + AlertsSentUserID = &Col{T: TAlertsSent, Name: "user_id", Nullable: false} + AlertsSentSent = &Col{T: TAlertsSent, Name: "sent", Nullable: false} +) + +// Columns of apps +var ( + AppsID = &Col{T: TApps, Name: "id", Nullable: false} + AppsApp = &Col{T: TApps, Name: "app", Nullable: true} + AppsUpdated = &Col{T: TApps, Name: "updated", Nullable: false} + AppsAppDomain = &Col{T: TApps, Name: "app_domain", Nullable: true} + AppsAppTeamOps = &Col{T: TApps, Name: "app_team_ops", Nullable: true} + AppsDescription = &Col{T: TApps, Name: "description", Nullable: false} +) + +// Columns of apps_import +var ( + AppsImportID = &Col{T: TAppsImport, Name: "id", Nullable: false} + AppsImportApp = &Col{T: TAppsImport, Name: "app", Nullable: true} + AppsImportDesc = &Col{T: TAppsImport, Name: "desc", Nullable: false} + AppsImportUpdated = &Col{T: TAppsImport, Name: "updated", Nullable: false} + AppsImportAppDomain = &Col{T: TAppsImport, Name: "app_domain", Nullable: true} + AppsImportAppTeamOps = &Col{T: TAppsImport, Name: "app_team_ops", Nullable: true} +) + +// Columns of apps_publications +var ( + AppsPublicationsID = &Col{T: TAppsPublications, Name: "id", Nullable: false} + AppsPublicationsGroupID = &Col{T: TAppsPublications, Name: "group_id", Nullable: false} + AppsPublicationsAppID = &Col{T: TAppsPublications, Name: "app_id", Nullable: false} +) + +// Columns of apps_responsibles +var ( + AppsResponsiblesID = &Col{T: TAppsResponsibles, Name: "id", Nullable: false} + AppsResponsiblesGroupID = &Col{T: TAppsResponsibles, Name: "group_id", Nullable: false} + AppsResponsiblesAppID = &Col{T: TAppsResponsibles, Name: "app_id", Nullable: false} +) + +// Columns of auth_event +var ( + AuthEventID = &Col{T: TAuthEvent, Name: "id", Nullable: false} + AuthEventTimeStamp = &Col{T: TAuthEvent, Name: "time_stamp", Nullable: true} + AuthEventClientIP = &Col{T: TAuthEvent, Name: "client_ip", Nullable: true} + AuthEventUserID = &Col{T: TAuthEvent, Name: "user_id", Nullable: true} + AuthEventOrigin = &Col{T: TAuthEvent, Name: "origin", Nullable: true} + AuthEventDescription = &Col{T: TAuthEvent, Name: "description", Nullable: true} +) + +// Columns of auth_filters +var ( + AuthFiltersID = &Col{T: TAuthFilters, Name: "id", Nullable: false} + AuthFiltersFilUid = &Col{T: TAuthFilters, Name: "fil_uid", Nullable: false} + AuthFiltersFilID = &Col{T: TAuthFilters, Name: "fil_id", Nullable: true} + AuthFiltersFilValue = &Col{T: TAuthFilters, Name: "fil_value", Nullable: false} + AuthFiltersFilActive = &Col{T: TAuthFilters, Name: "fil_active", Nullable: true} +) + +// Columns of auth_group +var ( + AuthGroupID = &Col{T: TAuthGroup, Name: "id", Nullable: false} + AuthGroupRole = &Col{T: TAuthGroup, Name: "role", Nullable: true} + AuthGroupDescription = &Col{T: TAuthGroup, Name: "description", Nullable: true} + AuthGroupPrivilege = &Col{T: TAuthGroup, Name: "privilege", Nullable: true} +) + +// Columns of auth_membership +var ( + AuthMembershipID = &Col{T: TAuthMembership, Name: "id", Nullable: false} + AuthMembershipUserID = &Col{T: TAuthMembership, Name: "user_id", Nullable: true} + AuthMembershipGroupID = &Col{T: TAuthMembership, Name: "group_id", Nullable: true} + AuthMembershipPrimaryGroup = &Col{T: TAuthMembership, Name: "primary_group", Nullable: true} +) + +// Columns of auth_node +var ( + AuthNodeID = &Col{T: TAuthNode, Name: "id", Nullable: false} + AuthNodeNodename = &Col{T: TAuthNode, Name: "nodename", Nullable: true} + AuthNodeUuid = &Col{T: TAuthNode, Name: "uuid", Nullable: false} + AuthNodeUpdated = &Col{T: TAuthNode, Name: "updated", Nullable: false} + AuthNodeNodeID = &Col{T: TAuthNode, Name: "node_id", Nullable: true} +) + +// Columns of auth_permission +var ( + AuthPermissionID = &Col{T: TAuthPermission, Name: "id", Nullable: false} + AuthPermissionGroupID = &Col{T: TAuthPermission, Name: "group_id", Nullable: true} + AuthPermissionName = &Col{T: TAuthPermission, Name: "name", Nullable: true} + AuthPermissionTableName = &Col{T: TAuthPermission, Name: "table_name", Nullable: true} + AuthPermissionRecordID = &Col{T: TAuthPermission, Name: "record_id", Nullable: true} +) + +// Columns of auth_user +var ( + AuthUserID = &Col{T: TAuthUser, Name: "id", Nullable: false} + AuthUserFirstName = &Col{T: TAuthUser, Name: "first_name", Nullable: true} + AuthUserLastName = &Col{T: TAuthUser, Name: "last_name", Nullable: true} + AuthUserEmail = &Col{T: TAuthUser, Name: "email", Nullable: true} + AuthUserPassword = &Col{T: TAuthUser, Name: "password", Nullable: true} + AuthUserRegistrationKey = &Col{T: TAuthUser, Name: "registration_key", Nullable: true} + AuthUserResetPasswordKey = &Col{T: TAuthUser, Name: "reset_password_key", Nullable: true} + AuthUserEmailNotifications = &Col{T: TAuthUser, Name: "email_notifications", Nullable: true} + AuthUserImNotifications = &Col{T: TAuthUser, Name: "im_notifications", Nullable: true} + AuthUserImType = &Col{T: TAuthUser, Name: "im_type", Nullable: true} + AuthUserImUsername = &Col{T: TAuthUser, Name: "im_username", Nullable: true} + AuthUserEmailLogLevel = &Col{T: TAuthUser, Name: "email_log_level", Nullable: true} + AuthUserImLogLevel = &Col{T: TAuthUser, Name: "im_log_level", Nullable: true} + AuthUserLockFilter = &Col{T: TAuthUser, Name: "lock_filter", Nullable: true} + AuthUserPhoneWork = &Col{T: TAuthUser, Name: "phone_work", Nullable: true} + AuthUserRegistrationID = &Col{T: TAuthUser, Name: "registration_id", Nullable: true} + AuthUserQuotaApp = &Col{T: TAuthUser, Name: "quota_app", Nullable: true} + AuthUserQuotaOrgGroup = &Col{T: TAuthUser, Name: "quota_org_group", Nullable: true} + AuthUserUsername = &Col{T: TAuthUser, Name: "username", Nullable: true} + AuthUserQuotaDockerRegistries = &Col{T: TAuthUser, Name: "quota_docker_registries", Nullable: true} + AuthUserImNotificationsDelay = &Col{T: TAuthUser, Name: "im_notifications_delay", Nullable: true} + AuthUserEmailNotificationsDelay = &Col{T: TAuthUser, Name: "email_notifications_delay", Nullable: true} +) + +// Columns of b_action_errors +var ( + BActionErrorsID = &Col{T: TBActionErrors, Name: "id", Nullable: false} + BActionErrorsSvcID = &Col{T: TBActionErrors, Name: "svc_id", Nullable: true} + BActionErrorsNodeID = &Col{T: TBActionErrors, Name: "node_id", Nullable: true} + BActionErrorsErr = &Col{T: TBActionErrors, Name: "err", Nullable: false} +) + +// Columns of b_apps_old +var ( + BAppsOldID = &Col{T: TBAppsOld, Name: "id", Nullable: false} + BAppsOldApp = &Col{T: TBAppsOld, Name: "app", Nullable: true} + BAppsOldRoles = &Col{T: TBAppsOld, Name: "roles", Nullable: true} + BAppsOldResponsibles = &Col{T: TBAppsOld, Name: "responsibles", Nullable: true} + BAppsOldMailto = &Col{T: TBAppsOld, Name: "mailto", Nullable: true} + BAppsOldAppDomain = &Col{T: TBAppsOld, Name: "app_domain", Nullable: true} + BAppsOldAppTeamOps = &Col{T: TBAppsOld, Name: "app_team_ops", Nullable: true} +) + +// Columns of charts +var ( + ChartsID = &Col{T: TCharts, Name: "id", Nullable: false} + ChartsChartName = &Col{T: TCharts, Name: "chart_name", Nullable: false} + ChartsChartYaml = &Col{T: TCharts, Name: "chart_yaml", Nullable: true} +) + +// Columns of chart_team_publication +var ( + ChartTeamPublicationID = &Col{T: TChartTeamPublication, Name: "id", Nullable: false} + ChartTeamPublicationChartID = &Col{T: TChartTeamPublication, Name: "chart_id", Nullable: false} + ChartTeamPublicationGroupID = &Col{T: TChartTeamPublication, Name: "group_id", Nullable: false} +) + +// Columns of chart_team_responsible +var ( + ChartTeamResponsibleID = &Col{T: TChartTeamResponsible, Name: "id", Nullable: false} + ChartTeamResponsibleChartID = &Col{T: TChartTeamResponsible, Name: "chart_id", Nullable: false} + ChartTeamResponsibleGroupID = &Col{T: TChartTeamResponsible, Name: "group_id", Nullable: false} +) + +// Columns of checks_defaults +var ( + ChecksDefaultsID = &Col{T: TChecksDefaults, Name: "id", Nullable: false} + ChecksDefaultsChkType = &Col{T: TChecksDefaults, Name: "chk_type", Nullable: false} + ChecksDefaultsChkLow = &Col{T: TChecksDefaults, Name: "chk_low", Nullable: true} + ChecksDefaultsChkHigh = &Col{T: TChecksDefaults, Name: "chk_high", Nullable: true} + ChecksDefaultsChkInst = &Col{T: TChecksDefaults, Name: "chk_inst", Nullable: true} + ChecksDefaultsChkPrio = &Col{T: TChecksDefaults, Name: "chk_prio", Nullable: true} +) + +// Columns of checks_live +var ( + ChecksLiveID = &Col{T: TChecksLive, Name: "id", Nullable: false} + ChecksLiveChkType = &Col{T: TChecksLive, Name: "chk_type", Nullable: false} + ChecksLiveChkUpdated = &Col{T: TChecksLive, Name: "chk_updated", Nullable: false} + ChecksLiveChkValue = &Col{T: TChecksLive, Name: "chk_value", Nullable: true} + ChecksLiveChkCreated = &Col{T: TChecksLive, Name: "chk_created", Nullable: false} + ChecksLiveChkInstance = &Col{T: TChecksLive, Name: "chk_instance", Nullable: true} + ChecksLiveChkLow = &Col{T: TChecksLive, Name: "chk_low", Nullable: true} + ChecksLiveChkHigh = &Col{T: TChecksLive, Name: "chk_high", Nullable: true} + ChecksLiveChkThresholdProvider = &Col{T: TChecksLive, Name: "chk_threshold_provider", Nullable: true} + ChecksLiveChkErr = &Col{T: TChecksLive, Name: "chk_err", Nullable: true} + ChecksLiveNodeID = &Col{T: TChecksLive, Name: "node_id", Nullable: true} + ChecksLiveSvcID = &Col{T: TChecksLive, Name: "svc_id", Nullable: true} +) + +// Columns of checks_settings +var ( + ChecksSettingsID = &Col{T: TChecksSettings, Name: "id", Nullable: false} + ChecksSettingsChkType = &Col{T: TChecksSettings, Name: "chk_type", Nullable: false} + ChecksSettingsChkLow = &Col{T: TChecksSettings, Name: "chk_low", Nullable: true} + ChecksSettingsChkHigh = &Col{T: TChecksSettings, Name: "chk_high", Nullable: true} + ChecksSettingsChkChanged = &Col{T: TChecksSettings, Name: "chk_changed", Nullable: false} + ChecksSettingsChkChangedBy = &Col{T: TChecksSettings, Name: "chk_changed_by", Nullable: false} + ChecksSettingsChkInstance = &Col{T: TChecksSettings, Name: "chk_instance", Nullable: true} + ChecksSettingsNodeID = &Col{T: TChecksSettings, Name: "node_id", Nullable: true} + ChecksSettingsSvcID = &Col{T: TChecksSettings, Name: "svc_id", Nullable: true} +) + +// Columns of clusters +var ( + ClustersID = &Col{T: TClusters, Name: "id", Nullable: false} + ClustersClusterID = &Col{T: TClusters, Name: "cluster_id", Nullable: true} + ClustersClusterName = &Col{T: TClusters, Name: "cluster_name", Nullable: false} + ClustersClusterData = &Col{T: TClusters, Name: "cluster_data", Nullable: true} +) + +// Columns of comp_log +var ( + CompLogID = &Col{T: TCompLog, Name: "id", Nullable: false} + CompLogRunModule = &Col{T: TCompLog, Name: "run_module", Nullable: false} + CompLogRunStatus = &Col{T: TCompLog, Name: "run_status", Nullable: false} + CompLogRunLog = &Col{T: TCompLog, Name: "run_log", Nullable: false} + CompLogRunDate = &Col{T: TCompLog, Name: "run_date", Nullable: false} + CompLogRunAction = &Col{T: TCompLog, Name: "run_action", Nullable: true} + CompLogRsetMD5 = &Col{T: TCompLog, Name: "rset_md5", Nullable: true} + CompLogNodeID = &Col{T: TCompLog, Name: "node_id", Nullable: true} + CompLogSvcID = &Col{T: TCompLog, Name: "svc_id", Nullable: true} +) + +// Columns of comp_log_daily +var ( + CompLogDailyID = &Col{T: TCompLogDaily, Name: "id", Nullable: false} + CompLogDailyRunModule = &Col{T: TCompLogDaily, Name: "run_module", Nullable: false} + CompLogDailyRunStatus = &Col{T: TCompLogDaily, Name: "run_status", Nullable: false} + CompLogDailyRunDate = &Col{T: TCompLogDaily, Name: "run_date", Nullable: false} + CompLogDailyNodeID = &Col{T: TCompLogDaily, Name: "node_id", Nullable: true} + CompLogDailySvcID = &Col{T: TCompLogDaily, Name: "svc_id", Nullable: true} +) + +// Columns of comp_moduleset +var ( + CompModulesetID = &Col{T: TCompModuleset, Name: "id", Nullable: false} + CompModulesetModsetName = &Col{T: TCompModuleset, Name: "modset_name", Nullable: false} + CompModulesetModsetAuthor = &Col{T: TCompModuleset, Name: "modset_author", Nullable: true} + CompModulesetModsetUpdated = &Col{T: TCompModuleset, Name: "modset_updated", Nullable: false} +) + +// Columns of comp_modulesets_services +var ( + CompModulesetsServicesID = &Col{T: TCompModulesetsServices, Name: "id", Nullable: false} + CompModulesetsServicesModsetID = &Col{T: TCompModulesetsServices, Name: "modset_id", Nullable: false} + CompModulesetsServicesModsetModAuthor = &Col{T: TCompModulesetsServices, Name: "modset_mod_author", Nullable: true} + CompModulesetsServicesModsetUpdated = &Col{T: TCompModulesetsServices, Name: "modset_updated", Nullable: false} + CompModulesetsServicesSlave = &Col{T: TCompModulesetsServices, Name: "slave", Nullable: true} + CompModulesetsServicesSvcID = &Col{T: TCompModulesetsServices, Name: "svc_id", Nullable: true} +) + +// Columns of comp_moduleset_modules +var ( + CompModulesetModulesID = &Col{T: TCompModulesetModules, Name: "id", Nullable: false} + CompModulesetModulesModsetID = &Col{T: TCompModulesetModules, Name: "modset_id", Nullable: true} + CompModulesetModulesModsetModName = &Col{T: TCompModulesetModules, Name: "modset_mod_name", Nullable: false} + CompModulesetModulesModsetModAuthor = &Col{T: TCompModulesetModules, Name: "modset_mod_author", Nullable: true} + CompModulesetModulesModsetModUpdated = &Col{T: TCompModulesetModules, Name: "modset_mod_updated", Nullable: false} + CompModulesetModulesAutofix = &Col{T: TCompModulesetModules, Name: "autofix", Nullable: true} +) + +// Columns of comp_moduleset_moduleset +var ( + CompModulesetModulesetID = &Col{T: TCompModulesetModuleset, Name: "id", Nullable: false} + CompModulesetModulesetParentModsetID = &Col{T: TCompModulesetModuleset, Name: "parent_modset_id", Nullable: false} + CompModulesetModulesetChildModsetID = &Col{T: TCompModulesetModuleset, Name: "child_modset_id", Nullable: false} +) + +// Columns of comp_moduleset_ruleset +var ( + CompModulesetRulesetID = &Col{T: TCompModulesetRuleset, Name: "id", Nullable: false} + CompModulesetRulesetModsetID = &Col{T: TCompModulesetRuleset, Name: "modset_id", Nullable: false} + CompModulesetRulesetRulesetID = &Col{T: TCompModulesetRuleset, Name: "ruleset_id", Nullable: false} +) + +// Columns of comp_moduleset_team_publication +var ( + CompModulesetTeamPublicationID = &Col{T: TCompModulesetTeamPublication, Name: "id", Nullable: false} + CompModulesetTeamPublicationModsetID = &Col{T: TCompModulesetTeamPublication, Name: "modset_id", Nullable: false} + CompModulesetTeamPublicationGroupID = &Col{T: TCompModulesetTeamPublication, Name: "group_id", Nullable: false} +) + +// Columns of comp_moduleset_team_responsible +var ( + CompModulesetTeamResponsibleID = &Col{T: TCompModulesetTeamResponsible, Name: "id", Nullable: false} + CompModulesetTeamResponsibleModsetID = &Col{T: TCompModulesetTeamResponsible, Name: "modset_id", Nullable: false} + CompModulesetTeamResponsibleGroupID = &Col{T: TCompModulesetTeamResponsible, Name: "group_id", Nullable: false} +) + +// Columns of comp_mod_status +var ( + CompModStatusID = &Col{T: TCompModStatus, Name: "id", Nullable: false} + CompModStatusTotal = &Col{T: TCompModStatus, Name: "total", Nullable: true} + CompModStatusOk = &Col{T: TCompModStatus, Name: "ok", Nullable: true} + CompModStatusNok = &Col{T: TCompModStatus, Name: "nok", Nullable: true} + CompModStatusNa = &Col{T: TCompModStatus, Name: "na", Nullable: true} + CompModStatusObs = &Col{T: TCompModStatus, Name: "obs", Nullable: true} + CompModStatusPct = &Col{T: TCompModStatus, Name: "pct", Nullable: true} + CompModStatusModName = &Col{T: TCompModStatus, Name: "mod_name", Nullable: true} +) + +// Columns of comp_node_moduleset +var ( + CompNodeModulesetID = &Col{T: TCompNodeModuleset, Name: "id", Nullable: false} + CompNodeModulesetModsetID = &Col{T: TCompNodeModuleset, Name: "modset_id", Nullable: false} + CompNodeModulesetModsetModAuthor = &Col{T: TCompNodeModuleset, Name: "modset_mod_author", Nullable: true} + CompNodeModulesetModsetUpdated = &Col{T: TCompNodeModuleset, Name: "modset_updated", Nullable: false} + CompNodeModulesetNodeID = &Col{T: TCompNodeModuleset, Name: "node_id", Nullable: true} +) + +// Columns of comp_node_status +var ( + CompNodeStatusID = &Col{T: TCompNodeStatus, Name: "id", Nullable: false} + CompNodeStatusTotal = &Col{T: TCompNodeStatus, Name: "total", Nullable: true} + CompNodeStatusOk = &Col{T: TCompNodeStatus, Name: "ok", Nullable: true} + CompNodeStatusNok = &Col{T: TCompNodeStatus, Name: "nok", Nullable: true} + CompNodeStatusNa = &Col{T: TCompNodeStatus, Name: "na", Nullable: true} + CompNodeStatusObs = &Col{T: TCompNodeStatus, Name: "obs", Nullable: true} + CompNodeStatusPct = &Col{T: TCompNodeStatus, Name: "pct", Nullable: true} + CompNodeStatusNodeName = &Col{T: TCompNodeStatus, Name: "node_name", Nullable: true} +) + +// Columns of comp_rulesets +var ( + CompRulesetsID = &Col{T: TCompRulesets, Name: "id", Nullable: false} + CompRulesetsRulesetName = &Col{T: TCompRulesets, Name: "ruleset_name", Nullable: true} + CompRulesetsRulesetType = &Col{T: TCompRulesets, Name: "ruleset_type", Nullable: true} + CompRulesetsRulesetPublic = &Col{T: TCompRulesets, Name: "ruleset_public", Nullable: true} +) + +// Columns of comp_rulesets_chains +var ( + CompRulesetsChainsID = &Col{T: TCompRulesetsChains, Name: "id", Nullable: false} + CompRulesetsChainsHeadRsetID = &Col{T: TCompRulesetsChains, Name: "head_rset_id", Nullable: false} + CompRulesetsChainsTailRsetID = &Col{T: TCompRulesetsChains, Name: "tail_rset_id", Nullable: false} + CompRulesetsChainsChainLen = &Col{T: TCompRulesetsChains, Name: "chain_len", Nullable: false} + CompRulesetsChainsChain = &Col{T: TCompRulesetsChains, Name: "chain", Nullable: false} +) + +// Columns of comp_rulesets_filtersets +var ( + CompRulesetsFiltersetsID = &Col{T: TCompRulesetsFiltersets, Name: "id", Nullable: false} + CompRulesetsFiltersetsRulesetID = &Col{T: TCompRulesetsFiltersets, Name: "ruleset_id", Nullable: false} + CompRulesetsFiltersetsFsetID = &Col{T: TCompRulesetsFiltersets, Name: "fset_id", Nullable: false} +) + +// Columns of comp_rulesets_nodes +var ( + CompRulesetsNodesID = &Col{T: TCompRulesetsNodes, Name: "id", Nullable: false} + CompRulesetsNodesRulesetID = &Col{T: TCompRulesetsNodes, Name: "ruleset_id", Nullable: false} + CompRulesetsNodesNodeID = &Col{T: TCompRulesetsNodes, Name: "node_id", Nullable: true} +) + +// Columns of comp_rulesets_rulesets +var ( + CompRulesetsRulesetsID = &Col{T: TCompRulesetsRulesets, Name: "id", Nullable: false} + CompRulesetsRulesetsParentRsetID = &Col{T: TCompRulesetsRulesets, Name: "parent_rset_id", Nullable: false} + CompRulesetsRulesetsChildRsetID = &Col{T: TCompRulesetsRulesets, Name: "child_rset_id", Nullable: false} +) + +// Columns of comp_rulesets_services +var ( + CompRulesetsServicesID = &Col{T: TCompRulesetsServices, Name: "id", Nullable: false} + CompRulesetsServicesRulesetID = &Col{T: TCompRulesetsServices, Name: "ruleset_id", Nullable: false} + CompRulesetsServicesSlave = &Col{T: TCompRulesetsServices, Name: "slave", Nullable: true} + CompRulesetsServicesSvcID = &Col{T: TCompRulesetsServices, Name: "svc_id", Nullable: true} +) + +// Columns of comp_rulesets_variables +var ( + CompRulesetsVariablesID = &Col{T: TCompRulesetsVariables, Name: "id", Nullable: false} + CompRulesetsVariablesRulesetID = &Col{T: TCompRulesetsVariables, Name: "ruleset_id", Nullable: false} + CompRulesetsVariablesVarName = &Col{T: TCompRulesetsVariables, Name: "var_name", Nullable: false} + CompRulesetsVariablesVarValue = &Col{T: TCompRulesetsVariables, Name: "var_value", Nullable: true} + CompRulesetsVariablesVarAuthor = &Col{T: TCompRulesetsVariables, Name: "var_author", Nullable: false} + CompRulesetsVariablesVarUpdated = &Col{T: TCompRulesetsVariables, Name: "var_updated", Nullable: false} + CompRulesetsVariablesVarClass = &Col{T: TCompRulesetsVariables, Name: "var_class", Nullable: false} +) + +// Columns of comp_ruleset_team_publication +var ( + CompRulesetTeamPublicationID = &Col{T: TCompRulesetTeamPublication, Name: "id", Nullable: false} + CompRulesetTeamPublicationRulesetID = &Col{T: TCompRulesetTeamPublication, Name: "ruleset_id", Nullable: false} + CompRulesetTeamPublicationGroupID = &Col{T: TCompRulesetTeamPublication, Name: "group_id", Nullable: false} +) + +// Columns of comp_ruleset_team_responsible +var ( + CompRulesetTeamResponsibleID = &Col{T: TCompRulesetTeamResponsible, Name: "id", Nullable: false} + CompRulesetTeamResponsibleRulesetID = &Col{T: TCompRulesetTeamResponsible, Name: "ruleset_id", Nullable: false} + CompRulesetTeamResponsibleGroupID = &Col{T: TCompRulesetTeamResponsible, Name: "group_id", Nullable: false} +) + +// Columns of comp_run_ruleset +var ( + CompRunRulesetID = &Col{T: TCompRunRuleset, Name: "id", Nullable: false} + CompRunRulesetRsetMD5 = &Col{T: TCompRunRuleset, Name: "rset_md5", Nullable: false} + CompRunRulesetRset = &Col{T: TCompRunRuleset, Name: "rset", Nullable: false} + CompRunRulesetDate = &Col{T: TCompRunRuleset, Name: "date", Nullable: true} +) + +// Columns of comp_status +var ( + CompStatusID = &Col{T: TCompStatus, Name: "id", Nullable: false} + CompStatusRunModule = &Col{T: TCompStatus, Name: "run_module", Nullable: false} + CompStatusRunStatus = &Col{T: TCompStatus, Name: "run_status", Nullable: false} + CompStatusRunLog = &Col{T: TCompStatus, Name: "run_log", Nullable: false} + CompStatusRunDate = &Col{T: TCompStatus, Name: "run_date", Nullable: false} + CompStatusRunAction = &Col{T: TCompStatus, Name: "run_action", Nullable: true} + CompStatusRsetMD5 = &Col{T: TCompStatus, Name: "rset_md5", Nullable: true} + CompStatusNodeID = &Col{T: TCompStatus, Name: "node_id", Nullable: true} + CompStatusSvcID = &Col{T: TCompStatus, Name: "svc_id", Nullable: true} +) + +// Columns of comp_svc_status +var ( + CompSvcStatusID = &Col{T: TCompSvcStatus, Name: "id", Nullable: false} + CompSvcStatusTotal = &Col{T: TCompSvcStatus, Name: "total", Nullable: true} + CompSvcStatusOk = &Col{T: TCompSvcStatus, Name: "ok", Nullable: true} + CompSvcStatusNok = &Col{T: TCompSvcStatus, Name: "nok", Nullable: true} + CompSvcStatusNa = &Col{T: TCompSvcStatus, Name: "na", Nullable: true} + CompSvcStatusObs = &Col{T: TCompSvcStatus, Name: "obs", Nullable: true} + CompSvcStatusPct = &Col{T: TCompSvcStatus, Name: "pct", Nullable: true} + CompSvcStatusSvcName = &Col{T: TCompSvcStatus, Name: "svc_name", Nullable: true} +) + +// Columns of dashboard +var ( + DashboardID = &Col{T: TDashboard, Name: "id", Nullable: false} + DashboardDashType = &Col{T: TDashboard, Name: "dash_type", Nullable: false} + DashboardSvcID = &Col{T: TDashboard, Name: "svc_id", Nullable: true} + DashboardDashSeverity = &Col{T: TDashboard, Name: "dash_severity", Nullable: false} + DashboardDashFmt = &Col{T: TDashboard, Name: "dash_fmt", Nullable: true} + DashboardDashDict = &Col{T: TDashboard, Name: "dash_dict", Nullable: true} + DashboardDashCreated = &Col{T: TDashboard, Name: "dash_created", Nullable: false} + DashboardDashDictMD5 = &Col{T: TDashboard, Name: "dash_dict_md5", Nullable: true} + DashboardDashEnv = &Col{T: TDashboard, Name: "dash_env", Nullable: true} + DashboardDashUpdated = &Col{T: TDashboard, Name: "dash_updated", Nullable: true} + DashboardNodeID = &Col{T: TDashboard, Name: "node_id", Nullable: true} + DashboardDashMD5 = &Col{T: TDashboard, Name: "dash_md5", Nullable: true} + DashboardDashInstance = &Col{T: TDashboard, Name: "dash_instance", Nullable: true} +) + +// Columns of dashboard_events +var ( + DashboardEventsID = &Col{T: TDashboardEvents, Name: "id", Nullable: false} + DashboardEventsSvcID = &Col{T: TDashboardEvents, Name: "svc_id", Nullable: true} + DashboardEventsDashMD5 = &Col{T: TDashboardEvents, Name: "dash_md5", Nullable: true} + DashboardEventsDashBegin = &Col{T: TDashboardEvents, Name: "dash_begin", Nullable: false} + DashboardEventsDashEnd = &Col{T: TDashboardEvents, Name: "dash_end", Nullable: false} + DashboardEventsNodeID = &Col{T: TDashboardEvents, Name: "node_id", Nullable: true} +) + +// Columns of dashboard_ref +var ( + DashboardRefID = &Col{T: TDashboardRef, Name: "id", Nullable: false} + DashboardRefDashMD5 = &Col{T: TDashboardRef, Name: "dash_md5", Nullable: true} + DashboardRefDashType = &Col{T: TDashboardRef, Name: "dash_type", Nullable: true} + DashboardRefDashFmt = &Col{T: TDashboardRef, Name: "dash_fmt", Nullable: true} + DashboardRefDashDict = &Col{T: TDashboardRef, Name: "dash_dict", Nullable: true} +) + +// Columns of digit +var ( + DigitI = &Col{T: TDigit, Name: "i", Nullable: false} +) + +// Columns of diskinfo +var ( + DiskinfoID = &Col{T: TDiskinfo, Name: "id", Nullable: false} + DiskinfoDiskID = &Col{T: TDiskinfo, Name: "disk_id", Nullable: true} + DiskinfoDiskDevid = &Col{T: TDiskinfo, Name: "disk_devid", Nullable: true} + DiskinfoDiskArrayid = &Col{T: TDiskinfo, Name: "disk_arrayid", Nullable: true} + DiskinfoDiskUpdated = &Col{T: TDiskinfo, Name: "disk_updated", Nullable: true} + DiskinfoDiskRaid = &Col{T: TDiskinfo, Name: "disk_raid", Nullable: true} + DiskinfoDiskSize = &Col{T: TDiskinfo, Name: "disk_size", Nullable: true} + DiskinfoDiskGroup = &Col{T: TDiskinfo, Name: "disk_group", Nullable: true} + DiskinfoDiskLevel = &Col{T: TDiskinfo, Name: "disk_level", Nullable: false} + DiskinfoDiskController = &Col{T: TDiskinfo, Name: "disk_controller", Nullable: true} + DiskinfoDiskName = &Col{T: TDiskinfo, Name: "disk_name", Nullable: true} + DiskinfoDiskAlloc = &Col{T: TDiskinfo, Name: "disk_alloc", Nullable: true} + DiskinfoDiskCreated = &Col{T: TDiskinfo, Name: "disk_created", Nullable: false} +) + +// Columns of disk_blacklist +var ( + DiskBlacklistID = &Col{T: TDiskBlacklist, Name: "id", Nullable: false} + DiskBlacklistDiskID = &Col{T: TDiskBlacklist, Name: "disk_id", Nullable: false} +) + +// Columns of docker_registries +var ( + DockerRegistriesID = &Col{T: TDockerRegistries, Name: "id", Nullable: false} + DockerRegistriesService = &Col{T: TDockerRegistries, Name: "service", Nullable: false} + DockerRegistriesURL = &Col{T: TDockerRegistries, Name: "url", Nullable: false} + DockerRegistriesInsecure = &Col{T: TDockerRegistries, Name: "insecure", Nullable: false} + DockerRegistriesCreated = &Col{T: TDockerRegistries, Name: "created", Nullable: false} + DockerRegistriesUpdated = &Col{T: TDockerRegistries, Name: "updated", Nullable: false} + DockerRegistriesRestricted = &Col{T: TDockerRegistries, Name: "restricted", Nullable: false} +) + +// Columns of docker_registries_publications +var ( + DockerRegistriesPublicationsID = &Col{T: TDockerRegistriesPublications, Name: "id", Nullable: false} + DockerRegistriesPublicationsGroupID = &Col{T: TDockerRegistriesPublications, Name: "group_id", Nullable: false} + DockerRegistriesPublicationsRegistryID = &Col{T: TDockerRegistriesPublications, Name: "registry_id", Nullable: false} +) + +// Columns of docker_registries_responsibles +var ( + DockerRegistriesResponsiblesID = &Col{T: TDockerRegistriesResponsibles, Name: "id", Nullable: false} + DockerRegistriesResponsiblesGroupID = &Col{T: TDockerRegistriesResponsibles, Name: "group_id", Nullable: false} + DockerRegistriesResponsiblesRegistryID = &Col{T: TDockerRegistriesResponsibles, Name: "registry_id", Nullable: false} +) + +// Columns of docker_repositories +var ( + DockerRepositoriesID = &Col{T: TDockerRepositories, Name: "id", Nullable: false} + DockerRepositoriesRegistryID = &Col{T: TDockerRepositories, Name: "registry_id", Nullable: false} + DockerRepositoriesRepository = &Col{T: TDockerRepositories, Name: "repository", Nullable: false} + DockerRepositoriesCreated = &Col{T: TDockerRepositories, Name: "created", Nullable: false} + DockerRepositoriesUpdated = &Col{T: TDockerRepositories, Name: "updated", Nullable: false} + DockerRepositoriesDescription = &Col{T: TDockerRepositories, Name: "description", Nullable: true} + DockerRepositoriesStars = &Col{T: TDockerRepositories, Name: "stars", Nullable: true} + DockerRepositoriesAutomated = &Col{T: TDockerRepositories, Name: "automated", Nullable: true} + DockerRepositoriesOfficial = &Col{T: TDockerRepositories, Name: "official", Nullable: true} +) + +// Columns of docker_tags +var ( + DockerTagsID = &Col{T: TDockerTags, Name: "id", Nullable: false} + DockerTagsRegistryID = &Col{T: TDockerTags, Name: "registry_id", Nullable: false} + DockerTagsRepositoryID = &Col{T: TDockerTags, Name: "repository_id", Nullable: false} + DockerTagsName = &Col{T: TDockerTags, Name: "name", Nullable: false} + DockerTagsCreated = &Col{T: TDockerTags, Name: "created", Nullable: false} + DockerTagsUpdated = &Col{T: TDockerTags, Name: "updated", Nullable: false} + DockerTagsConfigDigest = &Col{T: TDockerTags, Name: "config_digest", Nullable: true} + DockerTagsConfigSize = &Col{T: TDockerTags, Name: "config_size", Nullable: true} +) + +// Columns of drpprojects +var ( + DrpprojectsDRPProject = &Col{T: TDrpprojects, Name: "drp_project", Nullable: false} + DrpprojectsDRPProjectID = &Col{T: TDrpprojects, Name: "drp_project_id", Nullable: false} +) + +// Columns of drpservices +var ( + DrpservicesDRPWave = &Col{T: TDrpservices, Name: "drp_wave", Nullable: true} + DrpservicesDRPProjectID = &Col{T: TDrpservices, Name: "drp_project_id", Nullable: false} + DrpservicesSvcID = &Col{T: TDrpservices, Name: "svc_id", Nullable: true} +) + +// Columns of feed_queue +var ( + FeedQueueID = &Col{T: TFeedQueue, Name: "id", Nullable: false} + FeedQueueQFn = &Col{T: TFeedQueue, Name: "q_fn", Nullable: false} + FeedQueueQArgs = &Col{T: TFeedQueue, Name: "q_args", Nullable: false} + FeedQueueCreated = &Col{T: TFeedQueue, Name: "created", Nullable: false} +) + +// Columns of feed_queue_stats +var ( + FeedQueueStatsID = &Col{T: TFeedQueueStats, Name: "id", Nullable: false} + FeedQueueStatsQStart = &Col{T: TFeedQueueStats, Name: "q_start", Nullable: false} + FeedQueueStatsQEnd = &Col{T: TFeedQueueStats, Name: "q_end", Nullable: false} + FeedQueueStatsQFn = &Col{T: TFeedQueueStats, Name: "q_fn", Nullable: false} + FeedQueueStatsQCount = &Col{T: TFeedQueueStats, Name: "q_count", Nullable: false} + FeedQueueStatsQAvg = &Col{T: TFeedQueueStats, Name: "q_avg", Nullable: false} +) + +// Columns of filters +var ( + FiltersID = &Col{T: TFilters, Name: "id", Nullable: false} + FiltersFilName = &Col{T: TFilters, Name: "fil_name", Nullable: false} + FiltersFilColumn = &Col{T: TFilters, Name: "fil_column", Nullable: false} + FiltersFilNeedValue = &Col{T: TFilters, Name: "fil_need_value", Nullable: false} + FiltersFilPos = &Col{T: TFilters, Name: "fil_pos", Nullable: false} + FiltersFilTable = &Col{T: TFilters, Name: "fil_table", Nullable: false} + FiltersFilImg = &Col{T: TFilters, Name: "fil_img", Nullable: false} + FiltersFilSearchTable = &Col{T: TFilters, Name: "fil_search_table", Nullable: true} +) + +// Columns of forms +var ( + FormsID = &Col{T: TForms, Name: "id", Nullable: false} + FormsFormName = &Col{T: TForms, Name: "form_name", Nullable: true} + FormsFormYaml = &Col{T: TForms, Name: "form_yaml", Nullable: true} + FormsFormAuthor = &Col{T: TForms, Name: "form_author", Nullable: true} + FormsFormCreated = &Col{T: TForms, Name: "form_created", Nullable: false} + FormsFormType = &Col{T: TForms, Name: "form_type", Nullable: true} + FormsFormFolder = &Col{T: TForms, Name: "form_folder", Nullable: true} +) + +// Columns of forms_revisions +var ( + FormsRevisionsID = &Col{T: TFormsRevisions, Name: "id", Nullable: false} + FormsRevisionsFormYaml = &Col{T: TFormsRevisions, Name: "form_yaml", Nullable: false} + FormsRevisionsFormMD5 = &Col{T: TFormsRevisions, Name: "form_md5", Nullable: false} + FormsRevisionsFormDate = &Col{T: TFormsRevisions, Name: "form_date", Nullable: false} + FormsRevisionsFormID = &Col{T: TFormsRevisions, Name: "form_id", Nullable: true} + FormsRevisionsFormFolder = &Col{T: TFormsRevisions, Name: "form_folder", Nullable: true} + FormsRevisionsFormName = &Col{T: TFormsRevisions, Name: "form_name", Nullable: true} +) + +// Columns of forms_store +var ( + FormsStoreID = &Col{T: TFormsStore, Name: "id", Nullable: false} + FormsStoreFormSubmitter = &Col{T: TFormsStore, Name: "form_submitter", Nullable: false} + FormsStoreFormSubmitDate = &Col{T: TFormsStore, Name: "form_submit_date", Nullable: false} + FormsStoreFormData = &Col{T: TFormsStore, Name: "form_data", Nullable: false} + FormsStoreFormNextID = &Col{T: TFormsStore, Name: "form_next_id", Nullable: true} + FormsStoreFormPrevID = &Col{T: TFormsStore, Name: "form_prev_id", Nullable: true} + FormsStoreFormAssignee = &Col{T: TFormsStore, Name: "form_assignee", Nullable: true} + FormsStoreFormHeadID = &Col{T: TFormsStore, Name: "form_head_id", Nullable: true} + FormsStoreFormMD5 = &Col{T: TFormsStore, Name: "form_md5", Nullable: true} + FormsStoreFormVarID = &Col{T: TFormsStore, Name: "form_var_id", Nullable: true} + FormsStoreResultsID = &Col{T: TFormsStore, Name: "results_id", Nullable: true} +) + +// Columns of forms_team_publication +var ( + FormsTeamPublicationID = &Col{T: TFormsTeamPublication, Name: "id", Nullable: false} + FormsTeamPublicationFormID = &Col{T: TFormsTeamPublication, Name: "form_id", Nullable: false} + FormsTeamPublicationGroupID = &Col{T: TFormsTeamPublication, Name: "group_id", Nullable: false} +) + +// Columns of forms_team_responsible +var ( + FormsTeamResponsibleID = &Col{T: TFormsTeamResponsible, Name: "id", Nullable: false} + FormsTeamResponsibleFormID = &Col{T: TFormsTeamResponsible, Name: "form_id", Nullable: false} + FormsTeamResponsibleGroupID = &Col{T: TFormsTeamResponsible, Name: "group_id", Nullable: false} +) + +// Columns of form_output_results +var ( + FormOutputResultsID = &Col{T: TFormOutputResults, Name: "id", Nullable: false} + FormOutputResultsUserID = &Col{T: TFormOutputResults, Name: "user_id", Nullable: true} + FormOutputResultsNodeID = &Col{T: TFormOutputResults, Name: "node_id", Nullable: true} + FormOutputResultsResults = &Col{T: TFormOutputResults, Name: "results", Nullable: true} + FormOutputResultsSvcID = &Col{T: TFormOutputResults, Name: "svc_id", Nullable: true} +) + +// Columns of fset_cache +var ( + FsetCacheFsetID = &Col{T: TFsetCache, Name: "fset_id", Nullable: false} + FsetCacheObjType = &Col{T: TFsetCache, Name: "obj_type", Nullable: false} + FsetCacheObjID = &Col{T: TFsetCache, Name: "obj_id", Nullable: true} +) + +// Columns of gen_filters +var ( + GenFiltersID = &Col{T: TGenFilters, Name: "id", Nullable: false} + GenFiltersFTable = &Col{T: TGenFilters, Name: "f_table", Nullable: false} + GenFiltersFField = &Col{T: TGenFilters, Name: "f_field", Nullable: false} + GenFiltersFValue = &Col{T: TGenFilters, Name: "f_value", Nullable: true} + GenFiltersFUpdated = &Col{T: TGenFilters, Name: "f_updated", Nullable: false} + GenFiltersFAuthor = &Col{T: TGenFilters, Name: "f_author", Nullable: false} + GenFiltersFOp = &Col{T: TGenFilters, Name: "f_op", Nullable: false} + GenFiltersFCksum = &Col{T: TGenFilters, Name: "f_cksum", Nullable: true} + GenFiltersFLabel = &Col{T: TGenFilters, Name: "f_label", Nullable: true} +) + +// Columns of gen_filtersets +var ( + GenFiltersetsID = &Col{T: TGenFiltersets, Name: "id", Nullable: false} + GenFiltersetsFsetName = &Col{T: TGenFiltersets, Name: "fset_name", Nullable: true} + GenFiltersetsFsetUpdated = &Col{T: TGenFiltersets, Name: "fset_updated", Nullable: false} + GenFiltersetsFsetAuthor = &Col{T: TGenFiltersets, Name: "fset_author", Nullable: false} + GenFiltersetsFsetStats = &Col{T: TGenFiltersets, Name: "fset_stats", Nullable: true} +) + +// Columns of gen_filtersets_filters +var ( + GenFiltersetsFiltersID = &Col{T: TGenFiltersetsFilters, Name: "id", Nullable: false} + GenFiltersetsFiltersFsetID = &Col{T: TGenFiltersetsFilters, Name: "fset_id", Nullable: false} + GenFiltersetsFiltersFID = &Col{T: TGenFiltersetsFilters, Name: "f_id", Nullable: false} + GenFiltersetsFiltersFLogOp = &Col{T: TGenFiltersetsFilters, Name: "f_log_op", Nullable: false} + GenFiltersetsFiltersEncapFsetID = &Col{T: TGenFiltersetsFilters, Name: "encap_fset_id", Nullable: true} + GenFiltersetsFiltersFOrder = &Col{T: TGenFiltersetsFilters, Name: "f_order", Nullable: true} +) + +// Columns of gen_filterset_check_threshold +var ( + GenFiltersetCheckThresholdID = &Col{T: TGenFiltersetCheckThreshold, Name: "id", Nullable: false} + GenFiltersetCheckThresholdFsetID = &Col{T: TGenFiltersetCheckThreshold, Name: "fset_id", Nullable: false} + GenFiltersetCheckThresholdChkType = &Col{T: TGenFiltersetCheckThreshold, Name: "chk_type", Nullable: false} + GenFiltersetCheckThresholdChkLow = &Col{T: TGenFiltersetCheckThreshold, Name: "chk_low", Nullable: false} + GenFiltersetCheckThresholdChkHigh = &Col{T: TGenFiltersetCheckThreshold, Name: "chk_high", Nullable: false} + GenFiltersetCheckThresholdChkInstance = &Col{T: TGenFiltersetCheckThreshold, Name: "chk_instance", Nullable: false} +) + +// Columns of gen_filterset_team_responsible +var ( + GenFiltersetTeamResponsibleID = &Col{T: TGenFiltersetTeamResponsible, Name: "id", Nullable: false} + GenFiltersetTeamResponsibleFsetID = &Col{T: TGenFiltersetTeamResponsible, Name: "fset_id", Nullable: false} + GenFiltersetTeamResponsibleGroupID = &Col{T: TGenFiltersetTeamResponsible, Name: "group_id", Nullable: false} +) + +// Columns of gen_filterset_user +var ( + GenFiltersetUserID = &Col{T: TGenFiltersetUser, Name: "id", Nullable: false} + GenFiltersetUserFsetID = &Col{T: TGenFiltersetUser, Name: "fset_id", Nullable: false} + GenFiltersetUserUserID = &Col{T: TGenFiltersetUser, Name: "user_id", Nullable: false} +) + +// Columns of group_hidden_menu_entries +var ( + GroupHiddenMenuEntriesID = &Col{T: TGroupHiddenMenuEntries, Name: "id", Nullable: false} + GroupHiddenMenuEntriesGroupID = &Col{T: TGroupHiddenMenuEntries, Name: "group_id", Nullable: false} + GroupHiddenMenuEntriesMenuEntry = &Col{T: TGroupHiddenMenuEntries, Name: "menu_entry", Nullable: true} +) + +// Columns of hbmon +var ( + HbmonID = &Col{T: THbmon, Name: "id", Nullable: false} + HbmonNodeID = &Col{T: THbmon, Name: "node_id", Nullable: true} + HbmonPeerNodeID = &Col{T: THbmon, Name: "peer_node_id", Nullable: true} + HbmonHbType = &Col{T: THbmon, Name: "hb_type", Nullable: true} + HbmonHbName = &Col{T: THbmon, Name: "hb_name", Nullable: true} + HbmonHbDesc = &Col{T: THbmon, Name: "hb_desc", Nullable: true} + HbmonHbStatus = &Col{T: THbmon, Name: "hb_status", Nullable: true} + HbmonUpdated = &Col{T: THbmon, Name: "updated", Nullable: false} +) + +// Columns of hbmon_log +var ( + HbmonLogID = &Col{T: THbmonLog, Name: "id", Nullable: false} + HbmonLogNodeID = &Col{T: THbmonLog, Name: "node_id", Nullable: true} + HbmonLogPeerNodeID = &Col{T: THbmonLog, Name: "peer_node_id", Nullable: true} + HbmonLogHbName = &Col{T: THbmonLog, Name: "hb_name", Nullable: true} + HbmonLogHbStatus = &Col{T: THbmonLog, Name: "hb_status", Nullable: true} + HbmonLogHbBegin = &Col{T: THbmonLog, Name: "hb_begin", Nullable: false} + HbmonLogHbEnd = &Col{T: THbmonLog, Name: "hb_end", Nullable: false} +) + +// Columns of hbmon_log_last +var ( + HbmonLogLastID = &Col{T: THbmonLogLast, Name: "id", Nullable: false} + HbmonLogLastNodeID = &Col{T: THbmonLogLast, Name: "node_id", Nullable: true} + HbmonLogLastPeerNodeID = &Col{T: THbmonLogLast, Name: "peer_node_id", Nullable: true} + HbmonLogLastHbName = &Col{T: THbmonLogLast, Name: "hb_name", Nullable: true} + HbmonLogLastHbStatus = &Col{T: THbmonLogLast, Name: "hb_status", Nullable: true} + HbmonLogLastHbBegin = &Col{T: THbmonLogLast, Name: "hb_begin", Nullable: false} + HbmonLogLastHbEnd = &Col{T: THbmonLogLast, Name: "hb_end", Nullable: false} +) + +// Columns of im_types +var ( + ImTypesID = &Col{T: TImTypes, Name: "id", Nullable: false} + ImTypesImType = &Col{T: TImTypes, Name: "im_type", Nullable: false} +) + +// Columns of lifecycle_os +var ( + LifecycleOSID = &Col{T: TLifecycleOS, Name: "id", Nullable: false} + LifecycleOSLcOSConcat = &Col{T: TLifecycleOS, Name: "lc_os_concat", Nullable: false} + LifecycleOSLcCount = &Col{T: TLifecycleOS, Name: "lc_count", Nullable: false} + LifecycleOSLcDate = &Col{T: TLifecycleOS, Name: "lc_date", Nullable: false} + LifecycleOSLcOSName = &Col{T: TLifecycleOS, Name: "lc_os_name", Nullable: true} + LifecycleOSLcOSVendor = &Col{T: TLifecycleOS, Name: "lc_os_vendor", Nullable: true} + LifecycleOSFsetID = &Col{T: TLifecycleOS, Name: "fset_id", Nullable: true} +) + +// Columns of links +var ( + LinksID = &Col{T: TLinks, Name: "id", Nullable: false} + LinksLinkFunction = &Col{T: TLinks, Name: "link_function", Nullable: true} + LinksLinkParameters = &Col{T: TLinks, Name: "link_parameters", Nullable: true} + LinksLinkCreationUserID = &Col{T: TLinks, Name: "link_creation_user_id", Nullable: true} + LinksLinkCreationDate = &Col{T: TLinks, Name: "link_creation_date", Nullable: false} + LinksLinkLastConsultationDate = &Col{T: TLinks, Name: "link_last_consultation_date", Nullable: false} + LinksLinkMD5 = &Col{T: TLinks, Name: "link_md5", Nullable: true} + LinksLinkAccessCounter = &Col{T: TLinks, Name: "link_access_counter", Nullable: true} + LinksLinkTitle = &Col{T: TLinks, Name: "link_title", Nullable: true} + LinksLinkTitleArgs = &Col{T: TLinks, Name: "link_title_args", Nullable: true} +) + +// Columns of log +var ( + LogID = &Col{T: TLog, Name: "id", Nullable: false} + LogLogAction = &Col{T: TLog, Name: "log_action", Nullable: false} + LogLogUser = &Col{T: TLog, Name: "log_user", Nullable: false} + LogLogFmt = &Col{T: TLog, Name: "log_fmt", Nullable: true} + LogLogDict = &Col{T: TLog, Name: "log_dict", Nullable: true} + LogLogDate = &Col{T: TLog, Name: "log_date", Nullable: false} + LogSvcID = &Col{T: TLog, Name: "svc_id", Nullable: true} + LogLogGtalkSent = &Col{T: TLog, Name: "log_gtalk_sent", Nullable: true} + LogLogEmailSent = &Col{T: TLog, Name: "log_email_sent", Nullable: true} + LogLogEntryID = &Col{T: TLog, Name: "log_entry_id", Nullable: true} + LogLogLevel = &Col{T: TLog, Name: "log_level", Nullable: true} + LogNodeID = &Col{T: TLog, Name: "node_id", Nullable: true} +) + +// Columns of metrics +var ( + MetricsID = &Col{T: TMetrics, Name: "id", Nullable: false} + MetricsMetricName = &Col{T: TMetrics, Name: "metric_name", Nullable: false} + MetricsMetricSql = &Col{T: TMetrics, Name: "metric_sql", Nullable: true} + MetricsMetricAuthor = &Col{T: TMetrics, Name: "metric_author", Nullable: true} + MetricsMetricCreated = &Col{T: TMetrics, Name: "metric_created", Nullable: false} + MetricsMetricColValueIndex = &Col{T: TMetrics, Name: "metric_col_value_index", Nullable: true} + MetricsMetricColInstanceIndex = &Col{T: TMetrics, Name: "metric_col_instance_index", Nullable: true} + MetricsMetricColInstanceLabel = &Col{T: TMetrics, Name: "metric_col_instance_label", Nullable: true} + MetricsMetricHistorize = &Col{T: TMetrics, Name: "metric_historize", Nullable: true} +) + +// Columns of metric_team_publication +var ( + MetricTeamPublicationID = &Col{T: TMetricTeamPublication, Name: "id", Nullable: false} + MetricTeamPublicationMetricID = &Col{T: TMetricTeamPublication, Name: "metric_id", Nullable: false} + MetricTeamPublicationGroupID = &Col{T: TMetricTeamPublication, Name: "group_id", Nullable: false} +) + +// Columns of networks +var ( + NetworksID = &Col{T: TNetworks, Name: "id", Nullable: false} + NetworksName = &Col{T: TNetworks, Name: "name", Nullable: true} + NetworksNetwork = &Col{T: TNetworks, Name: "network", Nullable: true} + NetworksNetmask = &Col{T: TNetworks, Name: "netmask", Nullable: true} + NetworksTeamResponsible = &Col{T: TNetworks, Name: "team_responsible", Nullable: true} + NetworksComment = &Col{T: TNetworks, Name: "comment", Nullable: true} + NetworksPvid = &Col{T: TNetworks, Name: "pvid", Nullable: true} + NetworksGateway = &Col{T: TNetworks, Name: "gateway", Nullable: true} + NetworksBegin = &Col{T: TNetworks, Name: "begin", Nullable: true} + NetworksUpdated = &Col{T: TNetworks, Name: "updated", Nullable: false} + NetworksPrio = &Col{T: TNetworks, Name: "prio", Nullable: false} + NetworksEnd = &Col{T: TNetworks, Name: "end", Nullable: true} + NetworksBroadcast = &Col{T: TNetworks, Name: "broadcast", Nullable: true} +) + +// Columns of network_segments +var ( + NetworkSegmentsID = &Col{T: TNetworkSegments, Name: "id", Nullable: false} + NetworkSegmentsNetID = &Col{T: TNetworkSegments, Name: "net_id", Nullable: true} + NetworkSegmentsSegType = &Col{T: TNetworkSegments, Name: "seg_type", Nullable: true} + NetworkSegmentsSegBegin = &Col{T: TNetworkSegments, Name: "seg_begin", Nullable: true} + NetworkSegmentsSegEnd = &Col{T: TNetworkSegments, Name: "seg_end", Nullable: true} +) + +// Columns of network_segment_responsibles +var ( + NetworkSegmentResponsiblesID = &Col{T: TNetworkSegmentResponsibles, Name: "id", Nullable: false} + NetworkSegmentResponsiblesSegID = &Col{T: TNetworkSegmentResponsibles, Name: "seg_id", Nullable: true} + NetworkSegmentResponsiblesGroupID = &Col{T: TNetworkSegmentResponsibles, Name: "group_id", Nullable: true} +) + +// Columns of nodes +var ( + NodesNodename = &Col{T: TNodes, Name: "nodename", Nullable: true} + NodesLocCountry = &Col{T: TNodes, Name: "loc_country", Nullable: true} + NodesLocCity = &Col{T: TNodes, Name: "loc_city", Nullable: true} + NodesLocAddr = &Col{T: TNodes, Name: "loc_addr", Nullable: true} + NodesLocBuilding = &Col{T: TNodes, Name: "loc_building", Nullable: true} + NodesLocFloor = &Col{T: TNodes, Name: "loc_floor", Nullable: true} + NodesLocRoom = &Col{T: TNodes, Name: "loc_room", Nullable: true} + NodesLocRack = &Col{T: TNodes, Name: "loc_rack", Nullable: true} + NodesID = &Col{T: TNodes, Name: "id", Nullable: false} + NodesCPUFreq = &Col{T: TNodes, Name: "cpu_freq", Nullable: true} + NodesCPUCores = &Col{T: TNodes, Name: "cpu_cores", Nullable: true} + NodesCPUDies = &Col{T: TNodes, Name: "cpu_dies", Nullable: true} + NodesCPUVendor = &Col{T: TNodes, Name: "cpu_vendor", Nullable: true} + NodesCPUModel = &Col{T: TNodes, Name: "cpu_model", Nullable: true} + NodesMEMBanks = &Col{T: TNodes, Name: "mem_banks", Nullable: true} + NodesMEMSlots = &Col{T: TNodes, Name: "mem_slots", Nullable: true} + NodesMEMBytes = &Col{T: TNodes, Name: "mem_bytes", Nullable: true} + NodesOSName = &Col{T: TNodes, Name: "os_name", Nullable: true} + NodesOSRelease = &Col{T: TNodes, Name: "os_release", Nullable: true} + NodesOSUpdate = &Col{T: TNodes, Name: "os_update", Nullable: true} + NodesOSSegment = &Col{T: TNodes, Name: "os_segment", Nullable: true} + NodesOSArch = &Col{T: TNodes, Name: "os_arch", Nullable: true} + NodesOSVendor = &Col{T: TNodes, Name: "os_vendor", Nullable: true} + NodesOSKernel = &Col{T: TNodes, Name: "os_kernel", Nullable: true} + NodesLocZip = &Col{T: TNodes, Name: "loc_zip", Nullable: true} + NodesTeamResponsible = &Col{T: TNodes, Name: "team_responsible", Nullable: true} + NodesSerial = &Col{T: TNodes, Name: "serial", Nullable: true} + NodesModel = &Col{T: TNodes, Name: "model", Nullable: true} + NodesType = &Col{T: TNodes, Name: "type", Nullable: true} + NodesWarrantyEnd = &Col{T: TNodes, Name: "warranty_end", Nullable: true} + NodesStatus = &Col{T: TNodes, Name: "status", Nullable: true} + NodesRole = &Col{T: TNodes, Name: "role", Nullable: true} + NodesAssetEnv = &Col{T: TNodes, Name: "asset_env", Nullable: true} + NodesPowerCabinet1 = &Col{T: TNodes, Name: "power_cabinet1", Nullable: true} + NodesPowerCabinet2 = &Col{T: TNodes, Name: "power_cabinet2", Nullable: true} + NodesPowerSupplyNb = &Col{T: TNodes, Name: "power_supply_nb", Nullable: true} + NodesPowerProtect = &Col{T: TNodes, Name: "power_protect", Nullable: true} + NodesPowerProtectBreaker = &Col{T: TNodes, Name: "power_protect_breaker", Nullable: true} + NodesPowerBreaker1 = &Col{T: TNodes, Name: "power_breaker1", Nullable: true} + NodesPowerBreaker2 = &Col{T: TNodes, Name: "power_breaker2", Nullable: true} + NodesBladeCabinet = &Col{T: TNodes, Name: "blade_cabinet", Nullable: true} + NodesUpdated = &Col{T: TNodes, Name: "updated", Nullable: true} + NodesTeamInteg = &Col{T: TNodes, Name: "team_integ", Nullable: true} + NodesApp = &Col{T: TNodes, Name: "app", Nullable: true} + NodesTeamSupport = &Col{T: TNodes, Name: "team_support", Nullable: true} + NodesNodeEnv = &Col{T: TNodes, Name: "node_env", Nullable: true} + NodesMaintenanceEnd = &Col{T: TNodes, Name: "maintenance_end", Nullable: true} + NodesEnclosure = &Col{T: TNodes, Name: "enclosure", Nullable: true} + NodesHWObsWarnDate = &Col{T: TNodes, Name: "hw_obs_warn_date", Nullable: true} + NodesHWObsAlertDate = &Col{T: TNodes, Name: "hw_obs_alert_date", Nullable: true} + NodesOSObsWarnDate = &Col{T: TNodes, Name: "os_obs_warn_date", Nullable: true} + NodesOSObsAlertDate = &Col{T: TNodes, Name: "os_obs_alert_date", Nullable: true} + NodesFqdn = &Col{T: TNodes, Name: "fqdn", Nullable: true} + NodesListenerPort = &Col{T: TNodes, Name: "listener_port", Nullable: true} + NodesVersion = &Col{T: TNodes, Name: "version", Nullable: true} + NodesHvpool = &Col{T: TNodes, Name: "hvpool", Nullable: true} + NodesHv = &Col{T: TNodes, Name: "hv", Nullable: true} + NodesHvvdc = &Col{T: TNodes, Name: "hvvdc", Nullable: true} + NodesCPUThreads = &Col{T: TNodes, Name: "cpu_threads", Nullable: true} + NodesAssetname = &Col{T: TNodes, Name: "assetname", Nullable: true} + NodesEnclosureslot = &Col{T: TNodes, Name: "enclosureslot", Nullable: true} + NodesSecZone = &Col{T: TNodes, Name: "sec_zone", Nullable: true} + NodesLastBoot = &Col{T: TNodes, Name: "last_boot", Nullable: true} + NodesActionType = &Col{T: TNodes, Name: "action_type", Nullable: true} + NodesOSConcat = &Col{T: TNodes, Name: "os_concat", Nullable: true} + NodesConnectTo = &Col{T: TNodes, Name: "connect_to", Nullable: true} + NodesTz = &Col{T: TNodes, Name: "tz", Nullable: true} + NodesNodeID = &Col{T: TNodes, Name: "node_id", Nullable: true} + NodesCollector = &Col{T: TNodes, Name: "collector", Nullable: true} + NodesSpVersion = &Col{T: TNodes, Name: "sp_version", Nullable: true} + NodesBiosVersion = &Col{T: TNodes, Name: "bios_version", Nullable: true} + NodesLastComm = &Col{T: TNodes, Name: "last_comm", Nullable: true} + NodesManufacturer = &Col{T: TNodes, Name: "manufacturer", Nullable: true} + NodesNotifications = &Col{T: TNodes, Name: "notifications", Nullable: true} + NodesSnoozeTill = &Col{T: TNodes, Name: "snooze_till", Nullable: true} + NodesClusterID = &Col{T: TNodes, Name: "cluster_id", Nullable: true} + NodesNodeFrozen = &Col{T: TNodes, Name: "node_frozen", Nullable: true} + NodesNodeFrozenAt = &Col{T: TNodes, Name: "node_frozen_at", Nullable: true} +) + +// Columns of node_groups +var ( + NodeGroupsID = &Col{T: TNodeGroups, Name: "id", Nullable: false} + NodeGroupsGroupName = &Col{T: TNodeGroups, Name: "group_name", Nullable: false} + NodeGroupsGroupID = &Col{T: TNodeGroups, Name: "group_id", Nullable: true} + NodeGroupsUpdated = &Col{T: TNodeGroups, Name: "updated", Nullable: false} + NodeGroupsNodeID = &Col{T: TNodeGroups, Name: "node_id", Nullable: true} +) + +// Columns of node_hba +var ( + NodeHBAID = &Col{T: TNodeHBA, Name: "id", Nullable: false} + NodeHBAUpdated = &Col{T: TNodeHBA, Name: "updated", Nullable: false} + NodeHBAHBAID = &Col{T: TNodeHBA, Name: "hba_id", Nullable: false} + NodeHBAHBAType = &Col{T: TNodeHBA, Name: "hba_type", Nullable: true} + NodeHBANodeID = &Col{T: TNodeHBA, Name: "node_id", Nullable: true} +) + +// Columns of node_hw +var ( + NodeHWID = &Col{T: TNodeHW, Name: "id", Nullable: false} + NodeHWNodeID = &Col{T: TNodeHW, Name: "node_id", Nullable: true} + NodeHWHWType = &Col{T: TNodeHW, Name: "hw_type", Nullable: true} + NodeHWHWPath = &Col{T: TNodeHW, Name: "hw_path", Nullable: false} + NodeHWHWClass = &Col{T: TNodeHW, Name: "hw_class", Nullable: false} + NodeHWHWDescription = &Col{T: TNodeHW, Name: "hw_description", Nullable: false} + NodeHWHWDriver = &Col{T: TNodeHW, Name: "hw_driver", Nullable: false} + NodeHWUpdated = &Col{T: TNodeHW, Name: "updated", Nullable: false} +) + +// Columns of node_ip +var ( + NodeIPID = &Col{T: TNodeIP, Name: "id", Nullable: false} + NodeIPMac = &Col{T: TNodeIP, Name: "mac", Nullable: false} + NodeIPIntf = &Col{T: TNodeIP, Name: "intf", Nullable: true} + NodeIPType = &Col{T: TNodeIP, Name: "type", Nullable: true} + NodeIPAddr = &Col{T: TNodeIP, Name: "addr", Nullable: true} + NodeIPMask = &Col{T: TNodeIP, Name: "mask", Nullable: true} + NodeIPUpdated = &Col{T: TNodeIP, Name: "updated", Nullable: false} + NodeIPFlagDeprecated = &Col{T: TNodeIP, Name: "flag_deprecated", Nullable: true} + NodeIPNodeID = &Col{T: TNodeIP, Name: "node_id", Nullable: true} +) + +// Columns of node_pw +var ( + NodePWID = &Col{T: TNodePW, Name: "id", Nullable: false} + NodePWPW = &Col{T: TNodePW, Name: "pw", Nullable: false} + NodePWUpdated = &Col{T: TNodePW, Name: "updated", Nullable: false} + NodePWNodeID = &Col{T: TNodePW, Name: "node_id", Nullable: true} +) + +// Columns of node_tags +var ( + NodeTagsID = &Col{T: TNodeTags, Name: "id", Nullable: false} + NodeTagsCreated = &Col{T: TNodeTags, Name: "created", Nullable: false} + NodeTagsNodeID = &Col{T: TNodeTags, Name: "node_id", Nullable: true} + NodeTagsTagID = &Col{T: TNodeTags, Name: "tag_id", Nullable: true} + NodeTagsTagAttachData = &Col{T: TNodeTags, Name: "tag_attach_data", Nullable: true} +) + +// Columns of node_users +var ( + NodeUsersID = &Col{T: TNodeUsers, Name: "id", Nullable: false} + NodeUsersUserName = &Col{T: TNodeUsers, Name: "user_name", Nullable: false} + NodeUsersUserID = &Col{T: TNodeUsers, Name: "user_id", Nullable: true} + NodeUsersUpdated = &Col{T: TNodeUsers, Name: "updated", Nullable: false} + NodeUsersNodeID = &Col{T: TNodeUsers, Name: "node_id", Nullable: true} +) + +// Columns of obsolescence +var ( + ObsolescenceID = &Col{T: TObsolescence, Name: "id", Nullable: false} + ObsolescenceObsType = &Col{T: TObsolescence, Name: "obs_type", Nullable: false} + ObsolescenceObsName = &Col{T: TObsolescence, Name: "obs_name", Nullable: false} + ObsolescenceObsWarnDate = &Col{T: TObsolescence, Name: "obs_warn_date", Nullable: true} + ObsolescenceObsAlertDate = &Col{T: TObsolescence, Name: "obs_alert_date", Nullable: true} + ObsolescenceObsWarnDateUpdatedBy = &Col{T: TObsolescence, Name: "obs_warn_date_updated_by", Nullable: false} + ObsolescenceObsAlertDateUpdatedBy = &Col{T: TObsolescence, Name: "obs_alert_date_updated_by", Nullable: false} + ObsolescenceObsWarnDateUpdated = &Col{T: TObsolescence, Name: "obs_warn_date_updated", Nullable: false} + ObsolescenceObsAlertDateUpdated = &Col{T: TObsolescence, Name: "obs_alert_date_updated", Nullable: false} +) + +// Columns of oc3_scheduler +var ( + Oc3SchedulerID = &Col{T: TOc3Scheduler, Name: "id", Nullable: false} + Oc3SchedulerTaskName = &Col{T: TOc3Scheduler, Name: "task_name", Nullable: true} + Oc3SchedulerIsDisabled = &Col{T: TOc3Scheduler, Name: "is_disabled", Nullable: true} + Oc3SchedulerLastRunAt = &Col{T: TOc3Scheduler, Name: "last_run_at", Nullable: true} +) + +// Columns of packages +var ( + PackagesID = &Col{T: TPackages, Name: "id", Nullable: false} + PackagesPkgName = &Col{T: TPackages, Name: "pkg_name", Nullable: false} + PackagesPkgVersion = &Col{T: TPackages, Name: "pkg_version", Nullable: false} + PackagesPkgArch = &Col{T: TPackages, Name: "pkg_arch", Nullable: false} + PackagesPkgUpdated = &Col{T: TPackages, Name: "pkg_updated", Nullable: false} + PackagesPkgType = &Col{T: TPackages, Name: "pkg_type", Nullable: true} + PackagesPkgInstallDate = &Col{T: TPackages, Name: "pkg_install_date", Nullable: true} + PackagesPkgSig = &Col{T: TPackages, Name: "pkg_sig", Nullable: true} + PackagesNodeID = &Col{T: TPackages, Name: "node_id", Nullable: true} +) + +// Columns of patches +var ( + PatchesID = &Col{T: TPatches, Name: "id", Nullable: false} + PatchesPatchNum = &Col{T: TPatches, Name: "patch_num", Nullable: false} + PatchesPatchRev = &Col{T: TPatches, Name: "patch_rev", Nullable: false} + PatchesPatchUpdated = &Col{T: TPatches, Name: "patch_updated", Nullable: false} + PatchesPatchInstallDate = &Col{T: TPatches, Name: "patch_install_date", Nullable: true} + PatchesNodeID = &Col{T: TPatches, Name: "node_id", Nullable: true} +) + +// Columns of pkg_sig_provider +var ( + PkgSigProviderID = &Col{T: TPkgSigProvider, Name: "id", Nullable: false} + PkgSigProviderSigID = &Col{T: TPkgSigProvider, Name: "sig_id", Nullable: false} + PkgSigProviderSigProvider = &Col{T: TPkgSigProvider, Name: "sig_provider", Nullable: false} +) + +// Columns of prov_templates +var ( + ProvTemplatesID = &Col{T: TProvTemplates, Name: "id", Nullable: false} + ProvTemplatesTplName = &Col{T: TProvTemplates, Name: "tpl_name", Nullable: false} + ProvTemplatesTplDefinition = &Col{T: TProvTemplates, Name: "tpl_definition", Nullable: true} + ProvTemplatesTplComment = &Col{T: TProvTemplates, Name: "tpl_comment", Nullable: false} + ProvTemplatesTplAuthor = &Col{T: TProvTemplates, Name: "tpl_author", Nullable: false} + ProvTemplatesTplCreated = &Col{T: TProvTemplates, Name: "tpl_created", Nullable: false} +) + +// Columns of prov_template_team_publication +var ( + ProvTemplateTeamPublicationID = &Col{T: TProvTemplateTeamPublication, Name: "id", Nullable: false} + ProvTemplateTeamPublicationTplID = &Col{T: TProvTemplateTeamPublication, Name: "tpl_id", Nullable: false} + ProvTemplateTeamPublicationGroupID = &Col{T: TProvTemplateTeamPublication, Name: "group_id", Nullable: false} +) + +// Columns of prov_template_team_responsible +var ( + ProvTemplateTeamResponsibleID = &Col{T: TProvTemplateTeamResponsible, Name: "id", Nullable: false} + ProvTemplateTeamResponsibleTplID = &Col{T: TProvTemplateTeamResponsible, Name: "tpl_id", Nullable: false} + ProvTemplateTeamResponsibleGroupID = &Col{T: TProvTemplateTeamResponsible, Name: "group_id", Nullable: false} +) + +// Columns of replication_status +var ( + ReplicationStatusID = &Col{T: TReplicationStatus, Name: "id", Nullable: false} + ReplicationStatusRemote = &Col{T: TReplicationStatus, Name: "remote", Nullable: false} + ReplicationStatusMode = &Col{T: TReplicationStatus, Name: "mode", Nullable: true} + ReplicationStatusTableSchema = &Col{T: TReplicationStatus, Name: "table_schema", Nullable: false} + ReplicationStatusTableName = &Col{T: TReplicationStatus, Name: "table_name", Nullable: false} + ReplicationStatusTableCksum = &Col{T: TReplicationStatus, Name: "table_cksum", Nullable: false} + ReplicationStatusTableUpdated = &Col{T: TReplicationStatus, Name: "table_updated", Nullable: true} +) + +// Columns of reports +var ( + ReportsID = &Col{T: TReports, Name: "id", Nullable: false} + ReportsReportName = &Col{T: TReports, Name: "report_name", Nullable: false} + ReportsReportYaml = &Col{T: TReports, Name: "report_yaml", Nullable: true} +) + +// Columns of reports_user +var ( + ReportsUserID = &Col{T: TReportsUser, Name: "id", Nullable: false} + ReportsUserReportID = &Col{T: TReportsUser, Name: "report_id", Nullable: false} + ReportsUserUserID = &Col{T: TReportsUser, Name: "user_id", Nullable: false} +) + +// Columns of report_team_publication +var ( + ReportTeamPublicationID = &Col{T: TReportTeamPublication, Name: "id", Nullable: false} + ReportTeamPublicationReportID = &Col{T: TReportTeamPublication, Name: "report_id", Nullable: false} + ReportTeamPublicationGroupID = &Col{T: TReportTeamPublication, Name: "group_id", Nullable: false} +) + +// Columns of report_team_responsible +var ( + ReportTeamResponsibleID = &Col{T: TReportTeamResponsible, Name: "id", Nullable: false} + ReportTeamResponsibleReportID = &Col{T: TReportTeamResponsible, Name: "report_id", Nullable: false} + ReportTeamResponsibleGroupID = &Col{T: TReportTeamResponsible, Name: "group_id", Nullable: false} +) + +// Columns of resinfo +var ( + ResinfoID = &Col{T: TResinfo, Name: "id", Nullable: false} + ResinfoRid = &Col{T: TResinfo, Name: "rid", Nullable: true} + ResinfoResKey = &Col{T: TResinfo, Name: "res_key", Nullable: true} + ResinfoResValue = &Col{T: TResinfo, Name: "res_value", Nullable: true} + ResinfoUpdated = &Col{T: TResinfo, Name: "updated", Nullable: false} + ResinfoTopology = &Col{T: TResinfo, Name: "topology", Nullable: true} + ResinfoNodeID = &Col{T: TResinfo, Name: "node_id", Nullable: true} + ResinfoSvcID = &Col{T: TResinfo, Name: "svc_id", Nullable: true} +) + +// Columns of resmon +var ( + ResmonID = &Col{T: TResmon, Name: "id", Nullable: false} + ResmonRid = &Col{T: TResmon, Name: "rid", Nullable: true} + ResmonResStatus = &Col{T: TResmon, Name: "res_status", Nullable: true} + ResmonChanged = &Col{T: TResmon, Name: "changed", Nullable: false} + ResmonUpdated = &Col{T: TResmon, Name: "updated", Nullable: false} + ResmonResDesc = &Col{T: TResmon, Name: "res_desc", Nullable: true} + ResmonResLog = &Col{T: TResmon, Name: "res_log", Nullable: true} + ResmonVmname = &Col{T: TResmon, Name: "vmname", Nullable: true} + ResmonResMonitor = &Col{T: TResmon, Name: "res_monitor", Nullable: true} + ResmonResDisable = &Col{T: TResmon, Name: "res_disable", Nullable: true} + ResmonResOptional = &Col{T: TResmon, Name: "res_optional", Nullable: true} + ResmonResType = &Col{T: TResmon, Name: "res_type", Nullable: true} + ResmonNodeID = &Col{T: TResmon, Name: "node_id", Nullable: true} + ResmonSvcID = &Col{T: TResmon, Name: "svc_id", Nullable: true} +) + +// Columns of resmon_log +var ( + ResmonLogID = &Col{T: TResmonLog, Name: "id", Nullable: false} + ResmonLogNodeID = &Col{T: TResmonLog, Name: "node_id", Nullable: true} + ResmonLogSvcID = &Col{T: TResmonLog, Name: "svc_id", Nullable: true} + ResmonLogRid = &Col{T: TResmonLog, Name: "rid", Nullable: true} + ResmonLogResStatus = &Col{T: TResmonLog, Name: "res_status", Nullable: true} + ResmonLogResBegin = &Col{T: TResmonLog, Name: "res_begin", Nullable: false} + ResmonLogResEnd = &Col{T: TResmonLog, Name: "res_end", Nullable: false} + ResmonLogResLog = &Col{T: TResmonLog, Name: "res_log", Nullable: true} +) + +// Columns of resmon_log_last +var ( + ResmonLogLastID = &Col{T: TResmonLogLast, Name: "id", Nullable: false} + ResmonLogLastNodeID = &Col{T: TResmonLogLast, Name: "node_id", Nullable: true} + ResmonLogLastSvcID = &Col{T: TResmonLogLast, Name: "svc_id", Nullable: true} + ResmonLogLastRid = &Col{T: TResmonLogLast, Name: "rid", Nullable: true} + ResmonLogLastResStatus = &Col{T: TResmonLogLast, Name: "res_status", Nullable: true} + ResmonLogLastResBegin = &Col{T: TResmonLogLast, Name: "res_begin", Nullable: false} + ResmonLogLastResEnd = &Col{T: TResmonLogLast, Name: "res_end", Nullable: false} + ResmonLogLastResLog = &Col{T: TResmonLogLast, Name: "res_log", Nullable: true} +) + +// Columns of safe +var ( + SafeID = &Col{T: TSafe, Name: "id", Nullable: false} + SafeUploader = &Col{T: TSafe, Name: "uploader", Nullable: true} + SafeUploadedFrom = &Col{T: TSafe, Name: "uploaded_from", Nullable: true} + SafeUploadedDate = &Col{T: TSafe, Name: "uploaded_date", Nullable: false} + SafeName = &Col{T: TSafe, Name: "name", Nullable: true} + SafeSize = &Col{T: TSafe, Name: "size", Nullable: true} + SafeUuid = &Col{T: TSafe, Name: "uuid", Nullable: true} + SafeMD5 = &Col{T: TSafe, Name: "md5", Nullable: true} +) + +// Columns of safe_log +var ( + SafeLogID = &Col{T: TSafeLog, Name: "id", Nullable: false} + SafeLogSafeID = &Col{T: TSafeLog, Name: "safe_id", Nullable: false} + SafeLogUuid = &Col{T: TSafeLog, Name: "uuid", Nullable: false} + SafeLogArchived = &Col{T: TSafeLog, Name: "archived", Nullable: false} +) + +// Columns of safe_team_publication +var ( + SafeTeamPublicationID = &Col{T: TSafeTeamPublication, Name: "id", Nullable: false} + SafeTeamPublicationFileID = &Col{T: TSafeTeamPublication, Name: "file_id", Nullable: false} + SafeTeamPublicationGroupID = &Col{T: TSafeTeamPublication, Name: "group_id", Nullable: false} +) + +// Columns of safe_team_responsible +var ( + SafeTeamResponsibleID = &Col{T: TSafeTeamResponsible, Name: "id", Nullable: false} + SafeTeamResponsibleFileID = &Col{T: TSafeTeamResponsible, Name: "file_id", Nullable: false} + SafeTeamResponsibleGroupID = &Col{T: TSafeTeamResponsible, Name: "group_id", Nullable: false} +) + +// Columns of san_zone +var ( + SANZoneID = &Col{T: TSANZone, Name: "id", Nullable: false} + SANZoneCfg = &Col{T: TSANZone, Name: "cfg", Nullable: true} + SANZoneZone = &Col{T: TSANZone, Name: "zone", Nullable: true} + SANZonePort = &Col{T: TSANZone, Name: "port", Nullable: true} + SANZoneUpdated = &Col{T: TSANZone, Name: "updated", Nullable: false} +) + +// Columns of san_zone_alias +var ( + SANZoneAliasID = &Col{T: TSANZoneAlias, Name: "id", Nullable: false} + SANZoneAliasCfg = &Col{T: TSANZoneAlias, Name: "cfg", Nullable: true} + SANZoneAliasAlias = &Col{T: TSANZoneAlias, Name: "alias", Nullable: true} + SANZoneAliasPort = &Col{T: TSANZoneAlias, Name: "port", Nullable: true} + SANZoneAliasUpdated = &Col{T: TSANZoneAlias, Name: "updated", Nullable: false} +) + +// Columns of saves +var ( + SavesID = &Col{T: TSaves, Name: "id", Nullable: false} + SavesSaveName = &Col{T: TSaves, Name: "save_name", Nullable: false} + SavesSaveGroup = &Col{T: TSaves, Name: "save_group", Nullable: false} + SavesSaveSize = &Col{T: TSaves, Name: "save_size", Nullable: true} + SavesSaveDate = &Col{T: TSaves, Name: "save_date", Nullable: false} + SavesSaveRetention = &Col{T: TSaves, Name: "save_retention", Nullable: false} + SavesSaveVolume = &Col{T: TSaves, Name: "save_volume", Nullable: false} + SavesSaveLevel = &Col{T: TSaves, Name: "save_level", Nullable: false} + SavesSaveServer = &Col{T: TSaves, Name: "save_server", Nullable: false} + SavesSaveApp = &Col{T: TSaves, Name: "save_app", Nullable: true} + SavesSaveID = &Col{T: TSaves, Name: "save_id", Nullable: true} + SavesNodeID = &Col{T: TSaves, Name: "node_id", Nullable: true} + SavesSvcID = &Col{T: TSaves, Name: "svc_id", Nullable: true} + SavesChkInstance = &Col{T: TSaves, Name: "chk_instance", Nullable: true} + SavesSaveResolved = &Col{T: TSaves, Name: "save_resolved", Nullable: true} +) + +// Columns of saves_last +var ( + SavesLastID = &Col{T: TSavesLast, Name: "id", Nullable: false} + SavesLastSaveName = &Col{T: TSavesLast, Name: "save_name", Nullable: false} + SavesLastSaveGroup = &Col{T: TSavesLast, Name: "save_group", Nullable: false} + SavesLastSaveSize = &Col{T: TSavesLast, Name: "save_size", Nullable: true} + SavesLastSaveDate = &Col{T: TSavesLast, Name: "save_date", Nullable: false} + SavesLastSaveRetention = &Col{T: TSavesLast, Name: "save_retention", Nullable: false} + SavesLastSaveVolume = &Col{T: TSavesLast, Name: "save_volume", Nullable: false} + SavesLastSaveLevel = &Col{T: TSavesLast, Name: "save_level", Nullable: false} + SavesLastSaveServer = &Col{T: TSavesLast, Name: "save_server", Nullable: false} + SavesLastSaveApp = &Col{T: TSavesLast, Name: "save_app", Nullable: true} + SavesLastSaveID = &Col{T: TSavesLast, Name: "save_id", Nullable: true} + SavesLastChkInstance = &Col{T: TSavesLast, Name: "chk_instance", Nullable: true} + SavesLastSaveResolved = &Col{T: TSavesLast, Name: "save_resolved", Nullable: true} + SavesLastNodeID = &Col{T: TSavesLast, Name: "node_id", Nullable: true} + SavesLastSvcID = &Col{T: TSavesLast, Name: "svc_id", Nullable: true} +) + +// Columns of scheduler_run +var ( + SchedulerRunID = &Col{T: TSchedulerRun, Name: "id", Nullable: false} + SchedulerRunTaskID = &Col{T: TSchedulerRun, Name: "task_id", Nullable: true} + SchedulerRunStartTime = &Col{T: TSchedulerRun, Name: "start_time", Nullable: true} + SchedulerRunStopTime = &Col{T: TSchedulerRun, Name: "stop_time", Nullable: true} + SchedulerRunRunOutput = &Col{T: TSchedulerRun, Name: "run_output", Nullable: true} + SchedulerRunRunResult = &Col{T: TSchedulerRun, Name: "run_result", Nullable: true} + SchedulerRunTraceback = &Col{T: TSchedulerRun, Name: "traceback", Nullable: true} + SchedulerRunStatus = &Col{T: TSchedulerRun, Name: "status", Nullable: true} + SchedulerRunWorkerName = &Col{T: TSchedulerRun, Name: "worker_name", Nullable: true} + SchedulerRunDuration = &Col{T: TSchedulerRun, Name: "duration", Nullable: true} +) + +// Columns of scheduler_task +var ( + SchedulerTaskID = &Col{T: TSchedulerTask, Name: "id", Nullable: false} + SchedulerTaskUuid = &Col{T: TSchedulerTask, Name: "uuid", Nullable: true} + SchedulerTaskArgs = &Col{T: TSchedulerTask, Name: "args", Nullable: true} + SchedulerTaskVars = &Col{T: TSchedulerTask, Name: "vars", Nullable: true} + SchedulerTaskEnabled = &Col{T: TSchedulerTask, Name: "enabled", Nullable: true} + SchedulerTaskStartTime = &Col{T: TSchedulerTask, Name: "start_time", Nullable: true} + SchedulerTaskNextRunTime = &Col{T: TSchedulerTask, Name: "next_run_time", Nullable: true} + SchedulerTaskStopTime = &Col{T: TSchedulerTask, Name: "stop_time", Nullable: true} + SchedulerTaskRepeats = &Col{T: TSchedulerTask, Name: "repeats", Nullable: true} + SchedulerTaskRetryFailed = &Col{T: TSchedulerTask, Name: "retry_failed", Nullable: true} + SchedulerTaskPeriod = &Col{T: TSchedulerTask, Name: "period", Nullable: true} + SchedulerTaskTimeout = &Col{T: TSchedulerTask, Name: "timeout", Nullable: true} + SchedulerTaskSyncOutput = &Col{T: TSchedulerTask, Name: "sync_output", Nullable: true} + SchedulerTaskTimesRun = &Col{T: TSchedulerTask, Name: "times_run", Nullable: true} + SchedulerTaskTimesFailed = &Col{T: TSchedulerTask, Name: "times_failed", Nullable: true} + SchedulerTaskLastRunTime = &Col{T: TSchedulerTask, Name: "last_run_time", Nullable: true} + SchedulerTaskPreventDrift = &Col{T: TSchedulerTask, Name: "prevent_drift", Nullable: true} + SchedulerTaskGroupName = &Col{T: TSchedulerTask, Name: "group_name", Nullable: true} + SchedulerTaskFunctionName = &Col{T: TSchedulerTask, Name: "function_name", Nullable: true} + SchedulerTaskStatus = &Col{T: TSchedulerTask, Name: "status", Nullable: true} + SchedulerTaskTaskName = &Col{T: TSchedulerTask, Name: "task_name", Nullable: true} + SchedulerTaskApplicationName = &Col{T: TSchedulerTask, Name: "application_name", Nullable: true} + SchedulerTaskAssignedWorkerName = &Col{T: TSchedulerTask, Name: "assigned_worker_name", Nullable: true} +) + +// Columns of scheduler_task_deps +var ( + SchedulerTaskDepsID = &Col{T: TSchedulerTaskDeps, Name: "id", Nullable: false} + SchedulerTaskDepsJobName = &Col{T: TSchedulerTaskDeps, Name: "job_name", Nullable: true} + SchedulerTaskDepsTaskParent = &Col{T: TSchedulerTaskDeps, Name: "task_parent", Nullable: true} + SchedulerTaskDepsTaskChild = &Col{T: TSchedulerTaskDeps, Name: "task_child", Nullable: true} + SchedulerTaskDepsCanVisit = &Col{T: TSchedulerTaskDeps, Name: "can_visit", Nullable: true} +) + +// Columns of scheduler_worker +var ( + SchedulerWorkerID = &Col{T: TSchedulerWorker, Name: "id", Nullable: false} + SchedulerWorkerWorkerName = &Col{T: TSchedulerWorker, Name: "worker_name", Nullable: true} + SchedulerWorkerFirstHeartbeat = &Col{T: TSchedulerWorker, Name: "first_heartbeat", Nullable: true} + SchedulerWorkerLastHeartbeat = &Col{T: TSchedulerWorker, Name: "last_heartbeat", Nullable: true} + SchedulerWorkerIsTicker = &Col{T: TSchedulerWorker, Name: "is_ticker", Nullable: true} + SchedulerWorkerGroupNames = &Col{T: TSchedulerWorker, Name: "group_names", Nullable: true} + SchedulerWorkerStatus = &Col{T: TSchedulerWorker, Name: "status", Nullable: true} + SchedulerWorkerWorkerStats = &Col{T: TSchedulerWorker, Name: "worker_stats", Nullable: true} +) + +// Columns of services +var ( + ServicesSvcHostid = &Col{T: TServices, Name: "svc_hostid", Nullable: true} + ServicesSvcname = &Col{T: TServices, Name: "svcname", Nullable: true} + ServicesSvcNodes = &Col{T: TServices, Name: "svc_nodes", Nullable: true} + ServicesSvcDrpnode = &Col{T: TServices, Name: "svc_drpnode", Nullable: true} + ServicesSvcDrptype = &Col{T: TServices, Name: "svc_drptype", Nullable: true} + ServicesSvcAutostart = &Col{T: TServices, Name: "svc_autostart", Nullable: false} + ServicesSvcEnv = &Col{T: TServices, Name: "svc_env", Nullable: true} + ServicesSvcDrpnodes = &Col{T: TServices, Name: "svc_drpnodes", Nullable: true} + ServicesSvcComment = &Col{T: TServices, Name: "svc_comment", Nullable: true} + ServicesSvcApp = &Col{T: TServices, Name: "svc_app", Nullable: true} + ServicesSvcDrnoaction = &Col{T: TServices, Name: "svc_drnoaction", Nullable: true} + ServicesSvcCreated = &Col{T: TServices, Name: "svc_created", Nullable: false} + ServicesSvcConfigUpdated = &Col{T: TServices, Name: "svc_config_updated", Nullable: true} + ServicesSvcMetrocluster = &Col{T: TServices, Name: "svc_metrocluster", Nullable: true} + ServicesID = &Col{T: TServices, Name: "id", Nullable: false} + ServicesSvcWave = &Col{T: TServices, Name: "svc_wave", Nullable: false} + ServicesSvcConfig = &Col{T: TServices, Name: "svc_config", Nullable: true} + ServicesUpdated = &Col{T: TServices, Name: "updated", Nullable: true} + ServicesSvcTopology = &Col{T: TServices, Name: "svc_topology", Nullable: true} + ServicesSvcFlexMinNodes = &Col{T: TServices, Name: "svc_flex_min_nodes", Nullable: true} + ServicesSvcFlexMaxNodes = &Col{T: TServices, Name: "svc_flex_max_nodes", Nullable: true} + ServicesSvcFlexCPULowThreshold = &Col{T: TServices, Name: "svc_flex_cpu_low_threshold", Nullable: true} + ServicesSvcFlexCPUHighThreshold = &Col{T: TServices, Name: "svc_flex_cpu_high_threshold", Nullable: true} + ServicesSvcStatus = &Col{T: TServices, Name: "svc_status", Nullable: true} + ServicesSvcAvailstatus = &Col{T: TServices, Name: "svc_availstatus", Nullable: true} + ServicesSvcHa = &Col{T: TServices, Name: "svc_ha", Nullable: true} + ServicesSvcStatusUpdated = &Col{T: TServices, Name: "svc_status_updated", Nullable: true} + ServicesSvcID = &Col{T: TServices, Name: "svc_id", Nullable: true} + ServicesSvcFrozen = &Col{T: TServices, Name: "svc_frozen", Nullable: true} + ServicesSvcProvisioned = &Col{T: TServices, Name: "svc_provisioned", Nullable: true} + ServicesSvcPlacement = &Col{T: TServices, Name: "svc_placement", Nullable: true} + ServicesSvcNotifications = &Col{T: TServices, Name: "svc_notifications", Nullable: true} + ServicesSvcSnoozeTill = &Col{T: TServices, Name: "svc_snooze_till", Nullable: true} + ServicesClusterID = &Col{T: TServices, Name: "cluster_id", Nullable: true} + ServicesSvcFlexTarget = &Col{T: TServices, Name: "svc_flex_target", Nullable: true} +) + +// Columns of services_log +var ( + ServicesLogID = &Col{T: TServicesLog, Name: "id", Nullable: false} + ServicesLogSvcAvailstatus = &Col{T: TServicesLog, Name: "svc_availstatus", Nullable: false} + ServicesLogSvcBegin = &Col{T: TServicesLog, Name: "svc_begin", Nullable: false} + ServicesLogSvcEnd = &Col{T: TServicesLog, Name: "svc_end", Nullable: false} + ServicesLogSvcID = &Col{T: TServicesLog, Name: "svc_id", Nullable: true} +) + +// Columns of services_log_last +var ( + ServicesLogLastID = &Col{T: TServicesLogLast, Name: "id", Nullable: false} + ServicesLogLastSvcAvailstatus = &Col{T: TServicesLogLast, Name: "svc_availstatus", Nullable: false} + ServicesLogLastSvcBegin = &Col{T: TServicesLogLast, Name: "svc_begin", Nullable: false} + ServicesLogLastSvcEnd = &Col{T: TServicesLogLast, Name: "svc_end", Nullable: false} + ServicesLogLastSvcID = &Col{T: TServicesLogLast, Name: "svc_id", Nullable: true} +) + +// Columns of services_test +var ( + ServicesTestName = &Col{T: TServicesTest, Name: "name", Nullable: true} + ServicesTestApp = &Col{T: TServicesTest, Name: "app", Nullable: true} + ServicesTestID = &Col{T: TServicesTest, Name: "id", Nullable: false} + ServicesTestSvcID = &Col{T: TServicesTest, Name: "svc_id", Nullable: true} +) + +// Columns of service_ids +var ( + ServiceIdsSvcname = &Col{T: TServiceIds, Name: "svcname", Nullable: true} + ServiceIdsClusterID = &Col{T: TServiceIds, Name: "cluster_id", Nullable: true} + ServiceIdsID = &Col{T: TServiceIds, Name: "id", Nullable: false} + ServiceIdsSvcID = &Col{T: TServiceIds, Name: "svc_id", Nullable: true} +) + +// Columns of stats_compare +var ( + StatsCompareID = &Col{T: TStatsCompare, Name: "id", Nullable: false} + StatsCompareName = &Col{T: TStatsCompare, Name: "name", Nullable: false} +) + +// Columns of stats_compare_fset +var ( + StatsCompareFsetID = &Col{T: TStatsCompareFset, Name: "id", Nullable: false} + StatsCompareFsetCompareID = &Col{T: TStatsCompareFset, Name: "compare_id", Nullable: false} + StatsCompareFsetFsetID = &Col{T: TStatsCompareFset, Name: "fset_id", Nullable: false} +) + +// Columns of stats_compare_user +var ( + StatsCompareUserID = &Col{T: TStatsCompareUser, Name: "id", Nullable: false} + StatsCompareUserCompareID = &Col{T: TStatsCompareUser, Name: "compare_id", Nullable: false} + StatsCompareUserUserID = &Col{T: TStatsCompareUser, Name: "user_id", Nullable: false} +) + +// Columns of stat_day +var ( + StatDayID = &Col{T: TStatDay, Name: "id", Nullable: false} + StatDayDay = &Col{T: TStatDay, Name: "day", Nullable: false} + StatDayNbSvc = &Col{T: TStatDay, Name: "nb_svc", Nullable: false} + StatDayNbAction = &Col{T: TStatDay, Name: "nb_action", Nullable: false} + StatDayNbActionErr = &Col{T: TStatDay, Name: "nb_action_err", Nullable: false} + StatDayNbActionWarn = &Col{T: TStatDay, Name: "nb_action_warn", Nullable: false} + StatDayNbActionOk = &Col{T: TStatDay, Name: "nb_action_ok", Nullable: false} + StatDayDiskSize = &Col{T: TStatDay, Name: "disk_size", Nullable: false} + StatDayRamSize = &Col{T: TStatDay, Name: "ram_size", Nullable: true} + StatDayNbCPUCore = &Col{T: TStatDay, Name: "nb_cpu_core", Nullable: true} + StatDayNbCPUDie = &Col{T: TStatDay, Name: "nb_cpu_die", Nullable: true} + StatDayWatt = &Col{T: TStatDay, Name: "watt", Nullable: true} + StatDayRackunit = &Col{T: TStatDay, Name: "rackunit", Nullable: true} + StatDayNbApps = &Col{T: TStatDay, Name: "nb_apps", Nullable: false} + StatDayNbAccounts = &Col{T: TStatDay, Name: "nb_accounts", Nullable: false} + StatDayNbSvcWithDRP = &Col{T: TStatDay, Name: "nb_svc_with_drp", Nullable: false} + StatDayNbNodes = &Col{T: TStatDay, Name: "nb_nodes", Nullable: false} + StatDayNbSvcPrd = &Col{T: TStatDay, Name: "nb_svc_prd", Nullable: true} + StatDayNbSvcCluster = &Col{T: TStatDay, Name: "nb_svc_cluster", Nullable: true} + StatDayNbNodesPrd = &Col{T: TStatDay, Name: "nb_nodes_prd", Nullable: true} + StatDayFsetID = &Col{T: TStatDay, Name: "fset_id", Nullable: true} + StatDayNbVcpu = &Col{T: TStatDay, Name: "nb_vcpu", Nullable: true} + StatDayNbVmem = &Col{T: TStatDay, Name: "nb_vmem", Nullable: true} + StatDayNbRespAccounts = &Col{T: TStatDay, Name: "nb_resp_accounts", Nullable: true} + StatDayNbVirtNodes = &Col{T: TStatDay, Name: "nb_virt_nodes", Nullable: true} + StatDayLocalDiskSize = &Col{T: TStatDay, Name: "local_disk_size", Nullable: false} +) + +// Columns of stat_day_disk_app +var ( + StatDayDiskAppID = &Col{T: TStatDayDiskApp, Name: "id", Nullable: false} + StatDayDiskAppDay = &Col{T: TStatDayDiskApp, Name: "day", Nullable: false} + StatDayDiskAppApp = &Col{T: TStatDayDiskApp, Name: "app", Nullable: false} + StatDayDiskAppDiskUsed = &Col{T: TStatDayDiskApp, Name: "disk_used", Nullable: true} + StatDayDiskAppQuota = &Col{T: TStatDayDiskApp, Name: "quota", Nullable: true} +) + +// Columns of stat_day_disk_app_dg +var ( + StatDayDiskAppDGID = &Col{T: TStatDayDiskAppDG, Name: "id", Nullable: false} + StatDayDiskAppDGDay = &Col{T: TStatDayDiskAppDG, Name: "day", Nullable: false} + StatDayDiskAppDGDGID = &Col{T: TStatDayDiskAppDG, Name: "dg_id", Nullable: false} + StatDayDiskAppDGApp = &Col{T: TStatDayDiskAppDG, Name: "app", Nullable: false} + StatDayDiskAppDGDiskUsed = &Col{T: TStatDayDiskAppDG, Name: "disk_used", Nullable: true} + StatDayDiskAppDGQuota = &Col{T: TStatDayDiskAppDG, Name: "quota", Nullable: true} +) + +// Columns of stat_day_disk_array +var ( + StatDayDiskArrayID = &Col{T: TStatDayDiskArray, Name: "id", Nullable: false} + StatDayDiskArrayDay = &Col{T: TStatDayDiskArray, Name: "day", Nullable: false} + StatDayDiskArrayArrayName = &Col{T: TStatDayDiskArray, Name: "array_name", Nullable: false} + StatDayDiskArrayDiskUsed = &Col{T: TStatDayDiskArray, Name: "disk_used", Nullable: true} + StatDayDiskArrayDiskSize = &Col{T: TStatDayDiskArray, Name: "disk_size", Nullable: true} + StatDayDiskArrayReservable = &Col{T: TStatDayDiskArray, Name: "reservable", Nullable: true} + StatDayDiskArrayReserved = &Col{T: TStatDayDiskArray, Name: "reserved", Nullable: true} +) + +// Columns of stat_day_disk_array_dg +var ( + StatDayDiskArrayDGID = &Col{T: TStatDayDiskArrayDG, Name: "id", Nullable: false} + StatDayDiskArrayDGDay = &Col{T: TStatDayDiskArrayDG, Name: "day", Nullable: false} + StatDayDiskArrayDGArrayName = &Col{T: TStatDayDiskArrayDG, Name: "array_name", Nullable: false} + StatDayDiskArrayDGArrayDG = &Col{T: TStatDayDiskArrayDG, Name: "array_dg", Nullable: false} + StatDayDiskArrayDGDiskUsed = &Col{T: TStatDayDiskArrayDG, Name: "disk_used", Nullable: true} + StatDayDiskArrayDGDiskSize = &Col{T: TStatDayDiskArrayDG, Name: "disk_size", Nullable: true} + StatDayDiskArrayDGReserved = &Col{T: TStatDayDiskArrayDG, Name: "reserved", Nullable: true} + StatDayDiskArrayDGReservable = &Col{T: TStatDayDiskArrayDG, Name: "reservable", Nullable: true} +) + +// Columns of stat_day_svc +var ( + StatDaySvcID = &Col{T: TStatDaySvc, Name: "id", Nullable: false} + StatDaySvcDay = &Col{T: TStatDaySvc, Name: "day", Nullable: false} + StatDaySvcNbAction = &Col{T: TStatDaySvc, Name: "nb_action", Nullable: true} + StatDaySvcNbActionErr = &Col{T: TStatDaySvc, Name: "nb_action_err", Nullable: true} + StatDaySvcNbActionWarn = &Col{T: TStatDaySvc, Name: "nb_action_warn", Nullable: true} + StatDaySvcNbActionOk = &Col{T: TStatDaySvc, Name: "nb_action_ok", Nullable: true} + StatDaySvcDiskSize = &Col{T: TStatDaySvc, Name: "disk_size", Nullable: true} + StatDaySvcRamSize = &Col{T: TStatDaySvc, Name: "ram_size", Nullable: true} + StatDaySvcNbCPUCore = &Col{T: TStatDaySvc, Name: "nb_cpu_core", Nullable: true} + StatDaySvcNbCPUDie = &Col{T: TStatDaySvc, Name: "nb_cpu_die", Nullable: true} + StatDaySvcWatt = &Col{T: TStatDaySvc, Name: "watt", Nullable: true} + StatDaySvcRackunit = &Col{T: TStatDaySvc, Name: "rackunit", Nullable: true} + StatDaySvcNbNodes = &Col{T: TStatDaySvc, Name: "nb_nodes", Nullable: true} + StatDaySvcLocalDiskSize = &Col{T: TStatDaySvc, Name: "local_disk_size", Nullable: true} + StatDaySvcSvcID = &Col{T: TStatDaySvc, Name: "svc_id", Nullable: true} +) + +// Columns of stor_array +var ( + StorArrayID = &Col{T: TStorArray, Name: "id", Nullable: false} + StorArrayArrayName = &Col{T: TStorArray, Name: "array_name", Nullable: true} + StorArrayArrayModel = &Col{T: TStorArray, Name: "array_model", Nullable: false} + StorArrayArrayCache = &Col{T: TStorArray, Name: "array_cache", Nullable: true} + StorArrayArrayFirmware = &Col{T: TStorArray, Name: "array_firmware", Nullable: true} + StorArrayArrayUpdated = &Col{T: TStorArray, Name: "array_updated", Nullable: false} + StorArrayArrayLevel = &Col{T: TStorArray, Name: "array_level", Nullable: false} + StorArrayArrayComment = &Col{T: TStorArray, Name: "array_comment", Nullable: true} +) + +// Columns of stor_array_dg +var ( + StorArrayDGID = &Col{T: TStorArrayDG, Name: "id", Nullable: false} + StorArrayDGArrayID = &Col{T: TStorArrayDG, Name: "array_id", Nullable: false} + StorArrayDGDGName = &Col{T: TStorArrayDG, Name: "dg_name", Nullable: false} + StorArrayDGDGFree = &Col{T: TStorArrayDG, Name: "dg_free", Nullable: false} + StorArrayDGDGUpdated = &Col{T: TStorArrayDG, Name: "dg_updated", Nullable: false} + StorArrayDGDGSize = &Col{T: TStorArrayDG, Name: "dg_size", Nullable: true} + StorArrayDGDGUsed = &Col{T: TStorArrayDG, Name: "dg_used", Nullable: true} + StorArrayDGDGReserved = &Col{T: TStorArrayDG, Name: "dg_reserved", Nullable: true} +) + +// Columns of stor_array_dg_quota +var ( + StorArrayDGQuotaID = &Col{T: TStorArrayDGQuota, Name: "id", Nullable: false} + StorArrayDGQuotaDGID = &Col{T: TStorArrayDGQuota, Name: "dg_id", Nullable: false} + StorArrayDGQuotaAppID = &Col{T: TStorArrayDGQuota, Name: "app_id", Nullable: false} + StorArrayDGQuotaQuota = &Col{T: TStorArrayDGQuota, Name: "quota", Nullable: true} +) + +// Columns of stor_array_proxy +var ( + StorArrayProxyID = &Col{T: TStorArrayProxy, Name: "id", Nullable: false} + StorArrayProxyArrayID = &Col{T: TStorArrayProxy, Name: "array_id", Nullable: false} + StorArrayProxyNodeID = &Col{T: TStorArrayProxy, Name: "node_id", Nullable: true} +) + +// Columns of stor_array_tgtid +var ( + StorArrayTgtidID = &Col{T: TStorArrayTgtid, Name: "id", Nullable: false} + StorArrayTgtidArrayID = &Col{T: TStorArrayTgtid, Name: "array_id", Nullable: false} + StorArrayTgtidArrayTgtid = &Col{T: TStorArrayTgtid, Name: "array_tgtid", Nullable: false} + StorArrayTgtidUpdated = &Col{T: TStorArrayTgtid, Name: "updated", Nullable: false} +) + +// Columns of stor_zone +var ( + StorZoneID = &Col{T: TStorZone, Name: "id", Nullable: false} + StorZoneTgtID = &Col{T: TStorZone, Name: "tgt_id", Nullable: false} + StorZoneHBAID = &Col{T: TStorZone, Name: "hba_id", Nullable: false} + StorZoneUpdated = &Col{T: TStorZone, Name: "updated", Nullable: false} + StorZoneNodeID = &Col{T: TStorZone, Name: "node_id", Nullable: true} +) + +// Columns of svcactions +var ( + SvcactionsAction = &Col{T: TSvcactions, Name: "action", Nullable: true} + SvcactionsStatus = &Col{T: TSvcactions, Name: "status", Nullable: true} + SvcactionsBegin = &Col{T: TSvcactions, Name: "begin", Nullable: false} + SvcactionsEnd = &Col{T: TSvcactions, Name: "end", Nullable: true} + SvcactionsHostid = &Col{T: TSvcactions, Name: "hostid", Nullable: true} + SvcactionsStatusLog = &Col{T: TSvcactions, Name: "status_log", Nullable: true} + SvcactionsPid = &Col{T: TSvcactions, Name: "pid", Nullable: true} + SvcactionsID = &Col{T: TSvcactions, Name: "id", Nullable: false} + SvcactionsAck = &Col{T: TSvcactions, Name: "ack", Nullable: true} + SvcactionsAlert = &Col{T: TSvcactions, Name: "alert", Nullable: true} + SvcactionsAckedBy = &Col{T: TSvcactions, Name: "acked_by", Nullable: true} + SvcactionsAckedComment = &Col{T: TSvcactions, Name: "acked_comment", Nullable: true} + SvcactionsAckedDate = &Col{T: TSvcactions, Name: "acked_date", Nullable: true} + SvcactionsVersion = &Col{T: TSvcactions, Name: "version", Nullable: true} + SvcactionsCron = &Col{T: TSvcactions, Name: "cron", Nullable: true} + SvcactionsTime = &Col{T: TSvcactions, Name: "time", Nullable: true} + SvcactionsNodeID = &Col{T: TSvcactions, Name: "node_id", Nullable: true} + SvcactionsSvcID = &Col{T: TSvcactions, Name: "svc_id", Nullable: true} + SvcactionsSid = &Col{T: TSvcactions, Name: "sid", Nullable: true} + SvcactionsRid = &Col{T: TSvcactions, Name: "rid", Nullable: true} + SvcactionsSubset = &Col{T: TSvcactions, Name: "subset", Nullable: true} +) + +// Columns of svcdisks +var ( + SvcdisksID = &Col{T: TSvcdisks, Name: "id", Nullable: false} + SvcdisksDiskID = &Col{T: TSvcdisks, Name: "disk_id", Nullable: true} + SvcdisksDiskSize = &Col{T: TSvcdisks, Name: "disk_size", Nullable: false} + SvcdisksDiskVendor = &Col{T: TSvcdisks, Name: "disk_vendor", Nullable: true} + SvcdisksDiskModel = &Col{T: TSvcdisks, Name: "disk_model", Nullable: true} + SvcdisksDiskDG = &Col{T: TSvcdisks, Name: "disk_dg", Nullable: true} + SvcdisksDiskUpdated = &Col{T: TSvcdisks, Name: "disk_updated", Nullable: false} + SvcdisksDiskLocal = &Col{T: TSvcdisks, Name: "disk_local", Nullable: true} + SvcdisksDiskUsed = &Col{T: TSvcdisks, Name: "disk_used", Nullable: false} + SvcdisksDiskRegion = &Col{T: TSvcdisks, Name: "disk_region", Nullable: true} + SvcdisksAppID = &Col{T: TSvcdisks, Name: "app_id", Nullable: true} + SvcdisksNodeID = &Col{T: TSvcdisks, Name: "node_id", Nullable: true} + SvcdisksSvcID = &Col{T: TSvcdisks, Name: "svc_id", Nullable: true} +) + +// Columns of svcmon +var ( + SvcmonMonSvctype = &Col{T: TSvcmon, Name: "mon_svctype", Nullable: true} + SvcmonMonIpstatus = &Col{T: TSvcmon, Name: "mon_ipstatus", Nullable: true} + SvcmonMonFsstatus = &Col{T: TSvcmon, Name: "mon_fsstatus", Nullable: true} + SvcmonMonUpdated = &Col{T: TSvcmon, Name: "mon_updated", Nullable: true} + SvcmonID = &Col{T: TSvcmon, Name: "ID", Nullable: false} + SvcmonMonFrozen = &Col{T: TSvcmon, Name: "mon_frozen", Nullable: true} + SvcmonMonChanged = &Col{T: TSvcmon, Name: "mon_changed", Nullable: false} + SvcmonMonDiskstatus = &Col{T: TSvcmon, Name: "mon_diskstatus", Nullable: true} + SvcmonMonContainerstatus = &Col{T: TSvcmon, Name: "mon_containerstatus", Nullable: true} + SvcmonMonOverallstatus = &Col{T: TSvcmon, Name: "mon_overallstatus", Nullable: true} + SvcmonMonSyncstatus = &Col{T: TSvcmon, Name: "mon_syncstatus", Nullable: true} + SvcmonMonAppstatus = &Col{T: TSvcmon, Name: "mon_appstatus", Nullable: true} + SvcmonMonHbstatus = &Col{T: TSvcmon, Name: "mon_hbstatus", Nullable: true} + SvcmonMonAvailstatus = &Col{T: TSvcmon, Name: "mon_availstatus", Nullable: true} + SvcmonMonVmname = &Col{T: TSvcmon, Name: "mon_vmname", Nullable: true} + SvcmonMonGuestos = &Col{T: TSvcmon, Name: "mon_guestos", Nullable: true} + SvcmonMonVmem = &Col{T: TSvcmon, Name: "mon_vmem", Nullable: true} + SvcmonMonVcpus = &Col{T: TSvcmon, Name: "mon_vcpus", Nullable: true} + SvcmonMonContainerpath = &Col{T: TSvcmon, Name: "mon_containerpath", Nullable: true} + SvcmonMonVmtype = &Col{T: TSvcmon, Name: "mon_vmtype", Nullable: true} + SvcmonMonSharestatus = &Col{T: TSvcmon, Name: "mon_sharestatus", Nullable: true} + SvcmonNodeID = &Col{T: TSvcmon, Name: "node_id", Nullable: true} + SvcmonSvcID = &Col{T: TSvcmon, Name: "svc_id", Nullable: true} + SvcmonMonSmonStatus = &Col{T: TSvcmon, Name: "mon_smon_status", Nullable: true} + SvcmonMonSmonGlobalExpect = &Col{T: TSvcmon, Name: "mon_smon_global_expect", Nullable: true} + SvcmonMonFrozenAt = &Col{T: TSvcmon, Name: "mon_frozen_at", Nullable: true} + SvcmonMonEncapFrozenAt = &Col{T: TSvcmon, Name: "mon_encap_frozen_at", Nullable: true} +) + +// Columns of svcmon_log +var ( + SvcmonLogID = &Col{T: TSvcmonLog, Name: "id", Nullable: false} + SvcmonLogMonOverallstatus = &Col{T: TSvcmonLog, Name: "mon_overallstatus", Nullable: true} + SvcmonLogMonIpstatus = &Col{T: TSvcmonLog, Name: "mon_ipstatus", Nullable: true} + SvcmonLogMonFsstatus = &Col{T: TSvcmonLog, Name: "mon_fsstatus", Nullable: true} + SvcmonLogMonDiskstatus = &Col{T: TSvcmonLog, Name: "mon_diskstatus", Nullable: true} + SvcmonLogMonContainerstatus = &Col{T: TSvcmonLog, Name: "mon_containerstatus", Nullable: true} + SvcmonLogMonSyncstatus = &Col{T: TSvcmonLog, Name: "mon_syncstatus", Nullable: true} + SvcmonLogMonAppstatus = &Col{T: TSvcmonLog, Name: "mon_appstatus", Nullable: true} + SvcmonLogMonBegin = &Col{T: TSvcmonLog, Name: "mon_begin", Nullable: false} + SvcmonLogMonEnd = &Col{T: TSvcmonLog, Name: "mon_end", Nullable: false} + SvcmonLogMonHbstatus = &Col{T: TSvcmonLog, Name: "mon_hbstatus", Nullable: true} + SvcmonLogMonAvailstatus = &Col{T: TSvcmonLog, Name: "mon_availstatus", Nullable: true} + SvcmonLogMonSharestatus = &Col{T: TSvcmonLog, Name: "mon_sharestatus", Nullable: true} + SvcmonLogNodeID = &Col{T: TSvcmonLog, Name: "node_id", Nullable: true} + SvcmonLogSvcID = &Col{T: TSvcmonLog, Name: "svc_id", Nullable: true} +) + +// Columns of svcmon_log_ack +var ( + SvcmonLogAckID = &Col{T: TSvcmonLogAck, Name: "id", Nullable: false} + SvcmonLogAckMonBegin = &Col{T: TSvcmonLogAck, Name: "mon_begin", Nullable: false} + SvcmonLogAckMonEnd = &Col{T: TSvcmonLogAck, Name: "mon_end", Nullable: false} + SvcmonLogAckMonComment = &Col{T: TSvcmonLogAck, Name: "mon_comment", Nullable: false} + SvcmonLogAckMonAckedBy = &Col{T: TSvcmonLogAck, Name: "mon_acked_by", Nullable: false} + SvcmonLogAckMonAckedOn = &Col{T: TSvcmonLogAck, Name: "mon_acked_on", Nullable: false} + SvcmonLogAckMonAccount = &Col{T: TSvcmonLogAck, Name: "mon_account", Nullable: false} + SvcmonLogAckSvcID = &Col{T: TSvcmonLogAck, Name: "svc_id", Nullable: true} +) + +// Columns of svcmon_log_last +var ( + SvcmonLogLastID = &Col{T: TSvcmonLogLast, Name: "id", Nullable: false} + SvcmonLogLastMonOverallstatus = &Col{T: TSvcmonLogLast, Name: "mon_overallstatus", Nullable: true} + SvcmonLogLastMonIpstatus = &Col{T: TSvcmonLogLast, Name: "mon_ipstatus", Nullable: true} + SvcmonLogLastMonFsstatus = &Col{T: TSvcmonLogLast, Name: "mon_fsstatus", Nullable: true} + SvcmonLogLastMonDiskstatus = &Col{T: TSvcmonLogLast, Name: "mon_diskstatus", Nullable: true} + SvcmonLogLastMonContainerstatus = &Col{T: TSvcmonLogLast, Name: "mon_containerstatus", Nullable: true} + SvcmonLogLastMonSyncstatus = &Col{T: TSvcmonLogLast, Name: "mon_syncstatus", Nullable: true} + SvcmonLogLastMonAppstatus = &Col{T: TSvcmonLogLast, Name: "mon_appstatus", Nullable: true} + SvcmonLogLastMonBegin = &Col{T: TSvcmonLogLast, Name: "mon_begin", Nullable: false} + SvcmonLogLastMonEnd = &Col{T: TSvcmonLogLast, Name: "mon_end", Nullable: false} + SvcmonLogLastMonHbstatus = &Col{T: TSvcmonLogLast, Name: "mon_hbstatus", Nullable: true} + SvcmonLogLastMonAvailstatus = &Col{T: TSvcmonLogLast, Name: "mon_availstatus", Nullable: true} + SvcmonLogLastMonSharestatus = &Col{T: TSvcmonLogLast, Name: "mon_sharestatus", Nullable: true} + SvcmonLogLastNodeID = &Col{T: TSvcmonLogLast, Name: "node_id", Nullable: true} + SvcmonLogLastSvcID = &Col{T: TSvcmonLogLast, Name: "svc_id", Nullable: true} +) + +// Columns of svc_tags +var ( + SvcTagsID = &Col{T: TSvcTags, Name: "id", Nullable: false} + SvcTagsCreated = &Col{T: TSvcTags, Name: "created", Nullable: true} + SvcTagsSvcID = &Col{T: TSvcTags, Name: "svc_id", Nullable: true} + SvcTagsTagID = &Col{T: TSvcTags, Name: "tag_id", Nullable: true} + SvcTagsTagAttachData = &Col{T: TSvcTags, Name: "tag_attach_data", Nullable: true} +) + +// Columns of switches +var ( + SwitchesID = &Col{T: TSwitches, Name: "id", Nullable: false} + SwitchesSwName = &Col{T: TSwitches, Name: "sw_name", Nullable: false} + SwitchesSwSlot = &Col{T: TSwitches, Name: "sw_slot", Nullable: true} + SwitchesSwPort = &Col{T: TSwitches, Name: "sw_port", Nullable: true} + SwitchesSwPortspeed = &Col{T: TSwitches, Name: "sw_portspeed", Nullable: true} + SwitchesSwPortnego = &Col{T: TSwitches, Name: "sw_portnego", Nullable: true} + SwitchesSwPorttype = &Col{T: TSwitches, Name: "sw_porttype", Nullable: true} + SwitchesSwPortstate = &Col{T: TSwitches, Name: "sw_portstate", Nullable: true} + SwitchesSwPortname = &Col{T: TSwitches, Name: "sw_portname", Nullable: true} + SwitchesSwRportname = &Col{T: TSwitches, Name: "sw_rportname", Nullable: true} + SwitchesSwUpdated = &Col{T: TSwitches, Name: "sw_updated", Nullable: false} + SwitchesSwFabric = &Col{T: TSwitches, Name: "sw_fabric", Nullable: true} + SwitchesSwIndex = &Col{T: TSwitches, Name: "sw_index", Nullable: true} +) + +// Columns of sysrep_allow +var ( + SysrepAllowID = &Col{T: TSysrepAllow, Name: "id", Nullable: false} + SysrepAllowPattern = &Col{T: TSysrepAllow, Name: "pattern", Nullable: false} + SysrepAllowFsetID = &Col{T: TSysrepAllow, Name: "fset_id", Nullable: false} + SysrepAllowGroupID = &Col{T: TSysrepAllow, Name: "group_id", Nullable: false} +) + +// Columns of sysrep_changing +var ( + SysrepChangingID = &Col{T: TSysrepChanging, Name: "id", Nullable: false} + SysrepChangingPattern = &Col{T: TSysrepChanging, Name: "pattern", Nullable: false} +) + +// Columns of sysrep_secure +var ( + SysrepSecureID = &Col{T: TSysrepSecure, Name: "id", Nullable: false} + SysrepSecurePattern = &Col{T: TSysrepSecure, Name: "pattern", Nullable: false} +) + +// Columns of table_modified +var ( + TableModifiedID = &Col{T: TTableModified, Name: "id", Nullable: false} + TableModifiedTableName = &Col{T: TTableModified, Name: "table_name", Nullable: false} + TableModifiedTableModified = &Col{T: TTableModified, Name: "table_modified", Nullable: true} +) + +// Columns of tags +var ( + TagsID = &Col{T: TTags, Name: "id", Nullable: false} + TagsTagName = &Col{T: TTags, Name: "tag_name", Nullable: true} + TagsTagCreated = &Col{T: TTags, Name: "tag_created", Nullable: false} + TagsTagExclude = &Col{T: TTags, Name: "tag_exclude", Nullable: true} + TagsTagData = &Col{T: TTags, Name: "tag_data", Nullable: true} + TagsTagID = &Col{T: TTags, Name: "tag_id", Nullable: true} +) + +// Columns of tmp +var ( + TmpID = &Col{T: TTmp, Name: "id", Nullable: false} +) + +// Columns of tmpmd5 +var ( + Tmpmd5RsetMD5 = &Col{T: TTmpmd5, Name: "rset_md5", Nullable: true} +) + +// Columns of user_log +var ( + UserLogUserID = &Col{T: TUserLog, Name: "user_id", Nullable: false} + UserLogLogID = &Col{T: TUserLog, Name: "log_id", Nullable: true} +) + +// Columns of user_prefs +var ( + UserPrefsID = &Col{T: TUserPrefs, Name: "id", Nullable: false} + UserPrefsUserID = &Col{T: TUserPrefs, Name: "user_id", Nullable: false} + UserPrefsPrefs = &Col{T: TUserPrefs, Name: "prefs", Nullable: false} +) + +// Columns of u_inc +var ( + UIncInc = &Col{T: TUInc, Name: "inc", Nullable: true} +) + +// Columns of wiki_pages +var ( + WikiPagesID = &Col{T: TWikiPages, Name: "id", Nullable: false} + WikiPagesName = &Col{T: TWikiPages, Name: "name", Nullable: true} + WikiPagesAuthor = &Col{T: TWikiPages, Name: "author", Nullable: true} + WikiPagesSavedOn = &Col{T: TWikiPages, Name: "saved_on", Nullable: true} + WikiPagesTitle = &Col{T: TWikiPages, Name: "title", Nullable: true} + WikiPagesBody = &Col{T: TWikiPages, Name: "body", Nullable: true} + WikiPagesChangeNote = &Col{T: TWikiPages, Name: "change_note", Nullable: true} +) + +// Columns of workflows +var ( + WorkflowsID = &Col{T: TWorkflows, Name: "id", Nullable: false} + WorkflowsFormHeadID = &Col{T: TWorkflows, Name: "form_head_id", Nullable: false} + WorkflowsStatus = &Col{T: TWorkflows, Name: "status", Nullable: false} + WorkflowsSteps = &Col{T: TWorkflows, Name: "steps", Nullable: false} + WorkflowsCreator = &Col{T: TWorkflows, Name: "creator", Nullable: false} + WorkflowsCreateDate = &Col{T: TWorkflows, Name: "create_date", Nullable: false} + WorkflowsLastAssignee = &Col{T: TWorkflows, Name: "last_assignee", Nullable: false} + WorkflowsLastUpdate = &Col{T: TWorkflows, Name: "last_update", Nullable: false} + WorkflowsFormMD5 = &Col{T: TWorkflows, Name: "form_md5", Nullable: true} + WorkflowsLastFormID = &Col{T: TWorkflows, Name: "last_form_id", Nullable: true} + WorkflowsLastFormName = &Col{T: TWorkflows, Name: "last_form_name", Nullable: true} +) + +// AllCols is the full column registry used for join resolution. +var AllCols = []*Col{ + ActionQueueID, + ActionQueueStatus, + ActionQueueCommand, + ActionQueueDateQueued, + ActionQueueDateDequeued, + ActionQueueRet, + ActionQueueStdout, + ActionQueueStderr, + ActionQueueActionType, + ActionQueueUserID, + ActionQueueFormID, + ActionQueueConnectTo, + ActionQueueNodeID, + ActionQueueSvcID, + AlertsID, + AlertsSentAt, + AlertsSentTo, + AlertsBody, + AlertsSubject, + AlertsSendAt, + AlertsCreatedAt, + AlertsActionID, + AlertsAppID, + AlertsDomain, + AlertsActionIds, + AlertsSentID, + AlertsSentAlertID, + AlertsSentMsgType, + AlertsSentUserID, + AlertsSentSent, + AppsID, + AppsApp, + AppsUpdated, + AppsAppDomain, + AppsAppTeamOps, + AppsDescription, + AppsImportID, + AppsImportApp, + AppsImportDesc, + AppsImportUpdated, + AppsImportAppDomain, + AppsImportAppTeamOps, + AppsPublicationsID, + AppsPublicationsGroupID, + AppsPublicationsAppID, + AppsResponsiblesID, + AppsResponsiblesGroupID, + AppsResponsiblesAppID, + AuthEventID, + AuthEventTimeStamp, + AuthEventClientIP, + AuthEventUserID, + AuthEventOrigin, + AuthEventDescription, + AuthFiltersID, + AuthFiltersFilUid, + AuthFiltersFilID, + AuthFiltersFilValue, + AuthFiltersFilActive, + AuthGroupID, + AuthGroupRole, + AuthGroupDescription, + AuthGroupPrivilege, + AuthMembershipID, + AuthMembershipUserID, + AuthMembershipGroupID, + AuthMembershipPrimaryGroup, + AuthNodeID, + AuthNodeNodename, + AuthNodeUuid, + AuthNodeUpdated, + AuthNodeNodeID, + AuthPermissionID, + AuthPermissionGroupID, + AuthPermissionName, + AuthPermissionTableName, + AuthPermissionRecordID, + AuthUserID, + AuthUserFirstName, + AuthUserLastName, + AuthUserEmail, + AuthUserPassword, + AuthUserRegistrationKey, + AuthUserResetPasswordKey, + AuthUserEmailNotifications, + AuthUserImNotifications, + AuthUserImType, + AuthUserImUsername, + AuthUserEmailLogLevel, + AuthUserImLogLevel, + AuthUserLockFilter, + AuthUserPhoneWork, + AuthUserRegistrationID, + AuthUserQuotaApp, + AuthUserQuotaOrgGroup, + AuthUserUsername, + AuthUserQuotaDockerRegistries, + AuthUserImNotificationsDelay, + AuthUserEmailNotificationsDelay, + BActionErrorsID, + BActionErrorsSvcID, + BActionErrorsNodeID, + BActionErrorsErr, + BAppsOldID, + BAppsOldApp, + BAppsOldRoles, + BAppsOldResponsibles, + BAppsOldMailto, + BAppsOldAppDomain, + BAppsOldAppTeamOps, + ChartsID, + ChartsChartName, + ChartsChartYaml, + ChartTeamPublicationID, + ChartTeamPublicationChartID, + ChartTeamPublicationGroupID, + ChartTeamResponsibleID, + ChartTeamResponsibleChartID, + ChartTeamResponsibleGroupID, + ChecksDefaultsID, + ChecksDefaultsChkType, + ChecksDefaultsChkLow, + ChecksDefaultsChkHigh, + ChecksDefaultsChkInst, + ChecksDefaultsChkPrio, + ChecksLiveID, + ChecksLiveChkType, + ChecksLiveChkUpdated, + ChecksLiveChkValue, + ChecksLiveChkCreated, + ChecksLiveChkInstance, + ChecksLiveChkLow, + ChecksLiveChkHigh, + ChecksLiveChkThresholdProvider, + ChecksLiveChkErr, + ChecksLiveNodeID, + ChecksLiveSvcID, + ChecksSettingsID, + ChecksSettingsChkType, + ChecksSettingsChkLow, + ChecksSettingsChkHigh, + ChecksSettingsChkChanged, + ChecksSettingsChkChangedBy, + ChecksSettingsChkInstance, + ChecksSettingsNodeID, + ChecksSettingsSvcID, + ClustersID, + ClustersClusterID, + ClustersClusterName, + ClustersClusterData, + CompLogID, + CompLogRunModule, + CompLogRunStatus, + CompLogRunLog, + CompLogRunDate, + CompLogRunAction, + CompLogRsetMD5, + CompLogNodeID, + CompLogSvcID, + CompLogDailyID, + CompLogDailyRunModule, + CompLogDailyRunStatus, + CompLogDailyRunDate, + CompLogDailyNodeID, + CompLogDailySvcID, + CompModulesetID, + CompModulesetModsetName, + CompModulesetModsetAuthor, + CompModulesetModsetUpdated, + CompModulesetsServicesID, + CompModulesetsServicesModsetID, + CompModulesetsServicesModsetModAuthor, + CompModulesetsServicesModsetUpdated, + CompModulesetsServicesSlave, + CompModulesetsServicesSvcID, + CompModulesetModulesID, + CompModulesetModulesModsetID, + CompModulesetModulesModsetModName, + CompModulesetModulesModsetModAuthor, + CompModulesetModulesModsetModUpdated, + CompModulesetModulesAutofix, + CompModulesetModulesetID, + CompModulesetModulesetParentModsetID, + CompModulesetModulesetChildModsetID, + CompModulesetRulesetID, + CompModulesetRulesetModsetID, + CompModulesetRulesetRulesetID, + CompModulesetTeamPublicationID, + CompModulesetTeamPublicationModsetID, + CompModulesetTeamPublicationGroupID, + CompModulesetTeamResponsibleID, + CompModulesetTeamResponsibleModsetID, + CompModulesetTeamResponsibleGroupID, + CompModStatusID, + CompModStatusTotal, + CompModStatusOk, + CompModStatusNok, + CompModStatusNa, + CompModStatusObs, + CompModStatusPct, + CompModStatusModName, + CompNodeModulesetID, + CompNodeModulesetModsetID, + CompNodeModulesetModsetModAuthor, + CompNodeModulesetModsetUpdated, + CompNodeModulesetNodeID, + CompNodeStatusID, + CompNodeStatusTotal, + CompNodeStatusOk, + CompNodeStatusNok, + CompNodeStatusNa, + CompNodeStatusObs, + CompNodeStatusPct, + CompNodeStatusNodeName, + CompRulesetsID, + CompRulesetsRulesetName, + CompRulesetsRulesetType, + CompRulesetsRulesetPublic, + CompRulesetsChainsID, + CompRulesetsChainsHeadRsetID, + CompRulesetsChainsTailRsetID, + CompRulesetsChainsChainLen, + CompRulesetsChainsChain, + CompRulesetsFiltersetsID, + CompRulesetsFiltersetsRulesetID, + CompRulesetsFiltersetsFsetID, + CompRulesetsNodesID, + CompRulesetsNodesRulesetID, + CompRulesetsNodesNodeID, + CompRulesetsRulesetsID, + CompRulesetsRulesetsParentRsetID, + CompRulesetsRulesetsChildRsetID, + CompRulesetsServicesID, + CompRulesetsServicesRulesetID, + CompRulesetsServicesSlave, + CompRulesetsServicesSvcID, + CompRulesetsVariablesID, + CompRulesetsVariablesRulesetID, + CompRulesetsVariablesVarName, + CompRulesetsVariablesVarValue, + CompRulesetsVariablesVarAuthor, + CompRulesetsVariablesVarUpdated, + CompRulesetsVariablesVarClass, + CompRulesetTeamPublicationID, + CompRulesetTeamPublicationRulesetID, + CompRulesetTeamPublicationGroupID, + CompRulesetTeamResponsibleID, + CompRulesetTeamResponsibleRulesetID, + CompRulesetTeamResponsibleGroupID, + CompRunRulesetID, + CompRunRulesetRsetMD5, + CompRunRulesetRset, + CompRunRulesetDate, + CompStatusID, + CompStatusRunModule, + CompStatusRunStatus, + CompStatusRunLog, + CompStatusRunDate, + CompStatusRunAction, + CompStatusRsetMD5, + CompStatusNodeID, + CompStatusSvcID, + CompSvcStatusID, + CompSvcStatusTotal, + CompSvcStatusOk, + CompSvcStatusNok, + CompSvcStatusNa, + CompSvcStatusObs, + CompSvcStatusPct, + CompSvcStatusSvcName, + DashboardID, + DashboardDashType, + DashboardSvcID, + DashboardDashSeverity, + DashboardDashFmt, + DashboardDashDict, + DashboardDashCreated, + DashboardDashDictMD5, + DashboardDashEnv, + DashboardDashUpdated, + DashboardNodeID, + DashboardDashMD5, + DashboardDashInstance, + DashboardEventsID, + DashboardEventsSvcID, + DashboardEventsDashMD5, + DashboardEventsDashBegin, + DashboardEventsDashEnd, + DashboardEventsNodeID, + DashboardRefID, + DashboardRefDashMD5, + DashboardRefDashType, + DashboardRefDashFmt, + DashboardRefDashDict, + DigitI, + DiskinfoID, + DiskinfoDiskID, + DiskinfoDiskDevid, + DiskinfoDiskArrayid, + DiskinfoDiskUpdated, + DiskinfoDiskRaid, + DiskinfoDiskSize, + DiskinfoDiskGroup, + DiskinfoDiskLevel, + DiskinfoDiskController, + DiskinfoDiskName, + DiskinfoDiskAlloc, + DiskinfoDiskCreated, + DiskBlacklistID, + DiskBlacklistDiskID, + DockerRegistriesID, + DockerRegistriesService, + DockerRegistriesURL, + DockerRegistriesInsecure, + DockerRegistriesCreated, + DockerRegistriesUpdated, + DockerRegistriesRestricted, + DockerRegistriesPublicationsID, + DockerRegistriesPublicationsGroupID, + DockerRegistriesPublicationsRegistryID, + DockerRegistriesResponsiblesID, + DockerRegistriesResponsiblesGroupID, + DockerRegistriesResponsiblesRegistryID, + DockerRepositoriesID, + DockerRepositoriesRegistryID, + DockerRepositoriesRepository, + DockerRepositoriesCreated, + DockerRepositoriesUpdated, + DockerRepositoriesDescription, + DockerRepositoriesStars, + DockerRepositoriesAutomated, + DockerRepositoriesOfficial, + DockerTagsID, + DockerTagsRegistryID, + DockerTagsRepositoryID, + DockerTagsName, + DockerTagsCreated, + DockerTagsUpdated, + DockerTagsConfigDigest, + DockerTagsConfigSize, + DrpprojectsDRPProject, + DrpprojectsDRPProjectID, + DrpservicesDRPWave, + DrpservicesDRPProjectID, + DrpservicesSvcID, + FeedQueueID, + FeedQueueQFn, + FeedQueueQArgs, + FeedQueueCreated, + FeedQueueStatsID, + FeedQueueStatsQStart, + FeedQueueStatsQEnd, + FeedQueueStatsQFn, + FeedQueueStatsQCount, + FeedQueueStatsQAvg, + FiltersID, + FiltersFilName, + FiltersFilColumn, + FiltersFilNeedValue, + FiltersFilPos, + FiltersFilTable, + FiltersFilImg, + FiltersFilSearchTable, + FormsID, + FormsFormName, + FormsFormYaml, + FormsFormAuthor, + FormsFormCreated, + FormsFormType, + FormsFormFolder, + FormsRevisionsID, + FormsRevisionsFormYaml, + FormsRevisionsFormMD5, + FormsRevisionsFormDate, + FormsRevisionsFormID, + FormsRevisionsFormFolder, + FormsRevisionsFormName, + FormsStoreID, + FormsStoreFormSubmitter, + FormsStoreFormSubmitDate, + FormsStoreFormData, + FormsStoreFormNextID, + FormsStoreFormPrevID, + FormsStoreFormAssignee, + FormsStoreFormHeadID, + FormsStoreFormMD5, + FormsStoreFormVarID, + FormsStoreResultsID, + FormsTeamPublicationID, + FormsTeamPublicationFormID, + FormsTeamPublicationGroupID, + FormsTeamResponsibleID, + FormsTeamResponsibleFormID, + FormsTeamResponsibleGroupID, + FormOutputResultsID, + FormOutputResultsUserID, + FormOutputResultsNodeID, + FormOutputResultsResults, + FormOutputResultsSvcID, + FsetCacheFsetID, + FsetCacheObjType, + FsetCacheObjID, + GenFiltersID, + GenFiltersFTable, + GenFiltersFField, + GenFiltersFValue, + GenFiltersFUpdated, + GenFiltersFAuthor, + GenFiltersFOp, + GenFiltersFCksum, + GenFiltersFLabel, + GenFiltersetsID, + GenFiltersetsFsetName, + GenFiltersetsFsetUpdated, + GenFiltersetsFsetAuthor, + GenFiltersetsFsetStats, + GenFiltersetsFiltersID, + GenFiltersetsFiltersFsetID, + GenFiltersetsFiltersFID, + GenFiltersetsFiltersFLogOp, + GenFiltersetsFiltersEncapFsetID, + GenFiltersetsFiltersFOrder, + GenFiltersetCheckThresholdID, + GenFiltersetCheckThresholdFsetID, + GenFiltersetCheckThresholdChkType, + GenFiltersetCheckThresholdChkLow, + GenFiltersetCheckThresholdChkHigh, + GenFiltersetCheckThresholdChkInstance, + GenFiltersetTeamResponsibleID, + GenFiltersetTeamResponsibleFsetID, + GenFiltersetTeamResponsibleGroupID, + GenFiltersetUserID, + GenFiltersetUserFsetID, + GenFiltersetUserUserID, + GroupHiddenMenuEntriesID, + GroupHiddenMenuEntriesGroupID, + GroupHiddenMenuEntriesMenuEntry, + HbmonID, + HbmonNodeID, + HbmonPeerNodeID, + HbmonHbType, + HbmonHbName, + HbmonHbDesc, + HbmonHbStatus, + HbmonUpdated, + HbmonLogID, + HbmonLogNodeID, + HbmonLogPeerNodeID, + HbmonLogHbName, + HbmonLogHbStatus, + HbmonLogHbBegin, + HbmonLogHbEnd, + HbmonLogLastID, + HbmonLogLastNodeID, + HbmonLogLastPeerNodeID, + HbmonLogLastHbName, + HbmonLogLastHbStatus, + HbmonLogLastHbBegin, + HbmonLogLastHbEnd, + ImTypesID, + ImTypesImType, + LifecycleOSID, + LifecycleOSLcOSConcat, + LifecycleOSLcCount, + LifecycleOSLcDate, + LifecycleOSLcOSName, + LifecycleOSLcOSVendor, + LifecycleOSFsetID, + LinksID, + LinksLinkFunction, + LinksLinkParameters, + LinksLinkCreationUserID, + LinksLinkCreationDate, + LinksLinkLastConsultationDate, + LinksLinkMD5, + LinksLinkAccessCounter, + LinksLinkTitle, + LinksLinkTitleArgs, + LogID, + LogLogAction, + LogLogUser, + LogLogFmt, + LogLogDict, + LogLogDate, + LogSvcID, + LogLogGtalkSent, + LogLogEmailSent, + LogLogEntryID, + LogLogLevel, + LogNodeID, + MetricsID, + MetricsMetricName, + MetricsMetricSql, + MetricsMetricAuthor, + MetricsMetricCreated, + MetricsMetricColValueIndex, + MetricsMetricColInstanceIndex, + MetricsMetricColInstanceLabel, + MetricsMetricHistorize, + MetricTeamPublicationID, + MetricTeamPublicationMetricID, + MetricTeamPublicationGroupID, + NetworksID, + NetworksName, + NetworksNetwork, + NetworksNetmask, + NetworksTeamResponsible, + NetworksComment, + NetworksPvid, + NetworksGateway, + NetworksBegin, + NetworksUpdated, + NetworksPrio, + NetworksEnd, + NetworksBroadcast, + NetworkSegmentsID, + NetworkSegmentsNetID, + NetworkSegmentsSegType, + NetworkSegmentsSegBegin, + NetworkSegmentsSegEnd, + NetworkSegmentResponsiblesID, + NetworkSegmentResponsiblesSegID, + NetworkSegmentResponsiblesGroupID, + NodesNodename, + NodesLocCountry, + NodesLocCity, + NodesLocAddr, + NodesLocBuilding, + NodesLocFloor, + NodesLocRoom, + NodesLocRack, + NodesID, + NodesCPUFreq, + NodesCPUCores, + NodesCPUDies, + NodesCPUVendor, + NodesCPUModel, + NodesMEMBanks, + NodesMEMSlots, + NodesMEMBytes, + NodesOSName, + NodesOSRelease, + NodesOSUpdate, + NodesOSSegment, + NodesOSArch, + NodesOSVendor, + NodesOSKernel, + NodesLocZip, + NodesTeamResponsible, + NodesSerial, + NodesModel, + NodesType, + NodesWarrantyEnd, + NodesStatus, + NodesRole, + NodesAssetEnv, + NodesPowerCabinet1, + NodesPowerCabinet2, + NodesPowerSupplyNb, + NodesPowerProtect, + NodesPowerProtectBreaker, + NodesPowerBreaker1, + NodesPowerBreaker2, + NodesBladeCabinet, + NodesUpdated, + NodesTeamInteg, + NodesApp, + NodesTeamSupport, + NodesNodeEnv, + NodesMaintenanceEnd, + NodesEnclosure, + NodesHWObsWarnDate, + NodesHWObsAlertDate, + NodesOSObsWarnDate, + NodesOSObsAlertDate, + NodesFqdn, + NodesListenerPort, + NodesVersion, + NodesHvpool, + NodesHv, + NodesHvvdc, + NodesCPUThreads, + NodesAssetname, + NodesEnclosureslot, + NodesSecZone, + NodesLastBoot, + NodesActionType, + NodesOSConcat, + NodesConnectTo, + NodesTz, + NodesNodeID, + NodesCollector, + NodesSpVersion, + NodesBiosVersion, + NodesLastComm, + NodesManufacturer, + NodesNotifications, + NodesSnoozeTill, + NodesClusterID, + NodesNodeFrozen, + NodesNodeFrozenAt, + NodeGroupsID, + NodeGroupsGroupName, + NodeGroupsGroupID, + NodeGroupsUpdated, + NodeGroupsNodeID, + NodeHBAID, + NodeHBAUpdated, + NodeHBAHBAID, + NodeHBAHBAType, + NodeHBANodeID, + NodeHWID, + NodeHWNodeID, + NodeHWHWType, + NodeHWHWPath, + NodeHWHWClass, + NodeHWHWDescription, + NodeHWHWDriver, + NodeHWUpdated, + NodeIPID, + NodeIPMac, + NodeIPIntf, + NodeIPType, + NodeIPAddr, + NodeIPMask, + NodeIPUpdated, + NodeIPFlagDeprecated, + NodeIPNodeID, + NodePWID, + NodePWPW, + NodePWUpdated, + NodePWNodeID, + NodeTagsID, + NodeTagsCreated, + NodeTagsNodeID, + NodeTagsTagID, + NodeTagsTagAttachData, + NodeUsersID, + NodeUsersUserName, + NodeUsersUserID, + NodeUsersUpdated, + NodeUsersNodeID, + ObsolescenceID, + ObsolescenceObsType, + ObsolescenceObsName, + ObsolescenceObsWarnDate, + ObsolescenceObsAlertDate, + ObsolescenceObsWarnDateUpdatedBy, + ObsolescenceObsAlertDateUpdatedBy, + ObsolescenceObsWarnDateUpdated, + ObsolescenceObsAlertDateUpdated, + Oc3SchedulerID, + Oc3SchedulerTaskName, + Oc3SchedulerIsDisabled, + Oc3SchedulerLastRunAt, + PackagesID, + PackagesPkgName, + PackagesPkgVersion, + PackagesPkgArch, + PackagesPkgUpdated, + PackagesPkgType, + PackagesPkgInstallDate, + PackagesPkgSig, + PackagesNodeID, + PatchesID, + PatchesPatchNum, + PatchesPatchRev, + PatchesPatchUpdated, + PatchesPatchInstallDate, + PatchesNodeID, + PkgSigProviderID, + PkgSigProviderSigID, + PkgSigProviderSigProvider, + ProvTemplatesID, + ProvTemplatesTplName, + ProvTemplatesTplDefinition, + ProvTemplatesTplComment, + ProvTemplatesTplAuthor, + ProvTemplatesTplCreated, + ProvTemplateTeamPublicationID, + ProvTemplateTeamPublicationTplID, + ProvTemplateTeamPublicationGroupID, + ProvTemplateTeamResponsibleID, + ProvTemplateTeamResponsibleTplID, + ProvTemplateTeamResponsibleGroupID, + ReplicationStatusID, + ReplicationStatusRemote, + ReplicationStatusMode, + ReplicationStatusTableSchema, + ReplicationStatusTableName, + ReplicationStatusTableCksum, + ReplicationStatusTableUpdated, + ReportsID, + ReportsReportName, + ReportsReportYaml, + ReportsUserID, + ReportsUserReportID, + ReportsUserUserID, + ReportTeamPublicationID, + ReportTeamPublicationReportID, + ReportTeamPublicationGroupID, + ReportTeamResponsibleID, + ReportTeamResponsibleReportID, + ReportTeamResponsibleGroupID, + ResinfoID, + ResinfoRid, + ResinfoResKey, + ResinfoResValue, + ResinfoUpdated, + ResinfoTopology, + ResinfoNodeID, + ResinfoSvcID, + ResmonID, + ResmonRid, + ResmonResStatus, + ResmonChanged, + ResmonUpdated, + ResmonResDesc, + ResmonResLog, + ResmonVmname, + ResmonResMonitor, + ResmonResDisable, + ResmonResOptional, + ResmonResType, + ResmonNodeID, + ResmonSvcID, + ResmonLogID, + ResmonLogNodeID, + ResmonLogSvcID, + ResmonLogRid, + ResmonLogResStatus, + ResmonLogResBegin, + ResmonLogResEnd, + ResmonLogResLog, + ResmonLogLastID, + ResmonLogLastNodeID, + ResmonLogLastSvcID, + ResmonLogLastRid, + ResmonLogLastResStatus, + ResmonLogLastResBegin, + ResmonLogLastResEnd, + ResmonLogLastResLog, + SafeID, + SafeUploader, + SafeUploadedFrom, + SafeUploadedDate, + SafeName, + SafeSize, + SafeUuid, + SafeMD5, + SafeLogID, + SafeLogSafeID, + SafeLogUuid, + SafeLogArchived, + SafeTeamPublicationID, + SafeTeamPublicationFileID, + SafeTeamPublicationGroupID, + SafeTeamResponsibleID, + SafeTeamResponsibleFileID, + SafeTeamResponsibleGroupID, + SANZoneID, + SANZoneCfg, + SANZoneZone, + SANZonePort, + SANZoneUpdated, + SANZoneAliasID, + SANZoneAliasCfg, + SANZoneAliasAlias, + SANZoneAliasPort, + SANZoneAliasUpdated, + SavesID, + SavesSaveName, + SavesSaveGroup, + SavesSaveSize, + SavesSaveDate, + SavesSaveRetention, + SavesSaveVolume, + SavesSaveLevel, + SavesSaveServer, + SavesSaveApp, + SavesSaveID, + SavesNodeID, + SavesSvcID, + SavesChkInstance, + SavesSaveResolved, + SavesLastID, + SavesLastSaveName, + SavesLastSaveGroup, + SavesLastSaveSize, + SavesLastSaveDate, + SavesLastSaveRetention, + SavesLastSaveVolume, + SavesLastSaveLevel, + SavesLastSaveServer, + SavesLastSaveApp, + SavesLastSaveID, + SavesLastChkInstance, + SavesLastSaveResolved, + SavesLastNodeID, + SavesLastSvcID, + SchedulerRunID, + SchedulerRunTaskID, + SchedulerRunStartTime, + SchedulerRunStopTime, + SchedulerRunRunOutput, + SchedulerRunRunResult, + SchedulerRunTraceback, + SchedulerRunStatus, + SchedulerRunWorkerName, + SchedulerRunDuration, + SchedulerTaskID, + SchedulerTaskUuid, + SchedulerTaskArgs, + SchedulerTaskVars, + SchedulerTaskEnabled, + SchedulerTaskStartTime, + SchedulerTaskNextRunTime, + SchedulerTaskStopTime, + SchedulerTaskRepeats, + SchedulerTaskRetryFailed, + SchedulerTaskPeriod, + SchedulerTaskTimeout, + SchedulerTaskSyncOutput, + SchedulerTaskTimesRun, + SchedulerTaskTimesFailed, + SchedulerTaskLastRunTime, + SchedulerTaskPreventDrift, + SchedulerTaskGroupName, + SchedulerTaskFunctionName, + SchedulerTaskStatus, + SchedulerTaskTaskName, + SchedulerTaskApplicationName, + SchedulerTaskAssignedWorkerName, + SchedulerTaskDepsID, + SchedulerTaskDepsJobName, + SchedulerTaskDepsTaskParent, + SchedulerTaskDepsTaskChild, + SchedulerTaskDepsCanVisit, + SchedulerWorkerID, + SchedulerWorkerWorkerName, + SchedulerWorkerFirstHeartbeat, + SchedulerWorkerLastHeartbeat, + SchedulerWorkerIsTicker, + SchedulerWorkerGroupNames, + SchedulerWorkerStatus, + SchedulerWorkerWorkerStats, + ServicesSvcHostid, + ServicesSvcname, + ServicesSvcNodes, + ServicesSvcDrpnode, + ServicesSvcDrptype, + ServicesSvcAutostart, + ServicesSvcEnv, + ServicesSvcDrpnodes, + ServicesSvcComment, + ServicesSvcApp, + ServicesSvcDrnoaction, + ServicesSvcCreated, + ServicesSvcConfigUpdated, + ServicesSvcMetrocluster, + ServicesID, + ServicesSvcWave, + ServicesSvcConfig, + ServicesUpdated, + ServicesSvcTopology, + ServicesSvcFlexMinNodes, + ServicesSvcFlexMaxNodes, + ServicesSvcFlexCPULowThreshold, + ServicesSvcFlexCPUHighThreshold, + ServicesSvcStatus, + ServicesSvcAvailstatus, + ServicesSvcHa, + ServicesSvcStatusUpdated, + ServicesSvcID, + ServicesSvcFrozen, + ServicesSvcProvisioned, + ServicesSvcPlacement, + ServicesSvcNotifications, + ServicesSvcSnoozeTill, + ServicesClusterID, + ServicesSvcFlexTarget, + ServicesLogID, + ServicesLogSvcAvailstatus, + ServicesLogSvcBegin, + ServicesLogSvcEnd, + ServicesLogSvcID, + ServicesLogLastID, + ServicesLogLastSvcAvailstatus, + ServicesLogLastSvcBegin, + ServicesLogLastSvcEnd, + ServicesLogLastSvcID, + ServicesTestName, + ServicesTestApp, + ServicesTestID, + ServicesTestSvcID, + ServiceIdsSvcname, + ServiceIdsClusterID, + ServiceIdsID, + ServiceIdsSvcID, + StatsCompareID, + StatsCompareName, + StatsCompareFsetID, + StatsCompareFsetCompareID, + StatsCompareFsetFsetID, + StatsCompareUserID, + StatsCompareUserCompareID, + StatsCompareUserUserID, + StatDayID, + StatDayDay, + StatDayNbSvc, + StatDayNbAction, + StatDayNbActionErr, + StatDayNbActionWarn, + StatDayNbActionOk, + StatDayDiskSize, + StatDayRamSize, + StatDayNbCPUCore, + StatDayNbCPUDie, + StatDayWatt, + StatDayRackunit, + StatDayNbApps, + StatDayNbAccounts, + StatDayNbSvcWithDRP, + StatDayNbNodes, + StatDayNbSvcPrd, + StatDayNbSvcCluster, + StatDayNbNodesPrd, + StatDayFsetID, + StatDayNbVcpu, + StatDayNbVmem, + StatDayNbRespAccounts, + StatDayNbVirtNodes, + StatDayLocalDiskSize, + StatDayDiskAppID, + StatDayDiskAppDay, + StatDayDiskAppApp, + StatDayDiskAppDiskUsed, + StatDayDiskAppQuota, + StatDayDiskAppDGID, + StatDayDiskAppDGDay, + StatDayDiskAppDGDGID, + StatDayDiskAppDGApp, + StatDayDiskAppDGDiskUsed, + StatDayDiskAppDGQuota, + StatDayDiskArrayID, + StatDayDiskArrayDay, + StatDayDiskArrayArrayName, + StatDayDiskArrayDiskUsed, + StatDayDiskArrayDiskSize, + StatDayDiskArrayReservable, + StatDayDiskArrayReserved, + StatDayDiskArrayDGID, + StatDayDiskArrayDGDay, + StatDayDiskArrayDGArrayName, + StatDayDiskArrayDGArrayDG, + StatDayDiskArrayDGDiskUsed, + StatDayDiskArrayDGDiskSize, + StatDayDiskArrayDGReserved, + StatDayDiskArrayDGReservable, + StatDaySvcID, + StatDaySvcDay, + StatDaySvcNbAction, + StatDaySvcNbActionErr, + StatDaySvcNbActionWarn, + StatDaySvcNbActionOk, + StatDaySvcDiskSize, + StatDaySvcRamSize, + StatDaySvcNbCPUCore, + StatDaySvcNbCPUDie, + StatDaySvcWatt, + StatDaySvcRackunit, + StatDaySvcNbNodes, + StatDaySvcLocalDiskSize, + StatDaySvcSvcID, + StorArrayID, + StorArrayArrayName, + StorArrayArrayModel, + StorArrayArrayCache, + StorArrayArrayFirmware, + StorArrayArrayUpdated, + StorArrayArrayLevel, + StorArrayArrayComment, + StorArrayDGID, + StorArrayDGArrayID, + StorArrayDGDGName, + StorArrayDGDGFree, + StorArrayDGDGUpdated, + StorArrayDGDGSize, + StorArrayDGDGUsed, + StorArrayDGDGReserved, + StorArrayDGQuotaID, + StorArrayDGQuotaDGID, + StorArrayDGQuotaAppID, + StorArrayDGQuotaQuota, + StorArrayProxyID, + StorArrayProxyArrayID, + StorArrayProxyNodeID, + StorArrayTgtidID, + StorArrayTgtidArrayID, + StorArrayTgtidArrayTgtid, + StorArrayTgtidUpdated, + StorZoneID, + StorZoneTgtID, + StorZoneHBAID, + StorZoneUpdated, + StorZoneNodeID, + SvcactionsAction, + SvcactionsStatus, + SvcactionsBegin, + SvcactionsEnd, + SvcactionsHostid, + SvcactionsStatusLog, + SvcactionsPid, + SvcactionsID, + SvcactionsAck, + SvcactionsAlert, + SvcactionsAckedBy, + SvcactionsAckedComment, + SvcactionsAckedDate, + SvcactionsVersion, + SvcactionsCron, + SvcactionsTime, + SvcactionsNodeID, + SvcactionsSvcID, + SvcactionsSid, + SvcactionsRid, + SvcactionsSubset, + SvcdisksID, + SvcdisksDiskID, + SvcdisksDiskSize, + SvcdisksDiskVendor, + SvcdisksDiskModel, + SvcdisksDiskDG, + SvcdisksDiskUpdated, + SvcdisksDiskLocal, + SvcdisksDiskUsed, + SvcdisksDiskRegion, + SvcdisksAppID, + SvcdisksNodeID, + SvcdisksSvcID, + SvcmonMonSvctype, + SvcmonMonIpstatus, + SvcmonMonFsstatus, + SvcmonMonUpdated, + SvcmonID, + SvcmonMonFrozen, + SvcmonMonChanged, + SvcmonMonDiskstatus, + SvcmonMonContainerstatus, + SvcmonMonOverallstatus, + SvcmonMonSyncstatus, + SvcmonMonAppstatus, + SvcmonMonHbstatus, + SvcmonMonAvailstatus, + SvcmonMonVmname, + SvcmonMonGuestos, + SvcmonMonVmem, + SvcmonMonVcpus, + SvcmonMonContainerpath, + SvcmonMonVmtype, + SvcmonMonSharestatus, + SvcmonNodeID, + SvcmonSvcID, + SvcmonMonSmonStatus, + SvcmonMonSmonGlobalExpect, + SvcmonMonFrozenAt, + SvcmonMonEncapFrozenAt, + SvcmonLogID, + SvcmonLogMonOverallstatus, + SvcmonLogMonIpstatus, + SvcmonLogMonFsstatus, + SvcmonLogMonDiskstatus, + SvcmonLogMonContainerstatus, + SvcmonLogMonSyncstatus, + SvcmonLogMonAppstatus, + SvcmonLogMonBegin, + SvcmonLogMonEnd, + SvcmonLogMonHbstatus, + SvcmonLogMonAvailstatus, + SvcmonLogMonSharestatus, + SvcmonLogNodeID, + SvcmonLogSvcID, + SvcmonLogAckID, + SvcmonLogAckMonBegin, + SvcmonLogAckMonEnd, + SvcmonLogAckMonComment, + SvcmonLogAckMonAckedBy, + SvcmonLogAckMonAckedOn, + SvcmonLogAckMonAccount, + SvcmonLogAckSvcID, + SvcmonLogLastID, + SvcmonLogLastMonOverallstatus, + SvcmonLogLastMonIpstatus, + SvcmonLogLastMonFsstatus, + SvcmonLogLastMonDiskstatus, + SvcmonLogLastMonContainerstatus, + SvcmonLogLastMonSyncstatus, + SvcmonLogLastMonAppstatus, + SvcmonLogLastMonBegin, + SvcmonLogLastMonEnd, + SvcmonLogLastMonHbstatus, + SvcmonLogLastMonAvailstatus, + SvcmonLogLastMonSharestatus, + SvcmonLogLastNodeID, + SvcmonLogLastSvcID, + SvcTagsID, + SvcTagsCreated, + SvcTagsSvcID, + SvcTagsTagID, + SvcTagsTagAttachData, + SwitchesID, + SwitchesSwName, + SwitchesSwSlot, + SwitchesSwPort, + SwitchesSwPortspeed, + SwitchesSwPortnego, + SwitchesSwPorttype, + SwitchesSwPortstate, + SwitchesSwPortname, + SwitchesSwRportname, + SwitchesSwUpdated, + SwitchesSwFabric, + SwitchesSwIndex, + SysrepAllowID, + SysrepAllowPattern, + SysrepAllowFsetID, + SysrepAllowGroupID, + SysrepChangingID, + SysrepChangingPattern, + SysrepSecureID, + SysrepSecurePattern, + TableModifiedID, + TableModifiedTableName, + TableModifiedTableModified, + TagsID, + TagsTagName, + TagsTagCreated, + TagsTagExclude, + TagsTagData, + TagsTagID, + TmpID, + Tmpmd5RsetMD5, + UserLogUserID, + UserLogLogID, + UserPrefsID, + UserPrefsUserID, + UserPrefsPrefs, + UIncInc, + WikiPagesID, + WikiPagesName, + WikiPagesAuthor, + WikiPagesSavedOn, + WikiPagesTitle, + WikiPagesBody, + WikiPagesChangeNote, + WorkflowsID, + WorkflowsFormHeadID, + WorkflowsStatus, + WorkflowsSteps, + WorkflowsCreator, + WorkflowsCreateDate, + WorkflowsLastAssignee, + WorkflowsLastUpdate, + WorkflowsFormMD5, + WorkflowsLastFormID, + WorkflowsLastFormName, +} From ac11c60ec0e640eb66de93c6254765ee63641528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Tue, 14 Apr 2026 17:26:26 +0200 Subject: [PATCH 12/24] Add orderby and groupby parameters and adapt handlers --- server/api.yaml | 32 ++ server/codegen_server_gen.go | 184 +++++++-- server/codegen_type_gen.go | 54 +++ server/handlers/get_app_publications.go | 2 +- server/handlers/get_app_responsibles.go | 2 +- server/handlers/get_apps.go | 2 +- ...et_node_compliance_candidate_modulesets.go | 2 +- .../get_node_compliance_candidate_rulesets.go | 2 +- .../get_node_compliance_modulesets.go | 2 +- .../handlers/get_node_compliance_rulesets.go | 2 +- server/handlers/get_tags.go | 2 +- server/handlers/handle_list.go | 89 +++++ server/handlers/list_query_parameters.go | 80 +++- server/handlers/list_response.go | 2 +- server/handlers/props.go | 93 ++++- server/handlers/props_mapping.go | 355 ++++++++++++++++++ 16 files changed, 858 insertions(+), 47 deletions(-) create mode 100644 server/handlers/handle_list.go diff --git a/server/api.yaml b/server/api.yaml index 75705db..9a9d131 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -18,6 +18,8 @@ paths: - $ref: '#/components/parameters/inQueryOffset' - $ref: '#/components/parameters/inQueryMeta' - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' tags: - collector responses: @@ -185,6 +187,8 @@ paths: - $ref: '#/components/parameters/inQueryOffset' - $ref: '#/components/parameters/inQueryMeta' - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' tags: - collector responses: @@ -218,6 +222,8 @@ paths: - $ref: '#/components/parameters/inQueryOffset' - $ref: '#/components/parameters/inQueryMeta' - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' tags: - collector responses: @@ -312,6 +318,8 @@ paths: - $ref: '#/components/parameters/inQueryOffset' - $ref: '#/components/parameters/inQueryMeta' - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' responses: 200: description: List of candidate modulesets @@ -341,6 +349,8 @@ paths: - $ref: '#/components/parameters/inQueryOffset' - $ref: '#/components/parameters/inQueryMeta' - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' responses: 200: description: List of candidate rulesets @@ -397,6 +407,8 @@ paths: - $ref: '#/components/parameters/inQueryOffset' - $ref: '#/components/parameters/inQueryMeta' - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' responses: 200: description: List of attached modulesets @@ -476,6 +488,8 @@ paths: - $ref: '#/components/parameters/inQueryOffset' - $ref: '#/components/parameters/inQueryMeta' - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' responses: 200: description: List of attached rulesets @@ -553,6 +567,8 @@ paths: - $ref: '#/components/parameters/inQueryOffset' - $ref: '#/components/parameters/inQueryMeta' - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' tags: - collector responses: @@ -839,6 +855,22 @@ components: schema: type: string + inQueryOrderby: + in: query + name: orderby + required: false + description: Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + schema: + type: string + + inQueryGroupby: + in: query + name: groupby + required: false + description: Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + schema: + type: string + inQuerySync: in: query name: sync diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 450d78a..c84fc5a 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -138,6 +138,20 @@ func (w *ServerInterfaceWrapper) GetApps(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) } + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetApps(ctx, params) return err @@ -277,6 +291,20 @@ func (w *ServerInterfaceWrapper) GetAppPublications(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) } + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetAppPublications(ctx, appId, params) return err @@ -334,6 +362,20 @@ func (w *ServerInterfaceWrapper) GetAppResponsibles(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) } + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetAppResponsibles(ctx, appId, params) return err @@ -404,6 +446,20 @@ func (w *ServerInterfaceWrapper) GetNodeComplianceCandidateModulesets(ctx echo.C return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) } + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetNodeComplianceCandidateModulesets(ctx, nodeId, params) return err @@ -461,6 +517,20 @@ func (w *ServerInterfaceWrapper) GetNodeComplianceCandidateRulesets(ctx echo.Con return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) } + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetNodeComplianceCandidateRulesets(ctx, nodeId, params) return err @@ -547,6 +617,20 @@ func (w *ServerInterfaceWrapper) GetNodeComplianceModulesets(ctx echo.Context) e return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) } + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetNodeComplianceModulesets(ctx, nodeId, params) return err @@ -660,6 +744,20 @@ func (w *ServerInterfaceWrapper) GetNodeComplianceRulesets(ctx echo.Context) err return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) } + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetNodeComplianceRulesets(ctx, nodeId, params) return err @@ -775,6 +873,20 @@ func (w *ServerInterfaceWrapper) GetTags(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) } + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetTags(ctx, params) return err @@ -917,41 +1029,43 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "E0EAAA==", + "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", + "/wUAAP//o9hXbbxFAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index e966461..7abb0fe 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -63,6 +63,9 @@ type InPathNodeId = string // InPathRsetId defines model for inPathRsetId. type InPathRsetId = string +// InQueryGroupby defines model for inQueryGroupby. +type InQueryGroupby = string + // InQueryLimit defines model for inQueryLimit. type InQueryLimit = int @@ -72,6 +75,9 @@ type InQueryMeta = string // InQueryOffset defines model for inQueryOffset. type InQueryOffset = int +// InQueryOrderby defines model for inQueryOrderby. +type InQueryOrderby = string + // InQueryProps defines model for inQueryProps. type InQueryProps = string @@ -112,6 +118,12 @@ type GetAppsParams struct { // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } // PostAppsJSONBody defines parameters for PostApps. @@ -144,6 +156,12 @@ type GetAppPublicationsParams struct { // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } // GetAppResponsiblesParams defines parameters for GetAppResponsibles. @@ -162,6 +180,12 @@ type GetAppResponsiblesParams struct { // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } // PostAuthNodeJSONBody defines parameters for PostAuthNode. @@ -186,6 +210,12 @@ type GetNodeComplianceCandidateModulesetsParams struct { // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } // GetNodeComplianceCandidateRulesetsParams defines parameters for GetNodeComplianceCandidateRulesets. @@ -204,6 +234,12 @@ type GetNodeComplianceCandidateRulesetsParams struct { // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } // GetNodeComplianceLogsParams defines parameters for GetNodeComplianceLogs. @@ -228,6 +264,12 @@ type GetNodeComplianceModulesetsParams struct { // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } // PostNodeComplianceModulesetJSONBody defines parameters for PostNodeComplianceModuleset. @@ -249,6 +291,12 @@ type GetNodeComplianceRulesetsParams struct { // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } // PostNodeComplianceRulesetJSONBody defines parameters for PostNodeComplianceRuleset. @@ -270,6 +318,12 @@ type GetTagsParams struct { // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } // GetTagParams defines parameters for GetTag. diff --git a/server/handlers/get_app_publications.go b/server/handlers/get_app_publications.go index 0bce1ba..7d79a31 100644 --- a/server/handlers/get_app_publications.go +++ b/server/handlers/get_app_publications.go @@ -12,7 +12,7 @@ import ( // GetAppPublications handles GET /apps/{app_id}/publications func (a *Api) GetAppPublications(c echo.Context, appId string, params server.GetAppPublicationsParams) error { - query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["auth_group"]) + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["auth_group"]) if err != nil { return JSONProblem(c, http.StatusBadRequest, err.Error()) } diff --git a/server/handlers/get_app_responsibles.go b/server/handlers/get_app_responsibles.go index e21b12d..a14d1ac 100644 --- a/server/handlers/get_app_responsibles.go +++ b/server/handlers/get_app_responsibles.go @@ -12,7 +12,7 @@ import ( // GetAppResponsibles handles GET /apps/{app_id}/responsibles func (a *Api) GetAppResponsibles(c echo.Context, appId string, params server.GetAppResponsiblesParams) error { - query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["auth_group"]) + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["auth_group"]) if err != nil { return JSONProblem(c, http.StatusBadRequest, err.Error()) } diff --git a/server/handlers/get_apps.go b/server/handlers/get_apps.go index 636dd0d..c3843b5 100644 --- a/server/handlers/get_apps.go +++ b/server/handlers/get_apps.go @@ -12,7 +12,7 @@ import ( // GetApps handles GET /apps func (a *Api) GetApps(c echo.Context, params server.GetAppsParams) error { - query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["app"]) + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["app"]) if err != nil { return JSONProblem(c, http.StatusBadRequest, err.Error()) } diff --git a/server/handlers/get_node_compliance_candidate_modulesets.go b/server/handlers/get_node_compliance_candidate_modulesets.go index 364e42f..7cdefce 100644 --- a/server/handlers/get_node_compliance_candidate_modulesets.go +++ b/server/handlers/get_node_compliance_candidate_modulesets.go @@ -12,7 +12,7 @@ import ( // GetNodeComplianceCandidateModulesets handles GET /nodes/{node_id}/compliance/candidate_modulesets func (a *Api) GetNodeComplianceCandidateModulesets(c echo.Context, nodeId string, params server.GetNodeComplianceCandidateModulesetsParams) error { - query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["moduleset"]) + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["moduleset"]) if err != nil { return JSONProblem(c, http.StatusBadRequest, err.Error()) } diff --git a/server/handlers/get_node_compliance_candidate_rulesets.go b/server/handlers/get_node_compliance_candidate_rulesets.go index faaaf45..29671e7 100644 --- a/server/handlers/get_node_compliance_candidate_rulesets.go +++ b/server/handlers/get_node_compliance_candidate_rulesets.go @@ -12,7 +12,7 @@ import ( // GetNodeComplianceCandidateRulesets handles GET /nodes/{node_id}/compliance/candidate_rulesets func (a *Api) GetNodeComplianceCandidateRulesets(c echo.Context, nodeId string, params server.GetNodeComplianceCandidateRulesetsParams) error { - query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["ruleset"]) + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["ruleset"]) if err != nil { return JSONProblem(c, http.StatusBadRequest, err.Error()) } diff --git a/server/handlers/get_node_compliance_modulesets.go b/server/handlers/get_node_compliance_modulesets.go index 3c06f25..5b86192 100644 --- a/server/handlers/get_node_compliance_modulesets.go +++ b/server/handlers/get_node_compliance_modulesets.go @@ -12,7 +12,7 @@ import ( // GetNodeComplianceModulesets handles GET /nodes/{node_id}/compliance/modulesets func (a *Api) GetNodeComplianceModulesets(c echo.Context, nodeId string, params server.GetNodeComplianceModulesetsParams) error { - query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["moduleset"]) + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["moduleset"]) if err != nil { return JSONProblem(c, http.StatusBadRequest, err.Error()) } diff --git a/server/handlers/get_node_compliance_rulesets.go b/server/handlers/get_node_compliance_rulesets.go index ce0e766..9677868 100644 --- a/server/handlers/get_node_compliance_rulesets.go +++ b/server/handlers/get_node_compliance_rulesets.go @@ -12,7 +12,7 @@ import ( // GetNodeComplianceRulesets handles GET /nodes/{node_id}/compliance/rulesets func (a *Api) GetNodeComplianceRulesets(c echo.Context, nodeId string, params server.GetNodeComplianceRulesetsParams) error { - query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["ruleset"]) + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["ruleset"]) if err != nil { return JSONProblem(c, http.StatusBadRequest, err.Error()) } diff --git a/server/handlers/get_tags.go b/server/handlers/get_tags.go index 7766c5d..659facf 100644 --- a/server/handlers/get_tags.go +++ b/server/handlers/get_tags.go @@ -46,7 +46,7 @@ func (a *Api) handleGetTags(c echo.Context, tagID *int, query ListQueryParameter // GetTags handles GET /tags func (a *Api) GetTags(c echo.Context, params server.GetTagsParams) error { - query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, propsMapping["tag"]) + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["tag"]) if err != nil { return JSONProblem(c, http.StatusBadRequest, err.Error()) } diff --git a/server/handlers/handle_list.go b/server/handlers/handle_list.go new file mode 100644 index 0000000..ae5b0e5 --- /dev/null +++ b/server/handlers/handle_list.go @@ -0,0 +1,89 @@ +package serverhandlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// listFetcher is the DB call signature shared by all list endpoints. +type listFetcher func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) + +// listEndpointParams bundles the standard query parameters shared by every list endpoint. +type listEndpointParams struct { + props *server.InQueryProps + limit *server.InQueryLimit + offset *server.InQueryOffset + meta *server.InQueryMeta + stats *server.InQueryStats + orderby *server.InQueryOrderby + groupby *server.InQueryGroupby +} + +// handleList implements the common pipeline for all list endpoints: +// 1. Parse and validate query parameters (props, pagination, meta, stats) +// 2. Build SQL SELECT expressions from the resolved props +// 3. Build SQL JOIN fragments required by cross-table props +// 4. Call fetch to retrieve data from the database +// 5. Return a formatted JSON response with optional metadata +func (a *Api) handleList( + c echo.Context, + handlerName string, + mappingKey string, + p listEndpointParams, + fetch listFetcher, +) error { + mapping := propsMapping[mappingKey] + + query, err := buildListQueryParameters(p.props, p.limit, p.offset, p.meta, p.stats, p.orderby, p.groupby, mapping) + if err != nil { + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log := echolog.GetLogHandler(c, handlerName) + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", + "limit", query.Page.Limit, + "offset", query.Page.Offset, + "props", query.Props, + "meta", query.WithMeta, + "stats", query.WithStats, + "orderby", query.OrderBy, + "groupby", query.GroupBy, + "is_manager", isManager, + ) + + selectExprs, err := buildSelectClause(query.Props, mapping) + if err != nil { + log.Error("cannot build select clause", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot build select clause") + } + + dbParams := cdb.ListParams{ + Groups: groups, + IsManager: isManager, + Limit: query.Page.Limit, + Offset: query.Page.Offset, + Props: query.Props, + SelectExprs: selectExprs, + TypeHints: buildTypeHints(query.Props, mapping), + OrderBy: query.OrderBy, + GroupBy: query.GroupBy, + } + + items, err := fetch(c.Request().Context(), dbParams) + if err != nil { + log.Error("cannot fetch items", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get %s", mappingKey) + } + + return c.JSON(http.StatusOK, newListResponse(items, mapping, query)) +} diff --git a/server/handlers/list_query_parameters.go b/server/handlers/list_query_parameters.go index 6a207f4..2fd25a9 100644 --- a/server/handlers/list_query_parameters.go +++ b/server/handlers/list_query_parameters.go @@ -1,12 +1,19 @@ package serverhandlers -import "github.com/opensvc/oc3/server" +import ( + "fmt" + "strings" + + "github.com/opensvc/oc3/server" +) type ListQueryParameters struct { Page PageParams Props []string WithMeta bool WithStats bool + OrderBy []string + GroupBy []string } func buildListQueryParameters( @@ -15,6 +22,8 @@ func buildListQueryParameters( offset *server.InQueryOffset, meta *server.InQueryMeta, stats *server.InQueryStats, + orderby *server.InQueryOrderby, + groupby *server.InQueryGroupby, mapping propMapping, ) (ListQueryParameters, error) { selectedProps, err := buildProps(props, mapping) @@ -22,10 +31,79 @@ func buildListQueryParameters( return ListQueryParameters{}, err } + orderExprs, err := buildOrderBy(orderby, mapping) + if err != nil { + return ListQueryParameters{}, err + } + + groupExprs, err := buildGroupBy(groupby, mapping) + if err != nil { + return ListQueryParameters{}, err + } + return ListQueryParameters{ Page: buildPageParams(limit, offset), Props: selectedProps, WithMeta: queryWithMeta(meta), WithStats: queryWithStats(stats), + OrderBy: orderExprs, + GroupBy: groupExprs, }, nil } + +func buildGroupBy(groupby *server.InQueryGroupby, mapping propMapping) ([]string, error) { + if groupby == nil || *groupby == "" { + return nil, nil + } + tokens := strings.Split(*groupby, ",") + exprs := make([]string, 0, len(tokens)) + for _, token := range tokens { + token = strings.TrimSpace(token) + if token == "" { + continue + } + def, ok := mapping.Props[token] + if !ok { + return nil, fmt.Errorf("unknown groupby prop %q", token) + } + col := def.Col + if col == nil { + return nil, fmt.Errorf("prop %q cannot be used in groupby (no column reference)", token) + } + exprs = append(exprs, col.Qualified()) + } + return exprs, nil +} + +func buildOrderBy(orderby *server.InQueryOrderby, mapping propMapping) ([]string, error) { + if orderby == nil || *orderby == "" { + return nil, nil + } + tokens := strings.Split(*orderby, ",") + exprs := make([]string, 0, len(tokens)) + for _, token := range tokens { + token = strings.TrimSpace(token) + if token == "" { + continue + } + desc := false + if strings.HasPrefix(token, "-") { + desc = true + token = token[1:] + } + def, ok := mapping.Props[token] + if !ok { + return nil, fmt.Errorf("unknown orderby prop %q", token) + } + col := def.Col + if col == nil { + return nil, fmt.Errorf("prop %q cannot be used in orderby (no column reference)", token) + } + expr := col.Qualified() + if desc { + expr += " DESC" + } + exprs = append(exprs, expr) + } + return exprs, nil +} diff --git a/server/handlers/list_response.go b/server/handlers/list_response.go index b21c445..4aafa13 100644 --- a/server/handlers/list_response.go +++ b/server/handlers/list_response.go @@ -117,7 +117,7 @@ func newListResponse(items []map[string]any, mapping propMapping, query ListQuer response.Meta = &listMeta{ Count: len(items), - AvailableProps: allowedProps(mapping), + AvailableProps: availableProps(mapping), IncludedProps: query.Props, Limit: query.Page.Limit, Offset: query.Page.Offset, diff --git a/server/handlers/props.go b/server/handlers/props.go index 602f185..1455087 100644 --- a/server/handlers/props.go +++ b/server/handlers/props.go @@ -8,7 +8,31 @@ import ( "github.com/opensvc/oc3/server" ) -func allowedProps(mapping propMapping) []string { +func buildSelectClause(props []string, mapping propMapping) ([]string, error) { + exprs := make([]string, 0, len(props)) + for _, prop := range props { + if strings.Contains(prop, ".") { + // Cross-table prop: use "table.column" + exprs = append(exprs, prop) + continue + } + if mapping.Props == nil { + return nil, fmt.Errorf("no SQL expressions defined for this resource") + } + def, ok := mapping.Props[prop] + if !ok { + return nil, fmt.Errorf("no SQL expression for prop %q", prop) + } + expr := def.selectExpr() + if expr == "" { + return nil, fmt.Errorf("prop %q has no SQL expression or column reference", prop) + } + exprs = append(exprs, expr) + } + return exprs, nil +} + +func availableProps(mapping propMapping) []string { props := make([]string, 0, len(mapping.Available)) for _, prop := range mapping.Available { if _, blocked := mapping.Blacklist[prop]; !blocked { @@ -18,9 +42,23 @@ func allowedProps(mapping propMapping) []string { return props } +func defaultProps(mapping propMapping) []string { + source := mapping.Available + if mapping.Default != nil { + source = mapping.Default + } + props := make([]string, 0, len(source)) + for _, prop := range source { + if _, blocked := mapping.Blacklist[prop]; !blocked { + props = append(props, prop) + } + } + return props +} + func buildProps(props *server.InQueryProps, mapping propMapping) ([]string, error) { allowedSet := make(map[string]struct{}, len(mapping.Available)) - defaultProps := allowedProps(mapping) + defaultProps := defaultProps(mapping) for _, prop := range mapping.Available { allowedSet[prop] = struct{}{} @@ -37,6 +75,33 @@ func buildProps(props *server.InQueryProps, mapping propMapping) ([]string, erro if prop == "" { continue } + // Cross-table prop: "table.column" + if table, col, ok := strings.Cut(prop, "."); ok { + jd, joinKnown := mapping.Joins[table] + if !joinKnown { + return nil, fmt.Errorf("prop does not exist: %v", []string{table, col}) + } + refMapping, refFound := propsMapping[jd.MappingKey] + if !refFound { + return nil, fmt.Errorf("prop does not exist: %v", []string{table, col}) + } + colAllowed := false + for _, c := range refMapping.Available { + if c == col { + colAllowed = true + break + } + } + if !colAllowed { + return nil, fmt.Errorf("prop does not exist: %v", []string{table, col}) + } + if _, done := seen[prop]; done { + continue + } + seen[prop] = struct{}{} + selected = append(selected, prop) + continue + } if _, ok := allowedSet[prop]; !ok { return nil, fmt.Errorf("unknown prop %q", prop) } @@ -57,6 +122,30 @@ func buildProps(props *server.InQueryProps, mapping propMapping) ([]string, erro return selected, nil } +func buildTypeHints(props []string, mapping propMapping) map[string]string { + hints := make(map[string]string, len(props)) + for _, prop := range props { + if table, col, ok := strings.Cut(prop, "."); ok { + jd, joinKnown := mapping.Joins[table] + if !joinKnown { + continue + } + refMapping, refFound := propsMapping[jd.MappingKey] + if !refFound { + continue + } + if def, ok := refMapping.Props[col]; ok && def.Kind != "" { + hints[prop] = def.Kind + } + continue + } + if def, ok := mapping.Props[prop]; ok && def.Kind != "" { + hints[prop] = def.Kind + } + } + return hints +} + func filterItemFields(v any, props []string) (map[string]any, error) { data, err := json.Marshal(v) if err != nil { diff --git a/server/handlers/props_mapping.go b/server/handlers/props_mapping.go index f18ef52..60f03bf 100644 --- a/server/handlers/props_mapping.go +++ b/server/handlers/props_mapping.go @@ -1,13 +1,246 @@ package serverhandlers +import ( + "fmt" + + "github.com/opensvc/oc3/schema" +) + +type propDef struct { + Col *schema.Col + SQLExpr string + Kind string +} + +func (p propDef) selectExpr() string { + if p.SQLExpr != "" { + return p.SQLExpr + } + if p.Col != nil { + return p.Col.Qualified() + } + return "" +} + +func col(c *schema.Col) propDef { + return propDef{Col: c} +} + +func colStr(c *schema.Col) propDef { + return propDef{Col: c, SQLExpr: fmt.Sprintf("COALESCE(%s, '')", c.Qualified()), Kind: "string"} +} + +func colInt(c *schema.Col) propDef { + return propDef{Col: c, SQLExpr: fmt.Sprintf("COALESCE(%s, 0)", c.Qualified()), Kind: "int64"} +} + +type JoinDef struct { + MappingKey string +} + type propMapping struct { Available []string + // Default is the subset of Available returned when no props are requested. + // If nil, all Available props are returned by default. + Default []string Blacklist map[string]struct{} + // Props maps prop names to their propDef (column reference + optional SQL override). + // Enables column pushdown: only requested columns are fetched from DB. + Props map[string]propDef + // Joins declares joinable tables. A prop "table.column" is valid when "table" + // is a key in Joins and "column" is listed in JoinDef.Columns. + Joins map[string]JoinDef } var propsMapping = map[string]propMapping{ + "node": { + Available: []string{ + "node_id", "nodename", "app", "node_env", "cluster_id", + "loc_country", "loc_city", "loc_addr", "loc_building", "loc_floor", "loc_room", "loc_rack", "loc_zip", + "cpu_freq", "cpu_cores", "cpu_dies", "cpu_vendor", "cpu_model", "cpu_threads", + "mem_banks", "mem_slots", "mem_bytes", + "os_name", "os_release", "os_update", "os_segment", "os_arch", "os_vendor", "os_kernel", "os_concat", + "team_responsible", "team_integ", "team_support", + "serial", "model", "manufacturer", "type", "assetname", "asset_env", + "warranty_end", "maintenance_end", + "status", "role", "sec_zone", + "power_cabinet1", "power_cabinet2", "power_supply_nb", "power_protect", "power_protect_breaker", "power_breaker1", "power_breaker2", + "blade_cabinet", "enclosure", "enclosureslot", + "hv", "hvpool", "hvvdc", + "fqdn", "connect_to", "listener_port", "version", "collector", "sp_version", "bios_version", + "tz", "last_boot", "last_comm", + "node_frozen", "node_frozen_at", + "snooze_till", "notifications", "action_type", + "hw_obs_warn_date", "hw_obs_alert_date", "os_obs_warn_date", "os_obs_alert_date", + "updated", + }, + Props: map[string]propDef{ + "node_id": colStr(schema.NodesNodeID), + "nodename": colStr(schema.NodesNodename), + "app": colStr(schema.NodesApp), + "node_env": colStr(schema.NodesNodeEnv), + "cluster_id": colStr(schema.NodesClusterID), + "loc_country": colStr(schema.NodesLocCountry), + "loc_city": colStr(schema.NodesLocCity), + "loc_addr": colStr(schema.NodesLocAddr), + "loc_building": colStr(schema.NodesLocBuilding), + "loc_floor": colStr(schema.NodesLocFloor), + "loc_room": colStr(schema.NodesLocRoom), + "loc_rack": colStr(schema.NodesLocRack), + "loc_zip": colStr(schema.NodesLocZip), + "cpu_freq": colStr(schema.NodesCPUFreq), + "cpu_cores": colInt(schema.NodesCPUCores), + "cpu_dies": colInt(schema.NodesCPUDies), + "cpu_vendor": colStr(schema.NodesCPUVendor), + "cpu_model": colStr(schema.NodesCPUModel), + "cpu_threads": colInt(schema.NodesCPUThreads), + "mem_banks": colInt(schema.NodesMEMBanks), + "mem_slots": colInt(schema.NodesMEMSlots), + "mem_bytes": colInt(schema.NodesMEMBytes), + "os_name": colStr(schema.NodesOSName), + "os_release": colStr(schema.NodesOSRelease), + "os_update": colStr(schema.NodesOSUpdate), + "os_segment": colStr(schema.NodesOSSegment), + "os_arch": colStr(schema.NodesOSArch), + "os_vendor": colStr(schema.NodesOSVendor), + "os_kernel": colStr(schema.NodesOSKernel), + "os_concat": colStr(schema.NodesOSConcat), + "team_responsible": colStr(schema.NodesTeamResponsible), + "team_integ": colStr(schema.NodesTeamInteg), + "team_support": colStr(schema.NodesTeamSupport), + "serial": colStr(schema.NodesSerial), + "model": colStr(schema.NodesModel), + "manufacturer": colStr(schema.NodesManufacturer), + "type": colStr(schema.NodesType), + "assetname": colStr(schema.NodesAssetname), + "asset_env": colStr(schema.NodesAssetEnv), + "warranty_end": colStr(schema.NodesWarrantyEnd), + "maintenance_end": colStr(schema.NodesMaintenanceEnd), + "status": colStr(schema.NodesStatus), + "role": colStr(schema.NodesRole), + "sec_zone": colStr(schema.NodesSecZone), + "power_cabinet1": colStr(schema.NodesPowerCabinet1), + "power_cabinet2": colStr(schema.NodesPowerCabinet2), + "power_supply_nb": colInt(schema.NodesPowerSupplyNb), + "power_protect": colStr(schema.NodesPowerProtect), + "power_protect_breaker": colStr(schema.NodesPowerProtectBreaker), + "power_breaker1": colStr(schema.NodesPowerBreaker1), + "power_breaker2": colStr(schema.NodesPowerBreaker2), + "blade_cabinet": colStr(schema.NodesBladeCabinet), + "enclosure": colStr(schema.NodesEnclosure), + "enclosureslot": colStr(schema.NodesEnclosureslot), + "hv": colStr(schema.NodesHv), + "hvpool": colStr(schema.NodesHvpool), + "hvvdc": colStr(schema.NodesHvvdc), + "fqdn": colStr(schema.NodesFqdn), + "connect_to": colStr(schema.NodesConnectTo), + "listener_port": colInt(schema.NodesListenerPort), + "version": colStr(schema.NodesVersion), + "collector": colStr(schema.NodesCollector), + "sp_version": colStr(schema.NodesSpVersion), + "bios_version": colStr(schema.NodesBiosVersion), + "tz": colStr(schema.NodesTz), + "last_boot": colStr(schema.NodesLastBoot), + "last_comm": colStr(schema.NodesLastComm), + "node_frozen": colStr(schema.NodesNodeFrozen), + "node_frozen_at": colStr(schema.NodesNodeFrozenAt), + "snooze_till": colStr(schema.NodesSnoozeTill), + "notifications": colStr(schema.NodesNotifications), + "action_type": colStr(schema.NodesActionType), + "hw_obs_warn_date": colStr(schema.NodesHWObsWarnDate), + "hw_obs_alert_date": colStr(schema.NodesHWObsAlertDate), + "os_obs_warn_date": colStr(schema.NodesOSObsWarnDate), + "os_obs_alert_date": colStr(schema.NodesOSObsAlertDate), + "updated": colStr(schema.NodesUpdated), + }, + }, + "disk": { + Available: []string{ + "disk_id", "disk_name", "disk_devid", "disk_vendor", "disk_model", + "disk_size", "disk_used", "disk_alloc", "disk_raid", "disk_group", + "disk_level", "disk_arrayid", "disk_dg", "disk_region", + "node_id", "nodename", "svc_id", "svcname", "app", "updated", + }, + Props: map[string]propDef{ + "disk_id": col(schema.DiskinfoDiskID), + "disk_name": colStr(schema.DiskinfoDiskName), + "disk_devid": colStr(schema.DiskinfoDiskDevid), + "disk_vendor": colStr(schema.SvcdisksDiskVendor), + "disk_model": colStr(schema.SvcdisksDiskModel), + "disk_size": colInt(schema.DiskinfoDiskSize), + "disk_used": colInt(schema.SvcdisksDiskUsed), + "disk_alloc": colInt(schema.DiskinfoDiskAlloc), + "disk_raid": colStr(schema.DiskinfoDiskRaid), + "disk_group": colStr(schema.DiskinfoDiskGroup), + "disk_level": colInt(schema.DiskinfoDiskLevel), + "disk_arrayid": colStr(schema.DiskinfoDiskArrayid), + "disk_dg": colStr(schema.SvcdisksDiskDG), + "disk_region": colStr(schema.SvcdisksDiskRegion), + "node_id": colStr(schema.SvcdisksNodeID), + "nodename": colStr(schema.NodesNodename), + "svc_id": colStr(schema.SvcdisksSvcID), + "svcname": colStr(schema.ServicesSvcname), + "app": colStr(schema.AppsApp), + "updated": colStr(schema.DiskinfoDiskUpdated), + }, + }, + "node_interface": { + Available: []string{"id", "node_id", "intf", "mac", "type", "addr", "mask", "updated", "flag_deprecated"}, + Default: []string{"id", "node_id", "intf", "mac", "updated", "flag_deprecated"}, + Blacklist: map[string]struct{}{"type": {}, "addr": {}, "mask": {}}, + Props: map[string]propDef{ + "id": col(schema.NodeIPID), + "node_id": colStr(schema.NodeIPNodeID), + "intf": colStr(schema.NodeIPIntf), + "mac": colStr(schema.NodeIPMac), + "type": colStr(schema.NodeIPType), + "addr": colStr(schema.NodeIPAddr), + "mask": colStr(schema.NodeIPMask), + "updated": colStr(schema.NodeIPUpdated), + "flag_deprecated": colInt(schema.NodeIPFlagDeprecated), + }, + Joins: map[string]JoinDef{ + "nodes": { + MappingKey: "node", + }, + }, + }, + "hba": { + Available: []string{"id", "node_id", "hba_id", "hba_type", "updated"}, + Props: map[string]propDef{ + "id": col(schema.NodeHBAID), + "node_id": colStr(schema.NodeHBANodeID), + "hba_id": colStr(schema.NodeHBAHBAID), + "hba_type": colStr(schema.NodeHBAHBAType), + "updated": colStr(schema.NodeHBAUpdated), + }, + }, + "array": { + Available: []string{ + "id", "array_name", "array_comment", "array_model", + "array_firmware", "array_cache", "array_updated", "array_level", + }, + Props: map[string]propDef{ + "id": col(schema.StorArrayID), + "array_name": colStr(schema.StorArrayArrayName), + "array_comment": colStr(schema.StorArrayArrayComment), + "array_model": colStr(schema.StorArrayArrayModel), + "array_firmware": colStr(schema.StorArrayArrayFirmware), + "array_cache": colInt(schema.StorArrayArrayCache), + "array_updated": colStr(schema.StorArrayArrayUpdated), + "array_level": colInt(schema.StorArrayArrayLevel), + }, + }, "app": { Available: []string{"id", "app", "updated", "app_domain", "app_team_ops", "description"}, + Props: map[string]propDef{ + "id": col(schema.AppsID), + "app": col(schema.AppsApp), + "updated": colStr(schema.AppsUpdated), + "app_domain": colStr(schema.AppsAppDomain), + "app_team_ops": colStr(schema.AppsAppTeamOps), + "description": colStr(schema.AppsDescription), + }, }, "auth_group": { Available: []string{"id", "role", "privilege", "description"}, @@ -16,6 +249,128 @@ var propsMapping = map[string]propMapping{ Available: []string{"id", "tag_name", "tag_created", "tag_exclude", "tag_data", "tag_id"}, Blacklist: map[string]struct{}{"id": {}}, }, + "service": { + Available: []string{ + "svc_id", "svcname", "cluster_id", + "svc_status", "svc_availstatus", + "svc_app", "svc_env", "svc_ha", + "svc_topology", "svc_frozen", "svc_placement", "svc_provisioned", + "svc_flex_min_nodes", "svc_flex_max_nodes", + "svc_flex_cpu_low_threshold", "svc_flex_cpu_high_threshold", + "svc_autostart", + "svc_nodes", "svc_drpnode", "svc_drpnodes", + "svc_comment", "svc_created", "svc_status_updated", + "svc_notifications", "svc_snooze_till", + "updated", + }, + Default: []string{"svc_id", "svcname", "cluster_id", "svc_status", "svc_availstatus", "svc_app", "svc_env", "updated"}, + Props: map[string]propDef{ + "svc_id": colStr(schema.ServicesSvcID), + "svcname": colStr(schema.ServicesSvcname), + "cluster_id": colStr(schema.ServicesClusterID), + "svc_status": colStr(schema.ServicesSvcStatus), + "svc_availstatus": colStr(schema.ServicesSvcAvailstatus), + "svc_app": colStr(schema.ServicesSvcApp), + "svc_env": colStr(schema.ServicesSvcEnv), + "svc_ha": colStr(schema.ServicesSvcHa), + "svc_topology": colStr(schema.ServicesSvcTopology), + "svc_frozen": colStr(schema.ServicesSvcFrozen), + "svc_placement": colStr(schema.ServicesSvcPlacement), + "svc_provisioned": colStr(schema.ServicesSvcProvisioned), + "svc_flex_min_nodes": colInt(schema.ServicesSvcFlexMinNodes), + "svc_flex_max_nodes": colInt(schema.ServicesSvcFlexMaxNodes), + "svc_flex_cpu_low_threshold": colInt(schema.ServicesSvcFlexCPULowThreshold), + "svc_flex_cpu_high_threshold": colInt(schema.ServicesSvcFlexCPUHighThreshold), + "svc_autostart": colStr(schema.ServicesSvcAutostart), + "svc_nodes": colStr(schema.ServicesSvcNodes), + "svc_drpnode": colStr(schema.ServicesSvcDrpnode), + "svc_drpnodes": colStr(schema.ServicesSvcDrpnodes), + "svc_comment": colStr(schema.ServicesSvcComment), + "svc_created": colStr(schema.ServicesSvcCreated), + "svc_status_updated": colStr(schema.ServicesSvcStatusUpdated), + "svc_notifications": colStr(schema.ServicesSvcNotifications), + "svc_snooze_till": colStr(schema.ServicesSvcSnoozeTill), + "updated": colStr(schema.ServicesUpdated), + }, + }, + "instance": { + Available: []string{ + "svc_id", "node_id", + "mon_svctype", + "mon_availstatus", "mon_overallstatus", + "mon_smon_status", "mon_smon_global_expect", + "mon_ipstatus", "mon_fsstatus", "mon_diskstatus", "mon_containerstatus", + "mon_sharestatus", "mon_syncstatus", "mon_appstatus", "mon_hbstatus", + "mon_frozen", "mon_frozen_at", "mon_encap_frozen_at", + "mon_vmname", "mon_vmtype", "mon_guestos", "mon_vcpus", "mon_vmem", + "mon_updated", "mon_changed", + }, + Default: []string{ + "svc_id", "node_id", + "mon_availstatus", "mon_overallstatus", "mon_smon_status", + "mon_frozen", "mon_updated", + }, + Props: map[string]propDef{ + "svc_id": colStr(schema.SvcmonSvcID), + "node_id": colStr(schema.SvcmonNodeID), + "mon_svctype": colStr(schema.SvcmonMonSvctype), + "mon_availstatus": colStr(schema.SvcmonMonAvailstatus), + "mon_overallstatus": colStr(schema.SvcmonMonOverallstatus), + "mon_smon_status": colStr(schema.SvcmonMonSmonStatus), + "mon_smon_global_expect": colStr(schema.SvcmonMonSmonGlobalExpect), + "mon_ipstatus": colStr(schema.SvcmonMonIpstatus), + "mon_fsstatus": colStr(schema.SvcmonMonFsstatus), + "mon_diskstatus": colStr(schema.SvcmonMonDiskstatus), + "mon_containerstatus": colStr(schema.SvcmonMonContainerstatus), + "mon_sharestatus": colStr(schema.SvcmonMonSharestatus), + "mon_syncstatus": colStr(schema.SvcmonMonSyncstatus), + "mon_appstatus": colStr(schema.SvcmonMonAppstatus), + "mon_hbstatus": colStr(schema.SvcmonMonHbstatus), + "mon_frozen": colStr(schema.SvcmonMonFrozen), + "mon_frozen_at": colStr(schema.SvcmonMonFrozenAt), + "mon_encap_frozen_at": colStr(schema.SvcmonMonEncapFrozenAt), + "mon_vmname": colStr(schema.SvcmonMonVmname), + "mon_vmtype": colStr(schema.SvcmonMonVmtype), + "mon_guestos": colStr(schema.SvcmonMonGuestos), + "mon_vcpus": colInt(schema.SvcmonMonVcpus), + "mon_vmem": colInt(schema.SvcmonMonVmem), + "mon_updated": colStr(schema.SvcmonMonUpdated), + "mon_changed": colStr(schema.SvcmonMonChanged), + }, + }, + "instance_status_log": { + Available: []string{ + "id", + "svc_id", "node_id", + "mon_begin", "mon_end", + "mon_availstatus", "mon_overallstatus", + "mon_ipstatus", "mon_fsstatus", "mon_diskstatus", + "mon_sharestatus", "mon_containerstatus", + "mon_syncstatus", "mon_hbstatus", "mon_appstatus", + }, + Default: []string{ + "svc_id", "node_id", + "mon_begin", "mon_end", + "mon_availstatus", "mon_overallstatus", + }, + Props: map[string]propDef{ + "id": col(schema.SvcmonLogID), + "svc_id": colStr(schema.SvcmonLogSvcID), + "node_id": colStr(schema.SvcmonLogNodeID), + "mon_begin": colStr(schema.SvcmonLogMonBegin), + "mon_end": colStr(schema.SvcmonLogMonEnd), + "mon_availstatus": colStr(schema.SvcmonLogMonAvailstatus), + "mon_overallstatus": colStr(schema.SvcmonLogMonOverallstatus), + "mon_ipstatus": colStr(schema.SvcmonLogMonIpstatus), + "mon_fsstatus": colStr(schema.SvcmonLogMonFsstatus), + "mon_diskstatus": colStr(schema.SvcmonLogMonDiskstatus), + "mon_sharestatus": colStr(schema.SvcmonLogMonSharestatus), + "mon_containerstatus": colStr(schema.SvcmonLogMonContainerstatus), + "mon_syncstatus": colStr(schema.SvcmonLogMonSyncstatus), + "mon_hbstatus": colStr(schema.SvcmonLogMonHbstatus), + "mon_appstatus": colStr(schema.SvcmonLogMonAppstatus), + }, + }, "moduleset": { Available: []string{"id", "modset_name", "modset_author", "modset_updated"}, }, From 57bddc7816e64f3954d54a101f9e49ac2d2ea8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Tue, 14 Apr 2026 19:31:05 +0200 Subject: [PATCH 13/24] Add GET /disks and /disks/{id} endpoints --- cdb/db_disks.go | 102 ++++++++++++++++ server/api.yaml | 62 ++++++++++ server/codegen_server_gen.go | 218 +++++++++++++++++++++++++++++------ server/codegen_type_gen.go | 48 ++++++++ server/handlers/get_disk.go | 52 +++++++++ server/handlers/get_disks.go | 21 ++++ 6 files changed, 466 insertions(+), 37 deletions(-) create mode 100644 cdb/db_disks.go create mode 100644 server/handlers/get_disk.go create mode 100644 server/handlers/get_disks.go diff --git a/cdb/db_disks.go b/cdb/db_disks.go new file mode 100644 index 0000000..d834b01 --- /dev/null +++ b/cdb/db_disks.go @@ -0,0 +1,102 @@ +package cdb + +import ( + "context" + "fmt" + + "github.com/opensvc/oc3/qb" + "github.com/opensvc/oc3/schema" +) + +func buildDisksQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { + q := qb.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) +} diff --git a/server/api.yaml b/server/api.yaml index 9a9d131..528d827 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -241,6 +241,68 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /disks: + get: + operationId: GetDisks + description: List disks + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /disks/{disk_id}: + get: + operationId: GetDisk + description: List entries for a specific disk + parameters: + - in: path + name: disk_id + required: true + description: Disk identifier + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /auth/node: post: description: | diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index c84fc5a..ee29857 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -45,6 +45,12 @@ type ServerInterface interface { // (POST /auth/node) PostAuthNode(ctx echo.Context) error + // (GET /disks) + GetDisks(ctx echo.Context, params GetDisksParams) error + + // (GET /disks/{disk_id}) + GetDisk(ctx echo.Context, diskId string, params GetDiskParams) error + // (GET /nodes/{node_id}/compliance/candidate_modulesets) GetNodeComplianceCandidateModulesets(ctx echo.Context, nodeId InPathNodeId, params GetNodeComplianceCandidateModulesetsParams) error @@ -394,6 +400,141 @@ func (w *ServerInterfaceWrapper) PostAuthNode(ctx echo.Context) error { return err } +// GetDisks converts echo context to params. +func (w *ServerInterfaceWrapper) GetDisks(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetDisksParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetDisks(ctx, params) + return err +} + +// GetDisk converts echo context to params. +func (w *ServerInterfaceWrapper) GetDisk(ctx echo.Context) error { + var err error + // ------------- Path parameter "disk_id" ------------- + var diskId string + + err = runtime.BindStyledParameterWithOptions("simple", "disk_id", ctx.Param("disk_id"), &diskId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter disk_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetDiskParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetDisk(ctx, diskId, params) + return err +} + // GetNodeComplianceCandidateModulesets converts echo context to params. func (w *ServerInterfaceWrapper) GetNodeComplianceCandidateModulesets(ctx echo.Context) error { var err error @@ -1009,6 +1150,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/apps/:app_id/publications", wrapper.GetAppPublications) router.GET(baseURL+"/apps/:app_id/responsibles", wrapper.GetAppResponsibles) router.POST(baseURL+"/auth/node", wrapper.PostAuthNode) + router.GET(baseURL+"/disks", wrapper.GetDisks) + router.GET(baseURL+"/disks/:disk_id", wrapper.GetDisk) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_modulesets", wrapper.GetNodeComplianceCandidateModulesets) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_rulesets", wrapper.GetNodeComplianceCandidateRulesets) router.GET(baseURL+"/nodes/:node_id/compliance/logs", wrapper.GetNodeComplianceLogs) @@ -1029,43 +1172,44 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/wUAAP//o9hXbbxFAAA=", + "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", + "dEC7Pbq4XPwvAAD//+hfacmnSwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 7abb0fe..d259937 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -194,6 +194,54 @@ type PostAuthNodeJSONBody struct { Nodename string `json:"nodename"` } +// GetDisksParams defines parameters for GetDisks. +type GetDisksParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + +// GetDiskParams defines parameters for GetDisk. +type GetDiskParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetNodeComplianceCandidateModulesetsParams defines parameters for GetNodeComplianceCandidateModulesets. type GetNodeComplianceCandidateModulesetsParams struct { // Props A list of properties to include in each data dictionnary. diff --git a/server/handlers/get_disk.go b/server/handlers/get_disk.go new file mode 100644 index 0000000..6a8ec94 --- /dev/null +++ b/server/handlers/get_disk.go @@ -0,0 +1,52 @@ +package serverhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetDisk handles GET /disks/{disk_id} +func (a *Api) GetDisk(c echo.Context, diskId string, params server.GetDiskParams) error { + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["disk"]) + if err != nil { + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log := echolog.GetLogHandler(c, "GetDisk") + odb := a.getODB() + ctx := c.Request().Context() + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", "disk_id", diskId, "props", query.Props, "is_manager", isManager) + + selectExprs, err := buildSelectClause(query.Props, propsMapping["disk"]) + if err != nil { + log.Error("cannot build select clause", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot build select clause") + } + + disks, err := odb.GetDisk(ctx, diskId, cdb.ListParams{ + Groups: groups, + IsManager: isManager, + Limit: query.Page.Limit, + Offset: query.Page.Offset, + Props: query.Props, + SelectExprs: selectExprs, + }) + if err != nil { + log.Error("cannot get disk", "disk_id", diskId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get disk") + } + if len(disks) == 0 { + return JSONProblemf(c, http.StatusNotFound, "disk %s not found", diskId) + } + + return c.JSON(http.StatusOK, newListResponse(disks, propsMapping["disk"], query)) +} diff --git a/server/handlers/get_disks.go b/server/handlers/get_disks.go new file mode 100644 index 0000000..017c52a --- /dev/null +++ b/server/handlers/get_disks.go @@ -0,0 +1,21 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" +) + +// GetDisks handles GET /disks +func (a *Api) GetDisks(c echo.Context, params server.GetDisksParams) error { + odb := a.getODB() + return a.handleList(c, "GetDisks", "disk", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetDisks(ctx, p) + }) +} From 31366257bbd4a666be07aaa69abae0f99a769493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Tue, 14 Apr 2026 22:56:46 +0200 Subject: [PATCH 14/24] Add GET /nodes, /nodes/{id}, and /nodes/{id}/uuid endpoints --- cdb/db_auth_node.go | 5 +- cdb/db_nodes.go | 69 +++++++++ server/api.yaml | 94 ++++++++++++ server/codegen_server_gen.go | 245 ++++++++++++++++++++++++++----- server/codegen_type_gen.go | 48 ++++++ server/handlers/get_node.go | 52 +++++++ server/handlers/get_node_uuid.go | 65 ++++++++ server/handlers/get_nodes.go | 21 +++ 8 files changed, 559 insertions(+), 40 deletions(-) create mode 100644 server/handlers/get_node.go create mode 100644 server/handlers/get_node_uuid.go create mode 100644 server/handlers/get_nodes.go diff --git a/cdb/db_auth_node.go b/cdb/db_auth_node.go index e09e834..30fbc75 100644 --- a/cdb/db_auth_node.go +++ b/cdb/db_auth_node.go @@ -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) @@ -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 diff --git a/cdb/db_nodes.go b/cdb/db_nodes.go index 7624440..5a4e841 100644 --- a/cdb/db_nodes.go +++ b/cdb/db_nodes.go @@ -11,6 +11,8 @@ import ( "github.com/google/uuid" + "github.com/opensvc/oc3/qb" + "github.com/opensvc/oc3/schema" "github.com/opensvc/oc3/util/logkey" ) @@ -49,6 +51,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 := qb.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 == "" { diff --git a/server/api.yaml b/server/api.yaml index 528d827..9129a10 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -241,6 +241,100 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /nodes: + get: + operationId: GetNodes + description: List nodes + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /nodes/{node_id}: + get: + operationId: GetNode + description: List entries for a specific node + parameters: + - in: path + name: node_id + required: true + description: Node identifier (node_id UUID or nodename) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /nodes/{node_id}/uuid: + get: + operationId: GetNodeUUID + description: Display node uuid. Only node responsibles and managers are allowed. + parameters: + - in: path + name: node_id + required: true + description: Node identifier (node_id UUID or nodename) + schema: + type: string + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /disks: get: operationId: GetDisks diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index ee29857..4bc25df 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -51,6 +51,12 @@ type ServerInterface interface { // (GET /disks/{disk_id}) GetDisk(ctx echo.Context, diskId string, params GetDiskParams) error + // (GET /nodes) + GetNodes(ctx echo.Context, params GetNodesParams) error + + // (GET /nodes/{node_id}) + GetNode(ctx echo.Context, nodeId string, params GetNodeParams) error + // (GET /nodes/{node_id}/compliance/candidate_modulesets) GetNodeComplianceCandidateModulesets(ctx echo.Context, nodeId InPathNodeId, params GetNodeComplianceCandidateModulesetsParams) error @@ -78,6 +84,9 @@ type ServerInterface interface { // (POST /nodes/{node_id}/compliance/rulesets/{rset_id}) PostNodeComplianceRuleset(ctx echo.Context, nodeId InPathNodeId, rsetId InPathRsetId) error + // (GET /nodes/{node_id}/uuid) + GetNodeUUID(ctx echo.Context, nodeId string) error + // (GET /openapi.json) GetSwagger(ctx echo.Context) error @@ -535,6 +544,141 @@ func (w *ServerInterfaceWrapper) GetDisk(ctx echo.Context) error { return err } +// GetNodes converts echo context to params. +func (w *ServerInterfaceWrapper) GetNodes(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetNodesParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNodes(ctx, params) + return err +} + +// GetNode converts echo context to params. +func (w *ServerInterfaceWrapper) GetNode(ctx echo.Context) error { + var err error + // ------------- Path parameter "node_id" ------------- + var nodeId string + + err = runtime.BindStyledParameterWithOptions("simple", "node_id", ctx.Param("node_id"), &nodeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter node_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetNodeParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNode(ctx, nodeId, params) + return err +} + // GetNodeComplianceCandidateModulesets converts echo context to params. func (w *ServerInterfaceWrapper) GetNodeComplianceCandidateModulesets(ctx echo.Context) error { var err error @@ -960,6 +1104,26 @@ func (w *ServerInterfaceWrapper) PostNodeComplianceRuleset(ctx echo.Context) err return err } +// GetNodeUUID converts echo context to params. +func (w *ServerInterfaceWrapper) GetNodeUUID(ctx echo.Context) error { + var err error + // ------------- Path parameter "node_id" ------------- + var nodeId string + + err = runtime.BindStyledParameterWithOptions("simple", "node_id", ctx.Param("node_id"), &nodeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter node_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNodeUUID(ctx, nodeId) + return err +} + // GetSwagger converts echo context to params. func (w *ServerInterfaceWrapper) GetSwagger(ctx echo.Context) error { var err error @@ -1152,6 +1316,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/auth/node", wrapper.PostAuthNode) router.GET(baseURL+"/disks", wrapper.GetDisks) router.GET(baseURL+"/disks/:disk_id", wrapper.GetDisk) + router.GET(baseURL+"/nodes", wrapper.GetNodes) + router.GET(baseURL+"/nodes/:node_id", wrapper.GetNode) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_modulesets", wrapper.GetNodeComplianceCandidateModulesets) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_rulesets", wrapper.GetNodeComplianceCandidateRulesets) router.GET(baseURL+"/nodes/:node_id/compliance/logs", wrapper.GetNodeComplianceLogs) @@ -1161,6 +1327,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/nodes/:node_id/compliance/rulesets", wrapper.GetNodeComplianceRulesets) router.DELETE(baseURL+"/nodes/:node_id/compliance/rulesets/:rset_id", wrapper.DeleteNodeComplianceRuleset) router.POST(baseURL+"/nodes/:node_id/compliance/rulesets/:rset_id", wrapper.PostNodeComplianceRuleset) + router.GET(baseURL+"/nodes/:node_id/uuid", wrapper.GetNodeUUID) router.GET(baseURL+"/openapi.json", wrapper.GetSwagger) router.GET(baseURL+"/tags", wrapper.GetTags) router.GET(baseURL+"/tags/:tag_id", wrapper.GetTag) @@ -1172,44 +1339,46 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "dEC7Pbq4XPwvAAD//+hfacmnSwAA", + "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", + "//8MCL98LVQAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index d259937..82a3c01 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -242,6 +242,54 @@ type GetDiskParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetNodesParams defines parameters for GetNodes. +type GetNodesParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + +// GetNodeParams defines parameters for GetNode. +type GetNodeParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetNodeComplianceCandidateModulesetsParams defines parameters for GetNodeComplianceCandidateModulesets. type GetNodeComplianceCandidateModulesetsParams struct { // Props A list of properties to include in each data dictionnary. diff --git a/server/handlers/get_node.go b/server/handlers/get_node.go new file mode 100644 index 0000000..b753a02 --- /dev/null +++ b/server/handlers/get_node.go @@ -0,0 +1,52 @@ +package serverhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetNode handles GET /nodes/{node_id} +func (a *Api) GetNode(c echo.Context, nodeId string, params server.GetNodeParams) error { + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["node"]) + if err != nil { + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log := echolog.GetLogHandler(c, "GetNode") + odb := a.getODB() + ctx := c.Request().Context() + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", "node_id", nodeId, "props", query.Props, "is_manager", isManager) + + selectExprs, err := buildSelectClause(query.Props, propsMapping["node"]) + if err != nil { + log.Error("cannot build select clause", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot build select clause") + } + + nodes, err := odb.GetNode(ctx, nodeId, cdb.ListParams{ + Groups: groups, + IsManager: isManager, + Limit: query.Page.Limit, + Offset: query.Page.Offset, + Props: query.Props, + SelectExprs: selectExprs, + }) + if err != nil { + log.Error("cannot get node", "node_id", nodeId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get node") + } + if len(nodes) == 0 { + return JSONProblemf(c, http.StatusNotFound, "node %s not found", nodeId) + } + + return c.JSON(http.StatusOK, newListResponse(nodes, propsMapping["node"], query)) +} diff --git a/server/handlers/get_node_uuid.go b/server/handlers/get_node_uuid.go new file mode 100644 index 0000000..215c46b --- /dev/null +++ b/server/handlers/get_node_uuid.go @@ -0,0 +1,65 @@ +package serverhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetNodeUUID handles GET /nodes/{node_id}/uuid +func (a *Api) GetNodeUUID(c echo.Context, nodeId string) error { + log := echolog.GetLogHandler(c, "GetNodeUUID") + odb := a.getODB() + ctx := c.Request().Context() + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", "node_id", nodeId) + + if !IsAuthByUser(c) { + return JSONProblemf(c, http.StatusUnauthorized, "user authentication required") + } + + node, err := odb.NodeByNodeIDOrNodename(ctx, nodeId) + if err != nil { + log.Error("cannot resolve node", "node_id", nodeId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve node") + } + if node == nil { + return JSONProblemf(c, http.StatusNotFound, "node %s not found", nodeId) + } + + responsible, err := odb.NodeResponsible(ctx, node.NodeID, groups, isManager) + if err != nil { + log.Error("cannot check node responsibility", "node_id", nodeId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot check node responsibility") + } + if !responsible { + return JSONProblemf(c, http.StatusForbidden, "you are not responsible for this node") + } + + authNodes, err := odb.AuthNodesByNodeID(ctx, node.NodeID) + if err != nil { + log.Error("cannot get auth node", "node_id", node.NodeID, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get node uuid") + } + if len(authNodes) == 0 { + return JSONProblemf(c, http.StatusNotFound, "no uuid found for node %s", nodeId) + } + + an := authNodes[0] + return c.JSON(http.StatusOK, map[string]any{ + "data": []map[string]any{ + { + "id": an.ID, + "nodename": an.Nodename, + "uuid": an.UUID, + "node_id": an.NodeID, + "updated": an.Updated, + }, + }, + }) +} diff --git a/server/handlers/get_nodes.go b/server/handlers/get_nodes.go new file mode 100644 index 0000000..cb4ca22 --- /dev/null +++ b/server/handlers/get_nodes.go @@ -0,0 +1,21 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" +) + +// GetNodes handles GET /nodes +func (a *Api) GetNodes(c echo.Context, params server.GetNodesParams) error { + odb := a.getODB() + return a.handleList(c, "GetNodes", "node", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetNodes(ctx, p) + }) +} From 74f96f689f4a9bf87a2cb36d08348433d6a8de28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 15 Apr 2026 10:07:10 +0200 Subject: [PATCH 15/24] Add GET /services and /services/{id} endpoints --- cdb/db_services.go | 76 +++++++++++ server/api.yaml | 62 +++++++++ server/codegen_server_gen.go | 224 ++++++++++++++++++++++++++------ server/codegen_type_gen.go | 48 +++++++ server/handlers/get_service.go | 52 ++++++++ server/handlers/get_services.go | 21 +++ 6 files changed, 443 insertions(+), 40 deletions(-) create mode 100644 cdb/db_services.go create mode 100644 server/handlers/get_service.go create mode 100644 server/handlers/get_services.go diff --git a/cdb/db_services.go b/cdb/db_services.go new file mode 100644 index 0000000..9cf9e79 --- /dev/null +++ b/cdb/db_services.go @@ -0,0 +1,76 @@ +package cdb + +import ( + "context" + "fmt" + + "github.com/opensvc/oc3/qb" + "github.com/opensvc/oc3/schema" +) + +func buildServicesQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { + q := qb.From(schema.TServices). + 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( + "services.svc_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.ServicesID, ">", 0) + } + + query, args, err := q.Build() + if err != nil { + panic(fmt.Sprintf("buildServicesQuery: %v", err)) + } + return query, args +} + +func (oDb *DB) GetServices(ctx context.Context, p ListParams) ([]map[string]any, error) { + query, args := buildServicesQuery(p.Groups, p.IsManager, p.SelectExprs) + if gb := p.GroupByClause(""); gb != "" { + query += " " + gb + } + query += " " + p.OrderByClause("services.svcname") + query, args = appendLimitOffset(query, args, p.Limit, p.Offset) + + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("getServices: %w", err) + } + defer func() { _ = rows.Close() }() + + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// GetService fetches a single service by svc_id (UUID) or svcname. +func (oDb *DB) GetService(ctx context.Context, svcID string, p ListParams) ([]map[string]any, error) { + query, args := buildServicesQuery(p.Groups, p.IsManager, p.SelectExprs) + query += " AND (services.svc_id = ? OR services.svcname = ?)" + args = append(args, svcID, svcID) + query, args = appendLimitOffset(query, args, p.Limit, p.Offset) + + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("getService: %w", err) + } + defer func() { _ = rows.Close() }() + + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} diff --git a/server/api.yaml b/server/api.yaml index 9129a10..79b9219 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -335,6 +335,68 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /services: + get: + operationId: GetServices + description: List services + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /services/{svc_id}: + get: + operationId: GetService + description: List entries for a specific service + parameters: + - in: path + name: svc_id + required: true + description: Service identifier (svc_id UUID or svcname) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /disks: get: operationId: GetDisks diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 4bc25df..0b2055e 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -90,6 +90,12 @@ type ServerInterface interface { // (GET /openapi.json) GetSwagger(ctx echo.Context) error + // (GET /services) + GetServices(ctx echo.Context, params GetServicesParams) error + + // (GET /services/{svc_id}) + GetService(ctx echo.Context, svcId string, params GetServiceParams) error + // (GET /tags) GetTags(ctx echo.Context, params GetTagsParams) error @@ -1133,6 +1139,141 @@ func (w *ServerInterfaceWrapper) GetSwagger(ctx echo.Context) error { return err } +// GetServices converts echo context to params. +func (w *ServerInterfaceWrapper) GetServices(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetServicesParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetServices(ctx, params) + return err +} + +// GetService converts echo context to params. +func (w *ServerInterfaceWrapper) GetService(ctx echo.Context) error { + var err error + // ------------- Path parameter "svc_id" ------------- + var svcId string + + err = runtime.BindStyledParameterWithOptions("simple", "svc_id", ctx.Param("svc_id"), &svcId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter svc_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetServiceParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetService(ctx, svcId, params) + return err +} + // GetTags converts echo context to params. func (w *ServerInterfaceWrapper) GetTags(ctx echo.Context) error { var err error @@ -1329,6 +1470,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/nodes/:node_id/compliance/rulesets/:rset_id", wrapper.PostNodeComplianceRuleset) router.GET(baseURL+"/nodes/:node_id/uuid", wrapper.GetNodeUUID) router.GET(baseURL+"/openapi.json", wrapper.GetSwagger) + router.GET(baseURL+"/services", wrapper.GetServices) + router.GET(baseURL+"/services/:svc_id", wrapper.GetService) router.GET(baseURL+"/tags", wrapper.GetTags) router.GET(baseURL+"/tags/:tag_id", wrapper.GetTag) router.GET(baseURL+"/tags/:tag_id/nodes", wrapper.GetTagNodes) @@ -1339,46 +1482,47 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xcW5PaOPb/Kir9/w8zVQTIpOdhqJqHTHqS6t1cekl69yHp6hL2ATSxJUWSO81SfPet", - "I9nGNDIYOg0k4ycKdKRz0e9cJB8zp5FMlRQgrKGDOVVMsxQsaPeNi0tmp28M2IsYv8dgIs2V5VLQAb04", - "J3JM7BRIKuMsAQOWdijHIcXslHaoYCnQAU0N2Bse0w7V8CXjGmI6sDqDDjXRFFKGS9uZQlJjNRcTulh0", - "cuZvZQybmQsZQ5gvjuzLd7hVab1JZb2nyv/KQM9eaZmp0Wyd+QuZpuyJAdwlCzFJuLEojtJSgbYcDLGS", - "THC6FxFMllgympGfoDvp+pHR7HemVMfcRijrz91CgS/IeqlBTksbSfyap9yuy/sBscHueJqlRGTpCDRK", - "C8LqXFQNNtOiS/okBSYMEZIkuFSdUG5wRaQYxixLLB382u/QlAvkRQf9TiErFxYmoKvCvgHLAhsroiSL", - "gaRgWcwsI1wUNlRSGOiSPwUbJRCjOXOuXXJlgIxZYoBITfqokky59U4BlpExhySu0wYpmtn33XiMUFsT", - "+v1n7nd6zLWxpWVzhDo1okwbqetEkH7hoEUbG/SdjkHvj1cjNWK0Sy41jPkdYcX4jHzldkqekLHUBFcG", - "EXMxIRL55ZCWnvfv6OuoU+cJU6oW1Dl1M6NfaqnMulLPa9TgOYC4IMCiqbd+zCOcJpie1cmkHJtGEr23", - "zJqQmYXVMjFu050YhkuxBDD6GMRVWVB6Rj5Rgwt+ouQzzDokksIyLtDCOM9AAhHuWkXNmBvLRWTJLUsy", - "MCSSmbCmTjO3+kbNFhgfvX85vc76ffxASUA4vDOlEh4xFLz3l0F155X1/l/DmA7o//WWWaznR03vUstR", - "AqnnsmqwP1hMhvAlA2PpokPP+k8PwfVKsMxOpeb/hdizfXYIti+lHvE4BuF5nh2C51tpyUuZiVzP3w7B", - "84UU44RHbkd/PQyOLoQFLVhC3oO+BU3+1Fr6yJhPxrVfc2OLlLN0JSfTLeMJ5pQbVYQabiE1AU8pwy/T", - "ms3wu3O9CmUZlzu08FLHI445SsuSyxXe67PyX+ToL/BGzANavI90SVEQrPORZS4LyCAtS0JDIfnQsMM8", - "eqwbF8MvfkoB78Z08HFd+uVK96Svt9oDrHnvh+tFx+f/Legr0eNjZVFLfvT6XQcYFXhds4iFOxtKZ9Ms", - "ZeKJBhYjFgncqYQJ5ynEKIj4mEeY3+yUGyKjKNMaRAR5kfFJKM+v+0nQTiB1VWV2EoRkvgVtuHfKVZkr", - "A3DHUpXgvH633326lVkxdZ0f+idEmeZ29h7N7FmNmOHR88xOy9CAc9yvS15TaxUKPAKmQRfU/ttLqVNm", - "6YD+4z8fiqznlnCj99fwaX0s3c5w6xSTCoS5jUgkE8y9UhOmOK2Yhz7t9rvPnBcpEDg4oM+6/W6fdtwB", - "xCnSY8q76yRUMiKgSBl5iKN1y2m35Xjmoa/APve/V8+DH8NIXZL0Vgon9KNm9P700Jw+r4abT/AO1Jjc", - "l1k7yJNXlc1nFCe8xfW9AuiXb5i4VuJjIHu9+2clVYYWKiXrIVHVbRwYKg7z8Rp1rzrFx2vUzbIJAoeW", - "gKYY95Q0AWC+0MAsECZIRWUS+dP9Kj4vpSkAqn0Z94eMZzsZ7l4mViqY1phSN7FMGRe1wxZYepPnxzWC", - "FQ3nWwIWChEIVov7dwiLB2ImwCAEjLMmwECiZQW9jfZppezdRvusUjpuo/3tKEBedHyo7c0RBzxeeEwn", - "YGEd3efu90bo9qThAHwvdytFNERSx4THxKULVawZuJTyYu50J3V9KKw9Fn7OmtCenTzWOuFkfs6NStjM", - "7XslooXz+fHR1NmxgjgY+pqj5PhRpsfSG36TM+OjBGpLvaG7+SG4K4Tnl9Y+XYIm3JDKGu6KzVX4W8OT", - "B9Pz9GJYEeFHCVQjKRNg4rQj1QlgUGWjwpjbjhrrGY9UZvtnEnUh67LK5rsLX+0B6Hs4AH1nnlcJ2nt4", - "XjXkb/S8YZVN63mt5/0tPS+z057rLxjMa24thjDhrp5irhGB4BQQNjebuxMN3F1kvrHh0e8viqei2y8f", - "SsrQDcRDbxxWhS1vPe9Lm2U83i6po8rvTsPXJfeiUxSBsiuP3E7n7sHhsTlAEV05NmNuPm9JAJ4kEN/P", - "84H2dre93X2EsOlw15vjR3EtVg/SomcFD6Bs+cAJJ9dBd1s9gjSExxiGx9w9eQlUIbl0bRnSliGnXoZg", - "bt4S6j1JwF/e5gNtqG9D/WNBszfPO233C/Wi5pYxr5A3hnqkqYR68lMuCbm6ujjHo2hR1/78DVuEW/dp", - "s8DRXc0xTTgTEfQiJmIeMws3ZSd+fb54BZaUE5at+4Vb4vqBY2vujy9Kpi+KJd4sWe6eZiot/q1f/WB+", - "9Tpvkw6BreJxgYAupCXjon00d7aazkvjOy9h2Xl5SHfTuzmbfqCrDVtHax2tiaPpH8HNEjnZ4lglLUHa", - "Hb3qNS5/WEd6KEiadjCHmuPXTVW+PPc9g6RhwVMpc5i1LJpCTKzcAS1tldMG343Bt4TVD1LkLNXozfMX", - "eLc0OKL+hC31J2Mt03oX822ONV52ACervNocAFBg10rhiMmiCIwZZ0kyIzH4jd+821JXDHPsra9rwX5u", - "1/ZwU5C8lMae0P7t9yB1j3brXzZho4gDp9JCfWp3FY2OTPphybo9J7Wpukmq/iGOSbpM03qXNK33TtLD", - "g4b44S4pevigBK2/l/Ss90rOR9y3Y6bmYZuY6+NI0XK18e0S19iGlF3yTiT592ofKmEiJikTbALaEKaB", - "sCSRX8H980kwV19dXZyf4rO9v9H7TkfBX/7acrewXI67NYy8/8omE9e6c/THgifg/YU1/XsTuSn9jxuf", - "t9+5P4KYEEca8MQPbNL2hrS9IY/k6/h7b27ZZGNnSJFkLJuU//tUg9VtCWP532zWEQdygpemSUqo/PVI", - "+9biw/d/SxPbK8h72MjXKWgoNpHw5RVADShqGtxOEhmPHiyP94CpOFvnm8jt1L9OivauO3d9YJPgWeuw", - "MK38xUwQmv7/0whTnBSkAST+uxx6tFBfcP82Ub40B1NsxBPu3s64XnjL4qHW+1GmEzqg3R5dXC/+FwAA", - "//8MCL98LVQAAA==", + "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", + "AP//P7rbwERaAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 82a3c01..198965a 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -398,6 +398,54 @@ type GetNodeComplianceRulesetsParams struct { // PostNodeComplianceRulesetJSONBody defines parameters for PostNodeComplianceRuleset. type PostNodeComplianceRulesetJSONBody = map[string]interface{} +// GetServicesParams defines parameters for GetServices. +type GetServicesParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + +// GetServiceParams defines parameters for GetService. +type GetServiceParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetTagsParams defines parameters for GetTags. type GetTagsParams struct { // Props A list of properties to include in each data dictionnary. diff --git a/server/handlers/get_service.go b/server/handlers/get_service.go new file mode 100644 index 0000000..0b36f55 --- /dev/null +++ b/server/handlers/get_service.go @@ -0,0 +1,52 @@ +package serverhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetService handles GET /services/{svc_id} +func (a *Api) GetService(c echo.Context, svcId string, params server.GetServiceParams) error { + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["service"]) + if err != nil { + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log := echolog.GetLogHandler(c, "GetService") + odb := a.getODB() + ctx := c.Request().Context() + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", "svc_id", svcId, "props", query.Props, "is_manager", isManager) + + selectExprs, err := buildSelectClause(query.Props, propsMapping["service"]) + if err != nil { + log.Error("cannot build select clause", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot build select clause") + } + + services, err := odb.GetService(ctx, svcId, cdb.ListParams{ + Groups: groups, + IsManager: isManager, + Limit: query.Page.Limit, + Offset: query.Page.Offset, + Props: query.Props, + SelectExprs: selectExprs, + }) + if err != nil { + log.Error("cannot get service", "svc_id", svcId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get service") + } + if len(services) == 0 { + return JSONProblemf(c, http.StatusNotFound, "service %s not found", svcId) + } + + return c.JSON(http.StatusOK, newListResponse(services, propsMapping["service"], query)) +} diff --git a/server/handlers/get_services.go b/server/handlers/get_services.go new file mode 100644 index 0000000..68e8086 --- /dev/null +++ b/server/handlers/get_services.go @@ -0,0 +1,21 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" +) + +// GetServices handles GET /services +func (a *Api) GetServices(c echo.Context, params server.GetServicesParams) error { + odb := a.getODB() + return a.handleList(c, "GetServices", "service", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetServices(ctx, p) + }) +} From dbb635c1bdbbf1ae81a263c94b9791d19d90b9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 15 Apr 2026 12:59:31 +0200 Subject: [PATCH 16/24] Add GET /services_instances and /services_instances/{id} endpoints --- cdb/db_services_instances.go | 81 ++++++++ server/api.yaml | 62 ++++++ server/codegen_server_gen.go | 226 ++++++++++++++++++---- server/codegen_type_gen.go | 48 +++++ server/handlers/get_services_instance.go | 52 +++++ server/handlers/get_services_instances.go | 21 ++ 6 files changed, 449 insertions(+), 41 deletions(-) create mode 100644 cdb/db_services_instances.go create mode 100644 server/handlers/get_services_instance.go create mode 100644 server/handlers/get_services_instances.go diff --git a/cdb/db_services_instances.go b/cdb/db_services_instances.go new file mode 100644 index 0000000..8ccd977 --- /dev/null +++ b/cdb/db_services_instances.go @@ -0,0 +1,81 @@ +package cdb + +import ( + "context" + "fmt" + + "github.com/opensvc/oc3/qb" + "github.com/opensvc/oc3/schema" +) + +func buildServicesInstancesQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { + q := qb.From(schema.TSvcmon). + Via(schema.TServices). + 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( + "services.svc_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.SvcmonID, ">", 0) + } + + query, args, err := q.Build() + if err != nil { + panic(fmt.Sprintf("buildServicesInstancesQuery: %v", err)) + } + return query, args +} + +func (oDb *DB) GetServicesInstances(ctx context.Context, p ListParams) ([]map[string]any, error) { + query, args := buildServicesInstancesQuery(p.Groups, p.IsManager, p.SelectExprs) + if gb := p.GroupByClause(""); gb != "" { + query += " " + gb + } + query += " " + p.OrderByClause("svcmon.svc_id, svcmon.node_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("getServicesInstances: %w", err) + } + defer func() { _ = rows.Close() }() + + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// GetServicesInstance fetches all instances of a single service by svc_id (UUID) or svcname. +func (oDb *DB) GetServicesInstance(ctx context.Context, svcID string, p ListParams) ([]map[string]any, error) { + query, args := buildServicesInstancesQuery(p.Groups, p.IsManager, p.SelectExprs) + query += " AND (svcmon.svc_id = ? OR services.svcname = ?)" + args = append(args, svcID, svcID) + if gb := p.GroupByClause(""); gb != "" { + query += " " + gb + } + query += " " + p.OrderByClause("svcmon.node_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("getServicesInstance: %w", err) + } + defer func() { _ = rows.Close() }() + + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} diff --git a/server/api.yaml b/server/api.yaml index 79b9219..afce527 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -397,6 +397,68 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /services_instances: + get: + operationId: GetServicesInstances + description: List services instances + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /services_instances/{svc_id}: + get: + operationId: GetServicesInstance + description: List instances for a specific service (all nodes) + parameters: + - in: path + name: svc_id + required: true + description: Service identifier (svc_id UUID or svcname) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /disks: get: operationId: GetDisks diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 0b2055e..7f8855f 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -96,6 +96,12 @@ type ServerInterface interface { // (GET /services/{svc_id}) GetService(ctx echo.Context, svcId string, params GetServiceParams) error + // (GET /services_instances) + GetServicesInstances(ctx echo.Context, params GetServicesInstancesParams) error + + // (GET /services_instances/{svc_id}) + GetServicesInstance(ctx echo.Context, svcId string, params GetServicesInstanceParams) error + // (GET /tags) GetTags(ctx echo.Context, params GetTagsParams) error @@ -1274,6 +1280,141 @@ func (w *ServerInterfaceWrapper) GetService(ctx echo.Context) error { return err } +// GetServicesInstances converts echo context to params. +func (w *ServerInterfaceWrapper) GetServicesInstances(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetServicesInstancesParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetServicesInstances(ctx, params) + return err +} + +// GetServicesInstance converts echo context to params. +func (w *ServerInterfaceWrapper) GetServicesInstance(ctx echo.Context) error { + var err error + // ------------- Path parameter "svc_id" ------------- + var svcId string + + err = runtime.BindStyledParameterWithOptions("simple", "svc_id", ctx.Param("svc_id"), &svcId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter svc_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetServicesInstanceParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetServicesInstance(ctx, svcId, params) + return err +} + // GetTags converts echo context to params. func (w *ServerInterfaceWrapper) GetTags(ctx echo.Context) error { var err error @@ -1472,6 +1613,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/openapi.json", wrapper.GetSwagger) router.GET(baseURL+"/services", wrapper.GetServices) router.GET(baseURL+"/services/:svc_id", wrapper.GetService) + router.GET(baseURL+"/services_instances", wrapper.GetServicesInstances) + router.GET(baseURL+"/services_instances/:svc_id", wrapper.GetServicesInstance) router.GET(baseURL+"/tags", wrapper.GetTags) router.GET(baseURL+"/tags/:tag_id", wrapper.GetTag) router.GET(baseURL+"/tags/:tag_id/nodes", wrapper.GetTagNodes) @@ -1482,47 +1625,48 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AP//P7rbwERaAAA=", + "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", + "skyZYAAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 198965a..c29375e 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -446,6 +446,54 @@ type GetServiceParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetServicesInstancesParams defines parameters for GetServicesInstances. +type GetServicesInstancesParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + +// GetServicesInstanceParams defines parameters for GetServicesInstance. +type GetServicesInstanceParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetTagsParams defines parameters for GetTags. type GetTagsParams struct { // Props A list of properties to include in each data dictionnary. diff --git a/server/handlers/get_services_instance.go b/server/handlers/get_services_instance.go new file mode 100644 index 0000000..220c0cc --- /dev/null +++ b/server/handlers/get_services_instance.go @@ -0,0 +1,52 @@ +package serverhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetServicesInstance handles GET /services_instances/{svc_id} +func (a *Api) GetServicesInstance(c echo.Context, svcId string, params server.GetServicesInstanceParams) error { + query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["instance"]) + if err != nil { + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log := echolog.GetLogHandler(c, "GetServicesInstance") + odb := a.getODB() + ctx := c.Request().Context() + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + + log.Info("called", "svc_id", svcId, "props", query.Props, "is_manager", isManager) + + selectExprs, err := buildSelectClause(query.Props, propsMapping["instance"]) + if err != nil { + log.Error("cannot build select clause", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot build select clause") + } + + instances, err := odb.GetServicesInstance(ctx, svcId, cdb.ListParams{ + Groups: groups, + IsManager: isManager, + Limit: query.Page.Limit, + Offset: query.Page.Offset, + Props: query.Props, + SelectExprs: selectExprs, + }) + if err != nil { + log.Error("cannot get service instances", "svc_id", svcId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get service instances") + } + if len(instances) == 0 { + return JSONProblemf(c, http.StatusNotFound, "service %s not found or has no instances", svcId) + } + + return c.JSON(http.StatusOK, newListResponse(instances, propsMapping["instance"], query)) +} diff --git a/server/handlers/get_services_instances.go b/server/handlers/get_services_instances.go new file mode 100644 index 0000000..6b4a906 --- /dev/null +++ b/server/handlers/get_services_instances.go @@ -0,0 +1,21 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" +) + +// GetServicesInstances handles GET /services_instances +func (a *Api) GetServicesInstances(c echo.Context, params server.GetServicesInstancesParams) error { + odb := a.getODB() + return a.handleList(c, "GetServicesInstances", "instance", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetServicesInstances(ctx, p) + }) +} From b45c565ab52a2d55e341e054797ee1c6ffa70e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 15 Apr 2026 13:13:48 +0200 Subject: [PATCH 17/24] Add GET /services_instances_status_log endpoint --- cdb/db_services_instances_status_log.go | 61 +++++++ server/api.yaml | 27 ++++ server/codegen_server_gen.go | 153 +++++++++++++----- server/codegen_type_gen.go | 24 +++ .../get_services_instances_status_log.go | 21 +++ 5 files changed, 244 insertions(+), 42 deletions(-) create mode 100644 cdb/db_services_instances_status_log.go create mode 100644 server/handlers/get_services_instances_status_log.go diff --git a/cdb/db_services_instances_status_log.go b/cdb/db_services_instances_status_log.go new file mode 100644 index 0000000..188c2d0 --- /dev/null +++ b/cdb/db_services_instances_status_log.go @@ -0,0 +1,61 @@ +package cdb + +import ( + "context" + "fmt" + + "github.com/opensvc/oc3/qb" + "github.com/opensvc/oc3/schema" +) + +func buildServicesInstancesStatusLogQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { + q := qb.From(schema.TSvcmonLog). + Via(schema.TServices). + 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( + "services.svc_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.SvcmonLogID, ">", 0) + } + + query, args, err := q.Build() + if err != nil { + panic(fmt.Sprintf("buildServicesInstancesStatusLogQuery: %v", err)) + } + return query, args +} + +func (oDb *DB) GetServicesInstancesStatusLog(ctx context.Context, p ListParams) ([]map[string]any, error) { + query, args := buildServicesInstancesStatusLogQuery(p.Groups, p.IsManager, p.SelectExprs) + if gb := p.GroupByClause(""); gb != "" { + query += " " + gb + } + query += " " + p.OrderByClause("svcmon_log.svc_id, svcmon_log.node_id, svcmon_log.mon_begin") + query, args = appendLimitOffset(query, args, p.Limit, p.Offset) + + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("getServicesInstancesStatusLog: %w", err) + } + defer func() { _ = rows.Close() }() + + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} diff --git a/server/api.yaml b/server/api.yaml index afce527..c5ec6b5 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -459,6 +459,33 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /services_instances_status_log: + get: + operationId: GetServicesInstancesStatusLog + description: List services instances status log. Each entry is a time range where a service instance status was stable. + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /disks: get: operationId: GetDisks diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 7f8855f..94efd2e 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -102,6 +102,9 @@ type ServerInterface interface { // (GET /services_instances/{svc_id}) GetServicesInstance(ctx echo.Context, svcId string, params GetServicesInstanceParams) error + // (GET /services_instances_status_log) + GetServicesInstancesStatusLog(ctx echo.Context, params GetServicesInstancesStatusLogParams) error + // (GET /tags) GetTags(ctx echo.Context, params GetTagsParams) error @@ -1415,6 +1418,70 @@ func (w *ServerInterfaceWrapper) GetServicesInstance(ctx echo.Context) error { return err } +// GetServicesInstancesStatusLog converts echo context to params. +func (w *ServerInterfaceWrapper) GetServicesInstancesStatusLog(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetServicesInstancesStatusLogParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetServicesInstancesStatusLog(ctx, params) + return err +} + // GetTags converts echo context to params. func (w *ServerInterfaceWrapper) GetTags(ctx echo.Context) error { var err error @@ -1615,6 +1682,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/services/:svc_id", wrapper.GetService) router.GET(baseURL+"/services_instances", wrapper.GetServicesInstances) router.GET(baseURL+"/services_instances/:svc_id", wrapper.GetServicesInstance) + router.GET(baseURL+"/services_instances_status_log", wrapper.GetServicesInstancesStatusLog) router.GET(baseURL+"/tags", wrapper.GetTags) router.GET(baseURL+"/tags/:tag_id", wrapper.GetTag) router.GET(baseURL+"/tags/:tag_id/nodes", wrapper.GetTagNodes) @@ -1625,48 +1693,49 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "skyZYAAA", + "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", + "AQAA///dZznjwWMAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index c29375e..e8ac3b4 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -494,6 +494,30 @@ type GetServicesInstanceParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetServicesInstancesStatusLogParams defines parameters for GetServicesInstancesStatusLog. +type GetServicesInstancesStatusLogParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetTagsParams defines parameters for GetTags. type GetTagsParams struct { // Props A list of properties to include in each data dictionnary. diff --git a/server/handlers/get_services_instances_status_log.go b/server/handlers/get_services_instances_status_log.go new file mode 100644 index 0000000..5c62c42 --- /dev/null +++ b/server/handlers/get_services_instances_status_log.go @@ -0,0 +1,21 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" +) + +// GetServicesInstancesStatusLog handles GET /services_instances_status_log +func (a *Api) GetServicesInstancesStatusLog(c echo.Context, params server.GetServicesInstancesStatusLogParams) error { + odb := a.getODB() + return a.handleList(c, "GetServicesInstancesStatusLog", "instance_status_log", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetServicesInstancesStatusLog(ctx, p) + }) +} From 627e80a1a0cc5695dbe68967c39295de1d71e13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 15 Apr 2026 13:18:01 +0200 Subject: [PATCH 18/24] Add GET /arrays endpoint --- cdb/db_arrays.go | 38 +++++++++ server/api.yaml | 27 ++++++ server/codegen_server_gen.go | 154 ++++++++++++++++++++++++---------- server/codegen_type_gen.go | 24 ++++++ server/handlers/get_arrays.go | 21 +++++ 5 files changed, 221 insertions(+), 43 deletions(-) create mode 100644 cdb/db_arrays.go create mode 100644 server/handlers/get_arrays.go diff --git a/cdb/db_arrays.go b/cdb/db_arrays.go new file mode 100644 index 0000000..97c7400 --- /dev/null +++ b/cdb/db_arrays.go @@ -0,0 +1,38 @@ +package cdb + +import ( + "context" + "fmt" + + "github.com/opensvc/oc3/qb" + "github.com/opensvc/oc3/schema" +) + +func buildArraysQuery(selectExprs []string) (string, []any) { + q := qb.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) +} diff --git a/server/api.yaml b/server/api.yaml index c5ec6b5..a3f9483 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -486,6 +486,33 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /arrays: + get: + operationId: GetArrays + description: List storage arrays + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /disks: get: operationId: GetDisks diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 94efd2e..b60712b 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -42,6 +42,9 @@ type ServerInterface interface { // (GET /apps/{app_id}/responsibles) GetAppResponsibles(ctx echo.Context, appId string, params GetAppResponsiblesParams) error + // (GET /arrays) + GetArrays(ctx echo.Context, params GetArraysParams) error + // (POST /auth/node) PostAuthNode(ctx echo.Context) error @@ -411,6 +414,70 @@ func (w *ServerInterfaceWrapper) GetAppResponsibles(ctx echo.Context) error { return err } +// GetArrays converts echo context to params. +func (w *ServerInterfaceWrapper) GetArrays(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetArraysParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetArrays(ctx, params) + return err +} + // PostAuthNode converts echo context to params. func (w *ServerInterfaceWrapper) PostAuthNode(ctx echo.Context) error { var err error @@ -1662,6 +1729,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/apps/:app_id/am_i_responsible", wrapper.GetAppAmIResponsible) router.GET(baseURL+"/apps/:app_id/publications", wrapper.GetAppPublications) router.GET(baseURL+"/apps/:app_id/responsibles", wrapper.GetAppResponsibles) + router.GET(baseURL+"/arrays", wrapper.GetArrays) router.POST(baseURL+"/auth/node", wrapper.PostAuthNode) router.GET(baseURL+"/disks", wrapper.GetDisks) router.GET(baseURL+"/disks/:disk_id", wrapper.GetDisk) @@ -1693,49 +1761,49 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xcX2/bOBL/KgPdPbSAa7vb7MMa2Idu0xa565+c09w9tEFAS2OZW4lUSSqNz/B3X/CP", - "ZDmmbNlpbDerpyDmkDMkfzO/ITXSLAh5mnGGTMlgMAsyIkiKCoX5j7JzoibvJaqzSP8foQwFzRTlLBgE", - "Z6fAx6AmCCmP8gQlqqATUN2UETUJOgEjKQaDIJWormkUdAKB33IqMAoGSuTYCWQ4wZToodU006JSCcri", - "YD7vOOUfeITrlTMeoV+vbtlV73DjpMW6KYsdp/yfHMX0reB5NpquKn/F05Q8k6h3SWEECZVKm5MJnqFQ", - "FCUoDrHubk1EmScKRlN4gt24a1tG099JlnXkTahtfdotJvBNq17MwMkGjSx+R1OqVu39pLFBbmmap8Dy", - "dIRCW4tMCWeqQJUL1oU+pEiYBMYh0UPVGWUal0yKcEzyRAWDX/udIKVM6woG/U5hK2UKYxRVY9+jIp6N", - "ZWGSRwgpKhIRRYCyYg0zziR24TUjowQjvZxOaxcuJcKYJBKBC+jrKfGUKusUqAiMKSZR3Wy0RLP1/Tge", - "a6itGH3xldqdHlMhVbmyDqFmGmEuJBd1JnA7sHdFGy/oRxGh2B2vkguN0S6cCxzTWyBF+xS+UzWBZzDm", - "AvTIyCLKYuBan4M0t7p/176u59R5RrKsFtROutminwueydVJvayZBnUAogyQhBO7+hENdTdGxLTOpsyo", - "aWTRhSJK+paZKcETaTbdmCEpZwsAax/DqGqLtp7Al0DqAb8E8BWnHQg5U4QyvcK6n8QEQ71rlWlGVCrK", - "QgU3JMlRQshzpmTdzMzoa2c21/HR+peZ10m/r/9oS5AZvJMsS2hItOG9P6We7qwy3j8FjoNB8I/egsV6", - "tlX2zgUfJZhaLcsL9geJYIjfcpQqmHeCk/7zfWi9ZCRXEy7o/zGyal/sQ+0bLkY0ipBZnSf70PmBK3jD", - "c+bm+ds+dL7ibJzQ0Ozor/vB0RlTKBhJ4ALFDQp4LQS3kdF11mO/o1IVlLNwJWPTDaGJ5pTrrAg1VGEq", - "PZ5Shl8iBJnq/43rVSTLuNwJCi81OqKIamtJcr6ke7WX+4WP/kS7iC6gRbtYlxQJwaoeXnKZxwauSOJr", - "8tmnF3boosfq4urwq/9yhh/HweDzqvWLke5YX79q91jNOz9czTuW/zegr0SPjZVFLvnZzu/Ko6jA68qK", - "KLxVPjqb5ClhzwSSSGMR8DZLCDOeAjLDkI5pqPlNTagEHoa5EMhCdEnGF5ZZfd0vLOh4qKtqs7HAZ/MN", - "CkmtUy7bXGnAW5Jmie7X7/a7zzcqK7qu6tP+iWEuqJpe6GW2qkZE0vBlriZlaNB9zK8LXROlMm3wCIlA", - "UUjb/95wkRIVDIJ//e9TwXpmCNN6dwxL62NudoYqMzGeIZM3IYQ80dzLBZCMBpXlCZ53+90XxosyZLpx", - "ELzo9rv9oGMOIGYiPZJZd419KaMGFJSRB4ysGU6YLddnnuAtqpf29+p58LMfqQuR3lLipP2ombw9PTSX", - "d9lw8w7WgRqL2zRrC3tcVtm8R3HCm1/dSYB++YHEtRQfPez18d8VqvQNVFrW00JVtzFgqDjM5ys996pT", - "fL7Sc1Mk1sAJSkAHOu5lXHqA+UogUQiEQWXKENrT/TI+z7ksACpsGvcHj6ZbLdwdJs4yL62RLLuOeEoo", - "q21WSNJrx48rAksznG0IWNoIT7Ca371DmN8TMx4FPmCcNAGGFlpk0Jtkn1fS3k2yLyqp4ybZ3w4C5HnH", - "htreTOOARnOL6QQVrqL71PzeCN1W1B+A73B3loHAkIsIaASGLrJiTM+llDVzqzupq31h7aHwc9JE9uTo", - "sdbxk/kplVlCpmbfKxHNz+eHR1Nnywxib+hrjpLDR5keSa/ptVNGRwnWpnpDc/MDeleAuktrS5cogEqo", - "jGGu2EyGvzE8WTC9TM+GFRMeS6AacZ4gYccdqY4Ag1k+KhZz01FjlfGg0ts+k6gLWedVNT9d+GoPQD/D", - "Aegn87xK0N7B86ohf63nDatqWs9rPe9v6Xm5mvRMfcFgVnNrMcSYmnyKmEIE0F2QKbds5k7Uc3eR28KG", - "B7+/KJ6Kbr58KCV9NxD3vXFYNra89bxrbZ7TaLOlRsrdnfqvS+5EpzDETC09cjueuweDx+YA1ehy2Iyo", - "/LqBAKyIJ76fuob2dre93X2AsGlw15vpP8W1WD1Ii5oVfQAliwdOunMddDflI1oGaKTD8JiaJy+eLMRZ", - "16YhbRpy7GmI5uYNod6KePzlg2toQ30b6h8Kmr2Zq7TdLdSzmltGlyGvDfVaphLq4YmzBC4vz071UbTI", - "a5/+wBLh1n1aFji4qxmlCSUsxF5IWEQjovC6rMSv54u3qKDssCjdL9xSj+85tjp/fFUqfVUM8X6hcnua", - "qZT4t371yPzqnSuT9oGt4nGegM64gnFRPuqcrabyUtrKS1xUXu7T3cR2zibu6WrD1tFaR2viaOIxuFnC", - "4w2OVcqClt3Sq97p4ffrSPcFSdMKZl9x/OpSlS/P/cwgaZjwVNIcohQJJxiB4lugpc1y2uC7NviWsHok", - "Sc5iGr2Ze4F3Q4Gjnj+QxfxhLHha72K2zLHGy/bgZJVXmz0A8uxaaRzIPAxRynGeJFOI0G78+t3morIw", - "h976uhLsl2plD9cFyXMu1RHt324PUncot/5lHTaKOHAsJdTHdlfR6Mgk7kfW7TmppeomVP0ojkmipGmx", - "DU2LnUl6uNcQP9yGoof3Imjxs9Cz2ImcD7hvh6TmYUvM9XGkKLla+3aJKWzTkl34yBL3f7UOFQiLICWM", - "xCgkEIFAkoR/R/PlEy9XX16enR7js72/0ftOB8Gfe225W6ycw90KRi6+kzg2pTsHfyx4BN5frKZ9b8It", - "paYeGm4qDCmlPI54sWhry0Pa8pAHcPcCfb2ZvAl3LhBxo6yB8CYqcWJLbGItKsmk+B6an0uscFsm0paJ", - "HDvDFi53TZlUOu1vSBCwkF9DFWcVoZYzWs7YC4AbskcpX8Mf8IQkia2UfdoE4y2ptH7WkkqNT15LRVQu", - "rxMeb8svYLtCwuMuvCbhxOR9U6ASCCiaIgjCYoTvExSo/bjwMzdA0f87MUONEuw2oqwL0+0dj1vuarnr", - "YfzE/r72iHNrPs4YgxH1wPYTidvkqgXoAwK0N1MkXptOFRe/isTlt5hrsLopSVp8L10ZYU8aZK1pkgZV", - "Pgfafkno/vu/4cWyt+jeK3NM7DbR8HTxLMUPipqXzo4SGQ8eLA9X9Fk873abSNXEfuJJr3fds9BPJPY+", - "/9wvTCufffVC037THEhGoRD1IPG/ZdODhfpC+4+J8uVykIyMaELNFxOu5nZlxU3hR7lIgkHQ7QXzq/lf", - "AQAA///dZznjwWMAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index e8ac3b4..63befdd 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -188,6 +188,30 @@ type GetAppResponsiblesParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetArraysParams defines parameters for GetArrays. +type GetArraysParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // PostAuthNodeJSONBody defines parameters for PostAuthNode. type PostAuthNodeJSONBody struct { App *string `json:"app,omitempty"` diff --git a/server/handlers/get_arrays.go b/server/handlers/get_arrays.go new file mode 100644 index 0000000..cc88a4f --- /dev/null +++ b/server/handlers/get_arrays.go @@ -0,0 +1,21 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" +) + +// GetArrays handles GET /arrays +func (a *Api) GetArrays(c echo.Context, params server.GetArraysParams) error { + odb := a.getODB() + return a.handleList(c, "GetArrays", "array", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetArrays(ctx, p) + }) +} From b9949b632aa2a86e5fc3f776c898c270637c7b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 15 Apr 2026 14:35:54 +0200 Subject: [PATCH 19/24] Add GET /nodes/hbas and /nodes/{id}/hbas endpoints --- cdb/db_hbas.go | 80 +++++++++++ server/api.yaml | 62 ++++++++ server/codegen_server_gen.go | 230 ++++++++++++++++++++++++------ server/codegen_type_gen.go | 48 +++++++ server/handlers/get_node_hbas.go | 36 +++++ server/handlers/get_nodes_hbas.go | 21 +++ 6 files changed, 434 insertions(+), 43 deletions(-) create mode 100644 cdb/db_hbas.go create mode 100644 server/handlers/get_node_hbas.go create mode 100644 server/handlers/get_nodes_hbas.go diff --git a/cdb/db_hbas.go b/cdb/db_hbas.go new file mode 100644 index 0000000..0941239 --- /dev/null +++ b/cdb/db_hbas.go @@ -0,0 +1,80 @@ +package cdb + +import ( + "context" + "fmt" + + "github.com/opensvc/oc3/qb" + "github.com/opensvc/oc3/schema" +) + +func buildHbasQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { + q := qb.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) +} diff --git a/server/api.yaml b/server/api.yaml index a3f9483..66a7911 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -303,6 +303,41 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /nodes/{node_id}/hbas: + get: + operationId: GetNodeHbas + description: List a node storage host bus adapters + parameters: + - in: path + name: node_id + required: true + description: Node identifier (node_id UUID or nodename) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /nodes/{node_id}/uuid: get: operationId: GetNodeUUID @@ -486,6 +521,33 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /nodes/hbas: + get: + operationId: GetNodesHbas + description: List all nodes FC and iSCSI host bus adapters + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /arrays: get: operationId: GetArrays diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index b60712b..9622a3a 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -57,6 +57,9 @@ type ServerInterface interface { // (GET /nodes) GetNodes(ctx echo.Context, params GetNodesParams) error + // (GET /nodes/hbas) + GetNodesHbas(ctx echo.Context, params GetNodesHbasParams) error + // (GET /nodes/{node_id}) GetNode(ctx echo.Context, nodeId string, params GetNodeParams) error @@ -87,6 +90,9 @@ type ServerInterface interface { // (POST /nodes/{node_id}/compliance/rulesets/{rset_id}) PostNodeComplianceRuleset(ctx echo.Context, nodeId InPathNodeId, rsetId InPathRsetId) error + // (GET /nodes/{node_id}/hbas) + GetNodeHbas(ctx echo.Context, nodeId string, params GetNodeHbasParams) error + // (GET /nodes/{node_id}/uuid) GetNodeUUID(ctx echo.Context, nodeId string) error @@ -690,6 +696,70 @@ func (w *ServerInterfaceWrapper) GetNodes(ctx echo.Context) error { return err } +// GetNodesHbas converts echo context to params. +func (w *ServerInterfaceWrapper) GetNodesHbas(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetNodesHbasParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNodesHbas(ctx, params) + return err +} + // GetNode converts echo context to params. func (w *ServerInterfaceWrapper) GetNode(ctx echo.Context) error { var err error @@ -1186,6 +1256,77 @@ func (w *ServerInterfaceWrapper) PostNodeComplianceRuleset(ctx echo.Context) err return err } +// GetNodeHbas converts echo context to params. +func (w *ServerInterfaceWrapper) GetNodeHbas(ctx echo.Context) error { + var err error + // ------------- Path parameter "node_id" ------------- + var nodeId string + + err = runtime.BindStyledParameterWithOptions("simple", "node_id", ctx.Param("node_id"), &nodeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter node_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetNodeHbasParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNodeHbas(ctx, nodeId, params) + return err +} + // GetNodeUUID converts echo context to params. func (w *ServerInterfaceWrapper) GetNodeUUID(ctx echo.Context) error { var err error @@ -1734,6 +1875,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/disks", wrapper.GetDisks) router.GET(baseURL+"/disks/:disk_id", wrapper.GetDisk) router.GET(baseURL+"/nodes", wrapper.GetNodes) + router.GET(baseURL+"/nodes/hbas", wrapper.GetNodesHbas) router.GET(baseURL+"/nodes/:node_id", wrapper.GetNode) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_modulesets", wrapper.GetNodeComplianceCandidateModulesets) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_rulesets", wrapper.GetNodeComplianceCandidateRulesets) @@ -1744,6 +1886,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/nodes/:node_id/compliance/rulesets", wrapper.GetNodeComplianceRulesets) router.DELETE(baseURL+"/nodes/:node_id/compliance/rulesets/:rset_id", wrapper.DeleteNodeComplianceRuleset) router.POST(baseURL+"/nodes/:node_id/compliance/rulesets/:rset_id", wrapper.PostNodeComplianceRuleset) + router.GET(baseURL+"/nodes/:node_id/hbas", wrapper.GetNodeHbas) router.GET(baseURL+"/nodes/:node_id/uuid", wrapper.GetNodeUUID) router.GET(baseURL+"/openapi.json", wrapper.GetSwagger) router.GET(baseURL+"/services", wrapper.GetServices) @@ -1761,49 +1904,50 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xcTXPbONL+K11838OkSpGUiecwqppDJk5S3s2HV453D4nLBZEtChMSYADQsVal/76F", - "D1KUBYqUHMtKhieXhQa60Xi6nyYIcBGEPM04Q6ZkMFoEGREkRYXC/EfZOVGzdxLVWaT/j1CGgmaKchaM", - "grNT4FNQM4SUR3mCElXQC6huyoiaBb2AkRSDUZBKVNc0CnqBwK85FRgFIyVy7AUynGFK9NBqnmlRqQRl", - "cbBc9pzy9zzC7coZj9CvV7fsq3fcOGmxbcpizyn/K0cxfyN4nk3mm8pf8jQlTyXqVVIYQUKl0uZkgmco", - "FEUJikOsu1sTUeaJgskcfsF+3Lctk/kfJMt68ibUtj7pFxP4qlWvZuBkg1YWv6UpVZv2ftTYILc0zVNg", - "eTpBoa1FpoQzVaDKBevDEFIkTALjkOih6owyjWsmRTgleaKC0W/DXpBSpnUFo2GvsJUyhTGKqrHvUBHP", - "wrIwySOEFBWJiCJAWeHDjDOJfXjFyCTBSLvTae3DpUSYkkQicAFDPSWeUmWDAhWBKcUkqpuNlmjn3w/T", - "qYbahtEXX6hd6SkVUpWedQg10whzIbmoM4Hbgb0ebe3QDyJCsT9eJRcao304Fzilt0CK9jl8o2oGT2HK", - "BeiRkUWUxcC1PgdpbnX/oWNdz6n3lGRZLaiddDunnwueyc1JvaiZBnUAogyQhDPr/YiGuhsjYl5nU2bU", - "tLLoQhElfW5mSvBEmkU3ZkjK2QrAOsYwqtqirSfwOZB6wM8BfMF5D0LOFKFMe1j3k5hgqFetMs2ISkVZ", - "qOCGJDlKCHnOlKybmRl968yWOj/a+DLzOhkO9R9tCTKDd5JlCQ2JNnzwl9TTXVTG+3+B02AU/N9gxWID", - "2yoH54JPEkytlnWH/UkiGOPXHKUKlr3gZPjsEFovGcnVjAv6X4ys2ueHUPuaiwmNImRW58khdL7nCl7z", - "nLl5/n4InS85myY0NCv622FwdMYUCkYSuEBxgwJeCcFtZnSd9dhvqVQF5axCydh0Q2iiOeU6K1INVZhK", - "T6SU6ZcIQeb6fxN6FckyL/eCIkqNjiii2lqSnK/p3uzlfuGTv9A60SW0aB/rkqIg2NTDSy7z2MAVSXxN", - "Pvu0Y8cue2w6V6df/Zcz/DANRp82rV+NdMf6eq/dw5t3frha9iz/N6CvRI/NlUUt+cnO78qjqMDrhkcU", - "3iofnc3ylLCnAkmksQh4myWEmUgBmWFIpzTU/KZmVAIPw1wIZCG6IuMzy6y+/mcW9DzUVbXZWOCz+QaF", - "pDYo122uNOAtSbNE9xv2h/1njcqKrpv6dHximAuq5hfazVbVhEgavsjVrEwNuo/5daVrplSmDZ4gESgK", - "afvfay5SooJR8I//fCxYzwxhWu+OYWl9ys3KUGUmxjNk8iaEkCeae7kAktGg4p7gWX/Yf26iKEOmG0fB", - "8/6wPwx65gHETGRAMhuusa9k1ICCMvOAkTXDCbPk+pkneIPqhf29+jz4yY/UlchgrXDScdRO3j49tJd3", - "1XD7DjaAWovbMmsHe1xV2b5H8YS3vLpTAP36HYlrLT962OvDPytU6RuotGyghaphY8BQCZhPV3ru1aD4", - "dKXnpkisgROUgA503su49ADzpUCiEAiDypQhtE/36/g857IAqLBl3J88mu/kuDtMnGVeWiNZdh3xlFBW", - "26yQpNeOHzcE1ma4aEhY2ghPslre3UNY3hMzHgU+YJy0AYYWWlXQTbLPKmVvk+zzSunYJPv7owB52bOp", - "drDQOKDR0mI6QYWb6D41v7dCtxX1J+A73J1lIDDkIgIagaGLrBjTsyllzdxpT+rqUFh7KPyctJE9OXqs", - "9fxkfkpllpC5WfdKRvPz+eOjqbdjBXEw9LVHyeNnmQFJr+m1U0YnCdaWemOz8wN6VYC6TWtLlyiASqiM", - "YbbYTIXfmJ4smF6kZ+OKCT9LoppwniBhx52pjgCDWT4pnNn0qLHJeFDpbd9J1KWs86qaHy59dQ9AP8ID", - "0A8WeZWkvUfkVVP+1sgbV9V0kddF3t8y8oQg84Yok4oLEiM4WV80FS3dZlq3mfYQKM3VbGBOwYwWNXtr", - "Y4ypqfqJOS4Dugsy5Vxhdu49O2y5PX7z4Ltsxbv75i2yUtK3T3bffbF1Y8u9+bvW5jmNmi01Um6H37+p", - "d4dDwxAztfZi+Hh2yAwe2wNUo8thM6LyS0MCtSKevHnqGrq02aXNB0ibBneDhf5TbN7Wg7Q4WTXV1e3q", - "tajuXAfdpqpZywCNdBqeUvN+0FMrO+u6Yrkrlo+9WNbc3JDqrYgnXt67hi7Vd6n+oaA5WLjz4Pulelaz", - "F+4q5K2pXstUUj384iyBy8uzU+ACirr2yXc8yN6FT8cCjx5qRmlCCQtxEBIW0YgovC7vi9TzxRtUUHZY", - "XTApwlKP73lsdfH4slT6shji3Url7jRTuYjSxdVPFldv3WF+H9gqEedJ6IwrmBaHnF2w1ZwPlvZ8MK7O", - "Bx8y3MRuwSbuGWrjLtC6QGsTaOJnCLOExw2BVcqClt0xqt7q4Q8bSPcFSdtz9r4rHJuuKq94/sggaVnw", - "VMocohQJZxiB4jugpatyuuS7NfmWsPpJipzVNAYLd8284Riunj+Q1fxhKnhaH2L2MG5NlB0gyCoX8D0A", - "8qxaaRzIPAxRymmeJHOI0C789tXmouKYx176uosCL9TGGm5LkudcqiNav/1epO5xKeDXbdgo8sCxHPQ/", - "tr2KVo9M4n5k3T0ndVTdhqp/isckUdK02IWmxd4kPT5oih/vQtHjexG0+FHoWexFzo+4bo9JzeOOmOvz", - "SHHkausdKHOwTUv24QNL3P/V09JAWAQpYSRGIYEIBJIk/Bua7/N4ufry8uz0GN/t/Y1u5T0K/tzl+n7h", - "OYe7DYxcfCNxbI7uPPprwSOI/sKb9naPc6WmHho2HQwppTyBeLFq646HdMdDHiDcC/QNFvIm3PuAiBtl", - "C4SbqMSJrbGJtagkk+KrfX4uscLdMZHumMixM2wRcteUSaXL/pYEASv5LVRxVhHqOKPjjIMAuCV7lPI1", - "/AG/kCSxJ2WftMF4RypdnHWkUhOT11IRlcvrhMe78gvYrpDwuA+vSDgzdd8cqAQCiqYIgrAY4dsMBeo4", - "LuLMDVD0/0bMUJME+60o68J0e8vjjrs67nqYOLG/b33EuTWfEI3BiHpg+5HEXXHVAfQBATpYKBJvLaeK", - "jV9F4vKL4TVYbSqSVl/1V0bYUwZZa9qUQZWP1nbfu7r/+jdcLHuD7l6ZY2K3iIani3cpflDUXDo7SmQ8", - "eLJ8vEOfxftut4hUzeyHyLS/696FfiSx9/3nYWFa+TixF5r2y/tAMgqFqAeJ/y6bHizVF9q/T5Yv3UEy", - "MqEJNV9MuFpaz4qbIo5ykQSjoD8IllfL/wUAAP//m7HpjGdmAAA=", + "H4sIAAAAAAAC/+xdS3PbOBL+K13cPSRViqRMPIdR1RwyzmO9m4dXTnYPicsFkS0KExJgANCx1qX/voUH", + "KcoCRUqOZCXDk0tEA90Avq+7AQL0bRDyNOMMmZLB6DbIiCApKhTmF2XnRM3eSlRnkf4doQwFzRTlLBgF", + "Zy+AT0HNEFIe5QlKVEEvoLooI2oW9AJGUgxGQSpRXdEo6AUCv+ZUYBSMlMixF8hwhinRTat5pkWlEpTF", + "wWLRc8rf8Qg3K2c8Qr9eXbKr3nFjp8WmLosdu/zvHMX8teB5NpmvKz/laUqeSNSzpDCChEqlzckEz1Ao", + "ihIUh1hXtyaizBMFkzk8wn7ctyWT+e8ky3ryOtS2Pu4XHfiqVS974GSDVha/oSlV6/Z+0NggNzTNU2B5", + "OkGhrUWmhDNVoMoF68MQUiRMAuOQ6KbqjDKFKyZFOCV5ooLRr8NekFKmdQWjYa+wlTKFMYqqsW9REc/E", + "sjDJI4QUFYmIIkBZMYYZZxL78JKRSYKRHk6ntQ8fJcKUJBKBCxjqLvGUKksKVASmFJOorjdaot34vp9O", + "NdTWjL74Qu1MT6mQqhxZh1DTjTAXkos6E7ht2DuirQf0vYhQ7I5XyYXGaB/OBU7pDZCifA7fqJrBE5hy", + "AbplZBFlMXCtz0GaW92/a67rPvWekCyrBbWTbjfo54Jncr1Tz2u6QR2AKAMk4cyOfkRDXY0RMa+zKTNq", + "Wll0oYiSvmFmSvBEmkk3ZkjK2RLAmmMYVW3R1hP4HEjd4OcAvuC8ByFnilCmR1jXk5hgqGet0s2ISkVZ", + "qOCaJDlKCHnOlKzrmWl9Y88W2j9afpl+nQyH+o+2BJnBO8myhIZEGz74U+ru3lba+7vAaTAK/jZYRrGB", + "LZWDc8EnCaZWy+qA/UEiGOPXHKUKFr3gZPj0EFo/MpKrGRf0fxhZtc8OofYVFxMaRciszpND6HzHFbzi", + "OXP9/O0QOk85myY0NDP662FwdMYUCkYSuEBxjQJeCsGtZ3SVddtvqFRFyFlSydh0TWiiY8pVVrgaqjCV", + "HqaU7pcIQeb6t6FeRbL0y72gYKnREUVUW0uS8xXd67XcEz75E+0gOocW7WJdUiQE63p4Gcs8NnBFEl+R", + "zz49sGPnPdYHV7tf/ZczfD8NRp/WrV+2dMf6+lG7x2jeeXC56Nn434C+Ej3WVxa55Cfbv0uPogKvayOi", + "8Eb5wtksTwl7IpBEGouAN1lCmGEKyAxDOqWhjm9qRiXwMMyFQBaiSzI+s8zq639mQc8Tuqo2Gwt8Nl+j", + "kNSSctXmSgHekDRLdL1hf9h/2qisqLquT/MTw1xQNb/Qw2xVTYik4fNczUrXoOuYp0tdM6UybfAEiUBR", + "SNtfr7hIiQpGwT//+6GIeqYJU3q3DRvWp9zMDFWmYzxDJq9DCHmiYy8XQDIaVIYneNof9p8ZFmXIdOEo", + "eNYf9odBzyxATEcGJLN0jX0powYUlJ4HjKxpTpgp12ue4DWq5/Z5dT34yY/UpchgJXHSPGonb1cP7eVd", + "Nty+giVQa3GbZm1hj8sq29coVniLyzsJ0C/fMXCt+EdP9Hr/r0qo9DVUWjbQQlXaGDBUCPPpUve9SopP", + "l7pvisQaOEEJ6ED7vYxLDzBPBRKFQBhUugyhXd2v4vOcywKgwqZxf/BovtXA3YnEWeYNayTLriKeEspq", + "ixWS9MrFxzWBlR7eNjgsbYTHWS3u7iEs7okZjwIfME7aAEMLLTPoJtmnlbS3SfZZJXVskv3tQYC86FlX", + "O7jVOKDRwmI6QYXr6H5hnrdCtxX1O+A7sTvLQGDIRQQ0AhMusqJNz6aUNXOrPanLQ2FtX/g5aSN7cvRY", + "6/mD+Qsqs4TMzbxXPJo/nj88mnpbZhAHQ197lDy8lxmQ9IpeOWV0kmBtqjc2Oz+gZwWo27S24RIFUAmV", + "NswWm8nwG92TBdPz9GxcMeFncVQTzhMk7Lg91RFgMMsnxWA2LTXWIx5Uatt3EnUu67yq5odzX90C6EdY", + "AP1gzKs47R2YV3X5G5k3rqrpmNcx7y/JPCHIvIFlUnFBYgQn62NTUdJtpnWbaftAaa5mA3MKZnRbs7c2", + "xpiarJ+Y4zKgqyBTbijMzr1nhy23x2/2vstWvLtv3iIrJX37ZPfdF1s1ttybv2ttntOo2VIj5Xb4/Zt6", + "d2JoGGKmVl4MH88OmcFje4BqdDlsRlR+aXCgVsTjN1+4gs5tdm5zD27T4G5wq/8Um7f1IC1OVk11drt8", + "Laor10G3KWvWMkAj7Yan1Lwf9OTKzrouWe6S5WNPlnVsbnD1VsTDl3euoHP1navfFzQHswlp2jFJEotR", + "eHUKhEVAL04vzmDGpYJJLoFEJDPgrIPwP7SKDsYdjPcI41t3rWG3jIXVvNJxC72NGYuWqWQs8MhZAh8/", + "nr0ALqBYnj3+jvcxOvp0ycyDU80oTShhIQ5CwiIaEYVX5bWn+rDyGhWUFZb3pApa6vY9uy+Oj6el0tOi", + "ibdLlduHmcp9qo5XPxmv3rg7KT6wVRjnceiMK5gWZ/Ud2WqOuUt7zB2Xx9wPSTexHdnEPak27ojWEa0N", + "0cTPQLOExw3EKmVBy27Jqje6+cMS6b4gaXtdxHcTaX2oypvKPzJIWiY8lTSHKEXCGUag+BZo6bKczvlu", + "dL4lrH6SJGfZjcGt+1pCw2ly3X8gy/7DVPC0nmL2THkNyw5Assp3JDwA8sxaaRzIPAxRymmeJHOI0E78", + "5tnmojIwDz31dfddnqu1OdzkJM+5VEc0f7udB9jhbssvm7BR+IFjua9ybHsVrZZM4n7BulsndaG6Taj+", + "KZZJogzTYpswLXYO0uODuvjxNiF6fK8ALX6U8Cx2Cs4POG8PGZrHXWCu9yMtXkDb45nFmeLWL5797527", + "t3ddUO/e3kWLQXFkd+MdWsM8LdmH9yxxv6u3bcyJkJQwEqOQQAQCSRL+Dc333by01Mw6Rlr+hW51Pwj+", + "3MdZ+sXIOdytYeTiG4ljc/TzwRl9BGG3GE17O9QNpc75aNh0sLCU8hDxYlnWncvqzmXtge4F+ga38jrc", + "+WSWa2UDhJtCiRNbiSbWojKYFF999ccSK9xleF2Gd+wRtqDcFWVS6fV2ywABS/kNoeKsItTFjC5mHATA", + "LaNHKV8TP+BReYr9cRuMd0Gl41kXVGo4eSUVUbm8Sni8bXwBWxUSHvfhJQlnJu+bA5VAQNEUQRAWI3yb", + "oUDN44JnroGi/jdimpok2G8Vsi5MtTc87mJXF7v2wxP7fOMS58Z8gjoGI+qB7QcSd8lVB9A9AnRwq0i8", + "MZ0qNn4Vicv/OFGD1aYkaflfYZQR9qRB1po2aVDlo+fd9xLvP/8NF5Nfo7uX7CKxm0QTp4uXmH5Q1Fxa", + "Pkpk7N1ZPtxp6+KgiZtEqmb2Q5Z6vOsOIXwgsffgwWFhWvm4vRea9j+3AMkoFKIeJP6nLNqbqy+0fx8v", + "Xw4HyciEJtR8cedyYUdWXBc8ykUSjIL+IFhcLv4fAAD//9zrF8GnbAAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 63befdd..abaaab1 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -290,6 +290,30 @@ type GetNodesParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetNodesHbasParams defines parameters for GetNodesHbas. +type GetNodesHbasParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetNodeParams defines parameters for GetNode. type GetNodeParams struct { // Props A list of properties to include in each data dictionnary. @@ -422,6 +446,30 @@ type GetNodeComplianceRulesetsParams struct { // PostNodeComplianceRulesetJSONBody defines parameters for PostNodeComplianceRuleset. type PostNodeComplianceRulesetJSONBody = map[string]interface{} +// GetNodeHbasParams defines parameters for GetNodeHbas. +type GetNodeHbasParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetServicesParams defines parameters for GetServices. type GetServicesParams struct { // Props A list of properties to include in each data dictionnary. diff --git a/server/handlers/get_node_hbas.go b/server/handlers/get_node_hbas.go new file mode 100644 index 0000000..9908b27 --- /dev/null +++ b/server/handlers/get_node_hbas.go @@ -0,0 +1,36 @@ +package serverhandlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetNodeHbas handles GET /nodes/{node_id}/hbas +func (a *Api) GetNodeHbas(c echo.Context, nodeId string, params server.GetNodeHbasParams) error { + log := echolog.GetLogHandler(c, "GetNodeHbas") + odb := a.getODB() + ctx := c.Request().Context() + + node, err := odb.NodeByNodeIDOrNodename(ctx, nodeId) + if err != nil { + log.Error("cannot resolve node", "node_id", nodeId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve node") + } + if node == nil { + return JSONProblemf(c, http.StatusNotFound, "node %s not found", nodeId) + } + + return a.handleList(c, "GetNodeHbas", "hba", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetNodeHbas(ctx, node.NodeID, p) + }) +} diff --git a/server/handlers/get_nodes_hbas.go b/server/handlers/get_nodes_hbas.go new file mode 100644 index 0000000..c40af5d --- /dev/null +++ b/server/handlers/get_nodes_hbas.go @@ -0,0 +1,21 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" +) + +// GetNodesHbas handles GET /nodes/hbas +func (a *Api) GetNodesHbas(c echo.Context, params server.GetNodesHbasParams) error { + odb := a.getODB() + return a.handleList(c, "GetNodesHbas", "hba", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetHbas(ctx, p) + }) +} From 794ce849d4c06a143cab9a5e98ab052a628a29e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 15 Apr 2026 15:00:29 +0200 Subject: [PATCH 20/24] Add GET /nodes/{id}/disks endpoint --- server/api.yaml | 35 ++++++++ server/codegen_server_gen.go | 144 +++++++++++++++++++++++------- server/codegen_type_gen.go | 24 +++++ server/handlers/get_node_disks.go | 36 ++++++++ 4 files changed, 205 insertions(+), 34 deletions(-) create mode 100644 server/handlers/get_node_disks.go diff --git a/server/api.yaml b/server/api.yaml index 66a7911..c6a845d 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -338,6 +338,41 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /nodes/{node_id}/disks: + get: + operationId: GetNodeDisks + description: List disks of a specific node + parameters: + - in: path + name: node_id + required: true + description: Node identifier (node_id UUID or nodename) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /nodes/{node_id}/uuid: get: operationId: GetNodeUUID diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 9622a3a..401b8c3 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -90,6 +90,9 @@ type ServerInterface interface { // (POST /nodes/{node_id}/compliance/rulesets/{rset_id}) PostNodeComplianceRuleset(ctx echo.Context, nodeId InPathNodeId, rsetId InPathRsetId) error + // (GET /nodes/{node_id}/disks) + GetNodeDisks(ctx echo.Context, nodeId string, params GetNodeDisksParams) error + // (GET /nodes/{node_id}/hbas) GetNodeHbas(ctx echo.Context, nodeId string, params GetNodeHbasParams) error @@ -1256,6 +1259,77 @@ func (w *ServerInterfaceWrapper) PostNodeComplianceRuleset(ctx echo.Context) err return err } +// GetNodeDisks converts echo context to params. +func (w *ServerInterfaceWrapper) GetNodeDisks(ctx echo.Context) error { + var err error + // ------------- Path parameter "node_id" ------------- + var nodeId string + + err = runtime.BindStyledParameterWithOptions("simple", "node_id", ctx.Param("node_id"), &nodeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter node_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetNodeDisksParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNodeDisks(ctx, nodeId, params) + return err +} + // GetNodeHbas converts echo context to params. func (w *ServerInterfaceWrapper) GetNodeHbas(ctx echo.Context) error { var err error @@ -1886,6 +1960,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/nodes/:node_id/compliance/rulesets", wrapper.GetNodeComplianceRulesets) router.DELETE(baseURL+"/nodes/:node_id/compliance/rulesets/:rset_id", wrapper.DeleteNodeComplianceRuleset) router.POST(baseURL+"/nodes/:node_id/compliance/rulesets/:rset_id", wrapper.PostNodeComplianceRuleset) + router.GET(baseURL+"/nodes/:node_id/disks", wrapper.GetNodeDisks) router.GET(baseURL+"/nodes/:node_id/hbas", wrapper.GetNodeHbas) router.GET(baseURL+"/nodes/:node_id/uuid", wrapper.GetNodeUUID) router.GET(baseURL+"/openapi.json", wrapper.GetSwagger) @@ -1905,7 +1980,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "H4sIAAAAAAAC/+xdS3PbOBL+K13cPSRViqRMPIdR1RwyzmO9m4dXTnYPicsFkS0KExJgANCx1qX/voUH", - "KcoCRUqOZCXDk0tEA90Avq+7AQL0bRDyNOMMmZLB6DbIiCApKhTmF2XnRM3eSlRnkf4doQwFzRTlLBgF", + "KcoCRUqOZCXDk0pCA90Avq+7AQLUbRDyNOMMmZLB6DbIiCApKhTmG2XnRM3eSlRnkf4eoQwFzRTlLBgF", "Zy+AT0HNEFIe5QlKVEEvoLooI2oW9AJGUgxGQSpRXdEo6AUCv+ZUYBSMlMixF8hwhinRTat5pkWlEpTF", "wWLRc8rf8Qg3K2c8Qr9eXbKr3nFjp8WmLosdu/zvHMX8teB5NpmvKz/laUqeSNSzpDCChEqlzckEz1Ao", "ihIUh1hXtyaizBMFkzk8wn7ctyWT+e8ky3ryOtS2Pu4XHfiqVS974GSDVha/oSlV6/Z+0NggNzTNU2B5", @@ -1914,40 +1989,41 @@ var swaggerSpec = []string{ "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", + "cAAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index abaaab1..631285a 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -446,6 +446,30 @@ type GetNodeComplianceRulesetsParams struct { // PostNodeComplianceRulesetJSONBody defines parameters for PostNodeComplianceRuleset. type PostNodeComplianceRulesetJSONBody = map[string]interface{} +// GetNodeDisksParams defines parameters for GetNodeDisks. +type GetNodeDisksParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetNodeHbasParams defines parameters for GetNodeHbas. type GetNodeHbasParams struct { // Props A list of properties to include in each data dictionnary. diff --git a/server/handlers/get_node_disks.go b/server/handlers/get_node_disks.go new file mode 100644 index 0000000..3c798bb --- /dev/null +++ b/server/handlers/get_node_disks.go @@ -0,0 +1,36 @@ +package serverhandlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetNodeDisks handles GET /nodes/{node_id}/disks +func (a *Api) GetNodeDisks(c echo.Context, nodeId string, params server.GetNodeDisksParams) error { + log := echolog.GetLogHandler(c, "GetNodeDisks") + odb := a.getODB() + ctx := c.Request().Context() + + node, err := odb.NodeByNodeIDOrNodename(ctx, nodeId) + if err != nil { + log.Error("cannot resolve node", "node_id", nodeId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve node") + } + if node == nil { + return JSONProblemf(c, http.StatusNotFound, "node %s not found", nodeId) + } + + return a.handleList(c, "GetNodeDisks", "disk", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetNodeDisks(ctx, node.NodeID, p) + }) +} From 3ab0bade4e4ce3f8e1802ee7444c3e154d5683c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 15 Apr 2026 15:34:10 +0200 Subject: [PATCH 21/24] Add GET /nodes/{id}/interfaces endpoint --- cdb/db_node_interfaces.go | 72 +++++++++++ server/api.yaml | 35 ++++++ server/codegen_server_gen.go | 165 ++++++++++++++++++------- server/codegen_type_gen.go | 24 ++++ server/handlers/get_node_interfaces.go | 36 ++++++ 5 files changed, 287 insertions(+), 45 deletions(-) create mode 100644 cdb/db_node_interfaces.go create mode 100644 server/handlers/get_node_interfaces.go diff --git a/cdb/db_node_interfaces.go b/cdb/db_node_interfaces.go new file mode 100644 index 0000000..8d32dbb --- /dev/null +++ b/cdb/db_node_interfaces.go @@ -0,0 +1,72 @@ +package cdb + +import ( + "context" + "fmt" + "strings" + + "github.com/opensvc/oc3/qb" + "github.com/opensvc/oc3/schema" +) + +func buildNodeInterfacesQuery(nodeID string, p ListParams) (string, []any) { + q := qb.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) +} diff --git a/server/api.yaml b/server/api.yaml index c6a845d..93790e2 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -303,6 +303,41 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /nodes/{node_id}/interfaces: + get: + operationId: GetNodeInterfaces + description: List a node network interfaces + parameters: + - in: path + name: node_id + required: true + description: Node identifier (node_id UUID or nodename) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /nodes/{node_id}/hbas: get: operationId: GetNodeHbas diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 401b8c3..af6fc22 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -96,6 +96,9 @@ type ServerInterface interface { // (GET /nodes/{node_id}/hbas) GetNodeHbas(ctx echo.Context, nodeId string, params GetNodeHbasParams) error + // (GET /nodes/{node_id}/interfaces) + GetNodeInterfaces(ctx echo.Context, nodeId string, params GetNodeInterfacesParams) error + // (GET /nodes/{node_id}/uuid) GetNodeUUID(ctx echo.Context, nodeId string) error @@ -1401,6 +1404,77 @@ func (w *ServerInterfaceWrapper) GetNodeHbas(ctx echo.Context) error { return err } +// GetNodeInterfaces converts echo context to params. +func (w *ServerInterfaceWrapper) GetNodeInterfaces(ctx echo.Context) error { + var err error + // ------------- Path parameter "node_id" ------------- + var nodeId string + + err = runtime.BindStyledParameterWithOptions("simple", "node_id", ctx.Param("node_id"), &nodeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter node_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetNodeInterfacesParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNodeInterfaces(ctx, nodeId, params) + return err +} + // GetNodeUUID converts echo context to params. func (w *ServerInterfaceWrapper) GetNodeUUID(ctx echo.Context) error { var err error @@ -1962,6 +2036,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/nodes/:node_id/compliance/rulesets/:rset_id", wrapper.PostNodeComplianceRuleset) router.GET(baseURL+"/nodes/:node_id/disks", wrapper.GetNodeDisks) router.GET(baseURL+"/nodes/:node_id/hbas", wrapper.GetNodeHbas) + router.GET(baseURL+"/nodes/:node_id/interfaces", wrapper.GetNodeInterfaces) router.GET(baseURL+"/nodes/:node_id/uuid", wrapper.GetNodeUUID) router.GET(baseURL+"/openapi.json", wrapper.GetSwagger) router.GET(baseURL+"/services", wrapper.GetServices) @@ -1979,51 +2054,51 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "cAAA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 631285a..50b3558 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -494,6 +494,30 @@ type GetNodeHbasParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetNodeInterfacesParams defines parameters for GetNodeInterfaces. +type GetNodeInterfacesParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetServicesParams defines parameters for GetServices. type GetServicesParams struct { // Props A list of properties to include in each data dictionnary. diff --git a/server/handlers/get_node_interfaces.go b/server/handlers/get_node_interfaces.go new file mode 100644 index 0000000..b979ecd --- /dev/null +++ b/server/handlers/get_node_interfaces.go @@ -0,0 +1,36 @@ +package serverhandlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetNodeInterfaces handles GET /nodes/{node_id}/interfaces +func (a *Api) GetNodeInterfaces(c echo.Context, nodeId string, params server.GetNodeInterfacesParams) error { + log := echolog.GetLogHandler(c, "GetNodeInterfaces") + odb := a.getODB() + ctx := c.Request().Context() + + node, err := odb.NodeByNodeIDOrNodename(ctx, nodeId) + if err != nil { + log.Error("cannot resolve node", "node_id", nodeId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve node") + } + if node == nil { + return JSONProblemf(c, http.StatusNotFound, "node %s not found", nodeId) + } + + return a.handleList(c, "GetNodeInterfaces", "node_interface", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetNodeInterfaces(ctx, node.NodeID, p) + }) +} From 5bc9067d4c1da6ee848011092cae11417e5522ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Wed, 15 Apr 2026 17:05:45 +0200 Subject: [PATCH 22/24] Add POST /apps/{id} endpoint and refactor get and delete --- cdb/db_apps.go | 116 +++++++++++++++++++++++-------- server/api.yaml | 47 +++++++++++++ server/codegen_server_gen.go | 114 +++++++++++++++++++------------ server/codegen_type_gen.go | 11 +++ server/handlers/delete_apps.go | 11 +++ server/handlers/get_apps.go | 36 +++------- server/handlers/post_app.go | 121 +++++++++++++++++++++++++++++++++ 7 files changed, 353 insertions(+), 103 deletions(-) create mode 100644 server/handlers/post_app.go diff --git a/cdb/db_apps.go b/cdb/db_apps.go index 8662a0d..8316583 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -6,6 +6,10 @@ import ( "errors" "fmt" "strconv" + "strings" + + "github.com/opensvc/oc3/qb" + "github.com/opensvc/oc3/schema" ) type ( @@ -59,55 +63,53 @@ func scanApps(rows *sql.Rows) ([]App, error) { return apps, nil } -func buildAppsQuery(groups []string, isManager bool) (string, []any) { - query := ` - SELECT DISTINCT apps.id, apps.app, apps.updated, apps.app_domain, apps.app_team_ops, apps.description - FROM apps - ` - args := make([]any, 0) +func buildAppsQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { + q := qb.From(schema.TApps). + Distinct(). + RawSelect(selectExprs...) if !isManager { cleanGroups := cleanGroups(groups) - query += ` - JOIN apps_responsibles ON apps.id = apps_responsibles.app_id - JOIN auth_group ON apps_responsibles.group_id = auth_group.id - ` - if len(cleanGroups) == 0 { - query += " WHERE 1=0" - } else { - query += " WHERE auth_group.role IN (" + Placeholders(len(cleanGroups)) + ")" - for _, g := range cleanGroups { - args = append(args, g) - } - } + q = q.Via(schema.TAppsResponsibles). + WhereIn(schema.AuthGroupRole, cleanGroups) } else { - query += " WHERE apps.id > 0" + q = q.Where(schema.AppsID, ">", 0) } + query, args, err := q.Build() + if err != nil { + panic(fmt.Sprintf("buildAppsQuery: %v", err)) + } return query, args } -func (oDb *DB) GetApps(ctx context.Context, groups []string, isManager bool, limit, offset int) ([]App, error) { - query, args := buildAppsQuery(groups, isManager) - query += " ORDER BY apps.app, apps.id" - query, args = appendLimitOffset(query, args, limit, offset) +func buildAppsQueryAll(groups []string, isManager bool) (string, []any) { + return buildAppsQuery(groups, isManager, []string{ + "apps.id", "apps.app", + "COALESCE(apps.updated, '')", "COALESCE(apps.app_domain, '')", + "COALESCE(apps.app_team_ops, '')", "COALESCE(apps.description, '')", + }) +} - rows, err := oDb.DB.QueryContext(ctx, query, args...) - if err != nil { - return nil, fmt.Errorf("getApps: %w", err) +func (oDb *DB) GetApps(ctx context.Context, p ListParams) ([]map[string]any, error) { + query, args := buildAppsQuery(p.Groups, p.IsManager, p.SelectExprs) + if gb := p.GroupByClause(""); gb != "" { + query += " " + gb } - defer func() { _ = rows.Close() }() + query += " " + p.OrderByClause("apps.app, apps.id") + query, args = appendLimitOffset(query, args, p.Limit, p.Offset) - apps, err := scanApps(rows) + rows, err := oDb.DB.QueryContext(ctx, query, args...) if err != nil { return nil, fmt.Errorf("getApps: %w", err) } + defer func() { _ = rows.Close() }() - return apps, nil + return scanRowsToMaps(rows, p.Props, p.TypeHints) } func (oDb *DB) GetApp(ctx context.Context, appIDOrName string, groups []string, isManager bool) (*App, error) { - query, args := buildAppsQuery(groups, isManager) + query, args := buildAppsQueryAll(groups, isManager) if id, err := strconv.ParseInt(appIDOrName, 10, 64); err == nil { query += " AND apps.id = ?" @@ -554,6 +556,60 @@ func (oDb *DB) InsertApp(ctx context.Context, app, description, appDomain, appTe return &App{ID: id, App: app, Description: description, AppDomain: appDomain, AppTeamOps: appTeamOps}, nil } +type UpdateAppFields struct { + App *string + Description *string + AppDomain *string + AppTeamOps *string +} + +func (oDb *DB) UpdateApp(ctx context.Context, appID int64, fields UpdateAppFields) error { + setClauses := []string{} + args := []any{} + if fields.App != nil { + setClauses = append(setClauses, "app = ?") + args = append(args, *fields.App) + } + if fields.Description != nil { + setClauses = append(setClauses, "description = ?") + args = append(args, sql.NullString{String: *fields.Description, Valid: true}) + } + if fields.AppDomain != nil { + setClauses = append(setClauses, "app_domain = ?") + args = append(args, sql.NullString{String: *fields.AppDomain, Valid: true}) + } + if fields.AppTeamOps != nil { + setClauses = append(setClauses, "app_team_ops = ?") + args = append(args, sql.NullString{String: *fields.AppTeamOps, Valid: true}) + } + if len(setClauses) == 0 { + return nil + } + query := "UPDATE apps SET " + strings.Join(setClauses, ", ") + " WHERE id = ?" + args = append(args, appID) + if _, err := oDb.DB.ExecContext(ctx, query, args...); err != nil { + return fmt.Errorf("updateApp: %w", err) + } + oDb.SetChange("apps") + return nil +} + +func (oDb *DB) UpdateNodesApp(ctx context.Context, oldApp, newApp string) error { + const query = `UPDATE nodes SET app = ? WHERE app = ?` + if _, err := oDb.DB.ExecContext(ctx, query, newApp, oldApp); err != nil { + return fmt.Errorf("updateNodesApp: %w", err) + } + return nil +} + +func (oDb *DB) UpdateServicesApp(ctx context.Context, oldApp, newApp string) error { + const query = `UPDATE services SET svc_app = ? WHERE svc_app = ?` + if _, err := oDb.DB.ExecContext(ctx, query, newApp, oldApp); err != nil { + return fmt.Errorf("updateServicesApp: %w", err) + } + return nil +} + func (oDb *DB) InsertAppResponsible(ctx context.Context, appID, groupID int64) error { const query = `INSERT INTO apps_responsibles (app_id, group_id) VALUES (?, ?)` if _, err := oDb.DB.ExecContext(ctx, query, appID, groupID); err != nil { diff --git a/server/api.yaml b/server/api.yaml index 93790e2..800436a 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -79,6 +79,53 @@ paths: - bearerAuth: [ ] /apps/{app_id}: + post: + operationId: PostApp + description: Change an application code properties + parameters: + - in: path + name: app_id + required: true + description: App record id or app code + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + app: + type: string + description: + type: string + app_domain: + type: string + app_team_ops: + type: string + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + 400: + $ref: '#/components/responses/400' + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] get: operationId: GetApp description: Display app properties diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index af6fc22..8923771 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -33,6 +33,9 @@ type ServerInterface interface { // (GET /apps/{app_id}) GetApp(ctx echo.Context, appId string, params GetAppParams) error + // (POST /apps/{app_id}) + PostApp(ctx echo.Context, appId string) error + // (GET /apps/{app_id}/am_i_responsible) GetAppAmIResponsible(ctx echo.Context, appId string) error @@ -264,6 +267,26 @@ func (w *ServerInterfaceWrapper) GetApp(ctx echo.Context) error { return err } +// PostApp converts echo context to params. +func (w *ServerInterfaceWrapper) PostApp(ctx echo.Context) error { + var err error + // ------------- Path parameter "app_id" ------------- + var appId string + + err = runtime.BindStyledParameterWithOptions("simple", "app_id", ctx.Param("app_id"), &appId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter app_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PostApp(ctx, appId) + return err +} + // GetAppAmIResponsible converts echo context to params. func (w *ServerInterfaceWrapper) GetAppAmIResponsible(ctx echo.Context) error { var err error @@ -2015,6 +2038,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/apps", wrapper.PostApps) router.DELETE(baseURL+"/apps/:app_id", wrapper.DeleteApps) router.GET(baseURL+"/apps/:app_id", wrapper.GetApp) + router.POST(baseURL+"/apps/:app_id", wrapper.PostApp) router.GET(baseURL+"/apps/:app_id/am_i_responsible", wrapper.GetAppAmIResponsible) router.GET(baseURL+"/apps/:app_id/publications", wrapper.GetAppPublications) router.GET(baseURL+"/apps/:app_id/responsibles", wrapper.GetAppResponsibles) @@ -2054,51 +2078,51 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 50b3558..305af60 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -140,6 +140,14 @@ type GetAppParams struct { Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` } +// PostAppJSONBody defines parameters for PostApp. +type PostAppJSONBody struct { + App *string `json:"app,omitempty"` + AppDomain *string `json:"app_domain,omitempty"` + AppTeamOps *string `json:"app_team_ops,omitempty"` + Description *string `json:"description,omitempty"` +} + // GetAppPublicationsParams defines parameters for GetAppPublications. type GetAppPublicationsParams struct { // Props A list of properties to include in each data dictionnary. @@ -683,6 +691,9 @@ type GetTagNodesParams struct { // PostAppsJSONRequestBody defines body for PostApps for application/json ContentType. type PostAppsJSONRequestBody PostAppsJSONBody +// PostAppJSONRequestBody defines body for PostApp for application/json ContentType. +type PostAppJSONRequestBody PostAppJSONBody + // PostAuthNodeJSONRequestBody defines body for PostAuthNode for application/json ContentType. type PostAuthNodeJSONRequestBody PostAuthNodeJSONBody diff --git a/server/handlers/delete_apps.go b/server/handlers/delete_apps.go index 835839d..c7dd228 100644 --- a/server/handlers/delete_apps.go +++ b/server/handlers/delete_apps.go @@ -31,6 +31,8 @@ func (a *Api) DeleteApps(c echo.Context, appId string) error { log.Info("called", "app_id", appId) + isManager := IsManager(c) + app, err := odb.GetApp(ctx, appId, nil, true) if err != nil { log.Error("cannot get app", "app_id", appId, logkey.Error, err) @@ -40,6 +42,15 @@ func (a *Api) DeleteApps(c echo.Context, appId string) error { return JSONProblemf(c, http.StatusNotFound, "app %s not found", appId) } + responsible, err := odb.AppResponsible(ctx, appId, UserGroupsFromContext(c), isManager, "") + if err != nil { + log.Error("cannot check app responsibility", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot check app responsibility") + } + if !responsible { + return JSONProblemf(c, http.StatusForbidden, "you are not responsible for this app") + } + nodesCount, servicesCount, err := odb.AppUsageCounts(ctx, app.App) if err != nil { log.Error("cannot count app usage", "app_id", appId, "app", app.App, logkey.Error, err) diff --git a/server/handlers/get_apps.go b/server/handlers/get_apps.go index c3843b5..13c12db 100644 --- a/server/handlers/get_apps.go +++ b/server/handlers/get_apps.go @@ -1,41 +1,21 @@ package serverhandlers import ( - "net/http" + "context" "github.com/labstack/echo/v4" + "github.com/opensvc/oc3/cdb" "github.com/opensvc/oc3/server" - "github.com/opensvc/oc3/util/echolog" - "github.com/opensvc/oc3/util/logkey" ) // GetApps handles GET /apps func (a *Api) GetApps(c echo.Context, params server.GetAppsParams) error { - query, err := buildListQueryParameters(params.Props, params.Limit, params.Offset, params.Meta, params.Stats, params.Orderby, params.Groupby, propsMapping["app"]) - if err != nil { - return JSONProblem(c, http.StatusBadRequest, err.Error()) - } - - log := echolog.GetLogHandler(c, "GetApps") odb := a.getODB() - ctx := c.Request().Context() - groups := UserGroupsFromContext(c) - isManager := IsManager(c) - - log.Info("called", "limit", query.Page.Limit, "offset", query.Page.Offset, "props", query.Props, "meta", query.WithMeta, "stats", query.WithStats, "is_manager", isManager) - - apps, err := odb.GetApps(ctx, groups, isManager, query.Page.Limit, query.Page.Offset) - if err != nil { - log.Error("cannot get apps", logkey.Error, err) - return JSONProblemf(c, http.StatusInternalServerError, "cannot get apps") - } - - filteredItems, err := filterItemsFields(apps, query.Props) - if err != nil { - log.Error("cannot project app props", logkey.Error, err) - return JSONProblemf(c, http.StatusInternalServerError, "cannot project app props") - } - - return c.JSON(http.StatusOK, newListResponse(filteredItems, propsMapping["app"], query)) + return a.handleList(c, "GetApps", "app", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetApps(ctx, p) + }) } diff --git a/server/handlers/post_app.go b/server/handlers/post_app.go new file mode 100644 index 0000000..df0f383 --- /dev/null +++ b/server/handlers/post_app.go @@ -0,0 +1,121 @@ +package serverhandlers + +import ( + "context" + "database/sql" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// PostApp handles POST /apps/{app_id} +func (a *Api) PostApp(c echo.Context, appId string) error { + log := echolog.GetLogHandler(c, "PostApp") + ctx, cancel := context.WithTimeout(c.Request().Context(), a.SyncTimeout) + defer cancel() + + if !IsAuthByUser(c) { + return JSONProblemf(c, http.StatusUnauthorized, "user authentication required") + } + + if !IsManager(c) { + return JSONProblemf(c, http.StatusForbidden, "AppManager privilege required") + } + + var body server.PostAppJSONRequestBody + if err := c.Bind(&body); err != nil { + log.Error("invalid request body", logkey.Error, err) + return JSONProblem(c, http.StatusBadRequest, err.Error()) + } + + log.Info("called", "app_id", appId) + + odb := cdb.New(a.DB) + odb.CreateSession(a.Ev) + + isManager := IsManager(c) + + app, err := odb.GetApp(ctx, appId, nil, true) + if err != nil { + log.Error("cannot get app", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot get app") + } + if app == nil { + return JSONProblemf(c, http.StatusNotFound, "app %s not found", appId) + } + + responsible, err := odb.AppResponsible(ctx, appId, UserGroupsFromContext(c), isManager, "") + if err != nil { + log.Error("cannot check app responsibility", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot check app responsibility") + } + if !responsible { + return JSONProblemf(c, http.StatusForbidden, "you are not responsible for this app") + } + + fields := cdb.UpdateAppFields{ + App: body.App, + Description: body.Description, + AppDomain: body.AppDomain, + AppTeamOps: body.AppTeamOps, + } + + markSuccess, endTx, err := odb.BeginTxWithControl(ctx, log, &sql.TxOptions{}) + if err != nil { + log.Error("cannot start transaction", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot update app") + } + defer endTx() + + if err := odb.UpdateApp(ctx, app.ID, fields); err != nil { + log.Error("cannot update app", "app_id", appId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot update app") + } + + // If the app code is renamed, update nodes and services references + if body.App != nil && *body.App != app.App { + if err := odb.UpdateNodesApp(ctx, app.App, *body.App); err != nil { + log.Error("cannot update nodes app", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot update nodes app reference") + } + if err := odb.UpdateServicesApp(ctx, app.App, *body.App); err != nil { + log.Error("cannot update services app", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot update services app reference") + } + } + + userEmail, _ := c.Get(XUserEmail).(string) + if err := odb.Log(ctx, cdb.LogEntry{ + Action: "apps.change", + User: userEmail, + Fmt: "app %(app)s changed", + Dict: map[string]any{ + "app": app.App, + }, + Level: "info", + }); err != nil { + log.Error("cannot write audit log", logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot write audit log") + } + + markSuccess() + + if err := odb.Session.NotifyTableChangeWithData(ctx, "apps", map[string]any{"id": app.ID}); err != nil { + log.Error("cannot notify apps change", logkey.Error, err) + } + + newAppId := appId + if body.App != nil { + newAppId = *body.App + } + updated, err := odb.GetApp(ctx, newAppId, nil, true) + if err != nil || updated == nil { + return JSONProblemf(c, http.StatusInternalServerError, "cannot fetch updated app") + } + return c.JSON(http.StatusOK, updated) +} From 8e09f39ab09b8634dd96cf71100db8ec1ace340e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Mon, 20 Apr 2026 11:42:41 +0200 Subject: [PATCH 23/24] Move query builder to cdb --- cdb/db_apps.go | 3 +-- cdb/db_arrays.go | 3 +-- cdb/db_disks.go | 3 +-- cdb/db_hbas.go | 3 +-- cdb/db_node_interfaces.go | 3 +-- cdb/db_nodes.go | 3 +-- cdb/db_services.go | 3 +-- cdb/db_services_instances.go | 3 +-- cdb/db_services_instances_status_log.go | 3 +-- {qb => cdb}/qb.go | 18 ++++++++++-------- 10 files changed, 19 insertions(+), 26 deletions(-) rename {qb => cdb}/qb.go (95%) diff --git a/cdb/db_apps.go b/cdb/db_apps.go index 8316583..726bfa2 100644 --- a/cdb/db_apps.go +++ b/cdb/db_apps.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" - "github.com/opensvc/oc3/qb" "github.com/opensvc/oc3/schema" ) @@ -64,7 +63,7 @@ func scanApps(rows *sql.Rows) ([]App, error) { } func buildAppsQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { - q := qb.From(schema.TApps). + q := From(schema.TApps). Distinct(). RawSelect(selectExprs...) diff --git a/cdb/db_arrays.go b/cdb/db_arrays.go index 97c7400..3054b27 100644 --- a/cdb/db_arrays.go +++ b/cdb/db_arrays.go @@ -4,12 +4,11 @@ import ( "context" "fmt" - "github.com/opensvc/oc3/qb" "github.com/opensvc/oc3/schema" ) func buildArraysQuery(selectExprs []string) (string, []any) { - q := qb.From(schema.TStorArray). + q := From(schema.TStorArray). RawSelect(selectExprs...). Where(schema.StorArrayID, ">", 0) diff --git a/cdb/db_disks.go b/cdb/db_disks.go index d834b01..287b0b7 100644 --- a/cdb/db_disks.go +++ b/cdb/db_disks.go @@ -4,12 +4,11 @@ import ( "context" "fmt" - "github.com/opensvc/oc3/qb" "github.com/opensvc/oc3/schema" ) func buildDisksQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { - q := qb.From(schema.TDiskinfo). + q := From(schema.TDiskinfo). LeftJoin(schema.TSvcdisks, schema.TNodes, schema.TServices, schema.TApps). RawSelect(selectExprs...) diff --git a/cdb/db_hbas.go b/cdb/db_hbas.go index 0941239..71708d6 100644 --- a/cdb/db_hbas.go +++ b/cdb/db_hbas.go @@ -4,12 +4,11 @@ import ( "context" "fmt" - "github.com/opensvc/oc3/qb" "github.com/opensvc/oc3/schema" ) func buildHbasQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { - q := qb.From(schema.TNodeHBA). + q := From(schema.TNodeHBA). RawSelect(selectExprs...) if !isManager { diff --git a/cdb/db_node_interfaces.go b/cdb/db_node_interfaces.go index 8d32dbb..0b96a5b 100644 --- a/cdb/db_node_interfaces.go +++ b/cdb/db_node_interfaces.go @@ -5,12 +5,11 @@ import ( "fmt" "strings" - "github.com/opensvc/oc3/qb" "github.com/opensvc/oc3/schema" ) func buildNodeInterfacesQuery(nodeID string, p ListParams) (string, []any) { - q := qb.From(schema.TNodeIP). + q := From(schema.TNodeIP). RawSelect(p.SelectExprs...). Where(schema.NodeIPNodeID, "=", nodeID) diff --git a/cdb/db_nodes.go b/cdb/db_nodes.go index 5a4e841..862a880 100644 --- a/cdb/db_nodes.go +++ b/cdb/db_nodes.go @@ -11,7 +11,6 @@ import ( "github.com/google/uuid" - "github.com/opensvc/oc3/qb" "github.com/opensvc/oc3/schema" "github.com/opensvc/oc3/util/logkey" ) @@ -52,7 +51,7 @@ func (n *DBNode) String() string { } func buildNodesQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { - q := qb.From(schema.TNodes). + q := From(schema.TNodes). RawSelect(selectExprs...) if !isManager { diff --git a/cdb/db_services.go b/cdb/db_services.go index 9cf9e79..5fb9e5c 100644 --- a/cdb/db_services.go +++ b/cdb/db_services.go @@ -4,12 +4,11 @@ import ( "context" "fmt" - "github.com/opensvc/oc3/qb" "github.com/opensvc/oc3/schema" ) func buildServicesQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { - q := qb.From(schema.TServices). + q := From(schema.TServices). RawSelect(selectExprs...) if !isManager { diff --git a/cdb/db_services_instances.go b/cdb/db_services_instances.go index 8ccd977..1284567 100644 --- a/cdb/db_services_instances.go +++ b/cdb/db_services_instances.go @@ -4,12 +4,11 @@ import ( "context" "fmt" - "github.com/opensvc/oc3/qb" "github.com/opensvc/oc3/schema" ) func buildServicesInstancesQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { - q := qb.From(schema.TSvcmon). + q := From(schema.TSvcmon). Via(schema.TServices). RawSelect(selectExprs...) diff --git a/cdb/db_services_instances_status_log.go b/cdb/db_services_instances_status_log.go index 188c2d0..5e4187d 100644 --- a/cdb/db_services_instances_status_log.go +++ b/cdb/db_services_instances_status_log.go @@ -4,12 +4,11 @@ import ( "context" "fmt" - "github.com/opensvc/oc3/qb" "github.com/opensvc/oc3/schema" ) func buildServicesInstancesStatusLogQuery(groups []string, isManager bool, selectExprs []string) (string, []any) { - q := qb.From(schema.TSvcmonLog). + q := From(schema.TSvcmonLog). Via(schema.TServices). RawSelect(selectExprs...) diff --git a/qb/qb.go b/cdb/qb.go similarity index 95% rename from qb/qb.go rename to cdb/qb.go index a4c81d0..25ac264 100644 --- a/qb/qb.go +++ b/cdb/qb.go @@ -1,9 +1,11 @@ -// Package qb provides a lightweight SQL query builder with automatic JOIN -// resolution based on the FK relationships declared in schema/relations.go. +// Package cdb - query builder +// +// Lightweight SQL query builder with automatic JOIN resolution based on the FK +// relationships declared in schema/relations.go. // // # Basic usage // -// sql, args, err := qb.From(schema.TApps). +// sql, args, err := From(schema.TApps). // Select(schema.AppsID, schema.AppsApp). // Where(schema.AppsID, ">", 0). // Build() @@ -14,7 +16,7 @@ // builder resolves the required JOINs automatically by walking the Ref chain // declared in schema/relations.go: // -// sql, args, err := qb.From(schema.TAuthGroup). +// sql, args, err := From(schema.TAuthGroup). // Select(schema.AuthGroupID, schema.AuthGroupRole). // Via(schema.TAppsResponsibles). // disambiguates: apps_responsibles vs apps_publications // Where(schema.AppsResponsiblesAppID, "=", appID). @@ -31,11 +33,11 @@ // disambiguate the path: // // // Error: both apps_responsibles and apps_publications connect apps to auth_group -// qb.From(schema.TApps).Select(schema.AuthGroupRole).Build() +// From(schema.TApps).Select(schema.AuthGroupRole).Build() // // // OK: path is explicit -// qb.From(schema.TApps).Select(schema.AuthGroupRole).Via(schema.TAppsResponsibles).Build() -package qb +// From(schema.TApps).Select(schema.AuthGroupRole).Via(schema.TAppsResponsibles).Build() +package cdb import ( "fmt" @@ -96,7 +98,7 @@ func (q *Query) Select(cols ...*schema.Col) *Query { return q } -// RawSelect adds raw SQL expressions to the SELECT clause (e.g. "COALESCE(apps.updated, ”)"). +// RawSelect adds raw SQL expressions to the SELECT clause (e.g. "COALESCE(apps.updated, ")"). // These are emitted verbatim and do not participate in JOIN resolution. // Use Select() for typed column references whenever possible. func (q *Query) RawSelect(exprs ...string) *Query { From f15bd0e3d7fa6d023a70ce554e2a0ebc6647b873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Guih=C3=A9neuf?= Date: Mon, 20 Apr 2026 12:12:34 +0200 Subject: [PATCH 24/24] Add /missing /tags/* endpoints --- cdb/db_tags.go | 310 +++++++++ server/api.yaml | 229 +++++++ server/codegen_server_gen.go | 604 ++++++++++++++++-- server/codegen_type_gen.go | 168 +++++ server/handlers/get_node_candidate_tags.go | 36 ++ server/handlers/get_node_tags.go | 36 ++ server/handlers/get_service_candidate_tags.go | 41 ++ server/handlers/get_service_tags.go | 41 ++ server/handlers/get_tag_nodes.go | 15 +- server/handlers/get_tag_services.go | 26 + server/handlers/get_tags_nodes.go | 25 + server/handlers/get_tags_services.go | 25 + server/handlers/props_mapping.go | 30 + 13 files changed, 1536 insertions(+), 50 deletions(-) create mode 100644 server/handlers/get_node_candidate_tags.go create mode 100644 server/handlers/get_node_tags.go create mode 100644 server/handlers/get_service_candidate_tags.go create mode 100644 server/handlers/get_service_tags.go create mode 100644 server/handlers/get_tag_services.go create mode 100644 server/handlers/get_tags_nodes.go create mode 100644 server/handlers/get_tags_services.go diff --git a/cdb/db_tags.go b/cdb/db_tags.go index 1818108..0603cd2 100644 --- a/cdb/db_tags.go +++ b/cdb/db_tags.go @@ -5,6 +5,9 @@ import ( "database/sql" "encoding/json" "fmt" + "strings" + + "github.com/opensvc/oc3/schema" ) type Tag struct { @@ -76,3 +79,310 @@ func (oDb *DB) GetTags(ctx context.Context, tagID *int, limit, offset int) ([]Ta return tags, nil } + +// GetTagNodes returns nodes where a tag (by integer id) is attached, with app-based auth. +func (oDb *DB) GetTagNodes(ctx context.Context, tagID int, p ListParams) ([]map[string]any, error) { + query, args := buildNodesQuery(p.Groups, p.IsManager, p.SelectExprs) + query += " AND nodes.node_id IN (SELECT node_id FROM node_tags WHERE node_tags.tag_id = (SELECT tag_id FROM tags WHERE id = ?))" + args = append(args, tagID) + 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("GetTagNodes: %w", err) + } + defer func() { _ = rows.Close() }() + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// GetNodeTags returns tags attached to a node (identified by node_id UUID). +func (oDb *DB) GetNodeTags(ctx context.Context, nodeID string, p ListParams) ([]map[string]any, error) { + q := From(schema.TTags). + RawSelect(p.SelectExprs...). + WhereRaw("tags.tag_id IN (SELECT tag_id FROM node_tags WHERE node_id = ?)", nodeID) + q = applyNodeAppAuth(q, nodeID, p.Groups, p.IsManager) + query, args, err := q.Build() + if err != nil { + return nil, fmt.Errorf("GetNodeTags build: %w", err) + } + query += " " + p.OrderByClause("tags.tag_name") + query, args = appendLimitOffset(query, args, p.Limit, p.Offset) + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("GetNodeTags: %w", err) + } + defer func() { _ = rows.Close() }() + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// GetServiceTags returns tags attached to a service (identified by svc_id UUID or svcname). +func (oDb *DB) GetServiceTags(ctx context.Context, svcID string, p ListParams) ([]map[string]any, error) { + q := From(schema.TTags). + RawSelect(p.SelectExprs...). + WhereRaw("tags.tag_id IN (SELECT tag_id FROM svc_tags WHERE svc_id = (SELECT svc_id FROM services WHERE svc_id = ? OR svcname = ? LIMIT 1))", svcID, svcID) + q = applySvcAppAuth(q, svcID, p.Groups, p.IsManager) + query, args, err := q.Build() + if err != nil { + return nil, fmt.Errorf("GetServiceTags build: %w", err) + } + query += " " + p.OrderByClause("tags.tag_name") + query, args = appendLimitOffset(query, args, p.Limit, p.Offset) + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("GetServiceTags: %w", err) + } + defer func() { _ = rows.Close() }() + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// GetNodeCandidateTags returns tags not yet attached to the node and not excluded by existing tag rules. +func (oDb *DB) GetNodeCandidateTags(ctx context.Context, nodeID string, p ListParams) ([]map[string]any, error) { + type attachedTag struct { + TagID string + TagExclude sql.NullString + } + rowsAttached, err := oDb.DB.QueryContext(ctx, + "SELECT tags.tag_id, tags.tag_exclude FROM tags JOIN node_tags ON node_tags.tag_id = tags.tag_id WHERE node_tags.node_id = ?", + nodeID, + ) + if err != nil { + return nil, fmt.Errorf("GetNodeCandidateTags attached: %w", err) + } + defer func() { _ = rowsAttached.Close() }() + + var tagIDs []string + var excludePatterns []string + for rowsAttached.Next() { + var t attachedTag + if err := rowsAttached.Scan(&t.TagID, &t.TagExclude); err != nil { + return nil, fmt.Errorf("GetNodeCandidateTags scan: %w", err) + } + tagIDs = append(tagIDs, t.TagID) + if t.TagExclude.Valid && t.TagExclude.String != "" { + excludePatterns = append(excludePatterns, t.TagExclude.String) + } + } + if err := rowsAttached.Err(); err != nil { + return nil, fmt.Errorf("GetNodeCandidateTags rows: %w", err) + } + + q := From(schema.TTags).RawSelect(p.SelectExprs...).Where(schema.TagsID, ">", 0) + q = applyNodeAppAuth(q, nodeID, p.Groups, p.IsManager) + if len(tagIDs) > 0 { + q = q.WhereRaw("tags.tag_id NOT IN ("+Placeholders(len(tagIDs))+")", stringsToAny(tagIDs)...) + } + if len(excludePatterns) > 0 { + q = q.WhereRaw("tags.tag_name NOT REGEXP ?", strings.Join(excludePatterns, "|")) + } + query, args, err := q.Build() + if err != nil { + return nil, fmt.Errorf("GetNodeCandidateTags build: %w", err) + } + query += " " + p.OrderByClause("tags.tag_name") + query, args = appendLimitOffset(query, args, p.Limit, p.Offset) + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("GetNodeCandidateTags: %w", err) + } + defer func() { _ = rows.Close() }() + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// GetServiceCandidateTags returns tags not yet attached to the service and not excluded by existing tag rules. +func (oDb *DB) GetServiceCandidateTags(ctx context.Context, svcID string, p ListParams) ([]map[string]any, error) { + type attachedTag struct { + TagID string + TagExclude sql.NullString + } + rowsAttached, err := oDb.DB.QueryContext(ctx, + "SELECT tags.tag_id, tags.tag_exclude FROM tags JOIN svc_tags ON svc_tags.tag_id = tags.tag_id"+ + " JOIN services ON services.svc_id = svc_tags.svc_id WHERE services.svc_id = ? OR services.svcname = ?", + svcID, svcID, + ) + if err != nil { + return nil, fmt.Errorf("GetServiceCandidateTags attached: %w", err) + } + defer func() { _ = rowsAttached.Close() }() + + var tagIDs []string + var excludePatterns []string + for rowsAttached.Next() { + var t attachedTag + if err := rowsAttached.Scan(&t.TagID, &t.TagExclude); err != nil { + return nil, fmt.Errorf("GetServiceCandidateTags scan: %w", err) + } + tagIDs = append(tagIDs, t.TagID) + if t.TagExclude.Valid && t.TagExclude.String != "" { + excludePatterns = append(excludePatterns, t.TagExclude.String) + } + } + if err := rowsAttached.Err(); err != nil { + return nil, fmt.Errorf("GetServiceCandidateTags rows: %w", err) + } + + q := From(schema.TTags).RawSelect(p.SelectExprs...).Where(schema.TagsID, ">", 0) + q = applySvcAppAuth(q, svcID, p.Groups, p.IsManager) + if len(tagIDs) > 0 { + q = q.WhereRaw("tags.tag_id NOT IN ("+Placeholders(len(tagIDs))+")", stringsToAny(tagIDs)...) + } + if len(excludePatterns) > 0 { + q = q.WhereRaw("tags.tag_name NOT REGEXP ?", strings.Join(excludePatterns, "|")) + } + query, args, err := q.Build() + if err != nil { + return nil, fmt.Errorf("GetServiceCandidateTags build: %w", err) + } + query += " " + p.OrderByClause("tags.tag_name") + query, args = appendLimitOffset(query, args, p.Limit, p.Offset) + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("GetServiceCandidateTags: %w", err) + } + defer func() { _ = rows.Close() }() + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// GetTagServices returns services where a tag (by integer id) is attached, with app-based auth. +func (oDb *DB) GetTagServices(ctx context.Context, tagID int, p ListParams) ([]map[string]any, error) { + query, args := buildServicesQuery(p.Groups, p.IsManager, p.SelectExprs) + query += " AND services.svc_id IN (SELECT svc_id FROM svc_tags WHERE svc_tags.tag_id = (SELECT tag_id FROM tags WHERE id = ?))" + args = append(args, tagID) + if gb := p.GroupByClause(""); gb != "" { + query += " " + gb + } + query += " " + p.OrderByClause("services.svcname") + query, args = appendLimitOffset(query, args, p.Limit, p.Offset) + rows, err := oDb.DB.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("GetTagServices: %w", err) + } + defer func() { _ = rows.Close() }() + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// GetTagsNodes returns all node_tag attachment records with node app-based auth. +func (oDb *DB) GetTagsNodes(ctx context.Context, p ListParams) ([]map[string]any, error) { + q := From(schema.TNodeTags).RawSelect(p.SelectExprs...) + if !p.IsManager { + cleanG := cleanGroups(p.Groups) + if len(cleanG) == 0 { + q = q.WhereRaw("1=0") + } else { + gArgs := stringsToAny(cleanG) + q = q.WhereRaw( + "node_tags.node_id IN (SELECT n.node_id FROM nodes n WHERE n.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(cleanG))+")))", + gArgs..., + ) + } + } else { + q = q.Where(schema.NodeTagsID, ">", 0) + } + query, args, err := q.Build() + if err != nil { + return nil, fmt.Errorf("GetTagsNodes build: %w", err) + } + if gb := p.GroupByClause(""); gb != "" { + query += " " + gb + } + query += " " + p.OrderByClause("node_tags.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("GetTagsNodes: %w", err) + } + defer func() { _ = rows.Close() }() + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// GetTagsServices returns all svc_tag attachment records with service app-based auth. +func (oDb *DB) GetTagsServices(ctx context.Context, p ListParams) ([]map[string]any, error) { + q := From(schema.TSvcTags).RawSelect(p.SelectExprs...) + if !p.IsManager { + cleanG := cleanGroups(p.Groups) + if len(cleanG) == 0 { + q = q.WhereRaw("1=0") + } else { + gArgs := stringsToAny(cleanG) + q = q.WhereRaw( + "svc_tags.svc_id IN (SELECT s.svc_id FROM services s WHERE s.svc_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(cleanG))+")))", + gArgs..., + ) + } + } else { + q = q.Where(schema.SvcTagsID, ">", 0) + } + query, args, err := q.Build() + if err != nil { + return nil, fmt.Errorf("GetTagsServices build: %w", err) + } + if gb := p.GroupByClause(""); gb != "" { + query += " " + gb + } + query += " " + p.OrderByClause("svc_tags.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("GetTagsServices: %w", err) + } + defer func() { _ = rows.Close() }() + return scanRowsToMaps(rows, p.Props, p.TypeHints) +} + +// applyNodeAppAuth adds a WHERE condition ensuring the node's app is accessible to the user's groups. +func applyNodeAppAuth(q *Query, nodeID string, groups []string, isManager bool) *Query { + if isManager { + return q + } + cleanG := cleanGroups(groups) + if len(cleanG) == 0 { + return q.WhereRaw("1=0") + } + gArgs := stringsToAny(cleanG) + return q.WhereRaw( + "EXISTS (SELECT 1 FROM nodes n WHERE n.node_id = ? AND n.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(cleanG))+")))", + append([]any{nodeID}, gArgs...)..., + ) +} + +// applySvcAppAuth adds a WHERE condition ensuring the service's app is accessible to the user's groups. +func applySvcAppAuth(q *Query, svcID string, groups []string, isManager bool) *Query { + if isManager { + return q + } + cleanG := cleanGroups(groups) + if len(cleanG) == 0 { + return q.WhereRaw("1=0") + } + gArgs := stringsToAny(cleanG) + return q.WhereRaw( + "EXISTS (SELECT 1 FROM services s WHERE (s.svc_id = ? OR s.svcname = ?) AND s.svc_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(cleanG))+")))", + append([]any{svcID, svcID}, gArgs...)..., + ) +} + +func stringsToAny(ss []string) []any { + out := make([]any, len(ss)) + for i, s := range ss { + out[i] = s + } + return out +} diff --git a/server/api.yaml b/server/api.yaml index 800436a..fe915bc 100644 --- a/server/api.yaml +++ b/server/api.yaml @@ -1159,6 +1159,235 @@ paths: - basicAuth: [ ] - bearerAuth: [ ] + /nodes/{node_id}/tags: + get: + operationId: GetNodeTags + description: List tags attached to a node + parameters: + - in: path + name: node_id + required: true + description: Node identifier (node_id UUID or nodename) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /nodes/{node_id}/candidate_tags: + get: + operationId: GetNodeCandidateTags + description: List tags attachable to a node (not yet attached and not excluded) + parameters: + - in: path + name: node_id + required: true + description: Node identifier (node_id UUID or nodename) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /services/{svc_id}/tags: + get: + operationId: GetServiceTags + description: List tags attached to a service + parameters: + - in: path + name: svc_id + required: true + description: Service identifier (svc_id UUID or svcname) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /services/{svc_id}/candidate_tags: + get: + operationId: GetServiceCandidateTags + description: List tags attachable to a service (not yet attached and not excluded) + parameters: + - in: path + name: svc_id + required: true + description: Service identifier (svc_id UUID or svcname) + schema: + type: string + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /tags/nodes: + get: + operationId: GetTagsNodes + description: List all tag-node attachments + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /tags/services: + get: + operationId: GetTagsServices + description: List all tag-service attachments + parameters: + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + + /tags/{tag_id}/services: + get: + operationId: GetTagServices + description: List services where the tag is attached + parameters: + - in: path + name: tag_id + required: true + description: ID of the tag + schema: + type: integer + - $ref: '#/components/parameters/inQueryProps' + - $ref: '#/components/parameters/inQueryLimit' + - $ref: '#/components/parameters/inQueryOffset' + - $ref: '#/components/parameters/inQueryMeta' + - $ref: '#/components/parameters/inQueryStats' + - $ref: '#/components/parameters/inQueryOrderby' + - $ref: '#/components/parameters/inQueryGroupby' + tags: + - collector + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResponse' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + security: + - basicAuth: [ ] + - bearerAuth: [ ] + /version: get: operationId: GetVersion diff --git a/server/codegen_server_gen.go b/server/codegen_server_gen.go index 8923771..f3dc9ef 100644 --- a/server/codegen_server_gen.go +++ b/server/codegen_server_gen.go @@ -66,6 +66,9 @@ type ServerInterface interface { // (GET /nodes/{node_id}) GetNode(ctx echo.Context, nodeId string, params GetNodeParams) error + // (GET /nodes/{node_id}/candidate_tags) + GetNodeCandidateTags(ctx echo.Context, nodeId string, params GetNodeCandidateTagsParams) error + // (GET /nodes/{node_id}/compliance/candidate_modulesets) GetNodeComplianceCandidateModulesets(ctx echo.Context, nodeId InPathNodeId, params GetNodeComplianceCandidateModulesetsParams) error @@ -102,6 +105,9 @@ type ServerInterface interface { // (GET /nodes/{node_id}/interfaces) GetNodeInterfaces(ctx echo.Context, nodeId string, params GetNodeInterfacesParams) error + // (GET /nodes/{node_id}/tags) + GetNodeTags(ctx echo.Context, nodeId string, params GetNodeTagsParams) error + // (GET /nodes/{node_id}/uuid) GetNodeUUID(ctx echo.Context, nodeId string) error @@ -114,6 +120,12 @@ type ServerInterface interface { // (GET /services/{svc_id}) GetService(ctx echo.Context, svcId string, params GetServiceParams) error + // (GET /services/{svc_id}/candidate_tags) + GetServiceCandidateTags(ctx echo.Context, svcId string, params GetServiceCandidateTagsParams) error + + // (GET /services/{svc_id}/tags) + GetServiceTags(ctx echo.Context, svcId string, params GetServiceTagsParams) error + // (GET /services_instances) GetServicesInstances(ctx echo.Context, params GetServicesInstancesParams) error @@ -126,12 +138,21 @@ type ServerInterface interface { // (GET /tags) GetTags(ctx echo.Context, params GetTagsParams) error + // (GET /tags/nodes) + GetTagsNodes(ctx echo.Context, params GetTagsNodesParams) error + + // (GET /tags/services) + GetTagsServices(ctx echo.Context, params GetTagsServicesParams) error + // (GET /tags/{tag_id}) GetTag(ctx echo.Context, tagId int, params GetTagParams) error // (GET /tags/{tag_id}/nodes) GetTagNodes(ctx echo.Context, tagId int, params GetTagNodesParams) error + // (GET /tags/{tag_id}/services) + GetTagServices(ctx echo.Context, tagId int, params GetTagServicesParams) error + // (GET /version) GetVersion(ctx echo.Context) error } @@ -860,6 +881,77 @@ func (w *ServerInterfaceWrapper) GetNode(ctx echo.Context) error { return err } +// GetNodeCandidateTags converts echo context to params. +func (w *ServerInterfaceWrapper) GetNodeCandidateTags(ctx echo.Context) error { + var err error + // ------------- Path parameter "node_id" ------------- + var nodeId string + + err = runtime.BindStyledParameterWithOptions("simple", "node_id", ctx.Param("node_id"), &nodeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter node_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetNodeCandidateTagsParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNodeCandidateTags(ctx, nodeId, params) + return err +} + // GetNodeComplianceCandidateModulesets converts echo context to params. func (w *ServerInterfaceWrapper) GetNodeComplianceCandidateModulesets(ctx echo.Context) error { var err error @@ -1498,6 +1590,77 @@ func (w *ServerInterfaceWrapper) GetNodeInterfaces(ctx echo.Context) error { return err } +// GetNodeTags converts echo context to params. +func (w *ServerInterfaceWrapper) GetNodeTags(ctx echo.Context) error { + var err error + // ------------- Path parameter "node_id" ------------- + var nodeId string + + err = runtime.BindStyledParameterWithOptions("simple", "node_id", ctx.Param("node_id"), &nodeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter node_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetNodeTagsParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetNodeTags(ctx, nodeId, params) + return err +} + // GetNodeUUID converts echo context to params. func (w *ServerInterfaceWrapper) GetNodeUUID(ctx echo.Context) error { var err error @@ -1662,6 +1825,148 @@ func (w *ServerInterfaceWrapper) GetService(ctx echo.Context) error { return err } +// GetServiceCandidateTags converts echo context to params. +func (w *ServerInterfaceWrapper) GetServiceCandidateTags(ctx echo.Context) error { + var err error + // ------------- Path parameter "svc_id" ------------- + var svcId string + + err = runtime.BindStyledParameterWithOptions("simple", "svc_id", ctx.Param("svc_id"), &svcId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter svc_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetServiceCandidateTagsParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetServiceCandidateTags(ctx, svcId, params) + return err +} + +// GetServiceTags converts echo context to params. +func (w *ServerInterfaceWrapper) GetServiceTags(ctx echo.Context) error { + var err error + // ------------- Path parameter "svc_id" ------------- + var svcId string + + err = runtime.BindStyledParameterWithOptions("simple", "svc_id", ctx.Param("svc_id"), &svcId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter svc_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetServiceTagsParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetServiceTags(ctx, svcId, params) + return err +} + // GetServicesInstances converts echo context to params. func (w *ServerInterfaceWrapper) GetServicesInstances(ctx echo.Context) error { var err error @@ -1925,6 +2230,134 @@ func (w *ServerInterfaceWrapper) GetTags(ctx echo.Context) error { return err } +// GetTagsNodes converts echo context to params. +func (w *ServerInterfaceWrapper) GetTagsNodes(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetTagsNodesParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetTagsNodes(ctx, params) + return err +} + +// GetTagsServices converts echo context to params. +func (w *ServerInterfaceWrapper) GetTagsServices(ctx echo.Context) error { + var err error + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetTagsServicesParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetTagsServices(ctx, params) + return err +} + // GetTag converts echo context to params. func (w *ServerInterfaceWrapper) GetTag(ctx echo.Context) error { var err error @@ -1997,6 +2430,77 @@ func (w *ServerInterfaceWrapper) GetTagNodes(ctx echo.Context) error { return err } +// GetTagServices converts echo context to params. +func (w *ServerInterfaceWrapper) GetTagServices(ctx echo.Context) error { + var err error + // ------------- Path parameter "tag_id" ------------- + var tagId int + + err = runtime.BindStyledParameterWithOptions("simple", "tag_id", ctx.Param("tag_id"), &tagId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tag_id: %s", err)) + } + + ctx.Set(BasicAuthScopes, []string{}) + + ctx.Set(BearerAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetTagServicesParams + // ------------- Optional query parameter "props" ------------- + + err = runtime.BindQueryParameter("form", true, false, "props", ctx.QueryParams(), ¶ms.Props) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter props: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // ------------- Optional query parameter "meta" ------------- + + err = runtime.BindQueryParameter("form", true, false, "meta", ctx.QueryParams(), ¶ms.Meta) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter meta: %s", err)) + } + + // ------------- Optional query parameter "stats" ------------- + + err = runtime.BindQueryParameter("form", true, false, "stats", ctx.QueryParams(), ¶ms.Stats) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter stats: %s", err)) + } + + // ------------- Optional query parameter "orderby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "orderby", ctx.QueryParams(), ¶ms.Orderby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter orderby: %s", err)) + } + + // ------------- Optional query parameter "groupby" ------------- + + err = runtime.BindQueryParameter("form", true, false, "groupby", ctx.QueryParams(), ¶ms.Groupby) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter groupby: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetTagServices(ctx, tagId, params) + return err +} + // GetVersion converts echo context to params. func (w *ServerInterfaceWrapper) GetVersion(ctx echo.Context) error { var err error @@ -2049,6 +2553,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/nodes", wrapper.GetNodes) router.GET(baseURL+"/nodes/hbas", wrapper.GetNodesHbas) router.GET(baseURL+"/nodes/:node_id", wrapper.GetNode) + router.GET(baseURL+"/nodes/:node_id/candidate_tags", wrapper.GetNodeCandidateTags) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_modulesets", wrapper.GetNodeComplianceCandidateModulesets) router.GET(baseURL+"/nodes/:node_id/compliance/candidate_rulesets", wrapper.GetNodeComplianceCandidateRulesets) router.GET(baseURL+"/nodes/:node_id/compliance/logs", wrapper.GetNodeComplianceLogs) @@ -2061,16 +2566,22 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/nodes/:node_id/disks", wrapper.GetNodeDisks) router.GET(baseURL+"/nodes/:node_id/hbas", wrapper.GetNodeHbas) router.GET(baseURL+"/nodes/:node_id/interfaces", wrapper.GetNodeInterfaces) + router.GET(baseURL+"/nodes/:node_id/tags", wrapper.GetNodeTags) router.GET(baseURL+"/nodes/:node_id/uuid", wrapper.GetNodeUUID) router.GET(baseURL+"/openapi.json", wrapper.GetSwagger) router.GET(baseURL+"/services", wrapper.GetServices) router.GET(baseURL+"/services/:svc_id", wrapper.GetService) + router.GET(baseURL+"/services/:svc_id/candidate_tags", wrapper.GetServiceCandidateTags) + router.GET(baseURL+"/services/:svc_id/tags", wrapper.GetServiceTags) router.GET(baseURL+"/services_instances", wrapper.GetServicesInstances) router.GET(baseURL+"/services_instances/:svc_id", wrapper.GetServicesInstance) router.GET(baseURL+"/services_instances_status_log", wrapper.GetServicesInstancesStatusLog) router.GET(baseURL+"/tags", wrapper.GetTags) + router.GET(baseURL+"/tags/nodes", wrapper.GetTagsNodes) + router.GET(baseURL+"/tags/services", wrapper.GetTagsServices) router.GET(baseURL+"/tags/:tag_id", wrapper.GetTag) router.GET(baseURL+"/tags/:tag_id/nodes", wrapper.GetTagNodes) + router.GET(baseURL+"/tags/:tag_id/services", wrapper.GetTagServices) router.GET(baseURL+"/version", wrapper.GetVersion) } @@ -2078,51 +2589,54 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/codegen_type_gen.go b/server/codegen_type_gen.go index 305af60..41f84ca 100644 --- a/server/codegen_type_gen.go +++ b/server/codegen_type_gen.go @@ -346,6 +346,30 @@ type GetNodeParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetNodeCandidateTagsParams defines parameters for GetNodeCandidateTags. +type GetNodeCandidateTagsParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetNodeComplianceCandidateModulesetsParams defines parameters for GetNodeComplianceCandidateModulesets. type GetNodeComplianceCandidateModulesetsParams struct { // Props A list of properties to include in each data dictionnary. @@ -526,6 +550,30 @@ type GetNodeInterfacesParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetNodeTagsParams defines parameters for GetNodeTags. +type GetNodeTagsParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetServicesParams defines parameters for GetServices. type GetServicesParams struct { // Props A list of properties to include in each data dictionnary. @@ -574,6 +622,54 @@ type GetServiceParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetServiceCandidateTagsParams defines parameters for GetServiceCandidateTags. +type GetServiceCandidateTagsParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + +// GetServiceTagsParams defines parameters for GetServiceTags. +type GetServiceTagsParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetServicesInstancesParams defines parameters for GetServicesInstances. type GetServicesInstancesParams struct { // Props A list of properties to include in each data dictionnary. @@ -670,6 +766,54 @@ type GetTagsParams struct { Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` } +// GetTagsNodesParams defines parameters for GetTagsNodes. +type GetTagsNodesParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + +// GetTagsServicesParams defines parameters for GetTagsServices. +type GetTagsServicesParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // GetTagParams defines parameters for GetTag. type GetTagParams struct { // Props A list of properties to include in each data dictionnary. @@ -688,6 +832,30 @@ type GetTagNodesParams struct { Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` } +// GetTagServicesParams defines parameters for GetTagServices. +type GetTagServicesParams struct { + // Props A list of properties to include in each data dictionnary. + Props *InQueryProps `form:"props,omitempty" json:"props,omitempty"` + + // Limit The maximum number of entries to return. 0 means no limit. + Limit *InQueryLimit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Skip the first entries of the data cursor. + Offset *InQueryOffset `form:"offset,omitempty" json:"offset,omitempty"` + + // Meta Include metadata in the response. Enabled by default. Use false or 0 to omit the meta field. + Meta *InQueryMeta `form:"meta,omitempty" json:"meta,omitempty"` + + // Stats Controls the inclusion in the returned dictionnary of a "stats" key, containing the selected properties distinct values counts. + Stats *InQueryStats `form:"stats,omitempty" json:"stats,omitempty"` + + // Orderby Comma-separated list of properties to sort by. Prefix a property with - for descending order (e.g. orderby=nodename,-app). + Orderby *InQueryOrderby `form:"orderby,omitempty" json:"orderby,omitempty"` + + // Groupby Comma-separated list of properties to group the result by (e.g. groupby=app,svcname). + Groupby *InQueryGroupby `form:"groupby,omitempty" json:"groupby,omitempty"` +} + // PostAppsJSONRequestBody defines body for PostApps for application/json ContentType. type PostAppsJSONRequestBody PostAppsJSONBody diff --git a/server/handlers/get_node_candidate_tags.go b/server/handlers/get_node_candidate_tags.go new file mode 100644 index 0000000..17e6042 --- /dev/null +++ b/server/handlers/get_node_candidate_tags.go @@ -0,0 +1,36 @@ +package serverhandlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetNodeCandidateTags handles GET /nodes/{node_id}/candidate_tags +func (a *Api) GetNodeCandidateTags(c echo.Context, nodeId string, params server.GetNodeCandidateTagsParams) error { + log := echolog.GetLogHandler(c, "GetNodeCandidateTags") + odb := a.getODB() + ctx := c.Request().Context() + + node, err := odb.NodeByNodeIDOrNodename(ctx, nodeId) + if err != nil { + log.Error("cannot resolve node", logkey.NodeID, nodeId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve node") + } + if node == nil { + return JSONProblemf(c, http.StatusNotFound, "node %s not found", nodeId) + } + + return a.handleList(c, "GetNodeCandidateTags", "tag", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetNodeCandidateTags(ctx, node.NodeID, p) + }) +} diff --git a/server/handlers/get_node_tags.go b/server/handlers/get_node_tags.go new file mode 100644 index 0000000..28fcb56 --- /dev/null +++ b/server/handlers/get_node_tags.go @@ -0,0 +1,36 @@ +package serverhandlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetNodeTags handles GET /nodes/{node_id}/tags +func (a *Api) GetNodeTags(c echo.Context, nodeId string, params server.GetNodeTagsParams) error { + log := echolog.GetLogHandler(c, "GetNodeTags") + odb := a.getODB() + ctx := c.Request().Context() + + node, err := odb.NodeByNodeIDOrNodename(ctx, nodeId) + if err != nil { + log.Error("cannot resolve node", logkey.NodeID, nodeId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve node") + } + if node == nil { + return JSONProblemf(c, http.StatusNotFound, "node %s not found", nodeId) + } + + return a.handleList(c, "GetNodeTags", "tag", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetNodeTags(ctx, node.NodeID, p) + }) +} diff --git a/server/handlers/get_service_candidate_tags.go b/server/handlers/get_service_candidate_tags.go new file mode 100644 index 0000000..efdc19d --- /dev/null +++ b/server/handlers/get_service_candidate_tags.go @@ -0,0 +1,41 @@ +package serverhandlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetServiceCandidateTags handles GET /services/{svc_id}/candidate_tags +func (a *Api) GetServiceCandidateTags(c echo.Context, svcId string, params server.GetServiceCandidateTagsParams) error { + log := echolog.GetLogHandler(c, "GetServiceCandidateTags") + odb := a.getODB() + ctx := c.Request().Context() + + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + log.Info("called", "svc_id", svcId, "is_manager", isManager) + + // Verify service exists and is accessible to this user + svcs, err := odb.GetService(ctx, svcId, cdb.ListParams{Limit: 1, Groups: groups, IsManager: isManager}) + if err != nil { + log.Error("cannot resolve service", "svc_id", svcId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve service") + } + if len(svcs) == 0 { + return JSONProblemf(c, http.StatusNotFound, "service %s not found", svcId) + } + + return a.handleList(c, "GetServiceCandidateTags", "tag", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetServiceCandidateTags(ctx, svcId, p) + }) +} diff --git a/server/handlers/get_service_tags.go b/server/handlers/get_service_tags.go new file mode 100644 index 0000000..9332745 --- /dev/null +++ b/server/handlers/get_service_tags.go @@ -0,0 +1,41 @@ +package serverhandlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetServiceTags handles GET /services/{svc_id}/tags +func (a *Api) GetServiceTags(c echo.Context, svcId string, params server.GetServiceTagsParams) error { + log := echolog.GetLogHandler(c, "GetServiceTags") + odb := a.getODB() + ctx := c.Request().Context() + + groups := UserGroupsFromContext(c) + isManager := IsManager(c) + log.Info("called", "svc_id", svcId, "is_manager", isManager) + + // Verify service exists and is accessible to this user + svcs, err := odb.GetService(ctx, svcId, cdb.ListParams{Limit: 1, Groups: groups, IsManager: isManager}) + if err != nil { + log.Error("cannot resolve service", "svc_id", svcId, logkey.Error, err) + return JSONProblemf(c, http.StatusInternalServerError, "cannot resolve service") + } + if len(svcs) == 0 { + return JSONProblemf(c, http.StatusNotFound, "service %s not found", svcId) + } + + return a.handleList(c, "GetServiceTags", "tag", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetServiceTags(ctx, svcId, p) + }) +} diff --git a/server/handlers/get_tag_nodes.go b/server/handlers/get_tag_nodes.go index 3399f13..15f0573 100644 --- a/server/handlers/get_tag_nodes.go +++ b/server/handlers/get_tag_nodes.go @@ -1,8 +1,11 @@ package serverhandlers import ( + "context" + "github.com/labstack/echo/v4" + "github.com/opensvc/oc3/cdb" "github.com/opensvc/oc3/server" "github.com/opensvc/oc3/util/echolog" "github.com/opensvc/oc3/util/logkey" @@ -11,10 +14,12 @@ import ( // GetTagNodes handles GET /tags/{tag_id}/nodes func (a *Api) GetTagNodes(c echo.Context, tagIdParam int, params server.GetTagNodesParams) error { log := echolog.GetLogHandler(c, "GetTagNodes") + log.Info("called", logkey.TagID, tagIdParam) - log.Info("called", logkey.TagID, tagIdParam, "props", params.Props, "limit", params.Limit, "offset", params.Offset) - - // TODO - - return c.JSON(200, []interface{}{}) + odb := a.getODB() + return a.handleList(c, "GetTagNodes", "node", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetTagNodes(ctx, tagIdParam, p) + }) } diff --git a/server/handlers/get_tag_services.go b/server/handlers/get_tag_services.go new file mode 100644 index 0000000..09ec049 --- /dev/null +++ b/server/handlers/get_tag_services.go @@ -0,0 +1,26 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" + "github.com/opensvc/oc3/util/logkey" +) + +// GetTagServices handles GET /tags/{tag_id}/services +func (a *Api) GetTagServices(c echo.Context, tagId int, params server.GetTagServicesParams) error { + log := echolog.GetLogHandler(c, "GetTagServices") + log.Info("called", logkey.TagID, tagId) + + odb := a.getODB() + return a.handleList(c, "GetTagServices", "service", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetTagServices(ctx, tagId, p) + }) +} diff --git a/server/handlers/get_tags_nodes.go b/server/handlers/get_tags_nodes.go new file mode 100644 index 0000000..91a3ec8 --- /dev/null +++ b/server/handlers/get_tags_nodes.go @@ -0,0 +1,25 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" +) + +// GetTagsNodes handles GET /tags/nodes +func (a *Api) GetTagsNodes(c echo.Context, params server.GetTagsNodesParams) error { + log := echolog.GetLogHandler(c, "GetTagsNodes") + log.Info("called") + + odb := a.getODB() + return a.handleList(c, "GetTagsNodes", "node_tag", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetTagsNodes(ctx, p) + }) +} diff --git a/server/handlers/get_tags_services.go b/server/handlers/get_tags_services.go new file mode 100644 index 0000000..1df582d --- /dev/null +++ b/server/handlers/get_tags_services.go @@ -0,0 +1,25 @@ +package serverhandlers + +import ( + "context" + + "github.com/labstack/echo/v4" + + "github.com/opensvc/oc3/cdb" + "github.com/opensvc/oc3/server" + "github.com/opensvc/oc3/util/echolog" +) + +// GetTagsServices handles GET /tags/services +func (a *Api) GetTagsServices(c echo.Context, params server.GetTagsServicesParams) error { + log := echolog.GetLogHandler(c, "GetTagsServices") + log.Info("called") + + odb := a.getODB() + return a.handleList(c, "GetTagsServices", "svc_tag", listEndpointParams{ + props: params.Props, limit: params.Limit, offset: params.Offset, + meta: params.Meta, stats: params.Stats, orderby: params.Orderby, groupby: params.Groupby, + }, func(ctx context.Context, p cdb.ListParams) ([]map[string]any, error) { + return odb.GetTagsServices(ctx, p) + }) +} diff --git a/server/handlers/props_mapping.go b/server/handlers/props_mapping.go index 60f03bf..8da0715 100644 --- a/server/handlers/props_mapping.go +++ b/server/handlers/props_mapping.go @@ -248,6 +248,36 @@ var propsMapping = map[string]propMapping{ "tag": { Available: []string{"id", "tag_name", "tag_created", "tag_exclude", "tag_data", "tag_id"}, Blacklist: map[string]struct{}{"id": {}}, + Props: map[string]propDef{ + "id": col(schema.TagsID), + "tag_name": colStr(schema.TagsTagName), + "tag_created": colStr(schema.TagsTagCreated), + "tag_exclude": colStr(schema.TagsTagExclude), + "tag_data": col(schema.TagsTagData), + "tag_id": colStr(schema.TagsTagID), + }, + }, + "node_tag": { + Available: []string{"id", "created", "node_id", "tag_id", "tag_attach_data"}, + Blacklist: map[string]struct{}{"id": {}}, + Props: map[string]propDef{ + "id": col(schema.NodeTagsID), + "created": colStr(schema.NodeTagsCreated), + "node_id": colStr(schema.NodeTagsNodeID), + "tag_id": colStr(schema.NodeTagsTagID), + "tag_attach_data": colStr(schema.NodeTagsTagAttachData), + }, + }, + "svc_tag": { + Available: []string{"id", "created", "svc_id", "tag_id", "tag_attach_data"}, + Blacklist: map[string]struct{}{"id": {}}, + Props: map[string]propDef{ + "id": col(schema.SvcTagsID), + "created": colStr(schema.SvcTagsCreated), + "svc_id": colStr(schema.SvcTagsSvcID), + "tag_id": colStr(schema.SvcTagsTagID), + "tag_attach_data": colStr(schema.SvcTagsTagAttachData), + }, }, "service": { Available: []string{