-
Notifications
You must be signed in to change notification settings - Fork 18
[CFX-4746] Added remote plugin structure and commands #340
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
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
4df524d
[CFX-4746] Added remote plugin structure and commands
carsongee f3ca0ab
fixup! [CFX-4746] Added remote plugin structure and commands
carsongee e42c8f4
UX cleanup
carsongee 10538ff
Added commands to package and publish plugins
carsongee 6bfa542
fixup! Added commands to package and publish plugins
carsongee 3a7005f
Added real plugin from apps
carsongee 1a70703
Corrected missing description when running dr self plugin package
carsongee aab4de7
Updated so that authentication works with apps
carsongee 8904ec7
Added support for authentication in script output manifests
carsongee 0c45e3c
Add backup and rollback for plugin upgrades
carsongee 53673ea
Review based feedback -- checkpoint 1
carsongee d0ae11f
Renamed everything to registry
carsongee 8c96af4
Restore if installation fails
carsongee 3de2cc4
Switched to Mastermind/semver from homegrown version
carsongee 47bc24d
fixup! Renamed everything to registry
carsongee 903ee63
Made plugin prefix a constant
carsongee fe1a048
Corrected revert fiasco
carsongee faa7221
Corrected paths to run from the right module and use XDG_
carsongee dc83909
Fix CVE in extract
carsongee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,3 +54,4 @@ docs/uv.lock | |
| *.code-workspace | ||
| *.vscode/ | ||
| *.claude/ | ||
| __pycache__/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| // Copyright 2026 DataRobot, Inc. and its affiliates. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package install | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/datarobot/cli/cmd/plugin/shared" | ||
| "github.com/datarobot/cli/internal/plugin" | ||
| "github.com/datarobot/cli/tui" | ||
| "github.com/spf13/cobra" | ||
| "github.com/spf13/viper" | ||
| ) | ||
|
|
||
| var ( | ||
| versionConstraint string | ||
| registryURL string | ||
| listPlugins bool | ||
| listVersions bool | ||
| ) | ||
|
|
||
| func Cmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "install <plugin-name>", | ||
| Short: "Install a plugin from the remote registry", | ||
| Long: `Install a plugin from the remote plugin registry. | ||
|
|
||
| The plugin name should match an entry in the plugin registry. | ||
| Use --version to specify a version constraint: | ||
| - Exact version: 1.2.3 | ||
| - Caret (compatible): ^1.2.3 (any 1.x.x >= 1.2.3) | ||
| - Tilde (patch-level): ~1.2.3 (any 1.2.x >= 1.2.3) | ||
| - Minimum: >=1.0.0 | ||
| - Latest: latest (default)`, | ||
| Example: ` dr plugin install apps | ||
| dr plugin install apps | ||
| dr plugin install apps --version 1.0.0 | ||
| dr plugin install apps --version "^1.0.0" | ||
| dr plugin install apps --versions | ||
| dr plugin install --list`, | ||
| Args: cobra.MaximumNArgs(1), | ||
| RunE: runInstall, | ||
| } | ||
|
|
||
| cmd.Flags().StringVar(&versionConstraint, "version", "latest", "Version constraint") | ||
| cmd.Flags().BoolVar(&listVersions, "versions", false, "List available versions for a plugin") | ||
| cmd.Flags().StringVar(®istryURL, "registry-url", plugin.PluginRegistryURL, "URL of the plugin registry") | ||
| cmd.Flags().BoolVar(&listPlugins, "list", false, "List available plugins from the registry") | ||
|
|
||
| return cmd | ||
| } | ||
|
|
||
| func runInstall(_ *cobra.Command, args []string) error { | ||
| finalRegistryURL := shared.NormalizeRegistryURL(registryURL) | ||
| if viper.GetBool("verbose") { | ||
| fmt.Printf("Fetching plugin registry from %s...\n", finalRegistryURL) | ||
| } | ||
|
|
||
| registry, baseURL, err := plugin.FetchRegistry(finalRegistryURL) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to fetch plugin registry: %w", err) | ||
| } | ||
|
|
||
| // Handle --list flag or no args (show list by default) | ||
| if listPlugins || len(args) == 0 { | ||
| fmt.Println() | ||
| fmt.Println(tui.SubTitleStyle.Render("Available Plugins")) | ||
| printAvailablePlugins(registry) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| pluginName := args[0] | ||
|
|
||
| // Handle --versions flag | ||
| if listVersions { | ||
| pluginEntry, ok := registry.Plugins[pluginName] | ||
| if !ok { | ||
| printAvailablePlugins(registry) | ||
|
|
||
| return fmt.Errorf("plugin %q not found in registry", pluginName) | ||
| } | ||
|
|
||
| fmt.Println() | ||
| fmt.Println(tui.SubTitleStyle.Render("Available Versions for " + pluginName)) | ||
| printAvailableVersions(pluginEntry.Versions) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| fmt.Println() | ||
| fmt.Println(tui.SubTitleStyle.Render("Installing Plugin")) | ||
|
|
||
| pluginEntry, ok := registry.Plugins[pluginName] | ||
| if !ok { | ||
| printAvailablePlugins(registry) | ||
|
|
||
| return fmt.Errorf("plugin %q not found in registry", pluginName) | ||
| } | ||
|
|
||
| version, err := plugin.ResolveVersion(pluginEntry.Versions, versionConstraint) | ||
| if err != nil { | ||
| printAvailableVersions(pluginEntry.Versions) | ||
|
|
||
| return fmt.Errorf("failed to resolve version: %w", err) | ||
| } | ||
|
|
||
| fmt.Printf("Installing %s version %s...\n", pluginEntry.Name, version.Version) | ||
| fmt.Printf("Downloading from: %s/%s\n", baseURL, version.URL) | ||
|
|
||
| if err := plugin.InstallPlugin(pluginEntry, *version, baseURL); err != nil { | ||
| return fmt.Errorf("failed to install plugin: %w", err) | ||
| } | ||
|
|
||
| fmt.Println() | ||
| fmt.Println(tui.SuccessStyle.Render("✓ Successfully installed " + pluginEntry.Name + " " + version.Version)) | ||
| fmt.Println() | ||
| fmt.Printf("Run `dr %s --help` to get started.\n", pluginEntry.Name) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func printAvailablePlugins(registry *plugin.PluginRegistry) { | ||
| for name, p := range registry.Plugins { | ||
| latestVersion := "-" | ||
| if len(p.Versions) > 0 { | ||
| latestVersion = p.Versions[0].Version | ||
| } | ||
|
|
||
| fmt.Printf(" - %s (%s): %s\n", name, latestVersion, p.Description) | ||
| } | ||
| } | ||
|
|
||
| func printAvailableVersions(versions []plugin.RegistryVersion) { | ||
| for _, v := range versions { | ||
| fmt.Printf(" - %s\n", v.Version) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Copyright 2026 DataRobot, Inc. and its affiliates. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package shared | ||
|
|
||
| // NormalizeRegistryURL ensures the URL ends with index.json | ||
| func NormalizeRegistryURL(url string) string { | ||
| if len(url) > 0 && url[len(url)-1] == '/' { | ||
| return url + "index.json" | ||
| } | ||
|
|
||
| if len(url) > 5 && url[len(url)-5:] != ".json" { | ||
| return url + "/index.json" | ||
| } | ||
|
|
||
| return url | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // Copyright 2026 DataRobot, Inc. and its affiliates. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package uninstall | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/datarobot/cli/internal/plugin" | ||
| "github.com/datarobot/cli/tui" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func Cmd() *cobra.Command { | ||
| return &cobra.Command{ | ||
| Use: "uninstall <plugin-name>", | ||
| Short: "Uninstall a managed plugin", | ||
| Long: "Remove a plugin that was installed via `dr plugin install`.", | ||
| Example: " dr plugin uninstall apps", | ||
| Args: cobra.ExactArgs(1), | ||
| RunE: runUninstall, | ||
| } | ||
| } | ||
|
|
||
| func runUninstall(_ *cobra.Command, args []string) error { | ||
| pluginName := args[0] | ||
|
|
||
| installed, err := plugin.GetInstalledPlugins() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get installed plugins: %w", err) | ||
| } | ||
|
|
||
| var found bool | ||
|
|
||
| for _, p := range installed { | ||
| if p.Name == pluginName { | ||
| found = true | ||
|
|
||
| break | ||
| } | ||
| } | ||
|
|
||
| if !found { | ||
| return fmt.Errorf("plugin %q is not installed as a managed plugin", pluginName) | ||
| } | ||
|
|
||
| fmt.Printf("Uninstalling %s...\n", pluginName) | ||
|
|
||
| if err := plugin.UninstallPlugin(pluginName); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| fmt.Println() | ||
| fmt.Println(tui.SuccessStyle.Render("✓ Successfully uninstalled " + pluginName)) | ||
| fmt.Println() | ||
|
|
||
| return nil | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.