github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/cmdconfig/viper.go (about)

     1  package cmdconfig
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/turbot/steampipe/pkg/filepaths"
     6  	"log"
     7  	"os"
     8  
     9  	filehelpers "github.com/turbot/go-kit/files"
    10  	"github.com/turbot/steampipe/pkg/steampipeconfig"
    11  
    12  	"github.com/spf13/cobra"
    13  	"github.com/spf13/viper"
    14  	"github.com/turbot/go-kit/types"
    15  	"github.com/turbot/steampipe/pkg/constants"
    16  )
    17  
    18  // Viper fetches the global viper instance
    19  func Viper() *viper.Viper {
    20  	return viper.GetViper()
    21  }
    22  
    23  // bootstrapViper sets up viper with the essential path config (workspace-chdir and install-dir)
    24  func bootstrapViper(loader *steampipeconfig.WorkspaceProfileLoader, cmd *cobra.Command) error {
    25  	// set defaults  for keys which do not have a corresponding command flag
    26  	if err := setBaseDefaults(); err != nil {
    27  		return err
    28  	}
    29  
    30  	// set defaults from defaultWorkspaceProfile
    31  	SetDefaultsFromConfig(loader.DefaultProfile.ConfigMap(cmd))
    32  
    33  	// set defaults for install dir and mod location from env vars
    34  	// this needs to be done since the workspace profile definitions exist in the
    35  	// default install dir
    36  	setDirectoryDefaultsFromEnv()
    37  
    38  	// NOTE: if an explicit workspace profile was set, default the mod location and install dir _now_
    39  	// All other workspace profile values are defaults _after defaulting to the connection config options
    40  	// to give them higher precedence, but these must be done now as subsequent operations depend on them
    41  	// (and they cannot be set from hcl options)
    42  	if loader.ConfiguredProfile != nil {
    43  		if loader.ConfiguredProfile.ModLocation != nil {
    44  			log.Printf("[TRACE] setting mod location from configured profile '%s' to '%s'", loader.ConfiguredProfile.Name(), *loader.ConfiguredProfile.ModLocation)
    45  			viper.SetDefault(constants.ArgModLocation, *loader.ConfiguredProfile.ModLocation)
    46  		}
    47  		if loader.ConfiguredProfile.InstallDir != nil {
    48  			log.Printf("[TRACE] setting install dir from configured profile '%s' to '%s'", loader.ConfiguredProfile.Name(), *loader.ConfiguredProfile.InstallDir)
    49  			viper.SetDefault(constants.ArgInstallDir, *loader.ConfiguredProfile.InstallDir)
    50  		}
    51  	}
    52  
    53  	// tildefy all paths in viper
    54  	return tildefyPaths()
    55  }
    56  
    57  // tildefyPaths cleans all path config values and replaces '~' with the home directory
    58  func tildefyPaths() error {
    59  	pathArgs := []string{
    60  		constants.ArgModLocation,
    61  		constants.ArgInstallDir,
    62  	}
    63  	var err error
    64  	for _, argName := range pathArgs {
    65  		if argVal := viper.GetString(argName); argVal != "" {
    66  			if argVal, err = filehelpers.Tildefy(argVal); err != nil {
    67  				return err
    68  			}
    69  			if viper.IsSet(argName) {
    70  				// if the value was already set re-set
    71  				viper.Set(argName, argVal)
    72  			} else {
    73  				// otherwise just update the default
    74  				viper.SetDefault(argName, argVal)
    75  			}
    76  		}
    77  	}
    78  	return nil
    79  }
    80  
    81  // SetDefaultsFromConfig overrides viper default values from hcl config values
    82  func SetDefaultsFromConfig(configMap map[string]interface{}) {
    83  	for k, v := range configMap {
    84  		viper.SetDefault(k, v)
    85  	}
    86  }
    87  
    88  // for keys which do not have a corresponding command flag, we need a separate defaulting mechanism
    89  // any option setting, workspace profile property or env var which does not have a command line
    90  // MUST have a default (unless we want the zero value to take effect)
    91  //
    92  // Do not add keys here which have command line defaults - the way this is setup, this value takes
    93  // precedence over command line default
    94  func setBaseDefaults() error {
    95  	pipesInstallDir, err := filehelpers.Tildefy(filepaths.DefaultPipesInstallDir)
    96  	if err != nil {
    97  		return err
    98  	}
    99  	defaults := map[string]interface{}{
   100  		// global general options
   101  		constants.ArgTelemetry:       constants.TelemetryInfo,
   102  		constants.ArgUpdateCheck:     true,
   103  		constants.ArgPipesInstallDir: pipesInstallDir,
   104  
   105  		// workspace profile
   106  		constants.ArgAutoComplete:  true,
   107  		constants.ArgIntrospection: constants.IntrospectionNone,
   108  
   109  		// from global database options
   110  		constants.ArgDatabasePort:         constants.DatabaseDefaultPort,
   111  		constants.ArgDatabaseStartTimeout: constants.DBStartTimeout.Seconds(),
   112  		constants.ArgServiceCacheEnabled:  true,
   113  		constants.ArgCacheMaxTtl:          300,
   114  
   115  		// dashboard
   116  		constants.ArgDashboardStartTimeout: constants.DashboardStartTimeout.Seconds(),
   117  
   118  		// memory
   119  		constants.ArgMemoryMaxMbPlugin: 1024,
   120  		constants.ArgMemoryMaxMb:       1024,
   121  	}
   122  
   123  	for k, v := range defaults {
   124  		viper.SetDefault(k, v)
   125  	}
   126  	return nil
   127  }
   128  
   129  type envMapping struct {
   130  	configVar []string
   131  	varType   EnvVarType
   132  }
   133  
   134  // set default values of INSTALL_DIR and ModLocation from env vars
   135  func setDirectoryDefaultsFromEnv() {
   136  	envMappings := map[string]envMapping{
   137  		constants.EnvInstallDir:     {[]string{constants.ArgInstallDir}, String},
   138  		constants.EnvWorkspaceChDir: {[]string{constants.ArgModLocation}, String},
   139  		constants.EnvModLocation:    {[]string{constants.ArgModLocation}, String},
   140  	}
   141  
   142  	for envVar, mapping := range envMappings {
   143  		setConfigFromEnv(envVar, mapping.configVar, mapping.varType)
   144  	}
   145  }
   146  
   147  // setDefaultsFromEnv sets default values from env vars
   148  func setDefaultsFromEnv() {
   149  	// NOTE: EnvWorkspaceProfile has already been set as a viper default as we have already loaded workspace profiles
   150  	// (EnvInstallDir has already been set at same time but we set it again to make sure it has the correct precedence)
   151  
   152  	// a map of known environment variables to map to viper keys
   153  	envMappings := map[string]envMapping{
   154  		constants.EnvInstallDir:     {[]string{constants.ArgInstallDir}, String},
   155  		constants.EnvWorkspaceChDir: {[]string{constants.ArgModLocation}, String},
   156  		constants.EnvModLocation:    {[]string{constants.ArgModLocation}, String},
   157  		constants.EnvIntrospection:  {[]string{constants.ArgIntrospection}, String},
   158  		constants.EnvTelemetry:      {[]string{constants.ArgTelemetry}, String},
   159  		constants.EnvUpdateCheck:    {[]string{constants.ArgUpdateCheck}, Bool},
   160  		// deprecated
   161  		constants.EnvCloudHost:             {[]string{constants.ArgPipesHost}, String},
   162  		constants.EnvCloudToken:            {[]string{constants.ArgPipesToken}, String},
   163  		constants.EnvPipesHost:             {[]string{constants.ArgPipesHost}, String},
   164  		constants.EnvPipesToken:            {[]string{constants.ArgPipesToken}, String},
   165  		constants.EnvSnapshotLocation:      {[]string{constants.ArgSnapshotLocation}, String},
   166  		constants.EnvWorkspaceDatabase:     {[]string{constants.ArgWorkspaceDatabase}, String},
   167  		constants.EnvServicePassword:       {[]string{constants.ArgServicePassword}, String},
   168  		constants.EnvDisplayWidth:          {[]string{constants.ArgDisplayWidth}, Int},
   169  		constants.EnvMaxParallel:           {[]string{constants.ArgMaxParallel}, Int},
   170  		constants.EnvQueryTimeout:          {[]string{constants.ArgDatabaseQueryTimeout}, Int},
   171  		constants.EnvDatabaseStartTimeout:  {[]string{constants.ArgDatabaseStartTimeout}, Int},
   172  		constants.EnvDatabaseSSLPassword:   {[]string{constants.ArgDatabaseSSLPassword}, String},
   173  		constants.EnvDashboardStartTimeout: {[]string{constants.ArgDashboardStartTimeout}, Int},
   174  		constants.EnvCacheTTL:              {[]string{constants.ArgCacheTtl}, Int},
   175  		constants.EnvCacheMaxTTL:           {[]string{constants.ArgCacheMaxTtl}, Int},
   176  		constants.EnvMemoryMaxMb:           {[]string{constants.ArgMemoryMaxMb}, Int},
   177  		constants.EnvMemoryMaxMbPlugin:     {[]string{constants.ArgMemoryMaxMbPlugin}, Int},
   178  
   179  		// we need this value to go into different locations
   180  		constants.EnvCacheEnabled: {[]string{
   181  			constants.ArgClientCacheEnabled,
   182  			constants.ArgServiceCacheEnabled,
   183  		}, Bool},
   184  	}
   185  
   186  	for envVar, v := range envMappings {
   187  		setConfigFromEnv(envVar, v.configVar, v.varType)
   188  	}
   189  }
   190  
   191  func setConfigFromEnv(envVar string, configs []string, varType EnvVarType) {
   192  	for _, configVar := range configs {
   193  		SetDefaultFromEnv(envVar, configVar, varType)
   194  	}
   195  }
   196  
   197  func SetDefaultFromEnv(k string, configVar string, varType EnvVarType) {
   198  	if val, ok := os.LookupEnv(k); ok {
   199  		switch varType {
   200  		case String:
   201  			viper.SetDefault(configVar, val)
   202  		case Bool:
   203  			if boolVal, err := types.ToBool(val); err == nil {
   204  				viper.SetDefault(configVar, boolVal)
   205  			}
   206  		case Int:
   207  			if intVal, err := types.ToInt64(val); err == nil {
   208  				viper.SetDefault(configVar, intVal)
   209  			}
   210  		default:
   211  			// must be an invalid value in the map above
   212  			panic(fmt.Sprintf("invalid env var mapping type: %s", varType))
   213  		}
   214  	}
   215  }