Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(*): add custom action support #19

Merged
merged 3 commits into from
Aug 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions cmd/terraform/invoke.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"github.com/spf13/cobra"

"github.com/deislabs/porter-terraform/pkg/terraform"
)

func buildInvokeCommand(mixin *terraform.Mixin) *cobra.Command {
opts := terraform.ExecuteCommandOptions{}

cmd := &cobra.Command{
Use: "invoke",
Short: "Execute the invoke functionality of this mixin",
RunE: func(cmd *cobra.Command, args []string) error {
return mixin.Execute(opts)
},
}
flags := cmd.Flags()
flags.StringVar(&opts.Action, "action", "", "Custom action name to invoke.")

return cmd
}
2 changes: 1 addition & 1 deletion cmd/terraform/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ func buildRootCommand(in io.Reader) *cobra.Command {
cmd.AddCommand(buildSchemaCommand(m))
cmd.AddCommand(buildBuildCommand(m))
cmd.AddCommand(buildInstallCommand(m))
cmd.AddCommand(buildInvokeCommand(m))
cmd.AddCommand(buildUninstallCommand(m))
cmd.AddCommand(buildUpgradeCommand(m))
cmd.AddCommand(buildStatusCommand(m))

return cmd
}
17 changes: 0 additions & 17 deletions cmd/terraform/status.go

This file was deleted.

9 changes: 7 additions & 2 deletions examples/azure-aks/porter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ parameters:
destination:
env: TF_VAR_resource_group_name

customActions:
show:
description: "Invoke 'terraform show'"
modifies: false

install:
- terraform:
description: "Install Azure Kubernetes Service"
Expand All @@ -89,9 +94,9 @@ upgrade:
container_name: "{{ bundle.credentials.backend_storage_container }}"
access_key: "{{ bundle.credentials.backend_storage_access_key }}"

status:
show:
- terraform:
description: "Get Azure Kubernetes Service status"
description: "Invoke 'terraform show'"
backendConfig:
key: "{{ bundle.name }}.tfstate"
storage_account_name: "{{ bundle.credentials.backend_storage_account }}"
Expand Down
9 changes: 7 additions & 2 deletions examples/azure-keyvault/porter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ parameters:
destination:
env: TF_VAR_resource_group_name

customActions:
show:
description: "Invoke 'terraform show'"
modifies: false

install:
- terraform:
description: "Install Azure Key Vault"
Expand All @@ -73,9 +78,9 @@ upgrade:
outputs:
- name: vault_uri

status:
show:
- terraform:
description: "Get Azure Key Vault status"
description: "Invoke 'terraform show'"
backendConfig:
key: "{{ bundle.name }}.tfstate"
storage_account_name: "{{ bundle.credentials.backend_storage_account }}"
Expand Down
2 changes: 2 additions & 0 deletions examples/basic-tf-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Dockerfile
.cnab
75 changes: 75 additions & 0 deletions examples/basic-tf-example/porter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
mixins:
- terraform

name: basic-tf-example
version: 0.1.0
invocationImage: basic-tf-example:latest
tag: deislabs/basic-tf-example-bundle:v0.1.0

outputs:
- name: a
type: string
applyTo:
- install
- upgrade
- name: b
type: string
applyTo:
- upgrade
- name: c
type: string
applyTo:
- install

customActions:
plan:
description: "Invoke 'terraform plan'"
modifies: false
stateless: true
show:
description: "Invoke 'terraform show'"
modifies: false
stateless: true
printVersion:
description: "Invoke 'terraform version'"
modifies: false
stateless: true

install:
- terraform:
description: "Install Terraform assets"
autoApprove: true
outputs:
- name: a
- name: b
- name: c

upgrade:
- terraform:
description: "Upgrade Terraform assets"
autoApprove: true
outputs:
- name: a
- name: b
- name: c

show:
- terraform:
description: "Invoke 'terraform show'"

plan:
- terraform:
description: "Invoke 'terraform plan'"

# Note: this can't be 'version:' as this would conflict with top-level field
# Hence the need for the 'command:' override
printVersion:
- terraform:
description: "Invoke 'terraform version'"
command: "version"

uninstall:
- terraform:
autoApprove: true
description: "Uninstall Terraform assets"

Empty file.
11 changes: 11 additions & 0 deletions examples/basic-tf-example/terraform/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
output "a" {
value = "a"
}

output "b" {
value = "b"
}

output "c" {
value = "c"
}
120 changes: 120 additions & 0 deletions pkg/terraform/execute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package terraform

import (
"fmt"
"os"
"strings"

"github.com/pkg/errors"

yaml "gopkg.in/yaml.v2"
)

type ExecuteCommandOptions struct {
Action string
}

type ExecuteAction struct {
Steps []ExecuteStep // using UnmarshalYAML so that we don't need a custom type per action
}

// UnmarshalYAML takes any yaml in this form
// ACTION:
// - terraform: ...
// and puts the steps into the Action.Steps field
func (a *ExecuteAction) UnmarshalYAML(unmarshal func(interface{}) error) error {
actionMap := map[interface{}][]interface{}{}
err := unmarshal(&actionMap)
if err != nil {
return errors.Wrap(err, "could not unmarshal yaml into an action map of terraform steps")
}

for _, stepMaps := range actionMap {
b, err := yaml.Marshal(stepMaps)
if err != nil {
return err
}

var steps []ExecuteStep
err = yaml.Unmarshal(b, &steps)
if err != nil {
return err
}

a.Steps = append(a.Steps, steps...)
}

return nil
}

type ExecuteStep struct {
ExecuteInstruction `yaml:"terraform"`
}

type ExecuteInstruction struct {
// InstallAguments contains the usual terraform command args for re-use here
InstallArguments `yaml:",inline"`

// Command allows an override of the actual terraform command
Command string `yaml:"command,omitempty"`
}

// Execute will reapply manifests using kubectl
func (m *Mixin) Execute(opts ExecuteCommandOptions) error {

payload, err := m.getPayloadData()
if err != nil {
return err
}

var action ExecuteAction
err = yaml.Unmarshal(payload, &action)
if err != nil {
return err
}

if len(action.Steps) != 1 {
return errors.Errorf("expected a single step, but got %d", len(action.Steps))
}

step := action.Steps[0]

if step.LogLevel != "" {
os.Setenv("TF_LOG", step.LogLevel)
}

// First, change to specified working dir
if err := os.Chdir(m.WorkingDir); err != nil {
return fmt.Errorf("could not change directory to specified working dir: %s", err)
}

// Initialize Terraform
fmt.Println("Initializing Terraform...")
err = m.Init(step.BackendConfig)
if err != nil {
return fmt.Errorf("could not init terraform, %s", err)
}

command := opts.Action
if step.Command != "" {
command = step.Command
}
cmd := m.NewCommand("terraform", command)

cmd.Stdout = m.Out
cmd.Stderr = m.Err

prettyCmd := fmt.Sprintf("%s %s", cmd.Path, strings.Join(cmd.Args, " "))
fmt.Fprintln(m.Out, prettyCmd)

err = cmd.Start()
if err != nil {
return fmt.Errorf("could not execute command, %s: %s", prettyCmd, err)
}
err = cmd.Wait()
if err != nil {
return err
}

return m.handleOutputs(step.Outputs)
}
Loading