scripts/migration/migration_resource.go (1,341 lines of code) (raw):
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/printer"
"go/token"
"golang.org/x/tools/imports"
"log"
"os"
"path/filepath"
"regexp"
"strings"
)
var (
namespace = flag.String("n", "", "namespace")
resource = flag.String("r", "", "resource")
sourceProviderDir = flag.String("s", "", "source provider dir path")
destProviderDir = flag.String("t", "", "target provider dir path")
functionName = flag.String("f", "", "function name in service")
)
var specialServiceMap = map[string]string{
"private_zone": "pvtz",
"sls": "log",
}
var specialResourceMap = map[string]map[string]string{
"vpc": {
"vpc": "vpc",
"vswitch": "vswitch",
"havip_attachment": "havip_attachment",
"network_acl": "network_acl",
"route_table": "route_table",
"route_table_attachment": "route_table_attachment",
},
"ecs": {
"instance": "instance",
},
"rds": {
"instance": "db_instance",
},
"cbwp": {
"common_bandwidth_package": "common_bandwidth_package",
"common_bandwidth_package_attachment": "common_bandwidth_package_attachment",
},
"private_zone": {
"endpoint": "pvtz_endpoint",
"rule": "pvtz_rule",
"rule_attachment": "pvtz_rule_attachment",
"user_vpc_authorization": "pvtz_user_vpc_authorization",
"zone": "pvtz_zone",
"zone_attachment": "pvtz_zone_attachment",
"zone_record": "pvtz_zone_record",
},
"sls": {
"project": "log_project",
"store": "log_store",
},
}
var specialDataSourceMap = map[string]map[string]string{
"vpc": {
"vpc": "vpcs",
"vswitch": "vswitches",
"havip_attachment": "havip_attachments",
"network_acl": "network_acls",
"route_table": "route_tables",
"route_table_attachment": "route_table_attachments",
},
"ecs": {
"instance": "instances",
},
"rds": {
"instance": "db_instances",
},
"cbwp": {
"common_bandwidth_package": "common_bandwidth_packages",
"common_bandwidth_package_attachment": "common_bandwidth_package_attachments",
},
"private_zone": {
"endpoint": "pvtz_endpoints",
"rule": "pvtz_rules",
"rule_attachment": "pvtz_rule_attachments",
"user_vpc_authorization": "pvtz_user_vpc_authorizations",
"zone": "pvtz_zones",
"zone_attachment": "pvtz_zone_attachments",
"zone_record": "pvtz_zone_records",
},
"sls": {
"project": "log_projects",
"store": "log_stores",
},
}
var irregularPlurals = map[string]string{
"child": "children",
"person": "people",
"sheep": "sheep",
"fish": "fish",
}
func main() {
flag.Parse()
if *namespace == "" || *sourceProviderDir == "" || *destProviderDir == "" {
log.Fatal("Parameters -n, -s, -t are required")
}
if functionName != nil && *functionName != "" {
if err := migrateServiceFunction(namespace); err != nil {
log.Printf("Error migrate function: %v", err)
}
return
}
var resources []string
if *resource == "" {
rs, err := listResources(*namespace, *sourceProviderDir)
if err != nil {
log.Fatal(err)
}
resources = rs
log.Printf("Found %d resources to migrate: %v", len(resources), resources)
} else {
resources = []string{*resource}
}
for _, res := range resources {
log.Printf("====== Migrating %s ======", res)
migrateSingleResource(namespace, &res)
}
serviceName := *namespace
if v, ok := specialServiceMap[*namespace]; ok {
serviceName = v
}
serviceFileName := fmt.Sprintf("service_alicloud_%s.go", serviceName)
if err := migrateService(serviceFileName, "v1"); err != nil {
log.Printf("Error migrateService: %v", err)
}
serviceFileName = fmt.Sprintf("service_alicloud_%s_v2.go", *namespace)
if err := migrateService(serviceFileName, "v2"); err != nil {
log.Printf("Error migrateService: %v", err)
}
log.Println("All resources migrated!")
}
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
func listResources(namespace, sourceDir string) ([]string, error) {
dir := filepath.Join(sourceDir, "alicloud")
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("read dir failed: %w", err)
}
pattern := regexp.MustCompile(fmt.Sprintf(`^resource_alicloud_%s_(.+?)\.go$`, regexp.QuoteMeta(namespace)))
var resources []string
for _, entry := range entries {
if entry.IsDir() || strings.HasSuffix(entry.Name(), "_test.go") {
continue
}
if matches := pattern.FindStringSubmatch(entry.Name()); len(matches) == 2 {
resources = append(resources, matches[1])
}
}
if specialResources, ok := specialResourceMap[namespace]; ok {
for res := range specialResources {
if !contains(resources, res) {
resources = append(resources, res)
}
}
}
return resources, nil
}
func migrateSingleResource(namespace, resource *string) {
if err := migrateResource(namespace, resource); err != nil {
log.Printf("Error migrateResource: %v", err)
}
if err := migrateDataSource(namespace, resource); err != nil {
log.Printf("Error migrateDataSource: %v", err)
}
if err := migrateResourceTest(namespace, resource); err != nil {
log.Printf("Error migrateResourceTest: %v", err)
}
if err := migrateDataSourceTest(namespace, resource); err != nil {
log.Printf("Error migrateDataSource: %v", err)
}
if err := migrateResourceDocument(namespace, resource); err != nil {
log.Printf("Error migrateResourceDocument: %v", err)
}
if err := migrateDataSourceDocument(namespace, resource); err != nil {
log.Printf("Error migrateResourceDocument: %v", err)
}
log.Printf("Migrate %v finished! ^-^ ", *resource)
}
func migrateServiceFunction(namespace *string) error {
serviceFiles := []struct {
pattern string
version string
}{
{fmt.Sprintf("service_alicloud_%s.go", *namespace), "v1"},
// {fmt.Sprintf("service_alicloud_%s_v2.go", *namespace), "v2"},
}
for _, sf := range serviceFiles {
sourcePath := filepath.Join(*sourceProviderDir, "alicloud", sf.pattern)
destDir := filepath.Join(*destProviderDir, "internal", "service", *namespace)
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("create service dir failed: %w", err)
}
destFile := "service_common.go"
if sf.version == "v2" {
destFile = "service_common_v2.go"
}
if err := copyAndModifyFunction(sourcePath, filepath.Join(destDir, destFile), *namespace, sf.version); err != nil {
return fmt.Errorf("failed to migrate %s: %w", sf.pattern, err)
}
}
return nil
}
func functionExists(filePath string, funcName string) bool {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return false
}
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filePath, nil, parser.AllErrors)
if err != nil {
log.Printf("Error parsing file %s: %v", filePath, err)
return false
}
var exists bool
ast.Inspect(node, func(n ast.Node) bool {
if fn, ok := n.(*ast.FuncDecl); ok {
if fn.Name.Name == funcName {
exists = true
return false
}
}
return true
})
return exists
}
func copyAndModifyFunction(src, dest, namespace, version string) error {
if functionExists(dest, *functionName) {
log.Printf("Function %s already exists in %s, skip migration", *functionName, dest)
return nil
}
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, src, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("parse error: %w", err)
}
var funcDecl *ast.FuncDecl
ast.Inspect(node, func(n ast.Node) bool {
if fn, ok := n.(*ast.FuncDecl); ok && fn.Name.Name == *functionName {
funcDecl = fn
return false
}
return true
})
if funcDecl == nil {
return fmt.Errorf("function %s not found", *functionName)
}
var buf bytes.Buffer
printer.Fprint(&buf, fset, funcDecl)
code := applyFunctionModifications(buf.String(), version)
f, err := os.OpenFile(dest, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("open file failed: %w", err)
}
defer f.Close()
if _, err := f.WriteString("\n" + code + "\n"); err != nil {
return fmt.Errorf("write failed: %w", err)
}
return nil
}
func applyFunctionModifications(code string, version string) string {
lines := strings.Split(code, "\n")
var modifiedLines []string
for _, line := range lines {
line = modifyServiceFunctionLine(line, version)
modifiedLines = append(modifiedLines, line)
}
finalCode := strings.Join(modifiedLines, "\n")
return finalCode
}
func migrateResource(namespace, resource *string) error {
resourceName := getResourceName(*namespace, *resource)
sourceFileName := fmt.Sprintf("resource_alicloud_%s.go", resourceName)
sourceFilePath := fmt.Sprintf("%s/alicloud/%s", *sourceProviderDir, sourceFileName)
serviceDir := filepath.Join(*destProviderDir, "internal", "service", *namespace)
if err := os.MkdirAll(serviceDir, 0755); err != nil {
log.Fatalf("Create Service Dir Failed: %v", err)
}
destFileName := fmt.Sprintf("%s.go", *resource)
destFilePath := filepath.Join(serviceDir, destFileName)
err := copyFile(sourceFilePath, destFilePath)
if err != nil {
log.Fatalf("Error copying file: %v", err)
}
if err = modifyResourceFile(destFilePath, *namespace, *resource); err != nil {
log.Fatalf("Error modifying file: %v", err)
}
if err = formatFile(destFilePath); err != nil {
log.Fatalf("Error formatting file: %v", err)
}
resourceFunc, err := extractResourceFunction(destFilePath, *namespace, *resource, "Resource")
resourceKey := getResourceName(*namespace, *resource)
if err != nil {
return fmt.Errorf("extractResourceFunction failed: %w", err)
}
if err = registerToProvider(resourceKey, resourceFunc, false); err != nil {
log.Fatalf("Error registerToProvider: %v", err)
}
return err
}
func registerToProvider(resourceKey, resourceFunc string, datasource bool) error {
providerPath := filepath.Join(*destProviderDir, "internal", "provider", "provider.go")
return updateProviderMap(providerPath, *namespace, resourceKey, resourceFunc, datasource)
}
func extractResourceFunction(filePath, namespace, resource, prefix string) (string, error) {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
if err != nil {
return "", fmt.Errorf("parse error: %w", err)
}
var resourceFunc string
ast.Inspect(node, func(n ast.Node) bool {
if fn, ok := n.(*ast.FuncDecl); ok && strings.HasPrefix(fn.Name.Name, prefix) {
resourceFunc = fmt.Sprintf("%s.%s()", namespace, fn.Name.Name)
return false
}
return true
})
if resourceFunc == "" {
return "", fmt.Errorf("no ResourceXXX function found in %s", filePath)
}
return resourceFunc, nil
}
func updateProviderMap(filePath, namespace, resourceKey, resourceFunc string, datasource bool) error {
content, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("read provider.go failed: %w", err)
}
re := regexp.MustCompile("\"" + resourceKey + "\"")
containsKey := re.Match(content)
if containsKey {
return nil
}
targetComment := fmt.Sprintf("// %s", strings.ToUpper(namespace))
insertLine := fmt.Sprintf("\t\t\"%s\":\t\t\t%s,", resourceKey, resourceFunc)
var newContent []string
scanner := bufio.NewScanner(bytes.NewReader(content))
start := false
foundSection := false
inserted := false
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "import (") && !strings.Contains(string(content),
fmt.Sprintf("service/%s\"", namespace)) {
line += fmt.Sprintf("\n\t\"gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/service/%s\"",
namespace)
}
if datasource {
if strings.Contains(line, "func generateDataSourceMap()") {
start = true
}
if strings.Contains(line, "func generateResourceMap()") {
start = false
}
} else {
if strings.Contains(line, "func generateResourceMap()") {
start = true
}
}
if start && strings.Contains(line, targetComment) {
foundSection = true
}
if foundSection && !inserted {
if strings.Contains(line, "//") || strings.TrimSpace(line) == "" {
newContent = append(newContent, line)
} else {
newContent = append(newContent, insertLine)
newContent = append(newContent, line)
inserted = true
foundSection = false
}
} else {
newContent = append(newContent, line)
}
}
output := strings.Join(newContent, "\n")
formatted, err := format.Source([]byte(output))
if err != nil {
return fmt.Errorf("format failed: %w", err)
}
return os.WriteFile(filePath, formatted, 0644)
}
func migrateResourceDocument(namespace, resource *string) error {
resourceName := getResourceName(*namespace, *resource)
sourceFileName := fmt.Sprintf("%s.html.markdown", resourceName)
sourceFilePath := fmt.Sprintf("%s/website/docs/r/%s", *sourceProviderDir, sourceFileName)
targetDir := filepath.Join(*destProviderDir, "website", "docs", "r")
destFileName := fmt.Sprintf("%s.html.markdown", resourceName)
destFilePath := filepath.Join(targetDir, destFileName)
err := copyFile(sourceFilePath, destFilePath)
if err != nil {
log.Fatalf("Error copying file: %v", err)
}
if err = modifyDocument(destFilePath, *namespace, *resource); err != nil {
log.Fatalf("Error modifying file: %v", err)
}
return err
}
func migrateDataSourceDocument(namespace, resource *string) error {
resourceName := getDataSourceName(*namespace, *resource)
sourceFileName := fmt.Sprintf("%s.html.markdown", resourceName)
sourceFilePath := fmt.Sprintf("%s/website/docs/d/%s", *sourceProviderDir, sourceFileName)
targetDir := filepath.Join(*destProviderDir, "website", "docs", "d")
destFileName := fmt.Sprintf("%s.html.markdown", resourceName)
destFilePath := filepath.Join(targetDir, destFileName)
err := copyFile(sourceFilePath, destFilePath)
if err != nil {
log.Printf("Error copying file: %v", err)
log.Printf("Skipped copying file: %v", sourceFilePath)
return nil
}
if err = modifyDocument(destFilePath, *namespace, *resource); err != nil {
log.Fatalf("Error modifying file: %v", err)
}
return err
}
func migrateDataSource(namespace, resource *string) error {
resourceName := getDataSourceName(*namespace, *resource)
sourceFileName := fmt.Sprintf("data_source_alicloud_%s.go", resourceName)
sourceFilePath := fmt.Sprintf("%s/alicloud/%s", *sourceProviderDir, sourceFileName)
serviceDir := filepath.Join(*destProviderDir, "internal", "service", *namespace)
if err := os.MkdirAll(serviceDir, 0755); err != nil {
log.Fatalf("Create Service Dir Failed: %v", err)
}
destFileName := fmt.Sprintf("%s.go", toPlural(*resource))
destFilePath := filepath.Join(serviceDir, destFileName)
err := copyFile(sourceFilePath, destFilePath)
if err != nil {
log.Printf("Error copying file: %v", err)
log.Printf("Skipped copying file: %v", sourceFilePath)
return nil
}
if err = modifyResourceFile(destFilePath, *namespace, *resource); err != nil {
log.Fatalf("Error modifying file: %v", err)
}
if err = formatFile(destFilePath); err != nil {
log.Fatalf("Error formatting file: %v", err)
}
resourceFunc, err := extractResourceFunction(destFilePath, *namespace, *resource, "DataSource")
resourceKey := getDataSourceName(*namespace, *resource)
if err != nil {
return fmt.Errorf("extractResourceFunction failed: %w", err)
}
if err = registerToProvider(resourceKey, resourceFunc, true); err != nil {
log.Fatalf("Error registerToProvider: %v", err)
}
return err
}
func migrateService(serviceFileName, version string) error {
sourceFilePath := filepath.Join(*sourceProviderDir, "alicloud", serviceFileName)
if _, err := os.Stat(sourceFilePath); err != nil {
if os.IsNotExist(err) {
log.Printf("Cannot find file: %s", sourceFilePath)
return nil
}
}
serviceDir := filepath.Join(*destProviderDir, "internal", "service", *namespace)
if err := os.MkdirAll(serviceDir, 0755); err != nil {
log.Fatalf("Create Service Dir Failed: %v", err)
}
targetFileName := "service_common.go"
if version == "v2" {
targetFileName = "service_common_v2.go"
}
destFilePath := filepath.Join(serviceDir, targetFileName)
if _, err := os.Stat(destFilePath); err == nil {
return nil
}
err := copyFile(sourceFilePath, destFilePath)
if err != nil {
log.Printf("Error copying file: %v", err)
return nil
}
if err = modifyServiceFile(destFilePath, *namespace, version); err != nil {
log.Fatalf("Error modifying file: %v", err)
}
if err = formatFile(destFilePath); err != nil {
log.Fatalf("Error formatting file: %v", err)
}
return err
}
func migrateResourceTest(namespace, resource *string) error {
resourceName := getResourceName(*namespace, *resource)
sourceFileTestName := fmt.Sprintf("resource_alicloud_%s_test.go", resourceName)
sourceFileTestPath := fmt.Sprintf("%s/alicloud/%s", *sourceProviderDir, sourceFileTestName)
serviceDir := filepath.Join(*destProviderDir, "internal", "service", *namespace)
if err := os.MkdirAll(serviceDir, 0755); err != nil {
log.Fatalf("Create Service Dir Failed: %v", err)
}
destFileTestName := fmt.Sprintf("%s_test.go", *resource)
destFileTestPath := filepath.Join(serviceDir, destFileTestName)
err := copyFile(sourceFileTestPath, destFileTestPath)
if err != nil {
log.Fatalf("Error copying file: %v", err)
}
if err = modifyResourceTestFile(destFileTestPath, *namespace, *resource); err != nil {
log.Fatalf("Error modifying file: %v", err)
}
if err = formatFile(destFileTestPath); err != nil {
log.Fatalf("Error formatting file: %v", err)
}
return err
}
func migrateDataSourceTest(namespace, resource *string) error {
resourceName := getDataSourceName(*namespace, *resource)
sourceFileTestName := fmt.Sprintf("data_source_alicloud_%s_test.go", resourceName)
sourceFileTestPath := fmt.Sprintf("%s/alicloud/%s", *sourceProviderDir, sourceFileTestName)
serviceDir := filepath.Join(*destProviderDir, "internal", "service", *namespace)
if err := os.MkdirAll(serviceDir, 0755); err != nil {
log.Fatalf("Create Service Dir Failed: %v", err)
}
destFileTestName := fmt.Sprintf("%s_test.go", toPlural(*resource))
destFileTestPath := filepath.Join(serviceDir, destFileTestName)
err := copyFile(sourceFileTestPath, destFileTestPath)
if err != nil {
log.Printf("Error copying file: %v", err)
log.Printf("Skipped copying file: %v", sourceFileTestPath)
return nil
}
if err = modifyResourceTestFile(destFileTestPath, *namespace, *resource); err != nil {
log.Fatalf("Error modifying file: %v", err)
}
if err = formatFile(destFileTestPath); err != nil {
log.Fatalf("Error formatting file: %v", err)
}
return err
}
func copyFile(src, dest string) error {
content, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dest, content, 0644)
}
func modifyDocument(filePath, namespace, resource string) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file for modification: %w", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var lines []string
for scanner.Scan() {
line := scanner.Text()
line = strings.ReplaceAll(line, "alicloud", "apsara")
line = strings.ReplaceAll(line, "Alibaba", "Apsara")
line = strings.ReplaceAll(line, "Alicloud", "Apsara")
line = strings.ReplaceAll(line, "AliCloud", "Apsara")
line = strings.ReplaceAll(line, "alibabacloud", "apsaracloud")
line = strings.ReplaceAll(line, "aliyun", "apsara")
lines = append(lines, line)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error while reading file: %w", err)
}
fileOut, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("failed to create file for writing: %w", err)
}
defer fileOut.Close()
writer := bufio.NewWriter(fileOut)
for _, line := range lines {
_, err := writer.WriteString(line + "\n")
if err != nil {
return fmt.Errorf("error while writing to file: %w", err)
}
}
writer.Flush()
return nil
}
func commonReplaces(line string) string {
clientRe := regexp.MustCompile(`client\.Rpc([A-Za-z]+)\(\s*"([^"]+)",\s*"([^"]+)",\s*([^,]+),\s*([^,]+)(?:,\s*([^,]+))?(?:,\s*([^,]+))?(?:,\s*([^,]+))?(?:,\s*([^)]+))?\)`)
if strings.Contains(line, "client.Rpc") {
matches := clientRe.FindStringSubmatch(line)
if len(matches) >= 6 {
httpMethod := strings.ToUpper(matches[1])
service := matches[2]
version := matches[3]
action := matches[4]
pathParams := matches[5]
request := "nil"
options := "nil"
async := "true"
if len(matches) >= 7 && matches[6] != "" {
request = matches[6]
}
if len(matches) >= 8 && matches[7] != "" {
options = matches[7]
}
if len(matches) >= 9 && matches[8] != "" {
async = matches[8]
}
newLine := fmt.Sprintf(
`client.Do("%s", client.NewRpcParam("%s", "%s", %s), %s, %s, %s, nil, %s)`,
service,
httpMethod,
version,
action,
pathParams,
request,
options,
async,
)
line = strings.Replace(line, matches[0], newLine, 1)
}
}
line = strings.ReplaceAll(line, "string(PostgreSQL)", "\"PostgreSQL\"")
line = strings.ReplaceAll(line, "string(MySQL)", "\"MySQL\"")
line = strings.ReplaceAll(line, "string(MongoDB)", "\"MongoDB\"")
line = strings.ReplaceAll(line, "string(SQLServer)", "\"SQLServer\"")
line = strings.ReplaceAll(line, "NormalMode", "\"normal\"")
line = strings.ReplaceAll(line, "string(Month)", "string(names.Month)")
line = strings.ReplaceAll(line, "string(Year)", "string(names.Year)")
line = strings.ReplaceAll(line, "string(Postpaid)", "\"PostPaid\"")
line = strings.ReplaceAll(line, "string(Prepaid)", "\"PrePaid\"")
line = strings.ReplaceAll(line, "string(PostPaid)", "\"PostPaid\"")
line = strings.ReplaceAll(line, "string(PrePaid)", "\"PrePaid\"")
line = strings.ReplaceAll(line, "string(Serverless)", "\"Serverless\"")
line = strings.ReplaceAll(line, "PrePaid,", "names.PrePaid,")
line = strings.ReplaceAll(line, "PostPaid,", "names.PostPaid,")
line = strings.ReplaceAll(line, "Prepaid,", "names.Prepaid,")
line = strings.ReplaceAll(line, "Postpaid,", "names.Postpaid,")
line = strings.ReplaceAll(line, "Serverless,", "names.Serverless,")
line = strings.ReplaceAll(line, "PageSizeLarge", "names.PageSizeLarge")
line = strings.ReplaceAll(line, "PageSizeSmall", "names.PageSizeSmall")
line = strings.ReplaceAll(line, "PageSizeMedium", "names.PageSizeMedium")
line = strings.ReplaceAll(line, "convertListToJsonString", "helper.ConvertListToJsonString")
line = strings.ReplaceAll(line, "convertObjectToJsonString", "helper.ConvertObjectToJsonString")
line = strings.ReplaceAll(line, "expandStringList", "helper.ExpandStringList")
line = strings.ReplaceAll(line, "ParseResourceId", "helper.ParseResourceId")
line = strings.ReplaceAll(line, "convertListMapToJsonString", "helper.ConvertListMapToJsonString")
line = strings.ReplaceAll(line, "convertMaptoJsonString", "helper.ConvertMaptoJsonString")
line = strings.ReplaceAll(line, "convertListStringToListInterface", "helper.ConvertListStringToListInterface")
line = strings.ReplaceAll(line, "expandSingletonToList", "helper.ExpandSingletonToList")
line = strings.ReplaceAll(line, "GetFunc", "helper.GetFunc")
line = strings.ReplaceAll(line, "WaitTimeoutMsg", "tferr.WaitTimeoutMsg")
line = strings.ReplaceAll(line, "COMMA_SEPARATED", "names.COMMA_SEPARATED")
line = strings.ReplaceAll(line, "LOCAL_HOST_IP", "names.LOCAL_HOST_IP")
line = strings.ReplaceAll(line, "IsNil", "helper.IsNil")
roaParamRe := regexp.MustCompile(`roaParam\(\s*("[^"]+"|\w+)\s*,\s*("[^"]+"|\w+)\s*,\s*("[^"]+"|\w+)\s*,\s*([^)]+)\s*\)`)
line = roaParamRe.ReplaceAllString(line, `client.NewRpcParam($1, $2, $3)`)
if !strings.Contains(line, "schema.DefaultTimeout") {
line = strings.ReplaceAll(line, "DefaultTimeout", "names.DefaultTimeout")
}
if isVariable(line, "Throttling") {
line = strings.ReplaceAll(line, "Throttling", "\"Throttling\"")
}
if isVariable(line, "RenewAutoRenewal") {
line = strings.ReplaceAll(line, "RenewAutoRenewal", "\"AutoRenewal\"")
}
if isVariable(line, "RenewNormal") {
line = strings.ReplaceAll(line, "RenewNormal", "\"Normal\"")
}
if isVariable(line, "RenewNotRenewal") {
line = strings.ReplaceAll(line, "RenewNotRenewal", "\"NotRenewal\"")
}
if isVariable(line, "PayByTraffic") {
line = strings.ReplaceAll(line, "PayByTraffic", "\"PayByTraffic\"")
}
if isVariable(line, "Deleted") {
line = strings.ReplaceAll(line, "Deleted", "names.Deleted")
}
if isVariable(line, "Running") {
line = strings.ReplaceAll(line, "Running", "names.Running")
}
if isVariable(line, "Month") {
line = strings.ReplaceAll(line, "Month", "names.Month")
}
if isVariable(line, "Year") {
line = strings.ReplaceAll(line, "Year", "names.Year")
}
if isVariable(line, "Status") {
line = strings.ReplaceAll(line, "Status", "names.Status")
}
rdkPointerRe := regexp.MustCompile(`rdk\.StringPointer\(\s*d\.Get\("([^"]+)"\)(?:\.\(string\))?\s*\)`)
line = rdkPointerRe.ReplaceAllString(line, `rdk.StringPointer(d.Get("$1").(string))`)
if strings.Contains(line, " StringPointer") {
line = strings.ReplaceAll(line, " StringPointer", " rdk.StringPointer")
}
rdkNestedRe := regexp.MustCompile(`rdk\.StringPointer\(\s*StringPointer\(([^)]+)\)\s*\)`)
line = rdkNestedRe.ReplaceAllString(line, `rdk.StringPointer($1)`)
createRequestRe := regexp.MustCompile(`request := (\w+)\.Create(\w+)Request\(\)`)
line = createRequestRe.ReplaceAllString(line,
"action := \"$2\"\n\trequest := make(map[string]interface{})")
fieldAccessRe := regexp.MustCompile(`\b(request|req)\.(\w+)\b`)
line = fieldAccessRe.ReplaceAllString(line, `${1}["${2}"]`)
return line
}
func isVariable(line, code string) bool {
if strings.Contains(line, "\""+code) {
return false
}
if strings.Contains(line, code+"\"") {
return false
}
if strings.Contains(line, "."+code) {
return false
}
if strings.Contains(line, code+".") {
return false
}
return strings.Contains(line, " "+code+" ") || strings.Contains(line, " "+code+",")
}
func skipUpdate(filePath string) bool {
content, err := os.ReadFile(filePath)
if err != nil {
return true
}
re := regexp.MustCompile(` Cannot update`)
return re.Match(content)
}
func skipDelete(filePath string) bool {
content, err := os.ReadFile(filePath)
if err != nil {
return true
}
re := regexp.MustCompile(`(\[WARN\] Cannot destroy|Cannot delete)`)
return re.Match(content)
}
func modifyResourceFile(filePath, namespace, resource string) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file for modification: %w", err)
}
defer file.Close()
skippedUpdate := skipUpdate(filePath)
skippedDelete := skipDelete(filePath)
scanner := bufio.NewScanner(file)
var lines []string
headers := "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
headers = headers + "\"\n\"" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
headers = headers + "\"\n\"" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/rdk"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/names"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/err/sdkdiag"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/service"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/helper"
headers = headers + "\"\n" + "tferr \"gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/err"
imports := "import ("
imports = imports + "\n\"" + "context\""
resourceFuncRe := regexp.MustCompile(`func\s+resourceAli[Cc]loud(\w+)\s*\(\s*\)\s*\*schema\.Resource\s*\{`)
dataSourceFuncRe := regexp.MustCompile(`func\s+dataSourceAli[Cc]loud(\w+)\s*\(\s*\)\s*\*schema\.Resource\s*\{`)
clientRe := regexp.MustCompile(`client\.Rpc([A-Za-z]+)\(\s*"([^"]+)",\s*"([^"]+)",\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^)]+)\)`)
serviceRe := regexp.MustCompile(`([A-Z]\w*?)Service(V2)?\b`)
queryAssignRe := regexp.MustCompile(`query\["([^"]+)"\]\s*=\s*([^;\n]+)`)
diffSuppressRe := regexp.MustCompile(`(\w+)DiffSuppressFunc`)
var (
inValidateFunc bool
parenDepth int
)
for scanner.Scan() {
line := scanner.Text()
if inValidateFunc {
parenDepth += strings.Count(line, "(")
parenDepth -= strings.Count(line, ")")
trimmed := strings.TrimSpace(line)
if parenDepth <= 0 && strings.HasSuffix(trimmed, "),") {
inValidateFunc = false
parenDepth = 0
}
continue
}
if strings.Contains(line, "ValidateFunc") {
inValidateFunc = true
parenDepth += strings.Count(line, "(")
parenDepth -= strings.Count(line, ")")
trimmed := strings.TrimSpace(line)
if parenDepth <= 0 && (strings.HasSuffix(trimmed, ",") || strings.HasSuffix(trimmed, ")")) {
inValidateFunc = false
parenDepth = 0
}
continue
}
if strings.Contains(line, "SetPartial") {
continue
}
if strings.Contains(line, "helper/validation") {
continue
}
if strings.Contains(line, "helper/encryption") {
continue
}
if strings.Contains(line, "ConflictsWith") {
continue
}
if strings.Contains(line, "Removed:") {
continue
}
//if strings.Contains(line, "Deprecated:") {
// continue
//}
line = strings.ReplaceAll(line, "package alicloud", "package "+namespace)
line = strings.ReplaceAll(line, "import (", imports)
line = strings.ReplaceAll(line, "github.com/aliyun/terraform-provider-alicloud/alicloud/connectivity", "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/connectivity")
line = strings.ReplaceAll(line, "github.com/hashicorp/terraform-plugin-sdk/helper/resource", "")
line = strings.ReplaceAll(line, "github.com/hashicorp/terraform-plugin-sdk/helper/schema", headers)
line = resourceFuncRe.ReplaceAllString(line, "func Resource$1() *schema.Resource {")
line = dataSourceFuncRe.ReplaceAllString(line, "func DataSource$1() *schema.Resource {")
if strings.Contains(line, "Create:") {
if !strings.Contains(line, "Create: schema") {
line = strings.ReplaceAll(line, "Create:", "CreateContext:")
}
}
if strings.Contains(line, "Read:") {
if !strings.Contains(line, "Read: schema") {
line = strings.ReplaceAll(line, "Read:", "ReadContext:")
}
}
if strings.Contains(line, "Update:") {
if !strings.Contains(line, "Update: schema") {
line = strings.ReplaceAll(line, "Update:", "UpdateContext:")
}
}
if strings.Contains(line, "Delete:") {
if !strings.Contains(line, "Delete: schema") {
line = strings.ReplaceAll(line, "Delete:", "DeleteContext:")
}
}
line = strings.ReplaceAll(line, "(d, meta)", "(ctx, d, meta)")
line = strings.ReplaceAll(line, "tagsSchema()", "service.TagsSchema()")
if !strings.Contains(line, "func") {
line = strings.ReplaceAll(line, "tagsToMap", "service.TagsToMap")
}
line = strings.ReplaceAll(line, "AliCloud", "")
line = strings.ReplaceAll(line, "Alicloud", "")
line = strings.ReplaceAll(line, "connectivity.AliyunClient", "connectivity.Client")
line = strings.ReplaceAll(line, "(d *schema.ResourceData, meta interface{}) error {", "(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {")
line = strings.ReplaceAll(line, "State: schema.ImportStatePassthrough,", "StateContext: schema.ImportStatePassthroughContext,")
line = strings.ReplaceAll(line, "buildClientToken", "helper.BuildClientToken")
line = strings.ReplaceAll(line, "incrementalWait", "helper.IncrementalWait")
line = strings.ReplaceAll(line, "resource.", "retry.")
line = strings.ReplaceAll(line, "NeedRetry(err)", "tferr.NeedRetry(err)")
line = strings.ReplaceAll(line, "addDebug", "helper.AddDebug")
line = strings.ReplaceAll(line, "retry.Retry(", "retry.RetryContext(ctx, ")
line = strings.ReplaceAll(line, "StateRefreshFunc(", "StateRefreshFunc(ctx, ")
line = strings.ReplaceAll(line, "WaitForState()", "WaitForStateContext(ctx)")
line = strings.ReplaceAll(line, "convertListToCommaSeparate", "helper.ConvertListToCommaSeparate")
line = strings.ReplaceAll(line, "compareJsonTemplateAreEquivalent", "helper.CompareJsonTemplateAreEquivalent")
line = strings.ReplaceAll(line, "ConvertTags", "service.ConvertTags")
line = strings.ReplaceAll(line, "expandTagsToMap", "service.ExpandTagsToMap")
line = strings.ReplaceAll(line, "InArray", "helper.InArray")
line = strings.ReplaceAll(line, "parsingTags", "service.ParsingTags")
line = strings.ReplaceAll(line, "ignoredTags", "service.IgnoredTags")
if !strings.Contains(line, "strings.Trim") {
line = strings.ReplaceAll(line, "Trim(", "helper.Trim(")
}
line = strings.ReplaceAll(line, "isPagingRequest", "helper.IsPagingRequest")
line = strings.ReplaceAll(line, "formatInt", "helper.FormatInt")
line = strings.ReplaceAll(line, "formatBool", "helper.FormatBool")
line = strings.ReplaceAll(line, "IdMsg", "tferr.IdMsg")
line = strings.ReplaceAll(line, "WrapError(", "tferr.WrapError(")
line = strings.ReplaceAll(line, "WrapErrorf(", "tferr.WrapErrorf(")
line = strings.ReplaceAll(line, " DataDefaultErrorMsg", " tferr.DefaultErrorMsg")
line = strings.ReplaceAll(line, " DefaultErrorMsg", " tferr.DefaultErrorMsg")
line = strings.ReplaceAll(line, " FailedGetAttributeMsg", " tferr.FailedGetAttributeMsg")
line = strings.ReplaceAll(line, "AlibabaCloudSdkGoERROR", "tferr.SdkGoERROR")
line = strings.ReplaceAll(line, "IsExpectedErrors", "tferr.IsExpectedErrors")
line = strings.ReplaceAll(line, "NotFoundError", "tferr.NotFoundError")
line = strings.ReplaceAll(line, "BuildStateConf", "helper.BuildStateConf")
line = diffSuppressRe.ReplaceAllStringFunc(line, func(m string) string {
parts := diffSuppressRe.FindStringSubmatch(m)
if len(parts) < 2 {
return m
}
prefix := strings.ToUpper(string(parts[1][0])) + parts[1][1:]
return "helper." + prefix + "DiffSuppressFunc"
})
line = strings.ReplaceAll(line, "hashcode.String", "helper.HashString")
line = strings.ReplaceAll(line, "query := make(map[string]interface{})", "query := make(map[string]*string)")
line = strings.ReplaceAll(line, "var query map[string]interface{}", "var query map[string]*string")
line = strings.ReplaceAll(line, "query = make(map[string]interface{})", "query = make(map[string]*string)")
line = queryAssignRe.ReplaceAllString(line, `query["$1"] = rdk.StringPointer($2)`)
if strings.Contains(line, "return tferr.") {
line = strings.ReplaceAll(line, "WrapError(", "tferr.WrapError(")
line = strings.ReplaceAll(line, "WrapErrorf(", "tferr.WrapErrorf(")
line = strings.ReplaceAll(line, "return tferr.", "return sdkdiag.AppendFromErr(diags,")
line += ")"
}
if strings.TrimSpace(line) == "return err" {
line = "sdkdiag.AppendFromErr(diags, tferr.WrapError(err))"
}
if strings.Contains(line, "(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {") {
if (!strings.Contains(line, "Update") || !skippedUpdate) && (!strings.Contains(line, "Delete") || !skippedDelete) {
line = line + "\nvar diags diag.Diagnostics\n"
}
}
if strings.Contains(line, "client.Rpc") {
matches := clientRe.FindStringSubmatch(line)
if len(matches) == 8 {
httpMethod := strings.ToUpper(matches[1])
service := matches[2]
version := matches[3]
action := matches[4]
pathParams := matches[5]
request := matches[6]
async := matches[7]
newLine := fmt.Sprintf(
`client.Do("%s", client.NewRpcParam("%s", "%s", %s), %s, %s, nil, nil, %s)`,
service,
httpMethod,
version,
action,
pathParams,
request,
async,
)
line = strings.Replace(line, matches[0], newLine, 1)
}
}
line = serviceRe.ReplaceAllString(line, "Service$2")
line = strings.ReplaceAll(line, "alicloud_", "apsara_")
line = commonReplaces(line)
line = strings.ReplaceAll(line, "dataResourceIdHash", "helper.DataResourceIdHash")
line = strings.ReplaceAll(line, "writeToFile", "helper.WriteToFile")
line = strings.ReplaceAll(line, "(Error(", "(tferr.Error(")
lines = append(lines, line)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error while reading file: %w", err)
}
fileOut, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("failed to create file for writing: %w", err)
}
defer fileOut.Close()
writer := bufio.NewWriter(fileOut)
for _, line := range lines {
_, err := writer.WriteString(line + "\n")
if err != nil {
return fmt.Errorf("error while writing to file: %w", err)
}
}
writer.Flush()
return nil
}
func modifyServiceFile(filePath, namespace, version string) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file for modification: %w", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var lines []string
headers := "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
headers = headers + "\"\n\"" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/service"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/names"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/err/sdkdiag"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/helper"
headers = headers + "\"\n" + "tferr \"gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/err"
imports := "import ("
imports = imports + "\n\"" + "context\""
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "SetPartial") {
continue
}
if strings.Contains(line, "ValidateFunc") {
continue
}
line = strings.ReplaceAll(line, "package alicloud", "package "+namespace)
line = strings.ReplaceAll(line, "import (", imports)
line = strings.ReplaceAll(line, "github.com/aliyun/terraform-provider-alicloud/alicloud/connectivity", "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/connectivity")
line = strings.ReplaceAll(line, "github.com/hashicorp/terraform-plugin-sdk/helper/resource", headers)
line = strings.ReplaceAll(line, "github.com/hashicorp/terraform-plugin-sdk/helper/schema", "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema")
line = modifyServiceFunctionLine(line, version)
if strings.Contains(line, "type ServiceV2 struct {") {
lines = append(lines, "func NewServiceV2(client *connectivity.Client) *ServiceV2 {")
lines = append(lines, "return &ServiceV2{client}")
lines = append(lines, "}")
} else if strings.Contains(line, "type Service struct {") {
lines = append(lines, "func NewService(client *connectivity.Client) *Service {")
lines = append(lines, "return &Service{client}")
lines = append(lines, "}")
}
line = commonReplaces(line)
lines = append(lines, line)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error while reading file: %w", err)
}
fileOut, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("failed to create file for writing: %w", err)
}
defer fileOut.Close()
writer := bufio.NewWriter(fileOut)
for _, line := range lines {
_, err := writer.WriteString(line + "\n")
if err != nil {
return fmt.Errorf("error while writing to file: %w", err)
}
}
writer.Flush()
return nil
}
func modifyServiceFunctionLine(line, version string) string {
clientRe := regexp.MustCompile(`client\.Rpc([A-Za-z]+)\(\s*"([^"]+)",\s*"([^"]+)",\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^)]+)\)`)
serviceRe := regexp.MustCompile(`([A-Z]\w*?)Service\b`)
if version == "v2" {
serviceRe = regexp.MustCompile(`([A-Z]\w*?)ServiceV2\b`)
}
queryAssignRe := regexp.MustCompile(`query\["([^"]+)"\]\s*=\s*([^;\n]+)`)
stateRefreshDeclRe := regexp.MustCompile(`(func\s*\(.*?\)\s*\w+StateRefreshFunc)\s*\(`)
stateRefreshCallRe := regexp.MustCompile(`(\w+)\.(\w+StateRefreshFunc)\s*\(`)
diffSuppressRe := regexp.MustCompile(`(\w+)DiffSuppressFunc`)
line = strings.ReplaceAll(line, "(d, meta)", "(ctx, d, meta)")
line = strings.ReplaceAll(line, "tagsSchema()", "service.TagsSchema()")
if !strings.Contains(line, "func") {
line = strings.ReplaceAll(line, "tagsToMap", "service.TagsToMap")
}
line = strings.ReplaceAll(line, "AliCloud", "")
line = strings.ReplaceAll(line, "connectivity.AliyunClient", "connectivity.Client")
line = strings.ReplaceAll(line, "(d *schema.ResourceData, meta interface{}) error {", "(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {")
line = strings.ReplaceAll(line, "State: schema.ImportStatePassthrough,", "StateContext: schema.ImportStatePassthroughContext,")
line = strings.ReplaceAll(line, "buildClientToken", "helper.BuildClientToken")
line = strings.ReplaceAll(line, "incrementalWait", "helper.IncrementalWait")
line = strings.ReplaceAll(line, "resource.", "retry.")
line = strings.ReplaceAll(line, "NeedRetry(err)", "tferr.NeedRetry(err)")
line = strings.ReplaceAll(line, "addDebug", "helper.AddDebug")
line = strings.ReplaceAll(line, "retry.Retry(", "retry.RetryContext(ctx, ")
line = strings.ReplaceAll(line, "WaitForState()", "WaitForStateContext(ctx)")
if strings.Contains(line, "StateRefreshFunc") {
if matches := stateRefreshDeclRe.FindStringSubmatch(line); len(matches) > 0 {
line = strings.Replace(line, matches[0], matches[1]+"(ctx context.Context, ", 1)
}
if matches := stateRefreshCallRe.FindStringSubmatch(line); len(matches) > 0 {
line = strings.Replace(line, matches[0], matches[1]+"."+matches[2]+"(ctx, ", 1)
}
}
line = strings.ReplaceAll(line, "convertListToCommaSeparate", "helper.ConvertListToCommaSeparate")
line = strings.ReplaceAll(line, "ConvertTags", "service.ConvertTags")
line = strings.ReplaceAll(line, "expandTagsToMap", "service.ExpandTagsToMap")
line = strings.ReplaceAll(line, "InArray", "helper.InArray")
line = strings.ReplaceAll(line, "parsingTags", "service.ParsingTags")
line = strings.ReplaceAll(line, "ignoredTags", "service.IgnoredTags")
line = strings.ReplaceAll(line, "Trim(", "helper.Trim(")
line = strings.ReplaceAll(line, "isPagingRequest", "helper.IsPagingRequest")
line = strings.ReplaceAll(line, "formatInt", "helper.FormatInt")
line = strings.ReplaceAll(line, "formatBool", "helper.FormatBool")
line = strings.ReplaceAll(line, "IdMsg", "tferr.IdMsg")
line = strings.ReplaceAll(line, "WrapError(", "tferr.WrapError(")
line = strings.ReplaceAll(line, "WrapErrorf(", "tferr.WrapErrorf(")
line = strings.ReplaceAll(line, "DefaultErrorMsg", "tferr.DefaultErrorMsg")
line = strings.ReplaceAll(line, "AlibabaCloudSdkGoERROR", "tferr.SdkGoERROR")
line = strings.ReplaceAll(line, "IsExpectedErrors", "tferr.IsExpectedErrors")
line = strings.ReplaceAll(line, "NotFoundError", "tferr.NotFoundError")
line = strings.ReplaceAll(line, "NotFoundErr(", "tferr.NotFoundErr(")
line = strings.ReplaceAll(line, "NotFoundMsg", "tferr.NotFoundMsg")
line = strings.ReplaceAll(line, "ProviderERROR", "tferr.ProviderERROR")
line = strings.ReplaceAll(line, "FailedGetAttributeMsg", "tferr.FailedGetAttributeMsg")
line = strings.ReplaceAll(line, "NotFoundWithResponse", "tferr.NotFoundWithResponse")
line = strings.ReplaceAll(line, "FailedToReachTargetStatus", "tferr.FailedToReachTargetStatus")
line = strings.ReplaceAll(line, "BuildStateConf", "helper.BuildStateConf")
line = strings.ReplaceAll(line, "(Error(", "(tferr.Error(")
line = strings.ReplaceAll(line, "tferr.NotFoundMsg, tferr.ProviderERROR,", "tferr.NotFoundMsg,")
line = diffSuppressRe.ReplaceAllStringFunc(line, func(m string) string {
parts := diffSuppressRe.FindStringSubmatch(m)
if len(parts) < 2 {
return m
}
prefix := strings.ToUpper(string(parts[1][0])) + parts[1][1:]
return "helper." + prefix + "DiffSuppressFunc"
})
line = strings.ReplaceAll(line, "hashcode.String", "helper.HashString")
if strings.Contains(line, "client.Rpc") {
matches := clientRe.FindStringSubmatch(line)
if len(matches) == 8 {
httpMethod := strings.ToUpper(matches[1])
service := matches[2]
version := matches[3]
action := matches[4]
pathParams := matches[5]
request := matches[6]
async := matches[7]
newLine := fmt.Sprintf(
`client.Do("%s", client.NewRpcParam("%s", "%s", %s), %s, %s, nil, nil, %s)`,
service,
httpMethod,
version,
action,
pathParams,
request,
async,
)
line = strings.Replace(line, matches[0], newLine, 1)
}
}
if version == "v2" {
line = serviceRe.ReplaceAllString(line, "ServiceV2")
} else {
line = serviceRe.ReplaceAllString(line, "Service$2")
}
line = strings.ReplaceAll(line, "query := make(map[string]interface{})", "query := make(map[string]*string)")
line = strings.ReplaceAll(line, "var query map[string]interface{}", "var query map[string]*string")
line = strings.ReplaceAll(line, "query = make(map[string]interface{})", "query = make(map[string]*string)")
line = queryAssignRe.ReplaceAllString(line, `query["$1"] = rdk.StringPointer($2)`)
line = strings.ReplaceAll(line, "RetryContext(ctx,", "RetryContext(context.Background(),")
line = strings.ReplaceAll(line, "alicloud_", "apsara_")
return line
}
func modifyResourceTestFile(filePath, namespace, resource string) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file for modification: %w", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var lines []string
headers := "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/connectivity"
headers = headers + "\"\n\"" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/provider"
headers = headers + "\"\n" + "tftest \"gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/acctest"
headers = headers + "\"\n\"" + "gitlab.alibaba-inc.com/opensource-tools/terraform-provider-atlanta/internal/service/" + namespace
serviceConstructRe := regexp.MustCompile(`&\b([A-Za-z]+)(Service)(V2)?\b`)
skipCheckDestroyFunc := false
testAccCheckDestroyRe := regexp.MustCompile(`testAccCheck(\w+)Destroy\b`)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "testAccPreCheckWithEnvVariable") {
continue
}
if strings.Contains(line, "testAccPreCheckWithAccountSiteType") {
continue
}
if strings.Contains(line, "testAccPreCheckWithTime") {
continue
}
if strings.Contains(line, "testAccPreCheckEnterpriseAccountEnabled") {
continue
}
if strings.Contains(line, "func testAccCheck") && strings.Contains(line, "(s *terraform.State) error {") {
skipCheckDestroyFunc = true
continue
}
if skipCheckDestroyFunc {
if line == "}" {
skipCheckDestroyFunc = false
}
continue
}
line = testAccCheckDestroyRe.ReplaceAllStringFunc(line, func(m string) string {
parts := testAccCheckDestroyRe.FindStringSubmatch(m)
if len(parts) < 2 {
return m
}
return fmt.Sprintf("rac.CheckResourceDestroy()")
})
line = strings.ReplaceAll(line, "package alicloud", "package "+namespace+"_test")
line = strings.ReplaceAll(line, "github.com/hashicorp/terraform-plugin-sdk/helper/acctest", headers)
line = strings.ReplaceAll(line, "github.com/hashicorp/terraform-plugin-sdk/helper/resource", "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource")
line = strings.ReplaceAll(line, "github.com/aliyun/terraform-provider-alicloud/alicloud/connectivity", "")
line = strings.ReplaceAll(line, "AliCloud", "")
line = strings.ReplaceAll(line, "Alicloud", "")
line = strings.ReplaceAll(line, "alicloud_", "apsara_")
line = strings.ReplaceAll(line, "resourceAttrInit", "tftest.ResourceAttrInit")
line = strings.ReplaceAll(line, "resourceCheckInitWithDescribeMethod", "tftest.ResourceCheckInitWithDescribeMethod")
line = serviceConstructRe.ReplaceAllString(line, namespace+".New$2$3")
line = strings.ReplaceAll(line, "{testAccProvider.Meta().(*connectivity.AliyunClient)}", "(tftest.TestAccProvider.Meta().(*connectivity.Client))")
line = strings.ReplaceAll(line, "resourceAttrCheckInit", "tftest.ResourceAttrCheckInit")
line = strings.ReplaceAll(line, "resourceAttrMapUpdateSet", "ResourceAttrMapUpdateSet")
line = strings.ReplaceAll(line, "defaultRegionToTest", "provider.DefaultRegionToTest")
line = strings.ReplaceAll(line, "resourceTestAccConfigFunc", "tftest.ResourceTestAccConfig")
line = strings.ReplaceAll(line, "resourceCheckInit", "tftest.ResourceCheckInit")
line = strings.ReplaceAll(line, "testAccPreCheck(t)", "tftest.PreCheck(nil, t)")
line = strings.ReplaceAll(line, "Providers: testAccProviders", "ProtoV5ProviderFactories: tftest.ProtoV5ProviderFactories")
line = strings.ReplaceAll(line, "Providers: testAccProviders", "ProtoV5ProviderFactories: tftest.ProtoV5ProviderFactories")
line = strings.ReplaceAll(line, "rac.checkResourceDestroy()", "rac.CheckResourceDestroy()")
line = strings.ReplaceAll(line, "resourceAttrMapUpdateSet", "ResourceAttrMapUpdateSet")
line = strings.ReplaceAll(line, "testAccPreCheckWithRegions", "tftest.TestAccPreCheckWithRegions")
line = strings.ReplaceAll(line, "checkoutSupportedRegions", "tftest.TestAccPreCheckWithRegions")
line = strings.ReplaceAll(line, "CHECKSET", "tftest.CHECKSET")
line = strings.ReplaceAll(line, "REMOVEKEY", "tftest.REMOVEKEY")
line = strings.ReplaceAll(line, "NOSET", "tftest.NOSET")
line = strings.ReplaceAll(line, "dataSourceAttr", "tftest.DataSourceAttr")
line = strings.ReplaceAll(line, "dataSourceTestAccConfig", "tftest.DataSourceTestAccConfig")
line = strings.ReplaceAll(line, "existConfig:", "ExistConfig:")
line = strings.ReplaceAll(line, "fakeConfig:", "FakeConfig:")
line = strings.ReplaceAll(line, "resourceId:", "ResourceId:")
line = strings.ReplaceAll(line, "existMapFunc:", "ExistMapFunc:")
line = strings.ReplaceAll(line, "fakeMapFunc:", "FakeMapFunc:")
line = strings.ReplaceAll(line, "dataSourceTestCheck", "DataSourceTestCheck")
line = strings.ReplaceAll(line, "dataSourceTestAccConfig", "tftest.DataSourceTestAccConfig")
lines = append(lines, line)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error while reading file: %w", err)
}
fileOut, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("failed to create file for writing: %w", err)
}
defer fileOut.Close()
writer := bufio.NewWriter(fileOut)
for _, line := range lines {
_, err := writer.WriteString(line + "\n")
if err != nil {
return fmt.Errorf("error while writing to file: %w", err)
}
}
writer.Flush()
return nil
}
func formatFile(filePath string) error {
content, err := os.ReadFile(filePath)
if err != nil {
return err
}
if strings.Contains(filePath, "_test.go") {
funcNames := []string{"init", "testSweep", "TestUnit"}
content, err = removeFunctionsWithFuncNames(filePath, content, funcNames)
if err != nil {
return err
}
}
formattedContent, err := imports.Process(filePath, content, nil)
if err != nil {
log.Printf("error while optimizing %s imports: %v", filePath, err.Error())
formattedContent, err = format.Source(content)
if err != nil {
log.Printf("error while formatting to file: %v", err.Error())
formattedContent = content
}
}
return os.WriteFile(filePath, formattedContent, 0644)
}
func getResourceName(namespace, resource string) string {
if productMap, ok := specialResourceMap[namespace]; ok {
if mappedName, ok := productMap[resource]; ok {
return mappedName
}
}
return fmt.Sprintf("%s_%s", namespace, resource)
}
func getDataSourceName(namespace, resource string) string {
if productMap, ok := specialDataSourceMap[namespace]; ok {
if mappedName, ok := productMap[resource]; ok {
return mappedName
}
}
return fmt.Sprintf("%s_%s", namespace, toPlural(resource))
}
func removeFunctionsWithFuncNames(filename string, content interface{}, funcNames []string) ([]byte, error) {
fileSet := token.NewFileSet()
file, err := parser.ParseFile(fileSet, filename, content, parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("error while parsing AST: %w", err)
}
filteredDecls := make([]ast.Decl, 0, len(file.Decls))
for _, decl := range file.Decls {
if fnDecl, ok := decl.(*ast.FuncDecl); ok {
if containsFuncNames(fnDecl.Name.Name, funcNames) {
continue
}
}
filteredDecls = append(filteredDecls, decl)
}
file.Decls = filteredDecls
var buf bytes.Buffer
if err := format.Node(&buf, fileSet, file); err != nil {
return nil, fmt.Errorf("error while formatting to file: %w", err)
}
return buf.Bytes(), nil
}
func containsFuncNames(name string, funcNames []string) bool {
for _, funcName := range funcNames {
if strings.Contains(name, funcName) {
return true
}
}
return false
}
var pluralRules = []struct {
suffix string
replacement string
}{
{"ss", "sses"},
{"s", "ses"},
{"sh", "shes"},
{"ch", "ches"},
{"x", "xes"},
{"z", "zes"},
{"o", "oes"},
{"f", "ves"},
{"fe", "ves"},
{"us", "i"},
{"y", "ies"},
}
func toPlural(s string) string {
if plural, exists := irregularPlurals[strings.ToLower(s)]; exists {
return applyCase(s, plural)
}
lowerWord := strings.ToLower(s)
for _, rule := range pluralRules {
if strings.HasSuffix(lowerWord, rule.suffix) {
if rule.suffix == "y" {
if len(s) > 1 && !isVowel(rune(s[len(s)-2])) {
return applyCase(s, s[:len(s)-1]+"ies")
}
continue
}
if rule.suffix == "o" {
switch lowerWord {
case "photo", "piano", "halo":
return s + "s"
}
}
return applyCase(s, s[:len(s)-len(rule.suffix)]+rule.replacement)
}
}
return s + "s"
}
func applyCase(original, transformed string) string {
if len(original) == 0 {
return transformed
}
if original[0] >= 'A' && original[0] <= 'Z' {
if len(transformed) > 0 {
return strings.ToUpper(string(transformed[0])) + transformed[1:]
}
}
return transformed
}
func isVowel(r rune) bool {
switch r {
case 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U':
return true
}
return false
}