Skip to content

Commit

Permalink
Ensure sdkv2 replace mirrors the bridge replace (#248)
Browse files Browse the repository at this point in the history
Fixes #243

After this change the terraform SDK v2 override follows the replace from
the selected bridge version exactly. I think this is what we want - this
is compensating for the inability of Go tooling to propagate the replace
to dependencies.

The change also starts to unconditionally insert this replace. This is
because on the latest bridge tfgen somehow depends on sdkv2 and even
providers such as pulumi-random that presumably do not need sdkv2 now
depend on it and need the replace accordingly, see
pulumi/pulumi-random#687 for example.
  • Loading branch information
t0yv0 authored Feb 8, 2024
1 parent b38f879 commit 5179e8d
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 123 deletions.
73 changes: 73 additions & 0 deletions upgrade/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// HTTP-related effects.
package upgrade

import (
"context"
"fmt"
"io"
"net/http"
"time"
)

// Use this function in steps to perform HTTP GET.
func getHTTP(ctx context.Context, url string) ([]byte, error) {
h, ok := ctx.Value(httpHandlerKey).(httpHandler)
if !ok {
h = &defaultHttpHandler{
retryAttempts: 5,
delay: func(attempt int) time.Duration {
n := (attempt + 1) * (attempt + 1)
return time.Duration(n) * time.Second
},
}
}
return h.getHTTP(url)
}

// Set this to a mock in tests to avoid hitting actual HTTP.
type httpHandler interface {
getHTTP(url string) ([]byte, error)
}

var httpHandlerKey = struct{}{}

type defaultHttpHandler struct {
retryAttempts int
delay func(attempt int) time.Duration
}

func (*defaultHttpHandler) tryGetHTTP(url string) (content []byte, finalError error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer func() {
if err := resp.Body.Close(); err != nil && finalError == nil {
finalError = err
}
}()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Non-200 code for GET %v: %v", url, resp.StatusCode)
}
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if err := resp.Body.Close(); err != nil {
return nil, err
}
return respBytes, nil
}

func (h *defaultHttpHandler) getHTTP(url string) ([]byte, error) {
var c []byte
var err error
for attempt := 0; attempt < h.retryAttempts; attempt++ {
c, err = h.tryGetHTTP(url)
if err == nil {
return c, nil
}
time.Sleep(h.delay(attempt))
}
return nil, err
}
70 changes: 36 additions & 34 deletions upgrade/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -1002,51 +1002,53 @@ var planBridgeUpgrade = stepv2.Func11E("Planning Bridge Upgrade", func(
})

var planPluginSDKUpgrade = stepv2.Func12E("Planning Plugin SDK Upgrade", func(
ctx context.Context, repo ProviderRepo,
ctx context.Context, bridgeRef string,
) (_, display string, _ error) {
defer func() { stepv2.SetLabel(ctx, display) }()
current, ok := originalGoVersionOfV2(ctx, repo, "provider/go.mod",
"github.com/pulumi/terraform-plugin-sdk/v2")
if !ok {
return "", "not found", nil

sdkv2 := "github.com/hashicorp/terraform-plugin-sdk/v2"

br, err := ParseRef(bridgeRef)
if err != nil {
return "", "", fmt.Errorf("cannot parse a Git bridge ref: %v", bridgeRef)
}

var r string
switch br := br.(type) {
case *Version:
r = "v" + br.SemVer.String()
case *HashReference:
r = br.GitHash
case *Latest:
contract.Failf("Unsupported `latest` Ref")
default:
contract.Failf("Unsupported type of Ref: incomplete case match")
}
refs := gitRefsOfV2(ctx,
"https://github.com/pulumi/terraform-plugin-sdk.git", "heads")
currentRef, err := module.PseudoVersionRev(current.Version)

url := fmt.Sprintf("https://raw.githubusercontent.com/pulumi/pulumi-terraform-bridge/%s/go.mod", r)

gomodBytes, err := getHTTP(ctx, url)
if err != nil {
return "", "", fmt.Errorf("unable to parse PseudoVersionRef %q: %w",
current.Version, err)
return "", "", fmt.Errorf("Failed to get %v: %w", url, err)
}
latest := latestSemverTag("upstream-", refs)
currentBranch, ok := refs.labelOf(currentRef)
if !ok {
// use latest versioned branch
// This is not quite correct, since it could be newer than the
// version used in the bridge.
// TODO: https://github.com/pulumi/upgrade-provider/issues/245
latestSha, ok := refs.shaOf(fmt.Sprintf("refs/heads/upstream-%s", latest.Original()))
contract.Assertf(ok, "Failed to lookup sha of known tag: %q not in %#v", latest.Original(), refs.labelToRef)
return latestSha, fmt.Sprintf("Could not find head branch at ref %s. Upgrading to "+
"latest branch at %s instead.", currentRef, latest), nil

goMod, err := modfile.Parse("go.mod", gomodBytes, nil)
if err != nil {
return "", "", fmt.Errorf("Failed parse go.mod: %w", err)
}

trim := func(branch string) string {
const p = "refs/heads/upstream-"
return strings.TrimPrefix(branch, p)
version := ""
for _, re := range goMod.Replace {
if re.Old.Path == sdkv2 {
version = re.New.Version
}
}
currentBranch = trim(currentBranch)

// We are guaranteed to get a non-nil result because there
// are semver tags released tags with this prefix.
if latest.Original() == currentBranch {
return "", fmt.Sprintf("Up to date at %s", latest), nil
if version == "" {
return "", "", fmt.Errorf("Failed to find %v replace in bridge go.mod", sdkv2)
}
latestTag := fmt.Sprintf("refs/heads/upstream-%s", latest.Original())
latestSha, ok := refs.shaOf(latestTag)
contract.Assertf(ok, "Failed to lookup sha of known tag: %q not in %#v",
latestTag, refs.labelToRef)

return latestSha, fmt.Sprintf("%s -> %s", currentBranch, latest), nil
return version, fmt.Sprintf("bridge %s needs terraform-plugin-sdk %s", bridgeRef, version), nil
})

var plantPfUpgrade = stepv2.Func11E("Planning Plugin Framework Upgrade", func(
Expand Down
9 changes: 0 additions & 9 deletions upgrade/steps_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,15 +383,6 @@ func (g gitRepoRefs) shaOf(label string) (string, bool) {
return "", false
}

func (g gitRepoRefs) labelOf(sha string) (string, bool) {
for label, ref := range g.labelToRef {
if strings.HasPrefix(ref, sha) {
return label, true
}
}
return "", false
}

func (g gitRepoRefs) sortedLabels(less func(string, string) bool) []string {
labels := make([]string, 0, len(g.labelToRef))
for label := range g.labelToRef {
Expand Down
114 changes: 35 additions & 79 deletions upgrade/steps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,85 +359,41 @@ func TestCheckMaintenancePatchExpiredCadence(t *testing.T) {
]`), "Check if we should release a maintenance patch", maintenanceRelease)
}

func TestPluginSDKUpgradeLatest(t *testing.T) {
testReplay((&Context{
GoPath: "/Users/myuser/go",
}).Wrap(context.Background()),
t, jsonMarshal[[]*step.Step](t, `[
{
"name": "Planning Plugin SDK Upgrade",
"inputs": [
{
"Name": "pulumi-keycloak",
"Org": "pulumi"
}
],
"outputs": [
"74776a5cd5f9a1330c34124588e0ad800d26724d",
"Could not find head branch at ref e6d96b3b8f7e. Upgrading to latest branch at 2.29.0 instead.",
null
]
},
{
"name": "Original Go Version of",
"inputs": [
{
"Name": "pulumi-keycloak",
"Org": "pulumi"
},
"provider/go.mod",
"github.com/pulumi/terraform-plugin-sdk/v2"
],
"outputs": [
{
"Path": "github.com/pulumi/terraform-plugin-sdk/v2",
"Version": "v2.0.0-20230912190043-e6d96b3b8f7e"
},
true,
null
]
},
{
"name": "git",
"inputs": [
"git",
[
"show",
":provider/go.mod"
]
],
"outputs": [
"module github.com/pulumi/pulumi-keycloak/provider/v5\n\ngo 1.21\n\nreplace (\n\tgithub.com/hashicorp/terraform-plugin-sdk/v2 =\u003e github.com/pulumi/terraform-plugin-sdk/v2 v2.0.0-20230912190043-e6d96b3b8f7e\n\tgithub.com/hashicorp/vault =\u003e github.com/hashicorp/vault v1.2.0\n\tgithub.com/mrparkers/terraform-provider-keycloak =\u003e ../upstream\n)\n\nrequire (\n\tgithub.com/mrparkers/terraform-provider-keycloak v0.0.0-00010101000000-000000000000\n\tgithub.com/pulumi/pulumi-terraform-bridge/v3 v3.72.0\n\tgithub.com/pulumi/pulumi/sdk/v3 v3.103.1\n)\n\nrequire (\n\tcloud.google.com/go v0.110.8 // indirect\n\tcloud.google.com/go/compute v1.23.0 // indirect\n)\n",
null
],
"impure": true
},
{
"name": "git refs of",
"inputs": [
"https://github.com/pulumi/terraform-plugin-sdk.git",
"heads"
],
"outputs": [
{},
null
]
},
func TestPluginSDKUpgrade(t *testing.T) {
ctx := context.WithValue(context.Background(), httpHandlerKey, simpleHttpHandler(func(url string) ([]byte, error) {
if url == "https://raw.githubusercontent.com/pulumi/pulumi-terraform-bridge/v3.73.0/go.mod" {
return []byte(`
module github.com/pulumi/pulumi-terraform-bridge/v3
go 1.20
replace github.com/pulumi/pulumi-terraform-bridge/x/muxer => ./x/muxer
replace github.com/hashicorp/terraform-plugin-sdk/v2 => github.com/pulumi/terraform-plugin-sdk/v2 v2.0.0-20240129205329-74776a5cd5f9
`), nil
}
return nil, fmt.Errorf("not found")
}))
testReplay((&Context{GoPath: "/Users/myuser/go"}).Wrap(ctx), t, jsonMarshal[[]*step.Step](t, `
[
{
"name": "git",
"inputs": [
"git",
[
"ls-remote",
"--heads",
"https://github.com/pulumi/terraform-plugin-sdk.git"
]
],
"outputs": [
"efd600e5c6b7a15badde5c32d6b16312c8823409\trefs/heads/dependabot/go_modules/golang.org/x/crypto-0.17.0\n313a7c4cbcad744ab88abc26b42f8e526680ce49\trefs/heads/dependabot/go_modules/golang.org/x/net-0.17.0\ndacd5f7afbedcd7aeb5881b94573369385692784\trefs/heads/dependabot/go_modules/google.golang.org/grpc-1.56.3\n7ac578ce47fc07e0888beee6d4ab9db09369274f\trefs/heads/master\na81109190d574226079b2ceff408cc7bca82f6fe\trefs/heads/pgavlin/upstream-v2.12.0\n430f685de305a148a5a79d1c3959ecf109986675\trefs/heads/upstream-v2.24.1\n3fa930f865709d507154454c2af20e023d15b6e6\trefs/heads/upstream-v2.26.1\n03a71d0fca3d7d5ff24a52e334aa2e52f442fe04\trefs/heads/upstream-v2.27.0\n74776a5cd5f9a1330c34124588e0ad800d26724d\trefs/heads/upstream-v2.29.0\n",
null
],
"impure": true
"name": "Planning Plugin SDK Upgrade",
"inputs": [
"3.73.0"
],
"outputs": [
"v2.0.0-20240129205329-74776a5cd5f9",
"bridge 3.73.0 needs terraform-plugin-sdk v2.0.0-20240129205329-74776a5cd5f9",
null
]
}
]`), "Planning Plugin SDK Upgrade", planPluginSDKUpgrade)
]`), "Planning Plugin SDK Upgrade", planPluginSDKUpgrade)
}

type simpleHttpHandler func(string) ([]byte, error)

var _ httpHandler = (*simpleHttpHandler)(nil)

func (sh simpleHttpHandler) getHTTP(url string) ([]byte, error) {
return sh(url)
}
2 changes: 1 addition & 1 deletion upgrade/upgrade_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func UpgradeProvider(ctx context.Context, repoOrg, repoName string) (err error)
err = stepv2.PipelineCtx(ctx, "Plan Upgrade", func(ctx context.Context) {
if GetContext(ctx).UpgradeBridgeVersion {
targetBridgeVersion = planBridgeUpgrade(ctx, goMod)
tfSDKTargetSHA, tfSDKUpgrade = planPluginSDKUpgrade(ctx, repo)
tfSDKTargetSHA, tfSDKUpgrade = planPluginSDKUpgrade(ctx, targetBridgeVersion.String())
// Check if we need to release a maintenance patch and set context if so
GetContext(ctx).MaintenancePatch = maintenanceRelease(ctx, repo)
}
Expand Down

0 comments on commit 5179e8d

Please sign in to comment.