Skip to content

Commit

Permalink
Move terraform options into new terraform types (docker-archive#833)
Browse files Browse the repository at this point in the history
* Move terraform options into new terraform types

Creates a `types.go` that handles the definition of the terraform `Option`s
and the associated functions.

Signed-off-by: Steven Kaufer <[email protected]>

* Rename `ImportResourceOptions` struct to `Resource`

Signed-off-by: Steven Kaufer <[email protected]>
  • Loading branch information
kaufers authored Jan 17, 2018
1 parent 6ac620a commit befbf5e
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 194 deletions.
16 changes: 14 additions & 2 deletions pkg/provider/terraform/instance/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"
"time"

terraform_types "github.com/docker/infrakit/pkg/provider/terraform/instance/types"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"

Expand All @@ -26,7 +27,12 @@ func TestRunTerraformApply(t *testing.T) {
dir, err := os.Getwd()
require.NoError(t, err)
dir = path.Join(dir, "aws-two-tier")
terraform := NewTerraformInstancePlugin(dir, 1*time.Second, false, []string{}, nil)
options := terraform_types.Options{
Dir: dir,
PollInterval: types.FromDuration(2 * time.Minute),
}
terraform, err := NewTerraformInstancePlugin(options, nil)
require.NoError(t, err)
p, _ := terraform.(*plugin)
err = p.doTerraformApply()
require.NoError(t, err)
Expand All @@ -36,7 +42,13 @@ func TestContinuePollingStandalone(t *testing.T) {
dir, err := ioutil.TempDir("", "infrakit-instance-terraform")
require.NoError(t, err)
defer os.RemoveAll(dir)
terraform := NewTerraformInstancePlugin(dir, 1*time.Second, true, []string{}, nil)
options := terraform_types.Options{
Dir: dir,
Standalone: true,
PollInterval: types.FromDuration(2 * time.Minute),
}
terraform, err := NewTerraformInstancePlugin(options, nil)
require.NoError(t, err)
p, _ := terraform.(*plugin)
shoudApply := p.shouldApply()
require.True(t, shoudApply)
Expand Down
23 changes: 17 additions & 6 deletions pkg/provider/terraform/instance/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import (
plugin_base "github.com/docker/infrakit/pkg/plugin"
group_types "github.com/docker/infrakit/pkg/plugin/group/types"
terraform "github.com/docker/infrakit/pkg/provider/terraform/instance"
terraform_types "github.com/docker/infrakit/pkg/provider/terraform/instance/types"
instance_plugin "github.com/docker/infrakit/pkg/rpc/instance"
"github.com/docker/infrakit/pkg/run"
"github.com/docker/infrakit/pkg/spi/group"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/template"
"github.com/docker/infrakit/pkg/types"
"github.com/docker/machine/libmachine/log"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -71,7 +73,7 @@ func main() {
for _, resourceString := range *importResources {
split := strings.Split(resourceString, ":")
if len(split) < 2 || len(split) > 3 {
err := fmt.Errorf("Imported resource value is not valid: %v", resourceString)
err = fmt.Errorf("Imported resource value is not valid: %v", resourceString)
logger.Error("main", "error", err)
panic(err)
}
Expand All @@ -94,14 +96,23 @@ func main() {
}
resources = append(resources, &res)
}
importOpts := terraform.ImportOptions{
InstanceSpec: importInstSpec,
Resources: resources,
options := terraform_types.Options{
Dir: *dir,
PollInterval: types.FromDuration(*pollInterval),
Standalone: *standalone,
}
cli.SetLogLevel(*logLevel)
run.Plugin(plugin_base.DefaultTransport(*name), instance_plugin.PluginServer(
terraform.NewTerraformInstancePlugin(*dir, *pollInterval, *standalone, []string{}, &importOpts)),
plugin, err := terraform.NewTerraformInstancePlugin(options,
&terraform.ImportOptions{
InstanceSpec: importInstSpec,
Resources: resources,
},
)
if err != nil {
log.Error("error initializing pluing", "err", err)
panic(err)
}
run.Plugin(plugin_base.DefaultTransport(*name), instance_plugin.PluginServer(plugin))
}

cmd.AddCommand(cli.VersionCommand())
Expand Down
21 changes: 15 additions & 6 deletions pkg/provider/terraform/instance/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/docker/infrakit/pkg/discovery"
"github.com/docker/infrakit/pkg/discovery/local"
logutil "github.com/docker/infrakit/pkg/log"
terraform_types "github.com/docker/infrakit/pkg/provider/terraform/instance/types"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/template"
"github.com/docker/infrakit/pkg/types"
Expand Down Expand Up @@ -105,11 +106,11 @@ type ImportOptions struct {
}

// NewTerraformInstancePlugin returns an instance plugin backed by disk files.
func NewTerraformInstancePlugin(dir string, pollInterval time.Duration, standalone bool, envs []string, importOpts *ImportOptions) instance.Plugin {
logger.Info("NewTerraformInstancePlugin", "dir", dir)
func NewTerraformInstancePlugin(options terraform_types.Options, importOpts *ImportOptions) (instance.Plugin, error) {
logger.Info("NewTerraformInstancePlugin", "dir", options.Dir)

var pluginLookup func() discovery.Plugins
if !standalone {
if !options.Standalone {
if err := local.Setup(); err != nil {
panic(err)
}
Expand All @@ -121,10 +122,18 @@ func NewTerraformInstancePlugin(dir string, pollInterval time.Duration, standalo
return plugins
}
}
// // Environment varables to include when invoking terraform
envs, err := options.ParseOptionsEnvs()
if err != nil {
logger.Error("NewTerraformInstancePlugin",
"msg", "error parsing configuration Env Options",
"err", err)
return nil, err
}
p := plugin{
Dir: dir,
Dir: options.Dir,
fs: afero.NewOsFs(),
pollInterval: pollInterval,
pollInterval: options.PollInterval.Duration(),
pluginLookup: pluginLookup,
envs: envs,
}
Expand All @@ -140,7 +149,7 @@ func NewTerraformInstancePlugin(dir string, pollInterval time.Duration, standalo
// if the current node is the leader. However, when leadership changes, a Provision is
// not guaranteed to be executed so we need to create the goroutine now.
p.terraformApply()
return &p
return &p, nil
}

// processImport imports the resource with the given ID based on the instance Spec;
Expand Down
52 changes: 49 additions & 3 deletions pkg/provider/terraform/instance/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"
"time"

terraform_types "github.com/docker/infrakit/pkg/provider/terraform/instance/types"
"github.com/docker/infrakit/pkg/spi/group"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/types"
Expand Down Expand Up @@ -57,12 +58,47 @@ func TestProcessImportOptions(t *testing.T) {
require.NoError(t, err)
}

func TestEnvs(t *testing.T) {
dir, err := ioutil.TempDir("", "infrakit-instance-terraform")
require.NoError(t, err)
options := terraform_types.Options{
Dir: dir,
PollInterval: types.FromDuration(2 * time.Minute),
Envs: *types.AnyString(`["k1=v1", "keyval"]`),
}
_, err = NewTerraformInstancePlugin(options, nil)
require.Error(t, err)

options.Envs = *types.AnyString(`["k1=v1", "k2=v2"]`)
tf, err := NewTerraformInstancePlugin(options, nil)
require.NoError(t, err)
p, _ := tf.(*plugin)
require.Equal(t, []string{"k1=v1", "k2=v2"}, p.envs)

options.Envs = *types.AnyString("")
tf, err = NewTerraformInstancePlugin(options, nil)
require.NoError(t, err)
p, _ = tf.(*plugin)
require.Equal(t, []string{}, p.envs)

options.Envs = nil
tf, err = NewTerraformInstancePlugin(options, nil)
require.NoError(t, err)
p, _ = tf.(*plugin)
require.Equal(t, []string{}, p.envs)
}

// getPlugin returns the terraform instance plugin to use for testing and the
// directory where the .tf.json files should be stored
func getPlugin(t *testing.T) (*plugin, string) {
dir, err := ioutil.TempDir("", "infrakit-instance-terraform")
require.NoError(t, err)
tf := NewTerraformInstancePlugin(dir, 120*time.Second, false, []string{}, nil)
options := terraform_types.Options{
Dir: dir,
PollInterval: types.FromDuration(2 * time.Minute),
}
tf, err := NewTerraformInstancePlugin(options, nil)
require.NoError(t, err)
tf.(*plugin).pretend = true
p, is := tf.(*plugin)
require.True(t, is)
Expand All @@ -79,7 +115,12 @@ func getPluginDirNotExists(t *testing.T) (*plugin, string) {
_, err = os.Stat(dir)
require.Error(t, err)
require.True(t, os.IsNotExist(err), fmt.Sprintf("Incorrect error, expected NotExist, got %v", err))
tf := NewTerraformInstancePlugin(dir, 120*time.Second, false, []string{}, nil)
options := terraform_types.Options{
Dir: dir,
PollInterval: types.FromDuration(2 * time.Minute),
}
tf, err := NewTerraformInstancePlugin(options, nil)
require.NoError(t, err)
tf.(*plugin).pretend = true
p, is := tf.(*plugin)
require.True(t, is)
Expand All @@ -95,7 +136,12 @@ func getPluginDirNoPerms(t *testing.T) (*plugin, string) {
dir = dir + "/noperm"
os.Mkdir(dir, 0200)
require.NoError(t, err)
tf := NewTerraformInstancePlugin(dir, 120*time.Second, false, []string{}, nil)
options := terraform_types.Options{
Dir: dir,
PollInterval: types.FromDuration(2 * time.Minute),
}
tf, err := NewTerraformInstancePlugin(options, nil)
require.NoError(t, err)
tf.(*plugin).pretend = true
p, is := tf.(*plugin)
require.True(t, is)
Expand Down
146 changes: 146 additions & 0 deletions pkg/provider/terraform/instance/types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package types

import (
"encoding/json"
"fmt"
"strings"

logutil "github.com/docker/infrakit/pkg/log"
group_types "github.com/docker/infrakit/pkg/plugin/group/types"
"github.com/docker/infrakit/pkg/run/scope"
"github.com/docker/infrakit/pkg/spi/group"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/template"
"github.com/docker/infrakit/pkg/types"
"github.com/docker/machine/libmachine/log"
)

var (
logger = logutil.New("module", "provider/terraform/instance/types")
)

// Resource defines a resource to import
type Resource struct {
// Terraform resource type
ResourceType string

// Resource name in the group spec
ResourceName string

// ID of the resource to import
ResourceID string

// IDs of the properties to exclude from the instance spec
ExcludePropIDs []string
}

// Options capture the options for starting up the plugin.
type Options struct {
// Dir for storing plan files
Dir string

// PollInterval is the Terraform polling interval
PollInterval types.Duration

// Standalone - set if running standalone, disables manager leadership verification
Standalone bool

// ImportGroupSpecURL defines the group spec that the instance is imported into.
ImportGroupSpecURL string

// ImportResources defines the instances to import
ImportResources []Resource

// ImportGroupID defines the group ID to import the resource into (optional)
ImportGroupID string

// NewOption is an example... see the plugins.json file in this directory.
NewOption string

// Envs are the environment variables to include when invoking terraform
Envs types.Any
}

// ParseOptionsEnvs processes the data to create a key=value slice of strings
func (o Options) ParseOptionsEnvs() ([]string, error) {
envs := []string{}
if o.Envs == nil || len(o.Envs.Bytes()) == 0 {
return envs, nil
}
err := json.Unmarshal(o.Envs.Bytes(), &envs)
if err != nil {
return envs, fmt.Errorf("Failed to unmarshall Options.Envs data: %v", err)
}
// Must be key=value pairs
for _, val := range envs {
if !strings.Contains(val, "=") {
return []string{}, fmt.Errorf("Env var is missing '=' character: %v", val)
}
}
return envs, err
}

// ParseInstanceSpecFromGroup parses the instance.Spec from the group.Spec and adds
// in the tags that should be set on the imported instance
func (o Options) ParseInstanceSpecFromGroup(scope scope.Scope) (*instance.Spec, error) {
if o.ImportGroupSpecURL == "" {
log.Info("No group spec URL specified for import")
return nil, nil
}
var groupSpec group.Spec
t, err := scope.TemplateEngine(o.ImportGroupSpecURL, template.Options{MultiPass: false})
if err != nil {
logger.Error("ParseInstanceSpecFromGroup",
"msg", "Failed to create template",
"spec", o.ImportGroupSpecURL,
"err", err)
return nil, err
}
template, err := t.Render(nil)
if err != nil {
logger.Error("ParseInstanceSpecFromGroup",
"msg", "Failed to render template",
"spec", o.ImportGroupSpecURL,
"err", err)
return nil, err
}
if err = types.AnyString(template).Decode(&groupSpec); err != nil {
logger.Error("ParseInstanceSpecFromGroup",
"msg", "Failed to decode template",
"spec", o.ImportGroupSpecURL,
"err", err)
return nil, err
}
// Get the instance properties we care about
groupProps, err := group_types.ParseProperties(groupSpec)
if err != nil {
return nil, err
}

// Add in the bootstrap tag and (if set) the group ID
tags := map[string]string{
group.ConfigSHATag: "bootstrap",
}
// The group ID should match the spec
if o.ImportGroupID != "" {
if string(groupSpec.ID) != o.ImportGroupID {
return nil,
fmt.Errorf("Given spec ID '%v' does not match given group ID '%v'", string(groupSpec.ID), o.ImportGroupID)
}
tags[group.GroupTag] = o.ImportGroupID
}
// Use the first logical ID if set
if len(groupProps.Allocation.LogicalIDs) > 0 {
tags[instance.LogicalIDTag] = string(groupProps.Allocation.LogicalIDs[0])
}

spec := instance.Spec{
Properties: groupProps.Instance.Properties,
Tags: tags,
}
logger.Info("ParseInstanceSpecFromGroup",
"msg", "Successfully processed instance spec from group",
"group", groupSpec.ID,
"spec", spec)
return &spec, nil
}
Loading

0 comments on commit befbf5e

Please sign in to comment.