diff --git a/core/commoncmd/node_config_doc.go b/core/commoncmd/node_config_doc.go index d67451579..588eda45d 100644 --- a/core/commoncmd/node_config_doc.go +++ b/core/commoncmd/node_config_doc.go @@ -28,7 +28,7 @@ func NewCmdNodeConfigDoc() *cobra.Command { var options CmdNodeConfigDoc cmd := &cobra.Command{ Use: "doc", - Short: "print the documentation of the selected keywords", + Short: "print the keyword documentation", RunE: func(cmd *cobra.Command, args []string) error { return options.Run() }, diff --git a/daemon/daemonapi/lib_config_keywords.go b/core/doc/main.go similarity index 72% rename from daemon/daemonapi/lib_config_keywords.go rename to core/doc/main.go index 66819eb63..6b4a5569c 100644 --- a/daemon/daemonapi/lib_config_keywords.go +++ b/core/doc/main.go @@ -1,45 +1,61 @@ -package daemonapi +package doc import ( + "errors" "fmt" - "net/http" "strings" - "github.com/labstack/echo/v4" - "github.com/opensvc/om3/v3/core/keywords" "github.com/opensvc/om3/v3/core/naming" + "github.com/opensvc/om3/v3/core/xconfig" "github.com/opensvc/om3/v3/daemon/api" "github.com/opensvc/om3/v3/util/key" ) -func filterKeywordStore(ctx echo.Context, store keywords.Store, driver, section, option *string, path naming.Path, getConfigProvider func() (configProvider, error)) (keywords.Store, int, error) { +type ( + ConfigProvider interface { + Config() *xconfig.T + } +) + +var ( + ErrBadRequest = errors.New("driver and section filters are mutually exclusive") +) + +func FilterKeywordStore(store keywords.Store, driver, section, option *string, path naming.Path, getConfigProvider func() (ConfigProvider, error)) (keywords.Store, error) { var err error switch { case driver == nil && section == nil && option == nil: case driver != nil && section != nil && option == nil: - return nil, http.StatusBadRequest, fmt.Errorf("driver and section filters are mutually exclusive") + return nil, ErrBadRequest case driver != nil && section == nil && option == nil: l := keywords.ParseIndex(*driver) store, err = store.DriverKeywords(l[0], l[1], path.Kind) if err != nil { - return nil, http.StatusInternalServerError, err + return nil, err + } + case driver != nil && option != nil: + l := keywords.ParseIndex(*driver) + store, err = store.DriverKeywords(l[0], l[1], path.Kind) + if *option == "" && section != nil { + return store.ByOption(*section), nil } + return store.ByOption(*option), nil case driver == nil && section != nil && option == nil: o, err := getConfigProvider() if err != nil { - return nil, http.StatusInternalServerError, err + return nil, err } sectionType := o.Config().GetString(key.New(*section, "type")) drvGroup, _, _ := strings.Cut(*section, "#") store, err = store.DriverKeywords(drvGroup, sectionType, path.Kind) if err != nil { - return nil, http.StatusInternalServerError, fmt.Errorf("%s.%s: %s", drvGroup, sectionType, err) + return nil, fmt.Errorf("%s.%s: %s", drvGroup, sectionType, err) } case driver == nil && section != nil && option != nil: o, err := getConfigProvider() if err != nil { - return nil, http.StatusInternalServerError, err + return nil, err } sectionType := o.Config().GetString(key.New(*section, "type")) k := key.New(*section, *option) @@ -50,10 +66,10 @@ func filterKeywordStore(ctx echo.Context, store keywords.Store, driver, section, store = []keywords.Keyword{kw} } } - return store, http.StatusOK, nil + return store, nil } -func convertKeywordStore(store keywords.Store) api.KeywordDefinitionItems { +func ConvertKeywordStore(store keywords.Store) api.KeywordDefinitionItems { l := make(api.KeywordDefinitionItems, 0) for _, kw := range store { item := api.KeywordDefinitionItem{ diff --git a/core/keywords/keywords.go b/core/keywords/keywords.go index 66a17a51e..dcd66f790 100644 --- a/core/keywords/keywords.go +++ b/core/keywords/keywords.go @@ -189,6 +189,15 @@ func (t Store) Swap(i, j int) { t[i], t[j] = t[j], t[i] } +func (t Store) ByOption(option string) Store { + for _, kw := range t { + if kw.Option == option { + return Store{kw} + } + } + return Store{} +} + func (t Store) Lookup(k key.T, kind naming.Kind, sectionType string) Keyword { driverGroup := strings.Split(k.Section, "#")[0] baseOption := k.BaseOption() diff --git a/core/om/kind_all.go b/core/om/kind_all.go index 51bcdbaf5..833130c87 100644 --- a/core/om/kind_all.go +++ b/core/om/kind_all.go @@ -2,6 +2,7 @@ package om import ( "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/omcmd" "github.com/opensvc/om3/v3/util/hostname" ) @@ -126,7 +127,7 @@ func init() { newCmdObjectResourceList(kind), ) cmdObjectConfig.AddCommand( - commoncmd.NewCmdObjectConfigDoc(kind), + omcmd.NewCmdObjectConfigDoc(kind), newCmdObjectConfigEdit(kind), newCmdObjectConfigEval(kind), newCmdObjectConfigGet(kind), diff --git a/core/om/kind_ccfg.go b/core/om/kind_ccfg.go index 67585e382..8ac375cc7 100644 --- a/core/om/kind_ccfg.go +++ b/core/om/kind_ccfg.go @@ -1,6 +1,9 @@ package om -import "github.com/opensvc/om3/v3/core/commoncmd" +import ( + "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/omcmd" +) func init() { kind := "ccfg" @@ -45,7 +48,7 @@ func init() { newCmdObjectUnset(kind), ) cmdObjectConfig.AddCommand( - commoncmd.NewCmdObjectConfigDoc(kind), + omcmd.NewCmdObjectConfigDoc(kind), newCmdObjectConfigEdit(kind), newCmdObjectConfigEval(kind), newCmdObjectConfigGet(kind), diff --git a/core/om/kind_cfg.go b/core/om/kind_cfg.go index 38164e1a5..e64e3a12f 100644 --- a/core/om/kind_cfg.go +++ b/core/om/kind_cfg.go @@ -1,6 +1,9 @@ package om -import "github.com/opensvc/om3/v3/core/commoncmd" +import ( + "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/omcmd" +) func init() { kind := "cfg" @@ -49,7 +52,7 @@ func init() { newCmdObjectUnset(kind), ) cmdObjectConfig.AddCommand( - commoncmd.NewCmdObjectConfigDoc(kind), + omcmd.NewCmdObjectConfigDoc(kind), newCmdObjectConfigEdit(kind), newCmdObjectConfigEval(kind), newCmdObjectConfigGet(kind), diff --git a/core/om/kind_nscfg.go b/core/om/kind_nscfg.go index de1b2682f..813deabb4 100644 --- a/core/om/kind_nscfg.go +++ b/core/om/kind_nscfg.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/omcmd" ) func init() { @@ -41,7 +42,7 @@ func init() { ) cmdObjectConfig.AddCommand( - commoncmd.NewCmdObjectConfigDoc(kind), + omcmd.NewCmdObjectConfigDoc(kind), newCmdObjectConfigEdit(kind), newCmdObjectConfigEval(kind), newCmdObjectConfigGet(kind), diff --git a/core/om/kind_sec.go b/core/om/kind_sec.go index 19ab82d76..8374dadd0 100644 --- a/core/om/kind_sec.go +++ b/core/om/kind_sec.go @@ -1,6 +1,9 @@ package om -import "github.com/opensvc/om3/v3/core/commoncmd" +import ( + "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/omcmd" +) func init() { kind := "sec" @@ -59,7 +62,7 @@ func init() { newCmdObjectCertificatePKCS(kind), ) cmdObjectConfig.AddCommand( - commoncmd.NewCmdObjectConfigDoc(kind), + omcmd.NewCmdObjectConfigDoc(kind), newCmdObjectConfigEdit(kind), newCmdObjectConfigEval(kind), newCmdObjectConfigGet(kind), diff --git a/core/om/kind_svc.go b/core/om/kind_svc.go index 0254097d5..68c5f266c 100644 --- a/core/om/kind_svc.go +++ b/core/om/kind_svc.go @@ -2,6 +2,7 @@ package om import ( "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/omcmd" "github.com/opensvc/om3/v3/util/hostname" ) @@ -87,7 +88,7 @@ func init() { newCmdObjectUnset(kind), ) cmdObjectConfig.AddCommand( - commoncmd.NewCmdObjectConfigDoc(kind), + omcmd.NewCmdObjectConfigDoc(kind), newCmdObjectConfigEdit(kind), newCmdObjectConfigEval(kind), newCmdObjectConfigGet(kind), diff --git a/core/om/kind_usr.go b/core/om/kind_usr.go index 26ab2ff18..6049d13d5 100644 --- a/core/om/kind_usr.go +++ b/core/om/kind_usr.go @@ -1,6 +1,9 @@ package om -import "github.com/opensvc/om3/v3/core/commoncmd" +import ( + "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/omcmd" +) func init() { kind := "usr" @@ -57,7 +60,7 @@ func init() { newCmdObjectCertificatePKCS(kind), ) cmdObjectConfig.AddCommand( - commoncmd.NewCmdObjectConfigDoc(kind), + omcmd.NewCmdObjectConfigDoc(kind), newCmdObjectConfigEdit(kind), newCmdObjectConfigEval(kind), newCmdObjectConfigGet(kind), diff --git a/core/om/kind_vol.go b/core/om/kind_vol.go index afb7624ad..77aa81a88 100644 --- a/core/om/kind_vol.go +++ b/core/om/kind_vol.go @@ -2,6 +2,7 @@ package om import ( "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/omcmd" "github.com/opensvc/om3/v3/util/hostname" ) @@ -88,7 +89,7 @@ func init() { newCmdObjectCollectorTagShow(kind), ) cmdObjectConfig.AddCommand( - commoncmd.NewCmdObjectConfigDoc(kind), + omcmd.NewCmdObjectConfigDoc(kind), newCmdObjectConfigEdit(kind), newCmdObjectConfigEval(kind), newCmdObjectConfigGet(kind), diff --git a/core/om/node.go b/core/om/node.go index ce2596efe..767168000 100644 --- a/core/om/node.go +++ b/core/om/node.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/omcmd" ) var ( @@ -173,7 +174,7 @@ func init() { newCmdNodeVersion(), ) cmdNodeConfig.AddCommand( - commoncmd.NewCmdNodeConfigDoc(), + omcmd.NewCmdNodeConfigDoc(), newCmdNodeConfigEdit(), newCmdNodeConfigEval(), newCmdNodeConfigGet(), diff --git a/core/omcmd/node_config_doc.go b/core/omcmd/node_config_doc.go new file mode 100644 index 000000000..d5082c900 --- /dev/null +++ b/core/omcmd/node_config_doc.go @@ -0,0 +1,83 @@ +package omcmd + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/doc" + "github.com/opensvc/om3/v3/core/keywords" + "github.com/opensvc/om3/v3/core/naming" + "github.com/opensvc/om3/v3/core/object" + "github.com/opensvc/om3/v3/core/output" + "github.com/opensvc/om3/v3/core/rawconfig" +) + +type ( + CmdNodeConfigDoc struct { + Color string + Output string + Keyword string + Driver string + Depth int + } +) + +func NewCmdNodeConfigDoc() *cobra.Command { + var options CmdNodeConfigDoc + cmd := &cobra.Command{ + Use: "doc", + Short: "print the keyword documentation", + RunE: func(cmd *cobra.Command, args []string) error { + return options.Run() + }, + } + flags := cmd.Flags() + commoncmd.FlagColor(flags, &options.Color) + commoncmd.FlagOutput(flags, &options.Output) + commoncmd.FlagKeyword(flags, &options.Keyword) + commoncmd.FlagDriver(flags, &options.Driver) + commoncmd.FlagDepth(flags, &options.Depth) + return cmd +} + +func (t *CmdNodeConfigDoc) Run() error { + var path naming.Path + var driver, section, option *string + if t.Driver != "" { + driver = &t.Driver + } + if t.Keyword != "" { + index := keywords.ParseIndex(t.Keyword) + section = &index[0] + option = &index[1] + } + store := object.NodeKeywordStore + store, err := doc.FilterKeywordStore(store, driver, section, option, path, func() (doc.ConfigProvider, error) { + var ( + i any + err error + ) + i, err = object.NewNode(object.WithVolatile(true)) + if err != nil { + return nil, err + } + return i.(doc.ConfigProvider), nil + }) + if err != nil { + return err + } + items := doc.ConvertKeywordStore(store) + output.Renderer{ + HumanRenderer: func() string { + commoncmd.Doc(os.Stdout, items, path.Kind, t.Driver, t.Keyword, t.Depth) + return "" + }, + Output: t.Output, + Color: t.Color, + Data: items, + Colorize: rawconfig.Colorize, + }.Print() + return nil +} diff --git a/core/omcmd/object_config_doc.go b/core/omcmd/object_config_doc.go new file mode 100644 index 000000000..203d086b9 --- /dev/null +++ b/core/omcmd/object_config_doc.go @@ -0,0 +1,88 @@ +package omcmd + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/doc" + "github.com/opensvc/om3/v3/core/keywords" + "github.com/opensvc/om3/v3/core/naming" + "github.com/opensvc/om3/v3/core/object" + "github.com/opensvc/om3/v3/core/output" + "github.com/opensvc/om3/v3/core/rawconfig" +) + +type ( + CmdObjectConfigDoc struct { + OptsGlobal + Color string + Output string + Keyword string + Driver string + Depth int + } +) + +func NewCmdObjectConfigDoc(kind string) *cobra.Command { + var options CmdObjectConfigDoc + cmd := &cobra.Command{ + Use: "doc", + Short: "print the keyword documentation", + RunE: func(cmd *cobra.Command, args []string) error { + return options.Run(kind) + }, + } + flags := cmd.Flags() + commoncmd.FlagObjectSelector(flags, &options.ObjectSelector) + commoncmd.FlagColor(flags, &options.Color) + commoncmd.FlagOutput(flags, &options.Output) + commoncmd.FlagKeyword(flags, &options.Keyword) + commoncmd.FlagDriver(flags, &options.Driver) + commoncmd.FlagDepth(flags, &options.Depth) + return cmd +} + +func (t *CmdObjectConfigDoc) Run(kind string) error { + path, err := naming.ParsePath(t.ObjectSelector) + if err != nil { + path, _ = naming.ParsePath("ns1/" + kind + "/obj1") + } + var driver, section, option *string + if t.Driver != "" { + driver = &t.Driver + } + if t.Keyword != "" { + index := keywords.ParseIndex(t.Keyword) + section = &index[0] + option = &index[1] + } + store := object.KeywordStoreWithDrivers(path.Kind) + store, err = doc.FilterKeywordStore(store, driver, section, option, path, func() (doc.ConfigProvider, error) { + var ( + i any + err error + ) + i, err = object.NewConfigurer(path, object.WithVolatile(true)) + if err != nil { + return nil, err + } + return i.(doc.ConfigProvider), nil + }) + if err != nil { + return err + } + items := doc.ConvertKeywordStore(store) + output.Renderer{ + HumanRenderer: func() string { + commoncmd.Doc(os.Stdout, items, path.Kind, t.Driver, t.Keyword, t.Depth) + return "" + }, + Output: t.Output, + Color: t.Color, + Data: items, + Colorize: rawconfig.Colorize, + }.Print() + return nil +} diff --git a/daemon/daemonapi/get_cluster_config_keywords.go b/daemon/daemonapi/get_cluster_config_keywords.go index 68e039d19..edae8cf44 100644 --- a/daemon/daemonapi/get_cluster_config_keywords.go +++ b/daemon/daemonapi/get_cluster_config_keywords.go @@ -1,22 +1,21 @@ package daemonapi import ( + "errors" "net/http" "github.com/labstack/echo/v4" + "github.com/opensvc/om3/v3/core/doc" "github.com/opensvc/om3/v3/core/naming" "github.com/opensvc/om3/v3/core/object" "github.com/opensvc/om3/v3/daemon/api" ) func (a *DaemonAPI) GetClusterConfigKeywords(ctx echo.Context, params api.GetClusterConfigKeywordsParams) error { - var ( - err error - status int - ) + var err error store := object.KeywordStoreWithDrivers(naming.KindCcfg) - store, status, err = filterKeywordStore(ctx, store, params.Driver, params.Section, params.Option, naming.Cluster, func() (configProvider, error) { + store, err = doc.FilterKeywordStore(store, params.Driver, params.Section, params.Option, naming.Cluster, func() (doc.ConfigProvider, error) { var ( i any err error @@ -25,14 +24,18 @@ func (a *DaemonAPI) GetClusterConfigKeywords(ctx echo.Context, params api.GetClu if err != nil { return nil, err } - return i.(configProvider), nil + return i.(doc.ConfigProvider), nil }) - if err != nil { + if errors.Is(err, doc.ErrBadRequest) { + status := http.StatusBadRequest + return JSONProblemf(ctx, status, http.StatusText(status), "%s", err) + } else if err != nil { + status := http.StatusInternalServerError return JSONProblemf(ctx, status, http.StatusText(status), "%s", err) } r := api.KeywordDefinitionList{ Kind: "KeywordDefinitionList", - Items: convertKeywordStore(store), + Items: doc.ConvertKeywordStore(store), } return ctx.JSON(http.StatusOK, r) } diff --git a/daemon/daemonapi/get_node_config_keywords.go b/daemon/daemonapi/get_node_config_keywords.go index 9f3eb2264..ff82aa13e 100644 --- a/daemon/daemonapi/get_node_config_keywords.go +++ b/daemon/daemonapi/get_node_config_keywords.go @@ -1,23 +1,22 @@ package daemonapi import ( + "errors" "net/http" "github.com/labstack/echo/v4" + "github.com/opensvc/om3/v3/core/doc" "github.com/opensvc/om3/v3/core/naming" "github.com/opensvc/om3/v3/core/object" "github.com/opensvc/om3/v3/daemon/api" ) func (a *DaemonAPI) GetNodeConfigKeywords(ctx echo.Context, nodename string, params api.GetNodeConfigKeywordsParams) error { - var ( - err error - status int - ) + var err error store := object.NodeKeywordStore path := naming.Path{} - store, status, err = filterKeywordStore(ctx, store, params.Driver, params.Section, params.Option, path, func() (configProvider, error) { + store, err = doc.FilterKeywordStore(store, params.Driver, params.Section, params.Option, path, func() (doc.ConfigProvider, error) { var ( err error i any @@ -26,14 +25,18 @@ func (a *DaemonAPI) GetNodeConfigKeywords(ctx echo.Context, nodename string, par if err != nil { return nil, err } - return i.(configProvider), nil + return i.(doc.ConfigProvider), nil }) - if err != nil { + if errors.Is(err, doc.ErrBadRequest) { + status := http.StatusBadRequest + return JSONProblemf(ctx, status, http.StatusText(status), "%s", err) + } else if err != nil { + status := http.StatusInternalServerError return JSONProblemf(ctx, status, http.StatusText(status), "%s", err) } r := api.KeywordDefinitionList{ Kind: "KeywordDefinitionList", - Items: convertKeywordStore(store), + Items: doc.ConvertKeywordStore(store), } return ctx.JSON(http.StatusOK, r) } diff --git a/daemon/daemonapi/get_object_config_keywords.go b/daemon/daemonapi/get_object_config_keywords.go index d638bf6be..68aa9759e 100644 --- a/daemon/daemonapi/get_object_config_keywords.go +++ b/daemon/daemonapi/get_object_config_keywords.go @@ -1,10 +1,12 @@ package daemonapi import ( + "errors" "net/http" "github.com/labstack/echo/v4" + "github.com/opensvc/om3/v3/core/doc" "github.com/opensvc/om3/v3/core/naming" "github.com/opensvc/om3/v3/core/object" "github.com/opensvc/om3/v3/core/xconfig" @@ -28,7 +30,7 @@ func (a *DaemonAPI) GetObjectConfigKeywords(ctx echo.Context, namespace string, Namespace: namespace, Kind: kind, } - store, status, err = filterKeywordStore(ctx, store, params.Driver, params.Section, params.Option, path, func() (configProvider, error) { + store, err = doc.FilterKeywordStore(store, params.Driver, params.Section, params.Option, path, func() (doc.ConfigProvider, error) { var ( i any err error @@ -37,14 +39,18 @@ func (a *DaemonAPI) GetObjectConfigKeywords(ctx echo.Context, namespace string, if err != nil { return nil, err } - return i.(configProvider), nil + return i.(doc.ConfigProvider), nil }) - if err != nil { + if errors.Is(err, doc.ErrBadRequest) { + status = http.StatusBadRequest + return JSONProblemf(ctx, status, http.StatusText(status), "%s", err) + } else if err != nil { + status := http.StatusInternalServerError return JSONProblemf(ctx, status, http.StatusText(status), "%s", err) } r := api.KeywordDefinitionList{ Kind: "KeywordDefinitionList", - Items: convertKeywordStore(store), + Items: doc.ConvertKeywordStore(store), } return ctx.JSON(http.StatusOK, r) }