tool/processors/tracesconfig/tracesconfig.go (298 lines of code) (raw):

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT package tracesconfig import ( _ "embed" "encoding/json" "errors" "fmt" "os" "strconv" "github.com/aws/amazon-cloudwatch-agent/tool/data" "github.com/aws/amazon-cloudwatch-agent/tool/data/config" "github.com/aws/amazon-cloudwatch-agent/tool/processors" "github.com/aws/amazon-cloudwatch-agent/tool/processors/serialization" "github.com/aws/amazon-cloudwatch-agent/tool/runtime" "github.com/aws/amazon-cloudwatch-agent/tool/util" "github.com/aws/amazon-cloudwatch-agent/tool/xraydaemonmigration" ) var Processor processors.Processor = &processor{} type processor struct{} const addr = "127.0.0.1" func (p *processor) Process(ctx *runtime.Context, cfg *data.Config) { //skip if linux or windows noninteractive migration if ctx.WindowsNonInteractiveMigration { return } if !ctx.TracesOnly { yes := util.Yes("Do you want the CloudWatch agent to also retrieve X-ray traces?") if !yes { return } } newTraces, err := generateTracesConfiguration(ctx) if err != nil || newTraces == nil { return } cfg.TracesConfig = newTraces //user can review and update their current configurations if cfg.TracesConfig != nil && !ctx.NonInteractiveXrayMigration { cfg.TracesConfig = updateUserConfig(cfg.TracesConfig) } } func (p *processor) NextProcessor(ctx *runtime.Context, config *data.Config) interface{} { return serialization.Processor } //go:embed configtraces.json var DefaultTracesConfigFile []byte const ( anyExistingDaemonConfiguration = "Do you have an existing X-Ray Daemon configuration file to import for migration?" filePathXrayConfigQuestion = "What is the file path for the existing X-Ray Daemon configuration file?" ) func userBuildsTracesConfig(tracesConfig *config.Traces) *config.Traces { json.Unmarshal(DefaultTracesConfigFile, &tracesConfig) whichUDPPort(tracesConfig) whichTCPPort(tracesConfig) chooseBufferSize(tracesConfig) chooseConcurrency(tracesConfig) chooseRegion(tracesConfig) return tracesConfig } func whichUDPPort(tracesConfig *config.Traces) { answer := util.AskWithDefault("Which UDP port do you want XRay daemon to listen to?", "2000") num, err := strconv.Atoi(answer) if err != nil || num < 0 { tracesConfig.TracesCollected.Xray.BindAddress = addr + ":2000" } newAddr := fmt.Sprintf("%s:%s", addr, answer) tracesConfig.TracesCollected.Xray.BindAddress = newAddr } func whichTCPPort(tracesConfig *config.Traces) { answer := util.AskWithDefault("Which TCP port do you want XRay daemon to listen to?", "2000") num, err := strconv.Atoi(answer) if err != nil || num < 0 { tracesConfig.TracesCollected.Xray.BindAddress = addr + ":2000" } newAddr := fmt.Sprintf("%s:%s", addr, answer) tracesConfig.TracesCollected.Xray.TcpProxy.BindAddress = newAddr } func chooseBufferSize(tracesConfig *config.Traces) { answer := util.AskWithDefault("Enter Total Buffer Size in MB (minimum 3)", "3") bufferSize, err := strconv.Atoi(answer) if err != nil || bufferSize < 3 { fmt.Println("Buffer size set to 3 because input smaller than 3 or not a number") bufferSize = 3 } tracesConfig.BufferSizeMB = bufferSize } func chooseConcurrency(tracesConfig *config.Traces) { answer := util.AskWithDefault("Enter the maximum number of concurrent calls to AWS X-Ray to upload segment documents: ", "8") concurrency, err := strconv.Atoi(answer) if err != nil || concurrency < 0 { fmt.Println("Concurrency set to default value of 8 because input smaller than 0 or not a number") concurrency = 8 } tracesConfig.Concurrency = concurrency } func chooseRegion(tracesConfig *config.Traces) { answer := util.Ask("Enter the AWS Region to send segments to AWS X-Ray service (Optional)") tracesConfig.RegionOverride = answer } // made to print traces config correctly type TracesWrapper struct { Traces config.Traces `json:"traces"` } func updateUserConfig(tracesConfig *config.Traces) *config.Traces { fieldOptions := generateFieldOptions() for { jsonData := TracesWrapper{ Traces: *tracesConfig, } fmt.Println("Current Traces Configurations:") jsonByte, _ := json.MarshalIndent(jsonData, "", "\t") fmt.Println(string(jsonByte)) fmt.Println("Enter a number of the field you would like to update (or 0 to exit)") for i := 0; i < len(fieldOptions); i++ { fmt.Println(fieldOptions[i]) } answer := util.Ask("") if answer == "" { //Exit if user does not input anything break } option, err := strconv.Atoi(answer) if option < 0 || option > 11 || err != nil { fmt.Println("Please input a number from 0-11") continue } if option == 0 { break } switch option { case 1, 2, 5, 8, 9, 10, 11: newValue := util.Ask("Enter value you would like to update to: (Enter nothing to remove)") updateStringValueInConfig(tracesConfig, option, newValue) case 3, 4: answer := util.Ask("Enter value you would like to update to: (Enter nothing to remove)") newValue, err := strconv.Atoi(answer) if err != nil { fmt.Println("Wrong Input! Input has go be a int.") continue } else { updateIntValueInConfig(tracesConfig, option, newValue) } case 6, 7: answer := util.Ask("Enter value you would like to update to: (Enter nothing to remove)") newValue, err := strconv.ParseBool(answer) if err != nil { fmt.Println("Wrong Input! Input has go be a bool") } else { updateBoolValueInConfig(tracesConfig, option, newValue) } } } return tracesConfig } // array of fields func generateFieldOptions() []string { options := []string{ "0: Keep this configuration and exit", "1: UDP BindAddress", "2: TCP BindAddress", "3: concurrency", "4: buffer_size_mb", "5: resource_arn", "6: local_mode", "7: insecure", "8: role_arn", "9: endpoint_override", "10: region_override", "11: proxy_override", } return options } func updateIntValueInConfig(tracesConfig *config.Traces, option int, value int) error { switch option { case 3: tracesConfig.Concurrency = value case 4: if value < 3 { fmt.Println("Input has to be bigger than 3! Value has been set to 3") tracesConfig.BufferSizeMB = 3 return nil } tracesConfig.BufferSizeMB = value default: return errors.New("Unknown option") } return nil } func updateStringValueInConfig(tracesConfig *config.Traces, option int, value string) error { switch option { case 5: tracesConfig.ResourceArn = value case 9: tracesConfig.EndpointOverride = value case 10: tracesConfig.RegionOverride = value case 11: tracesConfig.ProxyOverride = value case 1: tracesConfig.TracesCollected.Xray.BindAddress = value case 2: tracesConfig.TracesCollected.Xray.TcpProxy.BindAddress = value case 8: if value == "" { tracesConfig.Credentials = nil } else if tracesConfig.Credentials == nil { tracesConfig.Credentials = &struct { RoleArn string `json:"role_arn,omitempty"` }{} tracesConfig.Credentials.RoleArn = value } default: return errors.New("Unknown Option") } return nil } func updateBoolValueInConfig(tracesConfig *config.Traces, option int, value bool) error { switch option { case 6: tracesConfig.LocalMode = value case 7: tracesConfig.Insecure = value default: return errors.New("Unknown Option") } return nil } func getCmdlines(processes []xraydaemonmigration.Process) []string { var cmdlines []string for i := 0; i < len(processes); i++ { curCmdline, err := processes[i].Cmdline() if err != nil { continue } cmdlines = append(cmdlines, curCmdline) } return cmdlines } func generateTracesConfiguration(ctx *runtime.Context) (*config.Traces, error) { var configFilePath string var tracesFile *config.Traces processes, err := xraydaemonmigration.FindAllDaemons() if err != nil { return nil, err } if len(processes) == 0 { yes := util.Yes(anyExistingDaemonConfiguration) if yes { return askUserInput(tracesFile, nil, yes) } else { //user can build config if they do not have a traces file return userBuildsTracesConfig(tracesFile), nil } } var chosenProcess xraydaemonmigration.Process if len(processes) > 1 { cmdlines := getCmdlines(processes) chosenCmdlineIndex := util.ChoiceIndex("Multiple active X-Ray Daemons detected.\nWhich of the configurations would you like to import?", 1, cmdlines) chosenProcess = processes[chosenCmdlineIndex] } else { fmt.Println("Detected X-Ray Daemon. The wizard will now attempt to import its configuration.") chosenProcess = processes[0] } configFilePath, err = xraydaemonmigration.FindConfigFile(chosenProcess) if err != nil { fmt.Println("Ran into error while trying to find Daemon Configurations. Using default traces configuration") err = json.Unmarshal(DefaultTracesConfigFile, &tracesFile) if err != nil { return nil, err } return tracesFile, nil } else if configFilePath == "" { //if user used command line to make configuration tracesFile, err = xraydaemonmigration.ConvertYamlToJson(nil, chosenProcess) if err != nil { fmt.Println("Failed to translate configuration to traces. Using default traces configuration") err = json.Unmarshal(DefaultTracesConfigFile, &tracesFile) if err != nil { return nil, err } } return tracesFile, nil } yamlFile, err := os.ReadFile(configFilePath) //incorrect configFilePath, user can decide to give path of default config will be used if err != nil { fmt.Println("Unable to import configuration from Detected Daemon") yes := util.Yes(anyExistingDaemonConfiguration) return askUserInput(tracesFile, chosenProcess, yes) } return xraydaemonmigration.ConvertYamlToJson(yamlFile, chosenProcess) } func askUserInput(tracesFile *config.Traces, chosenProcess xraydaemonmigration.Process, userHasImportConfig bool) (*config.Traces, error) { if userHasImportConfig { configFilePath := util.Ask(filePathXrayConfigQuestion) yamlFile, err := os.ReadFile(configFilePath) //error reading filepath given by user, using default config if err != nil { fmt.Println("There was an error reading X-Ray Daemon config file. Using default traces configurations") err := json.Unmarshal(DefaultTracesConfigFile, &tracesFile) return tracesFile, err } return xraydaemonmigration.ConvertYamlToJson(yamlFile, chosenProcess) } else { //user does not have exiting file, using default config fmt.Println("Using Default configuration file") err := json.Unmarshal(DefaultTracesConfigFile, &tracesFile) return tracesFile, err } }