Skip to content
This repository has been archived by the owner on May 14, 2022. It is now read-only.

Commit

Permalink
Merge pull request #6 from hashicorp/find-imported-packages-via-go-list
Browse files Browse the repository at this point in the history
Find imported packages and identifiers via go list
  • Loading branch information
kmoe authored Aug 29, 2019
2 parents af3309d + 429c383 commit 3156391
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 62 deletions.
41 changes: 32 additions & 9 deletions cmd/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,28 +121,29 @@ func (c *command) Run(args []string) int {
}

if !csv {
ui.Output("Checking whether provider uses packages removed from the new SDK...")
ui.Output("Checking whether provider uses deprecated SDK packages or identifiers...")
}
removedPackagesInUse, doesNotUseRemovedPackages, err := CheckSDKPackageImports(providerPath)
removedPackagesInUse, removedIdentsInUse, doesNotUseRemovedPackagesOrIdents, err := CheckSDKPackageImportsAndRefs(providerPath)
if !csv {
if err != nil {
log.Printf("Error determining use of removed SDK packages: %s", err)
log.Printf("Error determining use of deprecated SDK packages and identifiers: %s", err)
return 1
}
if doesNotUseRemovedPackages {
ui.Info("No imports of removed SDK packages: OK.")
if doesNotUseRemovedPackagesOrIdents {
ui.Info("No imports of deprecated SDK packages or identifiers: OK.")
} else {
ui.Warn(fmt.Sprintf("Removed SDK packages in use: %+v", removedPackagesInUse))
ui.Warn(fmt.Sprintf("Deprecated SDK packages in use: %+v", removedPackagesInUse))
ui.Warn(fmt.Sprintf("Deprecated SDK identifiers in use: %+v", removedIdentsInUse))
}
}
allConstraintsSatisfied := goVersionSatisfiesConstraint && providerUsesGoModules && SDKVersionSatisfiesConstraint && doesNotUseRemovedPackages
allConstraintsSatisfied := goVersionSatisfiesConstraint && providerUsesGoModules && SDKVersionSatisfiesConstraint && doesNotUseRemovedPackagesOrIdents
if csv {
ui.Output(fmt.Sprintf("go_version,go_version_satisfies_constraint,uses_go_modules,sdk_version,sdk_version_satisfies_constraint,does_not_use_removed_packages,all_constraints_satisfied\n%s,%t,%t,%s,%t,%t,%t", goVersion, goVersionSatisfiesConstraint, providerUsesGoModules, SDKVersion, SDKVersionSatisfiesConstraint, doesNotUseRemovedPackages, allConstraintsSatisfied))
ui.Output(fmt.Sprintf("go_version,go_version_satisfies_constraint,uses_go_modules,sdk_version,sdk_version_satisfies_constraint,does_not_use_removed_packages,all_constraints_satisfied\n%s,%t,%t,%s,%t,%t,%t", goVersion, goVersionSatisfiesConstraint, providerUsesGoModules, SDKVersion, SDKVersionSatisfiesConstraint, doesNotUseRemovedPackagesOrIdents, allConstraintsSatisfied))
} else {
if allConstraintsSatisfied {
ui.Info(fmt.Sprintf("\nAll constraints satisfied. Provider %s can be migrated to the new SDK.", providerPath))
return 0
} else if providerUsesGoModules && SDKVersionSatisfiesConstraint && doesNotUseRemovedPackages {
} else if providerUsesGoModules && SDKVersionSatisfiesConstraint && doesNotUseRemovedPackagesOrIdents {
ui.Info(fmt.Sprintf("\nProvider %s can be migrated to the new SDK, but Go version %s is recommended.", providerPath, goVersionConstraint))
return 0
}
Expand Down Expand Up @@ -202,3 +203,25 @@ func CheckProviderSDKVersion(providerPath string) (SDKVersion string, satisfiesC

return v.String(), c.Check(v), nil
}

func CheckSDKPackageImportsAndRefs(providerPath string) (removedPackagesInUse []string, removedIdentsInUse []string, doesNotUseRemovedPackagesOrIdents bool, e error) {
providerImportDetails, err := GoListPackageImports(providerPath)
if err != nil {
return nil, nil, false, err
}

removedPackagesInUse, err = CheckSDKPackageImports(providerImportDetails)
if err != nil {
return nil, nil, false, err
}

packageRefsOffences, err := CheckSDKPackageRefs(providerImportDetails)
if err != nil {
return nil, nil, false, err
}
for _, o := range packageRefsOffences {
removedIdentsInUse = append(removedIdentsInUse, fmt.Sprintf("Ident %v from package %v is used at %+v", o.IdentDeprecation.Identifier.Name, o.IdentDeprecation.ImportPath, o.Positions))
}

return removedPackagesInUse, removedIdentsInUse, len(removedPackagesInUse) == 0 && len(packageRefsOffences) == 0, nil
}
24 changes: 7 additions & 17 deletions cmd/check/sdk_imports.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package check

import (
"os"
"path/filepath"
"strings"

"github.com/hashicorp/tf-sdk-migrator/util"
)

const REMOVED_PACKAGES = `github.com/hashicorp/terraform/backend
Expand Down Expand Up @@ -80,22 +76,16 @@ github.com/hashicorp/terraform/tools/loggraphdiff
github.com/hashicorp/terraform/tools/terraform-bundle
github.com/hashicorp/terraform/tools/terraform-bundle/e2etest`

func CheckSDKPackageImports(providerPath string) (removedPackagesInUse []string, doesNotUseRemovedPackages bool, e error) {
func CheckSDKPackageImports(providerImportDetails *ProviderImportDetails) (removedPackagesInUse []string, e error) {

removedPackages := strings.Split(REMOVED_PACKAGES, "\n")
removedPackagesInUse = []string{}

filepath.Walk(providerPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() && info.Name() == "vendor" {
return filepath.SkipDir
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ".go") {
removedPackagesInUse = append(removedPackagesInUse, util.FindImportedPackages(path, removedPackages)...)
for _, p := range removedPackages {
if providerImportDetails.AllImportPathsHash[p] {
removedPackagesInUse = append(removedPackagesInUse, p)
}
return nil
})
}

return removedPackagesInUse, len(removedPackagesInUse) == 0, nil
return removedPackagesInUse, nil
}
189 changes: 189 additions & 0 deletions cmd/check/sdk_refs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package check

import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"path"

"github.com/hashicorp/tf-sdk-migrator/util"
goList "github.com/kmoe/go-list"
refsParser "github.com/radeksimko/go-refs/parser"
)

type Offence struct {
IdentDeprecation *identDeprecation
Positions []*token.Position
}

type identDeprecation struct {
ImportPath string
Identifier *ast.Ident
Message string
}

var deprecations = []*identDeprecation{
{
"github.com/hashicorp/terraform/httpclient",
ast.NewIdent("UserAgentString"),
"This function has been removed, please use httpclient.TerraformUserAgent(version) instead",
},
{
"github.com/hashicorp/terraform/httpclient",
ast.NewIdent("New"),
"This function has been removed, please use DefaultPooledClient() with custom Transport/round-tripper from github.com/hashicorp/go-cleanhttp instead",
},
{
"github.com/hashicorp/terraform/terraform",
ast.NewIdent("UserAgentString"),
"This function has been removed, please use httpclient.TerraformUserAgent(version) instead",
},
{
"github.com/hashicorp/terraform/terraform",
ast.NewIdent("VersionString"),
"This function has been removed, please use helper/schema's Provider.TerraformVersion available from Provider.ConfigureFunc",
},
{
"github.com/hashicorp/terraform/config",
ast.NewIdent("UserAgentString"),
"Please don't use this",
},
{
"github.com/hashicorp/terraform/config",
ast.NewIdent("NewRawConfig"),
"terraform.NewResourceConfig and config.NewRawConfig have been removed, please use terraform.NewResourceConfigRaw",
},
{
"github.com/hashicorp/terraform/terraform",
ast.NewIdent("NewResourceConfig"),
"terraform.NewResourceConfig and config.NewRawConfig have been removed, please use terraform.NewResourceConfigRaw",
},
}

// ProviderImports is a data structure we parse the `go list` output into
// for efficient searching
type ProviderImportDetails struct {
AllImportPathsHash map[string]bool
Packages map[string]ProviderPackage
}

type ProviderPackage struct {
Dir string
ImportPath string
GoFiles []string
TestGoFiles []string
Imports []string
TestImports []string
}

func GoListPackageImports(providerPath string) (*ProviderImportDetails, error) {
packages, err := goList.GoList(providerPath, "./...")
if err != nil {
return nil, err
}

allImportPathsHash := make(map[string]bool)
providerPackages := make(map[string]ProviderPackage)

for _, p := range packages {
for _, i := range p.Imports {
allImportPathsHash[i] = true
}

providerPackages[p.ImportPath] = ProviderPackage{
Dir: p.Dir,
ImportPath: p.ImportPath,
GoFiles: p.GoFiles,
TestGoFiles: p.TestGoFiles,
Imports: p.Imports,
TestImports: p.TestImports,
}
}

return &ProviderImportDetails{
AllImportPathsHash: allImportPathsHash,
Packages: providerPackages,
}, nil
}

func CheckSDKPackageRefs(providerImportDetails *ProviderImportDetails) ([]*Offence, error) {
offences := make([]*Offence, 0, 0)

for _, d := range deprecations {
fset := token.NewFileSet()
files, err := filesWhichImport(providerImportDetails, d.ImportPath)
if err != nil {
return nil, err
}

foundPositions := make([]*token.Position, 0, 0)

for _, filePath := range files {
f, err := parser.ParseFile(fset, filePath, nil, 0)
if err != nil {
return nil, err
}

identifiers, err := refsParser.FindPackageReferences(f, d.ImportPath)
if err != nil {
// package not imported in this file
continue
}

positions, err := findIdentifierPositions(fset, identifiers, d.Identifier)
if err != nil {
return nil, err
}

if len(positions) > 0 {
foundPositions = append(foundPositions, positions...)
}
}

if len(foundPositions) > 0 {
offences = append(offences, &Offence{
IdentDeprecation: d,
Positions: foundPositions,
})
}
}

return offences, nil
}

func findIdentifierPositions(fset *token.FileSet, nodes []ast.Node, ident *ast.Ident) ([]*token.Position, error) {
positions := make([]*token.Position, 0, 0)

for _, node := range nodes {
nodeName := fmt.Sprint(node)
if nodeName == ident.String() {
position := fset.Position(node.Pos())
positions = append(positions, &position)
}
}

return positions, nil
}

func filesWhichImport(providerImportDetails *ProviderImportDetails, importPath string) (files []string, e error) {
files = []string{}
for _, p := range providerImportDetails.Packages {
if util.StringSliceContains(p.Imports, importPath) {
files = append(files, prependDirToFilePaths(p.GoFiles, p.Dir)...)
}
if util.StringSliceContains(p.TestImports, importPath) {
files = append(files, prependDirToFilePaths(p.TestGoFiles, p.Dir)...)
}
}

return files, nil
}

func prependDirToFilePaths(filePaths []string, dir string) []string {
newFilePaths := []string{}
for _, f := range filePaths {
newFilePaths = append(newFilePaths, path.Join(dir, f))
}
return newFilePaths
}
2 changes: 1 addition & 1 deletion cmd/migrate/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (

const (
oldSDKImportPath = "github.com/hashicorp/terraform"
newSDKImportPath = "github.com/hashicorp/terraform-plugin-sdk/sdk"
newSDKImportPath = "github.com/hashicorp/terraform-plugin-sdk"
newSDKPackagePath = "github.com/hashicorp/terraform-plugin-sdk"
newSDKVersion = "v0.0.1"
)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.12
require (
github.com/hashicorp/go-multierror v1.0.0
github.com/hashicorp/go-version v1.2.0
github.com/kmoe/go-list v0.0.1
github.com/mitchellh/cli v1.0.0
github.com/radeksimko/go-refs v0.0.0-20190614111518-1b15b5989e59
github.com/radeksimko/mod v0.0.0-20190807092412-93f771451dae
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uP
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/kmoe/go-list v0.0.1 h1:hKNir14OZh/LBpw1EQNvy8LeCtlKidF00Bigly1h4ec=
github.com/kmoe/go-list v0.0.1/go.mod h1:8bBhPyBnH3wJago3lVPOKvxA+KDmvAHFgLLmhpiQORE=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
Expand Down
43 changes: 9 additions & 34 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ import (
"log"
"os"
"path/filepath"
"strconv"
"strings"

refs "github.com/radeksimko/go-refs/parser"
)

func StringSliceContains(ss []string, s string) bool {
for _, i := range ss {
if i == s {
return true
}
}
return false
}

func ReadOneOf(dir string, filenames ...string) (fullpath string, content []byte, err error) {
for _, filename := range filenames {
fullpath = filepath.Join(dir, filename)
Expand Down Expand Up @@ -71,34 +77,3 @@ func GetProviderPath(providerRepoName string) (string, error) {

return "", fmt.Errorf("Could not find %s in working directory or GOPATH: %s", providerRepoName, gopath)
}

func FindImportedPackages(filePath string, packagesToFind []string) (foundPackages []string) {
// TODO: check file exists so ParseFile doesn't panic
f, err := refs.ParseFile(filePath)
if err != nil {
log.Print(err)
}

packages := make(map[string]bool)

for _, impSpec := range f.Imports {
impPath, err := strconv.Unquote(impSpec.Path.Value)
if err != nil {
log.Print(err)
}
for i := range packagesToFind {
if packagesToFind[i] == impPath {
packageName := packagesToFind[i]
packages[packageName] = true
}
}

}

foundPackages = make([]string, len(packages))
for k := range packages {
foundPackages = append(foundPackages, k)
}

return foundPackages
}
2 changes: 1 addition & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ github.com/posener/complete/match
github.com/radeksimko/go-refs/parser
# github.com/radeksimko/mod v0.0.0-20190807092412-93f771451dae
github.com/radeksimko/mod/modfile
github.com/radeksimko/mod/lazyregexp
github.com/radeksimko/mod/module
github.com/radeksimko/mod/lazyregexp
github.com/radeksimko/mod/semver
# golang.org/x/sys v0.0.0-20190412213103-97732733099d
golang.org/x/sys/unix

0 comments on commit 3156391

Please sign in to comment.