github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/workspace/workspace_require.go (about) 1 package workspace 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sort" 8 "strings" 9 10 "github.com/Masterminds/semver/v3" 11 "github.com/turbot/steampipe/pkg/constants" 12 "github.com/turbot/steampipe/pkg/plugin" 13 "github.com/turbot/steampipe/pkg/utils" 14 ) 15 16 func (w *Workspace) CheckRequiredPluginsInstalled(ctx context.Context) error { 17 // get the list of all installed plugins 18 installedPlugins, err := plugin.GetInstalledPlugins(ctx) 19 if err != nil { 20 return err 21 } 22 23 // get the list of all the required plugins 24 requiredPlugins := w.getRequiredPlugins() 25 26 var pluginsNotInstalled []requiredPluginVersion 27 28 for name, requiredVersion := range requiredPlugins { 29 var req = requiredPluginVersion{plugin: name} 30 req.SetRequiredVersion(requiredVersion) 31 32 if installedVersion, found := installedPlugins[name]; found { 33 if installedVersion.IsLocal() { 34 req.installedVersion = installedVersion.String() 35 continue 36 } 37 smv := installedVersion.Semver() 38 req.SetInstalledVersion(smv) 39 40 if !requiredVersion.Check(smv) { 41 pluginsNotInstalled = append(pluginsNotInstalled, req) 42 } 43 } else { 44 req.installedVersion = "none" 45 pluginsNotInstalled = append(pluginsNotInstalled, req) 46 } 47 48 } 49 if len(pluginsNotInstalled) > 0 { 50 return errors.New(pluginVersionError(pluginsNotInstalled)) 51 } 52 53 return nil 54 } 55 56 func (w *Workspace) getRequiredPlugins() map[string]*semver.Constraints { 57 if w.Mod.Require != nil { 58 requiredPluginVersions := w.Mod.Require.Plugins 59 requiredVersion := make(map[string]*semver.Constraints) 60 for _, pluginVersion := range requiredPluginVersions { 61 requiredVersion[pluginVersion.ShortName()] = pluginVersion.Constraint 62 } 63 return requiredVersion 64 } 65 return nil 66 } 67 68 type requiredPluginVersion struct { 69 plugin string 70 requiredVersion string 71 installedVersion string 72 } 73 74 func (v *requiredPluginVersion) SetRequiredVersion(requiredVersion *semver.Constraints) { 75 if requiredVersion == nil { 76 v.requiredVersion = "*" 77 } else { 78 requiredVersionString := requiredVersion.String() 79 v.requiredVersion = requiredVersionString 80 } 81 } 82 83 func (v *requiredPluginVersion) SetInstalledVersion(installedVersion *semver.Version) { 84 v.installedVersion = installedVersion.String() 85 } 86 87 func pluginVersionError(pluginsNotInstalled []requiredPluginVersion) string { 88 failureCount := len(pluginsNotInstalled) 89 var notificationLines = []string{ 90 fmt.Sprintf("%d mod plugin %s not satisfied. ", failureCount, utils.Pluralize("requirement", failureCount)), 91 "", 92 } 93 longestNameLength := 0 94 for _, report := range pluginsNotInstalled { 95 thisName := report.plugin 96 if len(thisName) > longestNameLength { 97 longestNameLength = len(thisName) 98 } 99 } 100 101 // sort alphabetically 102 sort.Slice(pluginsNotInstalled, func(i, j int) bool { 103 return pluginsNotInstalled[i].plugin < pluginsNotInstalled[j].plugin 104 }) 105 106 // build first part of string 107 // recheck longest names 108 longestVersionLength := 0 109 110 var notInstalledStrings = make([]string, len(pluginsNotInstalled)) 111 for i, req := range pluginsNotInstalled { 112 format := fmt.Sprintf(" %%-%ds %%-2s", longestNameLength) 113 notInstalledStrings[i] = fmt.Sprintf( 114 format, 115 req.plugin, 116 req.installedVersion, 117 ) 118 119 if len(notInstalledStrings[i]) > longestVersionLength { 120 longestVersionLength = len(notInstalledStrings[i]) 121 } 122 } 123 124 for i, req := range pluginsNotInstalled { 125 format := fmt.Sprintf("%%-%ds → %%2s", longestVersionLength) 126 notificationLines = append(notificationLines, fmt.Sprintf( 127 format, 128 notInstalledStrings[i], 129 constants.Bold(req.requiredVersion), 130 )) 131 } 132 133 // add help message for missing plugins 134 msg := fmt.Sprintf("\nPlease %s the %s with: \n", checkInstallOrUpdate(pluginsNotInstalled), utils.Pluralize("plugin", len(pluginsNotInstalled))) 135 notificationLines = append(notificationLines, msg) 136 137 for i, req := range pluginsNotInstalled { 138 _, p, _ := strings.Cut(req.plugin, "/") 139 140 // check if plugin needs to be installed/updated 141 if strings.Contains(notInstalledStrings[i], "none") { 142 notificationLines = append(notificationLines, fmt.Sprintf( 143 " steampipe plugin install %s", p, 144 )) 145 } else { 146 notificationLines = append(notificationLines, fmt.Sprintf( 147 " steampipe plugin update %s", p, 148 )) 149 } 150 } 151 152 // add blank line (tactical - bold the empty string to force it to print blank line as part of error) 153 notificationLines = append(notificationLines, fmt.Sprintf("%s", constants.Bold(""))) 154 155 return strings.Join(notificationLines, "\n") 156 } 157 158 // function to check whether the missing plugins require to be installed or updated, or both 159 func checkInstallOrUpdate(pluginsNotInstalled []requiredPluginVersion) string { 160 var updateFlag, installFlag bool 161 162 for _, req := range pluginsNotInstalled { 163 if strings.Contains(req.installedVersion, "none") { 164 installFlag = true 165 } else { 166 updateFlag = true 167 } 168 } 169 170 if updateFlag { 171 if installFlag { 172 return "install/update" 173 } else { 174 return "update" 175 } 176 } 177 return "install" 178 }