Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ TBD
$ git clone git@github.com:commitdev/zero.git
$ cd zero && make
```
#### Running the tool locally

To install the CLI into your GOPATH and test it, run:
```
$ make install-go
$ zero --help
```



## Planning and Process

Expand Down
50 changes: 4 additions & 46 deletions cmd/apply.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package cmd

import (
"fmt"
"log"

"github.com/commitdev/zero/internal/apply"
"github.com/commitdev/zero/internal/config/projectconfig"
"github.com/commitdev/zero/internal/constants"
"github.com/commitdev/zero/pkg/util/exit"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
)

Expand All @@ -24,46 +21,7 @@ var applyCmd = &cobra.Command{
Use: "apply",
Short: "Execute modules to create projects, infrastructure, etc.",
Run: func(cmd *cobra.Command, args []string) {

if len(applyEnvironments) == 0 {
fmt.Println(`Choose the environments to apply. This will create infrastructure, CI pipelines, etc.
At this point, real things will be generated that may cost money!
Only a single environment may be suitable for an initial test, but for a real system we suggest setting up both staging and production environments.`)
applyEnvironments = promptEnvironments()
}

// Strict for now, we can brainstorm how much we want to support custom environments later
for _, env := range applyEnvironments {
if env != "staging" && env != "production" {
exit.Fatal("The currently supported environments are \"staging\" and \"production\"")
}
}

// @TODO : Pass environments to make commands
// @TODO rootdir?
apply.Apply(projectconfig.RootDir, applyConfigPath, applyEnvironments)
},
}

// promptEnvironments Prompts the user for the environments to apply against and returns a slice of strings representing the environments
func promptEnvironments() []string {
items := map[string][]string{
"Staging ": []string{"staging"},
"Production": []string{"production"},
"Both Staging and Production": []string{"staging", "production"},
}

var labels []string
for label := range items {
labels = append(labels, label)
}

providerPrompt := promptui.Select{
Label: "Environments",
Items: labels,
}
_, providerResult, err := providerPrompt.Run()
if err != nil {
log.Fatalf("Prompt failed %v\n", err)
panic(err)
}
return items[providerResult]
}
11 changes: 11 additions & 0 deletions cmd/apply_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package cmd_test

import (
"testing"
)

func TestApply(t *testing.T) {
// if false {
// t.Fatalf("apply failed!")
// }
}
102 changes: 102 additions & 0 deletions internal/apply/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package apply

import (
"fmt"

"log"
"os/exec"
"path"
"strings"

"github.com/commitdev/zero/internal/util"
"github.com/commitdev/zero/pkg/util/flog"

"github.com/commitdev/zero/internal/config/projectconfig"
"github.com/commitdev/zero/pkg/util/exit"
"github.com/manifoldco/promptui"
)

// Apply will bootstrap the runtime environment for the project
func Apply(dir string, applyConfigPath string, applyEnvironments []string) []string {
context := loadContext(dir, applyConfigPath, applyEnvironments)

flog.Infof(":tada: Bootstrapping project %s. Please use the zero.[hcl, yaml] file to modify the project as needed. %s.", context.Name)

flog.Infof("Cloud provider: %s", "AWS") // will this come from the config?

flog.Infof("Runtime platform: %s", "Kubernetes")

flog.Infof("Infrastructure executor: %s", "Terraform")

// other details...

return makeAll(dir, context, applyEnvironments)
}

// loadContext will load the context/configuration to be used by the apply command
func loadContext(dir string, applyConfigPath string, applyEnvironments []string) *projectconfig.ZeroProjectConfig {
if len(applyEnvironments) == 0 {
fmt.Println(`Choose the environments to apply. This will create infrastructure, CI pipelines, etc.
At this point, real things will be generated that may cost money!
Only a single environment may be suitable for an initial test, but for a real system we suggest setting up both staging and production environments.`)
applyEnvironments = promptEnvironments()
}

validateEnvironments(applyEnvironments)

if applyConfigPath == "" {
exit.Fatal("config path cannot be empty!")
}
configPath := path.Join(dir, applyConfigPath)
projectConfig := projectconfig.LoadConfig(configPath)
return projectConfig
}

// promptEnvironments Prompts the user for the environments to apply against and returns a slice of strings representing the environments
func promptEnvironments() []string {
items := map[string][]string{
"Staging ": {"staging"},
"Production": {"production"},
"Both Staging and Production": {"staging", "production"},
}

var labels []string
for label := range items {
labels = append(labels, label)
}

providerPrompt := promptui.Select{
Label: "Environments",
Items: labels,
}
_, providerResult, err := providerPrompt.Run()
if err != nil {
log.Fatalf("Prompt failed %v\n", err)
panic(err)
}
return items[providerResult]
}

func validateEnvironments(applyEnvironments []string) {
// Strict for now, we can brainstorm how much we want to support custom environments later
for _, env := range applyEnvironments {
if env != "staging" && env != "production" {
exit.Fatal("The currently supported environments are \"staging\" and \"production\"")
}
}
}

func makeAll(dir string, projectContext *projectconfig.ZeroProjectConfig, applyEnvironments []string) []string {
environmentArg := fmt.Sprintf("ENVIRONMENT=%s", strings.Join(applyEnvironments, ","))
envList := []string{environmentArg}
outputs := []string{}

for _, mod := range projectContext.Modules {
modulePath := path.Join(dir, mod.Files.Directory)
envList = util.AppendProjectEnvToCmdEnv(mod.Parameters, envList)

output := util.ExecuteCommandOutput(exec.Command("make"), modulePath, envList)
outputs = append(outputs, output)
}
return outputs
}
27 changes: 27 additions & 0 deletions internal/apply/apply_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package apply_test

import (
"testing"

"github.com/commitdev/zero/internal/apply"
"github.com/commitdev/zero/internal/constants"
"github.com/stretchr/testify/assert"
)

func TestApply(t *testing.T) {
// @TODO is there a way to do this without relative paths?
dir := "../../tests/test_data/sample_project/"
applyConfigPath := constants.ZeroProjectYml
applyEnvironments := []string{"staging", "production"}

want := []string{
"make module zero-aws-eks-stack\n",
"make module zero-deployable-backend\n",
"make module zero-deployable-react-frontend\n",
}

t.Run("Should run apply and execute make on each folder module", func(t *testing.T) {
got := apply.Apply(dir, applyConfigPath, applyEnvironments)
assert.ElementsMatch(t, want, got)
})
}
38 changes: 28 additions & 10 deletions internal/config/projectconfig/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/commitdev/zero/internal/constants"
"github.com/commitdev/zero/pkg/util/exit"
"gopkg.in/yaml.v2"
)

const exampleConfig = `name: %s
Expand All @@ -17,13 +18,26 @@ context:
# module can be in any format the go-getter supports (path, github, url, etc.)
# supports https://github.com/hashicorp/go-getter#url-format
# Example:
# - source: "../development/modules/ci"
# - output: "github-actions"

# - repo: "../development/modules/ci"
# - dir: "github-actions"
modules:
- source: "github.com/commitdev/zero-aws-eks-stack"
- source: "github.com/commitdev/zero-deployable-backend"
- source: "github.com/commitdev/zero-deployable-react-frontend"
aws-eks-stack:
parameters:
repoName: infrastructure
region: us-east-1
accountId: 12345
productionHost: something.com
files:
dir: infrastructure
repo: https://github.com/myorg/infrastructure
some-other-module:
parameters:
repoName: api
files:
dir: api
repo: https://github.com/myorg/api


`

var RootDir = "./"
Expand All @@ -34,10 +48,14 @@ func SetRootDir(dir string) {

func Init(dir string, projectName string, projectContext *ZeroProjectConfig) {
// TODO: template the zero-project.yml with projectContext
content := []byte(fmt.Sprintf(exampleConfig, projectName))

err := ioutil.WriteFile(path.Join(dir, projectName, constants.ZeroProjectYml), content, 0644)
// content := []byte(fmt.Sprintf(exampleConfig, projectName))
content, err := yaml.Marshal(projectContext)
if err != nil {
exit.Fatal(fmt.Sprintf("Failed to create example %s", constants.ZeroProjectYml))
exit.Fatal(fmt.Sprintf("Failed to serialize configuration file %s", constants.ZeroProjectYml))
}

writeErr := ioutil.WriteFile(path.Join(dir, projectName, constants.ZeroProjectYml), content, 0644)
if writeErr != nil {
exit.Fatal(fmt.Sprintf("Failed to create config file %s", constants.ZeroProjectYml))
}
}
51 changes: 49 additions & 2 deletions internal/config/projectconfig/project_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
)

type ZeroProjectConfig struct {
Name string
Name string `yaml:"name"`
ShouldPushRepositories bool
Infrastructure Infrastructure // TODO simplify and flatten / rename?
Parameters map[string]string
Modules []string
Modules Modules `yaml:"modules"`
}

type Infrastructure struct {
Expand All @@ -30,6 +30,20 @@ type terraform struct {
RemoteState bool
}

type Modules map[string]Module

type Module struct {
Parameters Parameters `yaml:"parameters,omitempty"`
Files Files `yaml:"files,omitempty"`
}

type Parameters map[string]string

type Files struct {
Directory string `yaml:"dir,omitempty"`
Repository string `yaml:"repo,omitempty"`
}

func LoadConfig(filePath string) *ZeroProjectConfig {
config := &ZeroProjectConfig{}
data, err := ioutil.ReadFile(filePath)
Expand All @@ -47,3 +61,36 @@ func LoadConfig(filePath string) *ZeroProjectConfig {
func (c *ZeroProjectConfig) Print() {
pp.Println(c)
}

// @TODO only an example, needs refactoring
func EKSGoReactSampleModules() Modules {
parameters := Parameters{}
return Modules{
"aws-eks-stack": NewModule(parameters, "zero-aws-eks-stack", "github.com/commitdev/zero-aws-eks-stack"),
"deployable-backend": NewModule(parameters, "zero-deployable-backend", "github.com/commitdev/zero-deployable-backend"),
"deployable-react-frontend": NewModule(parameters, "zero-deployable-react-frontend", "github.com/commitdev/zero-deployable-react-frontend"),
}
}

// @TODO only an example, needs refactoring
func InfrastructureSampleModules() Modules {
parameters := Parameters{
"repoName": "infrastructure",
"region": "us-east-1",
"accountId": "12345",
"productionHost": "something.com",
}
return Modules{
"infrastructure": NewModule(parameters, "infrastructure", "https://github.com/myorg/infrastructure"),
}
}

func NewModule(parameters Parameters, directory string, repository string) Module {
return Module{
Parameters: parameters,
Files: Files{
Directory: directory,
Repository: repository,
},
}
}
Loading