Skip to content

Commit

Permalink
[SEMVER-MAJOR] condition support to module params (#350)
Browse files Browse the repository at this point in the history
* [SEMVER-MAJOR] condition support to module params

changes includes
- removing global config
- module config’s parameter to support condition
- module config’s parameter to support custom typed prompts
- prompts to not return value, instead update map(can support multi-value)

* remove credentials

* dynamically populate from struct

* allow overwriting the env-var while apply

* add comments to public functions

* address comments

* project definition

* Update docs/module-definition.md

Co-authored-by: Bill Monkman <bmonkman@gmail.com>

* Update docs/project-definition.md

Co-authored-by: Bill Monkman <bmonkman@gmail.com>

* better error message to users

* explain template parameters

* condition clarification between module and param

Co-authored-by: Bill Monkman <bmonkman@gmail.com>
  • Loading branch information
davidcheung and bmonkman authored Mar 11, 2021
1 parent 7c3a230 commit 7f4c7a3
Show file tree
Hide file tree
Showing 24 changed files with 643 additions and 622 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ You need to [register a new domain](https://docs.aws.amazon.com/Route53/latest/D
___

### Building blocks of Zero

### Project Definition:
Each project is defined by this project definition file, this manifest contains your project details, and is the source of truth for the templating(`zero create`) and provision(`zero apply`) steps.

See [`zero-project.yml` reference](./docs/project-definition.md) for details.
### Module Definition
Module definition defines the information needed for the module to run (`zero apply`).
Also declares dependency used to determine the order of execution with other modules.

See [`zero-module.yml` reference](./docs/module-definition.md) for details.
___
## Using zero to spin up your own stack

Using Zero to spin up your infrastructure and application is easy and straightforward. Using just a few commands, you can configure and deploy your very own scalable, high-performance, production-ready infrastructure.
Expand Down
8 changes: 5 additions & 3 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"path"
"strings"

"github.com/commitdev/zero/internal/config/globalconfig"
"github.com/commitdev/zero/internal/config/projectconfig"
"github.com/commitdev/zero/internal/constants"
"github.com/commitdev/zero/internal/generate"
Expand Down Expand Up @@ -47,9 +46,12 @@ func Create(dir string, createConfigPath string) {
if projectConfig.ShouldPushRepositories {
flog.Infof(":up_arrow: Done Rendering - committing repositories to version control.")

globalConfig := globalconfig.GetProjectCredentials(projectConfig.Name)
for _, module := range projectConfig.Modules {
vcs.InitializeRepository(module.Files.Repository, globalConfig.GithubResourceConfig.AccessToken)
err, githubApiKey := projectconfig.ReadVendorCredentialsFromModule(module, "github")
if err != nil {
flog.Errorf(err.Error())
}
vcs.InitializeRepository(module.Files.Repository, githubApiKey)
}
} else {
flog.Infof(":up_arrow: Done Rendering - you will need to commit the created projects to version control.")
Expand Down
79 changes: 79 additions & 0 deletions docs/module-definition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
## Module Definition: `zero-module.yml`
This file is the definition of a Zero module. It contains a list of all the required parameters to be able to prompt a user for choices during `zero init`, information about how to template the contents of the module during `zero create`, and the information needed for the module to run (`zero apply`).
It also declares the module's dependencies to determine the order of execution in relation to other modules.

| Parameters | type | Description |
|---------------|-----------------|--------------------------------------------------|
| `name` | string | Name of module |
| `description` | string | Description of the module |
| `template` | template | default settings for templating out the module |
| `author` | string | Author of the module |
| `icon` | string | Path to logo image |
| `parameters` | list(Parameter) | Parameters to prompt users |


### Template
| Parameters | Type | Description |
|--------------|---------|-----------------------------------------------------------------------|
| `strictMode` | boolean | whether strict mode is enabled |
| `delimiters` | tuple | A tuple of open delimiter and ending delimiter eg: `<%` and `%>` |
| `inputDir` | string | Folder to template from the module, becomes the module root for users |
| `outputDir` | string | local directory name for the module, gets commited to version control |

### Condition(module)
Module conditions are considered during template phase (`zero create`), based on parameters supplied from project-definition,
modules can decide to have specific files ignored from the user's module. For example if user picks `userAuth: no`, we can ignore the auth resources via templating.

| Parameters | Type | Description |
|--------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
| `action` | enum(string) | type of condition, currently supports [`ignoreFile`] |
| `matchField` | string | Allows you to condition prompt based on another parameter's value |
| `WhenValue` | string | Matches for this value to satisfy the condition |
| `data` | list(string) | Supply extra data for condition to run `ignoreFile`: provide list of paths (file or directory path) to omit from module when condition is satisfied |

### Parameter:
Parameter defines the prompt during zero-init.
There are multiple ways of obtaining the value for each parameter.
Parameters may have `Conditions` and must be fulfilled when supplied, otherwise it skips the field entirely.

The precedence for different types of parameter prompts are as follow.
1. Execute
2. type: specific ways of obtaining values (in AWS credential case it will set 2 values to the map)
3. value: directly assigns a value to a parameter
4. prompt: requires users to select an option OR input a string
Note: Default is supplied as the starting point of the user's manual input (Not when value passed in is empty)

| Parameters | Type | Description |
|-----------------------|-----------------|---------------------------------------------------------------------------------------------------------------------------|
| `field` | string | key to store result for project definition |
| `label` | string | displayed name for the prompt |
| `options` | list(string) | A list of values for users to pick from |
| `default` | string | Defaults to this value during prompt |
| `value` | string | Skips prompt entirely when set |
| `info` | string | Displays during prompt as extra information guiding user's input |
| `fieldValidation` | Validation | Validations for the prompt value |
| `type` | enum(string) | Built in custom prompts: currently supports [`AWSProfilePicker`] |
| `execute` | string | executes commands and takes stdout as prompt result |
| `omitFromProjectFile` | bool | Field is skipped from adding to project definition |
| `conditions` | list(Condition) | Conditions for prompt to run, if supplied all conditions must pass |
| `envVarName` | string | During `zero apply` parameters are available as env-vars, defaults to field name but can be overwritten with `envVarName` |

### Condition(paramters)
Parameters conditions are considered while running user prompts, prompts are
executed in order of the yml, and will be skipped if conditions are not satisfied.
For example if a user decide to not use circleCI, condition can be used to skip the circleCI_api_key prompt.

| Parameters | Type | Description |
|--------------|--------------|-------------------------------------------------------------------|
| `action` | enum(string) | type of condition, currently supports [`KeyMatchCondition`] |
| `matchField` | string | Allows you to condition prompt based on another parameter's value |
| `WhenValue` | string | Matches for this value to satisfy the condition |
| `data` | list(string) | Supply extra data for condition to run |

### Validation

| Parameters | type | Description |
|----------------|--------------|-------------------------------------|
| `type` | enum(string) | Currently supports [`regex`] |
| `value` | string | Regular expression string |
| `errorMessage` | string | Error message when validation fails |
27 changes: 27 additions & 0 deletions docs/project-definition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
### Project Definition: `zero-project.yml`
Each project is defined by this file. This manifest reflects all the options a user chose during the `zero init` step. It defines which modules are part of the project, each of their parameters, and is the source of truth for the templating (`zero create`) and provision (`zero apply`) steps.

_Note: This file contains credentials, so make sure it is not shared with others._

| Parameters | Type | Description |
|--------------------------|--------------|------------------------------------------------|
| `name` | string | name of the project |
| `shouldPushRepositories` | boolean | whether to push the modules to version control |
| `modules` | map(modules) | a map containing modules of your project |


### Modules
| Parameters | Type | Description |
|--------------|-----------------|-------------------------------------------------------------------------|
| `parameters` | map(string) | key-value map of all the parameters to run the module |
| `files` | File | Stores information such as source-module location and destination |
| `dependsOn` | list(string) | a list of dependencies that should be fulfilled before this module |
| `conditions` | list(condition) | conditions to apply while templating out the module based on parameters |

### Condition
| Parameters | Type | Description |
|--------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
| `action` | enum(string) | type of condition, currently supports [`ignoreFile`] |
| `matchField` | string | Allows you to condition prompt based on another parameter's value |
| `WhenValue` | string | Matches for this value to satisfy the condition |
| `data` | list(string) | Supply extra data for condition to run `ignoreFile`: provide list of paths (file or directory path) to omit from module when condition is satisfied |
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/matryer/is v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.2.0
github.com/spf13/cobra v0.0.6
github.com/stretchr/testify v1.5.1
Expand Down
15 changes: 8 additions & 7 deletions internal/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/commitdev/zero/internal/util"
"github.com/hashicorp/terraform/dag"

"github.com/commitdev/zero/internal/config/globalconfig"
"github.com/commitdev/zero/internal/config/projectconfig"
"github.com/commitdev/zero/pkg/util/exit"
"github.com/commitdev/zero/pkg/util/flog"
Expand Down Expand Up @@ -87,11 +86,8 @@ func applyAll(dir string, projectConfig projectconfig.ZeroProjectConfig, applyEn
exit.Fatal("Failed to load module config, credentials cannot be injected properly")
}

// Get project credentials for the makefile
credentials := globalconfig.GetProjectCredentials(projectConfig.Name)
credentialEnvs := credentials.SelectedVendorsCredentialsAsEnv(modConfig.RequiredCredentials)
envList = util.AppendProjectEnvToCmdEnv(mod.Parameters, envList)
envList = util.AppendProjectEnvToCmdEnv(credentialEnvs, envList)
envVarTranslationMap := modConfig.GetParamEnvVarTranslationMap()
envList = util.AppendProjectEnvToCmdEnv(mod.Parameters, envList, envVarTranslationMap)
flog.Debugf("Env injected: %#v", envList)
flog.Infof("Executing apply command for %s...", modConfig.Name)
util.ExecuteCommand(exec.Command("make"), modulePath, envList)
Expand Down Expand Up @@ -160,7 +156,12 @@ func summarizeAll(dir string, projectConfig projectconfig.ZeroProjectConfig, app
}
flog.Debugf("Loaded module: %s from %s", name, modulePath)

envList = util.AppendProjectEnvToCmdEnv(mod.Parameters, envList)
modConfig, err := module.ParseModuleConfig(modulePath)
if err != nil {
exit.Fatal("Failed to load module config, credentials cannot be injected properly")
}
envVarTranslationMap := modConfig.GetParamEnvVarTranslationMap()
envList = util.AppendProjectEnvToCmdEnv(mod.Parameters, envList, envVarTranslationMap)
flog.Debugf("Env injected: %#v", envList)
util.ExecuteCommand(exec.Command("make", "summary"), modulePath, envList)
return nil
Expand Down
7 changes: 7 additions & 0 deletions internal/apply/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,11 @@ func TestApply(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "baz: qux\n", string(content))
})

t.Run("Zero apply honors the envVarName overwrite from module definition", func(t *testing.T) {
content, err := ioutil.ReadFile(filepath.Join(tmpDir, "project1/feature.out"))
assert.NoError(t, err)
assert.Equal(t, "envVarName of viaEnvVarName: baz\n", string(content))
})

}
183 changes: 0 additions & 183 deletions internal/config/globalconfig/global_config.go

This file was deleted.

Loading

0 comments on commit 7f4c7a3

Please sign in to comment.