11package cli
22
33import (
4+ "bytes"
45 "encoding/json"
56 "fmt"
7+ "io"
68 "io/ioutil"
79 "math"
810 "os"
@@ -24,33 +26,51 @@ import (
2426 "github.com/nsf/termbox-go"
2527)
2628
29+ const (
30+ iniFile = "goad.ini"
31+ coldef = termbox .ColorDefault
32+ nano = 1000000000
33+ general = "general"
34+ urlKey = "url"
35+ methodKey = "method"
36+ bodyKey = "body"
37+ concurrencyKey = "concurrency"
38+ requestsKey = "requests"
39+ timelimitKey = "timelimit"
40+ timeoutKey = "timeout"
41+ jsonOutputKey = "json-output"
42+ headerKey = "header"
43+ regionKey = "region"
44+ writeIniKey = "create-ini-template"
45+ )
46+
2747var (
2848 app = kingpin .New ("goad" , "An AWS Lambda powered load testing tool" )
29- urlArg = app .Arg ("url" , "[http[s]://]hostname[:port]/path optional if defined in goad.ini" )
49+ urlArg = app .Arg (urlKey , "[http[s]://]hostname[:port]/path optional if defined in goad.ini" )
3050 url = urlArg .String ()
31- requestsFlag = app .Flag ("requests" , "Number of requests to perform. Set to 0 in combination with a specified timelimit allows for unlimited requests for the specified time." ).Short ('n' ).Default ("1000" )
51+ requestsFlag = app .Flag (requestsKey , "Number of requests to perform. Set to 0 in combination with a specified timelimit allows for unlimited requests for the specified time." ).Short ('n' ).Default ("1000" )
3252 requests = requestsFlag .Int ()
33- concurrencyFlag = app .Flag ("concurrency" , "Number of multiple requests to make at a time" ).Short ('c' ).Default ("10" )
53+ concurrencyFlag = app .Flag (concurrencyKey , "Number of multiple requests to make at a time" ).Short ('c' ).Default ("10" )
3454 concurrency = concurrencyFlag .Int ()
35- timelimitFlag = app .Flag ("timelimit" , "Seconds to max. to spend on benchmarking" ).Short ('t' ).Default ("3600" )
55+ timelimitFlag = app .Flag (timelimitKey , "Seconds to max. to spend on benchmarking" ).Short ('t' ).Default ("3600" )
3656 timelimit = timelimitFlag .Int ()
37- timeoutFlag = app .Flag ("timeout" , "Seconds to max. wait for each response" ).Short ('s' ).Default ("15" )
57+ timeoutFlag = app .Flag (timeoutKey , "Seconds to max. wait for each response" ).Short ('s' ).Default ("15" )
3858 timeout = timeoutFlag .Int ()
39- headersFlag = app .Flag ("header" , "Add Arbitrary header line, eg. 'Accept-Encoding: gzip' (repeatable)" ).Short ('H' )
59+ headersFlag = app .Flag (headerKey , "Add Arbitrary header line, eg. 'Accept-Encoding: gzip' (repeatable)" ).Short ('H' )
4060 headers = headersFlag .Strings ()
41- regionsFlag = app .Flag ("region" , "AWS regions to run in. Repeat flag to run in more then one region. (repeatable)" )
61+ regionsFlag = app .Flag (regionKey , "AWS regions to run in. Repeat flag to run in more then one region. (repeatable)" )
4262 regions = regionsFlag .Strings ()
43- outputFileFlag = app .Flag ("output-json" , "Optional path to file for JSON result storage" )
63+ outputFileFlag = app .Flag (jsonOutputKey , "Optional path to file for JSON result storage" )
4464 outputFile = outputFileFlag .String ()
45- methodFlag = app .Flag ("method" , "HTTP method" ).Short ('m' ).Default ("GET" )
65+ methodFlag = app .Flag (methodKey , "HTTP method" ).Short ('m' ).Default ("GET" )
4666 method = methodFlag .String ()
47- bodyFlag = app .Flag ("body" , "HTTP request body" )
67+ bodyFlag = app .Flag (bodyKey , "HTTP request body" )
4868 body = bodyFlag .String ()
69+ writeIniFlag = app .Flag (writeIniKey , "create sample configuration file \" " + iniFile + "\" in current working directory" )
70+ writeIni = writeIniFlag .Bool ()
4971)
5072
51- const coldef = termbox .ColorDefault
52- const nano = 1000000000
53-
73+ // Run the goad cli
5474func Run () {
5575 app .HelpFlag .Short ('h' )
5676 app .Version (version .String ())
@@ -72,8 +92,19 @@ func Run() {
7292 start (test , & finalResult , sigChan )
7393}
7494
95+ func writeIniFile () {
96+ stream := bytes .NewBuffer (make ([]byte , 0 ))
97+ writeConfigStream (stream )
98+ ioutil .WriteFile (iniFile , stream .Bytes (), 0644 )
99+ }
100+
101+ func writeConfigStream (writer io.Writer ) {
102+ stream := bytes .NewBufferString (template )
103+ stream .WriteTo (writer )
104+ }
105+
75106func aggregateConfiguration () * goad.TestConfig {
76- config := parseSettingsFile ( "goad.ini" )
107+ config := parseSettings ( iniFile )
77108 applyDefaultsFromConfig (config )
78109 return parseCommandline ()
79110}
@@ -88,9 +119,7 @@ func applyDefaultsFromConfig(config *goad.TestConfig) {
88119 applyDefaultIfNotZero (requestsFlag , prepareInt (config .Requests ))
89120 applyDefaultIfNotZero (timelimitFlag , prepareInt (config .Timelimit ))
90121 applyDefaultIfNotZero (timeoutFlag , prepareInt (config .Timeout ))
91- if config .URL == "" {
92- urlArg .Required ()
93- } else {
122+ if config .URL != "" {
94123 urlArg .Default (config .URL )
95124 }
96125 if len (config .Regions ) == 0 {
@@ -147,31 +176,30 @@ func isZero(v reflect.Value) bool {
147176 return v .Interface () == z .Interface ()
148177}
149178
150- func parseSettingsFile ( file string ) * goad.TestConfig {
179+ func parseSettings ( source interface {} ) * goad.TestConfig {
151180 config := & goad.TestConfig {}
152- cfg , err := ini .LoadSources (ini.LoadOptions {AllowBooleanKeys : true }, "goad.ini" )
181+ cfg , err := ini .LoadSources (ini.LoadOptions {AllowBooleanKeys : true }, source )
153182 if err != nil {
154183 if ! os .IsNotExist (err ) {
155184 fmt .Println (err .Error ())
156185 }
157186 return config
158187 }
159- generalSection := cfg .Section ("general" )
160- config .URL = generalSection .Key ("url" ).String ()
161- config .Method = generalSection .Key ("method" ).String ()
162- config .Body = generalSection .Key ("body" ).String ()
163- config .Concurrency , _ = generalSection .Key ("concurrency" ).Int ()
164- config .Requests , _ = generalSection .Key ("requests" ).Int ()
165- config .Timelimit , _ = generalSection .Key ("timelimit" ).Int ()
166- config .Timeout , _ = generalSection .Key ("timeout" ).Int ()
167- config .Output = generalSection .Key ("json-output" ).String ()
168-
169188 regionsSection := cfg .Section ("regions" )
170189 config .Regions = regionsSection .KeyStrings ()
171190
172191 headersSection := cfg .Section ("headers" )
173192 headerHash := headersSection .KeysHash ()
174193 config .Headers = foldHeaders (headerHash )
194+ generalSection := cfg .Section (general )
195+ config .URL = generalSection .Key (urlKey ).String ()
196+ config .Method = generalSection .Key (methodKey ).String ()
197+ config .Body = generalSection .Key (bodyKey ).String ()
198+ config .Concurrency , _ = generalSection .Key (concurrencyKey ).Int ()
199+ config .Requests , _ = generalSection .Key (requestsKey ).Int ()
200+ config .Timelimit , _ = generalSection .Key (timelimitKey ).Int ()
201+ config .Timeout , _ = generalSection .Key (timeoutKey ).Int ()
202+ config .Output = generalSection .Key (jsonOutputKey ).String ()
175203 return config
176204}
177205
@@ -186,9 +214,15 @@ func foldHeaders(hash map[string]string) []string {
186214func parseCommandline () * goad.TestConfig {
187215 args := os .Args [1 :]
188216
189- _ , err := app .Parse (args )
190- if err != nil {
191- fmt .Println (err .Error ())
217+ app .Parse (args )
218+ if * writeIni {
219+ writeIniFile ()
220+ fmt .Printf ("Sample configuration written to: %s\n " , iniFile )
221+ os .Exit (0 )
222+ }
223+
224+ if * url == "" {
225+ fmt .Println ("No URL provided" )
192226 app .Usage (args )
193227 os .Exit (1 )
194228 }
0 commit comments