// getCmdState reads commandState from given file func getCmdState(log log.T, fileName string) message.CommandState { var commandState message.CommandState err := jsonutil.UnmarshalFile(fileName, &commandState) if err != nil { log.Errorf("encountered error with message %v while reading Interim state of command from file - %v", err, fileName) } else { //logging interim state as read from the file jsonString, err := jsonutil.Marshal(commandState) if err != nil { log.Errorf("encountered error with message %v while marshalling %v to string", err, commandState) } else { log.Tracef("interim CommandState read from file-system - %v", jsonutil.Indent(jsonString)) } } return commandState }
// Config loads the app configuration for amazon-ssm-agent. // If reload is true, it loads the config afresh, // otherwise it returns a previous loaded version, if any. func Config(reload bool) (SsmagentConfig, error) { if reload || !isLoaded() { var agentConfig SsmagentConfig agentConfig = DefaultConfig() path, pathErr := getAppConfigPath() if pathErr != nil { return agentConfig, nil } // Process config override fmt.Printf("Applying config override from %s.\n", path) if err := jsonutil.UnmarshalFile(path, &agentConfig); err != nil { fmt.Println("Failed to unmarshal config override. Fall back to default.") return agentConfig, err } agentConfig.Os.Name = runtime.GOOS agentConfig.Agent.Version = version.Version parser(&agentConfig) cache(agentConfig) } return getCached(), nil }
// processOlderMessages processes older messages that have been persisted in various locations (like from pending & current folders) func (p *Processor) processOlderMessages() { log := p.context.Log() instanceID, err := platform.InstanceID() if err != nil { log.Errorf("error fetching instance id, %v", err) return } //process older messages from PENDING folder unprocessedMsgsLocation := path.Join(appconfig.DefaultDataStorePath, instanceID, appconfig.DefaultCommandRootDirName, appconfig.DefaultLocationOfState, appconfig.DefaultLocationOfPending) if isDirectoryEmpty, _ := fileutil.IsDirEmpty(unprocessedMsgsLocation); isDirectoryEmpty { log.Debugf("No messages to process from %v", unprocessedMsgsLocation) } else { //get all pending messages files, err := ioutil.ReadDir(unprocessedMsgsLocation) //TODO: revisit this when bookkeeping is made invasive if err != nil { log.Errorf("skipping reading pending messages from %v. unexpected error encountered - %v", unprocessedMsgsLocation, err) return } //iterate through all pending messages for _, f := range files { log.Debugf("Processing an older message with messageID - %v", f.Name()) //construct the absolute path - safely assuming that interim state for older messages are already present in Pending folder file := path.Join(appconfig.DefaultDataStorePath, instanceID, appconfig.DefaultCommandRootDirName, appconfig.DefaultLocationOfState, appconfig.DefaultLocationOfPending, f.Name()) var msg ssmmds.Message //parse the message if err := jsonutil.UnmarshalFile(file, &msg); err != nil { log.Errorf("skipping processsing of pending messages. encountered error %v while reading pending message from file - %v", err, f) } else { //call processMessage for every message read from the pending folder //we will end up sending acks again for this message - but that's ok because //unless a message has been deleted - multiple acks are allowed by MDS. //If this becomes an issue - we can stub out ack part from processMessage p.processMessage(&msg) } } } //process older messages from CURRENT folder p.processMessagesFromCurrent(instanceID) return }
// processOlderMessagesFromCurrent processes older messages that were persisted in CURRENT folder func (p *Processor) processMessagesFromCurrent(instanceID string) { log := p.context.Log() config := p.context.AppConfig() unprocessedMsgsLocation := path.Join(appconfig.DefaultDataStorePath, instanceID, appconfig.DefaultCommandRootDirName, appconfig.DefaultLocationOfState, appconfig.DefaultLocationOfCurrent) if isDirectoryEmpty, _ := fileutil.IsDirEmpty(unprocessedMsgsLocation); isDirectoryEmpty { log.Debugf("no older messages to process from %v", unprocessedMsgsLocation) } else { //get all older messages from previous run of agent files, err := ioutil.ReadDir(unprocessedMsgsLocation) //TODO: revisit this when bookkeeping is made invasive if err != nil { log.Errorf("skipping reading inprogress messages from %v. unexpected error encountered - %v", unprocessedMsgsLocation, err) return } //iterate through all old executing messages for _, f := range files { log.Debugf("processing previously unexecuted message - %v", f.Name()) //construct the absolute path - safely assuming that interim state for older messages are already present in Current folder file := path.Join(appconfig.DefaultDataStorePath, instanceID, appconfig.DefaultCommandRootDirName, appconfig.DefaultLocationOfState, appconfig.DefaultLocationOfCurrent, f.Name()) var oldCmdState messageContracts.CommandState //parse the message //TODO: Not all messages in Current folder correspond to SendCommand. if err := jsonutil.UnmarshalFile(file, &oldCmdState); err != nil { log.Errorf("skipping processsing of previously unexecuted messages. encountered error %v while reading unprocessed message from file - %v", err, f) } else { if oldCmdState.DocumentInformation.RunCount >= config.Mds.CommandRetryLimit { //TODO: Move command to corrupt/failed // do not process as the command has failed too many times break } pluginOutputs := make(map[string]*contracts.PluginResult) // increment the command run count oldCmdState.DocumentInformation.RunCount++ // Update reboot status for v := range oldCmdState.PluginsInformation { plugin := oldCmdState.PluginsInformation[v] if plugin.HasExecuted && plugin.Result.Status == contracts.ResultStatusSuccessAndReboot { log.Debugf("plugin %v has completed a reboot. Setting status to Success.", v) plugin.Result.Status = contracts.ResultStatusSuccess oldCmdState.PluginsInformation[v] = plugin pluginOutputs[v] = &plugin.Result p.sendResponse(oldCmdState.DocumentInformation.MessageID, v, pluginOutputs) } } // func PersistData(log log.T, commandID, instanceID, locationFolder string, object interface{}) { commandStateHelper.PersistData(log, oldCmdState.DocumentInformation.CommandID, instanceID, appconfig.DefaultLocationOfCurrent, oldCmdState) //Submit the work to Job Pool so that we don't block for processing of new messages err := p.sendCommandPool.Submit(log, oldCmdState.DocumentInformation.MessageID, func(cancelFlag task.CancelFlag) { p.runCmdsUsingCmdState(p.context.With("[messageID="+oldCmdState.DocumentInformation.MessageID+"]"), p.service, p.pluginRunner, cancelFlag, p.buildReply, p.sendResponse, oldCmdState) }) if err != nil { log.Error("SendCommand failed for previously unexecuted commands", err) return } } } } }
func main() { defer log.Flush() commandPtr := flag.String("c", "", "a command") scriptFilePtr := flag.String("f", "", "a script file") dirPtr := flag.String("d", "", "working directory") bucketNamePtr := flag.String("b", "", "bucket name") keyPrefixPtr := flag.String("k", "", "bucket key prefix") cancelPtr := flag.Bool("cancel", false, "cancel command on key press") typePtr := flag.String("type", "", "instance type (windows, ubuntu or aml)") instanceIDPtr := flag.String("i", "", "instance id") regionPtr := flag.String("r", "us-east-1", "instance region") flag.Parse() var timeout int64 = 10000 timeoutPtr := &timeout var err error err = platform.SetRegion(*regionPtr) if err != nil { log.Error("please specify the region to use.") return } if *commandPtr == "" && *scriptFilePtr == "" { fmt.Println("No commands specified (use either -c or -f).") flag.Usage() return } if *keyPrefixPtr == "" { keyPrefixPtr = nil } if *bucketNamePtr == "" { bucketNamePtr = nil if keyPrefixPtr != nil { defaultBucket := "ec2configservice-ssm-logs" bucketNamePtr = &defaultBucket } } var cc consoleConfig err = jsonutil.UnmarshalFile("integration-cli.json", &cc) if err != nil { log.Error("error parsing consoleConfig ", err) return } // specific instance is provided use only that if *instanceIDPtr != "" { cc.Instances = make(map[string]string) if *typePtr != "" { cc.Instances[*instanceIDPtr] = *typePtr } else { cc.Instances[*instanceIDPtr] = "aml" } } else { // other wise select or filter from the consoleConfig file list if *typePtr != "" { for instanceID, instanceType := range cc.Instances { if instanceType != *typePtr { delete(cc.Instances, instanceID) } } if len(cc.Instances) == 0 { log.Error("no instances of type ", *typePtr) return } } } ssmSvc := ssm.NewService() if ssmSvc == nil { log.Error("couldn't create ssm service.") return } var instanceIDs []string // first get windows instances (bug in SSM if first instance is not win) for instanceID, instanceType := range cc.Instances { if instanceType == "windows" { instanceIDs = append(instanceIDs, instanceID) } } // then get rest of the instances for instanceID, instanceType := range cc.Instances { if instanceType != "windows" { instanceIDs = append(instanceIDs, instanceID) } } docName := "AWS-BETA-RunShellScript" if runtime.GOOS == "windows" { docName = "AWS-RunPowerShellScript" } var commands []string if *commandPtr != "" { commands = []string{*commandPtr} } else { f, err := os.Open(*scriptFilePtr) if err != nil { log.Error(err) return } scanner := bufio.NewScanner(f) for scanner.Scan() { commands = append(commands, scanner.Text()) } } for i := 0; i < len(commands); i++ { // escape backslashes commands[i] = strings.Replace(commands[i], `\`, `\\`, -1) // escape double quotes commands[i] = strings.Replace(commands[i], `"`, `\"`, -1) // escape single quotes // commands[i] = strings.Replace(commands[i], `'`, `\'`, -1) } parameters := map[string][]*string{ "commands": aws.StringSlice(commands), "workingDirectory": aws.StringSlice([]string{*dirPtr}), } log.Infof("Sending command %v", parameters) cmd, err := ssmSvc.SendCommand(log, docName, instanceIDs, parameters, timeoutPtr, bucketNamePtr, keyPrefixPtr) if cmd == nil || cmd.Command == nil || cmd.Command.CommandId == nil { log.Error("command was not created. Aborting!") return } if *cancelPtr { log.Info("Press any key to cancel command") var b = make([]byte, 1) os.Stdin.Read(b) log.Info("Canceling command ") ssmSvc.CancelCommand(log, *cmd.Command.CommandId, instanceIDs) } log.Info("================== Looping for results ================") log.Flush() time.Sleep(1000 * time.Millisecond) for { done := true inst: for instanceID, instanceType := range cc.Instances { descr := fmt.Sprintf("%v [%v]", instanceID, instanceType) out, err := ssmSvc.ListCommandInvocations(log, instanceID, *cmd.Command.CommandId) if err != nil { continue } for _, inv := range out.CommandInvocations { if *inv.Status == "Pending" { log.Infof("Instance %v is in status %v; waiting some more", descr, *inv.Status) done = false continue inst } data, err := json.Marshal(inv) if err != nil { log.Error(err) continue } log.Debug(jsonutil.Indent(string(data))) for _, cp := range inv.CommandPlugins { if cp.Output == nil { log.Errorf("Output Nil for %v", descr) continue } var o interface{} err := json.Unmarshal([]byte(*cp.Output), &o) if err != nil { log.Errorf("error parsing %v\n err=%v", *cp.Output, err) } log.Info(descr, " : ", prettyPrint(o, 0)) } } } if done { break } time.Sleep(3000 * time.Millisecond) } // c.Wait()*/ }