Skip to content

Commit 8186778

Browse files
Add new options flag to create sample configuration
This adds the `--create-ini-template` to the cli allowing users to create a sample configuration for goad in their current working directory.
1 parent 6cd24da commit 8186778

File tree

5 files changed

+192
-41
lines changed

5 files changed

+192
-41
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ Flags:
8282
--output-json=OUTPUT-JSON Optional path to file for JSON result storage
8383
-m, --method="GET" HTTP method
8484
--body=BODY HTTP request body
85+
--create-ini-template create sample configuration file "goad.ini"
86+
in current working directory
8587
-V, --version Show application version.
8688

8789
Args:

cli/cli.go

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package cli
22

33
import (
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+
2747
var (
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
5474
func 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+
75106
func 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 {
186214
func 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
}

cli/config_template.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package cli
2+
3+
const template = `[general]
4+
# The base URL to be loadtested if this is set you don't have to pass it on
5+
# the command-line.
6+
;url = http://example.com
7+
8+
# Number of concurrent lambda functions.
9+
concurrency = 10
10+
11+
# Total count of requests to be executed.
12+
requests = 1000
13+
14+
# Total timelimit for the benchmark to run.
15+
;timelimit = 3600
16+
17+
# timeout for individual http requests
18+
;timeout = 15
19+
20+
# Store output in json
21+
;json-output = result.json
22+
23+
# The HTTP method to be used
24+
;method = GET
25+
26+
# The requst body passed with each requests
27+
;body = Hello world
28+
29+
[regions]
30+
# You can specify various aws region to run lambda functions.
31+
32+
us-east-1 ;N.Virginia
33+
;us-east-2 ;Ohio
34+
;us-west-1 ;N.California
35+
;us-west-2 ;Oregon
36+
eu-west-1 ;Ireland
37+
;eu-central-1 ;Frankfurt
38+
;ap-southeast-1 ;Singapore
39+
;ap-southeast-2 ;Tokio
40+
ap-northeast-1 ;Sydney
41+
;ap-northeast-2 ;Seoul
42+
43+
[headers]
44+
# These headers are used in the HTTP request header
45+
46+
;cache-control: no-cache
47+
;auth-token: YOUR-SECRET-AUTH-TOKEN
48+
;base64-header: dGV4dG8gZGUgcHJ1ZWJhIA==
49+
`

cli/ini_parser_test.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
package cli
22

33
import (
4-
"os"
4+
"bytes"
5+
"io"
6+
"io/ioutil"
57
"sort"
68
"testing"
79

10+
"github.com/goadapp/goad/goad"
811
"github.com/stretchr/testify/assert"
912
)
1013

14+
const testDataFile = "testdata/test-config.ini"
15+
1116
var expectedRegions = []string{"us-east-1", "eu-west-1"}
1217
var expectedHeader = []string{"cache-control: no-cache", "auth-token: YOUR-SECRET-AUTH-TOKEN", "base64-header: dGV4dG8gZGUgcHJ1ZWJhIA=="}
1318

1419
func TestLoadStandardConfig(t *testing.T) {
20+
config := parseSettings(testDataFile)
21+
assertConfigContent(config, t)
22+
}
23+
24+
func assertConfigContent(config *goad.TestConfig, t *testing.T) {
1525
assert := assert.New(t)
16-
delete := createTemporaryConfigFile()
17-
defer delete()
18-
config := parseSettingsFile("goad.ini")
1926
assert.Equal("http://file-config.com/", config.URL, "Should load the URL")
2027
assert.Equal("GET", config.Method, "Should load the request method")
2128
assert.Equal("Hello world", config.Body, "Should load the request body")
@@ -30,10 +37,23 @@ func TestLoadStandardConfig(t *testing.T) {
3037
assert.Equal(expectedHeader, config.Headers, "Should load the output file")
3138
}
3239

33-
func createTemporaryConfigFile() func() {
34-
file := "goad.ini"
35-
os.Link("testdata/test-config.ini", file)
36-
return func() {
37-
os.Remove(file)
40+
func TestSaveConfig(t *testing.T) {
41+
writer := bytes.NewBuffer(make([]byte, 0))
42+
writeConfigStream(writer)
43+
assertMatchTemplate(t, writer)
44+
}
45+
46+
func assertMatchTemplate(t *testing.T, reader io.Reader) {
47+
assert := assert.New(t)
48+
expected := Must(ioutil.ReadFile("testdata/default.ini"))
49+
actual := Must(ioutil.ReadAll(reader))
50+
assert.Equal(len(expected), len(actual), "should be the same amount of bytes")
51+
assert.Equal(expected, actual, "Should exactly be that template")
52+
}
53+
54+
func Must(value []byte, err error) []byte {
55+
if err != nil {
56+
panic(1)
3857
}
58+
return value
3959
}

cli/testdata/default.ini

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
[general]
2+
# The base URL to be loadtested if this is set you don't have to pass it on
3+
# the command-line.
4+
;url = http://example.com
5+
6+
# Number of concurrent lambda functions.
7+
concurrency = 10
8+
9+
# Total count of requests to be executed.
10+
requests = 1000
11+
12+
# Total timelimit for the benchmark to run.
13+
;timelimit = 3600
14+
15+
# timeout for individual http requests
16+
;timeout = 15
17+
18+
# Store output in json
19+
;json-output = result.json
20+
21+
# The HTTP method to be used
22+
;method = GET
23+
24+
# The requst body passed with each requests
25+
;body = Hello world
26+
27+
[regions]
28+
# You can specify various aws region to run lambda functions.
29+
30+
us-east-1 ;N.Virginia
31+
;us-east-2 ;Ohio
32+
;us-west-1 ;N.California
33+
;us-west-2 ;Oregon
34+
eu-west-1 ;Ireland
35+
;eu-central-1 ;Frankfurt
36+
;ap-southeast-1 ;Singapore
37+
;ap-southeast-2 ;Tokio
38+
ap-northeast-1 ;Sydney
39+
;ap-northeast-2 ;Seoul
40+
41+
[headers]
42+
# These headers are used in the HTTP request header
43+
44+
;cache-control: no-cache
45+
;auth-token: YOUR-SECRET-AUTH-TOKEN
46+
;base64-header: dGV4dG8gZGUgcHJ1ZWJhIA==

0 commit comments

Comments
 (0)