// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, // either express or implied. See the License for the specific language governing // permissions and limitations under the License. package main import ( "bufio" "encoding/json" "flag" "fmt" "os" "runtime" "strings" "time" "github.com/aws/amazon-ssm-agent/agent/appconfig" "github.com/aws/amazon-ssm-agent/agent/context" "github.com/aws/amazon-ssm-agent/agent/jsonutil" logger "github.com/aws/amazon-ssm-agent/agent/log" logger2 "github.com/aws/amazon-ssm-agent/agent/log/logger" "github.com/aws/amazon-ssm-agent/agent/ssm" "github.com/aws/amazon-ssm-agent/common/identity/identity" "github.com/aws/aws-sdk-go/aws" ) var log logger.T func init() { log = logger2.DefaultLogger() defer log.Flush() } type consoleConfig struct { Instances map[string]string } func main() { defer log.Close() 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 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 } } } config, err := appconfig.Config(false) if err != nil { log.Warnf("appconfig could not be loaded, using default config - %v", err) } selector := identity.NewInstanceIDRegionAgentIdentitySelector(log, *instanceIDPtr, *regionPtr) agentIdentity, err := identity.NewAgentIdentity(log, &config, selector) if err != nil { log.Errorf("Failed to find agent identity: %v", err) return } context := context.Default(log, config, agentIdentity).With("[agentconsole]") ssmSvc := ssm.NewService(context) 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 } defer f.Close() 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()*/ } func indented(s string, indentLevel int) (res string) { for i := 0; i < indentLevel; i++ { res += " " } return res + s } func prettyPrint(input interface{}, indentLevel int) (res string) { switch input := input.(type) { case string: return input case []interface{}: if len(input) == 0 { return "[]" } // res = "[\n" + indented("", indentLevel+1) for _, v := range input { res += prettyPrint(v, indentLevel+1) + "\n" } // res += indented("]", indentLevel) return res case map[string]interface{}: if len(input) == 0 { return "{}" } // res = "{\n" res = "\n" for k, v := range input { res += indented("", indentLevel+1) prettyV := prettyPrint(v, indentLevel+1) if k == "Stdout" { prettyV = outText(prettyV) } if k == "Stderr" { prettyV = errText(prettyV) } if k == "Error" { prettyV = errText(prettyV) } res += fmt.Sprintf("%v : %v\n", k, prettyV) } // res += indented("}", indentLevel) return res case nil: return "" default: log.Errorf("unexpected object %v of type %T", input, input) return "" } } // Colors In Terminal const ( //Clr0 Colors In Terminal Clr0 = "\x1b[30;1m" ClrR = "\x1b[31;1m" ClrG = "\x1b[32;1m" ClrY = "\x1b[33;1m" ClrB = "\x1b[34;1m" ClrM = "\x1b[35;1m" ClrC = "\x1b[36;1m" ClrW = "\x1b[37;1m" ClrN = "\x1b[0m" ) func outText(text string) (res string) { if runtime.GOOS == "linux" { return fmt.Sprintf("%s%s%s\n", ClrB, text, ClrN) } return text } func errText(text string) string { if runtime.GOOS == "linux" { return fmt.Sprintf("%s%s%s\n", ClrR, text, ClrN) } return text }