github.com/aminjam/goflat@v0.4.1-0.20160331105230-ec639fc0d5b3/README.md (about) 1 # goflat [![Build Status](https://travis-ci.org/aminjam/goflat.png?branch=master)](https://travis-ci.org/aminjam/goflat) 2 A Go template flattener `goflat` is for creating complex configuration files (JSON, YAML, XML, etc.). 3 4 ## Motivation 5 Building long configuration files is not fun nor testable. Replacing passwords and secrets in a configuration file is usually done with regex and sometimes it's unpredictable! Why not use go templates, along with individual `.go` input files, that know how to unmarshall and parse their own data structure. This way we can build a complex configuration with inputs coming from different `structs` that we can test their behavior independently. That is what `goflat` does. A small and simple go template flattener that uses go runtime to dynamically create a template for parsing go structs. 6 7 ## Getting Started 8 9 ### Run as executable 10 ``` 11 go get github.com/aminjam/goflat/cmd/goflat 12 $GOPATH/bin/goflat --help 13 ``` 14 ### Run from source 15 Built with Go 1.5.3 and `GO15VENDOREXPERIMENT` flag. 16 ``` 17 git clone https://github.com/aminjam/goflat.git && cd goflat 18 make init 19 make build 20 ./pkg/*/goflat --help 21 ``` 22 ### Usage 23 ``` 24 goflat -t FILE.{yml,json,xml} -i private.go -i teams.go -i repos.go ... 25 ``` 26 ``` 27 goflat -t FILE.{yml,json,xml} -i <(lpass show 'private.go' --notes):Private 28 ``` 29 ## Example 30 31 Here is a sample YAML configuration used for creating [concourse](https://concourse.ci) pipeline. 32 ``` 33 {{ $global := . }} 34 resources: 35 - name: ci 36 type: git 37 source: 38 uri: https://github.com/cloudfoundry/myproject-ci.git 39 {{range .Repos}} 40 - name: {{.Name}} 41 type: git 42 source: 43 uri: {{.Repo}} 44 branch: {{.Branch}} 45 {{end}} 46 47 jobs: 48 {{range .Repos}} 49 - name: {{.Name}} 50 serial: true 51 plan: 52 - aggregate: 53 - get: ci 54 - get: project 55 resource: {{.Name}} 56 trigger: true 57 - task: do-something 58 config: 59 platform: linux 60 image: "docker:///alpine" 61 run: 62 path: sh 63 args: ["-c","echo Hi"] 64 params: 65 PASSWORD: {{$global.Private.Password}} 66 SECRET: {{$global.Private.Secret}} 67 {{end}} 68 - name-{{.Private.Secret}}: {{.Private.Password}} 69 - comma-seperated-repo-names: {{.Repos.Names | join ","}} 70 ``` 71 We have `Repos` and `Private` struct that contain some runtime data that needs to be parsed into the template. Here is a look at the checked-in `private.go` 72 73 ``` 74 package main 75 76 type Private struct { 77 Password string 78 Secret string 79 } 80 81 func NewPrivate() Private { 82 return Private{ 83 Password: "team3", 84 Secret: "cloud-foundry", 85 } 86 } 87 ``` 88 Each of the input files are required to have 2 things: 89 * A struct named after the filename (e.g. filename `hello-world.go` should have `HelloWorld` struct). If the struct name differs from the filename convention, you can optionally provide the name of the struct (e.g. `-i <(lpass show 'file.go' --notes):Private`) 90 * A `New{{.StructName}}` function that returns `{{.StructName}}` (e.g. `func NewPrivate() Private{}`) 91 92 Similarly, we can also define `repos.go` as an array of objects to use within `{{range .Repos}}`. 93 ``` 94 package main 95 96 type Repos []struct { 97 Name string 98 Repo string 99 Branch string 100 } 101 102 func (r Repos) Names() []string { 103 names := make([]string, len(r)) 104 for k, v := range r { 105 names[k] = v.Name 106 } 107 return names 108 } 109 110 func NewRepos() Repos { 111 return Repos{ 112 { 113 Name: "repo1", 114 Repo: "https://github.com/jane/repo1", 115 Branch: "master", 116 }, 117 { 118 Name: "repo2", 119 Repo: "https://github.com/john/repo2", 120 Branch: "develop", 121 }, 122 } 123 } 124 ``` 125 Now we can run the sample configuration in the `.examples`. 126 127 ``` 128 goflat -t .examples/template.yml -i .examples/inputs/repos.go -i .examples/inputs/private.go 129 ``` 130 131 ### Pipes "|" 132 Pipes can be nested and here is a set of supported helper functions: 133 134 - **join**: `{{.List | join "," }}` 135 - **map**: `{{.ListOfObjects | map "Name,Age" "," }}` (comma seperated property names) 136 - **replace**: `{{.StringValue | replace "," " " }}` 137 - **split**: `{{.StringValue | split "," }}` 138 - **toLower**: `{{.Field | toLower }}` 139 - **toUpper**: `{{.Field | toUpper }}` 140 141 You can optionally define a custom list of helper functions that overrides or extends the behavior of the default pipes. See [an exmaple](.examples/pipes/pipes.go) file that can optionally be passed via `--pipes` flag. Note that the function signature has to be the following: 142 143 ``` 144 func CustomPipes() tempate.FuncMap { 145 ... 146 } 147 ```