GeezerVison is showing github.com — feat: add CLI base construct and validation command · getstackhead/stackhead@4faec4f
This repository was archived by the owner on Feb 26, 2024. It is now read-only.
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +44Lines changed: 44 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +name: Test CLI | | 2 | + | | 3 | +on: | | 4 | + pull_request: | | 5 | + types: [opened, labeled, unlabeled, synchronize] | | 6 | + branches: | | 7 | + - master | | 8 | + - next | | 9 | + paths: | | 10 | + - cli/** | | 11 | + - .github/workflows/test-cli.yml | | 12 | + | | 13 | +jobs: | | 14 | + | | 15 | + build: | | 16 | + name: Build | | 17 | + runs-on: ubuntu-latest | | 18 | + steps: | | 19 | + | | 20 | + - name: Set up Go 1.13 | | 21 | + uses: actions/setup-go@v1 | | 22 | + with: | | 23 | + go-version: 1.13 | | 24 | + id: go | | 25 | + | | 26 | + - name: Check out code into the Go module directory | | 27 | + uses: actions/checkout@v2 | | 28 | + | | 29 | + - name: Get dependencies | | 30 | + working-directory: cli | | 31 | + run: | | | 32 | + go get -v -t -d ./... | | 33 | + if [ -f Gopkg.toml ]; then | | 34 | + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh | | 35 | + dep ensure | | 36 | + fi | | 37 | +
| | 38 | + - name: Build | | 39 | + working-directory: cli | | 40 | + run: go build -v -o bin/stackhead-cli . | | 41 | + | | 42 | + - name: Test | | 43 | + working-directory: cli | | 44 | + run: go test ./... |
Collapse file Copy file name to clipboard +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +cli | | 2 | +.stackhead-cli.yml |
Collapse file Copy file name to clipboard +14Lines changed: 14 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +``` |
Collapse file Copy file name to clipboard +55Lines changed: 55 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +} |
Collapse file Copy file name to clipboard +20Lines changed: 20 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +} |
Collapse file Copy file name to clipboard +46Lines changed: 46 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +} |
Collapse file Copy file name to clipboard +88Lines changed: 88 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +} |
Collapse file Copy file name to clipboard +35Lines changed: 35 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +} |
Collapse file Copy file name to clipboard +13Lines changed: 13 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +) |
|