diff --git a/admin/Makefile b/admin/Makefile index 62cd7d0553..29a52a64c7 100644 --- a/admin/Makefile +++ b/admin/Makefile @@ -25,13 +25,15 @@ LD_FLAGS = -ldflags " \ " release: ## Build pmm-admin release binary. - env CGO_ENABLED=0 go build -v $(LD_FLAGS) -o $(PMM_RELEASE_PATH)/pmm-admin + env CGO_ENABLED=0 go build -v $(LD_FLAGS) -o $(PMM_RELEASE_PATH)/pmm-admin ./cmd/pmm-admin/ -install: ## Install pmm-admin binary. - go build -v $(LD_FLAGS) -o $(GOBIN)/pmm-admin +install: ## Install pmm & pmm-admin binary. + go build -v $(LD_FLAGS) -o $(GOBIN)/pmm-admin ./cmd/pmm-admin/ + go build -v $(LD_FLAGS) -o $(GOBIN)/pmm ./cmd/pmm/ -install-race: ## Install pmm-admin binary with race detector. - go build -v $(LD_FLAGS) -race -o $(GOBIN)/pmm-admin +install-race: ## Install pmm & pmm-admin binary with race detector. + go build -v $(LD_FLAGS) -race -o $(GOBIN)/pmm-admin ./cmd/pmm-admin/ + go build -v $(LD_FLAGS) -race -o $(GOBIN)/pmm ./cmd/pmm/ TEST_FLAGS ?= -timeout=20s diff --git a/admin/cli/cli.go b/admin/cli/cli.go index f6cc7ca465..18a5171e03 100644 --- a/admin/cli/cli.go +++ b/admin/cli/cli.go @@ -29,10 +29,22 @@ import ( "github.com/percona/pmm/admin/commands" "github.com/percona/pmm/admin/commands/inventory" "github.com/percona/pmm/admin/commands/management" + "github.com/percona/pmm/admin/commands/pmm/server" ) -// Commands stores all commands, flags and arguments. -type Commands struct { +// GlobalFlagsGetter supports retrieving GlobalFlags. +type GlobalFlagsGetter interface { + GetGlobalFlags() *flags.GlobalFlags +} + +// Check interfaces. +var ( + _ GlobalFlagsGetter = &PMMAdminCommands{} //nolint:exhaustruct + _ GlobalFlagsGetter = &PMMCommands{} //nolint:exhaustruct +) + +// PMMAdminCommands stores all commands, flags and arguments for the "pmm-admin" binary. +type PMMAdminCommands struct { flags.GlobalFlags Status commands.StatusCommand `cmd:"" help:"Show information about local pmm-agent"` @@ -48,6 +60,33 @@ type Commands struct { Version commands.VersionCommand `cmd:"" help:"Print version"` } +// Run function is a top-level function which handles running all commands +// in a standard way based on the interface they implement. +func (c *PMMAdminCommands) Run(ctx *kong.Context, globals *flags.GlobalFlags) error { + return run(ctx, globals) +} + +func (c *PMMAdminCommands) GetGlobalFlags() *flags.GlobalFlags { + return &c.GlobalFlags +} + +// PMMCommands stores all commands, flags and arguments for the "pmm" binary. +type PMMCommands struct { + flags.GlobalFlags + + Server server.BaseCommand `cmd:"" help:"PMM server related commands"` +} + +func (c *PMMCommands) GetGlobalFlags() *flags.GlobalFlags { + return &c.GlobalFlags +} + +// Run function is a top-level function which handles running all commands +// in a standard way based on the interface they implement. +func (c *PMMCommands) Run(ctx *kong.Context, globals *flags.GlobalFlags) error { + return run(ctx, globals) +} + // CmdRunner represents a command to be run without arguments. type CmdRunner interface { RunCmd() (commands.Result, error) @@ -63,9 +102,7 @@ type CmdWithContextRunner interface { RunCmdWithContext(context.Context, *flags.GlobalFlags) (commands.Result, error) } -// Run function is a top-level function which handles running all commands -// in a standard way based on the interface they implement. -func (c *Commands) Run(ctx *kong.Context, globals *flags.GlobalFlags) error { +func run(ctx *kong.Context, globals *flags.GlobalFlags) error { var res commands.Result var err error @@ -82,7 +119,7 @@ func (c *Commands) Run(ctx *kong.Context, globals *flags.GlobalFlags) error { panic("The command does not implement RunCmd()") } - return printResponse(&c.GlobalFlags, res, err) + return printResponse(globals, res, err) } func printResponse(opts *flags.GlobalFlags, res commands.Result, err error) error { diff --git a/admin/main.go b/admin/cmd/bootstrap.go similarity index 74% rename from admin/main.go rename to admin/cmd/bootstrap.go index 1a60d9537c..349660abc7 100644 --- a/admin/main.go +++ b/admin/cmd/bootstrap.go @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -package main +// Package cmd holds common logic used by commands +package cmd import ( "context" @@ -28,6 +29,7 @@ import ( "github.com/percona/pmm/admin/agentlocal" "github.com/percona/pmm/admin/cli" + "github.com/percona/pmm/admin/cli/flags" "github.com/percona/pmm/admin/commands" "github.com/percona/pmm/admin/commands/base" "github.com/percona/pmm/admin/commands/management" @@ -36,7 +38,49 @@ import ( "github.com/percona/pmm/version" ) -func main() { +// Bootstrap is used to initialize the application. +func Bootstrap(opts any) { + var kongCtx *kong.Context + var parsedOpts any + + switch o := opts.(type) { + case cli.PMMAdminCommands: + kongCtx = kong.Parse(&o, getDefaultKongOptions("pmm-admin")...) + parsedOpts = &o + case cli.PMMCommands: + kongCtx = kong.Parse(&o, getDefaultKongOptions("pmm")...) + parsedOpts = &o + } + + f, ok := parsedOpts.(cli.GlobalFlagsGetter) + if !ok { + logrus.Panic("Cannot assert parsedOpts to GlobalFlagsGetter") + } + + globalFlags := f.GetGlobalFlags() + + configureLogger(globalFlags) + finishBootstrap(globalFlags) + + err := kongCtx.Run(globalFlags) + processFinalError(err, bool(globalFlags.JSON)) +} + +func configureLogger(opts *flags.GlobalFlags) { + logrus.SetFormatter(&logger.TextFormatter{}) // with levels and timestamps for debug and trace + if opts.JSON { + logrus.SetFormatter(&logrus.JSONFormatter{}) //nolint:exhaustruct // with levels and timestamps always present + } + if opts.EnableDebug { + logrus.SetLevel(logrus.DebugLevel) + } + if opts.EnableTrace { + logrus.SetLevel(logrus.TraceLevel) + logrus.SetReportCaller(true) // https://github.com/sirupsen/logrus/issues/954 + } +} + +func getDefaultKongOptions(appName string) []kong.Option { // Detect defaults nodeinfo := nodeinfo.Get() nodeTypeDefault := "generic" @@ -62,10 +106,8 @@ func main() { management.MongodbQuerySourceNone, } - // Configure CLI - var opts cli.Commands - kongCtx := kong.Parse(&opts, - kong.Name("pmm-admin"), + return []kong.Option{ + kong.Name(appName), kong.Description(fmt.Sprintf("Version %s", version.Version)), kong.UsageOnError(), kong.ConfigureHelp(kong.HelpOptions{ @@ -87,20 +129,11 @@ func main() { "mongoDbQuerySourceDefault": mongoDBQuerySources[0], "externalDefaultServiceName": management.DefaultServiceNameSuffix, "externalDefaultGroupExporter": management.DefaultGroupExternalExporter, - }) - - logrus.SetFormatter(&logger.TextFormatter{}) // with levels and timestamps for debug and trace - if opts.JSON { - logrus.SetFormatter(&logrus.JSONFormatter{}) // with levels and timestamps always present - } - if opts.EnableDebug { - logrus.SetLevel(logrus.DebugLevel) - } - if opts.EnableTrace { - logrus.SetLevel(logrus.TraceLevel) - logrus.SetReportCaller(true) // https://github.com/sirupsen/logrus/issues/954 + }, } +} +func finishBootstrap(globalFlags *flags.GlobalFlags) { ctx, cancel := context.WithCancel(context.Background()) // handle termination signals @@ -113,18 +146,19 @@ func main() { cancel() }() - agentlocal.SetTransport(ctx, opts.EnableDebug || opts.EnableTrace, opts.PMMAgentListenPort) + agentlocal.SetTransport(ctx, globalFlags.EnableDebug || globalFlags.EnableTrace, globalFlags.PMMAgentListenPort) // pmm-admin status command don't connect to PMM Server. if commands.SetupClientsEnabled { - base.SetupClients(ctx, &opts.GlobalFlags) + base.SetupClients(ctx, globalFlags) } commands.CLICtx = ctx +} - err := kongCtx.Run(&opts.GlobalFlags) +func processFinalError(err error, isJSON bool) { if err != nil { - if opts.JSON { + if isJSON { b, jErr := json.Marshal(err.Error()) if jErr != nil { logrus.Infof("Error: %#v.", err) diff --git a/admin/cmd/pmm-admin/main.go b/admin/cmd/pmm-admin/main.go new file mode 100644 index 0000000000..d2746fdb9a --- /dev/null +++ b/admin/cmd/pmm-admin/main.go @@ -0,0 +1,24 @@ +// Copyright 2019 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "github.com/percona/pmm/admin/cli" + "github.com/percona/pmm/admin/cmd" +) + +func main() { + cmd.Bootstrap(cli.PMMAdminCommands{}) //nolint:exhaustruct +} diff --git a/admin/main_test.go b/admin/cmd/pmm-admin/main_test.go similarity index 100% rename from admin/main_test.go rename to admin/cmd/pmm-admin/main_test.go diff --git a/admin/cmd/pmm/main.go b/admin/cmd/pmm/main.go new file mode 100644 index 0000000000..45b6697355 --- /dev/null +++ b/admin/cmd/pmm/main.go @@ -0,0 +1,24 @@ +// Copyright 2019 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "github.com/percona/pmm/admin/cli" + "github.com/percona/pmm/admin/cmd" +) + +func main() { + cmd.Bootstrap(cli.PMMCommands{}) //nolint:exhaustruct +} diff --git a/admin/commands/pmm/server/base.go b/admin/commands/pmm/server/base.go new file mode 100644 index 0000000000..28e6dbc8be --- /dev/null +++ b/admin/commands/pmm/server/base.go @@ -0,0 +1,29 @@ +// Copyright 2019 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package server holds the "pmm server" command +package server + +import "github.com/percona/pmm/admin/commands" + +// BaseCommand is used by Kong for CLI flags and commands and holds all server commands. +type BaseCommand struct { + Install InstallCommand `cmd:"" help:"Install PMM server"` +} + +// BeforeApply is run before the command is applied. +func (cmd *BaseCommand) BeforeApply() error { + commands.SetupClientsEnabled = false + return nil +} diff --git a/admin/commands/pmm/server/install.go b/admin/commands/pmm/server/install.go new file mode 100644 index 0000000000..eca91e5de0 --- /dev/null +++ b/admin/commands/pmm/server/install.go @@ -0,0 +1,35 @@ +// Copyright 2019 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import "github.com/percona/pmm/admin/commands" + +// InstallCommand is used by Kong for CLI flags and commands. +type InstallCommand struct{} + +type installResult struct{} + +// Result is a command run result. +func (res *installResult) Result() {} + +// String stringifies command result. +func (res *installResult) String() string { + return "works" +} + +// RunCmd runs install command. +func (c *InstallCommand) RunCmd() (commands.Result, error) { + return &installResult{}, nil +}