Skip to content

Commit

Permalink
Improve the missing plugin error to include the full name of the bina…
Browse files Browse the repository at this point in the history
…ry (#5208)

* Improve the plugin error to include the full name of the binary

* update changelog

* fix second error message

* PR feedback updates

* lint

* GetPluginPath shouldn't return empty path and no error

Co-authored-by: komal <komal@pulumi.com>
Co-authored-by: Fraser Waters <fraser@pulumi.com>
  • Loading branch information
3 people committed Mar 17, 2022
1 parent 8b516bb commit 4f1403e
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 26 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG_PENDING.md
Expand Up @@ -11,10 +11,13 @@

- [cli/plugins] Add support for downloading plugin from private Pulumi GitHub releases. This also drops the use of the `GITHUB_ACTOR` and `GITHUB_PERSONAL_ACCESS_TOKEN` environment variables for authenticating to github. Only `GITHUB_TOKEN` is now used, but this can be set to a personal access token.
[#9185](https://github.com/pulumi/pulumi/pull/9185)

- [cli] - Speed up `pulumi stack --show-name` by skipping unneeded snapshot loading.
[#9199](https://github.com/pulumi/pulumi/pull/9199)

- Improved error message for missing plugins.
[#5208](https://github.com/pulumi/pulumi/pull/5208)

### Bug Fixes

- [sdk/nodejs] - Fix uncaught error "ENOENT: no such file or directory" when an error occurs during the stack up.
Expand Down
6 changes: 1 addition & 5 deletions sdk/go/common/resource/plugin/analyzer_plugin.go
Expand Up @@ -58,12 +58,8 @@ func NewAnalyzer(host Host, ctx *Context, name tokens.QName) (Analyzer, error) {
workspace.AnalyzerPlugin, strings.Replace(string(name), tokens.QNameDelimiter, "_", -1), nil)
if err != nil {
return nil, rpcerror.Convert(err)
} else if path == "" {
return nil, workspace.NewMissingError(workspace.PluginInfo{
Kind: workspace.AnalyzerPlugin,
Name: string(name),
})
}
contract.Assert(path != "")

plug, err := newPlugin(ctx, ctx.Pwd, path, fmt.Sprintf("%v (analyzer)", name),
[]string{host.ServerAddr(), ctx.Pwd}, nil /*env*/)
Expand Down
7 changes: 2 additions & 5 deletions sdk/go/common/resource/plugin/langruntime_plugin.go
Expand Up @@ -50,13 +50,10 @@ func NewLanguageRuntime(host Host, ctx *Context, runtime string,
workspace.LanguagePlugin, strings.Replace(runtime, tokens.QNameDelimiter, "_", -1), nil)
if err != nil {
return nil, err
} else if path == "" {
return nil, workspace.NewMissingError(workspace.PluginInfo{
Kind: workspace.LanguagePlugin,
Name: runtime,
})
}

contract.Assert(path != "")

args, err := buildArgsForNewPlugin(host, ctx, options)
if err != nil {
return nil, err
Expand Down
8 changes: 2 additions & 6 deletions sdk/go/common/resource/plugin/provider_plugin.go
Expand Up @@ -81,14 +81,10 @@ func NewProvider(host Host, ctx *Context, pkg tokens.Package, version *semver.Ve
workspace.ResourcePlugin, strings.Replace(string(pkg), tokens.QNameDelimiter, "_", -1), version)
if err != nil {
return nil, err
} else if path == "" {
return nil, workspace.NewMissingError(workspace.PluginInfo{
Kind: workspace.ResourcePlugin,
Name: string(pkg),
Version: version,
})
}

contract.Assert(path != "")

// Runtime options are passed as environment variables to the provider.
env := os.Environ()
for k, v := range options {
Expand Down
31 changes: 22 additions & 9 deletions sdk/go/common/workspace/plugins.go
Expand Up @@ -122,25 +122,33 @@ func parsePluginDownloadURLOverrides(overrides string) (pluginDownloadOverrideAr
type MissingError struct {
// Info contains information about the plugin that was not found.
Info PluginInfo
// includeAmbient is true if we search $PATH for this plugin
includeAmbient bool
}

// NewMissingError allocates a new error indicating the given plugin info was not found.
func NewMissingError(info PluginInfo) error {
func NewMissingError(info PluginInfo, includeAmbient bool) error {
return &MissingError{
Info: info,
Info: info,
includeAmbient: includeAmbient,
}
}

func (err *MissingError) Error() string {
includePath := ""
if err.includeAmbient {
includePath = " or on your $PATH"
}

if err.Info.Version != nil {
return fmt.Sprintf("no %[1]s plugin '%[2]s-v%[3]s' found in the workspace or on your $PATH, "+
return fmt.Sprintf("no %[1]s plugin 'pulumi-%[1]s-%[2]s' found in the workspace at version v%[3]s%[4]s, "+
"install the plugin using `pulumi plugin install %[1]s %[2]s v%[3]s`",
err.Info.Kind, err.Info.Name, err.Info.Version)
err.Info.Kind, err.Info.Name, err.Info.Version, includePath)
}

return fmt.Sprintf("no %s plugin '%[1]s' found in the workspace or on your $PATH, "+
return fmt.Sprintf("no %[1]s plugin 'pulumi-%[1]s-%[2]s' found in the workspace%[3]s, "+
"install the plugin using `pulumi plugin install %[1]s %[2]s`",
err.Info.Kind, err.Info.Name)
err.Info.Kind, err.Info.Name, includePath)
}

// PluginSource deals with downloading a specific version of a plugin, or looking up the latest version of it.
Expand Down Expand Up @@ -1042,7 +1050,8 @@ func GetPluginPath(kind PluginKind, name string, version *semver.Version) (strin
// If we have a version of the plugin on its $PATH, use it, unless we have opted out of this behavior explicitly.
// This supports development scenarios.
optOut, isFound := os.LookupEnv("PULUMI_IGNORE_AMBIENT_PLUGINS")
if !(isFound && cmdutil.IsTruthy(optOut)) || isBundledLangauge {
includeAmbient := !(isFound && cmdutil.IsTruthy(optOut)) || isBundledLangauge
if includeAmbient {
filename = (&PluginInfo{Kind: kind, Name: name, Version: version}).FilePrefix()
if path, err := exec.LookPath(filename); err == nil {
logging.V(6).Infof("GetPluginPath(%s, %s, %v): found on $PATH %s", kind, name, version, path)
Expand Down Expand Up @@ -1092,7 +1101,7 @@ func GetPluginPath(kind PluginKind, name string, version *semver.Version) (strin
Name: name,
Kind: kind,
Version: version,
})
}, includeAmbient)
}
match = &candidate
} else {
Expand Down Expand Up @@ -1136,7 +1145,11 @@ func GetPluginPath(kind PluginKind, name string, version *semver.Version) (strin
return matchDir, matchPath, nil
}

return "", "", nil
return "", "", NewMissingError(PluginInfo{
Name: name,
Kind: kind,
Version: version,
}, includeAmbient)
}

// SortedPluginInfo is a wrapper around PluginInfo that allows for sorting by version.
Expand Down
77 changes: 77 additions & 0 deletions sdk/go/common/workspace/plugins_test.go
Expand Up @@ -726,3 +726,80 @@ func TestParsePluginDownloadURLOverride(t *testing.T) {
})
}
}

func TestMissingErrorText(t *testing.T) {
t.Parallel()

v1 := semver.MustParse("0.1.0")
tests := []struct {
Name string
Plugin PluginInfo
IncludeAmbient bool
ExpectedError string
}{
{
Name: "ResourceWithVersion",
Plugin: PluginInfo{
Name: "myplugin",
Kind: ResourcePlugin,
Version: &v1,
},
IncludeAmbient: true,
ExpectedError: "no resource plugin 'pulumi-resource-myplugin' found in the workspace " +
"at version v0.1.0 or on your $PATH, install the plugin using `pulumi plugin install resource myplugin v0.1.0`",
},
{
Name: "ResourceWithVersion_ExcludeAmbient",
Plugin: PluginInfo{
Name: "myplugin",
Kind: ResourcePlugin,
Version: &v1,
},
IncludeAmbient: false,
ExpectedError: "no resource plugin 'pulumi-resource-myplugin' found in the workspace " +
"at version v0.1.0, install the plugin using `pulumi plugin install resource myplugin v0.1.0`",
},
{
Name: "ResourceWithoutVersion",
Plugin: PluginInfo{
Name: "myplugin",
Kind: ResourcePlugin,
Version: nil,
},
IncludeAmbient: true,
ExpectedError: "no resource plugin 'pulumi-resource-myplugin' found in the workspace " +
"or on your $PATH, install the plugin using `pulumi plugin install resource myplugin`",
},
{
Name: "ResourceWithoutVersion_ExcludeAmbient",
Plugin: PluginInfo{
Name: "myplugin",
Kind: ResourcePlugin,
Version: nil,
},
IncludeAmbient: false,
ExpectedError: "no resource plugin 'pulumi-resource-myplugin' found in the workspace" +
", install the plugin using `pulumi plugin install resource myplugin`",
},
{
Name: "LanguageWithoutVersion",
Plugin: PluginInfo{
Name: "dotnet",
Kind: LanguagePlugin,
Version: nil,
},
IncludeAmbient: true,
ExpectedError: "no language plugin 'pulumi-language-dotnet' found in the workspace " +
"or on your $PATH, install the plugin using `pulumi plugin install language dotnet`",
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.Name, func(t *testing.T) {
t.Parallel()
err := NewMissingError(tt.Plugin, tt.IncludeAmbient)
assert.Equal(t, tt.ExpectedError, err.Error())
})
}
}

0 comments on commit 4f1403e

Please sign in to comment.