├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── assets ├── assets.go ├── assets_test.go ├── docs │ ├── spycli.md │ ├── spycli_blueprint.md │ ├── spycli_blueprint_new.md │ ├── spycli_completion.md │ ├── spycli_completion_bash.md │ ├── spycli_completion_fish.md │ ├── spycli_completion_powershell.md │ ├── spycli_completion_zsh.md │ ├── spycli_man.md │ ├── spycli_module.md │ ├── spycli_module_include.md │ ├── spycli_project.md │ ├── spycli_project_clean.md │ ├── spycli_project_clone.md │ ├── spycli_project_clone_env.md │ ├── spycli_project_init.md │ └── spycli_project_new.md ├── img │ ├── bp-thin-project.png │ ├── cover.png │ ├── tg-project-initialized.png │ ├── tg-project-repeat.png │ ├── tg-project-schema.png │ └── tg-project.png └── templates │ ├── bp │ ├── gitignore.tmpl │ └── region.yml.tmpl │ ├── generic │ ├── bar.tmpl │ └── foo.tmpl │ ├── mdl │ ├── gitignore.tmpl │ ├── module_main.tmpl │ ├── module_outputs.tf.tmpl │ ├── module_variables.tf.tmpl │ ├── provider_aws.tmpl │ └── terragrunt.hcl.tmpl │ ├── prj │ ├── env.yml.tmpl │ ├── gitignore.tmpl │ ├── gitignore_region.tmpl │ ├── prj.yml.tmpl │ ├── region.yml.tmpl │ └── terragrunt.hcl.tmpl │ ├── test.tmpl │ └── tf-aws-modules │ ├── vm │ ├── image.tf │ ├── main.tf │ ├── outputs.tf │ └── variables.tf │ └── vpc │ ├── main.tf │ ├── outputs.tf │ ├── subnets.tf │ └── variables.tf ├── blueprint ├── blueprint.go └── blueprint_test.go ├── build.sh ├── cmd ├── bp.go ├── docs.go ├── mdl.go ├── prj.go └── root.go ├── go.mod ├── go.sum ├── lib ├── files.go ├── files_test.go ├── folders.go ├── string.go ├── yaml.go └── yaml_test.go ├── main ├── main.go ├── model ├── generic.go ├── model.go └── model_test.go ├── module ├── module.go └── module_test.go ├── project ├── env_clone.go ├── model.go ├── project_init.go ├── project_init_test.go ├── project_new.go └── project_new_test.go ├── release.sh └── tests └── integration_test.go /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '36 22 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | terragrunt.iml 2 | .terraform 3 | .vscode 4 | *.tfstate 5 | *.tfstate.backup 6 | *.out 7 | .terragrunt-cache 8 | .terraform.lock.hcl 9 | .iac-test 10 | *.zip 11 | .history -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "New Project", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceFolder}/main.go", 13 | "args": [ 14 | "project", 15 | "new", 16 | "-d", "${workspaceFolder}/.iac-test", 17 | "-n", "My Project", 18 | "-b", "bp-aws-nearform", 19 | "-e", "develop", 20 | "-e", "production", 21 | "-r", "us-east-1", 22 | "-r", "us-west-1", 23 | "-s", "my-stack" 24 | ] 25 | }, 26 | { 27 | "name": "New Project - Remote State", 28 | "type": "go", 29 | "request": "launch", 30 | "mode": "auto", 31 | "program": "${workspaceFolder}/main.go", 32 | "args": [ 33 | "project", 34 | "new", 35 | "-d", "${workspaceFolder}/.iac-test", 36 | "-n", "My Project", 37 | "-b", "bp-aws-nearform", 38 | "-e", "develop", 39 | "-e", "production", 40 | "-r", "us-east-1", 41 | "-r", "us-west-1", 42 | "-s", "my-stack", 43 | "-t", 44 | "-u", "my-bucket", 45 | "-v", "us-east-1" 46 | ] 47 | }, 48 | { 49 | "name": "New Blueprint", 50 | "type": "go", 51 | "request": "launch", 52 | "mode": "auto", 53 | "program": "${workspaceFolder}/main.go", 54 | "args": [ 55 | "blueprint", 56 | "new", 57 | "-d", "${workspaceFolder}/.iac-test", 58 | "-n", "BP AWS Nearform", 59 | "-s", "my-stack", 60 | "-r", "us-east-1", 61 | "-r", "us-west-1", 62 | ] 63 | }, 64 | { 65 | "name": "Include Module", 66 | "type": "go", 67 | "request": "launch", 68 | "mode": "auto", 69 | "program": "${workspaceFolder}/main.go", 70 | "args": [ 71 | "module", 72 | "include", 73 | "-d", "${workspaceFolder}/.iac-test/bp-aws-nearform/my-stack/_any", 74 | "-n", "my-vpc", 75 | "-u", "git@github.com:terraform-aws-modules/terraform-aws-vpc.git" 76 | ] 77 | }, 78 | { 79 | "name": "Run Test", 80 | "type": "go", 81 | "request": "launch", 82 | "mode": "test", 83 | "program": "${file}", 84 | "showLog": true 85 | }, 86 | { 87 | "name": "Init Project", 88 | "type": "go", 89 | "request": "launch", 90 | "mode": "auto", 91 | "program": "${workspaceFolder}/main.go", 92 | "args": [ 93 | "project", 94 | "init", 95 | "-d", "${workspaceFolder}/.iac-test/my-project" 96 | ] 97 | }, 98 | { 99 | "name": "Init Project With Links", 100 | "type": "go", 101 | "request": "launch", 102 | "mode": "auto", 103 | "program": "${workspaceFolder}/main.go", 104 | "args": [ 105 | "project", 106 | "init", 107 | "-d", "${workspaceFolder}/.iac-test/my-project", 108 | "-l" 109 | ] 110 | }, 111 | { 112 | "name": "Clone Environment", 113 | "type": "go", 114 | "request": "launch", 115 | "mode": "auto", 116 | "program": "${workspaceFolder}/main.go", 117 | "args": [ 118 | "project", 119 | "clone", 120 | "env", 121 | "-d", "${workspaceFolder}/.iac-test/my-project", 122 | "-n", "pr-1", 123 | "-f", "develop", 124 | ] 125 | }, 126 | { 127 | "name": "Clone Environment Techbase", 128 | "type": "go", 129 | "request": "launch", 130 | "mode": "auto", 131 | "program": "${workspaceFolder}/main.go", 132 | "args": [ 133 | "project", 134 | "clone", 135 | "env", 136 | "-d", "/home/g/projects/nearform/techbase/infra/iac-techbase-hasura", 137 | "-n", "pr00", 138 | "-f", "staging", 139 | ] 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Spy Code 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SPY CLI 2 | 3 | **SpyCLI** spycli the killer iac tool for deal with projects that follows the **Blueprint** and **Thin Projects** with **[terragrunt](https://terragrunt.gruntwork.io/)**. 4 | 5 | 6 | ## Demonstration 7 | 8 | [![Watch the demo](assets/img/cover.png)](https://youtu.be/nBTP2VugADo) 9 | 10 | ## Terragrunt project structure 11 | 12 | Lets consider the following scenario: 13 | 14 | We need provide an infrastrucutre. This infrastructure must have two environments: 15 | - **environment-x** 16 | - **environment-y** 17 | 18 | And this must be replicated in three regions: 19 | - **region-a** 20 | - **region-b** 21 | - **region-c** 22 | 23 | And each region must contains two modules: 24 | - **module-1** 25 | - **module-2** 26 | 27 | These requeriments results in a project schema similar to this: 28 | 29 | ![Infrastructure](assets/img/tg-project-schema.png) 30 | 31 | And the files and folders structure must to be like this: 32 | 33 | ![Infrastructure](assets/img/tg-project-repeat.png) 34 | 35 | ### File repetition 36 | ``` 37 | Note: that every region have the same modules and this files repeats each region folder. 38 | It's very bad to deal with and the **Blueprint and Thin Projec Approach** intents to solve this problem. 39 | ``` 40 | 41 | --- 42 | 43 | ## Blueprint and Thin Project approach 44 | 45 | On this approach we remove modules from each region from the project and then put theses files in a distributable and reusable **blueprint**. 46 | 47 | The **blueprint** will contains the modules (per region or for any region) and the project will be composed of just the essencial files (aka config files) 48 | 49 | ![Infrastructure](assets/img/bp-thin-project.png) 50 | 51 | --- 52 | 53 | ## SpyCLI project init 54 | 55 | SpyCLI helps cloud and devops enginners to manage projects using scaffolds and automations. 56 | 57 | The most important is the command **init**. This commands creates the link between the project and the blueprint by copying or linkin files required 58 | 59 | 60 | After the execution of project init command the files and folders structure will appears something like this: 61 | 62 | ![Infrastructure](assets/img/tg-project-initialized.png) 63 | 64 | --- 65 | 66 | ## Commands Reference 67 | 68 | ### Access [SpyCLI commands reference](assets/docs/spycli.md) for more details 69 | -------------------------------------------------------------------------------- /assets/assets.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "text/template" 10 | ) 11 | 12 | //go:embed templates/* 13 | var TemplatesData embed.FS 14 | 15 | type FileTmpl struct { 16 | TmplFile string 17 | File string 18 | } 19 | 20 | type FileSet struct { 21 | AssetsPath string 22 | Set map[string]map[string][]FileTmpl 23 | Data embed.FS 24 | } 25 | 26 | type FileSetInterface interface { 27 | WithSet(set map[string]map[string][]FileTmpl) *FileSet 28 | WithMap(platform string, fileMap map[string][]FileTmpl) *FileSet 29 | WithFiles(platform string, level string, files []FileTmpl) *FileSet 30 | WriteObjToFile(tmplFile string, file string, obj interface{}) error 31 | WriteObjToPath(platform string, level string, basePath string, obj interface{}) 32 | } 33 | 34 | func NewFileSet(assetsPath string) *FileSet { 35 | return &FileSet{ 36 | AssetsPath: assetsPath, 37 | Data: TemplatesData, 38 | Set: make(map[string]map[string][]FileTmpl), 39 | } 40 | } 41 | 42 | func (f *FileSet) WithMap(platform string, fileMap map[string][]FileTmpl) *FileSet { 43 | f.Set[platform] = fileMap 44 | return f 45 | } 46 | 47 | func (f *FileSet) WithSet(set map[string]map[string][]FileTmpl) *FileSet { 48 | f.Set = set 49 | return f 50 | } 51 | 52 | func (f *FileSet) WithFiles(platform string, level string, files []FileTmpl) *FileSet { 53 | if _, ok := f.Set[platform][level]; !ok { 54 | f.Set[platform] = make(map[string][]FileTmpl) 55 | } 56 | f.Set[platform][level] = files 57 | return f 58 | } 59 | 60 | func (f *FileSet) WriteObjToFile(tmplFile string, file string, obj interface{}) error { 61 | tmplPath := tmplFile 62 | if f.AssetsPath != "" { 63 | tmplPath = fmt.Sprintf("%s/%s", f.AssetsPath, tmplFile) 64 | } 65 | 66 | log.Printf("Writing file %s from template %s", file, tmplPath) 67 | 68 | pTmpl, err := template.ParseFS(f.Data, tmplPath) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | //Create base folder if necessary 74 | dir := filepath.Dir(file) 75 | _, err = os.Stat(dir) 76 | if os.IsNotExist(err) { 77 | os.MkdirAll(dir, os.ModeSticky|os.ModePerm) 78 | err = nil 79 | } 80 | 81 | wf, err := os.Create(file) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | defer wf.Close() 87 | 88 | return pTmpl.Execute(wf, obj) 89 | } 90 | 91 | func (f *FileSet) WriteObjToPath(platform string, level string, basePath string, obj interface{}) (err error) { 92 | for _, tf := range f.Set[platform][level] { 93 | filePath := fmt.Sprintf("%s/%s", basePath, tf.File) 94 | err = f.WriteObjToFile(tf.TmplFile, filePath, obj) 95 | if nil != err { 96 | return 97 | } 98 | } 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /assets/assets_test.go: -------------------------------------------------------------------------------- 1 | package assets_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/spycode-io/spycli/assets" 8 | ) 9 | 10 | type testObj struct { 11 | Test string 12 | } 13 | 14 | func TestWriteFile(t *testing.T) { 15 | 16 | obj := testObj{ 17 | Test: "Test Write File", 18 | } 19 | 20 | fileSet := assets.NewFileSet("templates") 21 | err := fileSet.WriteObjToFile("test.tmpl", ".iac-test/test-file", obj) 22 | if nil != err { 23 | t.Error(err) 24 | } 25 | 26 | fileContent, err := ioutil.ReadFile(".iac-test/test-file") 27 | if nil != err { 28 | t.Error(err) 29 | } 30 | 31 | if string(fileContent) != obj.Test { 32 | t.Errorf("Invalid content: want be %s but %s found", fileContent, obj.Test) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /assets/docs/spycli.md: -------------------------------------------------------------------------------- 1 | ## spycli 2 | 3 | spycli the killer iac project tool 4 | 5 | ### Synopsis 6 | 7 | 8 | 9 | ███████╗██████╗ ██╗ ██╗ ██████╗██╗ ██╗ 10 | ██╔════╝██╔══██╗╚██╗ ██╔╝██╔════╝██║ ██║ 11 | ███████╗██████╔╝ ╚████╔╝ ██║ ██║ ██║ 12 | ╚════██║██╔═══╝ ╚██╔╝ ██║ ██║ ██║ 13 | ███████║██║ ██║ ╚██████╗███████╗██║ 14 | ╚══════╝╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝ 15 | v0.0.0 16 | 17 | SpyCLI is a command library tool for work with iac 18 | projects, blueprints, modules, etc 19 | 20 | ### Options 21 | 22 | ``` 23 | -h, --help help for spycli 24 | -V, --verbose verbose output 25 | ``` 26 | 27 | ### SEE ALSO 28 | 29 | * [spycli blueprint](spycli_blueprint.md) - Manipulate iac blueprints 30 | * [spycli completion](spycli_completion.md) - Generate the autocompletion script for the specified shell 31 | * [spycli man](spycli_man.md) - Generate markdown commands manual 32 | * [spycli module](spycli_module.md) - Manipulate modules 33 | * [spycli project](spycli_project.md) - Manipulate iac projects 34 | 35 | ###### Auto generated by spf13/cobra on 25-Jan-2022 36 | -------------------------------------------------------------------------------- /assets/docs/spycli_blueprint.md: -------------------------------------------------------------------------------- 1 | ## spycli blueprint 2 | 3 | Manipulate iac blueprints 4 | 5 | ### Synopsis 6 | 7 | Use blueprint new 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for blueprint 13 | ``` 14 | 15 | ### SEE ALSO 16 | 17 | * [spycli](spycli.md) - spycli the killer iac project tool 18 | * [spycli blueprint new](spycli_blueprint_new.md) - Create new blueprint 19 | 20 | ###### Auto generated by spf13/cobra on 25-Jan-2022 21 | -------------------------------------------------------------------------------- /assets/docs/spycli_blueprint_new.md: -------------------------------------------------------------------------------- 1 | ## spycli blueprint new 2 | 3 | Create new blueprint 4 | 5 | ### Synopsis 6 | 7 | 8 | 9 | Creates a new empty blueprint 10 | 11 | Ex: To create a new blueprint called bp-aws-nearform with a empty stack called simple-web-app with a region us-east-1 12 | 13 | spycli blueprint new -n "BP AWS Nearform" -s simple-web-app -r us-east-1 14 | 15 | 16 | 17 | ``` 18 | spycli blueprint new [flags] 19 | ``` 20 | 21 | ### Options 22 | 23 | ``` 24 | -d, --directory string Base directory where the files will be writen (default ".") 25 | -h, --help help for new 26 | -n, --name string Element name (ex: my-project or my-blueprint) 27 | -r, --region strings Pass a list of regions 28 | -s, --stack string Stack name 29 | ``` 30 | 31 | ### SEE ALSO 32 | 33 | * [spycli blueprint](spycli_blueprint.md) - Manipulate iac blueprints 34 | 35 | ###### Auto generated by spf13/cobra on 25-Jan-2022 36 | -------------------------------------------------------------------------------- /assets/docs/spycli_completion.md: -------------------------------------------------------------------------------- 1 | ## spycli completion 2 | 3 | Generate the autocompletion script for the specified shell 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for spycli for the specified shell. 8 | See each sub-command's help for details on how to use the generated script. 9 | 10 | 11 | ### Options 12 | 13 | ``` 14 | -h, --help help for completion 15 | ``` 16 | 17 | ### SEE ALSO 18 | 19 | * [spycli](spycli.md) - spycli the killer iac project tool 20 | * [spycli completion bash](spycli_completion_bash.md) - Generate the autocompletion script for bash 21 | * [spycli completion fish](spycli_completion_fish.md) - Generate the autocompletion script for fish 22 | * [spycli completion powershell](spycli_completion_powershell.md) - Generate the autocompletion script for powershell 23 | * [spycli completion zsh](spycli_completion_zsh.md) - Generate the autocompletion script for zsh 24 | 25 | ###### Auto generated by spf13/cobra on 25-Jan-2022 26 | -------------------------------------------------------------------------------- /assets/docs/spycli_completion_bash.md: -------------------------------------------------------------------------------- 1 | ## spycli completion bash 2 | 3 | Generate the autocompletion script for bash 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the bash shell. 8 | 9 | This script depends on the 'bash-completion' package. 10 | If it is not installed already, you can install it via your OS's package manager. 11 | 12 | To load completions in your current shell session: 13 | 14 | source <(spycli completion bash) 15 | 16 | To load completions for every new session, execute once: 17 | 18 | #### Linux: 19 | 20 | spycli completion bash > /etc/bash_completion.d/spycli 21 | 22 | #### macOS: 23 | 24 | spycli completion bash > /usr/local/etc/bash_completion.d/spycli 25 | 26 | You will need to start a new shell for this setup to take effect. 27 | 28 | 29 | ``` 30 | spycli completion bash 31 | ``` 32 | 33 | ### Options 34 | 35 | ``` 36 | -h, --help help for bash 37 | --no-descriptions disable completion descriptions 38 | ``` 39 | 40 | ### SEE ALSO 41 | 42 | * [spycli completion](spycli_completion.md) - Generate the autocompletion script for the specified shell 43 | 44 | ###### Auto generated by spf13/cobra on 25-Jan-2022 45 | -------------------------------------------------------------------------------- /assets/docs/spycli_completion_fish.md: -------------------------------------------------------------------------------- 1 | ## spycli completion fish 2 | 3 | Generate the autocompletion script for fish 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the fish shell. 8 | 9 | To load completions in your current shell session: 10 | 11 | spycli completion fish | source 12 | 13 | To load completions for every new session, execute once: 14 | 15 | spycli completion fish > ~/.config/fish/completions/spycli.fish 16 | 17 | You will need to start a new shell for this setup to take effect. 18 | 19 | 20 | ``` 21 | spycli completion fish [flags] 22 | ``` 23 | 24 | ### Options 25 | 26 | ``` 27 | -h, --help help for fish 28 | --no-descriptions disable completion descriptions 29 | ``` 30 | 31 | ### SEE ALSO 32 | 33 | * [spycli completion](spycli_completion.md) - Generate the autocompletion script for the specified shell 34 | 35 | ###### Auto generated by spf13/cobra on 25-Jan-2022 36 | -------------------------------------------------------------------------------- /assets/docs/spycli_completion_powershell.md: -------------------------------------------------------------------------------- 1 | ## spycli completion powershell 2 | 3 | Generate the autocompletion script for powershell 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for powershell. 8 | 9 | To load completions in your current shell session: 10 | 11 | spycli completion powershell | Out-String | Invoke-Expression 12 | 13 | To load completions for every new session, add the output of the above command 14 | to your powershell profile. 15 | 16 | 17 | ``` 18 | spycli completion powershell [flags] 19 | ``` 20 | 21 | ### Options 22 | 23 | ``` 24 | -h, --help help for powershell 25 | --no-descriptions disable completion descriptions 26 | ``` 27 | 28 | ### SEE ALSO 29 | 30 | * [spycli completion](spycli_completion.md) - Generate the autocompletion script for the specified shell 31 | 32 | ###### Auto generated by spf13/cobra on 25-Jan-2022 33 | -------------------------------------------------------------------------------- /assets/docs/spycli_completion_zsh.md: -------------------------------------------------------------------------------- 1 | ## spycli completion zsh 2 | 3 | Generate the autocompletion script for zsh 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the zsh shell. 8 | 9 | If shell completion is not already enabled in your environment you will need 10 | to enable it. You can execute the following once: 11 | 12 | echo "autoload -U compinit; compinit" >> ~/.zshrc 13 | 14 | To load completions for every new session, execute once: 15 | 16 | #### Linux: 17 | 18 | spycli completion zsh > "${fpath[1]}/_spycli" 19 | 20 | #### macOS: 21 | 22 | spycli completion zsh > /usr/local/share/zsh/site-functions/_spycli 23 | 24 | You will need to start a new shell for this setup to take effect. 25 | 26 | 27 | ``` 28 | spycli completion zsh [flags] 29 | ``` 30 | 31 | ### Options 32 | 33 | ``` 34 | -h, --help help for zsh 35 | --no-descriptions disable completion descriptions 36 | ``` 37 | 38 | ### SEE ALSO 39 | 40 | * [spycli completion](spycli_completion.md) - Generate the autocompletion script for the specified shell 41 | 42 | ###### Auto generated by spf13/cobra on 25-Jan-2022 43 | -------------------------------------------------------------------------------- /assets/docs/spycli_man.md: -------------------------------------------------------------------------------- 1 | ## spycli man 2 | 3 | Generate markdown commands manual 4 | 5 | ### Synopsis 6 | 7 | Generate markdown commands manual 8 | 9 | ``` 10 | spycli man [flags] 11 | ``` 12 | 13 | ### Options 14 | 15 | ``` 16 | -d, --directory string Base directory where the files will be writen (default ".") 17 | -h, --help help for man 18 | ``` 19 | 20 | ### SEE ALSO 21 | 22 | * [spycli](spycli.md) - spycli the killer iac project tool 23 | 24 | ###### Auto generated by spf13/cobra on 25-Jan-2022 25 | -------------------------------------------------------------------------------- /assets/docs/spycli_module.md: -------------------------------------------------------------------------------- 1 | ## spycli module 2 | 3 | Manipulate modules 4 | 5 | ### Synopsis 6 | 7 | Use module commands 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for module 13 | ``` 14 | 15 | ### SEE ALSO 16 | 17 | * [spycli](spycli.md) - spycli the killer iac project tool 18 | * [spycli module include](spycli_module_include.md) - Include module 19 | 20 | ###### Auto generated by spf13/cobra on 25-Jan-2022 21 | -------------------------------------------------------------------------------- /assets/docs/spycli_module_include.md: -------------------------------------------------------------------------------- 1 | ## spycli module include 2 | 3 | Include module 4 | 5 | ### Synopsis 6 | 7 | include: includes a new module in a blueprint region 8 | 9 | Ex: To create a vpc module called my-vpc from official terraform aws modules 10 | 11 | spycli module include -n "My VPC" -u git@github.com:terraform-aws-modules/terraform-aws-vpc.git 12 | 13 | or you can include a local module from a library in same path of the project: 14 | 15 | spycli module include -n "My VPC" -u tf-aws-components/vpc -l 16 | 17 | 18 | 19 | ``` 20 | spycli module include [flags] 21 | ``` 22 | 23 | ### Options 24 | 25 | ``` 26 | -d, --directory string Base directory where the files will be writen (default ".") 27 | -h, --help help for include 28 | -l, --local Use a local modules library 29 | -n, --name string Element name (ex: my-project or my-blueprint) 30 | -u, --url string Module URL. Ex: git@github.com:terraform-aws-modules/terraform-aws-vpc.git or a local folder tf-componets/my-module (using with -l parameter) 31 | ``` 32 | 33 | ### SEE ALSO 34 | 35 | * [spycli module](spycli_module.md) - Manipulate modules 36 | 37 | ###### Auto generated by spf13/cobra on 25-Jan-2022 38 | -------------------------------------------------------------------------------- /assets/docs/spycli_project.md: -------------------------------------------------------------------------------- 1 | ## spycli project 2 | 3 | Manipulate iac projects 4 | 5 | ### Synopsis 6 | 7 | Use project commands 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for project 13 | ``` 14 | 15 | ### SEE ALSO 16 | 17 | * [spycli](spycli.md) - spycli the killer iac project tool 18 | * [spycli project clean](spycli_project_clean.md) - Clean a project 19 | * [spycli project clone](spycli_project_clone.md) - Clone elements of a project 20 | * [spycli project init](spycli_project_init.md) - Initialize a project 21 | * [spycli project new](spycli_project_new.md) - Create new project 22 | 23 | ###### Auto generated by spf13/cobra on 25-Jan-2022 24 | -------------------------------------------------------------------------------- /assets/docs/spycli_project_clean.md: -------------------------------------------------------------------------------- 1 | ## spycli project clean 2 | 3 | Clean a project 4 | 5 | ### Synopsis 6 | 7 | Use project clean to remove all bp files 8 | 9 | ``` 10 | spycli project clean [flags] 11 | ``` 12 | 13 | ### Options 14 | 15 | ``` 16 | -h, --help help for clean 17 | ``` 18 | 19 | ### SEE ALSO 20 | 21 | * [spycli project](spycli_project.md) - Manipulate iac projects 22 | 23 | ###### Auto generated by spf13/cobra on 25-Jan-2022 24 | -------------------------------------------------------------------------------- /assets/docs/spycli_project_clone.md: -------------------------------------------------------------------------------- 1 | ## spycli project clone 2 | 3 | Clone elements of a project 4 | 5 | ### Synopsis 6 | 7 | Use project clone on a project folder 8 | Ex: 9 | spycli project clone 10 | 11 | ### Options 12 | 13 | ``` 14 | -h, --help help for clone 15 | ``` 16 | 17 | ### SEE ALSO 18 | 19 | * [spycli project](spycli_project.md) - Manipulate iac projects 20 | * [spycli project clone env](spycli_project_clone_env.md) - Clone a environment 21 | 22 | ###### Auto generated by spf13/cobra on 25-Jan-2022 23 | -------------------------------------------------------------------------------- /assets/docs/spycli_project_clone_env.md: -------------------------------------------------------------------------------- 1 | ## spycli project clone env 2 | 3 | Clone a environment 4 | 5 | ### Synopsis 6 | 7 | Clones a entire environment 8 | 9 | Ex: 10 | spycli project clone env --from develop --to pr-env 11 | 12 | ``` 13 | spycli project clone env [flags] 14 | ``` 15 | 16 | ### Options 17 | 18 | ``` 19 | -d, --directory string Base directory where the files will be writen (default ".") 20 | -f, --from string Environment origin of copy 21 | -h, --help help for env 22 | -n, --name string Element name (ex: my-project or my-blueprint) 23 | ``` 24 | 25 | ### SEE ALSO 26 | 27 | * [spycli project clone](spycli_project_clone.md) - Clone elements of a project 28 | 29 | ###### Auto generated by spf13/cobra on 25-Jan-2022 30 | -------------------------------------------------------------------------------- /assets/docs/spycli_project_init.md: -------------------------------------------------------------------------------- 1 | ## spycli project init 2 | 3 | Initialize a project 4 | 5 | ### Synopsis 6 | 7 | Use project init on a project folder 8 | 9 | Ex: 10 | spycli project init 11 | 12 | ``` 13 | spycli project init [flags] 14 | ``` 15 | 16 | ### Options 17 | 18 | ``` 19 | -d, --directory string Base directory where the files will be writen (default ".") 20 | -h, --help help for init 21 | -l, --link Link files locally instead of copy. This option is the best when editing blueprint files 22 | ``` 23 | 24 | ### SEE ALSO 25 | 26 | * [spycli project](spycli_project.md) - Manipulate iac projects 27 | 28 | ###### Auto generated by spf13/cobra on 25-Jan-2022 29 | -------------------------------------------------------------------------------- /assets/docs/spycli_project_new.md: -------------------------------------------------------------------------------- 1 | ## spycli project new 2 | 3 | Create new project 4 | 5 | ### Synopsis 6 | 7 | Creates a new project with local or remote reference for blueprint and components 8 | 9 | Examples: 10 | 11 | Create a project that: 12 | 13 | - Is called "My Project" 14 | - Uses blueprint bp-aws-nearform and stack simple-web-app locally 15 | - Have two environments: develop and production 16 | - Have two regions: us-east-1 and us-west-1 17 | 18 | spycli project new -n "My Project" -b bp-aws-nearform -l -s simple-web-app -r us-east-1 -e develop -e production 19 | 20 | The same project but using remove blueprint: 21 | 22 | spycli project new -n "My Project" -b git@github.com:nearform/bp-aws-nearform.git -s simple-web-app -r us-east-1 -e develop -e production 23 | 24 | The same project but using remote blueprint and remote state in terraform: 25 | 26 | spycli project new -n "My Project" -b git@github.com:nearform/bp-aws-nearform.git -s simple-web-app -r us-east-1 -e develop -e production -t -u my-bucket -v us-east-1 27 | 28 | 29 | 30 | ``` 31 | spycli project new [flags] 32 | ``` 33 | 34 | ### Options 35 | 36 | ``` 37 | -b, --blueprint string Blueprint 38 | -d, --directory string Base directory where the files will be writen (default ".") 39 | -e, --environment strings Pass a list of environments (default [dev]) 40 | -h, --help help for new 41 | -l, --local Local blueprint 42 | -n, --name string Element name (ex: my-project or my-blueprint) 43 | -p, --platform string Plataform or service (aws|azure) (default "aws") 44 | -r, --region strings Pass a list of environments (default [us-east-1]) 45 | -u, --remote-bucket string Stack name 46 | -v, --remote-bucket-region string Stack name 47 | -t, --remote-state Use remote state 48 | -s, --stack string Stack name 49 | ``` 50 | 51 | ### SEE ALSO 52 | 53 | * [spycli project](spycli_project.md) - Manipulate iac projects 54 | 55 | ###### Auto generated by spf13/cobra on 25-Jan-2022 56 | -------------------------------------------------------------------------------- /assets/img/bp-thin-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spycode-io/spycli/fc0a3cb48fce262538a7881aa6f73b73ffd1b49b/assets/img/bp-thin-project.png -------------------------------------------------------------------------------- /assets/img/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spycode-io/spycli/fc0a3cb48fce262538a7881aa6f73b73ffd1b49b/assets/img/cover.png -------------------------------------------------------------------------------- /assets/img/tg-project-initialized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spycode-io/spycli/fc0a3cb48fce262538a7881aa6f73b73ffd1b49b/assets/img/tg-project-initialized.png -------------------------------------------------------------------------------- /assets/img/tg-project-repeat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spycode-io/spycli/fc0a3cb48fce262538a7881aa6f73b73ffd1b49b/assets/img/tg-project-repeat.png -------------------------------------------------------------------------------- /assets/img/tg-project-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spycode-io/spycli/fc0a3cb48fce262538a7881aa6f73b73ffd1b49b/assets/img/tg-project-schema.png -------------------------------------------------------------------------------- /assets/img/tg-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spycode-io/spycli/fc0a3cb48fce262538a7881aa6f73b73ffd1b49b/assets/img/tg-project.png -------------------------------------------------------------------------------- /assets/templates/bp/gitignore.tmpl: -------------------------------------------------------------------------------- 1 | .*.sw? 2 | .idea 3 | terragrunt.iml 4 | vendor 5 | .terraform 6 | .vscode 7 | *.tfstate 8 | *.tfstate.backup 9 | *.out 10 | .terragrunt-cache 11 | .bundle 12 | .ruby-version 13 | .terraform.lock.hcl 14 | terragrunt-debug.tfvars.json 15 | .iac-test 16 | -------------------------------------------------------------------------------- /assets/templates/bp/region.yml.tmpl: -------------------------------------------------------------------------------- 1 | region: {{.Region}} -------------------------------------------------------------------------------- /assets/templates/generic/bar.tmpl: -------------------------------------------------------------------------------- 1 | {{.Bar}} -------------------------------------------------------------------------------- /assets/templates/generic/foo.tmpl: -------------------------------------------------------------------------------- 1 | {{.Foo}} -------------------------------------------------------------------------------- /assets/templates/mdl/gitignore.tmpl: -------------------------------------------------------------------------------- 1 | passwd 2 | *.key 3 | .*.sw? 4 | .idea 5 | terragrunt.iml 6 | vendor 7 | .terraform 8 | .vscode 9 | *.tfstate 10 | *.tfstate.backup 11 | *.out 12 | .terragrunt-cache 13 | .bundle 14 | .ruby-version 15 | .terraform.lock.hcl 16 | .iac-utils 17 | *-credentials.json -------------------------------------------------------------------------------- /assets/templates/mdl/module_main.tmpl: -------------------------------------------------------------------------------- 1 | # {{.Name}} 2 | resource "provider_component" "this" { 3 | name = var.name 4 | } 5 | -------------------------------------------------------------------------------- /assets/templates/mdl/module_outputs.tf.tmpl: -------------------------------------------------------------------------------- 1 | output "component_id" { 2 | value = azurerm_kubernetes_cluster.aks.private_fqdn 3 | description = "AKS Private FQDN" 4 | } 5 | -------------------------------------------------------------------------------- /assets/templates/mdl/module_variables.tf.tmpl: -------------------------------------------------------------------------------- 1 | variable "name" { } 2 | -------------------------------------------------------------------------------- /assets/templates/mdl/provider_aws.tmpl: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.2" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = "3.71.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/templates/mdl/terragrunt.hcl.tmpl: -------------------------------------------------------------------------------- 1 | include { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | locals { 6 | 7 | prj_vars = yamldecode(file(find_in_parent_folders("prj.yml"))) 8 | env_vars = yamldecode(file(find_in_parent_folders("env.yml"))) 9 | region_vars = yamldecode(file(find_in_parent_folders("region.yml"))) 10 | 11 | prj_slug_name = local.prj_vars.slugName 12 | environment = local.env_vars.environment 13 | region = local.region_vars.region 14 | 15 | name = "{{.Scaffold.SlugName}}-${local.environment}" 16 | module_url = "{{.ModuleUrl}}" 17 | 18 | tags = { 19 | Name = local.name 20 | Project = local.prj_slug_name 21 | Environment = local.environment 22 | Region = local.region 23 | Module = local.module_url 24 | } 25 | } 26 | 27 | terraform { 28 | source = local.module_url 29 | } 30 | 31 | inputs = { 32 | tags = local.tags 33 | } -------------------------------------------------------------------------------- /assets/templates/prj/env.yml.tmpl: -------------------------------------------------------------------------------- 1 | environment: {{.Name}} 2 | blueprint_version: {{.BlueprintVersion}} 3 | -------------------------------------------------------------------------------- /assets/templates/prj/gitignore.tmpl: -------------------------------------------------------------------------------- 1 | passwd 2 | *.key 3 | .*.sw? 4 | .idea 5 | terragrunt.iml 6 | vendor 7 | .terraform 8 | .vscode 9 | *.tfstate 10 | *.tfstate.backup 11 | *.out 12 | .terragrunt-cache 13 | .bundle 14 | .ruby-version 15 | .terraform.lock.hcl 16 | .iac-utils 17 | *-credentials.json -------------------------------------------------------------------------------- /assets/templates/prj/gitignore_region.tmpl: -------------------------------------------------------------------------------- 1 | * 2 | !region.yml 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /assets/templates/prj/prj.yml.tmpl: -------------------------------------------------------------------------------- 1 | name: {{.Name}} 2 | slugName: {{.SlugName}} 3 | blueprint: {{.Blueprint}} 4 | stack: {{.Stack}} 5 | {{if .UseRemoteState}}remoteBucket: {{.RemoteBucket}} 6 | remoteRegion: {{.RemoteRegion}}{{end}} 7 | ignore: [] 8 | 9 | -------------------------------------------------------------------------------- /assets/templates/prj/region.yml.tmpl: -------------------------------------------------------------------------------- 1 | region: {{.Region}} -------------------------------------------------------------------------------- /assets/templates/prj/terragrunt.hcl.tmpl: -------------------------------------------------------------------------------- 1 | # Project {{.Name}} {{.SlugName}} 2 | locals { 3 | 4 | version = "0.0.0" 5 | 6 | prj_vars = yamldecode(file(find_in_parent_folders("prj.yml"))) 7 | env_vars = yamldecode(file(find_in_parent_folders("env.yml"))) 8 | region_vars = yamldecode(file(find_in_parent_folders("region.yml"))) 9 | 10 | name = local.prj_vars.name 11 | slug_name = local.prj_vars.slugName 12 | environment = local.env_vars.environment 13 | region = local.region_vars.region 14 | {{if .UseRemoteState}} 15 | remote_bucket = local.prj_vars.remoteBucket 16 | remote_region = local.prj_vars.remoteRegion 17 | remote_key = "${local.slug_name}/${local.environment}/${local.region}.tfstate" 18 | {{end}} 19 | } 20 | 21 | # Generate an azure provider block 22 | generate "provider" { 23 | path = "provider.tf" 24 | if_exists = "overwrite_terragrunt" 25 | 26 | contents = < %s", source, dest) 226 | 227 | CleanStackFolder(dest, ignoreFolders) 228 | err = lib.LinkChild(source, dest, ignoreFolders, verbose) 229 | } 230 | } 231 | 232 | return 233 | } 234 | 235 | func CleanStackFolder(stackFolder string, ignoreFolders []string) (err error) { 236 | if skip, _ := skipFile(stackFolder, ignoreFolders); !skip { 237 | log.Println("Cleaning folder ", stackFolder) 238 | os.RemoveAll(stackFolder) 239 | } 240 | 241 | return 242 | } 243 | 244 | func CopyBlueprintFolders(workingFolder string, destinyFolder string, ignoreFolders []string, verbose bool) (err error) { 245 | 246 | folders, err := ioutil.ReadDir(workingFolder) 247 | 248 | for _, f := range folders { 249 | 250 | if skip, _ := skipFile(f.Name(), ignoreFolders); !skip { 251 | source := fmt.Sprintf("%s/%s", workingFolder, f.Name()) 252 | dest := fmt.Sprintf("%s/%s", destinyFolder, f.Name()) 253 | 254 | log.Printf("Copying blueprint folder %s -> %s", source, dest) 255 | CleanStackFolder(dest, ignoreFolders) 256 | 257 | err = cp.Copy(source, dest) 258 | if nil != err { 259 | return 260 | } 261 | } 262 | } 263 | 264 | return 265 | } 266 | 267 | func skipFile(path string, ignoreFolders []string) (skip bool, err error) { 268 | 269 | f, err := os.Stat(path) 270 | 271 | if nil == err { 272 | skip = lib.StringInSlice(f.Name(), ignoreFolders) 273 | } else { 274 | skip = lib.StringInSlice(path, ignoreFolders) 275 | } 276 | 277 | if skip { 278 | log.Printf("Skiping file %s", path) 279 | } 280 | 281 | return 282 | } 283 | -------------------------------------------------------------------------------- /project/project_init_test.go: -------------------------------------------------------------------------------- 1 | package project_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/spycode-io/spycli/blueprint" 10 | "github.com/spycode-io/spycli/model" 11 | "github.com/spycode-io/spycli/module" 12 | "github.com/spycode-io/spycli/project" 13 | ) 14 | 15 | func TestLinkingFiles(t *testing.T) { 16 | os.RemoveAll(".iac-test/source") 17 | os.RemoveAll(".iac/destiny") 18 | 19 | os.MkdirAll(".iac-test/source/m1", 0755) 20 | os.MkdirAll(".iac-test/source/m2", 0755) 21 | 22 | f1 := []byte("f1") 23 | os.WriteFile(".iac-test/source/m1/f1", f1, 0644) 24 | 25 | f2 := []byte("f2") 26 | os.WriteFile(".iac-test/source/m2/f2", f2, 0644) 27 | 28 | os.MkdirAll(".iac-test/destiny", 0755) 29 | 30 | project.LinkBlueprintFolders(".iac-test/source", ".iac-test/destiny", []string{}, true) 31 | 32 | if _, err := os.Stat(".iac-test/destiny/m1/f1"); errors.Is(err, os.ErrNotExist) { 33 | t.Fail() 34 | } 35 | 36 | if _, err := os.Stat(".iac-test/destiny/m1/f1"); errors.Is(err, os.ErrNotExist) { 37 | t.Fail() 38 | } 39 | } 40 | 41 | func TestCopyingFiles(t *testing.T) { 42 | os.RemoveAll(".iac-test/source") 43 | os.RemoveAll(".iac/destiny") 44 | 45 | os.MkdirAll(".iac-test/source/m1", 0755) 46 | os.MkdirAll(".iac-test/source/m2", 0755) 47 | 48 | f1 := []byte("f1") 49 | os.WriteFile(".iac-test/source/m1/f1", f1, 0644) 50 | 51 | f2 := []byte("f2") 52 | os.WriteFile(".iac-test/source/m2/f2", f2, 0644) 53 | 54 | os.MkdirAll(".iac-test/destiny", 0755) 55 | 56 | project.CopyBlueprintFolders(".iac-test/source", ".iac-test/destiny", []string{}, true) 57 | 58 | if _, err := os.Stat(".iac-test/destiny/m1/f1"); errors.Is(err, os.ErrNotExist) { 59 | t.Fail() 60 | } 61 | 62 | if _, err := os.Stat(".iac-test/destiny/m1/f1"); errors.Is(err, os.ErrNotExist) { 63 | t.Fail() 64 | } 65 | } 66 | 67 | func TestLocalFlow(t *testing.T) { 68 | 69 | prj, err := NewProjectStructure(t) 70 | if err != nil { 71 | t.Error(err) 72 | } 73 | 74 | _, err = NewBlueprint(t) 75 | if err != nil { 76 | t.Error(err) 77 | } 78 | 79 | err = project.InitProject(prj.ProjectPath, false) 80 | if err != nil { 81 | t.Error(err) 82 | } 83 | } 84 | 85 | func NewProjectStructure(t *testing.T) (*project.ProjectScaffold, error) { 86 | 87 | //Create a new project 88 | os.RemoveAll(".iac-test/my-project") 89 | return project.NewProject( 90 | model.NewScaffold("My Project", ".iac-test", "templates/prj"), 91 | "aws", 92 | "web-stack", 93 | "my-blueprint", 94 | false, 95 | "", 96 | "", 97 | project.DefaultEnvironments, project.DefaultRegions) 98 | } 99 | 100 | func NewBlueprint(t *testing.T) (bp *blueprint.BlueprintScaffold, err error) { 101 | //Create a new blueprint 102 | os.RemoveAll(".iac-test/bp-test") 103 | bpScaffold := model.NewScaffold( 104 | "BP Test", 105 | ".iac-test", "templates/bp") 106 | 107 | bp, err = blueprint.NewBlueprint( 108 | bpScaffold, 109 | "git@github.com:spycode-io/bp-test.git", 110 | []string{}) 111 | 112 | if nil != err { 113 | return 114 | } 115 | 116 | //Create new modules 117 | anyRegionBasePath := fmt.Sprintf(".iac-test/%s/%s/_any", bp.SlugName, bp.Stack) 118 | 119 | vpc, err := CreateModule(anyRegionBasePath, "VPC Test", "vpc") 120 | if nil != err || nil == vpc { 121 | t.Error(err) 122 | } 123 | 124 | vms, err := CreateModule(anyRegionBasePath, "VMS Test", "vm") 125 | if nil != err || nil == vms { 126 | t.Error(err) 127 | } 128 | 129 | return 130 | } 131 | 132 | func TestCleandFolder(t *testing.T) { 133 | os.RemoveAll(".iac-test/clean") 134 | os.MkdirAll(".iac-test/clean", 0755) 135 | } 136 | 137 | func CreateModule(baseFolder string, name string, moduleName string) (*module.Module, error) { 138 | scaffold := model.NewScaffold( 139 | name, 140 | baseFolder, 141 | "templates/mdl") 142 | 143 | return module.NewModule(scaffold, "my-module", true) 144 | } 145 | -------------------------------------------------------------------------------- /project/project_new.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/spycode-io/spycli/model" 9 | ) 10 | 11 | func NewProject( 12 | base *model.Scaffold, 13 | plaform string, 14 | stack string, 15 | blueprint string, 16 | useRemoteState bool, 17 | remoteStateBucket string, 18 | remoteStateRegion string, 19 | environments []string, 20 | regions []string) (project *ProjectScaffold, err error) { 21 | 22 | project = &ProjectScaffold{ 23 | Scaffold: *base, 24 | Platform: plaform, 25 | Stack: stack, 26 | Blueprint: blueprint, 27 | ProjectPath: fmt.Sprintf("%s/%s", base.BasePath, base.SlugName), 28 | UseRemoteState: useRemoteState, 29 | RemoteBucket: remoteStateBucket, 30 | RemoteRegion: remoteStateRegion, 31 | } 32 | 33 | for _, env := range environments { 34 | project.Environments = append(project.Environments, 35 | model.Environment{ 36 | Name: env, 37 | Path: fmt.Sprintf("%s/%s", project.ProjectPath, env), 38 | }, 39 | ) 40 | } 41 | 42 | for _, reg := range regions { 43 | project.Regions = append(project.Regions, 44 | model.Region{Region: reg}, 45 | ) 46 | } 47 | 48 | project.FileSet.WithSet(DefaultFileSet.Set) 49 | 50 | err = project.Init() 51 | 52 | return 53 | } 54 | 55 | func (p *ProjectScaffold) Init() (err error) { 56 | 57 | log.Printf("Initializing %s [%s] %s project on path: %s", p.Name, p.SlugName, p.Platform, p.BasePath) 58 | 59 | //Create base folder if necessary 60 | _, err = os.Stat(p.ProjectPath) 61 | if os.IsNotExist(err) { 62 | os.MkdirAll(p.ProjectPath, 0755) 63 | err = nil 64 | } 65 | 66 | //Write project level files 67 | err = p.FileSet.WriteObjToPath(p.Platform, "platform", p.PlatformPath, p) 68 | if nil != err { 69 | return 70 | } 71 | 72 | //Write project level files 73 | err = p.FileSet.WriteObjToPath(p.Platform, "project", p.ProjectPath, p) 74 | if nil != err { 75 | return 76 | } 77 | 78 | //Create folder structure 79 | for _, env := range p.Environments { 80 | for _, reg := range p.Regions { 81 | regPath := fmt.Sprintf("%s/%s", env.Path, reg.Region) 82 | err = p.FileSet.WriteObjToPath(p.Platform, "region", regPath, reg) 83 | if nil != err { 84 | return 85 | } 86 | } 87 | err = p.FileSet.WriteObjToPath(p.Platform, "environment", env.Path, env) 88 | if nil != err { 89 | return 90 | } 91 | } 92 | 93 | return 94 | } 95 | -------------------------------------------------------------------------------- /project/project_new_test.go: -------------------------------------------------------------------------------- 1 | package project_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/spycode-io/spycli/model" 7 | "github.com/spycode-io/spycli/project" 8 | ) 9 | 10 | func TestCreateAWSProject(t *testing.T) { 11 | 12 | baseScaffold := model.NewScaffold("My Project", ".iac-test", "templates/prj") 13 | 14 | _, err := project.NewProject( 15 | baseScaffold, 16 | "aws", 17 | "my-stack", 18 | "my-blueprint", 19 | false, "", "", 20 | project.DefaultEnvironments, project.DefaultRegions) 21 | if nil != err { 22 | t.Error(err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | version=$1 3 | 4 | env GOOS=linux GOARCH=amd64 go build . && zip -q -r spycli-linux-amd64-$version.zip spycli README.md assets/docs assets/img && rm spycli 5 | env GOOS=darwin GOARCH=amd64 go build . && zip -q -r spycli-darwin-amd64-$version.zip spycli README.md assets/docs assets/img && rm spycli 6 | env GOOS=windows GOARCH=amd64 go build . && zip -q -r spycli-windows-amd64-$version.zip spycli.exe README.md assets/docs assets/img && rm spycli.exe 7 | -------------------------------------------------------------------------------- /tests/integration_test.go: -------------------------------------------------------------------------------- 1 | package tests_test 2 | 3 | // func TestIntegrationLocalFlow(t *testing.T) { 4 | 5 | // //Cleam test folder 6 | // os.RemoveAll(".iac-test") 7 | 8 | // //Create a new blueprint 9 | // bpScaffold := model.NewScaffold( 10 | // "Bp Test", 11 | // ".iac-test", "templates/bp") 12 | 13 | // bp, err := blueprint.NewBlueprint( 14 | // bpScaffold, 15 | // "bp-test", 16 | // []string{}) 17 | 18 | // if nil != err || nil == bp { 19 | // t.Error(err) 20 | // } 21 | 22 | // //Create a new project 23 | // prj, err := project.NewProject( 24 | // model.NewScaffold( 25 | // "My Project", 26 | // ".iac-test", "templates/prj"), 27 | // "aws", 28 | // "web-stack", 29 | // "bp-test", 30 | // false, 31 | // "", 32 | // "", 33 | // project.DefaultEnvironments, project.DefaultRegions) 34 | 35 | // if nil != err || nil == prj { 36 | // t.Error(err) 37 | // } 38 | 39 | // //Create new modules 40 | // anyRegionBasePath := fmt.Sprintf(".iac-test/%s/%s/_any", bp.SlugName, bp.Stack) 41 | 42 | // vpc, err := createModule(anyRegionBasePath, "My VPC", "vpc") 43 | // if nil != err || nil == vpc { 44 | // t.Error(err) 45 | // } 46 | 47 | // vms, err := createModule(anyRegionBasePath, "My VMS", "vm") 48 | // if nil != err || nil == vms { 49 | // t.Error(err) 50 | // } 51 | 52 | // project.InitProject(prj.ProjectPath, true) 53 | 54 | // if _, err := os.Stat(fmt.Sprintf("%s/dev/us-east-1/my-vpc", prj.ProjectPath)); errors.Is(err, os.ErrNotExist) { 55 | // t.FailNow() 56 | // } 57 | 58 | // if _, err := os.Stat(fmt.Sprintf("%s/dev/us-east-1/my-vms", prj.ProjectPath)); errors.Is(err, os.ErrNotExist) { 59 | // t.FailNow() 60 | // } 61 | // } 62 | 63 | // func TestRemoteFlow(t *testing.T) { 64 | 65 | // //Cleam test folder 66 | // os.RemoveAll(".iac-test") 67 | 68 | // //Create a new blueprint 69 | // bpScaffold := model.NewScaffold( 70 | // "Bp Test", 71 | // ".iac-test", "templates/bp") 72 | 73 | // bp, err := blueprint.NewBlueprint( 74 | // bpScaffold, 75 | // "bp-test", 76 | // []string{}) 77 | 78 | // if nil != err || nil == bp { 79 | // t.Error(err) 80 | // } 81 | 82 | // //Create a new project 83 | // prj, err := project.NewProject( 84 | // model.NewScaffold("My Project", ".iac-test", "templates/prj"), 85 | // "aws", 86 | // "web-stack", 87 | // "bp-test", 88 | // false, "", "", 89 | // project.DefaultEnvironments, project.DefaultRegions) 90 | 91 | // if nil != err || nil == prj { 92 | // t.Error(err) 93 | // } 94 | 95 | // //Create new modules 96 | // anyRegionBasePath := fmt.Sprintf(".iac-test/%s/%s/_any", bp.SlugName, bp.Stack) 97 | 98 | // vpc, err := createModule(anyRegionBasePath, "My VPC", "vpc") 99 | // if nil != err || nil == vpc { 100 | // t.Error(err) 101 | // } 102 | 103 | // vms, err := createModule(anyRegionBasePath, "My VMS", "vm") 104 | // if nil != err || nil == vms { 105 | // t.Error(err) 106 | // } 107 | 108 | // err = project.InitProject(prj.ProjectPath, false) 109 | // if nil != err { 110 | // t.Error(err) 111 | // } 112 | // } 113 | 114 | // func createModule(baseFolder string, name string, moduleUrl string) (*module.Module, error) { 115 | // scaffold := model.NewScaffold( 116 | // name, 117 | // baseFolder, 118 | // "templates/mdl") 119 | 120 | // return module.NewModule(scaffold, moduleUrl, true) 121 | // } 122 | --------------------------------------------------------------------------------