Skip to content

Commit fc886ff

Browse files
committed
--resolve-plugins-conflicts
Signed-off-by: Smyslov Maxim <[email protected]>
1 parent dc58865 commit fc886ff

File tree

2 files changed

+87
-32
lines changed

2 files changed

+87
-32
lines changed

cmd/plugins/plugin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func NewPluginCommand(commandName string, description string, aliases []string,
9090

9191
if !installed {
9292
fmt.Println("Not installed, installing...")
93-
err = pc.InstallPlugin(cmd.Context(), commandName, "", -1)
93+
err = pc.InstallPlugin(cmd.Context(), commandName, nil)
9494
if err != nil {
9595
fmt.Println("Error installing:", err)
9696
return

cmd/plugins/plugins.go

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ func (pc *PluginsCommand) pluginsContractCommand() *cobra.Command {
482482
func (pc *PluginsCommand) pluginsInstallCommand() *cobra.Command {
483483
var version string
484484
var useMajor int
485+
var resolvePluginsConflicts bool
485486

486487
cmd := &cobra.Command{
487488
Use: "install [plugin-name]",
@@ -492,30 +493,49 @@ func (pc *PluginsCommand) pluginsInstallCommand() *cobra.Command {
492493
pluginName := args[0]
493494
ctx := cmd.Context()
494495

495-
return pc.InstallPlugin(ctx, pluginName, version, useMajor)
496+
return pc.InstallPlugin(ctx, pluginName, &installPluginOptions{
497+
version: version,
498+
useMajor: useMajor,
499+
resolvePluginsConflicts: resolvePluginsConflicts,
500+
})
496501
},
497502
}
498503

499504
cmd.Flags().StringVar(&version, "version", "", "Specific version of the plugin to install")
500505
cmd.Flags().IntVar(&useMajor, "use-major", -1, "Use specific major version (e.g., 1, 2)")
506+
cmd.Flags().BoolVar(&resolvePluginsConflicts, "resolve-plugins-conflicts", false, "Resolve conflicts between installed plugins")
501507

502508
return cmd
503509
}
504510

511+
type installPluginOptions struct {
512+
version string
513+
useMajor int
514+
resolvePluginsConflicts bool
515+
}
516+
505517
// function checks if plugin can be installed, creates folders layout and then installs plugin, creates symlink "current" and caches contract.json
506-
// if version (e.g. v1.0.0) is not specified - use latest version
507-
// if useMajor > -1 (can be 0) - use specific major version
508-
func (pc *PluginsCommand) InstallPlugin(ctx context.Context, pluginName, version string, useMajor int) error {
518+
// version - semver version string (e.g. v1.0.0), default: "" (use latest version)
519+
// useMajor - major version to install, default: -1 (use latest major version)
520+
// resolvePluginsConflicts - resolve conflicts between installed plugins, default: false
521+
func (pc *PluginsCommand) InstallPlugin(ctx context.Context, pluginName string, opts *installPluginOptions) error {
509522
// check if version is specified
510523
var installVersion *semver.Version
511-
var err error
512-
if version != "" {
513-
installVersion, err = semver.NewVersion(version)
524+
525+
if opts == nil {
526+
opts = &installPluginOptions{
527+
useMajor: -1,
528+
}
529+
}
530+
531+
if opts.version != "" {
532+
var err error
533+
installVersion, err = semver.NewVersion(opts.version)
514534
if err != nil {
515535
return fmt.Errorf("failed to parse version: %w", err)
516536
}
517537

518-
return pc.installPlugin(ctx, pluginName, installVersion, useMajor)
538+
return pc.installPlugin(ctx, pluginName, installVersion, opts.resolvePluginsConflicts)
519539
}
520540

521541
versions, err := pc.service.ListPluginTags(ctx, pluginName)
@@ -524,10 +544,10 @@ func (pc *PluginsCommand) InstallPlugin(ctx context.Context, pluginName, version
524544
return fmt.Errorf("failed to list plugin tags: %w", err)
525545
}
526546

527-
if useMajor >= 0 {
528-
versions = pc.filterMajorVersion(versions, useMajor)
547+
if opts.useMajor >= 0 {
548+
versions = pc.filterMajorVersion(versions, opts.useMajor)
529549
if len(versions) == 0 {
530-
return fmt.Errorf("no versions found for major version: %d", useMajor)
550+
return fmt.Errorf("no versions found for major version: %d", opts.useMajor)
531551
}
532552
}
533553

@@ -537,10 +557,10 @@ func (pc *PluginsCommand) InstallPlugin(ctx context.Context, pluginName, version
537557
return fmt.Errorf("failed to fetch latest version: %w", err)
538558
}
539559

540-
return pc.installPlugin(ctx, pluginName, installVersion, useMajor)
560+
return pc.installPlugin(ctx, pluginName, installVersion, opts.resolvePluginsConflicts)
541561
}
542562

543-
func (pc *PluginsCommand) installPlugin(ctx context.Context, pluginName string, version *semver.Version, useMajor int) error {
563+
func (pc *PluginsCommand) installPlugin(ctx context.Context, pluginName string, version *semver.Version, resolvePluginsConflicts bool) error {
544564
// create plugin directory if it doesn't exist
545565
// example path: /opt/deckhouse/lib/deckhouse-cli/plugins/example-plugin
546566
pluginDir := path.Join(pc.pluginDirectory, "plugins", pluginName)
@@ -583,9 +603,6 @@ func (pc *PluginsCommand) installPlugin(ctx context.Context, pluginName string,
583603

584604
fmt.Printf("Installing plugin: %s\n", pluginName)
585605
fmt.Printf("Tag: %s\n", tag)
586-
if useMajor >= 0 {
587-
fmt.Printf("Using major version: %d\n", useMajor)
588-
}
589606

590607
// get contract
591608
plugin, err := pc.service.GetPluginContract(ctx, pluginName, tag)
@@ -598,10 +615,20 @@ func (pc *PluginsCommand) installPlugin(ctx context.Context, pluginName string,
598615

599616
// validate requirements
600617
pc.logger.Debug("validating requirements", slog.String("plugin", plugin.Name))
601-
err = pc.validateRequirements(plugin)
618+
failedConstraints, err := pc.validateRequirements(plugin)
602619
if err != nil {
603620
return fmt.Errorf("failed to validate requirements: %w", err)
604621
}
622+
if len(failedConstraints) > 0 && !resolvePluginsConflicts {
623+
return fmt.Errorf("plugin requirements not satisfied")
624+
}
625+
if len(failedConstraints) > 0 && resolvePluginsConflicts {
626+
// try to resolve conflicts
627+
err = pc.resolvePluginConflicts(ctx, failedConstraints)
628+
if err != nil {
629+
return fmt.Errorf("failed to resolve conflicts: %w", err)
630+
}
631+
}
605632

606633
// check if binary exists (if yes - rename it to .old)
607634
// example path: /opt/deckhouse/lib/deckhouse-cli/plugins/example-plugin/v1/example-plugin
@@ -705,6 +732,24 @@ func (pc *PluginsCommand) fetchLatestVersion(ctx context.Context, pluginName str
705732
return latestVersion, nil
706733
}
707734

735+
func (pc *PluginsCommand) resolvePluginConflicts(ctx context.Context, failedConstraints FailedConstraints) error {
736+
installOptions := &installPluginOptions{
737+
resolvePluginsConflicts: true,
738+
}
739+
740+
// for each failed constraint, try to install the plugin
741+
for pluginName := range failedConstraints {
742+
pc.logger.Debug("resolving plugin conflict", slog.String("plugin", pluginName))
743+
744+
err := pc.InstallPlugin(ctx, pluginName, installOptions)
745+
if err != nil {
746+
return fmt.Errorf("failed to install plugin: %w", err)
747+
}
748+
}
749+
750+
return nil
751+
}
752+
708753
func (pc *PluginsCommand) pluginsUpdateCommand() *cobra.Command {
709754
cmd := &cobra.Command{
710755
Use: "update [plugin-name]",
@@ -717,7 +762,7 @@ func (pc *PluginsCommand) pluginsUpdateCommand() *cobra.Command {
717762

718763
ctx := cmd.Context()
719764

720-
return pc.InstallPlugin(ctx, pluginName, "", -1)
765+
return pc.InstallPlugin(ctx, pluginName, nil)
721766
},
722767
}
723768

@@ -743,7 +788,7 @@ func (pc *PluginsCommand) pluginsUpdateAllCommand() *cobra.Command {
743788
}
744789

745790
for _, plugin := range plugins {
746-
err := pc.InstallPlugin(ctx, plugin.Name(), "", -1)
791+
err := pc.InstallPlugin(ctx, plugin.Name(), nil)
747792
if err != nil {
748793
return fmt.Errorf("failed to update plugin: %w", err)
749794
}
@@ -859,56 +904,66 @@ func (pc *PluginsCommand) getInstalledPluginVersion(pluginName string) (*semver.
859904
return version, nil
860905
}
861906

862-
func (pc *PluginsCommand) validateRequirements(plugin *internal.Plugin) error {
907+
// map of plugin name to failed constraints
908+
type FailedConstraints map[string]*semver.Constraints
909+
910+
func (pc *PluginsCommand) validateRequirements(plugin *internal.Plugin) (FailedConstraints, error) {
863911
// validate plugin requirements
864912
pc.logger.Debug("validating plugin requirements", slog.String("plugin", plugin.Name))
865913

866-
err := pc.validatePluginRequirement(plugin)
914+
result := make(FailedConstraints)
915+
916+
var err error
917+
result, err = pc.validatePluginRequirement(plugin)
867918
if err != nil {
868-
return fmt.Errorf("plugin requirements: %w", err)
919+
return result, fmt.Errorf("plugin requirements: %w", err)
869920
}
870921

871922
// validate module requirements
872923
pc.logger.Debug("validating module requirements", slog.String("plugin", plugin.Name))
873924

874925
err = pc.validateModuleRequirement(plugin)
875926
if err != nil {
876-
return fmt.Errorf("module requirements: %w", err)
927+
return result, fmt.Errorf("module requirements: %w", err)
877928
}
878929

879-
return nil
930+
return result, nil
880931
}
881932

882-
func (pc *PluginsCommand) validatePluginRequirement(plugin *internal.Plugin) error {
933+
func (pc *PluginsCommand) validatePluginRequirement(plugin *internal.Plugin) (FailedConstraints, error) {
934+
result := make(FailedConstraints)
935+
883936
for _, pluginRequirement := range plugin.Requirements.Plugins {
884937
// check if plugin is installed
885938
installed, err := pc.checkInstalled(pluginRequirement.Name)
886939
if err != nil {
887-
return fmt.Errorf("failed to check if plugin is installed: %w", err)
940+
return nil, fmt.Errorf("failed to check if plugin is installed: %w", err)
888941
}
889942
if !installed {
890-
return fmt.Errorf("plugin %s is not installed", pluginRequirement.Name)
943+
result[pluginRequirement.Name] = nil
944+
continue
891945
}
892946

893947
// check constraint
894948
if pluginRequirement.Constraint != "" {
895949
installedVersion, err := pc.getInstalledPluginVersion(pluginRequirement.Name)
896950
if err != nil {
897-
return fmt.Errorf("failed to get installed version: %w", err)
951+
return nil, fmt.Errorf("failed to get installed version: %w", err)
898952
}
899953

900954
constraint, err := semver.NewConstraint(pluginRequirement.Constraint)
901955
if err != nil {
902-
return fmt.Errorf("failed to parse constraint: %w", err)
956+
return nil, fmt.Errorf("failed to parse constraint: %w", err)
903957
}
904958

905959
if !constraint.Check(installedVersion) {
906-
return fmt.Errorf("plugin %s version %s does not satisfy constraint %s", pluginRequirement.Name, installedVersion.Original(), pluginRequirement.Constraint)
960+
pc.logger.Warn("plugin requirement not satisfied", slog.String("plugin", plugin.Name), slog.String("requirement", pluginRequirement.Name), slog.String("constraint", pluginRequirement.Constraint), slog.String("installedVersion", installedVersion.Original()))
961+
result[pluginRequirement.Name] = constraint
907962
}
908963
}
909964
}
910965

911-
return nil
966+
return result, nil
912967
}
913968

914969
func (pc *PluginsCommand) validateModuleRequirement(_ *internal.Plugin) error {

0 commit comments

Comments
 (0)