Skip to content

Commit

Permalink
Add AutoAliasing code migration (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
aq17 committed Apr 17, 2023
1 parent 7510aff commit 25407c5
Show file tree
Hide file tree
Showing 8 changed files with 411 additions and 4 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ require (
github.com/briandowns/spinner v1.20.0
github.com/pulumi/pulumi/sdk/v3 v3.53.1
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.1 // indirect
golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 // indirect
golang.org/x/term v0.1.0 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -50,6 +54,10 @@ golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 h1:TyKJRhyo17yWxOMCTHKWrc5rd
golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e h1:aZzprAO9/8oim3qStq3wc1Xuxx4QmAGriC4VU4ojemQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
12 changes: 10 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,16 @@ func cmd() *cobra.Command {
case "all":
context.UpgradeBridgeVersion = true
context.UpgradeProviderVersion = true
context.UpgradeCodeMigration = true
case "bridge":
context.UpgradeBridgeVersion = true
case "provider":
context.UpgradeProviderVersion = true
case "code":
context.UpgradeCodeMigration = true
default:
return fmt.Errorf(
"--kind=%s invalid. Must be one of `all`, `bridge` or `provider`.",
"--kind=%s invalid. Must be one of `all`, `bridge`, `provider`, or `code`.",
upgradeKind)
}

Expand Down Expand Up @@ -92,7 +95,12 @@ If the passed version does not exist, an error is signaled.`)
`The kind of upgrade to perform:
- "all": Upgrade the upstream provider and the bridge.
- "bridge": Upgrade the bridge only.
- "provider: Upgrade the upstream provider only.`)
- "provider: Upgrade the upstream provider only.
- "code": Perform some number of code migrations.`)

cmd.PersistentFlags().StringSliceVar(&context.MigrationOpts, "migration-opts", nil,
`A comma separated list of code migration to perform:
- "autoalias": Apply auto aliasing to the provider.`)

return cmd
}
Expand Down
169 changes: 169 additions & 0 deletions upgrade/migrations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package upgrade

import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/printer"
"go/token"
"os"
"strings"

"golang.org/x/tools/go/ast/astutil"
)

const (
TfBridgeXPkg = "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/x"
ContractPkg = "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)

func AutoAliasingMigration(resourcesFilePath, providerName string) (bool, error) {
// Create the AST by parsing src
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, resourcesFilePath, nil, parser.ParseComments)
if err != nil {
return false, err
}

applied, initMetadata, errAssigned := false, true, false
// check to see if already implemented
astutil.Apply(file, nil, func(c *astutil.Cursor) bool {
n := c.Node()
switch x := n.(type) {
case *ast.AssignStmt:
if len(x.Rhs) == 1 {
if c, ok := x.Rhs[0].(*ast.CallExpr); ok {
if s, ok := c.Fun.(*ast.SelectorExpr); ok && s.Sel.Name == "AutoAliasing" {
applied = true
return true
}
}
}
}
return true
})
if applied {
return false, nil
}

astutil.AddImport(fset, file, TfBridgeXPkg)
astutil.AddImport(fset, file, ContractPkg)
astutil.AddNamedImport(fset, file, "EMBED_COMMENT_ANCHOR", "embed")

astutil.Apply(file, nil, func(c *astutil.Cursor) bool {
n := c.Node()
switch x := n.(type) {
case *ast.GenDecl:
if x.Tok == token.VAR {
if s, ok := x.Specs[0].(*ast.ValueSpec); ok && s.Names[0].Name == "metadata" {
initMetadata = false
}
}
case *ast.CompositeLit:
if s, ok := x.Type.(*ast.SelectorExpr); ok && s.Sel.Name == "ProviderInfo" {
x.Elts = append(x.Elts, &ast.KeyValueExpr{
Key: &ast.Ident{Name: "MetadataInfo"},
Value: &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "tfbridge"},
Sel: &ast.Ident{Name: "NewProviderMetadata"},
},
Args: []ast.Expr{&ast.Ident{Name: "metadata"}},
},
})
}
case *ast.AssignStmt:
for _, l := range x.Lhs {
if e, ok := l.(*ast.Ident); ok && e.Name == "err" {
errAssigned = true
}
}
case *ast.ExprStmt:
var id *ast.SelectorExpr
call, ok := x.X.(*ast.CallExpr)
if ok {
id, ok = call.Fun.(*ast.SelectorExpr)
}
if ok {
if id.Sel.Name == "SetAutonaming" {
tok := token.DEFINE
if errAssigned {
tok = token.ASSIGN
}
c.InsertBefore(&ast.AssignStmt{
Tok: tok,
Lhs: []ast.Expr{&ast.Ident{Name: "err", Obj: &ast.Object{Kind: ast.Var, Name: "err"}}},
Rhs: []ast.Expr{&ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "x"},
Sel: &ast.Ident{Name: "AutoAliasing"},
},
Args: []ast.Expr{
&ast.UnaryExpr{
Op: token.AND,
X: &ast.Ident{Name: "prov", Obj: &ast.Object{Kind: ast.Var, Name: "prov"}},
},
&ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "prov", Obj: &ast.Object{Kind: ast.Var, Name: "prov"}},
Sel: &ast.Ident{Name: "GetMetadata"},
},
},
},
}},
})
c.InsertBefore(&ast.ExprStmt{
X: &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "contract"},
Sel: &ast.Ident{Name: "AssertNoErrorf"},
},
Args: []ast.Expr{
&ast.Ident{Name: "err", Obj: &ast.Object{Kind: ast.Var, Name: "err"}},
&ast.BasicLit{Kind: token.STRING, Value: "\"auto aliasing apply failed\""},
},
}})
}
}
}

return true
})

// TODO: figure out how to properly append comments so everything can be done via AST manipulation
if initMetadata {
file.Decls = append(file.Decls, &ast.GenDecl{
Tok: token.VAR,
Specs: []ast.Spec{
&ast.ValueSpec{
Names: []*ast.Ident{{Name: "metadata", Obj: &ast.Object{Kind: ast.Var, Name: "metadata"}}},
Type: &ast.ArrayType{Elt: &ast.Ident{Name: "byte // EMBED_DIRECTIVE_ANCHOR"}},
},
},
})
}

buf := new(bytes.Buffer)
err = printer.Fprint(buf, fset, file)
if err != nil {
return false, err
}
s := buf.String()
s = strings.Replace(s, `EMBED_COMMENT_ANCHOR "embed"`,
"// embed is used to store bridge-metadata.json in the compiled binary\n _ \"embed\"", 1)
s = strings.Replace(s, `var metadata []byte // EMBED_DIRECTIVE_ANCHOR`,
fmt.Sprintf("//go:embed cmd/pulumi-resource-%s/bridge-metadata.json\nvar metadata []byte",
providerName), 1)
// format output
formatted, err := format.Source([]byte(s))
if err != nil {
return false, err
}
err = os.WriteFile(resourcesFilePath, formatted, 0600)
if err != nil {
return false, err
}
return true, nil
}
136 changes: 136 additions & 0 deletions upgrade/migrations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package upgrade

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAutoAliasingMigration(t *testing.T) {
origProgram := `package test
import (
"fmt"
"path/filepath"
"strings"
"unicode"
"github.com/mrparkers/terraform-test/provider"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge"
)
// all of the token components used below.
const (
// packages:
mainPkg = "test"
// modules:
mainMod = "index" // the y module
)
// Provider returns additional overlaid schema and metadata associated with the provider..
func Provider() tfbridge.ProviderInfo {
// Instantiate the Terraform provider
p := shimv2.NewProvider(provider.KeycloakProvider(nil))
// Create a Pulumi provider mapping
prov := tfbridge.ProviderInfo{
P: p,
Name: "test",
GitHubOrg: "testing",
Description: "A Pulumi package for creating and managing test cloud resources.",
Keywords: []string{"pulumi", "test"},
License: "Apache-2.0",
Homepage: "https://pulumi.io",
Repository: "https://github.com/pulumi/pulumi-keycloak",
TFProviderLicense: refProviderLicense(tfbridge.MITLicenseType),
UpstreamRepoPath: ".",
}
prov.SetAutonaming(255, "-")
return prov
}
`

// Write original program to temporary file
tmpDir := t.TempDir()
origPath := filepath.Join(tmpDir, "original.go")
orig, err := os.Create(origPath)
assert.Nil(t, err)
_, err = orig.Write([]byte(origProgram))
assert.Nil(t, err)

// Perform auto aliasing migration
changesMade, err := AutoAliasingMigration(origPath, "test")
assert.Nil(t, err)
assert.True(t, changesMade)

modified, err := os.ReadFile(origPath)
assert.Nil(t, err)

expected := `package test
import (
"fmt"
// embed is used to store bridge-metadata.json in the compiled binary
_ "embed"
"path/filepath"
"strings"
"unicode"
"github.com/mrparkers/terraform-test/provider"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/x"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
// all of the token components used below.
const (
// packages:
mainPkg = "test"
// modules:
mainMod = "index" // the y module
)
// Provider returns additional overlaid schema and metadata associated with the provider..
func Provider() tfbridge.ProviderInfo {
// Instantiate the Terraform provider
p := shimv2.NewProvider(provider.KeycloakProvider(nil))
// Create a Pulumi provider mapping
prov := tfbridge.ProviderInfo{
P: p,
Name: "test",
GitHubOrg: "testing",
Description: "A Pulumi package for creating and managing test cloud resources.",
Keywords: []string{"pulumi", "test"},
License: "Apache-2.0",
Homepage: "https://pulumi.io",
Repository: "https://github.com/pulumi/pulumi-keycloak",
TFProviderLicense: refProviderLicense(tfbridge.MITLicenseType),
UpstreamRepoPath: ".", MetadataInfo: tfbridge.NewProviderMetadata(metadata),
}
err := x.AutoAliasing(&prov, prov.GetMetadata())
contract.AssertNoErrorf(err, "auto aliasing apply failed")
prov.SetAutonaming(255, "-")
return prov
}
//go:embed cmd/pulumi-resource-test/bridge-metadata.json
var metadata []byte
`
// Compare against expected program
assert.Equal(t, string(modified), expected)

// Test running AutoAliasing twice doesn't change output
changesMade, err = AutoAliasingMigration(origPath, "test")
assert.Nil(t, err)
assert.False(t, changesMade)

modified2, err := os.ReadFile(origPath)
assert.Nil(t, err)

assert.Equal(t, string(modified2), expected)
}
Loading

0 comments on commit 25407c5

Please sign in to comment.