Skip to content

Commit 8640b81

Browse files
[breaking] Split daemon mode configs from core configs (#1622)
* Split daemon mode configs from core configs * Enhanced documentation Co-authored-by: per1234 <[email protected]> * Apply suggestions from code review Co-authored-by: per1234 <[email protected]> Co-authored-by: per1234 <[email protected]>
1 parent edc63f8 commit 8640b81

16 files changed

+554
-231
lines changed

cli/cli.go

+31-109
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package cli
1717

1818
import (
1919
"fmt"
20-
"io/ioutil"
2120
"os"
2221
"strings"
2322

@@ -37,7 +36,6 @@ import (
3736
"github.com/arduino/arduino-cli/cli/lib"
3837
"github.com/arduino/arduino-cli/cli/monitor"
3938
"github.com/arduino/arduino-cli/cli/outdated"
40-
"github.com/arduino/arduino-cli/cli/output"
4139
"github.com/arduino/arduino-cli/cli/sketch"
4240
"github.com/arduino/arduino-cli/cli/update"
4341
"github.com/arduino/arduino-cli/cli/updater"
@@ -47,17 +45,14 @@ import (
4745
"github.com/arduino/arduino-cli/configuration"
4846
"github.com/arduino/arduino-cli/i18n"
4947
"github.com/arduino/arduino-cli/inventory"
50-
"github.com/fatih/color"
51-
"github.com/mattn/go-colorable"
52-
"github.com/rifflock/lfshook"
48+
"github.com/arduino/arduino-cli/logging"
49+
"github.com/arduino/arduino-cli/output"
5350
"github.com/sirupsen/logrus"
5451
"github.com/spf13/cobra"
5552
semver "go.bug.st/relaxed-semver"
5653
)
5754

5855
var (
59-
verbose bool
60-
outputFormat string
6156
configFile string
6257
updaterMessageChan chan *semver.Version = make(chan *semver.Version)
6358
)
@@ -104,57 +99,34 @@ func createCliCommandTree(cmd *cobra.Command) {
10499
cmd.AddCommand(burnbootloader.NewCommand())
105100
cmd.AddCommand(version.NewCommand())
106101

107-
cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, tr("Print the logs on the standard output."))
108102
validLogLevels := []string{"trace", "debug", "info", "warn", "error", "fatal", "panic"}
109-
cmd.PersistentFlags().String("log-level", "", tr("Messages with this level and above will be logged. Valid levels are: %s", strings.Join(validLogLevels, ", ")))
103+
validLogFormats := []string{"text", "json"}
104+
cmd.PersistentFlags().String("log-level", "info", tr("Messages with this level and above will be logged. Valid levels are: %s", strings.Join(validLogLevels, ", ")))
105+
cmd.PersistentFlags().String("log-file", "", tr("Path to the file where logs will be written."))
106+
cmd.PersistentFlags().String("log-format", "text", tr("The output format for the logs, can be: %s", strings.Join(validLogFormats, ", ")))
110107
cmd.RegisterFlagCompletionFunc("log-level", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
111108
return validLogLevels, cobra.ShellCompDirectiveDefault
112109
})
113-
cmd.PersistentFlags().String("log-file", "", tr("Path to the file where logs will be written."))
114-
validLogFormats := []string{"text", "json"}
115-
cmd.PersistentFlags().String("log-format", "", tr("The output format for the logs, can be: %s", strings.Join(validLogFormats, ", ")))
116110
cmd.RegisterFlagCompletionFunc("log-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
117111
return validLogFormats, cobra.ShellCompDirectiveDefault
118112
})
119113
validOutputFormats := []string{"text", "json", "jsonmini", "yaml"}
120-
cmd.PersistentFlags().StringVar(&outputFormat, "format", "text", tr("The output format for the logs, can be: %s", strings.Join(validOutputFormats, ", ")))
114+
cmd.PersistentFlags().BoolP("verbose", "v", false, tr("Print the logs on the standard output."))
115+
cmd.PersistentFlags().String("format", "text", tr("The output format for the logs, can be: %s", strings.Join(validOutputFormats, ", ")))
116+
cmd.PersistentFlags().Bool("no-color", false, "Disable colored output.")
121117
cmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
122118
return validOutputFormats, cobra.ShellCompDirectiveDefault
123119
})
124120
cmd.PersistentFlags().StringVar(&configFile, "config-file", "", tr("The custom config file (if not specified the default will be used)."))
125121
cmd.PersistentFlags().StringSlice("additional-urls", []string{}, tr("Comma-separated list of additional URLs for the Boards Manager."))
126-
cmd.PersistentFlags().Bool("no-color", false, "Disable colored output.")
127122
configuration.BindFlags(cmd, configuration.Settings)
128123
}
129124

130-
// convert the string passed to the `--log-level` option to the corresponding
131-
// logrus formal level.
132-
func toLogLevel(s string) (t logrus.Level, found bool) {
133-
t, found = map[string]logrus.Level{
134-
"trace": logrus.TraceLevel,
135-
"debug": logrus.DebugLevel,
136-
"info": logrus.InfoLevel,
137-
"warn": logrus.WarnLevel,
138-
"error": logrus.ErrorLevel,
139-
"fatal": logrus.FatalLevel,
140-
"panic": logrus.PanicLevel,
141-
}[s]
142-
143-
return
144-
}
145-
146-
func parseFormatString(arg string) (feedback.OutputFormat, bool) {
147-
f, found := map[string]feedback.OutputFormat{
148-
"json": feedback.JSON,
149-
"jsonmini": feedback.JSONMini,
150-
"text": feedback.Text,
151-
"yaml": feedback.YAML,
152-
}[strings.ToLower(arg)]
153-
154-
return f, found
155-
}
156-
157125
func preRun(cmd *cobra.Command, args []string) {
126+
if cmd.Name() == "daemon" {
127+
return
128+
}
129+
158130
configFile := configuration.Settings.ConfigFileUsed()
159131

160132
// initialize inventory
@@ -164,12 +136,13 @@ func preRun(cmd *cobra.Command, args []string) {
164136
os.Exit(errorcodes.ErrBadArgument)
165137
}
166138

167-
// https://no-color.org/
168-
color.NoColor = configuration.Settings.GetBool("output.no_color") || os.Getenv("NO_COLOR") != ""
169-
170-
// Set default feedback output to colorable
171-
feedback.SetOut(colorable.NewColorableStdout())
172-
feedback.SetErr(colorable.NewColorableStderr())
139+
outputFormat, err := cmd.Flags().GetString("format")
140+
if err != nil {
141+
feedback.Errorf(tr("Error getting flag value: %s", err))
142+
os.Exit(errorcodes.ErrBadCall)
143+
}
144+
noColor := configuration.Settings.GetBool("output.no_color") || os.Getenv("NO_COLOR") != ""
145+
output.Setup(outputFormat, noColor)
173146

174147
updaterMessageChan = make(chan *semver.Version)
175148
go func() {
@@ -185,70 +158,19 @@ func preRun(cmd *cobra.Command, args []string) {
185158
updaterMessageChan <- updater.CheckForUpdate(currentVersion)
186159
}()
187160

188-
//
189-
// Prepare logging
190-
//
191-
192-
// decide whether we should log to stdout
193-
if verbose {
194-
// if we print on stdout, do it in full colors
195-
logrus.SetOutput(colorable.NewColorableStdout())
196-
logrus.SetFormatter(&logrus.TextFormatter{
197-
ForceColors: true,
198-
DisableColors: color.NoColor,
199-
})
200-
} else {
201-
logrus.SetOutput(ioutil.Discard)
202-
}
203-
204-
// set the Logger format
205-
logFormat := strings.ToLower(configuration.Settings.GetString("logging.format"))
206-
if logFormat == "json" {
207-
logrus.SetFormatter(&logrus.JSONFormatter{})
208-
}
209-
210-
// should we log to file?
211-
logFile := configuration.Settings.GetString("logging.file")
212-
if logFile != "" {
213-
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
214-
if err != nil {
215-
fmt.Println(tr("Unable to open file for logging: %s", logFile))
216-
os.Exit(errorcodes.ErrBadCall)
217-
}
218-
219-
// we use a hook so we don't get color codes in the log file
220-
if logFormat == "json" {
221-
logrus.AddHook(lfshook.NewHook(file, &logrus.JSONFormatter{}))
222-
} else {
223-
logrus.AddHook(lfshook.NewHook(file, &logrus.TextFormatter{}))
224-
}
225-
}
226-
227-
// configure logging filter
228-
if lvl, found := toLogLevel(configuration.Settings.GetString("logging.level")); !found {
229-
feedback.Errorf(tr("Invalid option for --log-level: %s"), configuration.Settings.GetString("logging.level"))
230-
os.Exit(errorcodes.ErrBadArgument)
231-
} else {
232-
logrus.SetLevel(lvl)
233-
}
234-
235-
//
236-
// Prepare the Feedback system
237-
//
238-
239-
// normalize the format strings
240-
outputFormat = strings.ToLower(outputFormat)
241-
// configure the output package
242-
output.OutputFormat = outputFormat
243-
// check the right output format was passed
244-
format, found := parseFormatString(outputFormat)
245-
if !found {
246-
feedback.Errorf(tr("Invalid output format: %s"), outputFormat)
161+
// Setups logging if necessary
162+
verbose, err := cmd.Flags().GetBool("verbose")
163+
if err != nil {
164+
feedback.Errorf(tr("Error getting flag value: %s", err))
247165
os.Exit(errorcodes.ErrBadCall)
248166
}
249-
250-
// use the output format to configure the Feedback
251-
feedback.SetFormat(format)
167+
logging.Setup(
168+
verbose,
169+
noColor,
170+
configuration.Settings.GetString("logging.level"),
171+
configuration.Settings.GetString("logging.file"),
172+
configuration.Settings.GetString("logging.format"),
173+
)
252174

253175
//
254176
// Print some status info and check command is consistent

cli/config/validate.go

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626

2727
var validMap = map[string]reflect.Kind{
2828
"board_manager.additional_urls": reflect.Slice,
29-
"daemon.port": reflect.String,
3029
"directories.data": reflect.String,
3130
"directories.downloads": reflect.String,
3231
"directories.user": reflect.String,

cli/daemon/daemon.go

+59-34
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import (
3131
"github.com/arduino/arduino-cli/commands/daemon"
3232
"github.com/arduino/arduino-cli/configuration"
3333
"github.com/arduino/arduino-cli/i18n"
34-
"github.com/arduino/arduino-cli/metrics"
34+
"github.com/arduino/arduino-cli/logging"
35+
"github.com/arduino/arduino-cli/output"
3536
srv_commands "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
3637
srv_debug "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/debug/v1"
3738
srv_monitor "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/monitor/v1"
@@ -44,64 +45,89 @@ import (
4445

4546
var (
4647
tr = i18n.Tr
47-
daemonize bool
48-
debug bool
4948
debugFilters []string
49+
50+
daemonConfigFile string
5051
)
5152

5253
// NewCommand created a new `daemon` command
5354
func NewCommand() *cobra.Command {
5455
daemonCommand := &cobra.Command{
5556
Use: "daemon",
56-
Short: tr("Run as a daemon on port: %s", configuration.Settings.GetString("daemon.port")),
57-
Long: tr("Running as a daemon the initialization of cores and libraries is done only once."),
57+
Short: tr("Run as a daemon on specified IP and port"),
58+
Long: tr("Running as a daemon multiple different client can use the same Arduino CLI process with different settings."),
5859
Example: " " + os.Args[0] + " daemon",
5960
Args: cobra.NoArgs,
6061
Run: runDaemonCommand,
6162
}
62-
daemonCommand.PersistentFlags().String("port", "", tr("The TCP port the daemon will listen to"))
63-
configuration.Settings.BindPFlag("daemon.port", daemonCommand.PersistentFlags().Lookup("port"))
64-
daemonCommand.Flags().BoolVar(&daemonize, "daemonize", false, tr("Do not terminate daemon process if the parent process dies"))
65-
daemonCommand.Flags().BoolVar(&debug, "debug", false, tr("Enable debug logging of gRPC calls"))
66-
daemonCommand.Flags().StringSliceVar(&debugFilters, "debug-filter", []string{}, tr("Display only the provided gRPC calls"))
63+
daemonCommand.Flags().String("ip", "127.0.0.1", tr("The IP the daemon will listen to"))
64+
daemonCommand.Flags().String("port", "50051", tr("The TCP port the daemon will listen to"))
65+
daemonCommand.Flags().Bool("daemonize", false, tr("Run daemon process in background"))
66+
daemonCommand.Flags().Bool("debug", false, tr("Enable debug logging of gRPC calls"))
67+
daemonCommand.Flags().StringSlice("debug-filter", []string{}, tr("Display only the provided gRPC calls when debug is enabled"))
68+
daemonCommand.Flags().Bool("metrics-enabled", false, tr("Enable local metrics collection"))
69+
daemonCommand.Flags().String("metrics-address", ":9090", tr("Metrics local address"))
70+
// Metrics for the time being are ignored and unused, might as well hide this setting
71+
// from the user since they would do nothing.
72+
daemonCommand.Flags().MarkHidden("metrics-enabled")
73+
daemonCommand.Flags().MarkHidden("metrics-address")
74+
75+
daemonCommand.Flags().StringVar(&daemonConfigFile, "config-file", "", tr("The daemon config file (if not specified default values will be used)."))
6776
return daemonCommand
6877
}
6978

7079
func runDaemonCommand(cmd *cobra.Command, args []string) {
71-
logrus.Info("Executing `arduino-cli daemon`")
80+
s, err := load(cmd, daemonConfigFile)
81+
if err != nil {
82+
feedback.Errorf(tr("Error reading daemon config file: %v"), err)
83+
os.Exit(errorcodes.ErrGeneric)
84+
}
7285

73-
if configuration.Settings.GetBool("metrics.enabled") {
74-
metrics.Activate("daemon")
75-
stats.Incr("daemon", stats.T("success", "true"))
76-
defer stats.Flush()
86+
noColor := s.NoColor || os.Getenv("NO_COLOR") != ""
87+
output.Setup(s.OutputFormat, noColor)
88+
89+
if daemonConfigFile != "" {
90+
// Tell the user which config file we're using only after output setup
91+
feedback.Printf(tr("Using daemon config file %s", daemonConfigFile))
7792
}
78-
port := configuration.Settings.GetString("daemon.port")
93+
94+
logging.Setup(
95+
s.Verbose,
96+
noColor,
97+
s.LogLevel,
98+
s.LogFile,
99+
s.LogFormat,
100+
)
101+
102+
logrus.Info("Executing `arduino-cli daemon`")
103+
79104
gRPCOptions := []grpc.ServerOption{}
80-
if debug {
105+
if s.Debug {
106+
debugFilters = s.DebugFilter
81107
gRPCOptions = append(gRPCOptions,
82108
grpc.UnaryInterceptor(unaryLoggerInterceptor),
83109
grpc.StreamInterceptor(streamLoggerInterceptor),
84110
)
85111
}
86-
s := grpc.NewServer(gRPCOptions...)
112+
server := grpc.NewServer(gRPCOptions...)
87113
// Set specific user-agent for the daemon
88114
configuration.Settings.Set("network.user_agent_ext", "daemon")
89115

90116
// register the commands service
91-
srv_commands.RegisterArduinoCoreServiceServer(s, &daemon.ArduinoCoreServerImpl{
117+
srv_commands.RegisterArduinoCoreServiceServer(server, &daemon.ArduinoCoreServerImpl{
92118
VersionString: globals.VersionInfo.VersionString,
93119
})
94120

95121
// Register the monitors service
96-
srv_monitor.RegisterMonitorServiceServer(s, &daemon.MonitorService{})
122+
srv_monitor.RegisterMonitorServiceServer(server, &daemon.MonitorService{})
97123

98124
// Register the settings service
99-
srv_settings.RegisterSettingsServiceServer(s, &daemon.SettingsService{})
125+
srv_settings.RegisterSettingsServiceServer(server, &daemon.SettingsService{})
100126

101127
// Register the debug session service
102-
srv_debug.RegisterDebugServiceServer(s, &daemon.DebugService{})
128+
srv_debug.RegisterDebugServiceServer(server, &daemon.DebugService{})
103129

104-
if !daemonize {
130+
if !s.Daemonize {
105131
// When parent process ends terminate also the daemon
106132
go func() {
107133
// Stdin is closed when the controlling parent process ends
@@ -112,51 +138,50 @@ func runDaemonCommand(cmd *cobra.Command, args []string) {
112138
}()
113139
}
114140

115-
ip := "127.0.0.1"
116-
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", ip, port))
141+
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", s.IP, s.Port))
117142
if err != nil {
118143
// Invalid port, such as "Foo"
119144
var dnsError *net.DNSError
120145
if errors.As(err, &dnsError) {
121-
feedback.Errorf(tr("Failed to listen on TCP port: %[1]s. %[2]s is unknown name."), port, dnsError.Name)
146+
feedback.Errorf(tr("Failed to listen on TCP port: %[1]s. %[2]s is unknown name."), s.Port, dnsError.Name)
122147
os.Exit(errorcodes.ErrCoreConfig)
123148
}
124149
// Invalid port number, such as -1
125150
var addrError *net.AddrError
126151
if errors.As(err, &addrError) {
127-
feedback.Errorf(tr("Failed to listen on TCP port: %[1]s. %[2]s is an invalid port."), port, addrError.Addr)
152+
feedback.Errorf(tr("Failed to listen on TCP port: %[1]s. %[2]s is an invalid port."), s.Port, addrError.Addr)
128153
os.Exit(errorcodes.ErrCoreConfig)
129154
}
130155
// Port is already in use
131156
var syscallErr *os.SyscallError
132157
if errors.As(err, &syscallErr) && errors.Is(syscallErr.Err, syscall.EADDRINUSE) {
133-
feedback.Errorf(tr("Failed to listen on TCP port: %s. Address already in use."), port)
158+
feedback.Errorf(tr("Failed to listen on TCP port: %s. Address already in use."), s.Port)
134159
os.Exit(errorcodes.ErrNetwork)
135160
}
136-
feedback.Errorf(tr("Failed to listen on TCP port: %[1]s. Unexpected error: %[2]v"), port, err)
161+
feedback.Errorf(tr("Failed to listen on TCP port: %[1]s. Unexpected error: %[2]v"), s.Port, err)
137162
os.Exit(errorcodes.ErrGeneric)
138163
}
139164

140165
// We need to parse the port used only if the user let
141166
// us choose it randomly, in all other cases we already
142167
// know which is used.
143-
if port == "0" {
168+
if s.Port == "0" {
144169
address := lis.Addr()
145170
split := strings.Split(address.String(), ":")
146171

147172
if len(split) == 0 {
148173
feedback.Error(tr("Failed choosing port, address: %s", address))
149174
}
150175

151-
port = split[len(split)-1]
176+
s.Port = split[len(split)-1]
152177
}
153178

154179
feedback.PrintResult(daemonResult{
155-
IP: ip,
156-
Port: port,
180+
IP: s.IP,
181+
Port: s.Port,
157182
})
158183

159-
if err := s.Serve(lis); err != nil {
184+
if err := server.Serve(lis); err != nil {
160185
logrus.Fatalf("Failed to serve: %v", err)
161186
}
162187
}

0 commit comments

Comments
 (0)