-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Consolidate go modules for bridged providers
- Loading branch information
Showing
56 changed files
with
494 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
213 changes: 213 additions & 0 deletions
213
provider-ci/internal/pkg/migrations/consolidate_modules.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
package migrations | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// consolidateModules moves ./provider/go.mod to the repository root (./go.mod) | ||
// and consolidates it with ./examples/go.mod and ./tests/go.mod (if they | ||
// exist). The SDK module is untouched, so SDK consumers are unaffected. | ||
// | ||
// The migration simplifies dependency management; eliminates the need for | ||
// `replace` directives (except for shims); ensures consistent package | ||
// versioning between provider logic and tests; makes it easier to share code; | ||
// yields better IDE integration; and is all-around easier to work with. | ||
// | ||
// This was initially motivated by work to shard our integration tests. Our old | ||
// module structure sometimes forced us to put integration tests alongside unit | ||
// tests under ./provider. We also had integration tests under ./examples. | ||
// Being able to shard both of those things concurrently (as part of a single | ||
// `go test` command) wasn't possible due to them existing in separate modules. | ||
// | ||
// See also: https://go.dev/wiki/Modules#should-i-have-multiple-modules-in-a-single-repository | ||
type consolidateModules struct{} | ||
|
||
func (consolidateModules) Name() string { | ||
return "Consolidate Go modules" | ||
} | ||
|
||
func (consolidateModules) ShouldRun(_ string) bool { | ||
_, err := os.Stat("provider/go.mod") | ||
return err == nil // Exists. | ||
} | ||
|
||
func (consolidateModules) Migrate(_, outDir string) error { | ||
run := func(args ...string) ([]byte, error) { | ||
cmd := exec.Command(args[0], args[1:]...) | ||
cmd.Dir = outDir | ||
cmd.Stderr = os.Stderr | ||
return cmd.Output() | ||
} | ||
|
||
// Move provider's module down. | ||
if _, err := run("git", "mv", "-f", "provider/go.mod", "go.mod"); err != nil { | ||
return fmt.Errorf("moving provider/go.mod: %w", err) | ||
} | ||
if _, err := run("git", "mv", "-f", "provider/go.sum", "go.sum"); err != nil { | ||
return fmt.Errorf("moving provider/go.sum: %w", err) | ||
} | ||
|
||
// Load the module as JSON. | ||
out, err := run("go", "mod", "edit", "-json", "go.mod") | ||
if err != nil { | ||
return fmt.Errorf("exporting go.mod: %w", err) | ||
} | ||
var mod gomod | ||
err = json.Unmarshal(out, &mod) | ||
if err != nil { | ||
return fmt.Errorf("reading go.mod: %w", err) | ||
} | ||
|
||
// Move relative `replace` paths up or down a directory. | ||
for idx, r := range mod.Replace { | ||
if strings.HasPrefix(r.New.Path, "../") { | ||
r.New.Path = strings.Replace(r.New.Path, "../", "./", 1) | ||
} else if strings.HasPrefix(r.New.Path, "./") { | ||
r.New.Path = strings.Replace(r.New.Path, "./", "./provider/", 1) | ||
} | ||
if r.New.Path == mod.Replace[idx].New.Path { | ||
continue // Unchanged. | ||
} | ||
|
||
// Commit the changes. | ||
old := r.Old.Path | ||
if r.Old.Version != "" { | ||
old += "@" + r.Old.Version | ||
} | ||
_, err = run("go", "mod", "edit", fmt.Sprintf("-replace=%s=%s", old, r.New.Path)) | ||
if err != nil { | ||
return fmt.Errorf("replacing %q: %w", old, err) | ||
} | ||
} | ||
|
||
// Remove examples/tests modules. We'll recover their requirements with a | ||
// `tidy` at the end. It's OK if these don't exist. | ||
_, _ = run("git", "rm", "examples/go.mod") | ||
_, _ = run("git", "rm", "examples/go.sum") | ||
_, _ = run("git", "rm", "tests/go.mod") | ||
_, _ = run("git", "rm", "tests/go.sum") | ||
|
||
// Rewrite our module path and determine our new import, if it's changed. | ||
// | ||
// The module `github.com/pulumi/pulumi-foo/provider/v6` becomes | ||
// `github.com/pulumi/pulumi-foo/v6` and existing code should be imported | ||
// as `github.com/pulumi/pulumi-foo/v6/provider`. | ||
// | ||
// For v1 modules, `github.com/pulumi/pulumi-foo/provider` becomes | ||
// `github.com/pulumi/pulumi-foo` and existing imports are unchanged. | ||
|
||
oldImport := mod.Module.Path | ||
newModule := filepath.Dir(oldImport) // Strip "/vN" or "/provider". | ||
newImport := oldImport | ||
|
||
// Handle major version. | ||
if base := filepath.Base(oldImport); base != "provider" { | ||
if !strings.HasPrefix(base, "v") { | ||
return fmt.Errorf("expected a major version, got %q", base) | ||
} | ||
newModule = filepath.Join(filepath.Dir(newModule), base) | ||
newImport = filepath.Join(newModule, "provider") | ||
} | ||
|
||
// Update our module name. | ||
_, err = run("go", "mod", "edit", "-module="+newModule) | ||
if err != nil { | ||
return fmt.Errorf("rewriting module name: %w", err) | ||
} | ||
|
||
// Re-write imports for our provider, examples, and tests modules. | ||
rewriteImport := func(oldImport, newImport string) error { | ||
if oldImport == newImport { | ||
return nil // Nothing to do. | ||
} | ||
_, err := run("find", ".", | ||
"-type", "f", | ||
"-not", "-path", "./sdk/*", | ||
"-not", "-path", "./upstream/*", | ||
"-not", "-path", "./.git/*", | ||
"-not", "-path", "./.pulumi/*", | ||
"-exec", "sed", "-i.bak", | ||
fmt.Sprintf("s/%s/%s/g", | ||
strings.Replace(oldImport, "/", `\/`, -1), | ||
strings.Replace(newImport, "/", `\/`, -1), | ||
), "{}", ";") | ||
if err != nil { | ||
return fmt.Errorf("rewriting %q to %q: %w", oldImport, newImport, err) | ||
} | ||
_, err = run("find", ".", "-name", "*.bak", "-exec", "rm", "{}", "+") | ||
if err != nil { | ||
return fmt.Errorf("cleaning up: %w", err) | ||
} | ||
return nil | ||
|
||
} | ||
if err := rewriteImport(oldImport, newImport); err != nil { | ||
return err | ||
} | ||
if err := rewriteImport( | ||
strings.Replace(oldImport, "provider", "examples", 1), | ||
strings.Replace(newImport, "provider", "examples", 1), | ||
); err != nil { | ||
return err | ||
} | ||
if err := rewriteImport( | ||
strings.Replace(oldImport, "provider", "tests", 1), | ||
strings.Replace(newImport, "provider", "tests", 1), | ||
); err != nil { | ||
return err | ||
} | ||
|
||
// Tidy up. | ||
_, err = run("go", "mod", "tidy") | ||
if err != nil { | ||
return fmt.Errorf("tidying up: %w", err) | ||
} | ||
|
||
return nil | ||
|
||
} | ||
|
||
// The types below are for loading the module as JSON and are copied from `go | ||
// help mod edit`. | ||
|
||
type module struct { | ||
Path string | ||
Version string | ||
} | ||
|
||
type gomod struct { | ||
Module modpath | ||
Go string | ||
Toolchain string | ||
Require []requirement | ||
Exclude []module | ||
Replace []replace | ||
Retract []retract | ||
} | ||
|
||
type modpath struct { | ||
Path string | ||
Deprecated string | ||
} | ||
|
||
type requirement struct { | ||
Path string | ||
Version string | ||
Indirect bool | ||
} | ||
|
||
type replace struct { | ||
Old module | ||
New module | ||
} | ||
|
||
type retract struct { | ||
Low string | ||
High string | ||
Rationale string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package migrations | ||
|
||
import ( | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestConsolidateModules(t *testing.T) { | ||
tests, err := os.ReadDir("testdata/modules") | ||
require.NoError(t, err) | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.Name(), func(t *testing.T) { | ||
if !tt.IsDir() { | ||
return | ||
} | ||
|
||
tmp := t.TempDir() | ||
|
||
// Copy GIVEN to a temp directory so we can mutate it. | ||
given := filepath.Join("testdata/modules", tt.Name(), "GIVEN") | ||
err = os.CopyFS(tmp, os.DirFS(given)) | ||
|
||
// We need to operate on a git repo, so initialize one. | ||
out, err := exec.Command("git", "init", tmp).CombinedOutput() | ||
require.NoError(t, err, string(out)) | ||
out, err = exec.Command("git", "-C", tmp, "add", ".").CombinedOutput() | ||
require.NoError(t, err, string(out)) | ||
out, err = exec.Command("git", "-C", tmp, | ||
"-c", "user.name=pulumi-bot", "-c", "[email protected]", | ||
"commit", "-m", "Initial commit").CombinedOutput() | ||
require.NoError(t, err, string(out)) | ||
|
||
// Do the migration. | ||
m := consolidateModules{} | ||
err = m.Migrate("", tmp) | ||
require.NoError(t, err) | ||
|
||
// Make sure we got the expected output. | ||
want := filepath.Join("testdata/modules", tt.Name(), "WANT") | ||
assertDirectoryContains(t, want, tmp) | ||
assertDirectoryContains(t, tmp, want) | ||
}) | ||
} | ||
} | ||
|
||
// assertDirectoryContains asserts that dir1 contains all of the files in dir2 | ||
// with exactly the same context. The .git directory is ignored. | ||
func assertDirectoryContains(t *testing.T, dir1, dir2 string) { | ||
t.Helper() | ||
|
||
entries, err := os.ReadDir(dir2) | ||
require.NoError(t, err) | ||
|
||
for _, entry := range entries { | ||
if entry.Name() == ".git" { | ||
continue | ||
} | ||
|
||
stat, err := os.Stat(filepath.Join(dir1, entry.Name())) | ||
assert.NoError(t, err) | ||
assert.Equal(t, entry.IsDir(), stat.IsDir()) | ||
|
||
subPath1 := filepath.Join(dir1, entry.Name()) | ||
subPath2 := filepath.Join(dir2, entry.Name()) | ||
|
||
if entry.IsDir() { | ||
assertDirectoryContains(t, subPath1, subPath2) | ||
continue | ||
} | ||
|
||
content1, err := os.ReadFile(subPath1) | ||
assert.NoError(t, err) | ||
|
||
content2, err := os.ReadFile(subPath2) | ||
assert.NoError(t, err) | ||
|
||
assert.Equal(t, string(content1), string(content2), subPath1) | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
provider-ci/internal/pkg/migrations/testdata/modules/v1/GIVEN/examples/examples_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package examples | ||
|
||
import ( | ||
_ "github.com/pulumi/pulumi-foo/examples/some-package" | ||
_ "github.com/pulumi/pulumi-foo/provider" | ||
) |
12 changes: 12 additions & 0 deletions
12
provider-ci/internal/pkg/migrations/testdata/modules/v1/GIVEN/examples/go.mod
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module github.com/pulumi/pulumi-foo/examples | ||
|
||
go 1.22.7 | ||
|
||
replace ( | ||
github.com/pulumi/pulumi-foo/provider => ../provider | ||
github.com/terraform-providers/terraform-provider-foo/shim => ../provider/shim | ||
) | ||
|
||
require github.com/pulumi/pulumi-foo/provider v1.0.0-20230306191832-8c7659ab0229 | ||
|
||
require github.com/terraform-providers/terraform-provider-foo/shim v0.0.0 // indirect |
Empty file.
1 change: 1 addition & 0 deletions
1
provider-ci/internal/pkg/migrations/testdata/modules/v1/GIVEN/examples/some-package/bar.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package bar |
7 changes: 7 additions & 0 deletions
7
provider-ci/internal/pkg/migrations/testdata/modules/v1/GIVEN/provider/go.mod
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module github.com/pulumi/pulumi-foo/provider | ||
|
||
go 1.22.7 | ||
|
||
require github.com/terraform-providers/terraform-provider-foo/shim v0.0.0 | ||
|
||
replace github.com/terraform-providers/terraform-provider-foo/shim => ./shim |
Empty file.
6 changes: 6 additions & 0 deletions
6
provider-ci/internal/pkg/migrations/testdata/modules/v1/GIVEN/provider/provider.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package provider | ||
|
||
import ( | ||
_ "github.com/pulumi/pulumi-foo/provider/some-package" | ||
_ "github.com/terraform-providers/terraform-provider-foo/shim" | ||
) |
3 changes: 3 additions & 0 deletions
3
provider-ci/internal/pkg/migrations/testdata/modules/v1/GIVEN/provider/shim/go.mod
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/terraform-providers/terraform-provider-foo/shim | ||
|
||
go 1.22.7 |
Empty file.
Oops, something went wrong.