Skip to content

Commit

Permalink
Merge pull request #325 from atc0005/i324-rework-config-handling-to-u…
Browse files Browse the repository at this point in the history
…se-app-types

Refactor config logic to support app types
  • Loading branch information
atc0005 authored Nov 18, 2022
2 parents 9127ceb + 069b1a3 commit 0c5b393
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 130 deletions.
9 changes: 2 additions & 7 deletions cmd/check_imap_mailbox/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,8 @@ func main() {
// defer this from the start so it is the last deferred function to run
defer nagiosExitState.ReturnCheckResults()

// Setup configuration by parsing user-provided flags. This plugin does
// not currently support retrieving settings from a user-provided config
// file. Because this may change in the near future, we are structuring
// this plugin in a way to support that direction.
useConfigFile := false
useLogFile := false
cfg, cfgErr := config.New(useConfigFile, useLogFile)
// Setup configuration by parsing user-provided flags.
cfg, cfgErr := config.New(config.AppType{PluginIMAPMailboxBasicAuth: true})
switch {
case errors.Is(cfgErr, config.ErrVersionRequested):
fmt.Println(config.Version())
Expand Down
4 changes: 1 addition & 3 deletions cmd/list-emails/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ func main() {
}(&appExitStatus)

// Setup configuration by parsing user-provided flags
useConfigFile := true
useLogFile := true
cfg, cfgErr := config.New(useConfigFile, useLogFile)
cfg, cfgErr := config.New(config.AppType{ReporterIMAPMailboxBasicAuth: true})
switch {
case errors.Is(cfgErr, config.ErrVersionRequested):
fmt.Println(config.Version())
Expand Down
144 changes: 40 additions & 104 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,41 @@ var Usage = func() {
flag.PrintDefaults()
}

// ErrVersionRequested indicates that the user requested application version
// information
var ErrVersionRequested = errors.New("version information requested")
var (
// ErrVersionRequested indicates that the user requested application
// version information
ErrVersionRequested = errors.New("version information requested")

// ErrAppTypeNotSpecified indicates that a tool in this project failed to
// specify a valid application type.
ErrAppTypeNotSpecified = errors.New("valid app type not specified")
)

// AppType represents the type of application that is being
// configured/initialized. Not all application types will use the same
// features and as a result will not accept the same flags. Unless noted
// otherwise, each of the application types are incompatible with each other,
// though some flags are common to all types.
type AppType struct {

// PluginIMAPMailboxBasicAuth represents an application used as a
// monitoring plugin for evaluating IMAP mailboxes.
//
// Basic Authentication is used to login.
PluginIMAPMailboxBasicAuth bool

// ReporterIMAPMailboxBasicAuth represents an application used for
// generating reports for specified IMAP mailboxes.
//
// Unlike an Inspector application which is focused on testing or
// gathering specific details for troubleshooting purposes or a monitoring
// plugin which is intended for providing a severity-based outcome, a
// Reporter application is intended for gathering information as an
// overview.
//
// Basic Authentication is used to login.
ReporterIMAPMailboxBasicAuth bool
}

// MailAccount represents an email account listed within a configuration file.
type MailAccount struct {
Expand Down Expand Up @@ -169,29 +201,29 @@ func Branding(msg string) func() string {
// provided flag and config file values. It is responsible for validating
// user-provided values and initializing the logging settings used by this
// application.
func New(useConfigFile bool, useLogFile bool) (*Config, error) {
func New(appType AppType) (*Config, error) {
var config Config

config.handleFlagsConfig(useConfigFile)
config.handleFlagsConfig(appType)

if config.ShowVersion {
return nil, ErrVersionRequested
}

if err := config.validate(useConfigFile); err != nil {
if err := config.validate(appType); err != nil {
return nil, fmt.Errorf("configuration validation failed: %w", err)
}

// initialize logging "early", just as soon as validation is complete so
// that we can rely on it to debug further configuration init work
if err := config.setupLogging(useLogFile); err != nil {
if err := config.setupLogging(appType); err != nil {
return nil, fmt.Errorf(
"failed to set logging configuration: %w",
err,
)
}

if useConfigFile {
if appType.ReporterIMAPMailboxBasicAuth {
if err := config.load(); err != nil {

// We log this message in an effort to populate the log file with
Expand All @@ -211,102 +243,6 @@ func New(useConfigFile bool, useLogFile bool) (*Config, error) {

}

// validate verifies all Config struct fields have been provided acceptable
// values.
func (c Config) validate(useConfigFile bool) error {

// NOTE: It's fine to *not* specify a config file. The expected behavior
// is that specifying a config file will be a rare thing; users will more
// often than not rely on config file auto-detection behavior.
//
// That said, if a user does not specify a config file, we need to require
// that one was found and loaded.
//
// if useConfigFile {
// if c.ConfigFile == "" {
// return fmt.Errorf("config file required, but not specified")
// }
// }

for _, account := range c.Accounts {
if account.Folders == nil {
return fmt.Errorf(
"one or more folders not provided for account %s",
account.Name,
)
}

if account.Port < 0 {
return fmt.Errorf(
"invalid TCP port number %d provided for account %s",
account.Port,
account.Name,
)
}

if account.Username == "" {
return fmt.Errorf("username not provided for account %s",
account.Name,
)
}

if account.Password == "" {
return fmt.Errorf("password not provided for account %s",
account.Name,
)
}

if account.Server == "" {
return fmt.Errorf("server FQDN not provided for account %s",
account.Name,
)
}
}

// these settings only apply to the list-emails application
if useConfigFile {

// set with a default value if not specified by the user, so should not
// ever be empty
if c.ReportFileOutputDir == "" {
return fmt.Errorf("missing report file output directory")
}

// set with a default value if not specified by the user, so should not
// ever be empty
if c.LogFileOutputDir == "" {
return fmt.Errorf("missing log file output directory")
}

}

switch strings.ToLower(c.minTLSVersion) {
case minTLSVersion10:
case minTLSVersion11:
case minTLSVersion12:
case minTLSVersion13:
default:
return fmt.Errorf("invalid TLS version keyword: %s", c.minTLSVersion)
}

switch strings.ToLower(c.NetworkType) {
case netTypeTCPAuto:
case netTypeTCP4:
case netTypeTCP6:
default:
return fmt.Errorf("invalid network type keyword: %s", c.NetworkType)
}

requestedLoggingLevel := strings.ToLower(c.LoggingLevel)
if _, ok := loggingLevels[requestedLoggingLevel]; !ok {
return fmt.Errorf("invalid logging level %s", c.LoggingLevel)
}

// Optimist
return nil

}

// load is a helper function to handle the bulk of the configuration loading
// work for the New constructor function.
func (c *Config) load() error {
Expand Down
22 changes: 9 additions & 13 deletions internal/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ package config
import "flag"

// handleFlagsConfig handles toggling the exposure of specific configuration
// flags to the user. This behavior is controlled via a boolean value
// initially set by each cmd. If enabled, a smaller subset of flags specific
// to the list-emails cmd is exposed, otherwise the set of flags specific to
// the Nagios plugin are exposed and processed.
func (c *Config) handleFlagsConfig(acceptConfigFile bool) {
// flags to the user. This behavior is controlled via the specified
// application type as set by each cmd. Based on the application's specified
// type, a smaller subset of flags specific to each type are exposed along
// with a set common to all application types.
func (c *Config) handleFlagsConfig(appType AppType) {

var account MailAccount

Expand All @@ -25,20 +25,22 @@ func (c *Config) handleFlagsConfig(acceptConfigFile bool) {
flag.StringVar(&c.minTLSVersion, "min-tls", defaultMinTLSVersion, minTLSVersionFlagHelp)

// currently only applies to list-emails app, don't expose to Nagios plugin
if acceptConfigFile {
if appType.ReporterIMAPMailboxBasicAuth {
flag.StringVar(&c.ConfigFile, "config-file", defaultINIConfigFileName, iniConfigFileFlagHelp)
flag.StringVar(&c.ReportFileOutputDir, "report-file-dir", defaultReportFileOutputDir, reportFileOutputDirFlagHelp)
flag.StringVar(&c.LogFileOutputDir, "log-file-dir", defaultLogFileOutputDir, logFileOutputDirFlagHelp)
}

// currently only applies to Nagios plugin
if !acceptConfigFile {
if appType.PluginIMAPMailboxBasicAuth {
flag.Var(&account.Folders, "folders", foldersFlagHelp)
flag.StringVar(&account.Username, "username", defaultUsername, usernameFlagHelp)
flag.StringVar(&account.Password, "password", defaultPassword, passwordFlagHelp)
flag.StringVar(&account.Server, "server", defaultServer, serverFlagHelp)
flag.IntVar(&account.Port, "port", defaultPort, portFlagHelp)
flag.BoolVar(&c.EmitBranding, "branding", defaultEmitBranding, emitBrandingFlagHelp)

c.Accounts = append(c.Accounts, account)
}

// Allow our function to override the default Help output
Expand All @@ -47,10 +49,4 @@ func (c *Config) handleFlagsConfig(acceptConfigFile bool) {
// parse flag definitions from the argument list
flag.Parse()

// if CLI-provided values were given then record those as an entry in the
// list
if !acceptConfigFile {
c.Accounts = append(c.Accounts, account)
}

}
13 changes: 10 additions & 3 deletions internal/config/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,17 @@ func setLoggingLevel(logLevel string) error {

// setupLogging is responsible for configuring logging settings for this
// application
func (c *Config) setupLogging(useLogFile bool) error {
func (c *Config) setupLogging(appType AppType) error {

var logOutput io.Writer

var useLogFile bool
switch {

// we want to log to a file only for list-emails
case appType.ReporterIMAPMailboxBasicAuth:

switch {
case useLogFile:
useLogFile = true

logFilename := fmt.Sprintf(
logFilenameTemplate,
Expand Down Expand Up @@ -136,6 +139,10 @@ func (c *Config) setupLogging(useLogFile bool) error {
// TODO: Set c.LogFileHandle to the newly opened file
default:

// Explicitly note that we disable use of a log file for all
// other application types.
useLogFile = false

// Nagios doesn't look at stderr, only stdout. We have to make sure
// that only whatever output is meant for consumption is emitted to
// stdout and whatever is meant for troubleshooting is sent to stderr.
Expand Down
Loading

0 comments on commit 0c5b393

Please sign in to comment.