Skip to content

Commit

Permalink
azurerm: Add the 'lifecycle' to 'azurerm_virtual_machine_data_disk_at…
Browse files Browse the repository at this point in the history
…tachment' and 'azurerm_virtual_machine'

So it does not give out more errors on import in any of the resources
  • Loading branch information
xescugc committed Jul 4, 2023
1 parent 2bb7d85 commit 5c01015
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 10 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
### Fixed
- The generated HCL now has the fixed version for the provider used instead of using the latest one by default
([Issue #378](https://github.com/cycloidio/terracognita/issues/378))
- Add resource_group scope to azurerm_storage_account
- Add `resource_group` scope to `azurerm_storage_account`
([Issue #393](https://github.com/cycloidio/terracognita/issues/393))
- AzureRM `azurerm_virtual_machine_data_disk_attachment` was still giving issues on import so we added the `lifecycle.ignore_changes=[create_option]` to it
([Issue #395](https://github.com/cycloidio/terracognita/issues/395))

## [0.8.4] _2023-05-18_

Expand Down
86 changes: 85 additions & 1 deletion azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/cycloidio/terracognita/filter"
"github.com/cycloidio/terracognita/log"
"github.com/cycloidio/terracognita/provider"
"github.com/cycloidio/terracognita/util"
)

// version of the Terraform provider, this is automatically changed with the 'make update-terraform-provider'
Expand Down Expand Up @@ -145,7 +146,38 @@ func (a *azurerm) TFClient() interface{} {
}

func (a *azurerm) TFProvider() *schema.Provider {
return a.tfProvider
tfp := a.tfProvider

for _, v := range tfp.ResourcesMap {
// We add the `lifecycle` to all the resource on AzureRM, if we
// need it for other providers then we'll add the missing
// Metadata attributes and abstract this too
v.Schema["lifecycle"] = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"create_before_destroy": &schema.Schema{
Optional: true,
Type: schema.TypeBool,
},
"prevent_destroy": &schema.Schema{
Optional: true,
Type: schema.TypeBool,
},
"ignore_changes": &schema.Schema{
Optional: true,
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
},
}
}

return tfp
}

func (a *azurerm) FixResource(t string, v cty.Value) (cty.Value, error) {
Expand All @@ -156,13 +188,16 @@ func (a *azurerm) FixResource(t string, v cty.Value) (cty.Value, error) {
// the unsetManageDiskID is the list of all the indexed of the storage_account_type that
// have the create_option as FromImage
var unsetManageDiskID = make(map[int]struct{})
var storageOSDiskCount int
var storageDataDiskCount int
err = cty.Walk(v, func(path cty.Path, val cty.Value) (bool, error) {
if len(path) == 3 {
if gas, ok := path[0].(cty.GetAttrStep); ok {

switch gas.Name {
case "storage_os_disk":
if path[2].(cty.GetAttrStep).Name == "create_option" {
storageOSDiskCount += 1
var co string
err := gocty.FromCtyValue(val, &co)
if err != nil {
Expand All @@ -177,6 +212,10 @@ func (a *azurerm) FixResource(t string, v cty.Value) (cty.Value, error) {
unsetManageDiskID[idx] = struct{}{}
}
}
case "storage_data_disk":
if path[2].(cty.GetAttrStep).Name == "create_option" {
storageDataDiskCount += 1
}
}
}
}
Expand Down Expand Up @@ -219,6 +258,32 @@ func (a *azurerm) FixResource(t string, v cty.Value) (cty.Value, error) {
}
return v, nil
})
m, err := util.CtyObjectToUnstructured(&v)
if err != nil {
return v, errors.Wrapf(err, "failed to convert cty object to map")
}

ignoreChanges := make([]string, 0, 0)
for i := 0; i < storageDataDiskCount; i++ {
ignoreChanges = append(ignoreChanges, fmt.Sprintf("=tc_unquote=storage_data_disk[%d].create_option", i))
}
for i := 0; i < storageOSDiskCount; i++ {
ignoreChanges = append(ignoreChanges, fmt.Sprintf("=tc_unquote=storage_os_disk[%d].create_option", i))
}
// We need to set all the possible attributes and if they do not have
// any value then set them to nil
m["lifecycle"] = []interface{}{
map[string]interface{}{
"create_before_destroy": nil,
"prevent_destroy": nil,
"ignore_changes": ignoreChanges,
},
}
nv, err := util.UnstructuredToCty(m)
if err != nil {
return v, errors.Wrapf(err, "failed to convert map to cty object")
}
return nv, nil
if err != nil {
return v, errors.Wrapf(err, "failed to convert CTY value to GO type")
}
Expand Down Expand Up @@ -281,6 +346,25 @@ func (a *azurerm) FixResource(t string, v cty.Value) (cty.Value, error) {
if err != nil {
return v, errors.Wrapf(err, "failed to convert CTY value to GO type")
}
m, err := util.CtyObjectToUnstructured(&v)
if err != nil {
return v, errors.Wrapf(err, "failed to convert cty object to map")
}

// We need to set all the possible attributes and if they do not have
// any value then set them to nil
m["lifecycle"] = []interface{}{
map[string]interface{}{
"create_before_destroy": nil,
"prevent_destroy": nil,
"ignore_changes": []string{"=tc_unquote=create_option"},
},
}
nv, err := util.UnstructuredToCty(m)
if err != nil {
return v, errors.Wrapf(err, "failed to convert map to cty object")
}
return nv, nil
case "azurerm_windows_virtual_machine":
v, err = cty.Transform(v, func(path cty.Path, v cty.Value) (cty.Value, error) {
if len(path) > 0 {
Expand Down
8 changes: 7 additions & 1 deletion hcl/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ var (
},
},
{
match: regexp.MustCompile(`=tc=`),
// Used for quoted values to unquote
// Replace all the `"key" = "=tc_unquote=value"` for `"key" = value`
match: regexp.MustCompile(`"=tc_unquote=([^"]+)"`),
replace: []byte(`$1`),
},
{
match: regexp.MustCompile(`=tc[^=]*=`),
replace: []byte{},
},
{
Expand Down
12 changes: 12 additions & 0 deletions hcl/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,18 @@ func TestFormat(t *testing.T) {
role = [var.isa-role,var.isa-role2,"{value}"]
}`),
},
{
name: "RemoveQuotes",
in: []byte(`
"role" = value
"env" = "=tc_unquote=value"
`),
out: []byte(`
role = value
env = value
`),
},
}

for _, tt := range tests {
Expand Down
2 changes: 1 addition & 1 deletion provider/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ func (r *resource) Read(f *filter.Filter) error {
return err
}

zstate, err := util.HashicorpToZclonfValue(newStateValue, r.tfResource.CoreConfigSchema().ImpliedType())
zstate, err := util.HashicorpToZclonfValue(newStateValue, r.ImpliedType())
if err != nil {
return err
}
Expand Down
14 changes: 12 additions & 2 deletions state/writer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package state

import (
"bytes"
"fmt"
"io"
"regexp"
Expand Down Expand Up @@ -122,15 +123,24 @@ func (w *Writer) Sync() error {
lstate := w.state.Lock()
defer w.state.Unlock()

log.Get().Log("func", "state.Sync(State)", "msg", "writting state to state file")
log.Get().Log("func", "state.Sync(State)", "msg", "writing state to state file")
file := statemgr.NewStateFile()
file.State = lstate

err := statefile.Write(file, w.writer)
b := bytes.Buffer{}
err := statefile.Write(file, &b)
if err != nil {
return err
}

// Normally the formatter is only used on the HCL as is the one that has wrong format
// but now we set thing before the HCL/State are generated so those are passed through
// both of them, for now it's only the `"=tc_unquote=value"` so we remove it
m := regexp.MustCompile(`=tc[^=]*=`)
fb := m.ReplaceAll(b.Bytes(), nil)
nb := bytes.NewBuffer(fb)
io.Copy(w.writer, nb)

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion state/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func TestSync(t *testing.T) {
tpt, err := util.HashicorpToZclonfType(aws.Provider().ResourcesMap[tp].CoreConfigSchema().ImpliedType())
require.NoError(t, err)

s, err := hcl2shim.HCL2ValueFromFlatmap(map[string]string{"name": "Pepito"}, tpt)
s, err := hcl2shim.HCL2ValueFromFlatmap(map[string]string{"name": "=tc_unquote=Pepito"}, tpt)
require.NoError(t, err)

res.EXPECT().Type().Return(tp)
Expand Down
39 changes: 36 additions & 3 deletions util/cty.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"encoding/json"

hcty "github.com/hashicorp/go-cty/cty"
hctyjson "github.com/hashicorp/go-cty/cty/json"
hmsgpack "github.com/hashicorp/go-cty/cty/msgpack"
"github.com/pkg/errors"
zcty "github.com/zclconf/go-cty/cty"
zmsgpack "github.com/zclconf/go-cty/cty/msgpack"
)
Expand All @@ -27,15 +29,46 @@ func HashicorpToZclonfType(ht hcty.Type) (zcty.Type, error) {
func HashicorpToZclonfValue(hv hcty.Value, ht hcty.Type) (zcty.Value, error) {
sb, err := hmsgpack.Marshal(hv, ht)
if err != nil {
return zcty.EmptyObjectVal, err
return zcty.EmptyObjectVal, errors.Wrapf(err, "failed to Hashicorp marshal")
}
ty, err := HashicorpToZclonfType(ht)
if err != nil {
return zcty.EmptyObjectVal, err
return zcty.EmptyObjectVal, errors.Wrapf(err, "failed to convert from Hashiciprt to Zclon")
}
zvalue, err := zmsgpack.Unmarshal(sb, ty)
if err != nil {
return zcty.EmptyObjectVal, err
return zcty.EmptyObjectVal, errors.Wrapf(err, "failed to Zclon unmarshal")
}
return zvalue, nil
}

// CtyObjectToUnstructured converts a Terraform specific cty.Object type manifest
// into a dynamic client specific unstructured object
func CtyObjectToUnstructured(in *hcty.Value) (map[string]interface{}, error) {
simple := &hctyjson.SimpleJSONValue{Value: *in}
jsonVal, err := simple.MarshalJSON()
if err != nil {
return nil, err
}
udata := map[string]interface{}{}
err = json.Unmarshal(jsonVal, &udata)
if err != nil {
return nil, err
}
return udata, nil
}

// UnstructuredToCty converts a dynamic client specific unstructured object
// into a Terraform specific cty.Object type manifest
func UnstructuredToCty(in map[string]interface{}) (hcty.Value, error) {
jsonVal, err := json.Marshal(in)
if err != nil {
return hcty.NilVal, errors.Wrapf(err, "unable to marshal value")
}
simple := &hctyjson.SimpleJSONValue{}
err = simple.UnmarshalJSON(jsonVal)
if err != nil {
return hcty.NilVal, errors.Wrapf(err, "unable to unmarshal to simple value")
}
return simple.Value, nil
}

0 comments on commit 5c01015

Please sign in to comment.