Skip to content

Commit fcab997

Browse files
authored
Warn on breaking function signatures (#49)
* Warn on a function signature change * Download schemas in parallel * Allow setting -1 to accept all items * Fix nonZeroArgs order
1 parent f5d7b04 commit fcab997

File tree

6 files changed

+81
-33
lines changed

6 files changed

+81
-33
lines changed

internal/cmd/compare.go

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"bytes"
5+
"context"
56
"fmt"
67
"io"
78
"os"
@@ -21,19 +22,21 @@ import (
2122

2223
func compareCmd() *cobra.Command {
2324
var provider, repository, oldCommit, newCommit string
25+
var maxChanges int
2426

2527
command := &cobra.Command{
2628
Use: "compare",
2729
Short: "Compare two versions of a Pulumi schema",
2830
RunE: func(cmd *cobra.Command, args []string) error {
29-
return compare(provider, repository, oldCommit, newCommit)
31+
return compare(provider, repository, oldCommit, newCommit, maxChanges)
3032
},
3133
}
3234

3335
command.Flags().StringVarP(&provider, "provider", "p", "", "the provider whose schema we are comparing")
3436
_ = command.MarkFlagRequired("provider")
3537

36-
command.Flags().StringVarP(&repository, "repository", "r", "github://api.github.com/pulumi", "the Git repository to download the schema file from")
38+
command.Flags().StringVarP(&repository, "repository", "r",
39+
"github://api.github.com/pulumi", "the Git repository to download the schema file from")
3740
_ = command.MarkFlagRequired("provider")
3841

3942
command.Flags().StringVarP(&oldCommit, "old-commit", "o", "master",
@@ -43,22 +46,33 @@ func compareCmd() *cobra.Command {
4346
"the new commit to compare against the old commit")
4447
_ = command.MarkFlagRequired("new-commit")
4548

49+
command.Flags().IntVarP(&maxChanges, "max-changes", "m", 500,
50+
"the maximum number of breaking changes to display. Pass -1 to display all changes")
51+
4652
return command
4753
}
4854

49-
func compare(provider string, repository string, oldCommit string, newCommit string) error {
50-
schOld, err := pkg.DownloadSchema(repository, provider, oldCommit)
51-
if err != nil {
52-
return err
53-
}
55+
func compare(provider string, repository string, oldCommit string, newCommit string, maxChanges int) error {
56+
ctx, cancel := context.WithCancel(context.Background())
57+
defer cancel()
58+
var schOld schema.PackageSpec
59+
schOldDone := make(chan error)
60+
go func() {
61+
var err error
62+
schOld, err = pkg.DownloadSchema(ctx, repository, provider, oldCommit)
63+
if err != nil {
64+
cancel()
65+
}
66+
schOldDone <- err
67+
}()
5468

5569
var schNew schema.PackageSpec
56-
5770
if newCommit == "--local" {
5871
usr, _ := user.Current()
5972
basePath := fmt.Sprintf("%s/go/src/github.com/pulumi/%s", usr.HomeDir, provider)
6073
schemaFile := pkg.StandardSchemaPath(provider)
6174
schemaPath := filepath.Join(basePath, schemaFile)
75+
var err error
6276
schNew, err = pkg.LoadLocalPackageSpec(schemaPath)
6377
if err != nil {
6478
return err
@@ -74,12 +88,18 @@ func compare(provider string, repository string, oldCommit string, newCommit str
7488
return err
7589
}
7690
} else {
77-
schNew, err = pkg.DownloadSchema(repository, provider, newCommit)
91+
var err error
92+
schNew, err = pkg.DownloadSchema(ctx, repository, provider, newCommit)
7893
if err != nil {
7994
return err
8095
}
8196
}
82-
compareSchemas(os.Stdout, provider, schOld, schNew)
97+
98+
if err := <-schOldDone; err != nil {
99+
return err
100+
}
101+
102+
compareSchemas(os.Stdout, provider, schOld, schNew, maxChanges)
83103
return nil
84104
}
85105

@@ -184,6 +204,24 @@ func breakingChanges(oldSchema, newSchema schema.PackageSpec) *diagtree.Node {
184204
}
185205
}
186206

207+
// The upstream issue is tracked at
208+
// https://github.com/pulumi/pulumi/issues/13563.
209+
isNonZeroArgs := func(ts *schema.ObjectTypeSpec) bool {
210+
if ts == nil {
211+
return false
212+
}
213+
return len(ts.Properties) > 0
214+
}
215+
type nonZeroArgs struct{ old, new bool }
216+
switch (nonZeroArgs{old: isNonZeroArgs(f.Inputs), new: isNonZeroArgs(newFunc.Inputs)}) {
217+
case nonZeroArgs{false, true}:
218+
msg.SetDescription(diagtree.Danger,
219+
"signature change (pulumi.InvokeOptions)->T => (Args, pulumi.InvokeOptions)->T")
220+
case nonZeroArgs{true, false}:
221+
msg.SetDescription(diagtree.Danger,
222+
"signature change (Args, pulumi.InvokeOptions)->T => (pulumi.InvokeOptions)->T")
223+
}
224+
187225
if f.Outputs != nil {
188226
msg := msg.Label("outputs")
189227
for propName, prop := range f.Outputs.Properties {
@@ -260,11 +298,11 @@ func breakingChanges(oldSchema, newSchema schema.PackageSpec) *diagtree.Node {
260298
return msg
261299
}
262300

263-
func compareSchemas(out io.Writer, provider string, oldSchema, newSchema schema.PackageSpec) {
301+
func compareSchemas(out io.Writer, provider string, oldSchema, newSchema schema.PackageSpec, maxChanges int) {
264302
fmt.Fprintf(out, "### Does the PR have any schema changes?\n\n")
265303
violations := breakingChanges(oldSchema, newSchema)
266304
displayedViolations := new(bytes.Buffer)
267-
lenViolations := violations.Display(displayedViolations, 500)
305+
lenViolations := violations.Display(displayedViolations, maxChanges)
268306
switch lenViolations {
269307
case 0:
270308
fmt.Fprintln(out, "Looking good! No breaking changes found.")

internal/cmd/stats.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"os"
@@ -37,7 +38,8 @@ func statsCmd() *cobra.Command {
3738
}
3839

3940
func stats(provider string, repositoryUrl string, details bool) error {
40-
sch, err := pkg.DownloadSchema(repositoryUrl, provider, "master")
41+
ctx := context.Background()
42+
sch, err := pkg.DownloadSchema(ctx, repositoryUrl, provider, "master")
4143
if err != nil {
4244
return err
4345
}

internal/pkg/git.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pkg
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
67
"io"
@@ -21,7 +22,7 @@ import (
2122
type GitSource interface {
2223
// Download fetches an io.ReadCloser for the schema file to download and also returns the size of the response (if known).
2324
Download(
24-
commit string,
25+
ctx context.Context, commit string,
2526
getHTTPResponse func(*http.Request) (io.ReadCloser, int64, error)) (io.ReadCloser, int64, error)
2627
}
2728

@@ -75,13 +76,13 @@ func newGitlabSource(url *url.URL, name string) (*gitlabSource, error) {
7576
}, nil
7677
}
7778

78-
func (source *gitlabSource) newHTTPRequest(url, accept string) (*http.Request, error) {
79+
func (source *gitlabSource) newHTTPRequest(ctx context.Context, url, accept string) (*http.Request, error) {
7980
var authorization string
8081
if source.token != "" {
8182
authorization = fmt.Sprintf("Bearer %s", source.token)
8283
}
8384

84-
req, err := buildHTTPRequest(url, authorization)
85+
req, err := buildHTTPRequest(ctx, url, authorization)
8586
if err != nil {
8687
return nil, err
8788
}
@@ -90,7 +91,7 @@ func (source *gitlabSource) newHTTPRequest(url, accept string) (*http.Request, e
9091
}
9192

9293
func (source *gitlabSource) Download(
93-
commit string,
94+
ctx context.Context, commit string,
9495
getHTTPResponse func(*http.Request) (io.ReadCloser, int64, error),
9596
) (io.ReadCloser, int64, error) {
9697
assetName := url.QueryEscape(StandardSchemaPath(source.name))
@@ -102,7 +103,7 @@ func (source *gitlabSource) Download(
102103
source.host, project, assetName, commit)
103104
logging.V(1).Infof("%s downloading from %s", source.name, assetURL)
104105

105-
req, err := source.newHTTPRequest(assetURL, "application/octet-stream")
106+
req, err := source.newHTTPRequest(ctx, assetURL, "application/octet-stream")
106107
if err != nil {
107108
return nil, -1, err
108109
}
@@ -158,13 +159,13 @@ func newGithubSource(url *url.URL, name string) (*githubSource, error) {
158159
}, nil
159160
}
160161

161-
func (source *githubSource) newHTTPRequest(url, accept string) (*http.Request, error) {
162+
func (source *githubSource) newHTTPRequest(ctx context.Context, url, accept string) (*http.Request, error) {
162163
var authorization string
163164
if source.token != "" {
164165
authorization = fmt.Sprintf("token %s", source.token)
165166
}
166167

167-
req, err := buildHTTPRequest(url, authorization)
168+
req, err := buildHTTPRequest(ctx, url, authorization)
168169
if err != nil {
169170
return nil, err
170171
}
@@ -209,23 +210,23 @@ func (source *githubSource) getHTTPResponse(
209210
}
210211

211212
func (source *githubSource) Download(
212-
commit string,
213+
ctx context.Context, commit string,
213214
getHTTPResponse func(*http.Request) (io.ReadCloser, int64, error),
214215
) (io.ReadCloser, int64, error) {
215216
schemaURL := fmt.Sprintf(
216217
"https://%s/repos/%s/%s/contents/%s?ref=%s",
217218
source.host, source.organization, source.repository, StandardSchemaPath(source.name), commit)
218219
logging.V(9).Infof("plugin GitHub schema url: %s", schemaURL)
219220

220-
req, err := source.newHTTPRequest(schemaURL, "application/vnd.github.v4.raw")
221+
req, err := source.newHTTPRequest(ctx, schemaURL, "application/vnd.github.v4.raw")
221222
if err != nil {
222223
return nil, -1, err
223224
}
224225
return source.getHTTPResponse(getHTTPResponse, req)
225226
}
226227

227-
func buildHTTPRequest(pluginEndpoint string, authorization string) (*http.Request, error) {
228-
req, err := http.NewRequest("GET", pluginEndpoint, nil)
228+
func buildHTTPRequest(ctx context.Context, pluginEndpoint string, authorization string) (*http.Request, error) {
229+
req, err := http.NewRequestWithContext(ctx, "GET", pluginEndpoint, nil)
229230
if err != nil {
230231
return nil, err
231232
}

internal/pkg/util.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pkg
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"io"
@@ -10,7 +11,8 @@ import (
1011
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
1112
)
1213

13-
func DownloadSchema(repositoryUrl string, provider string, commit string) (schema.PackageSpec, error) {
14+
func DownloadSchema(ctx context.Context, repositoryUrl string,
15+
provider string, commit string) (schema.PackageSpec, error) {
1416
baseSource, err := func() (GitSource, error) {
1517
// Support schematised URLS if the URL has a "schema" part we recognize
1618
url, err := url.Parse(repositoryUrl)
@@ -31,7 +33,7 @@ func DownloadSchema(repositoryUrl string, provider string, commit string) (schem
3133
return schema.PackageSpec{}, err
3234
}
3335

34-
resp, _, err := baseSource.Download(commit, getHTTPResponse)
36+
resp, _, err := baseSource.Download(ctx, commit, getHTTPResponse)
3537
if err != nil {
3638
return schema.PackageSpec{}, err
3739
}

internal/pkg/util_test.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pkg
22

33
import (
4+
"context"
45
"testing"
56

67
"github.com/h2non/gock"
@@ -16,7 +17,8 @@ func TestDownloadValidGithubOrg(t *testing.T) {
1617
Reply(200).
1718
File("schema.json")
1819

19-
spec, err := DownloadSchema("github://api.github.com/pulumiverse", "unifi", "main")
20+
spec, err := DownloadSchema(context.Background(),
21+
"github://api.github.com/pulumiverse", "unifi", "main")
2022

2123
assert.Nil(t, err)
2224
assert.NotNil(t, spec)
@@ -33,7 +35,8 @@ func TestDownloadValidGithubRepo(t *testing.T) {
3335
Reply(200).
3436
File("schema.json")
3537

36-
spec, err := DownloadSchema("github://api.github.com/pulumiverse/pulumi-unifi", "unifi", "main")
38+
spec, err := DownloadSchema(context.Background(),
39+
"github://api.github.com/pulumiverse/pulumi-unifi", "unifi", "main")
3740

3841
assert.Nil(t, err)
3942
assert.NotNil(t, spec)
@@ -49,7 +52,8 @@ func TestDownloadUnknownGithubRef(t *testing.T) {
4952
MatchParam("ref", "unknown").
5053
Reply(404)
5154

52-
_, err := DownloadSchema("github://api.github.com/pulumiverse/pulumi-unifi", "unifi", "unknown")
55+
_, err := DownloadSchema(context.Background(),
56+
"github://api.github.com/pulumiverse/pulumi-unifi", "unifi", "unknown")
5357

5458
assert.NotNil(t, err)
5559
assert.Equal(t, "404 HTTP error fetching schema from https://api.github.com/repos/pulumiverse/pulumi-unifi/contents/provider/cmd/pulumi-resource-unifi/schema.json?ref=unknown. If this is a private GitHub repository, try providing a token via the GITHUB_TOKEN environment variable. See: https://github.com/settings/tokens", err.Error())
@@ -64,7 +68,7 @@ func TestDownloadValidGitlabOwner(t *testing.T) {
6468
Reply(200).
6569
File("schema.json")
6670

67-
spec, err := DownloadSchema("gitlab://gitlab.com/pulumiverse", "unifi", "main")
71+
spec, err := DownloadSchema(context.Background(), "gitlab://gitlab.com/pulumiverse", "unifi", "main")
6872

6973
assert.Nil(t, err)
7074
assert.NotNil(t, spec)
@@ -81,7 +85,7 @@ func TestDownloadValidGitlabRepo(t *testing.T) {
8185
Reply(200).
8286
File("schema.json")
8387

84-
spec, err := DownloadSchema("gitlab://gitlab.com/pulumiverse/pulumi-unifi", "unifi", "main")
88+
spec, err := DownloadSchema(context.Background(), "gitlab://gitlab.com/pulumiverse/pulumi-unifi", "unifi", "main")
8589

8690
assert.Nil(t, err)
8791
assert.NotNil(t, spec)
@@ -97,7 +101,7 @@ func TestDownloadUnknownGitlabRef(t *testing.T) {
97101
MatchParam("ref", "unknown").
98102
Reply(404)
99103

100-
_, err := DownloadSchema("gitlab://gitlab.com/pulumiverse/pulumi-unifi", "unifi", "unknown")
104+
_, err := DownloadSchema(context.Background(), "gitlab://gitlab.com/pulumiverse/pulumi-unifi", "unifi", "unknown")
101105

102106
assert.NotNil(t, err)
103107
assert.Equal(t, "404 HTTP error fetching schema from https://gitlab.com/api/v4/projects/pulumiverse%2Fpulumi-unifi/repository/files/provider%2Fcmd%2Fpulumi-resource-unifi%2Fschema.json/raw?ref=unknown", err.Error())

internal/util/diagtree/diagtree.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,13 @@ type cappedWriter struct {
7878

7979
func (c *cappedWriter) incr() {
8080
if c.remaining > 0 {
81+
// We never step past 0, because -1 indicates that we should always print
8182
c.remaining--
8283
}
8384
}
8485

8586
func (c *cappedWriter) Write(p []byte) (n int, err error) {
86-
if c.remaining > 0 {
87+
if c.remaining > 0 || c.remaining == -1 {
8788
return c.out.Write(p)
8889
}
8990
// We pretend we finished the write, but we do nothing.

0 commit comments

Comments
 (0)