Files
golib/cobra/README.md
Nicolas JUHEL 49db5e2afb Add Package Config :
- major dependancies are :
  - golib/context config
  - golib/viper
- interface config extend :
  - golib/context config interface
  - component list
- interface component list :
  this interface implement all function to manage a collection of component. All component are registred with they config key.
  A component must implement basic function like start, stop, reload, defaultConfig...
  The main config json is set by calling all component config with the config key attached
  Each component have some status status like isStarted, isRunning, ...
  Each component must also declare his dependencies with other component. As that when start or reload is called,
  the component is sure that dependencies are still started or reloaded.
- They are 4 component for now : log, tls, headers and http server
- The config package will start a new context / cancelfunc on init to be sure to stop cleanly all component and process

Add Package Viper :
- this package is an helper to the config package with the spf13 viper lib
- this package can watch any change of a config file or can be connected to a remote config cluster like ETCD

Add Package Cobra :
- this package is an helper to make a CLI with flag / command
- this package is based on spf13 cobra has all method to be connected to viper golib
2022-02-21 07:32:44 +01:00

6.8 KiB

Cobra pakcage

Help build integration of spf13/Cobra lib. This package will simplify call and use of flags / command for a CLI Tools.

Exmaple of implement

Make some folder / file like this:

/api
|- root.go
|- one_command.go
/pkg
|- common.go
/main.go

Main init :

This commong.go file will make the init of version, logger, cobra, viper and config packages.

import (
	"strings"

	libcbr "github.com/nabbar/golib/cobra"
	libcfg "github.com/nabbar/golib/config"
	liblog "github.com/nabbar/golib/logger"
	libver "github.com/nabbar/golib/version"
	libvpr "github.com/nabbar/golib/viper"
)

Some variables :

var(
	// Golib Version Package
    vrs libver.Version
	
	// Main config / context Golib package
	cfg libcfg.Config
	
	// Root logger before config 
	log liblog.Logger
	
    // Main Cobra / Viper resource
    cbr libcbr.Cobra
	vpr libvpr.Viper
)

This function will init the libs if not set and return the lib resource :

func GetViper() libvpr.Viper {
    if vpr == nil {
        vpr = libvpr.New()
        vpr.SetHomeBaseName(strings.ToLower(GetVersion().GetPackage()))
        vpr.SetEnvVarsPrefix(GetVersion().GetPrefix())
        vpr.SetDefaultConfig(GetConfig().DefaultConfig)
    }

    return vpr
}

func GetCobra() libcbr.Cobra {
    if cbr == nil {
        cbr = libcbr.New()
        cbr.SetVersion(GetVersion())
        cbr.SetLogger(GetLogger)
        cbr.SetViper(GetViper)
    }

    return cbr
}

func GetConfig() libcfg.Config {
    if cfg == nil {
        cfg = libcfg.New()
        cfg.RegisterFuncViper(GetViper)
    }

    return cfg
}

func GetVersion() libver.Version {
    if vrs == nil {
        //vrs = libver.NewVersion(...)
    }

    return vrs
}

func GetLogger() liblog.Logger {
    if log == nil {
        log = liblog.New(cfg.Context())
        _ = log.SetOptions(&liblog.Options{
            DisableStandard:  false,
            DisableStack:     false,
            DisableTimestamp: false,
            EnableTrace:      true,
            TraceFilter:      GetVersion().GetRootPackagePath(),
            DisableColor:     false,
            LogFile:          nil, // TODO
            LogSyslog:        nil, // TODO
        })
        log.SetLevel(liblog.InfoLevel)
		log.SetSPF13Level(liblog.InfoLevel, nil)
    }

    return log
}

The root file in command folder

This file will be the global file for cobra. It will config and init the cobra for yours command. This file will import your pkg/common.go file :

import(
    "fmt"
    "path"
    
    libcns "github.com/nabbar/golib/console"
    liberr "github.com/nabbar/golib/errors"
    liblog "github.com/nabbar/golib/logger"
    
    compkg "../pkg"
    apipkg "../api"
)

And will define some flag vars :

var (
	// var for config file path to be load by viper
	cfgFile    string
	
	// var for log verbose
	flgVerbose int
	
	// flag for init cobra has error
	iniErr     liberr.Error
)

The InitCommand function will initialize the cobra / viper package and will be call by the main init function into you main/init() function:

func InitCommand() {
	//the initFunc is a function call on init cobra before calling command but after parsing flag / command ...
	compkg.GetCobra().SetFuncInit(initConfig)
	
	// must init after the SetFuncinit function
	compkg.GetCobra().Init()
	
	// add the Config file flag
	compkg.GetLogger().CheckError(liblog.FatalLevel, liblog.DebugLevel, "Add Flag Config File", compkg.GetCobra().SetFlagConfig(true, &cfgFile))
	
	// add the verbose flas
	compkg.GetCobra().SetFlagVerbose(true, &flgVerbose)

	// Add some generic command
	compkg.GetCobra().AddCommandCompletion()
	compkg.GetCobra().AddCommandConfigure("", compkg.GetConfig().DefaultConfig)
	compkg.GetCobra().AddCommandPrintErrorCode(cmdPrintError)
	
	// Add one custom command. The best is to isolate each command into a specific file
	// Each command file, will having a main function to create the cobra command. 
	// Here we will only call this main function to add each command into the main cobra command like this
	compkg.GetCobra().AddCommand(initCustomCommand1())
	
	// Carefully with the `init` function, because you will not manage the order of running each init function.
	// To prevent it, the best is to not having init function, but custom init call in the awaiting order into the `main/init` function
}

// this function will be use in the PrintError command to print first ErrorCode => Package
func cmdPrintError(item, value string) {
	println(fmt.Sprintf("%s : %s", libcns.PadLeft(item, 15, " "), path.Dir(value)))
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
	// Redefine the root logger level with the verbose flag
	switch flgVerbose {
	case 0:
		compkg.GetLogger().SetLevel(liblog.ErrorLevel)
	case 1:
		compkg.GetLogger().SetLevel(liblog.WarnLevel)
	case 2:
		compkg.GetLogger().SetLevel(liblog.InfoLevel)
	default:
		compkg.GetLogger().SetLevel(liblog.DebugLevel)
	}

	// Define the config file into viper to load it if is set
	iniErr = compkg.GetViper().SetConfigFile(cfgFile)
	compkg.GetLogger().CheckError(liblog.FatalLevel, liblog.DebugLevel, "define config file", iniErr)

	// try to init viper with config (local file or remote)
	if iniErr = compkg.GetViper().Config(liblog.NilLevel, liblog.NilLevel); iniErr == nil {
		// register the reload config function with the watch FS function 
		compkg.GetViper().SetRemoteReloadFunc(func() {
			_ = compkg.GetLogger().CheckError(liblog.ErrorLevel, liblog.DebugLevel, "config reload", compkg.GetConfig().Reload())
		})
		compkg.GetViper().WatchFS(liblog.InfoLevel)
	}

	// Make the config for this app (cf config package)
	apipkg.SetConfig(compkg.GetLogger().GetLevel(), apirtr.GetHandlers())
}

// This is called by main.main() to parse and run the app.
func Execute() {
    compkg.GetLogger().CheckError(liblog.FatalLevel, liblog.DebugLevel, "RootCmd Executed", compkg.GetCobra().Execute())
}

The main file of your app

The main file of your app, wil implement the main/init() function and the main/main() function.

import(
    liberr "github.com/nabbar/golib/errors"
    liblog "github.com/nabbar/golib/logger"

    compkg "./pkg"
    cmdpkg "./cmd"
)

func init() {
	// Configure the golib error package
	liberr.SetModeReturnError(liberr.ErrorReturnStringErrorFull)
	
	// Check the go runtime use to build
	compkg.GetLogger().CheckError(liblog.FatalLevel, liblog.DebugLevel, "Checking go version", compkg.GetVersion().CheckGo("1.16", ">="))
	
	// Call the `cmd/InitCommand` function
	cmdpkg.InitCommand()
}

func main() {
	// run the command Execute function
	cmdpkg.Execute()
}