Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the missing plugin error to include the full name of the binary #5208

Merged
merged 7 commits into from Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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())
})
}
}