Skip to content

Commit d6f2eec

Browse files
committed
Added stat command and migrated to cobra
1 parent eae950f commit d6f2eec

File tree

8 files changed

+404
-247
lines changed

8 files changed

+404
-247
lines changed

Godeps/Godeps.json

Lines changed: 10 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,28 @@ We use [godep](https://github.com/tools/godep) for dependencies management so en
99
3. Build as usually: ```$ go build```
1010

1111
## Running
12-
When run without arguments **sctl** tries to read **input.json** file in current directory and outputs XML files to the same directory:
12+
## Generating quota files
13+
Use **sctl generate** command. When run without arguments it tries to read **input.json** file in current directory and outputs XML files to the same directory:
1314
```
14-
$ sctl
15+
$ sctl generate
1516
```
1617
You may want to adjust input file and output directory like the following:
1718
```
18-
$ sctl --inputFile /path/to/input.json --outputDirectory /path/to/output/directory
19+
$ sctl generate --inputFile /path/to/input.json --outputDirectory /path/to/output/directory
1920
```
2021
If you want to view what will be outputted without actually creating XML files use "dry run" mode:
2122
```
22-
$ sctl --dryRun
23+
$ sctl generate --dryRun
24+
```
25+
26+
## Showing quota statistics
27+
Use **sctl stat** command:
28+
```
29+
$ sctl stat --inputFile /path/to/input.json
30+
```
31+
Additionally you can only show information for one quota:
32+
```
33+
$ sctl stat --inputFile /path/to/input.json --quotaName test-quota
2334
```
2435

2536
## Input file format

cmd/data.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package cmd
2+
3+
import "encoding/xml"
4+
5+
// Input data
6+
7+
type JsonHost struct {
8+
Port int `json:"port"`
9+
Count int `json:"count"`
10+
}
11+
12+
type JsonRegion map[string] JsonHost
13+
14+
type JsonRegions map[string]JsonRegion
15+
16+
type JsonHosts map[string]JsonRegions
17+
18+
type JsonVersions map[string] string
19+
20+
type JsonBrowser struct {
21+
DefaultVersion string `json:"defaultVersion"`
22+
Versions JsonVersions `json:"versions"`
23+
}
24+
25+
type JsonQuota map[string]JsonBrowser
26+
27+
type JsonInput struct {
28+
Hosts JsonHosts `json:"hosts"`
29+
Quota map[string]JsonQuota `json:"quota"`
30+
Aliases map[string] []string `json:"aliases"`
31+
}
32+
33+
// Output data
34+
35+
type XmlBrowsers struct {
36+
XMLName xml.Name `xml:"qa:browsers"`
37+
XmlNS string `xml:"xmlns:qa,attr"`
38+
Browsers []XmlBrowser `xml:"browser"`
39+
}
40+
41+
type XmlBrowser struct {
42+
Name string `xml:"name,attr"`
43+
DefaultVersion string `xml:"defaultVersion,attr"`
44+
Versions []XmlVersion `xml:"version"`
45+
}
46+
47+
type XmlVersion struct {
48+
Number string `xml:"number,attr"`
49+
Regions []XmlRegion `xml:"region"`
50+
}
51+
52+
type XmlHosts []XmlHost
53+
54+
type XmlRegion struct {
55+
Name string `xml:"name,attr"`
56+
Hosts XmlHosts `xml:"host"`
57+
}
58+
59+
type XmlHost struct {
60+
Name string `xml:"name,attr"`
61+
Port int `xml:"port,attr"`
62+
Count int `xml:"count,attr"`
63+
}
64+

cmd/generate.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"encoding/json"
6+
"fmt"
7+
"encoding/xml"
8+
"path"
9+
"io/ioutil"
10+
"regexp"
11+
"os"
12+
"errors"
13+
"strconv"
14+
"sort"
15+
)
16+
17+
const (
18+
fileMode = 0644
19+
)
20+
21+
var (
22+
outputDirectory string
23+
dryRun bool
24+
)
25+
26+
var generateCmd = &cobra.Command{
27+
Use: "generate",
28+
Short: "Generate XML quota using JSON input file",
29+
Run: func(cmd *cobra.Command, args []string) {
30+
input, err := parseInputFile(inputFilePath)
31+
if (err != nil) {
32+
fmt.Println(err.Error())
33+
os.Exit(1)
34+
}
35+
files := convert(*input)
36+
names := []string{}
37+
for name := range files {
38+
names = append(names, name)
39+
}
40+
sort.Strings(names)
41+
for _, name := range names {
42+
if err := output(name, files[name], outputDirectory); err != nil {
43+
fmt.Println(err.Error())
44+
os.Exit(1)
45+
}
46+
}
47+
},
48+
}
49+
50+
func init() {
51+
initCommonFlags(generateCmd)
52+
generateCmd.PersistentFlags().StringVar(&outputDirectory, "outputDirectory", ".", "output directory")
53+
generateCmd.PersistentFlags().BoolVar(&dryRun, "dryRun", false, "whether to send output to stdout instead of writing files")
54+
}
55+
56+
func convert(input JsonInput) map[string] XmlBrowsers {
57+
ret := make(map[string] XmlBrowsers)
58+
hostsMap := input.Hosts
59+
quotaMap := input.Quota
60+
aliasesMap := input.Aliases
61+
for quotaName, quota := range quotaMap {
62+
ret[quotaName] = createQuota(quotaName, hostsMap, quota)
63+
}
64+
for quotaName, aliases := range aliasesMap {
65+
if _, ok := ret[quotaName]; ok {
66+
for _, alias := range aliases {
67+
ret[alias] = ret[quotaName]
68+
}
69+
} else {
70+
fmt.Printf("Missing reference quota %s\n", quotaName)
71+
os.Exit(1)
72+
}
73+
}
74+
return ret
75+
}
76+
77+
func createQuota(quotaName string, hostsMap JsonHosts, quota JsonQuota) XmlBrowsers {
78+
browsers := []XmlBrowser{}
79+
for browserName, browser := range quota {
80+
xmlVersions := []XmlVersion{}
81+
for versionName, hostsRef := range browser.Versions {
82+
regions := hostsMap[hostsRef]
83+
if (regions != nil) {
84+
xmlVersion := XmlVersion{
85+
Number: versionName,
86+
Regions: jsonRegionsToXmlRegions(regions),
87+
}
88+
xmlVersions = append(xmlVersions, xmlVersion)
89+
} else {
90+
fmt.Printf("Missing host reference %s for browser %s:%s:%s\n", hostsRef, quotaName, browserName, versionName)
91+
os.Exit(1)
92+
}
93+
}
94+
xmlBrowser := XmlBrowser{
95+
Name: browserName,
96+
DefaultVersion: browser.DefaultVersion,
97+
Versions: xmlVersions,
98+
}
99+
browsers = append(browsers, xmlBrowser)
100+
}
101+
return XmlBrowsers{
102+
Browsers: browsers,
103+
XmlNS: "urn:config.gridrouter.qatools.ru",
104+
}
105+
}
106+
107+
func jsonRegionsToXmlRegions(regions JsonRegions) []XmlRegion {
108+
xmlRegions := []XmlRegion{}
109+
for regionName, region := range regions {
110+
xmlHosts := XmlHosts{}
111+
for hostPattern, host := range region {
112+
hostNames := parseHostPattern(hostPattern)
113+
for _, hostName := range hostNames {
114+
xmlHosts = append(xmlHosts, XmlHost{
115+
Name: hostName,
116+
Port: host.Port,
117+
Count: host.Count,
118+
})
119+
}
120+
}
121+
xmlRegions = append(xmlRegions, XmlRegion{
122+
Name: regionName,
123+
Hosts: xmlHosts,
124+
})
125+
}
126+
return xmlRegions
127+
}
128+
129+
func parseInputFile(filePath string) (*JsonInput, error) {
130+
bytes, err := ioutil.ReadFile(filePath)
131+
if err != nil {
132+
return nil, errors.New(fmt.Sprintf("error reading input file [%s]: %v", filePath, err))
133+
}
134+
input := new(JsonInput)
135+
if err := json.Unmarshal(bytes, input); err != nil {
136+
return nil, errors.New(fmt.Sprintf("error parsing input file [%s]: %v", filePath, err))
137+
}
138+
return input, nil
139+
}
140+
141+
func marshalBrowsers(browsers XmlBrowsers) ([]byte, error) {
142+
return xml.MarshalIndent(browsers, "", " ")
143+
}
144+
145+
func output(quotaName string, browsers XmlBrowsers, outputDirectory string) error {
146+
filePath := path.Join(outputDirectory, quotaName + ".xml")
147+
if (dryRun) {
148+
return printOutputFile(filePath, browsers)
149+
} else {
150+
return saveOutputFile(filePath, browsers)
151+
}
152+
}
153+
154+
func printOutputFile(filePath string, browsers XmlBrowsers) error {
155+
bytes, err := marshalBrowsers(browsers)
156+
if (err != nil) {
157+
return err
158+
}
159+
fmt.Println(filePath)
160+
fmt.Println("---")
161+
fmt.Println(string(bytes))
162+
fmt.Println("---")
163+
return nil
164+
}
165+
166+
func saveOutputFile(filePath string, browsers XmlBrowsers) error {
167+
bytes, err := marshalBrowsers(browsers)
168+
if (err != nil) {
169+
return err
170+
}
171+
if err := ioutil.WriteFile(filePath, bytes, fileMode); err != nil {
172+
return errors.New(fmt.Sprintf("error saving to output file [%s]: %v", filePath, err))
173+
}
174+
return nil
175+
}
176+
177+
//Only one [1:10] pattern can be included in host pattern
178+
func parseHostPattern(pattern string) []string {
179+
re := regexp.MustCompile("(.*)\\[(\\d+):(\\d+)\\](.*)")
180+
pieces := re.FindStringSubmatch(pattern)
181+
if len(pieces) == 5 {
182+
head := pieces[1]
183+
from, _ := strconv.Atoi(pieces[2])
184+
to, _ := strconv.Atoi(pieces[3])
185+
tail := pieces[4]
186+
if (from <= to) {
187+
ret := []string{}
188+
for i := from; i <= to; i++ {
189+
ret = append(ret, fmt.Sprintf("%s%d%s", head, i, tail))
190+
}
191+
return ret
192+
}
193+
}
194+
return []string{pattern}
195+
}

sctl_test.go renamed to cmd/generate_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
package main
1+
package cmd
22

33
import (
44
"testing"
55
. "github.com/aandryashin/matchers"
66
)
77

88
func TestParseInputFile(t *testing.T) {
9-
input, err := parseInputFile("test-data/input.json")
9+
input, err := parseInputFile("../test-data/input.json")
1010
AssertThat(t, err, Is{nil})
1111
AssertThat(t, len(input.Hosts), EqualTo{1})
1212
AssertThat(t, len(input.Quota), EqualTo{1})
@@ -20,7 +20,7 @@ func TestParseHostPattern(t *testing.T) {
2020
}
2121

2222
func TestConvert(t *testing.T) {
23-
input, _ := parseInputFile("test-data/input.json")
23+
input, _ := parseInputFile("../test-data/input.json")
2424
output := convert(*input)
2525
_, containsKey := output["test-quota"]
2626
AssertThat(t, containsKey, Is{true})

cmd/sctl.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"os"
6+
)
7+
8+
var (
9+
inputFilePath string
10+
sctlCmd = &cobra.Command{
11+
Use: "sctl",
12+
Short: "sctl is a Selenium configuration management tool",
13+
RunE: func(cmd *cobra.Command, args []string) error {
14+
return cmd.Usage()
15+
},
16+
}
17+
)
18+
19+
func Execute() {
20+
sctlCmd.AddCommand(generateCmd)
21+
sctlCmd.AddCommand(statCmd)
22+
23+
if _, err := sctlCmd.ExecuteC(); err != nil {
24+
os.Exit(1)
25+
}
26+
}
27+
28+
func initCommonFlags(cmd *cobra.Command) {
29+
cmd.PersistentFlags().StringVar(&inputFilePath, "inputFile", "input.json", "path to input file")
30+
}

0 commit comments

Comments
 (0)