github.com/Axway/agent-sdk@v1.1.101/pkg/cmd/root.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "math/rand" 6 "net/url" 7 "os" 8 "strings" 9 "time" 10 11 "github.com/Axway/agent-sdk/pkg/agent" 12 "github.com/Axway/agent-sdk/pkg/apic" 13 "github.com/Axway/agent-sdk/pkg/cmd/agentsync" 14 "github.com/Axway/agent-sdk/pkg/cmd/properties" 15 "github.com/Axway/agent-sdk/pkg/cmd/properties/resolver" 16 "github.com/Axway/agent-sdk/pkg/config" 17 "github.com/Axway/agent-sdk/pkg/jobs" 18 "github.com/Axway/agent-sdk/pkg/util" 19 "github.com/Axway/agent-sdk/pkg/util/errors" 20 hc "github.com/Axway/agent-sdk/pkg/util/healthcheck" 21 "github.com/Axway/agent-sdk/pkg/util/log" 22 23 "github.com/fsnotify/fsnotify" 24 "github.com/spf13/cobra" 25 26 "github.com/spf13/viper" 27 ) 28 29 // Constants for cmd flags 30 const ( 31 pathConfigFlag = "pathConfig" 32 beatsPathConfigFlag = "path.config" 33 EnvFileFlag = "envFile" 34 EnvFileFlagDescription = "Path of the file with environment variables to override configuration" 35 cpuprofile = "cpuprofile" 36 memprofile = "memprofile" 37 httpprofile = "httpprofile" 38 ) 39 40 // CommandHandler - Root command execution handler 41 type CommandHandler func() error 42 43 // InitConfigHandler - Handler to be invoked on config initialization 44 type InitConfigHandler func(centralConfig config.CentralConfig) (interface{}, error) 45 46 // AgentRootCmd - Root Command for the Agents 47 type AgentRootCmd interface { 48 RootCmd() *cobra.Command 49 Execute() error 50 51 // Get the agentType 52 GetAgentType() config.AgentType 53 AddCommand(*cobra.Command) 54 55 GetProperties() properties.Properties 56 } 57 58 // agentRootCommand - Represents the agent root command 59 type agentRootCommand struct { 60 agentName string 61 rootCmd *cobra.Command 62 commandHandler CommandHandler 63 initConfigHandler InitConfigHandler 64 agentType config.AgentType 65 props properties.Properties 66 statusCfg config.StatusConfig 67 agentFeaturesCfg config.AgentFeaturesConfig 68 centralCfg config.CentralConfig 69 agentCfg interface{} 70 secretResolver resolver.SecretResolver 71 initialized bool 72 memprofile string 73 cpuprofile string 74 httpprofile bool 75 } 76 77 func init() { 78 config.AgentTypeName = BuildAgentName 79 config.AgentVersion = BuildVersion + "-" + BuildCommitSha 80 config.AgentDataPlaneType = apic.Unidentified.String() 81 if BuildDataPlaneType != "" { 82 config.AgentDataPlaneType = BuildDataPlaneType 83 } 84 85 config.SDKVersion = SDKBuildVersion 86 // initalize the global Source used by rand.Intn() and other functions of the rand package using rand.Seed(). 87 rand.Seed(time.Now().UnixNano()) 88 } 89 90 func buildCmdVersion(desc string) string { 91 return fmt.Sprintf("- %s", buildAgentInfo(desc)) 92 } 93 94 func buildAgentInfo(desc string) string { 95 return fmt.Sprintf("%s version %s-%s, Amplify Agents SDK version %s", desc, BuildVersion, BuildCommitSha, SDKBuildVersion) 96 } 97 98 // NewRootCmd - Creates a new Agent Root Command 99 func NewRootCmd(exeName, desc string, initConfigHandler InitConfigHandler, commandHandler CommandHandler, agentType config.AgentType) AgentRootCmd { 100 c := &agentRootCommand{ 101 agentName: exeName, 102 commandHandler: commandHandler, 103 initConfigHandler: initConfigHandler, 104 agentType: agentType, 105 secretResolver: resolver.NewSecretResolver(), 106 initialized: false, 107 } 108 109 // use the description from the build if available 110 if BuildAgentDescription != "" { 111 desc = BuildAgentDescription 112 } 113 114 c.rootCmd = &cobra.Command{ 115 Use: c.agentName, 116 Short: desc, 117 Version: buildCmdVersion(desc), 118 RunE: c.run, 119 PreRunE: c.initialize, 120 } 121 122 c.props = properties.NewPropertiesWithSecretResolver(c.rootCmd, c.secretResolver) 123 c.addBaseProps(agentType) 124 config.AddLogConfigProperties(c.props, fmt.Sprintf("%s.log", exeName)) 125 config.AddMetricLogConfigProperties(c.props, agentType) 126 config.AddUsageConfigProperties(c.props, agentType) 127 agentsync.AddSyncConfigProperties(c.props) 128 config.AddCentralConfigProperties(c.props, agentType) 129 config.AddStatusConfigProperties(c.props) 130 config.AddAgentFeaturesConfigProperties(c.props) 131 132 hc.SetNameAndVersion(exeName, c.rootCmd.Version) 133 134 // Call the config add props 135 return c 136 } 137 138 // NewCmd - Creates a new Agent Root Command using existing cmd 139 func NewCmd(rootCmd *cobra.Command, exeName, desc string, initConfigHandler InitConfigHandler, commandHandler CommandHandler, agentType config.AgentType) AgentRootCmd { 140 c := &agentRootCommand{ 141 agentName: exeName, 142 commandHandler: commandHandler, 143 initConfigHandler: initConfigHandler, 144 agentType: agentType, 145 secretResolver: resolver.NewSecretResolver(), 146 initialized: false, 147 } 148 149 // use the description from the build if available 150 if BuildAgentDescription != "" { 151 desc = BuildAgentDescription 152 } 153 154 c.rootCmd = rootCmd 155 c.rootCmd.Use = c.agentName 156 c.rootCmd.Short = desc 157 c.rootCmd.Version = buildCmdVersion(desc) 158 c.rootCmd.RunE = c.run 159 c.rootCmd.PreRunE = c.initialize 160 161 c.props = properties.NewPropertiesWithSecretResolver(c.rootCmd, c.secretResolver) 162 if agentType == config.TraceabilityAgent { 163 properties.SetAliasKeyPrefix(c.agentName) 164 } 165 166 c.addBaseProps(agentType) 167 config.AddLogConfigProperties(c.props, fmt.Sprintf("%s.log", exeName)) 168 agentsync.AddSyncConfigProperties(c.props) 169 config.AddCentralConfigProperties(c.props, agentType) 170 config.AddStatusConfigProperties(c.props) 171 config.AddAgentFeaturesConfigProperties(c.props) 172 173 hc.SetNameAndVersion(exeName, c.rootCmd.Version) 174 175 removeBeatSubCommands(c.rootCmd) 176 // Call the config add props 177 return c 178 } 179 180 func removeBeatSubCommands(rootCmd *cobra.Command) { 181 removeBeatSubCommand(rootCmd, "export") 182 removeBeatSubCommand(rootCmd, "keystore") 183 removeBeatSubCommand(rootCmd, "run") 184 removeBeatSubCommand(rootCmd, "setup") 185 removeBeatSubCommand(rootCmd, "test") 186 removeBeatSubCommand(rootCmd, "version") 187 } 188 189 func removeBeatSubCommand(rootCmd *cobra.Command, subCmdName string) { 190 subCmd, _, err := rootCmd.Find([]string{subCmdName}) 191 if err == nil { 192 rootCmd.RemoveCommand(subCmd) 193 } 194 } 195 196 // Add the command line properties for the logger and path config 197 func (c *agentRootCommand) addBaseProps(agentType config.AgentType) { 198 c.props.AddStringPersistentFlag(pathConfigFlag, ".", "Path to the directory containing the YAML configuration file for the agent") 199 c.props.AddStringPersistentFlag(EnvFileFlag, "", EnvFileFlagDescription) 200 if agentType == config.DiscoveryAgent { 201 c.props.AddStringProperty(cpuprofile, "", "write cpu profile to `file`") 202 c.props.AddStringProperty(memprofile, "", "write memory profile to `file`") 203 c.props.AddBoolProperty(httpprofile, false, "set to setup the http profiling endpoints") 204 } 205 } 206 207 func (c *agentRootCommand) initialize(cmd *cobra.Command, args []string) error { 208 _, envFile := c.props.StringFlagValue(EnvFileFlag) 209 err := util.LoadEnvFromFile(envFile) 210 if err != nil { 211 return errors.Wrap(config.ErrEnvConfigOverride, err.Error()) 212 } 213 214 _, agentConfigFilePath := c.props.StringFlagValue(pathConfigFlag) 215 _, beatsConfigFilePath := c.props.StringFlagValue(beatsPathConfigFlag) 216 if c.agentType == config.DiscoveryAgent { 217 _, c.cpuprofile = c.props.StringFlagValue(cpuprofile) 218 _, c.memprofile = c.props.StringFlagValue(memprofile) 219 c.httpprofile = c.props.BoolFlagValue(httpprofile) 220 } 221 222 // If the Agent pathConfig value is set and the beats path.config is not then use the pathConfig value for both 223 if beatsConfigFilePath == "" && agentConfigFilePath != "" { 224 c.props.SetStringFlagValue(beatsPathConfigFlag, agentConfigFilePath) 225 _, beatsConfigFilePath = c.props.StringFlagValue(beatsPathConfigFlag) 226 } 227 228 viper.SetConfigName(c.agentName) 229 // viper.SetConfigType("yaml") //Comment out since yaml, yml is a support extension already. We need an updated story to take into account the other supported extensions 230 231 // Add both the agent pathConfig and beats path.config paths to the config path array 232 viper.AddConfigPath(agentConfigFilePath) 233 viper.AddConfigPath(beatsConfigFilePath) 234 viper.AddConfigPath(".") 235 viper.SetTypeByDefaultValue(true) 236 viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 237 viper.AutomaticEnv() 238 err = viper.ReadInConfig() 239 if err != nil { 240 if envFile == "" { 241 return err 242 } else if _, ok := err.(viper.ConfigFileNotFoundError); !ok { 243 return err 244 } 245 } 246 247 viper.WatchConfig() 248 viper.OnConfigChange(func(e fsnotify.Event) { 249 log.Debugf("Config file changed : %s", e.Name) 250 c.onConfigChange() 251 }) 252 253 c.checkStatusFlag() 254 agentsync.SetSyncMode(c.GetProperties()) 255 return nil 256 } 257 258 func (c *agentRootCommand) checkStatusFlag() { 259 statusPort := c.props.IntPropertyValue("status.port") 260 if c.props.BoolFlagValue("status") { 261 urlObj := url.URL{ 262 Scheme: "http", 263 Host: fmt.Sprintf("localhost:%d", statusPort), 264 Path: "status", 265 } 266 statusOut, err := hc.GetHealthcheckOutput(urlObj.String()) 267 if statusOut != "" { 268 fmt.Println(statusOut) 269 } 270 271 if err != nil { 272 fmt.Println("Error in getting status : " + err.Error()) 273 os.Exit(1) 274 } 275 os.Exit(0) 276 } 277 } 278 279 func (c *agentRootCommand) onConfigChange() { 280 c.initConfig() 281 agentConfigChangeHandler := agent.GetConfigChangeHandler() 282 if agentConfigChangeHandler != nil { 283 agentConfigChangeHandler() 284 } 285 } 286 287 // initConfig - Initializes the central config and invokes initConfig handler 288 // to initialize the agent config. Performs validation on returned agent config 289 func (c *agentRootCommand) initConfig() error { 290 // Clean the secret map on config change 291 c.secretResolver.ResetResolver() 292 293 _, err := config.ParseAndSetupLogConfig(c.GetProperties(), c.agentType) 294 if err != nil { 295 return err 296 } 297 298 c.statusCfg, _ = config.ParseStatusConfig(c.GetProperties()) 299 err = c.statusCfg.ValidateCfg() 300 if err != nil { 301 return err 302 } 303 304 // Init Agent Features Config 305 c.agentFeaturesCfg, err = config.ParseAgentFeaturesConfig(c.GetProperties()) 306 if err != nil { 307 return err 308 } 309 310 // Init Central Config 311 c.centralCfg, err = config.ParseCentralConfig(c.GetProperties(), c.GetAgentType()) 312 if err != nil { 313 return err 314 } 315 316 // must set the hc config now, because the healthchecker loop starts in agent.Initialize 317 hc.SetStatusConfig(c.statusCfg) 318 319 err = agent.InitializeWithAgentFeatures(c.centralCfg, c.agentFeaturesCfg) 320 if err != nil { 321 return err 322 } 323 agent.InitializeProfiling(c.cpuprofile, c.memprofile) 324 325 jobs.UpdateDurations(c.statusCfg.GetHealthCheckInterval(), c.centralCfg.GetJobExecutionTimeout()) 326 327 // Initialize Agent Config 328 c.agentCfg, err = c.initConfigHandler(c.centralCfg) 329 if err != nil { 330 return err 331 } 332 333 if c.agentCfg != nil { 334 err := agent.ApplyResourceToConfig(c.agentCfg) 335 if err != nil { 336 return err 337 } 338 339 // Validate Agent Config 340 err = config.ValidateConfig(c.agentCfg) 341 if err != nil { 342 return err 343 } 344 } 345 346 if !c.initialized { 347 err = c.finishInit() 348 if err != nil { 349 return err 350 } 351 } 352 c.initialized = true 353 return nil 354 } 355 356 func (c *agentRootCommand) finishInit() error { 357 if util.IsNotTest() && c.agentFeaturesCfg.ConnectionToCentralEnabled() && !c.centralCfg.GetUsageReportingConfig().IsOfflineMode() { 358 eventSync, err := agent.NewEventSync() 359 if err != nil { 360 return errors.Wrap(errors.ErrInitServicesNotReady, err.Error()) 361 } 362 363 if err := eventSync.SyncCache(); err != nil { 364 return errors.Wrap(errors.ErrInitServicesNotReady, err.Error()) 365 } 366 // set the rebuild function in the agent resource manager 367 agent.GetAgentResourceManager().SetRebuildCacheFunc(eventSync) 368 369 } 370 371 // Start the initial and recurring version check jobs 372 startVersionCheckJobs(c.centralCfg, c.agentFeaturesCfg) 373 374 if util.IsNotTest() { 375 healthCheckServer := hc.NewServer(c.httpprofile) 376 healthCheckServer.HandleRequests() 377 } 378 379 return nil 380 } 381 382 // run - Executes the agent command 383 func (c *agentRootCommand) run(cmd *cobra.Command, args []string) (err error) { 384 err = c.initConfig() 385 statusText := "" 386 if err == nil { 387 // Register resource change handler to re-initialize config on resource change 388 // This should trigger config init and applyresourcechange handlers 389 agent.OnAgentResourceChange(c.onConfigChange) 390 391 // Check the sync flag 392 exitcode := agentsync.CheckSyncFlag() 393 if exitcode > -1 { 394 os.Exit(exitcode) 395 } 396 397 log.Infof("Starting %s", buildAgentInfo(c.rootCmd.Short)) 398 if c.commandHandler != nil { 399 // Setup logp to use beats logger. 400 // Setting up late here as log entries for agent/command initialization are not logged 401 // as the beats logger is initialized only when the beat instance is created. 402 if c.agentType == config.TraceabilityAgent { 403 properties.SetAliasKeyPrefix(c.agentName) 404 log.SetIsLogP() 405 } 406 407 c.healthCheckTicker() 408 409 if util.IsNotTest() && c.agentFeaturesCfg.AgentStatusUpdatesEnabled() && !c.centralCfg.GetUsageReportingConfig().IsOfflineMode() { 410 agent.StartAgentStatusUpdate() 411 } 412 413 err = c.commandHandler() 414 if err != nil { 415 log.Error(err.Error()) 416 statusText = err.Error() 417 } 418 } 419 } else { 420 statusText = err.Error() 421 } 422 status := agent.AgentStopped 423 if statusText != "" { 424 status = agent.AgentFailed 425 } 426 agent.UpdateStatusWithPrevious(status, agent.AgentRunning, statusText) 427 return 428 } 429 430 // Run health check ticker for every 5 seconds 431 // If after 5 minutes, the health checker still returns HC status !OK, exit the agent. Otherwise, return true and continue processing 432 func (c *agentRootCommand) healthCheckTicker() { 433 log.Trace("run health checker ticker to check health status on RunChecks") 434 ticker := time.NewTicker(5 * time.Second) 435 tickerTimeout := time.NewTicker(5 * time.Minute) 436 437 defer ticker.Stop() 438 defer tickerTimeout.Stop() 439 440 for { 441 select { 442 case <-tickerTimeout.C: 443 log.Error("healthcheck run checks failing. Stopping agent - Check docs.axway.com for more info on the reported error code") 444 agent.UpdateStatus(agent.AgentFailed, "healthchecks on startup failed") 445 os.Exit(0) 446 case <-ticker.C: 447 status := hc.RunChecks() 448 if status == hc.OK { 449 log.Trace("healthcheck on startup is OK. Continue processing") 450 return 451 } else { 452 log.Warn("healthchecks on startup are still processing") 453 } 454 } 455 } 456 } 457 458 func (c *agentRootCommand) RootCmd() *cobra.Command { 459 return c.rootCmd 460 } 461 462 func (c *agentRootCommand) Execute() error { 463 return c.rootCmd.Execute() 464 } 465 466 func (c *agentRootCommand) GetAgentType() config.AgentType { 467 return c.agentType 468 } 469 470 func (c *agentRootCommand) GetProperties() properties.Properties { 471 return c.props 472 } 473 474 func (c *agentRootCommand) AddCommand(cmd *cobra.Command) { 475 c.rootCmd.AddCommand(cmd) 476 }