Browse Source

Initial Working 0.5 version

pull/26/head
Jacob McCann 8 years ago
parent
commit
0208bfdc19
  1. 4
      DOCS.md
  2. 2
      Dockerfile
  3. 261
      main.go
  4. 190
      plugin.go

4
DOCS.md

@ -6,6 +6,8 @@ Use the Terraform plugin to apply the infrastructure configuration contained wit
* `config` - a map of configuration parameters for the remote state backend. Each value is passed as a `-backend-config=<key>=<value>` option.
* `vars` - a map of variables to pass to the Terraform `plan` and `apply` commands. Each value is passed as a `-var
<key>=<value>` option.
* `secrets` - a map of variables to pass to the Terraform `plan` and `apply` commands. Each value is passed as a `-var
<key>=<ENVVAR>` option. The `ENVVAR` is read as the key/pair value.
* `ca_cert` - ca cert to add to your environment to allow terraform to use internal/private resources
* `sensitive` (default: `false`) - Whether or not to suppress terraform commands to stdout.
* `role_arn_to_assume` - A role to assume before running the terraform commands.
@ -27,6 +29,8 @@ deploy:
vars:
app_name: my-project
app_version: 1.0.0
secrets:
my_secret: TERRAFORM_SECRET
```
# Advanced Configuration

2
Dockerfile

@ -6,7 +6,7 @@
FROM gliderlabs/alpine:3.2
RUN apk-install ca-certificates git
ENV TERRAFORM_VERSION 0.6.16
ENV TERRAFORM_VERSION 0.7.5
RUN apk update && \
wget -q "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.21-r2/glibc-2.21-r2.apk" && \

261
main.go

@ -1,188 +1,115 @@
package main
import (
"fmt"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/drone/drone-plugin-go/plugin"
"io/ioutil"
"encoding/json"
"os"
"os/exec"
"strings"
"time"
)
var (
buildCommit string
"github.com/joho/godotenv"
"github.com/Sirupsen/logrus"
"github.com/urfave/cli"
)
type terraform struct {
Remote remote `json:"remote"`
Plan bool `json:"plan"`
Vars map[string]string `json:"vars"`
Cacert string `json:"ca_cert"`
Sensitive bool `json:"sensitive"`
RoleARN string `json:"role_arn_to_assume"`
RootDir string `json:"root_dir"`
Parallelism int `json:"parallelism"`
}
type remote struct {
Backend string `json:"backend"`
Config map[string]string `json:"config"`
}
var version string // build number set at compile-time
func main() {
fmt.Printf("Drone Terraform Plugin built from %s\n", buildCommit)
workspace := plugin.Workspace{}
vargs := terraform{}
plugin.Param("workspace", &workspace)
plugin.Param("vargs", &vargs)
plugin.MustParse()
if vargs.RoleARN != "" {
assumeRole(vargs.RoleARN)
}
var commands []*exec.Cmd
remote := vargs.Remote
if vargs.Cacert != "" {
commands = append(commands, installCaCert(vargs.Cacert))
}
if remote.Backend != "" {
commands = append(commands, deleteCache())
commands = append(commands, remoteConfigCommand(remote))
}
commands = append(commands, getModules())
commands = append(commands, planCommand(vargs.Vars, vargs.Parallelism))
if !vargs.Plan {
commands = append(commands, applyCommand(vargs.Parallelism))
}
commands = append(commands, deleteCache())
for _, c := range commands {
c.Env = os.Environ()
c.Dir = workspace.Path
if c.Dir == "" {
wd, err := os.Getwd()
if err == nil {
c.Dir = wd
}
}
if vargs.RootDir != "" {
c.Dir = c.Dir + "/" + vargs.RootDir
}
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if !vargs.Sensitive {
trace(c)
}
err := c.Run()
if err != nil {
fmt.Println("Error!")
fmt.Println(err)
os.Exit(1)
app := cli.NewApp()
app.Name = "terraform plugin"
app.Usage = "terraform plugin"
app.Action = run
app.Version = version
app.Flags = []cli.Flag{
//
// plugin args
//
cli.BoolFlag{
Name: "terraform.plan",
Usage: "calculates a plan but does NOT apply it",
EnvVar: "PLUGIN_PLAN",
},
cli.StringFlag{
Name: "terraform.remote",
Usage: "contains the configuration for the Terraform remote state tracking",
EnvVar: "PLUGIN_REMOTE",
},
cli.StringFlag{
Name: "terraform.vars",
Usage: "a map of variables to pass to the Terraform `plan` and `apply` commands. Each value is passed as a `<key>=<value>` option",
EnvVar: "PLUGIN_VARS",
},
cli.StringFlag{
Name: "terraform.secrets",
Usage: "a map of secrets to pass to the Terraform `plan` and `apply` commands. Each value is passed as a `<key>=<ENV>` option",
EnvVar: "PLUGIN_SECRETS",
},
cli.StringFlag{
Name: "terraform.ca_cert",
Usage: "ca cert to add to your environment to allow terraform to use internal/private resources",
EnvVar: "PLUGIN_CA_CERT",
},
cli.BoolFlag{
Name: "terraform.sensitive",
Usage: "whether or not to suppress terraform commands to stdout",
EnvVar: "PLUGIN_SENSITIVE",
},
cli.StringFlag{
Name: "terraform.role_arn_to_assume",
Usage: "A role to assume before running the terraform commands",
EnvVar: "PLUGIN_ROLE_ARN_TO_ASSUME",
},
cli.StringFlag{
Name: "terraform.root_dir",
Usage: "The root directory where the terraform files live. When unset, the top level directory will be assumed",
EnvVar: "PLUGIN_ROOT_DIR",
},
cli.IntFlag{
Name: "terraform.parallelism",
Usage: "The number of concurrent operations as Terraform walks its graph",
EnvVar: "PLUGIN_PARALLELISM",
},
cli.StringFlag{
Name: "env-file",
Usage: "source env file",
},
}
if err := app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
fmt.Println("Command completed successfully")
}
}
func installCaCert(cacert string) *exec.Cmd {
ioutil.WriteFile("/usr/local/share/ca-certificates/ca_cert.crt", []byte(cacert), 0644)
return exec.Command(
"update-ca-certificates",
)
}
func deleteCache() *exec.Cmd {
return exec.Command(
"rm",
"-rf",
".terraform",
)
}
func remoteConfigCommand(config remote) *exec.Cmd {
args := []string{
"remote",
"config",
fmt.Sprintf("-backend=%s", config.Backend),
}
for k, v := range config.Config {
args = append(args, fmt.Sprintf("-backend-config=%s=%s", k, v))
func run(c *cli.Context) error {
if c.String("env-file") != "" {
_ = godotenv.Load(c.String("env-file"))
}
return exec.Command(
"terraform",
args...,
)
}
func getModules() *exec.Cmd {
return exec.Command(
"terraform",
"get",
)
}
func planCommand(variables map[string]string, parallelism int) *exec.Cmd {
args := []string{
"plan",
"-out=plan.tfout",
}
for k, v := range variables {
args = append(args, "-var")
args = append(args, fmt.Sprintf("%s=%s", k, v))
}
if parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", parallelism))
}
return exec.Command(
"terraform",
args...,
)
}
remote := Remote{}
json.Unmarshal([]byte(c.String("terraform.remote")), &remote)
func applyCommand(parallelism int) *exec.Cmd {
args := []string{
"apply",
var vars map[string]string
if err := json.Unmarshal([]byte(c.String("terraform.vars")), &vars); err != nil {
panic(err)
}
if parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", parallelism))
}
args = append(args, "plan.tfout")
return exec.Command(
"terraform",
args...,
)
}
func assumeRole(roleArn string) {
client := sts.New(session.New())
duration := time.Hour * 1
stsProvider := &stscreds.AssumeRoleProvider{
Client: client,
Duration: duration,
RoleARN: roleArn,
RoleSessionName: "drone",
var secrets map[string]string
if err := json.Unmarshal([]byte(c.String("terraform.secrets")), &secrets); err != nil {
panic(err)
}
value, err := credentials.NewCredentials(stsProvider).Get()
if err != nil {
fmt.Println("Error assuming role!")
fmt.Println(err)
os.Exit(1)
plugin := Plugin{
Config: Config{
Remote: remote,
Plan: c.Bool("terraform.plan"),
Vars: vars,
Secrets: secrets,
Cacert: c.String("terraform.ca_cert"),
Sensitive: c.Bool("terraform.sensitive"),
RoleARN: c.String("terraform.role_arn_to_assume"),
RootDir: c.String("terraform.root_dir"),
Parallelism: c.Int("terraform.parallelism"),
},
}
os.Setenv("AWS_ACCESS_KEY_ID", value.AccessKeyID)
os.Setenv("AWS_SECRET_ACCESS_KEY", value.SecretAccessKey)
os.Setenv("AWS_SESSION_TOKEN", value.SessionToken)
}
func trace(cmd *exec.Cmd) {
fmt.Println("$", strings.Join(cmd.Args, " "))
return plugin.Exec()
}

190
plugin.go

@ -0,0 +1,190 @@
package main
import (
"fmt"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"io/ioutil"
"os"
"os/exec"
"strings"
"time"
)
var (
buildCommit string
)
type (
Config struct {
Remote Remote
Plan bool
Vars map[string]string
Secrets map[string]string
Cacert string
Sensitive bool
RoleARN string
RootDir string
Parallelism int
}
Remote struct {
Backend string `json:"backend"`
Config map[string]string `json:"config"`
}
Plugin struct {
Config Config
}
)
func (p Plugin) Exec() error {
fmt.Printf("Drone Terraform Plugin built from %s\n", buildCommit)
if p.Config.RoleARN != "" {
assumeRole(p.Config.RoleARN)
}
var commands []*exec.Cmd
remote := p.Config.Remote
if p.Config.Cacert != "" {
commands = append(commands, installCaCert(p.Config.Cacert))
}
if remote.Backend != "" {
commands = append(commands, deleteCache())
commands = append(commands, remoteConfigCommand(remote))
}
commands = append(commands, getModules())
commands = append(commands, planCommand(p.Config.Vars, p.Config.Secrets, p.Config.Parallelism))
if !p.Config.Plan {
commands = append(commands, applyCommand(p.Config.Parallelism))
}
commands = append(commands, deleteCache())
for _, c := range commands {
if c.Dir == "" {
wd, err := os.Getwd()
if err == nil {
c.Dir = wd
}
}
if p.Config.RootDir != "" {
c.Dir = c.Dir + "/" + p.Config.RootDir
}
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if !p.Config.Sensitive {
trace(c)
}
err := c.Run()
if err != nil {
fmt.Println("Error!")
fmt.Println(err)
os.Exit(1)
}
fmt.Println("Command completed successfully")
}
return nil
}
func installCaCert(cacert string) *exec.Cmd {
ioutil.WriteFile("/usr/local/share/ca-certificates/ca_cert.crt", []byte(cacert), 0644)
return exec.Command(
"update-ca-certificates",
)
}
func deleteCache() *exec.Cmd {
return exec.Command(
"rm",
"-rf",
".terraform",
)
}
func remoteConfigCommand(config Remote) *exec.Cmd {
args := []string{
"remote",
"config",
fmt.Sprintf("-backend=%s", config.Backend),
}
for k, v := range config.Config {
args = append(args, fmt.Sprintf("-backend-config=%s=%s", k, v))
}
return exec.Command(
"terraform",
args...,
)
}
func getModules() *exec.Cmd {
return exec.Command(
"terraform",
"get",
)
}
func planCommand(variables map[string]string, secrets map[string]string, parallelism int) *exec.Cmd {
args := []string{
"plan",
"-out=plan.tfout",
}
for k, v := range variables {
args = append(args, "-var")
args = append(args, fmt.Sprintf("%s=%s", k, v))
}
for k, v := range secrets {
args = append(args, "-var")
args = append(args, fmt.Sprintf("%s=%s", k, os.Getenv(v)))
}
if parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", parallelism))
}
return exec.Command(
"terraform",
args...,
)
}
func applyCommand(parallelism int) *exec.Cmd {
args := []string{
"apply",
}
if parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", parallelism))
}
args = append(args, "plan.tfout")
return exec.Command(
"terraform",
args...,
)
}
func assumeRole(roleArn string) {
client := sts.New(session.New())
duration := time.Hour * 1
stsProvider := &stscreds.AssumeRoleProvider{
Client: client,
Duration: duration,
RoleARN: roleArn,
RoleSessionName: "drone",
}
value, err := credentials.NewCredentials(stsProvider).Get()
if err != nil {
fmt.Println("Error assuming role!")
fmt.Println(err)
os.Exit(1)
}
os.Setenv("AWS_ACCESS_KEY_ID", value.AccessKeyID)
os.Setenv("AWS_SECRET_ACCESS_KEY", value.SecretAccessKey)
os.Setenv("AWS_SESSION_TOKEN", value.SessionToken)
}
func trace(cmd *exec.Cmd) {
fmt.Println("$", strings.Join(cmd.Args, " "))
}
Loading…
Cancel
Save