diff --git a/src/arm/data.go b/src/arm/data.go new file mode 100644 index 0000000..e91fc45 --- /dev/null +++ b/src/arm/data.go @@ -0,0 +1,40 @@ +package arm + +import ( + "bytes" + "sato/src/cf" + tftemplate "text/template" + + "github.com/rs/zerolog/log" +) + +// parseData writes out to data.tf +func parseData(result map[string]interface{}, funcMap tftemplate.FuncMap, destination string) error { + if result["data"] == nil { + return nil + } + + data := result["data"] + + var output bytes.Buffer + tmpl, err := tftemplate.New("test").Funcs(funcMap).Parse(string(dataFile)) + if err != nil { + return err + } + err = tmpl.Execute(&output, m{ + "data": data, + }) + + if err != nil { + log.Print(err) + return err + } + + err = cf.Write(output.String(), destination, "data") + if err != nil { + log.Print(err) + return err + } + + return nil +} diff --git a/src/arm/data.template b/src/arm/data.template index 2600d95..d0c7cc2 100644 --- a/src/arm/data.template +++ b/src/arm/data.template @@ -3,7 +3,13 @@ data "azurerm_resource_group" "sato" { name = "sato" } {{- end }} - +{{- if eq true .data.client_config }} +data "azurerm_client_config" "sato" { +} +{{- end }} +{{- if .data.uuid }} +{{Uuid .data.uuid }} +{{- end }} provider "azurerm" { features{} } diff --git a/src/arm/data_test.go b/src/arm/data_test.go new file mode 100644 index 0000000..be33ec9 --- /dev/null +++ b/src/arm/data_test.go @@ -0,0 +1,34 @@ +package arm + +import ( + "testing" + "text/template" +) + +func Test_parseData(t *testing.T) { + t.Parallel() + + type args struct { + result map[string]interface{} + funcMap template.FuncMap + destination string + } + + tests := []struct { + name string + args args + wantErr bool + }{ + // {}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if err := parseData(tt.args.result, tt.args.funcMap, tt.args.destination); (err != nil) != tt.wantErr { + t.Errorf("parseData() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/src/arm/helpers.go b/src/arm/helpers.go index 2211e83..5bffb44 100644 --- a/src/arm/helpers.go +++ b/src/arm/helpers.go @@ -3,6 +3,7 @@ package arm import ( "errors" "fmt" + "regexp" "strconv" "strings" @@ -76,11 +77,11 @@ func fixType(myItem map[string]interface{}) (map[string]interface{}, error) { temptTypes = "{\n" + strings.TrimSuffix(myType, "\n") + "}" } if result != "" { - result = result + "," + name + "= [" + temp + "]" - types = types + "," + name + "= list(object(" + temptTypes + "))" + result += "," + name + "= [" + temp + "]" + types += "," + name + "= list(object(" + temptTypes + "))" } else { - result = result + name + "= [" + temp + "]" - types = types + name + "= list(object(" + temptTypes + "))" + result += name + "= [" + temp + "]" + types += name + "= list(object(" + temptTypes + "))" } } case map[string]interface{}: @@ -90,12 +91,11 @@ func fixType(myItem map[string]interface{}) (map[string]interface{}, error) { case string: { if result == "" { - result = name + " = " + escapeQuote(item) + result = name + " = " + "\"" + escapeQuote(item) + "\"" types = name + " = " + "string" } else { - temp := result - result = temp + ",\n\t" + name + " = " + escapeQuote(item) - types = types + ",\n\t" + name + " = " + "string" + result += ",\n\t" + name + " = " + "\"" + escapeQuote(item) + "\"" + types += ",\n\t" + name + " = " + "string" } } case bool: @@ -144,7 +144,7 @@ func fixType(myItem map[string]interface{}) (map[string]interface{}, error) { { myItem["default"] = escapeQuote(myItem["default"]) } - case typeListString, typeNumber: + case typeListString, typeNumber, "bool": { // do nothing } @@ -182,7 +182,11 @@ func arrayToString(defaultValue []interface{}) string { func tags(tags map[string]interface{}) string { tagged := "{\n" for item, name := range tags { - tagged += "\t\"" + item + "\"" + " = " + "\"" + name.(string) + "\"\n" + if _, ok := name.(string); ok { + tagged += "\t\"" + item + "\"" + " = " + "\"" + name.(string) + "\"\n" + } else { + tagged += "\t\"" + item + "\"" + " = " + "\"OBJECT\"\n" + } } tagged += "\t}" @@ -198,3 +202,62 @@ func notNil(unknown interface{}) bool { return true } + +func enabled(status string) bool { + if strings.ToLower(status) == "enabled" { + return true + } + return false +} + +func loseSQBrackets(newAttribute string) string { + re := regexp.MustCompile(`^\[(.*)\]`) // format('{0}/{1}', + Matched := re.FindStringSubmatch(newAttribute) + if len(Matched) > 1 { + return Matched[1] + } + return newAttribute +} + +func ditch(Attribute string, name string) string { + + leftBrackets := strings.SplitAfter(Attribute, "(") + + if len(leftBrackets) == 0 { + return Attribute + } + + var brackets []string + + for _, item := range leftBrackets { + rbrackets := strings.SplitAfter(item, ")") + brackets = append(brackets, rbrackets...) + } + + y := 100 + var raw []string + for x, item := range brackets { + if strings.Contains(item, name) { + y = len(brackets) - 2 + x + raw = append(raw, strings.Replace(item, name+"(", "", 1)) + } + + if y != x && !strings.Contains(item, name) { + raw = append(raw, item) + } + + if y == x { + raw = append(raw, strings.Replace(item, ")", "", 1)) + } + } + return strings.Join(raw, "") +} + +func uuid(count int) string { + var i int + var uuids string + for i = 0; i < (count); i++ { + uuids += "resource \"random_uuid\" \"sato" + strconv.Itoa(i) + "\" {}\n" + } + return uuids +} diff --git a/src/arm/helpers_test.go b/src/arm/helpers_test.go index b32c3c4..e9e6459 100644 --- a/src/arm/helpers_test.go +++ b/src/arm/helpers_test.go @@ -143,6 +143,72 @@ func Test_fixType(t *testing.T) { myItem map[string]interface{} } + myString := map[string]interface{}{ + "type": "string", + "defaultValue": "2", + "maxValue": 25, + "minValue": 0, + "metadata": map[string]interface{}{ + "description": "Minimum number of replicas that will be deployed", + }, + "default": "1", + } + + myStringResult := map[string]interface{}{ + "type": "string", + "defaultValue": "2", + "maxValue": 25, + "minValue": 0, + "metadata": map[string]interface{}{ + "description": "Minimum number of replicas that will be deployed", + }, + "default": "1", + } + + myInt := map[string]interface{}{ + "type": "int", + "defaultValue": 2, + "maxValue": 25, + "minValue": 0, + "metadata": map[string]interface{}{ + "description": "Minimum number of replicas that will be deployed", + }, + "default": "1", + } + + myIntResult := map[string]interface{}{ + "type": "number", + "defaultValue": 2, + "maxValue": 25, + "minValue": 0, + "metadata": map[string]interface{}{ + "description": "Minimum number of replicas that will be deployed", + }, + "default": "1", + } + + mySlice := map[string]interface{}{ + "type": "[]interface{}", + "defaultValue": []interface{}{1, 2}, + "maxValue": 25, + "minValue": 0, + "metadata": map[string]interface{}{ + "description": "Minimum number of replicas that will be deployed", + }, + "default": "1", + } + + mySliceResult := map[string]interface{}{ + "type": "[]interface{}", + "defaultValue": []interface{}{1, 2}, + "maxValue": 25, + "minValue": 0, + "metadata": map[string]interface{}{ + "description": "Minimum number of replicas that will be deployed", + }, + "default": "1", + } + myItem := map[string]interface{}{ "type": "number", "defaultValue": 1, @@ -232,7 +298,9 @@ func Test_fixType(t *testing.T) { {"Do Nothing", args{myItem}, myItem, false}, {"Object", args{myObject}, newObject, false}, {"Not string", args{notObject}, returnNotObject, true}, - //{"Convert Object", args{myObject2}, myObjectReturned, false}, + {"Slice", args{mySlice}, mySliceResult, false}, + {"Int", args{myInt}, myIntResult, false}, + {"String", args{myString}, myStringResult, false}, } for _, tt := range tests { @@ -252,3 +320,188 @@ func Test_fixType(t *testing.T) { }) } } + +func Test_ditch(t *testing.T) { + t.Parallel() + + type args struct { + Attribute string + name string + } + + tests := []struct { + name string + args args + want string + }{ + {"none", args{"bastionPublicIpAddressName", "variables"}, "bastionPublicIpAddressName"}, + {"variables", args{"variables('bastionPublicIpAddressName')", "variables"}, "'bastionPublicIpAddressName'"}, + {"variables not", args{"parameters('vmName')", "variables"}, "parameters('vmName')"}, + {"mixed", + args{"concat(parameters('vmName'),'/', variables('omsAgentForLinuxName'))", "variables"}, + "concat(parameters('vmName'),'/', 'omsAgentForLinuxName'))"}, + {"mixed 2", + args{"concat(variables('blobPrivateDnsZoneName'), '/link_to_', toLower(parameters('virtualNetworkName')))", "variables"}, + "concat('blobPrivateDnsZoneName'), '/link_to_', toLower(parameters('virtualNetworkName')))"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := ditch(tt.args.Attribute, tt.args.name); got != tt.want { + t.Errorf("ditch() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_enabled(t *testing.T) { + t.Parallel() + + type args struct { + status string + } + tests := []struct { + name string + args args + want bool + }{ + {"Enabled", args{"Enabled"}, true}, + {"Disabled", args{"Disabled"}, false}, + {"Guff", args{"guff"}, false}, + {"Nil", args{""}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := enabled(tt.args.status); got != tt.want { + t.Errorf("enabled() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_notNil(t *testing.T) { + t.Parallel() + + type args struct { + unknown interface{} + } + + var test interface{} + var test2 interface{} + var test3 interface{} + + test = true + test3 = false + test4 := "" + + tests := []struct { + name string + args args + want bool + }{ + {"True", args{test}, true}, + {"Nil", args{test2}, false}, + {"False", args{test3}, true}, + {"String", args{""}, true}, + {"Pointer", args{&test4}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := notNil(tt.args.unknown); got != tt.want { + t.Errorf("notNil() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_tags(t *testing.T) { + t.Parallel() + + myTags := map[string]interface{}{ + "test": "something", + "another": "stuff", + } + + myBodge := map[string]interface{}{ + "test": "something", + "another": false, + } + + type args struct { + tags map[string]interface{} + } + + result := "{\n\t\"test\" = \"something\"\n\t\"another\" = \"stuff\"\n\t}" + bodge := "{\n\t\"test\" = \"something\"\n\t\"another\" = \"OBJECT\"\n\t}" + + tests := []struct { + name string + args args + want string + }{ + {"pass", args{myTags}, result}, + {"bodge", args{myBodge}, bodge}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := tags(tt.args.tags); got != tt.want { + t.Errorf("tags() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_uuid(t *testing.T) { + t.Parallel() + + type args struct { + count int + } + tests := []struct { + name string + args args + want string + }{ + {"Pass", args{1}, "resource \"random_uuid\" \"sato0\" {}\n"}, + {"Two", args{2}, "resource \"random_uuid\" \"sato0\" {}\nresource \"random_uuid\" \"sato1\" {}\n"}, + {"Zero", args{0}, ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := uuid(tt.args.count); got != tt.want { + t.Errorf("uuid() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_loseSQBrackets(t *testing.T) { + type args struct { + newAttribute string + } + + tests := []struct { + name string + args args + want string + }{ + {"pass", args{"[pass]"}, "pass"}, + {"no pass", args{"[pass"}, "[pass"}, + {"leave", args{"stuff[pass]"}, "stuff[pass]"}, + {"leave with outside", args{"[stuff[pass]]"}, "stuff[pass]"}, + {"just", args{"[]"}, ""}, + {"nil", args{""}, ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := loseSQBrackets(tt.args.newAttribute); got != tt.want { + t.Errorf("loseSQBrackets() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/arm/locals.go b/src/arm/locals.go new file mode 100644 index 0000000..ea9e953 --- /dev/null +++ b/src/arm/locals.go @@ -0,0 +1,29 @@ +package arm + +import ( + "strings" +) + +func parseLocals(result map[string]interface{}) (string, map[string]interface{}, error) { + var locals string + myLocals := result["locals"].(map[string]interface{}) + + for x, value := range myLocals { + var theValue string + var local string + theValue, result = parseString(value.(string), result) + + myLocals[x] = theValue + if strings.Contains(theValue, "${") { + local = "\t" + x + " = \"" + theValue + "\" #" + value.(string) + "\n" + } else { + local = "\t" + x + " = " + theValue + " #" + value.(string) + "\n" + } + + locals += strings.ReplaceAll(local, "'", "\"") + } + + result["locals"] = myLocals + + return locals, result, nil +} diff --git a/src/arm/lookup.go b/src/arm/lookup.go index 7b6086c..ad725ac 100644 --- a/src/arm/lookup.go +++ b/src/arm/lookup.go @@ -8,35 +8,49 @@ import ( func lookup(myType string) []byte { TFLookup := map[string]interface{}{ - "Microsoft.AnalysisServices/servers": azurermAnalysisServicesServer, - "Microsoft.ApiManagement/service": azurermAPIManagement, - "Microsoft.App/containerApps": azurermContainerApp, - "Microsoft.App/managedEnvironments": azurermContainerAppEnvironment, - "Microsoft.Authorization/roleAssignments": azurermRoleAssignment, - "Microsoft.Authorization/roleDefinitions": azurermRoleDefinition, - "Microsoft.Compute/virtualMachines": azurermVirtualMachine, - "Microsoft.Compute/virtualMachines/extensions": azurermVirtualMachineExtension, - "Microsoft.ManagedIdentity/userAssignedIdentities": azurermUserAssignedIdentity, - "Microsoft.Network/networkInterfaces": azurermNetworkInterface, - "Microsoft.Network/networkSecurityGroups": azurermNetworkSecurityGroup, - "Microsoft.Network/publicIPAddresses": azurermPublicIP, - "Microsoft.Network/virtualNetworks": azurermVirtualNetwork, - "Microsoft.Network/virtualNetworks/subnets": azurermSubnet, - "Microsoft.OperationalInsights/workspaces": azurermLogAnalyticsWorkspace, - "Microsoft.Storage/storageAccounts": azurermStorageAccount, - "Microsoft.Resources/deployments": azurermTemplateDeployment, - "Microsoft.ServiceBus/namespaces": azurermServicebusNamespace, - "Microsoft.ServiceBus/namespaces/authorizationRules": azurermServicebusNamespaceAuthorizationRule, - "Microsoft.ServiceBus/namespaces/queues": azurermServicebusQueue, - "Microsoft.AAD/domainServices": azurermActiveDirectoryDomainService, + "microsoft.aad/domainservices": azurermActiveDirectoryDomainService, + "microsoft.analysisservices/servers": azurermAnalysisServicesServer, + "microsoft.apiManagement/service": azurermAPIManagement, + "microsoft.app/containerapps": azurermContainerApp, + "microsoft.app/managedenvironments": azurermContainerAppEnvironment, + "microsoft.authorization/roleassignments": azurermRoleAssignment, + "microsoft.authorization/roledefinitions": azurermRoleDefinition, + "microsoft.containerregistry/registries": azurermContainerRegistry, + "microsoft.containerservice/managedclusters": azurermKubernetesCluster, + "microsoft.compute/virtualmachines": azurermVirtualMachine, + "microsoft.compute/virtualmachines/extensions": azurermVirtualMachineExtension, + "microsoft.keyvault/vaults": azurermKeyVault, + "microsoft.managedidentity/userassignedidentities": azurermUserAssignedIdentity, + "microsoft.network/applicationgateways": azurermApplicationGateway, + "Microsoft.Network/applicationGateways/backendAddressPools": azurermNetworkInterfaceApplicationGatewayBackendAddressPoolAssociation, + "microsoft.network/bastionhosts": azurermBastionHost, + "microsoft.network/networkinterfaces": azurermNetworkInterface, + "microsoft.network/networksecuritygroups": azurermNetworkSecurityGroup, + "microsoft.network/publicipaddresses": azurermPublicIP, + "microsoft.network/virtualnetworks": azurermVirtualNetwork, + "microsoft.network/virtualnetworks/subnets": azurermSubnet, + "microsoft.network/privatednszones": azurermPrivateDNSZone, + "microsoft.network/privatednszones/virtualnetworklinks": azurermPrivateDNSZoneVirtualNetworkLink, + "microsoft.network/privateendpoints": azurermPrivateEndpoint, + "microsoft.network/applicationgatewaywebapplicationfirewallpolicies": azurermWebApplicationFirewallPolicy, + "microsoft.operationalinsights/workspaces": azurermLogAnalyticsWorkspace, + "microsoft.insights/activitylogalerts": azurermMonitorActivityLogAlert, + "microsoft.operationsmanagement/solutions": azurermLogAnalyticsSolution, + "microsoft.resources/deployments": azurermTemplateDeployment, + "microsoft.servicebus/namespaces": azurermServicebusNamespace, + "microsoft.servicebus/namespaces/authorizationRules": azurermServicebusNamespaceAuthorizationRule, + "microsoft.servicebus/namespaces/queues": azurermServicebusQueue, + "microsoft.storage/storageaccounts": azurermStorageAccount, } var myContent []byte var ok bool + myType = strings.ToLower(strings.TrimSuffix(myType, "/")) + if TFLookup[myType] != nil { - myContent, ok = TFLookup[strings.TrimSuffix(myType, "/")].([]byte) + myContent, ok = TFLookup[myType].([]byte) if !ok { log.Warn().Msg("Failed to cast lookup") } diff --git a/src/arm/outputs.go b/src/arm/outputs.go new file mode 100644 index 0000000..69d1286 --- /dev/null +++ b/src/arm/outputs.go @@ -0,0 +1,44 @@ +package arm + +import ( + "bytes" + "sato/src/cf" + tftemplate "text/template" +) + +// parseOutputs writes out to outputs.tf +func parseOutputs(result map[string]interface{}, funcMap tftemplate.FuncMap, destination string) error { + if result["outputs"] == nil { + return nil + } + + outputs := result["outputs"].(map[string]interface{}) + + var All string + for name, value := range outputs { + var myVar cf.Output + var someString string + myVar.Type = "string" + myVar.Name = name + temp := value.(map[string]interface{}) + someString, result = parseString(temp["value"].(string), result) + myVar.Value = someString + var output bytes.Buffer + tmpl, err := tftemplate.New("test").Funcs(funcMap).Parse(string(outputFile)) + if err != nil { + return err + } + _ = tmpl.Execute(&output, m{ + "variable": myVar, + "item": name, + }) + All = All + output.String() + } + + err := cf.Write(All, destination, "outputs") + if err != nil { + return err + } + + return nil +} diff --git a/src/arm/outputs_test.go b/src/arm/outputs_test.go new file mode 100644 index 0000000..af70541 --- /dev/null +++ b/src/arm/outputs_test.go @@ -0,0 +1,33 @@ +package arm + +import ( + "testing" + "text/template" +) + +func Test_parseOutputs(t *testing.T) { + t.Parallel() + + type args struct { + result map[string]interface{} + funcMap template.FuncMap + destination string + } + + tests := []struct { + name string + args args + wantErr bool + }{ + // {}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if err := parseOutputs(tt.args.result, tt.args.funcMap, tt.args.destination); (err != nil) != tt.wantErr { + t.Errorf("parseOutputs() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/src/arm/parameters.go b/src/arm/parameters.go new file mode 100644 index 0000000..f7453d3 --- /dev/null +++ b/src/arm/parameters.go @@ -0,0 +1,40 @@ +package arm + +import ( + "bytes" + "fmt" + tftemplate "text/template" + + "github.com/rs/zerolog/log" +) + +func parseParameters(result map[string]interface{}, funcMap tftemplate.FuncMap, All string) (string, []interface{}, error) { + parameters, ok := result["parameters"].(map[string]interface{}) + if !ok { + return "", nil, fmt.Errorf("failed to cast to map") + } + myVariables := make([]interface{}, 0) + var err error + for name, item := range parameters { + + myItem := item.(map[string]interface{}) + + myItem, err = fixType(myItem) + if err != nil { + log.Print(err) + } + + var output bytes.Buffer + tmpl, err := tftemplate.New("test").Funcs(funcMap).Parse(string(variableFile)) + if err != nil { + return "", nil, err + } + _ = tmpl.Execute(&output, m{ + "variable": myItem, + "item": name, + }) + All = All + output.String() + myVariables = append(myVariables, myItem) + } + return All, myVariables, nil +} diff --git a/src/arm/parse.go b/src/arm/parse.go index bc75e28..96ec019 100644 --- a/src/arm/parse.go +++ b/src/arm/parse.go @@ -1,13 +1,11 @@ package arm import ( - "bytes" "encoding/json" "fmt" "io" "os" "path/filepath" - "reflect" "regexp" "sato/src/cf" "sato/src/see" @@ -51,6 +49,7 @@ func Parse(file string, destination string) error { "Array": cf.Array, "ArrayReplace": cf.ArrayReplace, "Contains": cf.Contains, + "Enabled": enabled, "Sprint": cf.Sprint, "Decode64": cf.Decode64, "Boolean": cf.Boolean, @@ -68,6 +67,7 @@ func Parse(file string, destination string) error { return string(a) }, + "Set": arrayToString, "Split": cf.Split, "SplitOn": cf.SplitOn, "Replace": cf.Replace, @@ -77,6 +77,7 @@ func Parse(file string, destination string) error { "Snake": cf.Snake, "Kebab": cf.Kebab, "ZipFile": cf.Zipfile, + "Uuid": uuid, } result = preprocess(result) @@ -103,186 +104,6 @@ func Parse(file string, destination string) error { return nil } -// parseVariables convert ARM Parameters into terraform variables. -func parseVariables(result map[string]interface{}, funcMap tftemplate.FuncMap, destination string) (map[string]interface{}, error) { - variables := make(map[string]interface{}) - if result["variables"] != nil { - variables = result["variables"].(map[string]interface{}) - } - - var All string - - All, myVariables, err := parseParameters(result, funcMap, All) - if err != nil { - return result, err - } - - locals, result, err := parseLocals(result) - if err != nil { - return result, err - } - - for name, value := range variables { - myItem := make(map[string]interface{}) - - if value != nil { - var local string - - if reflect.TypeOf(value).String() == typeString { - if strings.Contains(value.(string), "()") || - strings.Contains(value.(string), "[") { - value, result = parseString(value.(string), result) - - local = "\t" + name + " = " + value.(string) + "\n" - locals += local - continue - } - - myItem["default"] = value - } - - if reflect.TypeOf(value).String() == "map[string]interface {}" { - blob, err := json.Marshal(value) - if err != nil { - log.Warn().Msgf("fail to marshal %s", value) - } - - myItem["default"] = string(blob) - } - - myItem["name"] = name - myItem["type"] = typeString - } - - myItem, err = fixType(myItem) - if err != nil { - log.Print(err) - } - - var output bytes.Buffer - - tmpl, err := tftemplate.New("test").Funcs(funcMap).Parse(string(variableFile)) - - if err != nil { - return result, err - } - _ = tmpl.Execute(&output, m{ - "variable": myItem, - "item": name, - }) - All += output.String() - - myVariables = append(myVariables, myItem) - } - - err = cf.Write(All, destination, "variables") - if err != nil { - return result, err - } - - locals = "locals {\n" + locals + "}\n" - err = cf.Write(locals, destination, "locals") - - if err != nil { - return result, err - } - - return result, nil -} - -func parseParameters(result map[string]interface{}, funcMap tftemplate.FuncMap, All string) (string, []interface{}, error) { - parameters, ok := result["parameters"].(map[string]interface{}) - if !ok { - return "", nil, fmt.Errorf("failed to cast to map") - } - myVariables := make([]interface{}, 0) - var err error - for name, item := range parameters { - - myItem := item.(map[string]interface{}) - - myItem, err = fixType(myItem) - if err != nil { - log.Print(err) - } - - var output bytes.Buffer - tmpl, err := tftemplate.New("test").Funcs(funcMap).Parse(string(variableFile)) - if err != nil { - return "", nil, err - } - _ = tmpl.Execute(&output, m{ - "variable": myItem, - "item": name, - }) - All = All + output.String() - myVariables = append(myVariables, myItem) - } - return All, myVariables, nil -} - -func parseLocals(result map[string]interface{}) (string, map[string]interface{}, error) { - var locals string - myLocals := result["locals"].(map[string]interface{}) - - for x, y := range myLocals { - var temp *string - temp, result = parseString(y.(string), result) - local := "\t" + x + " = " + *temp + "\n" - locals = locals + local - } - - return locals, result, nil -} - -// parseResources handles resources in ARM conversion -func parseResources(result map[string]interface{}, funcMap tftemplate.FuncMap, destination string) (map[string]interface{}, error) { - resources := result["resources"].([]interface{}) - - newResources, err := parseList(resources, result) - - if err != nil { - return nil, err - } - - result["resources"] = newResources - - for _, resource := range newResources { - var output bytes.Buffer - var name *string - myType := resource.(map[string]interface{}) - myContent := lookup(myType["type"].(string)) - first, err := see.Lookup(myType["type"].(string)) - - if err != nil { - log.Warn().Err(err) - continue - } - - temp := myType["resource"].(string) - name = &temp - - // needs to pivot on policy template from resource - tmpl, err := tftemplate.New("sato").Funcs(funcMap).Parse(string(myContent)) - if err != nil { - log.Printf("failed at %s for %s %s", err, *first, *name) - continue - } - - _ = tmpl.Execute(&output, cf.M{ - "resource": resource, - "item": name, - }) - - err = cf.Write(output.String(), destination, *first+"."+strings.Replace(*name, "var.", "", 1)) - if err != nil { - return nil, err - } - } - - return result, nil -} - func parseList(resources []interface{}, result map[string]interface{}) ([]interface{}, error) { var newResources []interface{} for _, resource := range resources { @@ -300,9 +121,9 @@ func parseMap(myResource map[string]interface{}, result map[string]interface{}) for name, attribute := range myResource { switch attribute.(type) { case string: - var temp *string + var temp string temp, result = parseString(attribute.(string), result) - myResource[name] = *temp + myResource[name] = temp case map[string]interface{}: var err error myResource[name], err = parseMap(attribute.(map[string]interface{}), result) @@ -314,9 +135,9 @@ func parseMap(myResource map[string]interface{}, result map[string]interface{}) for index, resource := range myArray { switch resource.(type) { case string: - var temp *string + var temp string temp, result = parseString(resource.(string), result) - myArray[index] = *temp + myArray[index] = temp case map[string]interface{}: myArray[index], _ = parseMap(resource.(map[string]interface{}), result) case []interface{}: @@ -334,64 +155,90 @@ func parseMap(myResource map[string]interface{}, result map[string]interface{}) return myResource, nil } -func parseString(newAttribute string, result map[string]interface{}) (*string, map[string]interface{}) { +func parseString(attribute string, result map[string]interface{}) (string, map[string]interface{}) { matches := []string{ "parameters", "variables", "toLower", "resourceGroup().location", "resourceGroup().id", - "substring", "uniqueString", "reference", "resourceId", "listKeys", "format('", - } - - if what, found := contains(matches, newAttribute); found { - newAttribute, result = replace(matches, newAttribute, what, result) - newAttribute = loseSQBrackets(newAttribute) + "substring", "uniqueString", "reference", "resourceId", "listKeys", "format('", "SubscriptionResourceId", + "concat", "subscription().tenantId", "uuid", } - return &newAttribute, result -} -func loseSQBrackets(newAttribute string) string { - re := regexp.MustCompile(`^\[(.*)\]`) // format('{0}/{1}', - Matched := re.FindStringSubmatch(newAttribute) - if len(Matched) > 1 { - return Matched[1] + if what, found := contains(matches, attribute); found { + attribute, result = replace(matches, attribute, what, result) + attribute = loseSQBrackets(attribute) } - return newAttribute + return attribute, result } func replace(matches []string, newAttribute string, what *string, result map[string]interface{}) (string, map[string]interface{}) { var Attribute string switch *what { - case "reference", "resourceId": + case "concat": { - if *what == "reference" { - Attribute = loseSQBrackets(newAttribute) - re := regexp.MustCompile(`reference\((.*?)\)\.`) // format('{0}/{1}', - Match := re.FindStringSubmatch(Attribute) - if (Match) != nil { - rem := regexp.MustCompile(`\)\.(.*)`) // format('{0}/{1}', - remains := rem.FindStringSubmatch(Attribute) - if len(remains) <= 1 { - log.Print("no attribute") + Attribute = loseSQBrackets(newAttribute) + ditched := ditch(Attribute, "concat") + + raw := strings.Split(ditched, ",") + var after string + + for item, value := range raw { + if value == "/" { + value = "_" + } + + if strings.Contains(value, "Microsoft") { + var err error + value, err = resourceToName(value, result) + if err != nil { + log.Debug().Msgf("Concat failed: %v", err) } - re2 := regexp.MustCompile(`resourceId\((.*?)\)`) // format('{0}/{1}', - Match2 := re2.FindStringSubmatch(Attribute) - Attribute = Match2[0] + "." + remains[1] - } else { - log.Info().Msgf("ignoring %s", newAttribute) } - } else { - var err error - Attribute, err = replaceResourceID(loseSQBrackets(newAttribute), result) - if err != nil { - log.Warn().Msgf("failed to parse %s", newAttribute) + + s := []string{"var.", "azurerm_", "local.", "substr"} + for _, v := range s { + if strings.Contains(strings.ToLower(value), strings.ToLower(v)) { + if v == "substr" { + after = "${" + strings.TrimSpace(strings.Join(raw[1:], ",")) + "}" + continue + } else { + raw[item] = fmt.Sprintf("${%s}", strings.ReplaceAll(strings.TrimSpace(value), "'", "")) + } + } + } + raw[item] = strings.ReplaceAll(strings.TrimSpace(raw[item]), "'", "") + } + + if after == "" { + Attribute = strings.Join(raw, "") + } else { + Attribute = raw[0] + after + } + } + case "reference": + { + Attribute = ditch(loseSQBrackets(newAttribute), "reference") + } + case "resourceId": + { + Attribute = loseSQBrackets(newAttribute) + + var err error + Attribute, err = replaceResourceID(Attribute, result) + if err != nil { + log.Warn().Msgf("failed to parse %s", newAttribute) } } case "uniqueString", "uniquestring": { - target := *what + "\\((.*?)\\)" + target := "uniquestring\\((.*?)\\)" re := regexp.MustCompile(target) // format('{0}/{1}', - Match := re.ReplaceAllString(newAttribute, "substr(uuid(), 0, 8)") + Match := re.ReplaceAllString(strings.ToLower(newAttribute), "substr(uuid(), 0, 8)") Attribute = Match } + case "subscriptionResourceId": + { + Attribute = ditch(newAttribute, "subscriptionResourceId") + } case "format('": { re := regexp.MustCompile(`{.}`) // format('{0}/{1}', @@ -433,35 +280,68 @@ func replace(matches []string, newAttribute string, what *string, result map[str re := regexp.MustCompile(`variables\('(.*?)\'\)`) Match := re.FindStringSubmatch(newAttribute) if (Match) != nil { - var temp string + var myTemp string if IsLocal(Match[1], result) { - temp = "local." + Match[1] + myTemp = "local." + Match[1] } else { - temp = "var." + Match[1] + myTemp = "var." + Match[1] } - Attribute = strings.Replace(newAttribute, Match[0], temp, -1) + Attribute = strings.Replace(newAttribute, Match[0], myTemp, -1) } else { log.Printf("not found %s", newAttribute) } } - case "toLower": + case "toLower", "tolower": { - Attribute = strings.Replace(newAttribute, "toLower", "lower", -1) + re := regexp.MustCompile(`(?i)tolower`) + Attribute = re.ReplaceAllString(newAttribute, "lower") } case "resourceGroup().location": { Attribute = strings.Replace(newAttribute, "resourceGroup().location", "data.azurerm_resource_group.sato.location", -1) - data := make(map[string]bool) + data := make(map[string]interface{}) if result["data"] == nil { data["resource_group"] = true } else { - data = result["data"].(map[string]bool) + data = result["data"].(map[string]interface{}) data["resource_group"] = true } result["data"] = data } + case "uuid": + { + data := make(map[string]interface{}) + if result["data"] == nil { + data["uuid"] = 0 + } else { + data = result["data"].(map[string]interface{}) + //temp := data["uuid"].(int) + 1 + if data["uuid"] != nil { + data["uuid"] = data["uuid"].(int) + 1 + } else { + data["uuid"] = 0 + } + } + + result["data"] = data + replacement := "random_uuid.sato" + strconv.Itoa(data["uuid"].(int)) + ".result" + Attribute = strings.Replace(newAttribute, "uuid()", replacement, 1) + } + case "subscription().tenantId": + { + Attribute = strings.Replace(newAttribute, "subscription().tenantId", + "data.azurerm_client_config.sato.tenant_id", -1) + data := make(map[string]interface{}) + if result["data"] == nil { + data["client_config"] = true + } else { + data = result["data"].(map[string]interface{}) + data["client_config"] = true + } + result["data"] = data + } case "resourceGroup().id": { Attribute = loseSQBrackets(strings.Replace(newAttribute, "resourceGroup().id", @@ -488,8 +368,11 @@ func replace(matches []string, newAttribute string, what *string, result map[str func replaceResourceID(Match string, result map[string]interface{}) (string, error) { if strings.Contains(Match, "extensionResourceId") { - re := regexp.MustCompile(`extensionResourceId\((.*?)\)`) - Match = re.ReplaceAllString(Match, "NIL") + Match = strings.Replace(Match, "extensionResourceId", "resourceId", 1) + } + + if strings.Contains(Match, "subscriptionResourceId") { + Match = strings.Replace(Match, "subscriptionResourceId", "resourceId", 1) } re := regexp.MustCompile(`resourceId\((.*?)\)`) @@ -506,6 +389,7 @@ func replaceResourceID(Match string, result map[string]interface{}) (string, err re3 := regexp.MustCompile(`,(![^[]*\])`) Attribute = re3.FindStringSubmatch(Match) } + arm, name, err := splitResourceName(Attribute[1]) if err != nil { log.Warn().Msgf("failed to parse %s", Attribute[1]) @@ -515,6 +399,7 @@ func replaceResourceID(Match string, result map[string]interface{}) (string, err if err != nil { log.Print(err) } + var resourceName *string if findResourceType(result, arm) { resourceName, err = see.Lookup(arm) @@ -531,8 +416,14 @@ func replaceResourceID(Match string, result map[string]interface{}) (string, err } } - switch arm { - case "Microsoft.ContainerRegistry/registries": + resourceName, err = see.Lookup(arm) + + if err != nil { + return "", err + } + + switch strings.ToLower(arm) { + case "microsoft.containerregistry/registries": { temp := "azurerm_container_registry" resourceName = &temp @@ -541,7 +432,7 @@ func replaceResourceID(Match string, result map[string]interface{}) (string, err log.Warn().Msgf("no match found %s", arm) } } - case "Microsoft.Network/virtualNetworks/subnets": + case "microsoft.network/virtualnetworks/subnets": { temp := "tolist(azurerm_virtual_network" resourceName = &temp @@ -551,14 +442,68 @@ func replaceResourceID(Match string, result map[string]interface{}) (string, err } name = strings.Split(splutters[0], ".")[0] + ".subnet)[0].id" } - case "Microsoft.Authorization/roleDefinitions": + case "microsoft.authorization/roledefinitions": { - temp := "azurerm_role_definition" - resourceName = &temp if unicode.IsDigit(rune(name[0])) { name = "_" + name } } + case "microsoft.network/privateendpoints/privatednszonegroups": + { + // this isn't a separate terraform resource just part of + // private_dns_zone_group in a azurerm_private_endpoint + name, err = resourceToName(Match, result) + if err != nil { + return "", err + } + + return *resourceName + "." + name + ".private_dns_zone_group.id", nil + } + case "microsoft.network/applicationgateways/httplisteners": + { + name, err = resourceToName(Match, result) + + if err != nil { + return "", err + } + return *resourceName + "." + name + ".http_listener.id", nil + } + case "microsoft.network/applicationgateways/frontendipconfigurations": + { + name, err = resourceToName(Match, result) + + if err != nil { + return "", err + } + return *resourceName + "." + name + ".frontend_ip_configuration.id", nil + } + case "microsoft.network/applicationgateways/frontendports": + { + name, err = resourceToName(Match, result) + + if err != nil { + return "", err + } + return *resourceName + "." + name + ".frontend_port", nil + } + case "microsoft.network/applicationgateways/backendaddresspools": + { + name, err = resourceToName(Match, result) + + if err != nil { + return "", err + } + return *resourceName + "." + name + ".frontend_ip_configuration.id", nil + } + case "microsoft.network/applicationgateways/backendhttpsettingscollection": + { + name, err = resourceToName(Match, result) + + if err != nil { + return "", err + } + return *resourceName + "." + name + ".backend_http_settings", nil + } default: { if strings.Contains(name, "local") { @@ -569,7 +514,7 @@ func replaceResourceID(Match string, result map[string]interface{}) (string, err } resourceName, err = see.Lookup(cf.Dequote(arm)) if err != nil { - resourceName = toPointer() + resourceName = toUnknownPointer() log.Warn().Msgf("no match found %s", arm) } } @@ -584,7 +529,36 @@ func replaceResourceID(Match string, result map[string]interface{}) (string, err return "", err } -func toPointer() *string { +func resourceToName(Match string, result map[string]interface{}) (string, error) { + re := regexp.MustCompile(`^resourceId\((.*)\)`) + splitter := re.FindStringSubmatch(Match) + if len(splitter) > 1 { + //ditch type + _, found, _ := strings.Cut(splitter[1], ",") + myResourceName, _, _ := strings.Cut(found, ",") + name, err := findResourceName(result, strings.TrimSpace(myResourceName)) + + if err != nil { + log.Warn().Msgf("no match found %s", Match) + } else { + return name, err + } + } + + if len(splitter) == 0 { + name, err := findResourceName(result, strings.TrimSpace(Match)) + + if err != nil { + log.Warn().Msgf("no match found %s", Match) + } else { + return name, err + } + } + + return "", fmt.Errorf("failed to split resource %s", Match) +} + +func toUnknownPointer() *string { temp := "UNKNOWN" return &temp } @@ -605,7 +579,11 @@ func splitResourceName(Attribute string) (string, string, error) { newAttribute := re.FindStringSubmatch(splitsy[1]) if len(newAttribute) <= 1 { arm = cf.Dequote(splitsy[0]) + // check it's not an array name = strings.TrimSpace(splitsy[1]) + if strings.Contains(name, ",") { + name = strings.Split(name, ",")[0] + } } else { name = newAttribute[1] newArm := re.FindStringSubmatch(splitsy[0]) @@ -631,12 +609,53 @@ func findResourceName(result map[string]interface{}, name string) (string, error name = cf.Dequote(name) var err error - resources := result["resources"].([]interface{}) + + if result["resources"] == nil { + return "", fmt.Errorf("resources are empty") + } + + resources, ok := result["resources"].([]interface{}) + + if !ok { + return name, fmt.Errorf("no resources found") + } + for _, myResource := range resources { - test := myResource.(map[string]interface{}) - if name == test["name"].(string) { + test, ok := myResource.(map[string]interface{}) + if !ok { + log.Print("resource is not a map") + continue + } + temp := loseSQBrackets(test["name"].(string)) + + if name == temp { + return test["resource"].(string), nil + } + + trimName := strings.Replace(name, "var.", "", 1) + trimTemp := strings.Replace(ditch(temp, "variables"), "'", "", 2) + + if trimTemp == trimName { return test["resource"].(string), nil } + + if strings.Contains(name, "local") { + resourceName := strings.Split(name, ".")[1] + if strings.Contains(temp, resourceName) { + retrieved := test["resource"].(string) + return retrieved, nil + } + } + + re := regexp.MustCompile(`\((.*?)\)`) + splits := re.FindStringSubmatch(temp) + if len(splits) > 1 { + name = strings.Trim(name, "var.") + + if name == strings.ReplaceAll(splits[1], "'", "") { + return test["resource"].(string), nil + } + } } // not simple name lookup @@ -688,6 +707,10 @@ func getNameValue(result map[string]interface{}, name string) (string, error) { } func findResourceType(result map[string]interface{}, name string) bool { + if result["resources"] == nil { + return false + } + resources := result["resources"].([]interface{}) for _, myResource := range resources { test := myResource.(map[string]interface{}) @@ -698,85 +721,6 @@ func findResourceType(result map[string]interface{}, name string) bool { return false } -// parseOutputs writes out to outputs.tf -func parseOutputs(result map[string]interface{}, funcMap tftemplate.FuncMap, destination string) error { - if result["outputs"] == nil { - return nil - } - - outputs := result["outputs"].(map[string]interface{}) - - var All string - for name, value := range outputs { - var myVar cf.Output - var someString *string - myVar.Type = "string" - myVar.Name = name - temp := value.(map[string]interface{}) - someString, result = parseString(temp["value"].(string), result) - myVar.Value = *someString - var output bytes.Buffer - tmpl, err := tftemplate.New("test").Funcs(funcMap).Parse(string(outputFile)) - if err != nil { - return err - } - _ = tmpl.Execute(&output, m{ - "variable": myVar, - "item": name, - }) - All = All + output.String() - } - - err := cf.Write(All, destination, "outputs") - if err != nil { - return err - } - - return nil -} - -// parseData writes out to data.tf -func parseData(result map[string]interface{}, funcMap tftemplate.FuncMap, destination string) error { - if result["data"] == nil { - return nil - } - - data := result["data"] - - var output bytes.Buffer - tmpl, err := tftemplate.New("test").Funcs(funcMap).Parse(string(dataFile)) - if err != nil { - return err - } - err = tmpl.Execute(&output, m{ - "data": data, - }) - - if err != nil { - log.Print(err) - return err - } - - err = cf.Write(output.String(), destination, "data") - if err != nil { - log.Print(err) - return err - } - - return nil -} - -// getValue gets from variables -func getValue(item string, variables []cf.Variable) (*string, error) { - for _, x := range variables { - if x.Name == item { - return &x.Default, nil - } - } - something := fmt.Errorf("%s Not found", item) - return nil, something -} - func preprocess(results map[string]interface{}) map[string]interface{} { results["resources"] = setResourceNames(results) locals := make(map[string]interface{}) @@ -815,7 +759,7 @@ func preprocess(results map[string]interface{}) map[string]interface{} { _, ok := myResult["defaultValue"] if ok { myType := myResult["type"].(string) - switch myType { + switch strings.ToLower(myType) { case "string": { defaultValue := myResult["defaultValue"].(string) @@ -849,6 +793,11 @@ func preprocess(results map[string]interface{}) map[string]interface{} { { log.Printf("handled %s", myType) } + case "bool": + { + myResult["default"] = fmt.Sprintf("%v", myResult["defaultValue"]) + newParams[item] = myResult + } default: { log.Printf("unhandled %s", myType) diff --git a/src/arm/parse_test.go b/src/arm/parse_test.go index d7f3234..259e837 100644 --- a/src/arm/parse_test.go +++ b/src/arm/parse_test.go @@ -3,46 +3,9 @@ package arm import ( "path/filepath" "reflect" - sato "sato/src/cf" "testing" - "text/template" ) -func TestGetValue(t *testing.T) { - t.Parallel() - - type args struct { - item string - variables []sato.Variable - } - - tests := []struct { - name string - args args - want *string - wantErr bool - }{ - //{}, - } - - for _, tt := range tests { - tt := tt - - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - got, err := getValue(tt.args.item, tt.args.variables) - if (err != nil) != tt.wantErr { - t.Errorf("getValue() error = %v, wantErr %v", err, tt.wantErr) - - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("getValue() got = %v, want %v", got, tt.want) - } - }) - } -} - func TestParse(t *testing.T) { t.Parallel() @@ -71,133 +34,31 @@ func TestParse(t *testing.T) { } } -func TestParseData(t *testing.T) { - t.Parallel() - - type args struct { - result map[string]interface{} - funcMap template.FuncMap - destination string - } - - tests := []struct { - name string - args args - wantErr bool - }{ - // {}, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - if err := parseData(tt.args.result, tt.args.funcMap, tt.args.destination); (err != nil) != tt.wantErr { - t.Errorf("parseData() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestParseOutputs(t *testing.T) { - t.Parallel() - - type args struct { - result map[string]interface{} - funcMap template.FuncMap - destination string - } - - tests := []struct { - name string - args args - wantErr bool - }{ - // {}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - if err := parseOutputs(tt.args.result, tt.args.funcMap, tt.args.destination); (err != nil) != tt.wantErr { - t.Errorf("parseOutputs() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestParseResources(t *testing.T) { - t.Parallel() - - type args struct { - result map[string]interface{} - funcMap template.FuncMap - destination string - } - - tests := []struct { - name string - args args - want map[string]interface{} - wantErr bool - }{ - //{}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := parseResources(tt.args.result, tt.args.funcMap, tt.args.destination) - if (err != nil) != tt.wantErr { - t.Errorf("parseResources() error = %v, wantErr %v", err, tt.wantErr) - - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("parseResources() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestParseVariables(t *testing.T) { +func Test_findResourceName(t *testing.T) { t.Parallel() type args struct { - result map[string]interface{} - funcMap template.FuncMap - destination string + result map[string]interface{} + name string } - tests := []struct { - name string - args args - want []interface{} - wantErr bool - }{ - //{}, + target := map[string]interface{}{ + "resources": []interface{}{ + map[string]interface{}{ + "name": "[variables('PrivateDNSZone')]", + "resource": "sato0", + }, + }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - got, err := parseVariables(tt.args.result, tt.args.funcMap, tt.args.destination) - if (err != nil) != tt.wantErr { - t.Errorf("parseVariables() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("parseVariables() got = %v, want %v", got, tt.want) - } - }) + empty := map[string]interface{}{ + "resources": []interface{}{ + nil, + }, } -} - -func Test_findResourceName(t *testing.T) { - t.Parallel() - type args struct { - result map[string]interface{} - name string + veryEmpty := map[string]interface{}{ + "resources": nil, } tests := []struct { @@ -206,7 +67,11 @@ func Test_findResourceName(t *testing.T) { want string wantErr bool }{ - //{}, + {"Pass", args{target, "PrivateDNSZone"}, "sato0", false}, + {"Fail", args{target, "MyDNSZone"}, "", true}, + {"format", args{target, "format('PrivateDNSZone')"}, "format('PrivateDNSZone')", true}, + {"Empty", args{empty, "PrivateDNSZone"}, "PrivateDNSZone", false}, + {"Very Empty", args{veryEmpty, "PrivateDNSZone"}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -231,12 +96,34 @@ func Test_findResourceType(t *testing.T) { name string } + target := map[string]interface{}{ + "resources": []interface{}{ + map[string]interface{}{ + "name": "[variables('PrivateDNSZone')]", + "resource": "sato0", + "type": "fred", + }, + }, + } + + empty := map[string]interface{}{ + "resources": []interface{}{ + nil, + }, + } + + veryEmpty := map[string]interface{}{ + "resources": nil, + } + tests := []struct { name string args args want bool }{ - //{}, + {"Pass", args{target, "fred"}, true}, + {"Empty", args{empty, ""}, false}, + {"Very Empty", args{veryEmpty, ""}, false}, } for _, tt := range tests { @@ -291,33 +178,6 @@ func Test_getNameValue(t *testing.T) { } } -func Test_loseSQBrackets(t *testing.T) { - type args struct { - newAttribute string - } - - tests := []struct { - name string - args args - want string - }{ - {"pass", args{"[pass]"}, "pass"}, - {"no pass", args{"[pass"}, "[pass"}, - {"leave", args{"stuff[pass]"}, "stuff[pass]"}, - {"leave with outside", args{"[stuff[pass]]"}, "stuff[pass]"}, - {"just", args{"[]"}, ""}, - {"nil", args{""}, ""}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := loseSQBrackets(tt.args.newAttribute); got != tt.want { - t.Errorf("loseSQBrackets() = %v, want %v", got, tt.want) - } - }) - } -} - func Test_parseList(t *testing.T) { type args struct { resources []interface{} @@ -330,7 +190,7 @@ func Test_parseList(t *testing.T) { want []interface{} wantErr bool }{ - {}, + //{"pass", args{}}, } for _, tt := range tests { @@ -381,43 +241,6 @@ func Test_parseMap(t *testing.T) { } } -func Test_parseParameters(t *testing.T) { - t.Parallel() - - type args struct { - result map[string]interface{} - funcMap template.FuncMap - All string - } - - tests := []struct { - name string - args args - want string - want1 []interface{} - wantErr bool - }{ - //{"Pass", args{}, "false"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - got, got1, err := parseParameters(tt.args.result, tt.args.funcMap, tt.args.All) - if (err != nil) != tt.wantErr { - t.Errorf("parseParameters() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("parseParameters() got = %v, want %v", got, tt.want) - } - if !reflect.DeepEqual(got1, tt.want1) { - t.Errorf("parseParameters() got1 = %v, want %v", got1, tt.want1) - } - }) - } -} - func Test_preprocess(t *testing.T) { t.Parallel() @@ -544,6 +367,33 @@ func Test_parseLocals(t *testing.T) { result map[string]interface{} } + parsed := "\tvmSubnetNsgId = azurerm_network_security_group.sato8 #[resourceId(\"Microsoft.Network/networkSecurityGroups\",variables(\"vmSubnetNsgName\"))]\n" + bodge := make(map[string]interface{}) + + startLocals := map[string]interface{}{ + "vmSubnetNsgId": "[resourceId('Microsoft.Network/networkSecurityGroups',variables('vmSubnetNsgName'))]", + } + + locals := map[string]interface{}{ + "vmSubnetNsgId": "azurerm_network_security_group.sato8", + } + var resources []interface{} + resource := map[string]interface{}{ + "type": "Microsoft.Network/networkSecurityGroups", + "name": "[variables('vmSubnetNsgName')]", + "location": "[parameters('location')]", + "resource": "sato8", + } + resources = append(resources, resource) + + result := make(map[string]interface{}) + + result["locals"] = locals + result["resources"] = resources + + bodge["locals"] = startLocals + bodge["resources"] = resources + tests := []struct { name string args args @@ -551,7 +401,7 @@ func Test_parseLocals(t *testing.T) { want1 map[string]interface{} wantErr bool }{ - //{}, + {"test", args{bodge}, parsed, result, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -563,7 +413,7 @@ func Test_parseLocals(t *testing.T) { return } if got != tt.want { - t.Errorf("parseLocals() got = %v, want %v", got, tt.want) + t.Errorf("parseLocals()\n got:\n %v\n want:\n %v", got, tt.want) } if !reflect.DeepEqual(got1, tt.want1) { t.Errorf("parseLocals() got1 = %v, want %v", got1, tt.want1) diff --git a/src/arm/resource.go b/src/arm/resource.go new file mode 100644 index 0000000..50fede1 --- /dev/null +++ b/src/arm/resource.go @@ -0,0 +1,104 @@ +package arm + +import ( + _ "embed" // required for embed +) + +//go:embed resources/azurerm_virtual_machine.template +var azurermVirtualMachine []byte + +//go:embed resources/azurerm_virtual_machine_extension.template +var azurermVirtualMachineExtension []byte + +//go:embed resources/azurerm_network_interface.template +var azurermNetworkInterface []byte + +//go:embed resources/azurerm_network_security_group.template +var azurermNetworkSecurityGroup []byte + +//go:embed resources/azurerm_public_ip.template +var azurermPublicIP []byte + +//go:embed resources/azurerm_virtual_network.template +var azurermVirtualNetwork []byte + +//go:embed resources/azurerm_storage_account.template +var azurermStorageAccount []byte + +//go:embed resources/azurerm_subnet.template +var azurermSubnet []byte + +//go:embed resources/azurerm_analysis_services_server.template +var azurermAnalysisServicesServer []byte + +//go:embed resources/azurerm_api_management.template +var azurermAPIManagement []byte + +//go:embed resources/azurerm_container_app.template +var azurermContainerApp []byte + +//go:embed resources/azurerm_container_app_environment.template +var azurermContainerAppEnvironment []byte + +//go:embed resources/azurerm_template_deployment.template +var azurermTemplateDeployment []byte + +//go:embed resources/azurerm_role_assignment.template +var azurermRoleAssignment []byte + +//go:embed resources/azurerm_user_assigned_identity.template +var azurermUserAssignedIdentity []byte + +//go:embed resources/azurerm_log_analytics_workspace.template +var azurermLogAnalyticsWorkspace []byte + +//go:embed resources/azurerm_role_definition.template +var azurermRoleDefinition []byte + +//go:embed resources/azurerm_servicebus_namespace.template +var azurermServicebusNamespace []byte + +//go:embed resources/azurerm_servicebus_namespace_authorization_rule.template +var azurermServicebusNamespaceAuthorizationRule []byte + +//go:embed resources/azurerm_servicebus_queue.template +var azurermServicebusQueue []byte + +//go:embed resources/azurerm_active_directory_domain_service.template +var azurermActiveDirectoryDomainService []byte + +//go:embed resources/azurerm_bastion_host.template +var azurermBastionHost []byte + +//go:embed resources/azurerm_key_vault.template +var azurermKeyVault []byte + +//go:embed resources/azurerm_container_registry.template +var azurermContainerRegistry []byte + +//go:embed resources/azurerm_kubernetes_cluster.template +var azurermKubernetesCluster []byte + +//go:embed resources/azurerm_log_analytics_solution.template +var azurermLogAnalyticsSolution []byte + +//go:embed resources/azurerm_private_dns_zone.template +var azurermPrivateDNSZone []byte + +//go:embed resources/azurerm_private_dns_zone_virtual_network_link.template +var azurermPrivateDNSZoneVirtualNetworkLink []byte + +//go:embed resources/azurerm_private_endpoint.template +var azurermPrivateEndpoint []byte + +//go:embed resources/azurerm_monitor_activity_log_alert.template +var azurermMonitorActivityLogAlert []byte + +//go:embed resources/azurerm_web_application_firewall_policy.template +var azurermWebApplicationFirewallPolicy []byte + +//go:embed resources/azurerm_application_gateway.template +var azurermApplicationGateway []byte + +//go:embed resources/azurerm_network_interface_application_gateway_backend_address_pool_association.template +var azurermNetworkInterfaceApplicationGatewayBackendAddressPoolAssociation []byte diff --git a/src/arm/resources.go b/src/arm/resources.go index 6f35bbe..aed7834 100644 --- a/src/arm/resources.go +++ b/src/arm/resources.go @@ -1,68 +1,59 @@ package arm import ( - _ "embed" // required for embed -) - -//go:embed resources/azurerm_virtual_machine.template -var azurermVirtualMachine []byte - -//go:embed resources/azurerm_virtual_machine_extension.template -var azurermVirtualMachineExtension []byte - -//go:embed resources/azurerm_network_interface.template -var azurermNetworkInterface []byte - -//go:embed resources/azurerm_network_security_group.template -var azurermNetworkSecurityGroup []byte - -//go:embed resources/azurerm_public_ip.template -var azurermPublicIP []byte - -//go:embed resources/azurerm_virtual_network.template -var azurermVirtualNetwork []byte - -//go:embed resources/azurerm_storage_account.template -var azurermStorageAccount []byte - -//go:embed resources/azurerm_subnet.template -var azurermSubnet []byte - -//go:embed resources/azurerm_analysis_services_server.template -var azurermAnalysisServicesServer []byte - -//go:embed resources/azurerm_api_management.template -var azurermAPIManagement []byte + "bytes" + "sato/src/cf" + "sato/src/see" + "strings" + tftemplate "text/template" -//go:embed resources/azurerm_container_app.template -var azurermContainerApp []byte - -//go:embed resources/azurerm_container_app_environment.template -var azurermContainerAppEnvironment []byte - -//go:embed resources/azurerm_template_deployment.template -var azurermTemplateDeployment []byte - -//go:embed resources/azurerm_role_assignment.template -var azurermRoleAssignment []byte - -//go:embed resources/azurerm_user_assigned_identity.template -var azurermUserAssignedIdentity []byte - -//go:embed resources/azurerm_log_analytics_workspace.template -var azurermLogAnalyticsWorkspace []byte - -//go:embed resources/azurerm_role_definition.template -var azurermRoleDefinition []byte - -//go:embed resources/azurerm_servicebus_namespace.template -var azurermServicebusNamespace []byte - -//go:embed resources/azurerm_servicebus_namespace_authorization_rule.template -var azurermServicebusNamespaceAuthorizationRule []byte - -//go:embed resources/azurerm_servicebus_queue.template -var azurermServicebusQueue []byte + "github.com/rs/zerolog/log" +) -//go:embed resources/azurerm_active_directory_domain_service.template -var azurermActiveDirectoryDomainService []byte +// parseResources handles resources in ARM conversion +func parseResources(result map[string]interface{}, funcMap tftemplate.FuncMap, destination string) (map[string]interface{}, error) { + resources := result["resources"].([]interface{}) + + newResources, err := parseList(resources, result) + + if err != nil { + return nil, err + } + + result["resources"] = newResources + + for _, resource := range newResources { + var output bytes.Buffer + var name *string + myType := resource.(map[string]interface{}) + myContent := lookup(myType["type"].(string)) + first, err := see.Lookup(myType["type"].(string)) + + if err != nil { + log.Warn().Err(err) + continue + } + + temp := myType["resource"].(string) + name = &temp + + // needs to pivot on policy template from resource + tmpl, err := tftemplate.New("sato").Funcs(funcMap).Parse(string(myContent)) + if err != nil { + log.Printf("failed at %s for %s %s", err, *first, *name) + continue + } + + _ = tmpl.Execute(&output, cf.M{ + "resource": resource, + "item": name, + }) + + err = cf.Write(output.String(), destination, *first+"."+strings.Replace(*name, "var.", "", 1)) + if err != nil { + return nil, err + } + } + + return result, nil +} diff --git a/src/arm/resources/azurerm_application_gateway.template b/src/arm/resources/azurerm_application_gateway.template new file mode 100644 index 0000000..891ad4a --- /dev/null +++ b/src/arm/resources/azurerm_application_gateway.template @@ -0,0 +1,69 @@ +resource "azurerm_application_gateway" "{{.item}}" { +{{- if .resource.location}} + location = {{ .resource.location|Quote }} +{{- else}} + location = data.azurerm_resource_group.sato.location +{{- end }} + name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} + + sku { + name = "Standard_Small" + tier = "Standard" + capacity = 2 + } + + gateway_ip_configuration { + name = "my-gateway-ip-configuration" + subnet_id = azurerm_subnet.frontend.id + } + + frontend_port { + name = local.frontend_port_name + port = 80 + } + + frontend_ip_configuration { + name = local.frontend_ip_configuration_name + public_ip_address_id = azurerm_public_ip.example.id + } + + backend_address_pool { + name = local.backend_address_pool_name + } + + backend_http_settings { + name = local.http_setting_name + cookie_based_affinity = "Disabled" + path = "/path1/" + port = 80 + protocol = "Http" + request_timeout = 60 + } + + http_listener { + name = local.listener_name + frontend_ip_configuration_name = local.frontend_ip_configuration_name + frontend_port_name = local.frontend_port_name + protocol = "Http" + } + + request_routing_rule { + name = local.request_routing_rule_name + rule_type = "Basic" + http_listener_name = local.listener_name + backend_address_pool_name = local.backend_address_pool_name + backend_http_settings_name = local.http_setting_name + } + +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{$j}}, {{- end}}] +{{- end}} +} diff --git a/src/arm/resources/azurerm_bastion_host.template b/src/arm/resources/azurerm_bastion_host.template new file mode 100644 index 0000000..539cb8f --- /dev/null +++ b/src/arm/resources/azurerm_bastion_host.template @@ -0,0 +1,29 @@ +resource "azurerm_bastion_host" "{{.item}}" { +{{- if .resource.location}} + location = {{ .resource.location|Quote }} +{{- else}} + location = data.azurerm_resource_group.sato.location +{{- end }} + name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} + +{{- if .resource.properties.ipConfigurations}} +{{ range $i, $j:= .resource.properties.ipConfigurations}} + ip_configuration { + name = {{$j.name|Quote}} + subnet_id = {{$j.properties.subnet.id}} + public_ip_address_id = {{$j.properties.publicIPAddress.id}} + } +{{- end}} +{{- end}} +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{$j}}, {{- end}}] +{{- end}} +} diff --git a/src/arm/resources/azurerm_container_registry.template b/src/arm/resources/azurerm_container_registry.template new file mode 100644 index 0000000..fce5130 --- /dev/null +++ b/src/arm/resources/azurerm_container_registry.template @@ -0,0 +1,114 @@ +resource "azurerm_container_registry" "{{.item}}" { +{{- if .resource.location}} + location = {{ .resource.location|Quote }} +{{- else}} + location = data.azurerm_resource_group.sato.location +{{- end }} + name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} + sku = {{.resource.sku.name}} +{{- if NotNil .resource.properties.adminUserEnabled}} + admin_enabled = {{.resource.properties.adminUserEnabled}} +{{- end}} +{{- if .resource.properties.networkRuleSet}} + network_rule_set { +{{- if .resource.properties.networkRuleSet.defaultAction}} + default_action = {{.resource.properties.networkRuleSet.defaultAction}} +{{- end}} +{{- if .resource.properties.networkRuleSet.ipRule}} + ip_rule { + + } +{{- end}} +{{- if .resource.properties.networkRuleSet.virtualNetwork}} + virtual_network { + } +{{- end}} + } +{{- end}} +{{- if .resource.properties.policies.quarantinePolicy.status}} + quarantine_policy_enabled={{.resource.properties.policies.quarantinePolicy.status|Quote}} +{{- end}} +{{- if .resource.properties.policies.trustPolicy.status}} + trust_policy { + enabled ={{Enabled .resource.properties.policies.trustPolicy.status}} + } +{{- end}} +{{- if .resource.properties.policies.retentionPolicy.status}} + retention_policy { + days = {{.resource.properties.policies.retentionPolicy.days}} + enabled = {{Enabled .resource.properties.policies.retentionPolicy.status}} + } +{{- end}} +{{- if .resource.properties.publicNetworkAccess}} + public_network_access_enabled= {{.resource.properties.publicNetworkAccess}} +{{- end}} +{{- if .resource.properties.encryption}} + encryption { + enabled = {{Enabled .resource.properties.encryption.status}} + } +{{- end}} +{{- if NotNil .resource.properties.dataEndpointEnabled}} + data_endpoint_enabled= {{.resource.properties.dataEndpointEnabled}} +{{- end}} +{{- if .resource.properties.networkRuleBypassOptions}} + network_rule_bypass_option = {{ .resource.properties.networkRuleBypassOptions|Quote}} +{{- end}} +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{ $j}}, {{- end}}] +{{- end}} +} + + +{{- if .resource.resources}} +{{- $j := (index .resource.resources 0)}} +resource "azurerm_role_assignment" "{{.item}}" { + principal_id = {{Nil $j.properties.PrincipalId|Quote}} + role_definition_name = {{$j.name}} + scope = azurerm_container_registry.{{.item}}.id +{{- if $j.dependsOn }} + depends_on = [{{- range $x, $y:= $j.dependsOn }} {{ $y}}, {{- end}}] +{{- end}} +} + + +{{- $y := (index .resource.resources 1)}} +resource "azurerm_monitor_diagnostic_setting" "{{.item}}" { + name={{$y.name|Quote}} +{{- if $y.properties.workspaceId}} + log_analytics_workspace_id={{$y.properties.workspaceId}} +{{- end}} +{{- if $y.properties.metrics}} +{{- range $a, $b:= $y.properties.metrics}} + metric { + category = {{$b.category|Quote}} + retention_policy { + enabled = true + } + enabled = {{$b.enabled}} + } +{{- end}} +{{- end}} +{{- if $y.properties.logs}} +{{- range $a, $b:= $y.properties.logs}} + enabled_log{ + category = {{$b.category|Quote}} + retention_policy { + enabled = true + } + } +{{- end}} +{{- end}} + target_resource_id = azurerm_container_registry.{{.item}}.id + {{- if $y.dependsOn }} + depends_on = [{{- range $x, $z:= $y.dependsOn }} {{ $z}}, {{- end}}] + {{- end}} +} +{{- end}} diff --git a/src/arm/resources/azurerm_key_vault.template b/src/arm/resources/azurerm_key_vault.template new file mode 100644 index 0000000..177bac3 --- /dev/null +++ b/src/arm/resources/azurerm_key_vault.template @@ -0,0 +1,113 @@ +resource "azurerm_key_vault" "{{.item}}" { +{{- if .resource.location}} + location = {{ .resource.location|Quote }} +{{- else}} + location = data.azurerm_resource_group.sato.location +{{- end }} + name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} + tenant_id = {{.resource.properties.tenantId|Quote}} + soft_delete_retention_days = 7 + purge_protection_enabled = false + sku_name = {{.resource.properties.sku.name|Quote}} +{{- if .resource.properties.accessPolicies}} +{{- range $i, $j:= .resource.properties.accessPolicies}} + + access_policy { + tenant_id = {{$j.tenantId}} + object_id = {{$j.objectId}} + +{{- if $j.permissions.keys}} + key_permissions = {{Set $j.permissions.keys}} +{{- end}} +{{- if $j.permissions.secrets}} + secret_permissions = {{Set $j.permissions.secrets}} +{{- end}} +{{- if $j.permissions.storage}} + storage_permissions = {{Set $j.permissions.storage}} +{{- end}} +{{- if $j.permissions.certificates}} + certificate_permissions = {{Set $j.permissions.certificates}} +{{- end}} + } +{{- end}} +{{- end}} +{{- if .resource.properties.networkAcls}} + networks_acls { + bypass = {{.resource.properties.networkAcls.bypass|Quote}} + defaultAction = {{.resource.properties.networkAcls.defaultAction|Quote}} +{{- if .resource.properties.networkAcls.ipRules}} + ip_rules = {{.resource.properties.networkAcls.ipRules}} +{{- end}} +{{- if .resource.properties.networkAcls.virtualNetworkSubnetIds}} + virtual_network_subnet_ids= {{.resource.properties.networkAcls.virtualNetworkSubnetIds}} +{{- end}} + } +{{- end}} +{{- if NotNil .resource.properties.enabledForDeployment}} + enabled_for_deployment = {{.resource.properties.enabledForDeployment}} +{{- end}} +{{- if NotNil .resource.properties.enabledForDiskEncryption}} + enabled_for_disk_encryption = {{.resource.properties.enabledForDiskEncryption}} +{{- end}} +{{- if NotNil .resource.properties.enabledForTemplateDeployment}} + enabled_for_template_deployment = {{.resource.properties.enabledForTemplateDeployment}} +{{ end}} +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{$j}}, {{- end}}] +{{- end}} +} + + +{{- if .resource.resources}} +{{- $j := (index .resource.resources 0)}} +resource "azurerm_role_assignment" "{{.item}}" { + principal_id = {{Nil $j.properties.PrincipalId|Quote}} + role_definition_name = {{$j.name}} + scope = azurerm_container_registry.{{.item}}.id +{{- if $j.dependsOn }} + depends_on = [{{- range $x, $y:= $j.dependsOn }} {{ $y}}, {{- end}}] +{{- end}} +} + + +{{- $y := (index .resource.resources 1)}} +resource "azurerm_monitor_diagnostic_setting" "{{.item}}" { + name={{$y.name|Quote}} +{{- if $y.properties.workspaceId}} + log_analytics_workspace_id={{$y.properties.workspaceId}} +{{- end}} +{{- if $y.properties.metrics}} +{{- range $a, $b:= $y.properties.metrics}} + metric { + category = {{$b.category|Quote}} + retention_policy { + enabled = true + } + enabled = {{$b.enabled}} + } +{{- end}} +{{- end}} +{{- if $y.properties.logs}} +{{- range $a, $b:= $y.properties.logs}} + enabled_log{ + category = {{$b.category|Quote}} + retention_policy { + enabled = true + } + } +{{- end}} +{{- end}} + target_resource_id = azurerm_key_vault.{{.item}}.id + {{- if $y.dependsOn }} + depends_on = [{{- range $x, $z:= $y.dependsOn }} {{ $z}}, {{- end}}] + {{- end}} +} +{{- end}} diff --git a/src/arm/resources/azurerm_kubernetes_cluster.template b/src/arm/resources/azurerm_kubernetes_cluster.template new file mode 100644 index 0000000..a337aaf --- /dev/null +++ b/src/arm/resources/azurerm_kubernetes_cluster.template @@ -0,0 +1,111 @@ +resource "azurerm_kubernetes_cluster" "{{.item}}" { +{{- if .resource.location}} + location = {{ .resource.location|Quote }} +{{- else}} + location = data.azurerm_resource_group.sato.location +{{- end }} + name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} +{{- if .resource.identity}} + identity { + type = {{.resource.identity.type}} + {{- if .resource.identity.userAssignedIdentities.aksClusterUserDefinedManagedIdentityId}} + identity_ids= {{.resource.identity.userAssignedIdentities.aksClusterUserDefinedManagedIdentityId}} + {{- end}} + } +{{- end}} + +{{- if .resource.properties.defaultNodePool}} +default_node_pool {} +dns_prefix +dns_prefix_private_cluster +aci_connector_linux{} +automatic_channel_upgrade +api_server_access_profile {} +auto_scaler_profile {} +azure_active_directory_role_based_access_control { +} +azure_policy_enabled +confidential_computing { +} +disk_encryption_set_id +edge_zone +http_application_routing_enabled +http_proxy_config +image_cleaner_enabled +image_cleaner_interval_hours +ingress_application_gateway +key_management_service{} +key_vault_secrets_provider{} +kubelet_identity +kubernetes_version +linux_profile{} +local_account_disabled +maintenance_windows +microsoft_defender +monitor_metrics{} +networks_profile +node_resource_group +oidc_issuer_enabled +oms_agent +private_cluster_enabled +private_dns_zone_id +private_cluster_public_fqdn_enabled +service_mesh_profile +workload_autoscaler_profile +workload_identity_enabled +public_network_access_enabled +role_based_access_control_enabled +run_command_enabled +service_principal +sku_tier +storage_profile +web_app_routing +windows_profile +{{- end}} +{{- if .resource.tags}} + tags = {{.resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{ $j}}, {{- end}}] +{{- end}} +} + +{{- if .resource.resources}} +{{- $y := (index .resource.resources 0)}} +resource "azurerm_monitor_diagnostic_setting" "{{.item}}" { + name={{$y.name|Quote}} +{{- if $y.properties.workspaceId}} + log_analytics_workspace_id={{$y.properties.workspaceId}} +{{- end}} +{{- if $y.properties.metrics}} +{{- range $a, $b:= $y.properties.metrics}} + metric { + category = {{$b.category|Quote}} + retention_policy { + enabled = true + } + enabled = {{$b.enabled}} + } +{{- end}} +{{- end}} +{{- if $y.properties.logs}} +{{- range $a, $b:= $y.properties.logs}} + enabled_log{ + category = {{$b.category|Quote}} + retention_policy { + enabled = true + } + } +{{- end}} +{{- end}} + target_resource_id = azurerm_kubernetes_cluster.{{.item}}.id + {{- if $y.dependsOn }} + depends_on = [{{- range $x, $z:= $y.dependsOn }} {{ $z}}, {{- end}}] + {{- end}} +} +{{- end}} diff --git a/src/arm/resources/azurerm_log_analytics_solution.template b/src/arm/resources/azurerm_log_analytics_solution.template new file mode 100644 index 0000000..c36f22c --- /dev/null +++ b/src/arm/resources/azurerm_log_analytics_solution.template @@ -0,0 +1,30 @@ +resource "azurerm_log_analytics_solution" "{{.item}}" { +{{- if .resource.location}} + location = {{ .resource.location|Quote }} +{{- else}} + location = data.azurerm_resource_group.sato.location +{{- end }} + solution_name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} + workspace_resource_id = {{Nil .resource.properties.workspaceresourceId|Quote}} + workspace_name = "" +{{- if .resource.plan}} + plan { + publisher = {{ .resource.plan.publisher|Quote}} + product = {{ .resource.plan.product|Quote}} +{{- if .resource.plan.promotionCode}} + promotion_code = {{ .resource.plan.promotionCode|Quote}} +{{- end}} + } +{{- end}} +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{ $j}}, {{- end}}] +{{- end}} +} diff --git a/src/arm/resources/azurerm_log_analytics_workspace.template b/src/arm/resources/azurerm_log_analytics_workspace.template index 56f664a..de5ac37 100644 --- a/src/arm/resources/azurerm_log_analytics_workspace.template +++ b/src/arm/resources/azurerm_log_analytics_workspace.template @@ -20,19 +20,19 @@ local_authentication_disabled = {{.resource.properties.localAuthenticationDisabled}} {{- end}} {{- if .resource.properties.retentionInDays}} - retention_in_days = {{.resource.properties.retentionInDays}} + retention_in_days = {{.resource.properties.retentionInDays}} {{- end}} {{- if .resource.properties.dailyQuotaGB}} - daily_quota_gb= {{ .resource.properties.dailyQuotaGB}} + daily_quota_gb = {{ .resource.properties.dailyQuotaGB}} {{- end}} {{- if NotNil .resource.properties.cmkForQueryForced}} - cmk_for_query_forced ={{ .resource.properties.cmkForQueryForced}} + cmk_for_query_forced = {{ .resource.properties.cmkForQueryForced}} {{- end}} {{- if NotNil .resource.properties.internetIngestionEnabled}} - internet_ingestion_enabled = {{.resource.properties.internetIngestionEnabled}} + internet_ingestion_enabled = {{.resource.properties.internetIngestionEnabled}} {{- end}} {{- if NotNil .resource.properties.internetQueryEnabled}} - internet_query_enabled = {{.resource.properties.internetQueryEnabled}} + internet_query_enabled = {{.resource.properties.internetQueryEnabled}} {{- end}} {{- if .resource.properties.reservationCapacityInGBPerDay}} reservation_capacity_in_gb_per_day = {{.resource.properties.reservationCapacityInGBPerDay}} diff --git a/src/arm/resources/azurerm_monitor_activity_log_alert.template b/src/arm/resources/azurerm_monitor_activity_log_alert.template new file mode 100644 index 0000000..f1522aa --- /dev/null +++ b/src/arm/resources/azurerm_monitor_activity_log_alert.template @@ -0,0 +1,17 @@ +resource "azurerm_monitor_activity_log_alert" "{{.item}}" { + name = {{.resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} + scopes = {{index .resource.properties.scopes 0}} + description = {{ .resource.properties.description|Quote}} + enabled = {{ .resource.properties.enabled}} +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{$j}}, {{- end}}] +{{- end}} +} diff --git a/src/arm/resources/azurerm_network_interface_application_gateway_backend_address_pool_association.template b/src/arm/resources/azurerm_network_interface_application_gateway_backend_address_pool_association.template new file mode 100644 index 0000000..8d9a2e4 --- /dev/null +++ b/src/arm/resources/azurerm_network_interface_application_gateway_backend_address_pool_association.template @@ -0,0 +1,11 @@ +resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "{{.item}}" { + network_interface_id = azurerm_network_interface.example.id + ip_configuration_name = "testconfiguration1" + backend_address_pool_id = tolist(azurerm_application_gateway.network.backend_address_pool).0.id +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{$j}}, {{- end}}] +{{- end}} +} diff --git a/src/arm/resources/azurerm_network_security_group.template b/src/arm/resources/azurerm_network_security_group.template index 8c9fc5c..6b766d2 100644 --- a/src/arm/resources/azurerm_network_security_group.template +++ b/src/arm/resources/azurerm_network_security_group.template @@ -21,7 +21,9 @@ resource "azurerm_network_security_group" "{{ .item }}" { access = {{$i.properties.access|Quote}} protocol = {{$i.properties.protocol|Quote}} source_port_range = {{$i.properties.sourcePortRange|Quote}} +{{- if $i.properties.destinationPortRange}} destination_port_range = {{$i.properties.destinationPortRange|Quote}} +{{- end}} {{- if $i.properties.sourceAddressPrefixes}} source_address_prefixes = {{$i.properties.sourceAddressPrefixes|Quote}} {{- end}} diff --git a/src/arm/resources/azurerm_private_dns_zone.template b/src/arm/resources/azurerm_private_dns_zone.template new file mode 100644 index 0000000..e6d8627 --- /dev/null +++ b/src/arm/resources/azurerm_private_dns_zone.template @@ -0,0 +1,16 @@ +resource "azurerm_private_dns_zone" "{{.item}}" { + name = {{.resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} + + +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{$j}}, {{- end}}] +{{- end}} +} diff --git a/src/arm/resources/azurerm_private_dns_zone_virtual_network_link.template b/src/arm/resources/azurerm_private_dns_zone_virtual_network_link.template new file mode 100644 index 0000000..674503d --- /dev/null +++ b/src/arm/resources/azurerm_private_dns_zone_virtual_network_link.template @@ -0,0 +1,19 @@ +resource "azurerm_private_dns_zone_virtual_network_link" "{{.item}}" { + name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} + private_dns_zone_name = {{.resource.name|Quote}} + virtual_network_id = {{.resource.properties.virtualNetwork.id|Quote}} +{{- if NotNil .resource.properties.registrationEnabled}} + registration_enabled = {{.resource.properties.registrationEnabled|Quote}} +{{- end}} +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{$j}}, {{- end}}] +{{- end}} +} diff --git a/src/arm/resources/azurerm_private_endpoint.template b/src/arm/resources/azurerm_private_endpoint.template new file mode 100644 index 0000000..152c3a5 --- /dev/null +++ b/src/arm/resources/azurerm_private_endpoint.template @@ -0,0 +1,33 @@ +resource "azurerm_private_endpoint" "{{.item}}" { +{{- if .resource.location}} + location = {{ .resource.location|Quote }} +{{- else}} + location = data.azurerm_resource_group.sato.location +{{- end }} + name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} + subnet_id = {{ .resource.properties.subnet.id|Quote}} +{{- if .resource.properties.privateLinkServiceConnections}} +{{- range $x, $y:= .resource.properties.privateLinkServiceConnections}} + private_service_connection { +{{- if $y.name}} + name = {{$y.name|Quote}} +{{- end}} +{{- if $y.properties.privateLinkServiceId}} + private_connection_resource_id = {{$y.properties.privateLinkServiceId}} +{{- end}} + is_manual_connection = false + } +{{- end}} +{{- end}} +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{ $j}}, {{- end}}] +{{- end}} +} diff --git a/src/arm/resources/azurerm_role_assignment.template b/src/arm/resources/azurerm_role_assignment.template index 5e23c6a..18a3a72 100644 --- a/src/arm/resources/azurerm_role_assignment.template +++ b/src/arm/resources/azurerm_role_assignment.template @@ -1,13 +1,23 @@ resource "azurerm_role_assignment" "{{.item}}" { name = {{.resource.name}} principal_id = {{.resource.properties.principalId}} +{{- if .resource.properties.scope}} + scope = {{.resource.properties.scope}} +{{- else}} scope = "" +{{- end}} role_definition_id = {{.resource.properties.roleDefinitionId}} +{{- if .resource.metadata.description}} description = {{.resource.metadata.description|Quote}} +{{- else}} +{{- if .resource.properties.description }} + description = {{.resource.properties.description|Quote}} +{{- end}} +{{- end}} {{- if .resource.tags}} tags = {{Tags .resource.tags}} {{- end}} {{- if .resource.dependsOn }} - depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{Deref $j}}, {{- end}}] + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{ $j}}, {{- end}}] {{- end}} } diff --git a/src/arm/resources/azurerm_virtual_machine_extension.template b/src/arm/resources/azurerm_virtual_machine_extension.template index d5a49c6..2ca65e6 100644 --- a/src/arm/resources/azurerm_virtual_machine_extension.template +++ b/src/arm/resources/azurerm_virtual_machine_extension.template @@ -4,9 +4,18 @@ resource "azurerm_virtual_machine_extension" "{{.item}}" { publisher = {{$p.publisher|Quote}} type = {{$p.type|Quote}} type_handler_version = {{$p.typeHandlerVersion|Quote}} +{{- if NotNil $p.autoUpgradeMinorVersion}} auto_upgrade_minor_version = {{$p.autoUpgradeMinorVersion}} +{{- end}} +{{- if NotNil $p.enableAutomaticUpgrade}} automatic_upgrade_enabled = {{$p.enableAutomaticUpgrade}} +{{- end}} +{{- if $p.settings}} settings = jsonencode({{Marshal $p.settings}}) +{{- end}} +{{- if $p.protectedSettings}} + protected_settings = jsonencode({{Marshal $p.protectedSettings}}) +{{- end}} {{- if .resource.tags}} tags = {{Tags .resource.tags}} {{- end}} diff --git a/src/arm/resources/azurerm_virtual_network.template b/src/arm/resources/azurerm_virtual_network.template index b765450..5a38da1 100644 --- a/src/arm/resources/azurerm_virtual_network.template +++ b/src/arm/resources/azurerm_virtual_network.template @@ -13,12 +13,24 @@ resource "azurerm_virtual_network" "{{.item}}" { address_space=[{{index .resource.properties.addressSpace.addressPrefixes 0}}] {{- if .resource.properties.subnets }} {{- range $a, $i:= .resource.properties.subnets }} + {{- if $i.name}} subnet { name={{ $i.name|Quote}} address_prefix={{$i.properties.addressPrefix |Quote}} +{{- if $i.properties.networkSecurityGroup.id}} security_group={{$i.properties.networkSecurityGroup.id |Quote}}.id +{{- end}} } + {{- end}} +{{- end}} {{- end}} +{{- if NotNil .resource.properties.enableDdosProtection}} + ddos_protection_plan { + id = azurerm_network_ddos_protection_plan.{{.item}}.id +{{- if NotNil .resource.properties.enableDdosProtection}} + enable = {{.resource.properties.enableDdosProtection}} +{{- end}} + } {{- end}} {{- if .resource.tags}} tags = {{Tags .resource.tags}} @@ -30,3 +42,19 @@ resource "azurerm_virtual_network" "{{.item}}" { {{- if .resource.properties.subnets }} {{- end}} + +{{- if NotNil .resource.properties.enableDdosProtection}} +resource "azurerm_network_ddos_protection_plan" "{{.item}}" { +{{- if .resource.location}} + location = {{ .resource.location|Quote }} +{{- else}} + location = data.azurerm_resource_group.sato.location +{{- end }} + name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} +} +{{- end}} diff --git a/src/arm/resources/azurerm_web_application_firewall_policy.template b/src/arm/resources/azurerm_web_application_firewall_policy.template new file mode 100644 index 0000000..cb6857b --- /dev/null +++ b/src/arm/resources/azurerm_web_application_firewall_policy.template @@ -0,0 +1,45 @@ +resource "azurerm_web_application_firewall_policy" "example" { +{{- if .resource.location}} + location = {{ .resource.location|Quote }} +{{- else}} + location = data.azurerm_resource_group.sato.location +{{- end }} + name = {{ .resource.name|Quote }} +{{- if .resource.resourceGroupName }} + resource_group_name = {{ .resource.resourceGroupName|Quote }} +{{- else }} + resource_group_name = data.azurerm_resource_group.sato.name +{{- end}} +{{range $x, $i:= .resource.properties.customRules}} + custom_rules { + name = {{$i.name|Quote}} + priority = {{$i.priority}} + rule_type = {{$i.ruleType|Quote}} + +{{- if $i.matchConditions}} +{{- range $a, $b:= $i.matchConditions}} + match_conditions { + match_variables { +{{- range $c, $d:= (index $b.matchVariables 0)}} + {{Snake $c}} = {{$d|Quote}} +{{- end}} + } + + operator = {{$b.operator|Quote}} +{{- if NotNil $b.negationCondition}} + negation_condition = {{$b.negationCondition}} +{{- end}} + match_values = {{Set $b.matchValues}} + } +{{- end}} +{{- end}} + action = {{$i.action|Quote}} + } +{{- end}} +{{- if .resource.tags}} + tags = {{Tags .resource.tags}} +{{- end}} +{{- if .resource.dependsOn }} + depends_on = [{{- range $x, $j:= .resource.dependsOn }} {{$j}}, {{- end}}] +{{- end}} +} diff --git a/src/arm/resources_test.go b/src/arm/resources_test.go new file mode 100644 index 0000000..afa1748 --- /dev/null +++ b/src/arm/resources_test.go @@ -0,0 +1,40 @@ +package arm + +import ( + "reflect" + "testing" + "text/template" +) + +func Test_parseResources(t *testing.T) { + t.Parallel() + + type args struct { + result map[string]interface{} + funcMap template.FuncMap + destination string + } + + tests := []struct { + name string + args args + want map[string]interface{} + wantErr bool + }{ + //{ "Pass", args{}, ResultPass, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseResources(tt.args.result, tt.args.funcMap, tt.args.destination) + if (err != nil) != tt.wantErr { + t.Errorf("parseResources() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseResources() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/arm/variable.template b/src/arm/variable.template index 385deb5..adc031d 100644 --- a/src/arm/variable.template +++ b/src/arm/variable.template @@ -10,7 +10,7 @@ variable "{{.item}}" { {{- if or (eq .variable.type "string") (eq .variable.type "securestring")}} default = {{ Replace .variable.default "${" "$${" | Quote }} {{- else}} - default = {{ Replace .variable.default "${" "$${"}} + default = {{ Replace .variable.default "${" "$${" }} {{- end }} {{- end }} {{- if .variable.metadata.description }} diff --git a/src/arm/variables.go b/src/arm/variables.go new file mode 100644 index 0000000..2f80149 --- /dev/null +++ b/src/arm/variables.go @@ -0,0 +1,99 @@ +package arm + +import ( + "bytes" + "encoding/json" + "reflect" + "sato/src/cf" + "strings" + tftemplate "text/template" + + "github.com/rs/zerolog/log" +) + +// parseVariables convert ARM Parameters into terraform variables. +func parseVariables(result map[string]interface{}, funcMap tftemplate.FuncMap, destination string) (map[string]interface{}, error) { + variables := make(map[string]interface{}) + if result["variables"] != nil { + variables = result["variables"].(map[string]interface{}) + } + + var All string + + All, myVariables, err := parseParameters(result, funcMap, All) + if err != nil { + return result, err + } + + locals, result, err := parseLocals(result) + if err != nil { + return result, err + } + + for name, value := range variables { + myItem := make(map[string]interface{}) + + if value != nil { + var local string + + if reflect.TypeOf(value).String() == typeString { + if strings.Contains(value.(string), "()") || + strings.Contains(value.(string), "[") { + value, result = parseString(value.(string), result) + + local = "\t" + name + " = " + value.(string) + "\n" + locals += local + continue + } + + myItem["default"] = value + } + + if reflect.TypeOf(value).String() == "map[string]interface {}" { + blob, err := json.Marshal(value) + if err != nil { + log.Warn().Msgf("fail to marshal %s", value) + } + + myItem["default"] = string(blob) + } + + myItem["name"] = name + myItem["type"] = typeString + } + + myItem, err = fixType(myItem) + if err != nil { + log.Print(err) + } + + var output bytes.Buffer + + tmpl, err := tftemplate.New("test").Funcs(funcMap).Parse(string(variableFile)) + + if err != nil { + return result, err + } + _ = tmpl.Execute(&output, m{ + "variable": myItem, + "item": name, + }) + All += output.String() + + myVariables = append(myVariables, myItem) + } + + err = cf.Write(All, destination, "variables") + if err != nil { + return result, err + } + + locals = "locals {\n" + locals + "}\n" + err = cf.Write(locals, destination, "locals") + + if err != nil { + return result, err + } + + return result, nil +} diff --git a/src/arm/variables_test.go b/src/arm/variables_test.go new file mode 100644 index 0000000..c9884e2 --- /dev/null +++ b/src/arm/variables_test.go @@ -0,0 +1,40 @@ +package arm + +import ( + "reflect" + "testing" + "text/template" +) + +func Test_parseVariables(t *testing.T) { + t.Parallel() + + type args struct { + result map[string]interface{} + funcMap template.FuncMap + destination string + } + + tests := []struct { + name string + args args + want []interface{} + wantErr bool + }{ + //{}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := parseVariables(tt.args.result, tt.args.funcMap, tt.args.destination) + if (err != nil) != tt.wantErr { + t.Errorf("parseVariables() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseVariables() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/cf/helpers.go b/src/cf/helpers.go index 123373f..7072a06 100644 --- a/src/cf/helpers.go +++ b/src/cf/helpers.go @@ -57,8 +57,8 @@ func Dequote(target string) string { // Quote is a template function. func Quote(target string) string { // is it a resource or variable - if strings.Contains(target, "var.") || strings.Contains(target, "local.") || - (strings.Contains(target, "_") && strings.Contains(target, ".")) { + if (strings.Contains(target, "var.") || strings.Contains(target, "local.") || + (strings.Contains(target, "_") && strings.Contains(target, "."))) && (!strings.Contains(target, "${")) { return target } diff --git a/src/see/lookup.go b/src/see/lookup.go index 7c722b6..7ee97fb 100644 --- a/src/see/lookup.go +++ b/src/see/lookup.go @@ -8,145 +8,161 @@ import ( // Lookup converts from cf/arm to terraform resource name. func Lookup(resource string) (*string, error) { Lookup := map[string]string{ - "AWS::ApplicationAutoScaling::ScalableTarget": "aws_appautoscaling_target", - "AWS::ApplicationAutoScaling::ScalingPolicy": "aws_appAutoscaling_policy", - "AWS::AutoScaling::AutoScalingGroup": "aws_autoscaling_group", - "AWS::AutoScaling::LaunchConfiguration": "aws_launch_configuration", - "AWS::AutoScaling::LifecycleHook": "aws_autoscaling_lifecycle_hook", - "AWS::AutoScaling::ScalingPolicy": "aws_autoscaling_policy", - "AWS::AutoScaling::ScheduledAction": "aws_autoscaling_schedule", - "AWS::Backup::BackupPlan": "aws_backup_plan", - "AWS::Backup::BackupSelection": "aws_backup_selection", - "AWS::Backup::BackupVault": "aws_backup_vault", - "AWS::Cloud9::EnvironmentEC2": "aws_cloud9_environment_ec2", - "AWS::CloudFormation::Stack": "aws_cloudformation_stack", - "AWS::CloudFront::CloudFrontOriginAccessIdentity": "aws_cloudfront_origin_access_identity", - "AWS::CloudFront::Distribution": "aws_cloudfront_distribution", - "AWS::CloudWatch::Alarm": "aws_cloudwatch_metric_alarm", - "AWS::CloudWatch::Dashboard": "aws_cloudwatch_dashboard", - "AWS::CodeBuild::Project": "aws_codebuild_project", - "AWS::CodeCommit::Repository": "aws_codecommit_repository", - "AWS::CodePipeline::Pipeline": "aws_codepipeline", - "AWS::Config::ConfigRule": "aws_config_config_rule", - "AWS::Config::ConfigurationRecorder": "aws_config_configuration_recorder", - "AWS::Config::DeliveryChannel": "aws_config_delivery_channel", - "AWS::DMS::Endpoint": "aws_dms_endpoint", - "AWS::DMS::ReplicationInstance": "aws_dms_replication_instance", - "AWS::DMS::ReplicationSubnetGroup": "aws_dms_replication_subnet_group", - "AWS::DMS::ReplicationTask": "aws_dms_replication_task", - "AWS::DirectoryService::MicrosoftAD": "aws_directory_service_directory", - "AWS::DynamoDB::Table": "aws_dynamodb_table", - "AWS::EC2::DHCPOptions": "aws_vpc_dhcp_options", - "AWS::EC2::EIP": "aws_eip", - "AWS::EC2::EIPAssociation": "aws_eip_association", - "AWS::EC2::FlowLog": "aws_flow_log", - "AWS::EC2::Instance": "aws_instance", - "AWS::EC2::InternetGateway": "aws_Internet_gateway", - "AWS::EC2::LaunchTemplate": "aws_launch_template", - "AWS::EC2::NatGateway": "aws_nat_gateway", - "AWS::EC2::NetworkAcl": "aws_network_acl", - "AWS::EC2::NetworkAclEntry": "aws_network_acl_rule", - "AWS::EC2::NetworkInterface": "aws_network_interface", - "AWS::EC2::Route": "aws_route", - "AWS::EC2::RouteTable": "aws_route_table", - "AWS::EC2::SecurityGroup": "aws_security_group", - "AWS::EC2::SecurityGroupEgress": "aws_security_group_rule_egress", - "AWS::EC2::SecurityGroupIngress": "aws_security_group_rule_ingress", - "AWS::EC2::Subnet": "aws_subnet", - "AWS::EC2::SubnetNetworkAclAssociation": "aws_network_acl_association", - "AWS::EC2::SubnetRouteTableAssociation": "aws_route_table_association", - "AWS::EC2::VPC": "aws_vpc", - "AWS::EC2::VPCDHCPOptionsAssociation": "aws_vpc_dhcp_options_association", - "AWS::EC2::VPCEndpoint": "aws_vpc_rndpoint", - "AWS::EC2::VPCGatewayAttachment": "aws_vpn_gatewayAttachment", - "AWS::EC2::Volume": "aws_ebs_volume", - "AWS::ECS::Cluster": "aws_ecs_cluster", - "AWS::ECS::Service": "aws_ecs_service", - "AWS::ECS::TaskDefinition": "aws_ecs_task_definition", - "AWS::EFS::FileSystem": "aws_efs_file_system", - "AWS::EFS::MountTarget": "aws_efs_mount_target", - "AWS::EKS::Cluster": "aws_eks_cluster", - "AWS::EKS::Nodegroup": "aws_eks_node_group", - "AWS::ElastiCache::ParameterGroup": "aws_elasticache_parameter_group", - "AWS::ElastiCache::ReplicationGroup": "aws_elasticache_replication_group", - "AWS::ElastiCache::SubnetGroup": "aws_elasticache_subnet_group", - "AWS::ElasticLoadBalancing::LoadBalancer": "aws_elb", - "AWS::ElasticLoadBalancingV2::Listener": "aws_lb_listener", - "AWS::ElasticLoadBalancingV2::ListenerRule": "aws_lb_listener_rule", - "AWS::ElasticLoadBalancingV2::LoadBalancer": "aws_lb", - "AWS::ElasticLoadBalancingV2::TargetGroup": "aws_lb_target_group", - "AWS::Events::Rule": "aws_cloudwatch_event_rule", - "AWS::IAM::AccessKey": "aws_iam_access_key", - "AWS::IAM::Group": "aws_iam_group", - "AWS::IAM::InstanceProfile": "aws_iam_instance_profile", - "AWS::IAM::ManagedPolicy": "aws_iam_managed_policy", - "AWS::IAM::Policy": "aws_iam_policy", - "AWS::IAM::Role": "aws_iam_role", - "AWS::IAM::User": "aws_iam_user", - "AWS::IAM::UserToGroupAddition": "aws_iam_group_membership", - "AWS::KMS::Alias": "aws_kms_alias", - "AWS::KMS::Key": "aws_Kms_key", - "AWS::Lambda::EventSourceMapping": "aws_lambda_event_source_mapping", - "AWS::Lambda::Function": "aws_lambda_function", - "AWS::Lambda::Permission": "aws_lambda_permission", - "AWS::Lambda::Version": "aws_lambda_version", - "AWS::Logs::LogGroup": "aws_cloudwatch_logGroup", - "AWS::Logs::MetricFilter": "aws_cloudwatch_logMetricFilter", - "AWS::Neptune::DBCluster": "aws_neptune_cluster", - "AWS::Neptune::DBClusterParameterGroup": "aws_neptune_cluster_parameter_group", - "AWS::Neptune::DBInstance": "aws_neptune_cluster_instance", - "AWS::Neptune::DBParameterGroup": "aws_neptune_parameter_group", - "AWS::Neptune::DBSubnetGroup": "aws_neptune_subnet_group", - "AWS::RDS::DBCluster": "aws_rds_cluster", - "AWS::RDS::DBClusterParameterGroup": "aws_rds_cluster_parameter_group", - "AWS::RDS::DBInstance": "aws_db_instance", - "AWS::RDS::DBParameterGroup": "aws_db_parameter_group", - "AWS::RDS::DBSubnetGroup": "aws_db_subnet_group", - "AWS::Route53::RecordSet": "aws_route53_record", - "AWS::S3::Bucket": "aws_s3_bucket", - "AWS::S3::BucketPolicy": "aws_s3_bucket_policy", - "AWS::SNS::Subscription": "aws_sns_subscription", - "AWS::SNS::Topic": "aws_sns_topic", - "AWS::SNS::TopicPolicy": "aws_sns_topic_policy", - "AWS::SQS::Queue": "aws_sqs_queue", - "AWS::SSM::Association": "aws_ssm_association", - "AWS::SSM::Document": "aws_ssm_document", - "AWS::SSM::MaintenanceWindow": "aws_ssm_maintenance_window", - "AWS::SSM::MaintenanceWindowTarget": "aws_ssm_maintenance_window_target", - "AWS::SSM::MaintenanceWindowTask": "aws_ssm_maintenance_window_task", - "AWS::SecretsManager::Secret": "aws_secrets_manager_secret", - "AWS::ServiceCatalog::Portfolio": "aws_service_catalog_portfolio", - "AWS::ServiceCatalog::PortfolioProductAssociation": "aws_service_catalog_product_portfolio_association", - "AWS::ServiceCatalog::PortfolioShare": "aws_service_catalog_portfolio_share", - "AWS::ServiceCatalog::TagOption": "aws_service_catalog_tag_option", - "AWS::ServiceCatalog::TagOptionAssociation": "aws_service_catalog_tag_option_association", - "AWS::ServiceDiscovery::Service": "aws_service_discovery_service", - "AWS::StepFunctions::StateMachine": "aws_sfn_state_machine", - "AWS::WAFv2::WebACLAssociation": "aws_wafv2_webacl_association", - "Microsoft.AnalysisServices/servers": "azurerm_analysis_services_server", - "Microsoft.ApiManagement/service": "azurerm_api_management", - "Microsoft.App/containerApps": "azurerm_container_app", - "Microsoft.App/managedEnvironments": "azurerm_container_app_environment", - "Microsoft.Authorization/roleAssignments": "azurerm_role_assignment", - "Microsoft.Authorization/roleDefinitions": "azurerm_role_definition", - "Microsoft.Compute/virtualMachines": "azurerm_virtual_machine", - "Microsoft.Compute/virtualMachines/extensions": "azurerm_virtual_machine_extension", - "Microsoft.ManagedIdentity/userAssignedIdentities": "azurerm_user_assigned_identity", - "Microsoft.Network/networkInterfaces": "azurerm_network_interface", - "Microsoft.Network/networkSecurityGroups": "azurerm_network_security_group", - "Microsoft.Network/publicIPAddresses": "azurerm_public_ip", - "Microsoft.Network/virtualNetworks": "azurerm_virtual_network", - "Microsoft.Network/virtualNetworks/subnets": "azurerm_subnet", - "Microsoft.OperationalInsights/workspaces": "azurerm_log_analytics_workspace", - "Microsoft.Resources/deployments": "azurerm_template_deployment", - "Microsoft.Storage/storageAccounts": "azurerm_storage_account", - "Microsoft.ContainerRegistry/registries": "azurerm_container_registry", - "Microsoft.ServiceBus/namespaces": "azurerm_servicebus_namespace", - "Microsoft.ServiceBus/namespaces/authorizationRules": "azurerm_servicebus_namespace_authorization_rule", - "Microsoft.ServiceBus/namespaces/queues": "azurerm_servicebus_queue", - "Microsoft.AAD/domainServices": "azurerm_active_directory_domain_service", + "aws::EFS::filesystem": "aws_efs_file_system", + "aws::EFS::mounttarget": "aws_efs_mount_target", + "aws::EKS::cluster": "aws_eks_cluster", + "aws::EKS::nodegroup": "aws_eks_node_group", + "aws::applicationautoscaling::scalabletarget": "aws_appautoscaling_target", + "aws::applicationautoscaling::scalingpolicy": "aws_appAutoscaling_policy", + "aws::autoscaling::autoscalinggroup": "aws_autoscaling_group", + "aws::autoscaling::launchconfiguration": "aws_launch_configuration", + "aws::autoscaling::lifecyclehook": "aws_autoscaling_lifecycle_hook", + "aws::autoscaling::scalingpolicy": "aws_autoscaling_policy", + "aws::autoscaling::scheduledaction": "aws_autoscaling_schedule", + "aws::backup::backupplan": "aws_backup_plan", + "aws::backup::backupselection": "aws_backup_selection", + "aws::backup::backupvault": "aws_backup_vault", + "aws::cloud9::environmentec2": "aws_cloud9_environment_ec2", + "aws::cloudformation::Stack": "aws_cloudformation_stack", + "aws::cloudfront::cloudFrontOriginAccessIdentity": "aws_cloudfront_origin_access_identity", + "aws::cloudfront::distribution": "aws_cloudfront_distribution", + "aws::cloudwatch::alarm": "aws_cloudwatch_metric_alarm", + "aws::cloudwatch::dashboard": "aws_cloudwatch_dashboard", + "aws::codebuild::project": "aws_codebuild_project", + "aws::codecommit::repository": "aws_codecommit_repository", + "aws::codepipeline::pipeline": "aws_codepipeline", + "aws::config::configrule": "aws_config_config_rule", + "aws::config::configurationrecorder": "aws_config_configuration_recorder", + "aws::config::deliverychannel": "aws_config_delivery_channel", + "aws::directoryservice::microsoftad": "aws_directory_service_directory", + "aws::dns::endpoint": "aws_dms_endpoint", + "aws::dns::replicationinstance": "aws_dms_replication_instance", + "aws::dns::replicationsubnetgroup": "aws_dms_replication_subnet_group", + "aws::dns::replicationtask": "aws_dms_replication_task", + "aws::dynamodb::table": "aws_dynamodb_table", + "aws::ec2::dhcpoptions": "aws_vpc_dhcp_options", + "aws::ec2::eip": "aws_eip", + "aws::ec2::eipassociation": "aws_eip_association", + "aws::ec2::flowlog": "aws_flow_log", + "aws::ec2::instance": "aws_instance", + "aws::ec2::internetgateway": "aws_Internet_gateway", + "aws::ec2::launchtemplate": "aws_launch_template", + "aws::ec2::natgateway": "aws_nat_gateway", + "aws::ec2::networkacl": "aws_network_acl", + "aws::ec2::networkaclentry": "aws_network_acl_rule", + "aws::ec2::networkinterface": "aws_network_interface", + "aws::ec2::route": "aws_route", + "aws::ec2::routetable": "aws_route_table", + "aws::ec2::securitygroup": "aws_security_group", + "aws::ec2::securitygroupegress": "aws_security_group_rule_egress", + "aws::ec2::securitygroupingress": "aws_security_group_rule_ingress", + "aws::ec2::subnet": "aws_subnet", + "aws::ec2::subnetnetworkaclassociation": "aws_network_acl_association", + "aws::ec2::subnetroutetableassociation": "aws_route_table_association", + "aws::ec2::volume": "aws_ebs_volume", + "aws::ec2::vpc": "aws_vpc", + "aws::ec2::vpcdhcpoptionsassociation": "aws_vpc_dhcp_options_association", + "aws::ec2::vpcendpoint": "aws_vpc_endpoint", + "aws::ec2::vpcgatewayattachment": "aws_vpn_gatewayAttachment", + "aws::ecs::cluster": "aws_ecs_cluster", + "aws::ecs::service": "aws_ecs_service", + "aws::ecs::taskdefinition": "aws_ecs_task_definition", + "aws::elasticache::parametergroup": "aws_elasticache_parameter_group", + "aws::elasticache::replicationgroup": "aws_elasticache_replication_group", + "aws::elasticache::subnetgroup": "aws_elasticache_subnet_group", + "aws::elasticloadbalancing::loadbalancer": "aws_elb", + "aws::elasticloadbalancingv2::listener": "aws_lb_listener", + "aws::elasticloadbalancingv2::listenerrule": "aws_lb_listener_rule", + "aws::elasticloadbalancingv2::loadbalancer": "aws_lb", + "aws::elasticloadbalancingv2::targetgroup": "aws_lb_target_group", + "aws::events::rule": "aws_cloudwatch_event_rule", + "aws::iam::accessKey": "aws_iam_access_key", + "aws::iam::group": "aws_iam_group", + "aws::iam::instanceprofile": "aws_iam_instance_profile", + "aws::iam::managedpolicy": "aws_iam_managed_policy", + "aws::iam::policy": "aws_iam_policy", + "aws::iam::role": "aws_iam_role", + "aws::iam::user": "aws_iam_user", + "aws::iam::usertogroupaddition": "aws_iam_group_membership", + "aws::kms::alias": "aws_kms_alias", + "aws::kms::key": "aws_Kms_key", + "aws::lambda::eventsourcemapping": "aws_lambda_event_source_mapping", + "aws::lambda::function": "aws_lambda_function", + "aws::lambda::permission": "aws_lambda_permission", + "aws::lambda::version": "aws_lambda_version", + "aws::logs::loggroup": "aws_cloudwatch_loggroup", + "aws::logs::metricfilter": "aws_cloudwatch_logMetricFilter", + "aws::neptune::dbcluster": "aws_neptune_cluster", + "aws::neptune::dbclusterparametergroup": "aws_neptune_cluster_parameter_group", + "aws::neptune::dbinstance": "aws_neptune_cluster_instance", + "aws::neptune::dbparametergroup": "aws_neptune_parameter_group", + "aws::neptune::dbsubnetgroup": "aws_neptune_subnet_group", + "aws::rds::dbcluster": "aws_rds_cluster", + "aws::rds::dbclusterparametergroup": "aws_rds_cluster_parameter_group", + "aws::rds::dbinstance": "aws_db_instance", + "aws::rds::dbparametergroup": "aws_db_parameter_group", + "aws::rds::dbsubnetgroup": "aws_db_subnet_group", + "aws::route53::recordset": "aws_route53_record", + "aws::s3::bucket": "aws_s3_bucket", + "aws::s3::bucketpolicy": "aws_s3_bucket_policy", + "aws::secretsmanager::secret": "aws_secrets_manager_secret", + "aws::servicecatalog::portfolio": "aws_service_catalog_portfolio", + "aws::servicecatalog::portfolioproductassociation": "aws_service_catalog_product_portfolio_association", + "aws::servicecatalog::portfolioshare": "aws_service_catalog_portfolio_share", + "aws::servicecatalog::tagoption": "aws_service_catalog_tag_option", + "aws::servicecatalog::tagoptionassociation": "aws_service_catalog_tag_option_association", + "aws::servicediscovery::service": "aws_service_discovery_service", + "aws::sns::subscription": "aws_sns_subscription", + "aws::sns::topic": "aws_sns_topic", + "aws::sns::topicpolicy": "aws_sns_topic_policy", + "aws::sqs::queue": "aws_sqs_queue", + "aws::ssm::association": "aws_ssm_association", + "aws::ssm::document": "aws_ssm_document", + "aws::ssm::maintenancewindow": "aws_ssm_maintenance_window", + "aws::ssm::maintenancewindowtarget": "aws_ssm_maintenance_window_target", + "aws::ssm::maintenancewindowtask": "aws_ssm_maintenance_window_task", + "aws::stepfunctions::statemachine": "aws_sfn_state_machine", + "aws::wafv2::webaclassociation": "aws_wafv2_webacl_association", + "microsoft.authorization": "sato", + "microsoft.aad/domainservices": "azurerm_active_directory_domain_service", + "microsoft.analysisservices/servers": "azurerm_analysis_services_server", + "microsoft.apimanagement/service": "azurerm_api_management", + "microsoft.app/containerapps": "azurerm_container_app", + "microsoft.app/managedenvironments": "azurerm_container_app_environment", + "microsoft.authorization/roleassignments": "azurerm_role_assignment", + "microsoft.authorization/roledefinitions": "azurerm_role_definition", + "microsoft.compute/virtualmachines": "azurerm_virtual_machine", + "microsoft.compute/virtualmachines/extensions": "azurerm_virtual_machine_extension", + "microsoft.containerregistry/registries": "azurerm_container_registry", + "microsoft.containerservice/managedclusters": "azurerm_kubernetes_cluster", + "microsoft.insights/activitylogalerts": "azurerm_monitor_activity_log_alert", + "microsoft.keyvault/vaults": "azurerm_key_vault", + "microsoft.managedidentity/userassignedidentities": "azurerm_user_assigned_identity", + "microsoft.network/applicationgateways": "azurerm_application_gateway", + "microsoft.network/applicationgateways/backendaddresspools": "azurerm_network_interface_application_gateway_backend_address_pool_association", + "microsoft.network/applicationgateways/frontendipconfigurations": "azurerm_application_gateway", + "microsoft.network/applicationgateways/httplisteners": "azurerm_application_gateway", + "microsoft.network/applicationgateways/frontendports": "azurerm_application_gateway", + "microsoft.network/applicationgateways/backendhttpsettingscollection": "azurerm_application_gateway", + "microsoft.network/applicationgatewaywebapplicationfirewallpolicies": "azurerm_web_application_firewall_policy", + "microsoft.network/bastionhosts": "azurerm_bastion_host", + "microsoft.network/networkinterfaces": "azurerm_network_interface", + "microsoft.network/networksecuritygroups": "azurerm_network_security_group", + "microsoft.network/privatednszones": "azurerm_private_dns_zone", + "microsoft.network/privateendpoints": "azurerm_private_endpoint", + "microsoft.network/privateendpoints/privatednszonegroups": "azurerm_private_endpoint", + "microsoft.network/publicipaddresses": "azurerm_public_ip", + "microsoft.network/virtualnetworks": "azurerm_virtual_network", + "microsoft.network/virtualnetworks/subnets": "azurerm_subnet", + "microsoft.operationalinsights/workspaces": "azurerm_log_analytics_workspace", + "microsoft.operationsmanagement/solutions": "azurerm_log_analytics_solution", + "microsoft.resources/deployments": "azurerm_template_deployment", + "microsoft.servicebus/namespaces": "azurerm_servicebus_namespace", + "microsoft.servicebus/namespaces/authorizationRules": "azurerm_servicebus_namespace_authorization_rule", + "microsoft.servicebus/namespaces/queues": "azurerm_servicebus_queue", + "microsoft.storage/storageaccounts": "azurerm_storage_account", } - result := Lookup[strings.TrimSuffix(resource, "/")] + result := Lookup[strings.TrimSuffix(strings.ToLower(resource), "/")] var err error