@@ -482,6 +482,7 @@ func (pc *PluginsCommand) pluginsContractCommand() *cobra.Command {
482482func (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+
708753func (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
914969func (pc * PluginsCommand ) validateModuleRequirement (_ * internal.Plugin ) error {
0 commit comments