Skip to content

Commit

Permalink
Allow configuration per os
Browse files Browse the repository at this point in the history
Add run-after and run-before scripts support
  • Loading branch information
jocgir committed Jan 26, 2018
1 parent 99eb9ed commit defb3ef
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 67 deletions.
67 changes: 46 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,19 @@ or install it through command line:
On `OSX`:

```bash
curl -sL https://github.com/coveo/tgf/releases/download/v1.15.4/tgf_1.15.4_macOS_64-bits.zip | bsdtar -xf- -C /usr/local/bin && chmod +x /usr/local/bin/tgf
curl -sL https://github.com/coveo/tgf/releases/download/v1.16.0/tgf_1.16.0_macOS_64-bits.zip | bsdtar -xf- -C /usr/local/bin && chmod +x /usr/local/bin/tgf
```

On `Linux`:

```bash
curl -sL https://github.com/coveo/tgf/releases/download/v1.15.4/tgf_1.15.4_linux_64-bits.zip | gzip -d > /usr/local/bin/tgf && chmod +x /usr/local/bin/tgf
curl -sL https://github.com/coveo/tgf/releases/download/v1.16.0/tgf_1.16.0_linux_64-bits.zip | gzip -d > /usr/local/bin/tgf && chmod +x /usr/local/bin/tgf
```

On `Windows` with Powershell:

```powershell
Invoke-WebRequest https://github.com/coveo/tgf/releases/download/v1.15.4/tgf_1.15.4_windows_64-bits.zip -OutFile tgf.zip
Invoke-WebRequest https://github.com/coveo/tgf/releases/download/v1.16.0/tgf_1.16.0_windows_64-bits.zip -OutFile tgf.zip
```

## Configuration
Expand Down Expand Up @@ -97,9 +97,33 @@ Key | Description | Default value
| tgf-recommended-version | The minimal tgf version recommended in your context (should not be placed in `.tgf.config file`) | *no default*
| recommended-image | The tgf image recommended in your context (should not be placed in `.tgf.config file`) | *no default*
| environment | Allows temporary addition of environment variables | *no default*
| run-before | Script that is executed before the actual command | *no default*
| run-after | Script that is executed after the actual command | *no default*

Note: *The key names are not case sensitive*

### Configuration section

It is possible to specify configuration elements that only apply on specific os.

Example of HCL configuration file:

```text
docker-refresh: 1h
logging-level: notice
windows:
logging-level: debug
linux:
docker-refresh: 2h
```

section | Description
--- | ---
| windows | Configuration that is applied only on Windows systems
| linux | Configuration that is applied only on Linux systems
| darwin | Configuration that is applied only on OSX systems
| ix | Configuration that is applied only on Linux or OSX systems

## TGF Invocation

```text
Expand Down Expand Up @@ -130,34 +154,35 @@ invocation arguments.
tgf ls -- -D # Avoid -D to be interpretated by tgf as --debug-docker
VERSION: 1.15.4
VERSION: 1.16.0
AUTHOR: Coveo
Flags:
-H, --tgf-help Show context-sensitive help (also try --help-man).
-D, --debug-docker Print the docker command issued
-F, --flush-cache Invoke terragrunt with --terragrunt-update-source to flush the cache
--refresh-image Force a refresh of the docker image (alias --ri)
--docker-arg=<opt> ... Supply extra argument to Docker (alias --da)
--get-image-name Just return the resulting image name (alias --gi)
--no-home Disable the mapping of the home directory (alias --nh)
--no-temp Disable the mapping of the temp directory (alias --nt)
-E, --entrypoint=terragrunt Override the entry point for docker
--image=coveo/tgf Use the specified image instead of the default one
--image-version=version Use a different version of docker image instead of the default one (alias --iv)
-T, --tag=latest Use a different tag of docker image instead of the default one
-P, --profile="" Set the AWS profile configuration to use
-L, --logging-level=<level> Set the logging level (critical=0, error=1, warning=2, notice=3, info=4, debug=5, full=6)
--all-versions Get versions of TGF & all others underlying utilities (alias --av)
--current-version Get current version infomation (alias --cv)
-H, --tgf-help Show context-sensitive help (also try --help-man).
-D, --debug-docker Print the docker command issued
-F, --flush-cache Invoke terragrunt with --terragrunt-update-source to flush the cache
--refresh-image Force a refresh of the docker image (alias --ri)
--get-image-name Just return the resulting image name (alias --gi)
--no-home Disable the mapping of the home directory (alias --nh)
--no-temp Disable the mapping of the temp directory (alias --nt)
--mount-point=MOUNT-POINT Specify a mount point for the current folder --mp)
--docker-arg=<opt> ... Supply extra argument to Docker (alias --da)
--all-versions Get versions of TGF & all others underlying utilities (alias --av)
--current-version Get current version infomation (alias --cv)
-E, --entrypoint=terragrunt Override the entry point for docker
--image=coveo/tgf Use the specified image instead of the default one
--image-version=version Use a different version of docker image instead of the default one (alias --iv)
-T, --tag=latest Use a different tag of docker image instead of the default one
-P, --profile="" Set the AWS profile configuration to use
-L, --logging-level=<level> Set the logging level (critical=0, error=1, warning=2, notice=3, info=4, debug=5, full=6)
```

Example:

```bash
> tgf --current-version
tgf v1.15.4
tgf v1.16.0
```

Returns the current version of the tgf tool
Expand Down
82 changes: 68 additions & 14 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"path/filepath"
"reflect"
"regexp"
"runtime"
"sort"
"strings"
"time"
Expand Down Expand Up @@ -37,8 +38,11 @@ const (
requiredImageVersion = "required-image-version"
deprecatedRecommendedImage = "recommended-image"
environment = "environment"
runBefore = "run-before"
runAfter = "run-after"
)

// TGFConfig contains the resulting configuration that will be applied
type TGFConfig struct {
Image string
ImageVersion *string
Expand All @@ -52,6 +56,7 @@ type TGFConfig struct {
RequiredVersionRange string
RecommendedTGFVersion string
Environment map[string]string
RunBefore, RunAfter []string

recommendedImage string
separator string
Expand Down Expand Up @@ -122,35 +127,67 @@ func (config *TGFConfig) InitAWS(profile string) error {
// SetDefaultValues sets the uninitialized values from the config files and the parameter store
func (config *TGFConfig) SetDefaultValues() {
for _, configFile := range findConfigFiles(Must(os.Getwd()).(string)) {
var result interface{}
var content interface{}
if debug {
printfDebug(os.Stderr, "# Reading configuration from %s\n", configFile)
}
content := Must(ioutil.ReadFile(configFile)).([]byte)
errYAML := yaml.Unmarshal(content, &result)
fileContent := Must(ioutil.ReadFile(configFile)).([]byte)
errYAML := yaml.Unmarshal(fileContent, &content)
if errYAML != nil {
errHCL := hcl.Unmarshal(content, &result)
errHCL := hcl.Unmarshal(fileContent, &content)
if errHCL != nil {
fmt.Fprintln(os.Stderr, errorString("Error while loading configuration file %s\nConfiguration file must be valid YAML, JSON or HCL", configFile))
continue
}
result = hcl.Flatten(utils.MapKeyInterface2string(result).(map[string]interface{}))
content = hcl.Flatten(utils.MapKeyInterface2string(content).(map[string]interface{}))
} else {
result = utils.MapKeyInterface2string(result).(map[string]interface{})
content = utils.MapKeyInterface2string(content).(map[string]interface{})
}

switch result := result.(type) {
switch content := content.(type) {
case map[string]interface{}:
// We sort the keys to ensure that we alway process them in the same order
keys := make([]string, 0, len(result))
for key := range result {
keys = append(keys, key)
extract := func(key string) (result interface{}) {
result = content[key]
delete(content, key)
return
}
sort.Strings(keys)

for _, key := range keys {
config.SetValue(key, result[key])
apply := func(content interface{}) {
if content == nil {
return
}
switch content := content.(type) {
case map[string]interface{}:
// We sort the keys to ensure that we alway process them in the same order
keys := make([]string, 0, len(content))
for key := range content {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
config.SetValue(key, content[key])
}
default:
fmt.Fprintln(os.Stderr, errorString("Invalid configuration format in file %s", configFile))
}
}

windows := extract("windows")
darwin := extract("darwin")
linux := extract("linux")
ix := extract("ix")

switch runtime.GOOS {
case "windows":
apply(windows)
case "darwin":
apply(darwin)
apply(ix)
case "linux":
apply(linux)
apply(ix)
}
apply(content)
default:
fmt.Fprintln(os.Stderr, errorString("Invalid configuration format in file %s", configFile))
}
Expand Down Expand Up @@ -237,6 +274,23 @@ func (config *TGFConfig) SetValue(key string, value interface{}) {
default:
fmt.Fprintln(os.Stderr, warningString("Environment must be a map of key/value %T", value))
}
case runBefore, runAfter:
list := &config.RunBefore
if key == runAfter {
list = &config.RunAfter
}
switch value := value.(type) {
case string:
*list = append(*list, value)
case []interface{}:
for i := len(value) - 1; i >= 0; i-- {
*list = append(*list, fmt.Sprint(value[i]))
}
case map[string]interface{}:
for _, value := range value {
*list = append(*list, fmt.Sprint(value))
}
}
case deprecatedRecommendedImage:
fmt.Fprintln(os.Stderr, warningString("Config key %s is deprecated (%s ignored)", key, valueStr))
default:
Expand Down
27 changes: 27 additions & 0 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import (
"os/user"
"path/filepath"
"runtime"
"sort"
"strings"
"syscall"

"github.com/blang/semver"
"github.com/coveo/gotemplate/utils"
"github.com/fatih/color"
"github.com/gruntwork-io/terragrunt/util"
)
Expand Down Expand Up @@ -122,6 +124,9 @@ func callDocker(args ...string) int {
printfDebug(os.Stderr, "%s\n\n", strings.Join(dockerCmd.Args, " "))
}

if err := runCommands(config.RunBefore); err != nil {
return -1
}
if err := dockerCmd.Run(); err != nil {
if stderr.Len() > 0 {
fmt.Fprintf(os.Stderr, errorString(stderr.String()))
Expand All @@ -132,11 +137,33 @@ func callDocker(args ...string) int {
}
}
}
if err := runCommands(config.RunAfter); err != nil {
fmt.Fprintf(os.Stderr, errorString("%v", err))
}

return dockerCmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
}

var printfDebug = color.New(color.FgWhite, color.Faint).FprintfFunc()

func runCommands(commands []string) error {
sort.Sort(sort.Reverse(sort.StringSlice(commands)))
for _, script := range commands {
cmd, tempFile, err := utils.GetCommandFromString(script)
if err != nil {
return err
}
if tempFile != "" {
defer func() { os.Remove(tempFile) }()
}
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
return err
}
}
return nil
}

// Returns the image name to use
// If docker-image-build option has been set, an image is dynamically built and the resulting image digest is returned
func getImage() string {
Expand Down
Loading

0 comments on commit defb3ef

Please sign in to comment.