Skip to content

Commit

Permalink
Merge pull request #1 from janritter/feat/multi-backend
Browse files Browse the repository at this point in the history
Feat/multi backend
  • Loading branch information
janritter authored Mar 2, 2019
2 parents a55e9e1 + e0f79ea commit abd5fab
Show file tree
Hide file tree
Showing 13 changed files with 330 additions and 127 deletions.
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,58 @@
# Terrastate

[![Build Status](https://travis-ci.com/janritter/terrastate.svg?token=fPhMwJC3SnTkQrfzte44&branch=master)](https://travis-ci.com/janritter/terrastate)
[![Maintainability](https://api.codeclimate.com/v1/badges/235b50a37a1d73929d5c/maintainability)](https://codeclimate.com/github/janritter/terrastate/maintainability)

## Commands
> Tool to manage multiple states in Terraform - Allows Multi account setups
## Use case

> TODO
## Usage

### Generate statefile in the current directory

``` bash
terrastate --var-file ../../dev.tfvars
```

This generates a statefile called terrastate.tf

It will also replace an exisiting terrastate.tf file with the new infromation.

#### Required Terraform variables

Some variables must be set in the varfile used by terrastate. Some of these variables are used by terrastate and others are depending on the backend.

The state_backend var defines the backend type and therefore which backend implementation should be used by terrastate. If the given backend is not supported terrastate returns an error.
In this example s3 is configured as backend.

```bash
state_backend = "s3"
```

The currently supported backends are:

- [S3](docs/s3-backend.md)

Feel free to add a new backend and create a pull request - [How to create a new backend?](docs/own-backend.md)

For more information about the backend specific variables click the backend in the list above.

### Version

``` bash
terrastate version
```

### Help

``` bash
terrastate help
```

## Development

### Resolve dependencies

Expand Down
5 changes: 5 additions & 0 deletions backend/iface/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package iface

type BackendAPI interface {
GenerateStatefileForBackend(in interface{}) error
}
20 changes: 20 additions & 0 deletions backend/mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package backend

import (
"errors"
"log"

"github.com/janritter/terrastate/backend/iface"
"github.com/janritter/terrastate/backend/s3"
)

func GetBackendInterface(backendType string) (iface.BackendAPI, error) {
switch backendType {
case "s3":
return s3.NewS3Backend(), nil
}

err := errors.New("Backend " + backendType + " is currently not supported")
log.Println(err)
return nil, err
}
11 changes: 11 additions & 0 deletions backend/s3/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package s3

type S3BackendAPI interface {
GenerateStatefileForBackend(in interface{}) error
}

type S3Backend struct{}

func NewS3Backend() *S3Backend {
return &S3Backend{}
}
32 changes: 32 additions & 0 deletions backend/s3/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package s3

import (
"io/ioutil"
"log"

"github.com/fatih/color"
)

func createStateFile(in stateConfig) error {
fileContent :=
`terraform {
backend "s3" {
encrypt = true
bucket = "` + in.Bucket + `"
region = "` + in.Region + `"
key = "` + in.Key + `"
dynamodb_table = "` + in.DynamoDBTable + `"
}
}
`

data := []byte(fileContent)
err := ioutil.WriteFile("terrastate.tf", data, 0644)
if err != nil {
log.Println(err)
return err
}

color.Green("Successfully created terrastate.tf")
return nil
}
18 changes: 18 additions & 0 deletions backend/s3/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package s3

import (
"github.com/janritter/terrastate/helper"
)

func (backend *S3Backend) GenerateStatefileForBackend(in interface{}) error {
stateParams := stateConfig{}
err := parseBackendParameter(in, &stateParams)
if err != nil {
return err
}

helper.PrintStateValues(stateParams)

err = createStateFile(stateParams)
return err
}
93 changes: 93 additions & 0 deletions backend/s3/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package s3

import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"reflect"
"strings"
)

func parseBackendParameter(in interface{}, out *stateConfig) error {
switch in.(type) {
case map[string]interface{}:
mapped := in.(map[string]interface{})

if mapped["state_bucket"] == nil {
err := errors.New("state_bucket must be defined")
log.Println(err)
return err
}
if reflect.TypeOf(mapped["state_bucket"]).String() != "string" {
err := errors.New("state_bucket must be of type string, was " + reflect.TypeOf(mapped["state_bucket"]).String())
log.Println(err)
return err
}
out.Bucket = mapped["state_bucket"].(string)

if mapped["state_dynamodb_table"] == nil {
err := errors.New("state_dynamodb_table must be defined")
log.Println(err)
return err
}
if reflect.TypeOf(mapped["state_dynamodb_table"]).String() != "string" {
err := errors.New("state_dynamodb_table must be of type string, was " + reflect.TypeOf(mapped["state_dynamodb_table"]).String())
log.Println(err)
return err
}
out.DynamoDBTable = mapped["state_dynamodb_table"].(string)

if mapped["state_key"] == nil {
err := errors.New("state_key must be defined")
log.Println(err)
return err
}
if reflect.TypeOf(mapped["state_key"]).String() != "string" {
err := errors.New("state_key must be of type string, was " + reflect.TypeOf(mapped["state_key"]).String())
log.Println(err)
return err
}

// Remove all spaces
key := strings.ReplaceAll(mapped["state_key"].(string), " ", "")

// Check if the key string contains current.dir
if !strings.Contains(key, "{{current.dir}}") {
err := errors.New("{{current.dir}} is missing the state_key string")
log.Println(err)
return err
}

// Replace placeholder with current dir
path, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
dir := filepath.Base(path)
fmt.Println("Current Directory = " + dir)
key = strings.ReplaceAll(key, "{{current.dir}}", dir)

out.Key = key

if mapped["region"] == nil {
err := errors.New("region must be defined")
log.Println(err)
return err
}
if reflect.TypeOf(mapped["region"]).String() != "string" {
err := errors.New("region must be of type string, was " + reflect.TypeOf(mapped["region"]).String())
log.Println(err)
return err
}
out.Region = mapped["region"].(string)

default:
err := errors.New("Unknown var-file format")
log.Println(err)
return err
}

return nil
}
8 changes: 8 additions & 0 deletions backend/s3/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package s3

type stateConfig struct {
Bucket string
DynamoDBTable string
Key string
Region string
}
Loading

0 comments on commit abd5fab

Please sign in to comment.