Skip to content
This repository was archived by the owner on Sep 18, 2021. It is now read-only.

Commit 79a1236

Browse files
committed
feat: add CLI base construct and validation command
0 parents  commit 79a1236

File tree

14 files changed

+928
-0
lines changed

14 files changed

+928
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cli
2+
.stackhead-cli.yml

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# StackHead CLI
2+
3+
## Configuration file
4+
5+
.stackhead-cli.yml in current directory or $HOME
6+
```yaml
7+
---
8+
ansible:
9+
collection_path: /my/collection/path # optional
10+
software:
11+
webserver: nginx
12+
container: docker
13+
14+
```

ansible/collection.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package ansible
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
homedir "github.com/mitchellh/go-homedir"
10+
"github.com/spf13/viper"
11+
)
12+
13+
func GetCollectionDirs() ([]string, error) {
14+
var customCollectionPath = viper.GetString("ansible.collection_path")
15+
if customCollectionPath != "" {
16+
absPath, err := filepath.Abs(customCollectionPath)
17+
if err != nil {
18+
return nil, err
19+
}
20+
return []string{absPath}, nil
21+
}
22+
23+
homeDir, err := homedir.Dir()
24+
if err != nil {
25+
fmt.Println(err)
26+
os.Exit(1)
27+
}
28+
29+
var pathEnv = os.Getenv("COLLECTIONS_PATHS")
30+
var pathList []string
31+
if pathEnv == "" {
32+
pathList = []string{homeDir + "/.ansible/collections/ansible_collections"}
33+
} else {
34+
pathList = strings.Split(pathEnv, ":")
35+
if len(pathList) == 0 {
36+
pathList = []string{homeDir + "/.ansible/collections/ansible_collections"}
37+
}
38+
}
39+
return pathList, nil
40+
}
41+
42+
func GetStackHeadCollectionLocation() (string, error) {
43+
collectionDirs, err := GetCollectionDirs()
44+
if err != nil {
45+
return "", err
46+
}
47+
// Look for Ansible directory
48+
for _, singlePath := range collectionDirs {
49+
var installPath = filepath.Join(singlePath, "getstackhead", "stackhead")
50+
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
51+
return installPath, nil
52+
}
53+
}
54+
return "", fmt.Errorf("unable to find StackHead Ansible collection ")
55+
}

commands/init.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package commands
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/getstackhead/stackhead/cli/commands/init"
7+
"github.com/getstackhead/stackhead/cli/routines"
8+
)
9+
10+
var Init = &cobra.Command{
11+
Use: "init",
12+
Short: "Install StackHead dependencies according to configuration file",
13+
Long: `init will install all required dependencies according to configuration file.
14+
If no configuration file exists, it will start a wizard to create one.`,
15+
Run: func(cmd *cobra.Command, args []string) {
16+
17+
routines.RunTask(commands_init.InstallCollection...)
18+
routines.RunTask(commands_init.InstallModules...)
19+
},
20+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package commands_init
2+
3+
import (
4+
"sync"
5+
6+
"github.com/getstackhead/stackhead/cli/ansible"
7+
"github.com/getstackhead/stackhead/cli/routines"
8+
)
9+
10+
func installCollection() error {
11+
return routines.ExecAnsibleGalaxy(
12+
"collection", "install", "git+https://github.com/getstackhead/stackhead.git",
13+
)
14+
}
15+
16+
func installCollectionDependencies() error {
17+
collectionDir, err := ansible.GetStackHeadCollectionLocation()
18+
if err != nil {
19+
return err
20+
}
21+
return routines.ExecAnsibleGalaxy(
22+
"install",
23+
"-r", collectionDir+"/requirements/requirements.yml",
24+
)
25+
}
26+
27+
var InstallCollection = []routines.TaskOption{
28+
routines.Text("Installing StackHead Ansible collection"),
29+
routines.Execute(func(wg *sync.WaitGroup, result chan routines.TaskResult) {
30+
defer wg.Done()
31+
32+
err := installCollection()
33+
if err == nil {
34+
err = installCollectionDependencies()
35+
}
36+
37+
taskResult := routines.TaskResult{
38+
Error: err != nil,
39+
}
40+
if err != nil {
41+
taskResult.Message = err.Error()
42+
}
43+
44+
result <- taskResult
45+
}),
46+
}

commands/init/install_modules.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package commands_init
2+
3+
import (
4+
"fmt"
5+
"github.com/spf13/viper"
6+
"strings"
7+
"sync"
8+
9+
"github.com/getstackhead/stackhead/cli/routines"
10+
"github.com/getstackhead/stackhead/cli/stackhead"
11+
)
12+
13+
func collectModules() []string {
14+
var modules []string
15+
var err error
16+
17+
var webserver = viper.GetString("modules.webserver")
18+
if len(webserver) == 0 {
19+
webserver = "getstackhead.stackhead_webserver_nginx"
20+
}
21+
webserver, err = stackhead.AutoCompleteModuleName(webserver, stackhead.ModuleWebserver)
22+
if err != nil {
23+
// error
24+
}
25+
modules = append(modules, webserver)
26+
27+
var container = viper.GetString("modules.container")
28+
if len(container) == 0 {
29+
container = "getstackhead.stackhead_container_docker"
30+
}
31+
container, err = stackhead.AutoCompleteModuleName(container, stackhead.ModuleContainer)
32+
if err != nil {
33+
// error
34+
}
35+
36+
modules = append(modules, container)
37+
return modules
38+
}
39+
40+
func installStackHeadModules() error {
41+
var modules = collectModules()
42+
43+
var wg sync.WaitGroup
44+
wg.Add(len(modules))
45+
allErrorsChan := make(chan routines.TaskResult, len(modules))
46+
for _, moduleName := range modules {
47+
go func(name string) {
48+
defer wg.Done()
49+
err := routines.ExecAnsibleGalaxy("install", name)
50+
if err != nil {
51+
allErrorsChan <- routines.TaskResult{
52+
Name: name,
53+
Message: err.Error(),
54+
Error: true,
55+
}
56+
}
57+
}(moduleName)
58+
}
59+
wg.Wait()
60+
close(allErrorsChan)
61+
62+
if len(allErrorsChan) > 0 {
63+
var readableErrors []string
64+
for err := range allErrorsChan {
65+
readableErrors = append(readableErrors, fmt.Sprintf("- %s: %s ", err.Name, err.Message))
66+
}
67+
return fmt.Errorf("The following errors occurred while installing StackHead modules:\n%s", strings.Join(readableErrors, "\n"))
68+
}
69+
return nil
70+
}
71+
72+
var InstallModules = []routines.TaskOption{
73+
routines.Text("Installing StackHead modules"),
74+
routines.Execute(func(wg *sync.WaitGroup, result chan routines.TaskResult) {
75+
defer wg.Done()
76+
77+
err := installStackHeadModules()
78+
79+
taskResult := routines.TaskResult{
80+
Error: err != nil,
81+
}
82+
if err != nil {
83+
taskResult.Message = err.Error()
84+
}
85+
86+
result <- taskResult
87+
}),
88+
}

commands/validate.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/getstackhead/stackhead/cli/jsonschema"
10+
)
11+
12+
var Validate = &cobra.Command{
13+
Use: "validate [path to project definition file]",
14+
Short: "Validate a project definition file",
15+
Long: `validate is used to make sure your project definition file meets the StackHead project definition syntax.`,
16+
Args: cobra.ExactArgs(1),
17+
Run: func(cmd *cobra.Command, args []string) {
18+
result, err := jsonschema.ValidateFile(args[0])
19+
20+
if err != nil {
21+
panic(err.Error())
22+
}
23+
24+
errorMessage := jsonschema.ShouldValidate(result)
25+
if len(errorMessage) == 0 {
26+
_, err = fmt.Fprintln(os.Stdout, "The project definition is valid")
27+
} else {
28+
_, err = fmt.Fprintln(os.Stderr, errorMessage)
29+
os.Exit(1)
30+
}
31+
if err != nil {
32+
panic(err.Error())
33+
}
34+
},
35+
}

go.mod

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module github.com/getstackhead/stackhead/cli
2+
3+
go 1.13
4+
5+
require (
6+
github.com/briandowns/spinner v1.11.1
7+
github.com/ghodss/yaml v1.0.0
8+
github.com/mitchellh/go-homedir v1.1.0
9+
github.com/smartystreets/goconvey v1.6.4
10+
github.com/spf13/cobra v1.0.0
11+
github.com/spf13/viper v1.7.1
12+
github.com/xeipuuv/gojsonschema v1.2.0
13+
)

0 commit comments

Comments
 (0)