diff --git a/.goreleaser.yml b/.goreleaser.yml index 7ce3cb8b1..8db297aa5 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -32,45 +32,15 @@ builds: - goos: darwin goarch: "386" binary: "{{ .ProjectName }}_v{{ .Version }}" - - id: migration-tool - dir: ./cmd/migration-tool/ - env: - # goreleaser does not work with CGO, it could also complicate - # usage by users in CI/CD systems like Terraform Cloud where - # they are unable to install libraries. - - CGO_ENABLED=0 - mod_timestamp: "{{ .CommitTimestamp }}" - flags: - - -trimpath - ldflags: - - "-s -w -X main.Version={{.Version}}" - goos: - - freebsd - - windows - - linux - - darwin - goarch: - - amd64 - - "386" - - arm - - arm64 - ignore: - - goos: darwin - goarch: "386" - binary: equinix-migration-tool/equinix-migration-tool archives: - format: zip name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" builds: - provider - - migration-tool files: - LICENSE* - README* - CHANGELOG* - - src: "cmd/migration-tool/*.md" - dst: equinix-migration-tool - strip_parent: true checksum: name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS" algorithm: sha256 diff --git a/CODEOWNERS b/CODEOWNERS index ab4dcf5bb..6c3ed2313 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,7 +1,6 @@ # See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#example-of-a-codeowners-file # * @equinix/governor-devrel-engineering -/cmd/migration-tool @equinix/governor-metal-client-interfaces *metal* @equinix/governor-metal-client-interfaces *fabric* @equinix/governor-digin-fabric *connection_e2e* @equinix/governor-digin-fabric diff --git a/cmd/migration-tool/Makefile b/cmd/migration-tool/Makefile deleted file mode 100644 index bb0e5eaab..000000000 --- a/cmd/migration-tool/Makefile +++ /dev/null @@ -1,21 +0,0 @@ - -BINARY =equinix-migration-tool -GOCMD =go -TEST ?=$$(go list ./... |grep -v 'vendor') - -default: clean build test - -all: default - -test: - echo $(TEST) | \ - xargs -t ${GOCMD} test -v -timeout=10m - -clean: - ${GOCMD} clean - rm -f ${BINARY} - -build: - ${GOCMD} build -o ${BINARY} - -.PHONY: build clean release diff --git a/cmd/migration-tool/README.md b/cmd/migration-tool/README.md deleted file mode 100644 index dd0662458..000000000 --- a/cmd/migration-tool/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# Equinix Terraform Provider Migration Tool - -[Equinix Metal](https://metal.equinix.com/) (formerly Packet), has been fully integrated into Platform Equinix and therefore the teraform provider changes too. Together with Equinix Fabric and Equinix Network Edge, from `v1.5.0` the Equinix Terraform Provider will be used to interact with the resources provided by Equinix Metal. - -This tool will target a terraform working directory and transform all\* `metal` or `packet` names found in *.tf* and *.tfstate* files to the `equinix` provider name. It creates a backup of the target directory *\.backup* as a sibling folder. - -\**This tool will not transform variable names or comments even if they contain the words `metal` or `packet`.* - -## Provider Setup and Config Verfification - -The migration will transform the `metal` or `packet` provider block as well as the required_providers in the terraform block and, if included, it comments the attribute `version` to take the latest available of the `equinix` provider: - -From: - -```hcl -terraform { - required_providers { - packet = { - source = "packethost/packet" - version = "3.2.1" - } - } -} - -provider "packet" { - auth_token = var.auth_token -} -``` - -To: - -```hcl -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - #version = "3.2.1" - } - } -} - -provider "equinix" { - auth_token = var.auth_token -} -``` - -__NOTE__ - -If your code already includes both `equinix` provider and `metal` | `packet`, the resulting code will have two `equinix` provider blocks and they will also be duplicated in the required_providers definition. If this is your case, after migrate you must manually combine them in a single one with all the parameters required: - -From: - -```hcl -provider "equinix" { - auth_token = var.auth_token -} -provider "equinix" { - client_id = var.client_id - client_secret = var.client_secret -} -``` - -To: - -```hcl -provider "equinix" { - auth_token = var.auth_token - client_id = var.client_id - client_secret = var.client_secret -} -``` - -If you have any other requirements in the provider definition that this tool does not address, you will need to manually modify them after running a migration. - -## Remote State - -The **equinix-terraform-tool** does not support [remote state](https://www.terraform.io/docs/state/remote.html). If you are using remote state, then the recommended approach is to copy the state file locally, run the **equinix-terraform-tool**, and then push the state file back to the remote location. See the documentation [here](https://www.terraform.io/docs/backends/config.html) for details about how to unconfigure and reconfigure your backend. - -## Using the tool - -To migrate your terraform project, follow these steps: - -From the project directory, run `terraform plan`, make sure there are no pending changes in your plan. - -Execute the **equinix-terraform-tool** binary, passing the path to your project directory, example: - -`equinix-terraform-tool migrate -dir=` - -After migrating, run `terraform plan` again and verify there are no new pending modifications. - -For Terraform v.10+, you will need to initialize terraform for the directory using `terraform init` - -If the migration was not successful, manually restore the project files -from the .backup directory or run: - -`equinix-terraform-tool backup -dir= -restore` - -After you have verified the migration was successful, delete the -backup directory or run: - -`equinix-terraform-tool backup -dir= -purge` - -## Credits - -Based on [OCI Provider migration tool](https://registry.terraform.io/providers/hashicorp/oci/latest/docs/guides/version-2-upgrade#migration-tool) - *Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.* diff --git a/cmd/migration-tool/actions.go b/cmd/migration-tool/actions.go deleted file mode 100644 index 94bc07b8b..000000000 --- a/cmd/migration-tool/actions.go +++ /dev/null @@ -1,191 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "io" - "io/ioutil" - "os" - "path" - "path/filepath" -) - -// Individual file io strategies for different operations -type FileAction func(string, string) error - -// Traverse a directory, executing the supplied FileAction on each file -func ProcessDirectory(targetDir string, backupDir string, fileActionFn FileAction, targetExtns ...string) (err error) { - _, err = os.Stat(targetDir) - - if err != nil { - return fmt.Errorf("error reading directory\n %s", err) - } - - files, err := ioutil.ReadDir(targetDir) - if err != nil { - return fmt.Errorf("error reading directory contents \n %s", err) - } - - for _, res := range files { - targetRes := path.Join(targetDir, res.Name()) - backupRes := path.Join(backupDir, res.Name()) - - if res.IsDir() { - err = ProcessDirectory(targetRes, backupRes, fileActionFn, targetExtns...) - - if err != nil { - return err - } - } else { - if len(targetExtns) == 0 { - err = fileActionFn(targetRes, backupRes) - - if err != nil { - return err - } - } else { - if contains(targetExtns, filepath.Ext(res.Name())) { - err = fileActionFn(targetRes, backupRes) - - if err != nil { - return err - } - } else { - fmt.Println("Skipping: ", targetDir) - } - } - } - } - - return -} - -// Copy file from targetFile path to backupFile path -func CopyFile(targetFile string, backupFile string) (err error) { - // make sure directory structure exists - bkDir := path.Dir(backupFile) - _, err = os.Stat(bkDir) - - if err != nil { - if os.IsNotExist(err) { - oDir := path.Dir(targetFile) - fi, err := os.Stat(oDir) - if err != nil { - return fmt.Errorf("error reading original directory %s", err) - } - - err = os.MkdirAll(bkDir, fi.Mode()) - - if err != nil { - return fmt.Errorf("error creating directory for file %s", err) - } - } else { - return fmt.Errorf("unexpected error reading original directory %s", err) - } - } - - src, err := os.Open(targetFile) - if err != nil { - return fmt.Errorf("error reading original file\n %s", err) - } - - defer src.Close() - - dst, err := os.Create(backupFile) - if err != nil { - return fmt.Errorf("error creating backup file\n %s", err) - } - - defer dst.Close() - - fmt.Printf("Copying %s --> %s", targetFile, backupFile) - size, err := io.Copy(dst, src) - if err != nil { - return fmt.Errorf("error writing file\n %s", err) - } - - fmt.Printf(", %d bytes\n", size) - return -} - -// Read file from backup location, apply transforms and overwrite original file -func MigratePlanFile(targetFile string, backupFile string) (err error) { - src, err := os.Open(backupFile) - if err != nil { - return fmt.Errorf("error reading file\n %s", err) - } - - defer src.Close() - - dst, err := os.Create(targetFile) - if err != nil { - return fmt.Errorf("error creating write location\n %s", err) - } - - defer dst.Close() - - wrtr := bufio.NewWriter(dst) - - var replaceStrategy func(string) string - if filepath.Ext(backupFile) == ".tf" { - replaceStrategy = replaceTemplateTokens - } else { - replaceStrategy = replaceStatefileTokens - } - - scanner := bufio.NewScanner(src) - for scanner.Scan() { - str := scanner.Text() - str = replaceStrategy(str) - fmt.Fprintln(wrtr, str) - } - wrtr.Flush() - - return -} - -// Scan TF files for terraform:required_providers and provider blocks and define or update Equinix provider -func TransformProvider(targetFile string, backupFile string) error { - fmt.Printf("Scanning %s\n", targetFile) - - fileInfo, err := os.Stat(targetFile) - if err != nil { - return fmt.Errorf("error while updating provider\n %s", err) - } - - const maxSize = 1024 * 1024 - if fileInfo.Size() > maxSize { - return fmt.Errorf("file too large to process") - } - - fileBytes, err := ioutil.ReadFile(targetFile) - if err != nil { - return fmt.Errorf("error updating terraform:required_providers block\n %s", err) - } - - content := string(fileBytes) - - content, err = scanAndUpdateRequiredProvider(content) - if err != nil { - return fmt.Errorf("error updating terraform:required_providers block\n %s", err) - } - - content, err = scanAndUpdateProvider(content) - if err != nil { - return fmt.Errorf("error updating provider block\n %s", err) - } - - ioutil.WriteFile(targetFile, []byte(content), fileInfo.Mode()) - - return err -} - -// find a string in a slice of strings -func contains(items []string, target string) bool { - for _, item := range items { - if item == target { - return true - } - } - return false -} diff --git a/cmd/migration-tool/commands.go b/cmd/migration-tool/commands.go deleted file mode 100644 index 4d819d30a..000000000 --- a/cmd/migration-tool/commands.go +++ /dev/null @@ -1,117 +0,0 @@ -package main - -import ( - "fmt" - "os" -) - -// Copy target directory and append .backup -func CreateBackup(targetDir string, backupDir string) (err error) { - fmt.Println("creating backup...", targetDir, "-->", backupDir) - - fi, err := os.Stat(targetDir) - if err != nil { - return fmt.Errorf("error reading directory\n %s", err) - } - - if !fi.IsDir() { - return fmt.Errorf("file targeted for migration") - } - - _, err = os.Stat(backupDir) - - if err == nil { - return fmt.Errorf("attempting to overwrite backups") - } - - fmt.Println("copying", targetDir, "-->", backupDir) - - err = ProcessDirectory(targetDir, backupDir, CopyFile) - - if err != nil { - return err - } - - bfi, err := os.Stat(backupDir) - - if fi.Size() != bfi.Size() { - return fmt.Errorf("backup corrupt") - } - - fmt.Println("complete") - return -} - -// Overwrite target directory with contents of .backup directory -func RestoreBackup(backupDir string, targetDir string) (err error) { - fmt.Println("restoring from backup...") - - fi, err := os.Stat(backupDir) - if err != nil { - return fmt.Errorf("error reading backup\n %s", err) - } - - err = os.RemoveAll(targetDir) - - if err != nil { - return fmt.Errorf("error removing original directory\n %s", err) - } - - os.MkdirAll(targetDir, fi.Mode()) - - err = ProcessDirectory(backupDir, targetDir, CopyFile) - - if err != nil { - return fmt.Errorf("error restoring from backup directory\n %s", err) - } - - fmt.Println("complete") - return -} - -// Remove .backup directory -func DeleteBackup(backupDir string) (err error) { - fmt.Println("Purging backup...") - - err = os.RemoveAll(backupDir) - - if err != nil { - return fmt.Errorf("error removing backup directory\n %s", err) - } - - fmt.Println("complete") - return -} - -// Traverse all .tf files and apply transforms -func Migrate(targetDir string, backupDir string) (err error) { - fmt.Println("migrating plan directory...") - err = CreateBackup(targetDir, backupDir) - - if err != nil { - return fmt.Errorf("error backing up directory before migration\n %s", err) - } - - err = ProcessDirectory(targetDir, backupDir, MigratePlanFile, ".tf", ".tfstate") - - if err != nil { - return fmt.Errorf("error removing backup directory\n %s", err) - } - - fmt.Println("complete") - return -} - -// Traverse all .tf files and migrate or update Equinix provider -func MigrateProvider(targetDir string, backupDir string) (err error) { - fmt.Println("scanning tf files for provider...") - - err = ProcessDirectory(targetDir, backupDir, TransformProvider, ".tf") - - if err != nil { - return fmt.Errorf("error scanning providers for missing region value\n %s", err) - } - - fmt.Println("complete") - return -} diff --git a/cmd/migration-tool/main.go b/cmd/migration-tool/main.go deleted file mode 100644 index b1b210395..000000000 --- a/cmd/migration-tool/main.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - "path" -) - -func main() { - if len(os.Args) < 2 { - fmt.Println("Missing required command. One of [migrate, backup, version]") - os.Exit(1) - } - - if os.Args[1] == "-v" || os.Args[1] == "-version" || os.Args[1] == "--version" || os.Args[1] == "version" { - fmt.Println(Version) - os.Exit(0) - } - - if os.Args[1] == "backup" { - backup := flag.NewFlagSet("backup", flag.PanicOnError) - backup.Usage = func() { - backup.PrintDefaults() - os.Exit(0) - } - dir := backup.String("dir", "", "Required, specify the plan directory to operate on") - purge := backup.Bool("purge", false, "Optional, whether to purge the backup directory") - restore := backup.Bool("restore", false, "Optional, whether to restore from the backup directory") - - err := backup.Parse(os.Args[2:]) - - if *dir == "" { - fmt.Println("Missing required directory flag\nCommand flags:") - backup.PrintDefaults() - os.Exit(1) - } - - if err != nil { - panic(err) - } - - targetDir := path.Clean(*dir) - backupDir := targetDir + ".backup" - - fmt.Println(targetDir) - - if *purge { - err := DeleteBackup(backupDir) - if err != nil { - panic(err) - } - - return - } - - if *restore { - err := RestoreBackup(backupDir, targetDir) - if err != nil { - panic(err) - } - - return - } - - err = CreateBackup(targetDir, backupDir) - - if err != nil { - panic(err) - } - os.Exit(0) - } - - if os.Args[1] == "migrate" { - migrate := flag.NewFlagSet("migrate", flag.PanicOnError) - migrate.Usage = func() { - migrate.PrintDefaults() - os.Exit(0) - } - dir := migrate.String("dir", "", "Required, specify the plan directory to operate on") - err := migrate.Parse(os.Args[2:]) - - if *dir == "" { - fmt.Println("Missing required directory flag\nCommand flags:") - migrate.PrintDefaults() - os.Exit(1) - } - - if err != nil { - panic(err) - } - - targetDir := path.Clean(*dir) - backupDir := targetDir + ".backup" - - err = Migrate(targetDir, backupDir) - - if err != nil { - panic(err) - } - - err = MigrateProvider(targetDir, backupDir) - - if err != nil { - panic(err) - } - - fmt.Println(`Migration Successful!`) - os.Exit(0) - } - - fmt.Println("Unknown command") - os.Exit(1) -} diff --git a/cmd/migration-tool/transforms.go b/cmd/migration-tool/transforms.go deleted file mode 100644 index 849c92360..000000000 --- a/cmd/migration-tool/transforms.go +++ /dev/null @@ -1,317 +0,0 @@ -package main - -import ( - "fmt" - "regexp" - "strings" - "unicode/utf8" -) - -// matches block headers, ex: -// resource "metal_project" "fooproject" { -// data "packet_vlan" "foovlan" { -var matchBlockHeader = regexp.MustCompile(`(resource|data)(\s+")(metal|packet)(.*?)`) - -// matches resource interpolation strings (Terraform v0.11 and earlier), ex: -// device_id = "${metal_device.foodevice.id}" -var matchResourceInterpolation = regexp.MustCompile(`(.*?)(\${\s*)(metal|packet)(_.*?)`) - -// matches resource reference (Terraform v0.12+), ex: -// device_id = metal_device.foodevice.id -var matchResourceReference = regexp.MustCompile(`(.*?)(=\s*)(metal|packet)(_.*?)`) - -// matches resource reference in function, ex: -// cidr_notation = join("/", [cidrhost(metal_reserved_ip_block.fooblock.cidr_notation, 0), "32"]) -var matchResourceFunction = regexp.MustCompile(`(.*?)(\(\s*)(metal|packet)(_.*?)`) - -// matches resource reference in conditional, ex: -// ip_address = "${var.network_type == "public" ? metal_device.foodevice.access_public_ipv4 : metal_device.foodevice.access_private_ipv4}" -// ip_address = var.network_type == "public" ? metal_device.foodevice.access_public_ipv4 : metal_device.foodevice.access_private_ipv4 -var matchResourceConditional = regexp.MustCompile(`(.*?[:|\?])(\s*)(metal|packet)(_.*?)`) - -// matches resource reference in for loop,ex: -// toset([for network in metal_device.foodevice.network : network.family]) -var matchResourceForLoop = regexp.MustCompile(`(.*?)(in\s*)(metal|packet)(_.*?)`) - -// matches resource in expression,ex: -// tolist([metal_device.foodevice[*].access_public_ipv4]) -// !metal_ip_attachment.fooattach.public -// totalSpeed = metal_connection.fooconnA.speed + metal_connection.fooconnB.speed -var matchResourceExpression = regexp.MustCompile(`(.*?[\+|-|\*|\/|>|<|&|\|\||%|!|\[]\s*)(metal|packet)(_.*?)`) - -// matches datasource references, ex: -// address_family = "${lookup(data.packet_device_bgp_neighbors.test.bgp_neighbors[0], "address_family")}" -var matchDatasourceReference = regexp.MustCompile(`(.*?data)(\.)(metal|packet)(_.*?)`) - -// replace specific string patterns in template files -func replaceTemplateTokens(str string) string { - // resources - str = matchBlockHeader.ReplaceAllString(str, `$1 "equinix_metal$4`) - str = matchResourceInterpolation.ReplaceAllString(str, `$1${equinix_metal$4`) - str = matchResourceReference.ReplaceAllString(str, `${1}= equinix_metal$4`) - str = matchResourceFunction.ReplaceAllString(str, `$1(equinix_metal$4`) - str = matchResourceConditional.ReplaceAllString(str, `$1 equinix_metal$4`) - str = matchResourceForLoop.ReplaceAllString(str, `${1}in equinix_metal$4`) - str = matchResourceExpression.ReplaceAllString(str, `${1}equinix_metal$3`) - // datasources - return matchDatasourceReference.ReplaceAllString(str, `$1.equinix_metal$4`) -} - -// matches '"metal_' or '"packet_' prefixes in statefile -var matchStatePrefixes = regexp.MustCompile(`(.*")(metal|packet)(_.*)`) - -// matches provider url in statefile -var matchStateProvider = regexp.MustCompile(`(.*?)(equinix\/metal|packethost\/packet)(\\".*?)`) - -// replace metal|packet in statefile -func replaceStatefileTokens(str string) string { - // provider - str = matchStateProvider.ReplaceAllString(str, `${1}equinix/equinix$3`) - // datasources - str = matchDatasourceReference.ReplaceAllString(str, `$1.equinix_metal$4`) - // metal and prefixes - return matchStatePrefixes.ReplaceAllString(str, `${1}equinix_metal$3`) -} - -// rewrite matching required provider to have equinix provider with no version -func updateRequiredProvider(content string) (string, error) { - idx, _ := findToken("metal", content) - if idx == -1 { - idx, _ = findToken("packet", content) - } - if idx == -1 { - return content, nil - } - - subStr := content[idx:] // ignore everything before metal/packet provider - - // replace provider name - subStr = strings.Replace(subStr, "metal", "equinix", 1) - subStr = strings.Replace(subStr, "packet", "equinix", 1) - - blockStart, blockEnd := indexOpenCloseTokens('{', '}', subStr) // limit search to logical provider block - if blockStart == -1 || blockEnd == -1 { - return content, fmt.Errorf("required Provider metal/packet block start or end not detected") - } - - blkContents := subStr[:blockEnd] // get just from provider name to the end of logical block - // replace source - blkContents = strings.Replace(blkContents, "equinix/metal", "equinix/equinix", 1) - blkContents = strings.Replace(blkContents, "packethost/packet", "equinix/equinix", 1) - - // comment version - blkContents = strings.Replace(blkContents, "version", "#version", 1) - - return content[:idx] + blkContents + subStr[blockEnd:], nil -} - -// find all required_providers definitions and make required transforms -func scanAndUpdateRequiredProvider(content string) (string, error) { - for start, i := 0, -1; ; { - i, _ = findTokenAfter("required_providers", content, start) - - // "required_providers" block not present in file - if i == -1 { - return content, nil - } - - start += i - - blockStart, blockEnd := indexOpenCloseTokens('{', '}', content[start:]) - - if blockStart == -1 { - return content, fmt.Errorf("required provider detected, block start not found") - } - - if blockEnd == -1 { - return content, fmt.Errorf("required provider detected, block end not found") - } - - end := start + blockEnd + 1 - - res, err := updateRequiredProvider(content[start:end]) - if err != nil { - return content, fmt.Errorf("problem parsing terraform:required_providers block\n %s", err) - } - - content = content[:start] + res + content[end:] - - start = end - } -} - -// rewrite matching provider block to have equinix provider -func updateProviderBlock(content string) (string, error) { - idx, _ := findToken("metal", content) - if idx == -1 { - idx, _ = findToken("packet", content) - } - if idx == -1 { - return content, nil - } - - subStr := content[idx:] // ignore everything before metal/packet provider - - // replace provider name - subStr = strings.Replace(subStr, "metal", "equinix", 1) - subStr = strings.Replace(subStr, "packet", "equinix", 1) - - return content[:idx] + subStr, nil -} - -// find all providers blocks and make required transforms -func scanAndUpdateProvider(content string) (string, error) { - for start, i := 0, -1; ; { - i, _ = findTokenAfter("provider", content, start) - - // "providers" block not present in file - if i == -1 { - return content, nil - } - - start += i - - blockStart, blockEnd := indexOpenCloseTokens('{', '}', content[start:]) - - if blockStart == -1 { - return content, fmt.Errorf("provider detected, block start not found") - } - - if blockEnd == -1 { - return content, fmt.Errorf("provider detected, block end not found") - } - - end := start + blockEnd + 1 - - res, err := updateProviderBlock(content[start:end]) - if err != nil { - return content, fmt.Errorf("problem parsing provider block\n %s", err) - } - - content = content[:start] + res + content[end:] - - start = end - } -} - -// return the text extent of a token match in a string -func findToken(token string, content string) (start int, end int) { - idx := strings.Index(content, token) - return idx, idx + len(token) -} - -// return the text extent of a token match in a string after a specified index -func findTokenAfter(token string, content string, begin int) (start int, end int) { - newStr := content[begin:] - idx := strings.Index(newStr, token) - - if idx == -1 { - return -1, -1 - } - - return idx, idx + len(token) -} - -// parse logical terraform blocks to find open and closing braces -func indexOpenCloseTokens(open rune, close rune, content string) (start int, end int) { - ct := 0 - start = -1 - - for idx := 0; idx < len(content); { - rn, rnWidth := utf8.DecodeRuneInString(content[idx:]) - - // keep track of opening brackets to account for nesting - if rn == open { - ct++ - if start < 0 { // start index still -1, record the first opening bracket - start = idx - } - } - - // closing brackets decrement nest level - if rn == close { - ct-- - if ct == 0 { // bracket count back to 0, record the final closing bracket - return start, idx - } - } - - idx += rnWidth - nextRn, nextRnWidth := utf8.DecodeRuneInString(content[idx:]) - - // match " and advance idx to closing " - if rn == '"' { - for idx < len(content)-1 { - rn1, w1 := utf8.DecodeRuneInString(content[idx:]) - rn2, w2 := utf8.DecodeRuneInString(content[idx+w1:]) - - if rn1 == '\\' && rn2 == '"' { - idx += w1 + w2 - continue - } - - idx += w1 - if rn1 == '"' { - break - } - } - continue - } - - // match '#' and advance idx to line end - if rn == '#' { - for idx < len(content) { - rn1, w1 := utf8.DecodeRuneInString(content[idx:]) - idx += w1 - - if rn1 == '\n' { - break - } - } - continue - } - - // match '//' and advance idx to line end - if rn == '/' && nextRn == '/' { - idx += nextRnWidth - for idx < len(content) { - rn1, w1 := utf8.DecodeRuneInString(content[idx:]) - if rn1 == '\n' { - break - } - idx += w1 - } - continue - } - - // match '/*' and advance idx to closing '*/' - if rn == '/' && nextRn == '*' { - idx += nextRnWidth - for idx < len(content)-1 { - rn1, w1 := utf8.DecodeRuneInString(content[idx:]) - rn2, w2 := utf8.DecodeRuneInString(content[idx+w1:]) - idx += w1 - if rn1 == '*' && rn2 == '/' { - idx += w2 - break - } - } - continue - } - - // match '${' and advance idx to closing '}' - if rn == '$' && nextRn == '{' { - idx += rnWidth + nextRnWidth - for idx < len(content)-1 { - rn1, w1 := utf8.DecodeRuneInString(content[idx:]) - idx += w1 - if rn1 == '}' { - break - } - } - continue - } - } - - return start, -1 -} diff --git a/cmd/migration-tool/transforms_test.go b/cmd/migration-tool/transforms_test.go deleted file mode 100644 index b4179a731..000000000 --- a/cmd/migration-tool/transforms_test.go +++ /dev/null @@ -1,309 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestMigrationReplaceTemplateTokens_basic(t *testing.T) { - const original = ` -resource "metal_port" "bond0" { - port_id = local.bond0_id - layer2 = false - bonded = true - vlan_ids = [metal_vlan.test.id] -} - -resource "metal_vlan" "test" { - description = "test" - metro = "sv" - project = metal_project.test.id -} -` - - const expected = ` -resource "equinix_metal_port" "bond0" { - port_id = local.bond0_id - layer2 = false - bonded = true - vlan_ids = [equinix_metal_vlan.test.id] -} - -resource "equinix_metal_vlan" "test" { - description = "test" - metro = "sv" - project = equinix_metal_project.test.id -} -` - - var actual strings.Builder - scanner := bufio.NewScanner(strings.NewReader(original)) - for scanner.Scan() { - str := scanner.Text() - str = replaceTemplateTokens(str) - actual.WriteString(fmt.Sprintf("%s\n", str)) - } - - assert.Equal(t, expected, actual.String(), "Result matches expected result") -} - -func TestMigrationReplaceTemplateTokens_multiMatchPerLine(t *testing.T) { - const original = ` -terraform { - required_providers { - packet = { - source = "packethost/packet" - version = "1.0.0" - } - } -} - -# Configure Packet Provider. -provider "packet" { - auth_token = var.auth_token -} - -data "packet_project" "test" { - name = var.packet_project_name -} - -resource "packet_connection" "test" { - name = var.packet_connection_name - organization_id = data.packet_project.test.organization_id - project_id = data.packet_project.test.project_id - metro = var.packet_connection_metro - redundancy = var.packet_connection_redundancy - type = "shared" - description = var.packet_connection_description - tags = var.packet_connection_tags -} - -resource "packet_device" "test" { - count = 3 - - hostname = "tf.coreos2" - plan = "c3.small.x86" - metro = "sv" - operating_system = "ubuntu_20_04" - billing_cycle = "hourly" - project_id = local.project_id -} - -data "packet_device_bgp_neighbors" "test" { - device_id = packet_device.test[1].id -} - -locals { - address_family = "${lookup(data.packet_device_bgp_neighbors.test.bgp_neighbors[0], "address_family")}" - ips = tolist([packet_device.test[*].access_public_ipv4]) - ip_address = var.packet_network_type == "public" ? metal_device.foodevice.access_public_ipv4 : metal_device.foodevice.access_private_ipv4 -} -` - - const expected = ` -terraform { - required_providers { - packet = { - source = "packethost/packet" - version = "1.0.0" - } - } -} - -# Configure Packet Provider. -provider "packet" { - auth_token = var.auth_token -} - -data "equinix_metal_project" "test" { - name = var.packet_project_name -} - -resource "equinix_metal_connection" "test" { - name = var.packet_connection_name - organization_id = data.equinix_metal_project.test.organization_id - project_id = data.equinix_metal_project.test.project_id - metro = var.packet_connection_metro - redundancy = var.packet_connection_redundancy - type = "shared" - description = var.packet_connection_description - tags = var.packet_connection_tags -} - -resource "equinix_metal_device" "test" { - count = 3 - - hostname = "tf.coreos2" - plan = "c3.small.x86" - metro = "sv" - operating_system = "ubuntu_20_04" - billing_cycle = "hourly" - project_id = local.project_id -} - -data "equinix_metal_device_bgp_neighbors" "test" { - device_id = equinix_metal_device.test[1].id -} - -locals { - address_family = "${lookup(data.equinix_metal_device_bgp_neighbors.test.bgp_neighbors[0], "address_family")}" - ips = tolist([equinix_metal_device.test[*].access_public_ipv4]) - ip_address = var.packet_network_type == "public" ? equinix_metal_device.foodevice.access_public_ipv4 : equinix_metal_device.foodevice.access_private_ipv4 -} -` - - var actual strings.Builder - scanner := bufio.NewScanner(strings.NewReader(original)) - for scanner.Scan() { - str := scanner.Text() - str = replaceTemplateTokens(str) - actual.WriteString(fmt.Sprintf("%s\n", str)) - } - - assert.Equal(t, expected, actual.String(), "Result matches expected result") -} - -func TestMigrationBraceIndexing_basic(t *testing.T) { - const str = `{{}}` - expectStart := 0 - expectEnd := 3 - start, end := indexOpenCloseTokens('{', '}', str) - - if start != expectStart { - t.Errorf("expected %d, got %d\n", expectStart, start) - } - - if end != expectEnd { - t.Errorf("expected %d, got %d\n", expectEnd, end) - } -} - -func TestMigrationBraceIndexingWith_strings(t *testing.T) { - const str = ` " " { { } } ` - expectStart := 5 - expectEnd := 11 - start, end := indexOpenCloseTokens('{', '}', str) - - if start != expectStart { - t.Errorf("expected %d, got %d\n", expectStart, start) - } - - if end != expectEnd { - t.Errorf("expected %d, got %d\n", expectEnd, end) - } -} - -func TestMigrationBraceIndexing_Provider(t *testing.T) { - const str = `provider "metal" { - auth_token = "${var.auth_token}" -}` - - expectStart := 17 - expectEnd := len(str) - 1 - start, end := indexOpenCloseTokens('{', '}', str) - - if start != expectStart { - t.Errorf("expected %d, got %d\n", expectStart, start) - } - - if end != expectEnd { - t.Errorf("expected %d, got %d\n", expectEnd, end) - } -} - -func TestMigrationFindOpeningBrace(t *testing.T) { - const str = `}{}` - expect := 1 - start, _ := indexOpenCloseTokens('{', '}', str) - - if start != expect { - t.Errorf("expected %d, got %d\n", expect, start) - } -} - -func TestMigrationFindClosingBrace(t *testing.T) { - const str = `{}}` - expect := 1 - _, end := indexOpenCloseTokens('{', '}', str) - if end != expect { - t.Errorf("expected %d, got %d\n", expect, end) - } -} - -func TestMigrationMissingOpeningBrace(t *testing.T) { - const str = `}}` - expect := -1 - start, _ := indexOpenCloseTokens('{', '}', str) - - if start != expect { - t.Errorf("expected %d, got %d\n", expect, start) - } -} - -func TestMigrationMissingClosingBrace(t *testing.T) { - const str = `{{}` - expect := -1 - _, end := indexOpenCloseTokens('{', '}', str) - - if end != expect { - t.Errorf("expected %d, got %d\n", expect, end) - } -} - -func TestMigrationReplaceProvider(t *testing.T) { - // given - context := ` -provider "metal" { - auth_token = var.auth_token -}` - expected := ` -provider "equinix" { - auth_token = var.auth_token -}` - // when - result, _ := scanAndUpdateProvider(context) - - // then - assert.Equal(t, expected, result, "Result matches expected result") -} - -func TestMigrationReplaceRequiredProvider(t *testing.T) { - // given - context := ` -terraform { - required_providers { - metal = { - source = "equinix/metal" - #commment - version = "3.2.1" - } - foo = { - source = "foo/fooprovider" - version = "1.0.0" - } - } -}` - expected := ` -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - #commment - #version = "3.2.1" - } - foo = { - source = "foo/fooprovider" - version = "1.0.0" - } - } -}` - // when - result, _ := scanAndUpdateRequiredProvider(context) - - // then - assert.Equal(t, expected, result, "Result matches expected result") -} diff --git a/cmd/migration-tool/version.go b/cmd/migration-tool/version.go deleted file mode 100644 index 858312436..000000000 --- a/cmd/migration-tool/version.go +++ /dev/null @@ -1,4 +0,0 @@ -package main - -// Version is set at build-time in the release process -var Version = "dev" diff --git a/docs/guides/migration_guide_equinix_metal.md b/docs/guides/migration_guide_equinix_metal.md deleted file mode 100644 index 84307223a..000000000 --- a/docs/guides/migration_guide_equinix_metal.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -page_title: "Migrating from the Equinix Metal provider" ---- - -# Migrating from equinix/metal to equinix/equinix - -[Equinix Metal](https://metal.equinix.com/) (formerly Packet), has been fully integrated into Platform Equinix and therefore the terraform provider changes too. This (terraform-provider-equinix, provider equinix/equinix) is the current provider of the various services available on Platform Equinix that can be managed using Terraform. - -If you've been using terraform-provider-metal, and you want to use a newer provider version to manage resources in Equinix Metal, you will need to change the references in you HCL files. You can just change the names of the resources, e.g. from `metal_device` to `equinix_metal_device`. That should work, but it will cause the `metal_device` to be destroyed and new `equinix_metal_device` to be created instead. Re-creation of the resources might be undesirable, and this guide shows how to migrate to `equinix_metal_` resources without the re-creation. - -Before starting to migrate your Terraform templates, please upgrade - -* equinix/metal provider to the latest version (3.2.1) -* Terraform to version at least v0.13 - -## Fast migration with Equinix Migration Tool - -As part of the release v1.5.0, a migration tool has been supplied that will automatically update your `Metal` terraform plans and state files to work with the unified `Equinix` provider. The latest version of the `Equinix Migration Tool` can be found [here](https://github.com/equinix/terraform-provider-equinix/releases/latest). You will need to download the zip file corresponding to your operating system from the `Assets` section. Once unzipped you will find a folder `equinix-migration-tool` with the tool binary. Please, refer to the migration tool [readme file](https://github.com/equinix/terraform-provider-equinix/tree/main/cmd/migration-tool#readme) for further details. - -Once downloaded, you will need to unzip the file and use the binary corresponding to your OS. Running this tool against your terraform project directory will: - -* Transform all the "metal_" resource and datasource definitions and references to the "equinix_" name. -* Transform all "metal" references in .tfstate file to the "equinix" provider name. -* Transform the "metal" provider block and the "required_providers" section to match the latest "equinix" provider version. - -Alternatively, you can make the changes manually following one of the options described below. - -## Fast migration with replace-provider and sed - -Just like the Terraform HCL templates, the Terraform state is a file containing resource names and their attributes in structured text. We can attempt the migration as a text substitution task, basically replacing `metal_` with `equinix_metal_` wherever possible, and fixing the provider source reference. - -It's a good idea to make a backup of the whole Terraform directory before doing this. - -Considering we have infrastructure created from following template: - -```hcl-terraform -terraform { - required_providers { - metal = { - source = "equinix/metal" - } - } -} - -resource "metal_project" "example" { - name = "example" -} - -resource "metal_vlan" "example" { - project_id = metal_project.example.id - facility = "sv15" - description = "example" -} -``` - -We can first change the provider in Terraform state file (`terraform.tfstate`) with `terraform state` subcommand `replace-provider`: - -```shell -terraform state replace-provider equinix/metal equinix/equinix -``` - -Then we replace the provider reference in the HCL templates. Do this for every file where you have the reference: - -```shell -sed -i 's|equinix/metal|equinix/equinix|g' main.tf -``` - -Then we simply replace all strings `metal_` with `equinix_metal_` in the Terraform HCL files. - -```shell -sed -i 's/metal_/equinix_metal_/g' main.tf -``` - -..this is a bit dangerous, so check your `git diff` after. It should replace all the `metal_` prefixes and also the key from the `required_providers` block. - -Then replace `metal_` with `equinix_metal_` in the terraform state file: - -```shell -sed -i 's/metal_/equinix_metal_/g' terraform.tfstate -``` - -The example template would now look as: - -```hcl-terraform -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - } - } -} - -resource "equinix_metal_project" "example" { - name = "example" -} - -resource "equinix_metal_vlan" "example" { - project_id = equinix_metal_project.example.id - facility = "sv15" - description = "example" -} -``` - -We then need to install the `equinix/equinix` provider by running `terraform init`. After that, our templates should be in check with the Terraform state and with the upstream resources in Equinix Metal. You can verify the result by running `terraform plan`. - -If the plan is not empty, it means that some resources can not be simply read fom upstream, or that attributes have changed between your version of the `equinix/metal` provider and the current version of the `equinix/equinix` provider. - -## Migrating one resource at a time - -We can use `terraform state` and `terraform import` to achieve transition without destroying existing resources. - -### Existing infrastructure - -We assume to have infrastructure created with provider `equinix/metal` with a device and an IP reservation. The HCL looks like: - -```hcl -terraform { - required_providers { - metal = { - source = "equinix/metal" - version = "3.2.1" - } - } -} - -resource "metal_reserved_ip_block" "example" { - project_id = local.project_id - facility = "sv15" - quantity = 2 -} - -resource "metal_device" "example" { - project_id = local.project_id - facilities = ["sv15"] - plan = "c3.medium.x86" - operating_system = "ubuntu_20_04" - hostname = "test" - billing_cycle = "hourly" - - ip_address { - type = "public_ipv4" - cidr = 31 - reservation_ids = [metal_reserved_ip_block.example.id] - } - - ip_address { - type = "private_ipv4" - } -} -``` - -### Resource UUIDs - -In order to transition to provider `equinix/equinix`, we need to find out UUIDs of all the resources we want to migrate. In this case `metal_reserved_ip_block.example` and `metal_device.example`. We can use `terraform state` to find out the UUIDs. - -For the reserved IP block: - -```shell -$ terraform state show metal_reserved_ip_block.example - -# metal_reserved_ip_block.example: -resource "metal_reserved_ip_block" "example" { - [...] - id = "e689072f-aa6e-4d51-8e37-c2fbe18b4ff0" - [...] -} -``` - -For the device: - -```shell -$ terraform state show metal_device.example - -# metal_device.example -resource "metal_device" "example" { - [...] - id = "8eb3bc10-0e1a-476a-aec2-6dc699df9c1c" - [...] - -``` - -### Migrated template - -Once we find out the UUIDs of resources to migrate, in the HCL template, we need to change: - -* the required_providers block to require `equinix/equinix` -* the names of the resources to corresponding resources from provider `equinix/equinix`: `sed 's/metal_/equinix_metal_'` -* all the references from `metal_` resources to `equinix_metal_` resources - -The modified template will then look as: - -```hcl -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - } - } -} - -resource "equinix_metal_reserved_ip_block" "example" { - project_id = local.project_id - facility = "sv15" - quantity = 2 -} - -resource "equinix_metal_device" "example" { - project_id = local.project_id - facilities = ["sv15"] - plan = "c3.medium.x86" - operating_system = "ubuntu_20_04" - hostname = "test" - billing_cycle = "hourly" - - ip_address { - type = "public_ipv4" - cidr = 31 - reservation_ids = [equinix_metal_reserved_ip_block.example.id] - } - - ip_address { - type = "private_ipv4" - } -} -``` - -### Migrating Terraform state - -Once we changed the template accordingly, we can remove the old `metal_` resources from Terraform state and import the new ones as `equinix_metal_` resources by their UUIDs. - -From checking the state before, we remember that UUID of the metal_device.example is 8eb3bc10-0e1a-476a-aec2-6dc699df9c1c, and UUID of the metal_reserved_ip_block.example is e689072f-aa6e-4d51-8e37-c2fbe18b4ff0. - - In the terraform state and import commands, we use the resource type and name, separated by dot: - -```shell -terraform state rm metal_reserved_ip_block.example -terraform import equinix_metal_reserved_ip_block.example e689072f-aa6e-4d51-8e37-c2fbe18b4ff0 -terraform state rm metal_device.example -terraform import equinix_metal_device.example 8eb3bc10-0e1a-476a-aec2-6dc699df9c1c -``` - -We then need to install the `equinix/equinix` provider by running `terraform init`. After that, our templates should be in check with the Terraform state and with the upstream resources in Equinix Metal. We can verify the migration by running `terraform plan`, it should show that infrastructure is up to date. - -## Resolving migration issues - -When we run `terraform plan` to verify that migration was successful, terraform might warn that some resource attributes from templates are not aligned with imported state. It's because not all of the resource attribute can be computed, for example the `ip_address` blocks in `metal_device` are user-defined and will result to a non-empty diff against downloaded imported state. - -In case of the `ip_address`, a consequent `terraform apply` will update the local state without changing the upstream resource, but if an attribute causes an upstream update, you will need to resolve it manually, either changing your template, or letting Terraform change the resource upstream. diff --git a/docs/guides/migration_guide_packet.md b/docs/guides/migration_guide_packet.md deleted file mode 100644 index fef8d0e3c..000000000 --- a/docs/guides/migration_guide_packet.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -page_title: "Migrating from the Packet provider" ---- - -# Migrating from packethost/packet to equinix/equinix - -[Equinix Metal](https://metal.equinix.com/) (formerly Packet), has been fully integrated into Platform Equinix and therefore the terraform provider changes too. This (terraform-provider-equinix, provider equinix/equinix) is the current provider of the various services available on Platform Equinix that can be managed using Terraform. - -If you've been using terraform-provider-packet, and you want to use a newer provider version to manage resources in Equinix Metal, you will need to change the references in you HCL files. You can just change the names of the resources, e.g. from `packet_device` to `equinix_metal_device`. That should work, but it will cause the `packet_device` to be destroyed and new `equinix_metal_device` to be created instead. Re-creation of the resources might be undesirable, and this guide shows how to migrate to `equinix_metal_` resources without the re-creation. - -Before starting to migrate your Terraform templates, please upgrade - -* packethost/packet provider to the latest version (3.2.1) -* Terraform to version at least v0.13 - -## Fast migration with Equinix Migration Tool - -As part of the release v1.5.0, a migration tool has been supplied that will automatically update your `Packet` terraform plans and state files to work with the unified `Equinix` provider. The latest version of the `Equinix Migration Tool` can be found [here](https://github.com/equinix/terraform-provider-equinix/releases/tag/v1.5.0). You will need to download the zip file corresponding to your operating system from the `Assets` section. Once unzipped you will find a folder `equinix-migration-tool` with the tool binary. Please, refer to the migration tool [readme file](https://github.com/equinix/terraform-provider-equinix/tree/v1.5.0/cmd/migration-tool#readme) for further details. - -Once downloaded, you will need to unzip the file and use the binary corresponding to your OS. Running this tool against your terraform project directory will: - -* Transform all the "packet_" resource and datasource definitions and references to the "equinix_" name. -* Transform all "packet" references in .tfstate file to the "equinix" provider name. -* Transform the "packet" provider block and the "required_providers" section to match the latest "equinix" provider version. - -Alternatively, you can make the changes manually following one of the options described below. - -## Fast migration with replace-provider and sed - -Just like the Terraform HCL templates, the Terraform state is a file containing resource names and their attributes in structured text. We can attempt the migration as a text substitution task, basically replacing `packet_` with `equinix_metal_` wherever possible, and fixing the provider source reference. - -It's a good idea to make a backup of the whole Terraform directory before doing this. - -Considering we have infrastructure created from following template: - -```hcl-terraform -terraform { - required_providers { - packet = { - source = "packethost/packet" - } - } -} - -resource "packet_project" "example" { - name = "example" -} - -resource "packet_vlan" "example" { - project_id = packet_project.example.id - facility = "sv15" - description = "example" -} -``` - -We can first change the provider in Terraform state file (`terraform.tfstate`) with `terraform state` subcommand `replace-provider`: - -```shell -terraform state replace-provider packethost/packet equinix/equinix -``` - -Then we replace the provider reference in the HCL templates. Do this for every file where you have the reference: - -```shell -sed -i 's|packethost/packet|equinix/equinix|g' main.tf -``` - -Then we simply replace all strings `packet_` with `equinix_metal_` in the Terraform HCL files. - -```shell -sed -i 's/packet_/equinix_metal_/g' main.tf -``` - -..this is a bit dangerous, so check your `git diff` after. It should replace all the `packet_` prefixes and also the key from the `required_providers` block. - -Then replace `packet_` with `equinix_metal_` in the terraform state file: - -```shell -sed -i 's/packet_/equinix_metal_/g' terraform.tfstate -``` - -The example template would now look as: - -```hcl-terraform -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - } - } -} - -resource "equinix_metal_project" "example" { - name = "example" -} - -resource "equinix_metal_vlan" "example" { - project_id = equinix_metal_project.example.id - facility = "sv15" - description = "example" -} -``` - -We then need to install the `equinix/equinix` provider by running `terraform init`. After that, our templates should be in check with the Terraform state and with the upstream resources in Equinix Metal. You can verify the result by running `terraform plan`. - -If the plan is not empty, it means that some resources can not be simply read fom upstream, or that attributes have changed between your version of the `packethost/packet` provider and the current version of the `equinix/equinix` provider. - -## Migrating one resource at a time - -We can use `terraform state` and `terraform import` to achieve transition without destroying existing resources. - -### Existing infrastructure - -We assume to have infrastructure created with provider `packethost/packet` with a device and an IP reservation. The HCL looks like: - -```hcl -terraform { - required_providers { - packet = { - source = "packethost/packet" - version = "3.2.1" - } - } -} - -resource "packet_reserved_ip_block" "example" { - project_id = local.project_id - facility = "sv15" - quantity = 2 -} - -resource "packet_device" "example" { - project_id = local.project_id - facilities = ["sv15"] - plan = "c3.medium.x86" - operating_system = "ubuntu_20_04" - hostname = "test" - billing_cycle = "hourly" - - ip_address { - type = "public_ipv4" - cidr = 31 - reservation_ids = [packet_reserved_ip_block.example.id] - } - - ip_address { - type = "private_ipv4" - } -} -``` - -### Resource UUIDs - -In order to transition to provider `equinix/equinix`, we need to find out UUIDs of all the resources we want to migrate. In this case `packet_reserved_ip_block.example` and `packet_device.example`. We can use `terraform state` to find out the UUIDs. - -For the reserved IP block: - -```shell -$ terraform state show packet_reserved_ip_block.example - -# packet_reserved_ip_block.example: -resource "packet_reserved_ip_block" "example" { - [...] - id = "e689072f-aa6e-4d51-8e37-c2fbe18b4ff0" - [...] -} -``` - -For the device: - -```shell -$ terraform state show packet_device.example - -# packet_device.example -resource "packet_device" "example" { - [...] - id = "8eb3bc10-0e1a-476a-aec2-6dc699df9c1c" - [...] - -``` - -### Migrated template - -Once we find out the UUIDs of resources to migrate, in the HCL template, we need to change: - -* the required_providers block to require `equinix/equinix` -* the names of the resources to corresponding resources from provider `equinix/equinix`: `sed 's/packet_/equinix_metal_'` -* all the references from `packet_` resources to `equinix_metal_` resources - -The modified template will then look as: - -```hcl -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - } - } -} - -resource "equinix_metal_reserved_ip_block" "example" { - project_id = local.project_id - facility = "sv15" - quantity = 2 -} - -resource "equinix_metal_device" "example" { - project_id = local.project_id - facilities = ["sv15"] - plan = "c3.medium.x86" - operating_system = "ubuntu_20_04" - hostname = "test" - billing_cycle = "hourly" - - ip_address { - type = "public_ipv4" - cidr = 31 - reservation_ids = [equinix_metal_reserved_ip_block.example.id] - } - - ip_address { - type = "private_ipv4" - } -} -``` - -### Migrating Terraform state - -Once we changed the template accordingly, we can remove the old `packet_` resources from Terraform state and import the new ones as `equinix_metal_` resources by their UUIDs. - -From checking the state before, we remember that UUID of the packet_device.example is 8eb3bc10-0e1a-476a-aec2-6dc699df9c1c, and UUID of the packet_reserved_ip_block.example is e689072f-aa6e-4d51-8e37-c2fbe18b4ff0. - - In the terraform state and import commands, we use the resource type and name, separated by dot: - -```shell -terraform state rm packet_reserved_ip_block.example -terraform import equinix_metal_reserved_ip_block.example e689072f-aa6e-4d51-8e37-c2fbe18b4ff0 -terraform state rm packet_device.example -terraform import equinix_metal_device.example 8eb3bc10-0e1a-476a-aec2-6dc699df9c1c -``` - -We then need to install the `equinix/equinix` provider by running `terraform init`. After that, our templates should be in check with the Terraform state and with the upstream resources in Equinix Metal. We can verify the migration by running `terraform plan`, it should show that infrastructure is up to date. - -## Resolving migration issues - -When we run `terraform plan` to verify that migration was successful, terraform might warn that some resource attributes from templates are not aligned with imported state. It's because not all of the resource attribute can be computed, for example the `ip_address` blocks in `packet_device` are user-defined and will result to a non-empty diff against downloaded imported state. - -In case of the `ip_address`, a consequent `terraform apply` will update the local state without changing the upstream resource, but if an attribute causes an upstream update, you will need to resolve it manually, either changing your template, or letting Terraform change the resource upstream. diff --git a/templates/guides/migration_guide_equinix_metal.md b/templates/guides/migration_guide_equinix_metal.md deleted file mode 100644 index 84307223a..000000000 --- a/templates/guides/migration_guide_equinix_metal.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -page_title: "Migrating from the Equinix Metal provider" ---- - -# Migrating from equinix/metal to equinix/equinix - -[Equinix Metal](https://metal.equinix.com/) (formerly Packet), has been fully integrated into Platform Equinix and therefore the terraform provider changes too. This (terraform-provider-equinix, provider equinix/equinix) is the current provider of the various services available on Platform Equinix that can be managed using Terraform. - -If you've been using terraform-provider-metal, and you want to use a newer provider version to manage resources in Equinix Metal, you will need to change the references in you HCL files. You can just change the names of the resources, e.g. from `metal_device` to `equinix_metal_device`. That should work, but it will cause the `metal_device` to be destroyed and new `equinix_metal_device` to be created instead. Re-creation of the resources might be undesirable, and this guide shows how to migrate to `equinix_metal_` resources without the re-creation. - -Before starting to migrate your Terraform templates, please upgrade - -* equinix/metal provider to the latest version (3.2.1) -* Terraform to version at least v0.13 - -## Fast migration with Equinix Migration Tool - -As part of the release v1.5.0, a migration tool has been supplied that will automatically update your `Metal` terraform plans and state files to work with the unified `Equinix` provider. The latest version of the `Equinix Migration Tool` can be found [here](https://github.com/equinix/terraform-provider-equinix/releases/latest). You will need to download the zip file corresponding to your operating system from the `Assets` section. Once unzipped you will find a folder `equinix-migration-tool` with the tool binary. Please, refer to the migration tool [readme file](https://github.com/equinix/terraform-provider-equinix/tree/main/cmd/migration-tool#readme) for further details. - -Once downloaded, you will need to unzip the file and use the binary corresponding to your OS. Running this tool against your terraform project directory will: - -* Transform all the "metal_" resource and datasource definitions and references to the "equinix_" name. -* Transform all "metal" references in .tfstate file to the "equinix" provider name. -* Transform the "metal" provider block and the "required_providers" section to match the latest "equinix" provider version. - -Alternatively, you can make the changes manually following one of the options described below. - -## Fast migration with replace-provider and sed - -Just like the Terraform HCL templates, the Terraform state is a file containing resource names and their attributes in structured text. We can attempt the migration as a text substitution task, basically replacing `metal_` with `equinix_metal_` wherever possible, and fixing the provider source reference. - -It's a good idea to make a backup of the whole Terraform directory before doing this. - -Considering we have infrastructure created from following template: - -```hcl-terraform -terraform { - required_providers { - metal = { - source = "equinix/metal" - } - } -} - -resource "metal_project" "example" { - name = "example" -} - -resource "metal_vlan" "example" { - project_id = metal_project.example.id - facility = "sv15" - description = "example" -} -``` - -We can first change the provider in Terraform state file (`terraform.tfstate`) with `terraform state` subcommand `replace-provider`: - -```shell -terraform state replace-provider equinix/metal equinix/equinix -``` - -Then we replace the provider reference in the HCL templates. Do this for every file where you have the reference: - -```shell -sed -i 's|equinix/metal|equinix/equinix|g' main.tf -``` - -Then we simply replace all strings `metal_` with `equinix_metal_` in the Terraform HCL files. - -```shell -sed -i 's/metal_/equinix_metal_/g' main.tf -``` - -..this is a bit dangerous, so check your `git diff` after. It should replace all the `metal_` prefixes and also the key from the `required_providers` block. - -Then replace `metal_` with `equinix_metal_` in the terraform state file: - -```shell -sed -i 's/metal_/equinix_metal_/g' terraform.tfstate -``` - -The example template would now look as: - -```hcl-terraform -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - } - } -} - -resource "equinix_metal_project" "example" { - name = "example" -} - -resource "equinix_metal_vlan" "example" { - project_id = equinix_metal_project.example.id - facility = "sv15" - description = "example" -} -``` - -We then need to install the `equinix/equinix` provider by running `terraform init`. After that, our templates should be in check with the Terraform state and with the upstream resources in Equinix Metal. You can verify the result by running `terraform plan`. - -If the plan is not empty, it means that some resources can not be simply read fom upstream, or that attributes have changed between your version of the `equinix/metal` provider and the current version of the `equinix/equinix` provider. - -## Migrating one resource at a time - -We can use `terraform state` and `terraform import` to achieve transition without destroying existing resources. - -### Existing infrastructure - -We assume to have infrastructure created with provider `equinix/metal` with a device and an IP reservation. The HCL looks like: - -```hcl -terraform { - required_providers { - metal = { - source = "equinix/metal" - version = "3.2.1" - } - } -} - -resource "metal_reserved_ip_block" "example" { - project_id = local.project_id - facility = "sv15" - quantity = 2 -} - -resource "metal_device" "example" { - project_id = local.project_id - facilities = ["sv15"] - plan = "c3.medium.x86" - operating_system = "ubuntu_20_04" - hostname = "test" - billing_cycle = "hourly" - - ip_address { - type = "public_ipv4" - cidr = 31 - reservation_ids = [metal_reserved_ip_block.example.id] - } - - ip_address { - type = "private_ipv4" - } -} -``` - -### Resource UUIDs - -In order to transition to provider `equinix/equinix`, we need to find out UUIDs of all the resources we want to migrate. In this case `metal_reserved_ip_block.example` and `metal_device.example`. We can use `terraform state` to find out the UUIDs. - -For the reserved IP block: - -```shell -$ terraform state show metal_reserved_ip_block.example - -# metal_reserved_ip_block.example: -resource "metal_reserved_ip_block" "example" { - [...] - id = "e689072f-aa6e-4d51-8e37-c2fbe18b4ff0" - [...] -} -``` - -For the device: - -```shell -$ terraform state show metal_device.example - -# metal_device.example -resource "metal_device" "example" { - [...] - id = "8eb3bc10-0e1a-476a-aec2-6dc699df9c1c" - [...] - -``` - -### Migrated template - -Once we find out the UUIDs of resources to migrate, in the HCL template, we need to change: - -* the required_providers block to require `equinix/equinix` -* the names of the resources to corresponding resources from provider `equinix/equinix`: `sed 's/metal_/equinix_metal_'` -* all the references from `metal_` resources to `equinix_metal_` resources - -The modified template will then look as: - -```hcl -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - } - } -} - -resource "equinix_metal_reserved_ip_block" "example" { - project_id = local.project_id - facility = "sv15" - quantity = 2 -} - -resource "equinix_metal_device" "example" { - project_id = local.project_id - facilities = ["sv15"] - plan = "c3.medium.x86" - operating_system = "ubuntu_20_04" - hostname = "test" - billing_cycle = "hourly" - - ip_address { - type = "public_ipv4" - cidr = 31 - reservation_ids = [equinix_metal_reserved_ip_block.example.id] - } - - ip_address { - type = "private_ipv4" - } -} -``` - -### Migrating Terraform state - -Once we changed the template accordingly, we can remove the old `metal_` resources from Terraform state and import the new ones as `equinix_metal_` resources by their UUIDs. - -From checking the state before, we remember that UUID of the metal_device.example is 8eb3bc10-0e1a-476a-aec2-6dc699df9c1c, and UUID of the metal_reserved_ip_block.example is e689072f-aa6e-4d51-8e37-c2fbe18b4ff0. - - In the terraform state and import commands, we use the resource type and name, separated by dot: - -```shell -terraform state rm metal_reserved_ip_block.example -terraform import equinix_metal_reserved_ip_block.example e689072f-aa6e-4d51-8e37-c2fbe18b4ff0 -terraform state rm metal_device.example -terraform import equinix_metal_device.example 8eb3bc10-0e1a-476a-aec2-6dc699df9c1c -``` - -We then need to install the `equinix/equinix` provider by running `terraform init`. After that, our templates should be in check with the Terraform state and with the upstream resources in Equinix Metal. We can verify the migration by running `terraform plan`, it should show that infrastructure is up to date. - -## Resolving migration issues - -When we run `terraform plan` to verify that migration was successful, terraform might warn that some resource attributes from templates are not aligned with imported state. It's because not all of the resource attribute can be computed, for example the `ip_address` blocks in `metal_device` are user-defined and will result to a non-empty diff against downloaded imported state. - -In case of the `ip_address`, a consequent `terraform apply` will update the local state without changing the upstream resource, but if an attribute causes an upstream update, you will need to resolve it manually, either changing your template, or letting Terraform change the resource upstream. diff --git a/templates/guides/migration_guide_packet.md b/templates/guides/migration_guide_packet.md deleted file mode 100644 index fef8d0e3c..000000000 --- a/templates/guides/migration_guide_packet.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -page_title: "Migrating from the Packet provider" ---- - -# Migrating from packethost/packet to equinix/equinix - -[Equinix Metal](https://metal.equinix.com/) (formerly Packet), has been fully integrated into Platform Equinix and therefore the terraform provider changes too. This (terraform-provider-equinix, provider equinix/equinix) is the current provider of the various services available on Platform Equinix that can be managed using Terraform. - -If you've been using terraform-provider-packet, and you want to use a newer provider version to manage resources in Equinix Metal, you will need to change the references in you HCL files. You can just change the names of the resources, e.g. from `packet_device` to `equinix_metal_device`. That should work, but it will cause the `packet_device` to be destroyed and new `equinix_metal_device` to be created instead. Re-creation of the resources might be undesirable, and this guide shows how to migrate to `equinix_metal_` resources without the re-creation. - -Before starting to migrate your Terraform templates, please upgrade - -* packethost/packet provider to the latest version (3.2.1) -* Terraform to version at least v0.13 - -## Fast migration with Equinix Migration Tool - -As part of the release v1.5.0, a migration tool has been supplied that will automatically update your `Packet` terraform plans and state files to work with the unified `Equinix` provider. The latest version of the `Equinix Migration Tool` can be found [here](https://github.com/equinix/terraform-provider-equinix/releases/tag/v1.5.0). You will need to download the zip file corresponding to your operating system from the `Assets` section. Once unzipped you will find a folder `equinix-migration-tool` with the tool binary. Please, refer to the migration tool [readme file](https://github.com/equinix/terraform-provider-equinix/tree/v1.5.0/cmd/migration-tool#readme) for further details. - -Once downloaded, you will need to unzip the file and use the binary corresponding to your OS. Running this tool against your terraform project directory will: - -* Transform all the "packet_" resource and datasource definitions and references to the "equinix_" name. -* Transform all "packet" references in .tfstate file to the "equinix" provider name. -* Transform the "packet" provider block and the "required_providers" section to match the latest "equinix" provider version. - -Alternatively, you can make the changes manually following one of the options described below. - -## Fast migration with replace-provider and sed - -Just like the Terraform HCL templates, the Terraform state is a file containing resource names and their attributes in structured text. We can attempt the migration as a text substitution task, basically replacing `packet_` with `equinix_metal_` wherever possible, and fixing the provider source reference. - -It's a good idea to make a backup of the whole Terraform directory before doing this. - -Considering we have infrastructure created from following template: - -```hcl-terraform -terraform { - required_providers { - packet = { - source = "packethost/packet" - } - } -} - -resource "packet_project" "example" { - name = "example" -} - -resource "packet_vlan" "example" { - project_id = packet_project.example.id - facility = "sv15" - description = "example" -} -``` - -We can first change the provider in Terraform state file (`terraform.tfstate`) with `terraform state` subcommand `replace-provider`: - -```shell -terraform state replace-provider packethost/packet equinix/equinix -``` - -Then we replace the provider reference in the HCL templates. Do this for every file where you have the reference: - -```shell -sed -i 's|packethost/packet|equinix/equinix|g' main.tf -``` - -Then we simply replace all strings `packet_` with `equinix_metal_` in the Terraform HCL files. - -```shell -sed -i 's/packet_/equinix_metal_/g' main.tf -``` - -..this is a bit dangerous, so check your `git diff` after. It should replace all the `packet_` prefixes and also the key from the `required_providers` block. - -Then replace `packet_` with `equinix_metal_` in the terraform state file: - -```shell -sed -i 's/packet_/equinix_metal_/g' terraform.tfstate -``` - -The example template would now look as: - -```hcl-terraform -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - } - } -} - -resource "equinix_metal_project" "example" { - name = "example" -} - -resource "equinix_metal_vlan" "example" { - project_id = equinix_metal_project.example.id - facility = "sv15" - description = "example" -} -``` - -We then need to install the `equinix/equinix` provider by running `terraform init`. After that, our templates should be in check with the Terraform state and with the upstream resources in Equinix Metal. You can verify the result by running `terraform plan`. - -If the plan is not empty, it means that some resources can not be simply read fom upstream, or that attributes have changed between your version of the `packethost/packet` provider and the current version of the `equinix/equinix` provider. - -## Migrating one resource at a time - -We can use `terraform state` and `terraform import` to achieve transition without destroying existing resources. - -### Existing infrastructure - -We assume to have infrastructure created with provider `packethost/packet` with a device and an IP reservation. The HCL looks like: - -```hcl -terraform { - required_providers { - packet = { - source = "packethost/packet" - version = "3.2.1" - } - } -} - -resource "packet_reserved_ip_block" "example" { - project_id = local.project_id - facility = "sv15" - quantity = 2 -} - -resource "packet_device" "example" { - project_id = local.project_id - facilities = ["sv15"] - plan = "c3.medium.x86" - operating_system = "ubuntu_20_04" - hostname = "test" - billing_cycle = "hourly" - - ip_address { - type = "public_ipv4" - cidr = 31 - reservation_ids = [packet_reserved_ip_block.example.id] - } - - ip_address { - type = "private_ipv4" - } -} -``` - -### Resource UUIDs - -In order to transition to provider `equinix/equinix`, we need to find out UUIDs of all the resources we want to migrate. In this case `packet_reserved_ip_block.example` and `packet_device.example`. We can use `terraform state` to find out the UUIDs. - -For the reserved IP block: - -```shell -$ terraform state show packet_reserved_ip_block.example - -# packet_reserved_ip_block.example: -resource "packet_reserved_ip_block" "example" { - [...] - id = "e689072f-aa6e-4d51-8e37-c2fbe18b4ff0" - [...] -} -``` - -For the device: - -```shell -$ terraform state show packet_device.example - -# packet_device.example -resource "packet_device" "example" { - [...] - id = "8eb3bc10-0e1a-476a-aec2-6dc699df9c1c" - [...] - -``` - -### Migrated template - -Once we find out the UUIDs of resources to migrate, in the HCL template, we need to change: - -* the required_providers block to require `equinix/equinix` -* the names of the resources to corresponding resources from provider `equinix/equinix`: `sed 's/packet_/equinix_metal_'` -* all the references from `packet_` resources to `equinix_metal_` resources - -The modified template will then look as: - -```hcl -terraform { - required_providers { - equinix = { - source = "equinix/equinix" - } - } -} - -resource "equinix_metal_reserved_ip_block" "example" { - project_id = local.project_id - facility = "sv15" - quantity = 2 -} - -resource "equinix_metal_device" "example" { - project_id = local.project_id - facilities = ["sv15"] - plan = "c3.medium.x86" - operating_system = "ubuntu_20_04" - hostname = "test" - billing_cycle = "hourly" - - ip_address { - type = "public_ipv4" - cidr = 31 - reservation_ids = [equinix_metal_reserved_ip_block.example.id] - } - - ip_address { - type = "private_ipv4" - } -} -``` - -### Migrating Terraform state - -Once we changed the template accordingly, we can remove the old `packet_` resources from Terraform state and import the new ones as `equinix_metal_` resources by their UUIDs. - -From checking the state before, we remember that UUID of the packet_device.example is 8eb3bc10-0e1a-476a-aec2-6dc699df9c1c, and UUID of the packet_reserved_ip_block.example is e689072f-aa6e-4d51-8e37-c2fbe18b4ff0. - - In the terraform state and import commands, we use the resource type and name, separated by dot: - -```shell -terraform state rm packet_reserved_ip_block.example -terraform import equinix_metal_reserved_ip_block.example e689072f-aa6e-4d51-8e37-c2fbe18b4ff0 -terraform state rm packet_device.example -terraform import equinix_metal_device.example 8eb3bc10-0e1a-476a-aec2-6dc699df9c1c -``` - -We then need to install the `equinix/equinix` provider by running `terraform init`. After that, our templates should be in check with the Terraform state and with the upstream resources in Equinix Metal. We can verify the migration by running `terraform plan`, it should show that infrastructure is up to date. - -## Resolving migration issues - -When we run `terraform plan` to verify that migration was successful, terraform might warn that some resource attributes from templates are not aligned with imported state. It's because not all of the resource attribute can be computed, for example the `ip_address` blocks in `packet_device` are user-defined and will result to a non-empty diff against downloaded imported state. - -In case of the `ip_address`, a consequent `terraform apply` will update the local state without changing the upstream resource, but if an attribute causes an upstream update, you will need to resolve it manually, either changing your template, or letting Terraform change the resource upstream.