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  }