Skip to content
This repository was archived by the owner on Jan 21, 2020. It is now read-only.

Commit 41a17c1

Browse files
kaufersDavid Chung
authored andcommitted
Add tf import support to conditionally honor all props in spec (#740)
Signed-off-by: Steven Kaufer <[email protected]>
1 parent a4165de commit 41a17c1

File tree

4 files changed

+126
-31
lines changed

4 files changed

+126
-31
lines changed

pkg/provider/terraform/instance/README.md

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,66 @@ and update its state. When an instance is removed, Terraform will do the same b
105105
and update its state.
106106

107107

108-
## Running
108+
## Runtime Options
109+
110+
The plugin support the following `Launch` `inproc` `Options`:
111+
112+
* `Dir`: Directory that will be used to contain the `tfstate` and `tf.json` files
113+
* `PollInterval`: Frequency that `terraform apply` is invoked; note that it is only invoked on the leader
114+
manager (unless `standalone=true`, see below)
115+
* `Standalone`: If `true` then manager leadership is not verified prior to invoking `terraform apply`
116+
(default is `false`)
117+
* `Envs`: Array of environment variables to include when invoking the `terraform` commands
118+
119+
The plugin also supports importing existing resources into terraform; this can be used to import the
120+
initial manager into terraform. Once the resource is imported into terraform, a corresponding `.tf.json`
121+
file is also created. The following optional fields are used for this purpose:
122+
* `ImportGroupSpecURL`: The group specification URL that contains a nested instance specification; the
123+
`.tf.json` file for the imported resource contains the properties in the instance specification
124+
* `ImportGroupID`: Optional group ID that the imported resource should be tagged with
125+
* `ImportResources`: An array of resources to import into terraform, these resources must correspond
126+
with those in the instance specification (nested in the group specification). Each element contains:
127+
* `ResourceType`: The terraform resource type being imported
128+
* `ResourceID`: The resource ID being imported
129+
* `ResourceName`: The terraform resource name to assign to the the resource being imported; this value
130+
must match the name in the instance spec (required if there is more then one resource of the same type
131+
in the specification)
132+
* `ExcludePropIDs`: An array of property IDs in the instance specification that should _not_ be
133+
included in the corresponding `.tf.json` file
134+
135+
For example:
109136

110-
Begin by building plugin [binaries](/README.md#binaries).
137+
```
138+
{
139+
"Key" : "terraform",
140+
"Launch" : {
141+
"inproc": {
142+
"Kind" : "terraform",
143+
"Options" : {
144+
"Dir": "/infrakit",
145+
"PollInterval": "60s",
146+
"Standalone": false,
147+
"ImportGroupSpecURL" : "file://defn-mgr-group.json",
148+
"ImportGroupID": "managers",
149+
"ImportResources": [
150+
{
151+
"ResourceType": "ibm_compute_vm_instance",
152+
"ResourceID": "123456"
153+
},
154+
{
155+
"ResourceType": "ibm_subnet",
156+
"ResourceID": "abc-123-xyz"
157+
}
158+
]
159+
}
160+
}
161+
}
162+
}
163+
```
111164

112-
The supported fields are:
113-
* `dir`: Directory that will be used to contain the `tfstate` and `tf.json` files
114-
* `poll-interval`: Frequency that `terraform apply` is invoked; note that it is only invoked on the leader manager (unless `standalone=true`, see below)
115-
* `standalone`: If `true` then manager leadership is not verified prior to invoking `terraform apply` (default is `false`)
165+
## Running
116166

117-
The plugin also supports importing an existing resource into terraform; this can be used to import the initial manager into terraform. Once the resource is imported into terraform, a corresponding `.tf.json` file is also created. The following optional fields are used for this purpose:
118-
* `import-group-spec-url`: The group specification URL that contains a nested instance specification; the `.tf.json` file for the imported resource contains the properties in the instance specification
119-
* `import-resource`: The terraform resource type, optional resource name, and resource ID to import into in the form of `<type>:[<name>:]<id>`. The `<type>` and `<name>` must match a resource in the instance specification; the `<name>` is required if there is more then one resource of the given type in the specification. This value may be specified multiple times.
120-
* `import-group-id`: Optional group ID that the imported resource should be tagged with
167+
Begin by building plugin [binaries](/README.md#binaries).
121168

122169
The plugin also checks to make sure it can call `terraform`. Install Terraform [here](https://www.terraform.io/downloads.html) if you haven't done so.
123170

pkg/provider/terraform/instance/plugin.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ type ImportResource struct {
7878
ResourceID *string
7979
ResourceType *TResourceType
8080
ResourceName *TResourceName // Name of resource in the instance spec
81+
ExcludePropIDs *[]string // Property IDs that exist in the instance spec that should be excluded
8182
ResourceProps TResourceProperties // Populated via tf show
8283
SpecProps TResourceProperties // Parsed from instance spec
8384
FinalProps TResourceProperties // Properties for the tf.json.new file
@@ -1767,20 +1768,33 @@ func setFinalResourceAndFilename(resource *ImportResource, filename string, reso
17671768
func determineFinalPropsForImport(res *ImportResource) {
17681769
log.Infof("Using spec for %v import: %v", string(*res.ResourceType), res.SpecProps)
17691770
finalProps := TResourceProperties{}
1770-
for k := range res.SpecProps {
1771+
for k, specVal := range res.SpecProps {
17711772
// Ignore certain keys in spec
17721773
if k == PropScope {
17731774
continue
17741775
}
17751776
if k == PropHostnamePrefix {
17761777
k = "hostname"
17771778
}
1778-
v, has := res.ResourceProps[k]
1779-
if !has {
1780-
log.Warningf("Imported terraform resource missing '%s' property, not setting", k)
1781-
continue
1779+
if res.ExcludePropIDs != nil && len(*res.ExcludePropIDs) > 0 {
1780+
exclude := false
1781+
for _, propID := range *res.ExcludePropIDs {
1782+
if propID == k {
1783+
exclude = true
1784+
log.Infof("Excluding spec property '%s' for resource type %v", propID, string(*res.ResourceType))
1785+
break
1786+
}
1787+
}
1788+
if exclude {
1789+
continue
1790+
}
1791+
}
1792+
if v, has := res.ResourceProps[k]; has {
1793+
finalProps[k] = v
1794+
} else {
1795+
log.Warningf("Imported terraform resource missing '%s' property, using spec value: %v", k, specVal)
1796+
finalProps[k] = specVal
17821797
}
1783-
finalProps[k] = v
17841798
}
17851799
// Always keep the tags, even if the spec does not have them as a property
17861800
if _, has := finalProps["tags"]; !has {

pkg/provider/terraform/instance/plugin_test.go

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3592,11 +3592,13 @@ func TestWriteTfJSONFilesForImportMultipleFiles(t *testing.T) {
35923592

35933593
func TestDetermineFinalPropsForImport(t *testing.T) {
35943594
specProps := TResourceProperties{
3595-
PropHostnamePrefix: "some-prefix",
3596-
PropScope: "some-scope",
3597-
"ssh-key-ids": []interface{}{789},
3598-
"datacenter": "some-datacenter",
3599-
"z-other": "not-imported",
3595+
PropHostnamePrefix: "some-prefix",
3596+
PropScope: "some-scope",
3597+
"ssh-key-ids": []interface{}{789},
3598+
"datacenter": "some-datacenter",
3599+
"some-key-in-spec-only": "some-key-value",
3600+
"lifecycle": map[string][]string{"ignore_changes": {"static-key"}},
3601+
"exclude-prop": "exclude-val",
36003602
}
36013603
// Include tags
36023604
resourceProps := TResourceProperties{
@@ -3611,10 +3613,13 @@ func TestDetermineFinalPropsForImport(t *testing.T) {
36113613
"z-imported": "imported-but-not-in-spec",
36123614
}
36133615
resType := VMIBMCloud
3616+
// With excluded props
3617+
excludePropIDs := []string{"exclude-prop"}
36143618
res := ImportResource{
3615-
ResourceType: &resType,
3616-
ResourceProps: resourceProps,
3617-
SpecProps: specProps,
3619+
ResourceType: &resType,
3620+
ExcludePropIDs: &excludePropIDs,
3621+
ResourceProps: resourceProps,
3622+
SpecProps: specProps,
36183623
}
36193624
determineFinalPropsForImport(&res)
36203625
expectedProps := TResourceProperties{
@@ -3625,18 +3630,42 @@ func TestDetermineFinalPropsForImport(t *testing.T) {
36253630
"actual-tag1:actual-val1",
36263631
"actual-tag2:actual-val2",
36273632
},
3633+
"some-key-in-spec-only": "some-key-value",
3634+
"lifecycle": map[string][]string{"ignore_changes": {"static-key"}},
36283635
}
36293636
require.Equal(t, expectedProps, res.FinalProps)
36303637
// Without tags
36313638
delete(resourceProps, "tags")
36323639
res = ImportResource{
3633-
ResourceType: &resType,
3634-
ResourceProps: resourceProps,
3635-
SpecProps: specProps,
3640+
ResourceType: &resType,
3641+
ExcludePropIDs: &excludePropIDs,
3642+
ResourceProps: resourceProps,
3643+
SpecProps: specProps,
36363644
}
36373645
determineFinalPropsForImport(&res)
36383646
delete(expectedProps, "tags")
36393647
require.Equal(t, expectedProps, res.FinalProps)
3648+
// Without excluded props
3649+
excludePropIDs = []string{}
3650+
res = ImportResource{
3651+
ResourceType: &resType,
3652+
ExcludePropIDs: &excludePropIDs,
3653+
ResourceProps: resourceProps,
3654+
SpecProps: specProps,
3655+
}
3656+
determineFinalPropsForImport(&res)
3657+
expectedProps["exclude-prop"] = "exclude-val"
3658+
require.Equal(t, expectedProps, res.FinalProps)
3659+
// nil excluded props
3660+
res = ImportResource{
3661+
ResourceType: &resType,
3662+
ExcludePropIDs: nil,
3663+
ResourceProps: resourceProps,
3664+
SpecProps: specProps,
3665+
}
3666+
determineFinalPropsForImport(&res)
3667+
expectedProps["exclude-prop"] = "exclude-val"
3668+
require.Equal(t, expectedProps, res.FinalProps)
36403669
}
36413670

36423671
func TestImportNoVm(t *testing.T) {

pkg/run/v0/terraform/terraform.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ type ImportResourceOptions struct {
4949

5050
// ID of the resource to import
5151
ResourceID string
52+
53+
// IDs of the properties to exclude from the instance spec
54+
ExcludePropIDs []string
5255
}
5356

5457
// Options capture the options for starting up the plugin.
@@ -74,7 +77,7 @@ type Options struct {
7477
// NewOption is an example... see the plugins.json file in this directory.
7578
NewOption string
7679

77-
// Envs are the environemtn variables to include when invoking terraform
80+
// Envs are the environment variables to include when invoking terraform
7881
Envs types.Any
7982
}
8083

@@ -121,10 +124,12 @@ func Run(plugins func() discovery.Plugins, name plugin.Name,
121124
resType := terraform.TResourceType(importResource.ResourceType)
122125
resName := terraform.TResourceName(importResource.ResourceName)
123126
resID := importResource.ResourceID
127+
excludePropIDs := importResource.ExcludePropIDs
124128
res := terraform.ImportResource{
125-
ResourceType: &resType,
126-
ResourceName: &resName,
127-
ResourceID: &resID,
129+
ResourceType: &resType,
130+
ResourceName: &resName,
131+
ResourceID: &resID,
132+
ExcludePropIDs: &excludePropIDs,
128133
}
129134
resources = append(resources, &res)
130135
}

0 commit comments

Comments
 (0)