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

Check if Plugin is bundled before installing #12575

Merged
merged 2 commits into from Aug 8, 2023
Merged

Check if Plugin is bundled before installing #12575

merged 2 commits into from Aug 8, 2023

Conversation

RobbieMcKinstry
Copy link
Contributor

Description

This PR adds a check to pulumi plugin install to confirm that the plugin is not bundled with Pulumi before performing the download. Without this check, the CLI will produce a 404 error because the language plugins aren't distributable independently of the CLI executable.

Fixes #11703

Checklist

  • I have added tests that prove my fix is effective or that my feature works I have manually checked the relevant CLI commands.
  • I have run make changelog and committed the changelog/pending/<file> documenting my change
  • Yes, there are changes in this PR that warrants bumping the Pulumi Service API version

@pulumi-bot
Copy link
Contributor

pulumi-bot commented Mar 31, 2023

Changelog

[uncommitted] (2023-08-07)

Bug Fixes

  • [cli/plugin] Improve error message during pulumi plugin install if the plugin is bundled with Pulumi
    #12575

// bundled plugins are not installable with this command.
// They are expected to be distributed with Pulumi itself.
if workspace.IsBundledPlugin(pluginSpec.Kind, pluginSpec.Name) {
return fmt.Errorf(
"the %v %v plugin is bundled with Pulumi, and cannot be directly installed"+
" with this command. If you need to reinstall this plugin, uninstall"+
" Pulumi via your package manager and re-install.",
pluginSpec.Name,
pluginSpec.Kind,
)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a small test on this to ensure that this check is run?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to follow @abhinav's style of splitting command code out of the cobra callback for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you want the control flow for the test?
Because this is in the top-level of the cobra cmd, we'd have to mock out the function, add a faker interface, and check the fake. Currently, this function has no tests at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Frassle link?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eg #12499

@@ -103,6 +103,18 @@ func newPluginInstallCmd() *cobra.Command {
Checksums: checksums,
}

// bundled plugins are not installable with this command.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annoyingly I'm not sure we should use "bundled" to decided this.
java, dotnet, and yaml are all bundled (that is we expect to see them side-by-side with the CLI binary) but they are installable via pulumi plugin install and we probably are going to make use of that testing the install these things as we move to making them true plugins.
But maybe for decent UX just disable this check if PULUMI_DEV is set, I don't think any users will be depending on those installs yet but DEV will mean we can still quickly hook into it for testing.

// bundled plugins are not installable with this command.
// They are expected to be distributed with Pulumi itself.
if workspace.IsBundledPlugin(pluginSpec.Kind, pluginSpec.Name) {
return fmt.Errorf(
"the %v %v plugin is bundled with Pulumi, and cannot be directly installed"+
" with this command. If you need to reinstall this plugin, uninstall"+
" Pulumi via your package manager and re-install.",
pluginSpec.Name,
pluginSpec.Kind,
)
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to follow @abhinav's style of splitting command code out of the cobra callback for that.

@@ -1513,6 +1513,20 @@ func IsPluginKind(k string) bool {
}
}

// IsPreinstalledLanguagePlugin returns true if the spec describes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// IsPreinstalledLanguagePlugin returns true if the spec describes
// IsBundledPlugin returns true if the spec describes

@@ -1513,6 +1513,20 @@ func IsPluginKind(k string) bool {
}
}

// IsPreinstalledLanguagePlugin returns true if the spec describes
// a language plugin that comes preinstalled with the CLI.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// a language plugin that comes preinstalled with the CLI.
// a plugin that comes preinstalled with the CLI.

Not just language plugins

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole comment is wrong lol

Comment on lines 1520 to 1522
// We currently bundle some plugins with "pulumi" and thus expect them to be next to the pulumi binary. We
// also always allow these plugins to be picked up from PATH even if PULUMI_IGNORE_AMBIENT_PLUGINS is set.
// Eventually we want to fix this so new plugins are true plugins in the plugin cache.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is more appropriate in "getPluginInfoAndPath" rather than here.

runtime.GOOS + "-" + runtime.GOARCH: checksumBytes,
}
}
// Bundled plugins are generally not installable with this command. They are expected to be
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only real change here, the rest is just indent change from putting this in the Run method.

),
}

// TODO: This test actually hits GitHub to fail, it would be better if plugin installation could be mocked.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @abhinav your generally good at thinking up clean designs for this sort of thing, any recommendation here.
I've been using functions for this in some places (e.g. in pkg/codegen/convert/plugin_mapper.go), maybe not overkill to do that here?

Copy link
Contributor

@abhinav abhinav Jul 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a function reference to workspace.DownloadToFile (or whatever is responsible for downloading) is fine here.
Longer term, it makes sense for these top-level functions with so much complex behavior to be turned into objects with clearer contracts that we inject in here, e.g. a *PluginDownloader struct and an interface for that in the command implementation. Then we can mock it out and return an error on a download attempt, or start up a test HTTP server and make the downloader implementation hit that.

Edit: looks like PluginSpec.GetLatestVersion is the one that makes the request here. Again, short term we can do a reference. ((PluginSpec).GetLatestVersion is an unbound method reference.)
Long-term, we should separate data-only types and behavior types. PluginSpec appears to be intended as a data type. We should look towards dropping complex behavior from its methods, moving it into a Plugin[Something] abstraction responsible for plugin-related work that has side effects.

// distributed with Pulumi itself. But we turn this check off if PULUMI_DEV is set so we can
// test installing plugins that are being moved to their own distribution (such as when we move
// pulumi-nodejs).
isBundled := (pluginSpec.Kind == workspace.LanguagePlugin && pluginSpec.Name == "nodejs") ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very undecided about what's the right logic here.

  1. Should it be we give this warning for all language plugins + the python/node policy/resource, on the basis that we don't really support language as plugins yet, they are tightly coupled to the same CLI version (we do make grpc breaking changes to the language interface).
  2. Should it error for all our plugins, but if someone tries to install a community rust (for example) language plugin we just allow that (even if DEV=false)
  3. Should it error for just the plugins which we know can't install via plugin install, i.e. nodejs, go, and python language plugins + the nodejs/python policy/resource. So if someone wants to install dotnet like this they can, even though it's not really supported.

I'm currently leaning towards 1 or 2.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(2) sounds more reasonable to me.
We should only consider what we know is bundled with pulumi as bundled for the purposes of this command. This avoids that assumption about what's supported by the wider tooling.
So if/when we do support other language plugins, nobody should have to come back here and fix this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I've reworked this to explicitly list our plugins and only our plugins as "bundled". Any other language plugin will not look next to the pulumi binary, and will be allowed to install via "plugin install"

@abhinav
Copy link
Contributor

abhinav commented Jul 28, 2023

@Frassle I pushed a commit with the test modified to not make that request. Feel free to squash it into the behavioral change, although I would recommend keeping the refactor commit separate.

@Frassle Frassle force-pushed the mckinstry/#11703 branch 2 times, most recently from 6d8e293 to 69bf8b8 Compare August 1, 2023 08:16
Comment on lines +1666 to +1674
return (kind == LanguagePlugin && name == "nodejs") ||
(kind == LanguagePlugin && name == "go") ||
(kind == LanguagePlugin && name == "python") ||
(kind == LanguagePlugin && name == "dotnet") ||
(kind == LanguagePlugin && name == "yaml") ||
(kind == LanguagePlugin && name == "java") ||
(kind == ResourcePlugin && name == "pulumi-nodejs") ||
(kind == ResourcePlugin && name == "pulumi-python") ||
(kind == AnalyzerPlugin && name == "policy") ||
(kind == AnalyzerPlugin && name == "policy-python")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's too bad we don't have pattern matching.

This is completely fine as-is, but if you'd prefer for this to not be a series of &&/||s, you can use a map/set with a composite key.

type bundledPlugin struct{ kind PluginKind; name string }

You can either declare a map, e.g.,

var bundledPlugins = map[bundledPlugin]struct{}{
  {LanguagePlugin, "nodejs"}: {},
  {LanguagePlugin, "go"}: {},
  // ..
}

func IsPluginBundled(kind PluginKind, name string) bool {
	_, ok := bundledPlugins[bundledPlugin{kind, name}]
	return ok
}

Or if you'd prefer to not add the : {}, you can declare a slice and compile the map at init.

var bundledPlugins = []bundledPlugin{
	{LanguagePlugin, "nodejs"},
	{LanguagePlugin, "go"},
	// ...
}

// TODO: Do we have a shared set type we can use here
// and do something like `set.FromSlice[bundledPlugin](bundledPlugins)`?
var bundledPluginSet = make(map[bundledPlugin]struct{})

func init() {
	for _, p := range bundledPlugins {
		bundledPluginSet[p] = struct{}{}
	}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's simple to read, it's only in one place, we'll shrink this over time hopefully, probably fine as is.

abhinav and others added 2 commits August 7, 2023 15:32
Extracts the implementation of the `pulumi plugin install` command
into a separate pluginInstallCmd.
This mirrors the layout we use for some of the other commands,
and will allow injecting global dependencies from tests.

This change contains no behavioral changes.
This commit adds a check to `pulumi plugin install` to confirm
that the plugin is not bundled with Pulumi before performing the download.
Without this check, the CLI will produce a 404 error because the language
plugins aren't distributable independently of the CLI executable.

If PULUMI_DEV is set we'll skip the error and try to download anyway.
@Frassle
Copy link
Member

Frassle commented Aug 7, 2023

bors merge

bors bot added a commit that referenced this pull request Aug 7, 2023
12575: Check if Plugin is bundled before installing r=Frassle a=RobbieMcKinstry

<!--- 
Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation.
-->

# Description

This PR adds a check to `pulumi plugin install` to confirm that the plugin is not bundled with Pulumi before performing the download. Without this check, the CLI will produce a 404 error because the language plugins aren't distributable independently of the CLI executable.

<!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. -->

Fixes #11703

## Checklist

<!--- Please provide details if the checkbox below is to be left unchecked. -->
- [x] ~I have added tests that prove my fix is effective or that my feature works~ I have manually checked the relevant CLI commands.
<!--- 
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the Pulumi Service,
then the service should honor older versions of the CLI where this change would not exist.
You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version
  <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. -->


Co-authored-by: Abhinav Gupta <abhinav@pulumi.com>
Co-authored-by: Robbie McKinstry <robbie@pulumi.com>
@bors
Copy link
Contributor

bors bot commented Aug 7, 2023

Build failed:

@Frassle
Copy link
Member

Frassle commented Aug 8, 2023

bors retry

bors bot added a commit that referenced this pull request Aug 8, 2023
12575: Check if Plugin is bundled before installing r=Frassle a=RobbieMcKinstry

<!--- 
Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation.
-->

# Description

This PR adds a check to `pulumi plugin install` to confirm that the plugin is not bundled with Pulumi before performing the download. Without this check, the CLI will produce a 404 error because the language plugins aren't distributable independently of the CLI executable.

<!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. -->

Fixes #11703

## Checklist

<!--- Please provide details if the checkbox below is to be left unchecked. -->
- [x] ~I have added tests that prove my fix is effective or that my feature works~ I have manually checked the relevant CLI commands.
<!--- 
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the Pulumi Service,
then the service should honor older versions of the CLI where this change would not exist.
You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version
  <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. -->


Co-authored-by: Abhinav Gupta <abhinav@pulumi.com>
Co-authored-by: Robbie McKinstry <robbie@pulumi.com>
@bors
Copy link
Contributor

bors bot commented Aug 8, 2023

Build failed:

@Frassle
Copy link
Member

Frassle commented Aug 8, 2023

Setup actions failed

bors retry

@bors
Copy link
Contributor

bors bot commented Aug 8, 2023

Build succeeded!

The publicly hosted instance of bors-ng is deprecated and will go away soon.

If you want to self-host your own instance, instructions are here.
For more help, visit the forum.

If you want to switch to GitHub's built-in merge queue, visit their help page.

@bors bors bot merged commit 64aed77 into master Aug 8, 2023
52 checks passed
@bors bors bot deleted the mckinstry/#11703 branch August 8, 2023 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

404 getting pulumi-language-nodejs
5 participants