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  ```