github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/plugin/actions.go (about)

     1  package plugin
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/turbot/go-kit/files"
    12  	"github.com/turbot/steampipe-plugin-sdk/v5/sperr"
    13  	"github.com/turbot/steampipe/pkg/filepaths"
    14  	"github.com/turbot/steampipe/pkg/ociinstaller"
    15  	"github.com/turbot/steampipe/pkg/ociinstaller/versionfile"
    16  	"github.com/turbot/steampipe/pkg/statushooks"
    17  	"github.com/turbot/steampipe/pkg/steampipeconfig"
    18  	"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
    19  )
    20  
    21  // Remove removes an installed plugin
    22  func Remove(ctx context.Context, image string, pluginConnections map[string][]*modconfig.Connection) (*steampipeconfig.PluginRemoveReport, error) {
    23  	statushooks.SetStatus(ctx, fmt.Sprintf("Removing plugin %s", image))
    24  
    25  	imageRef := ociinstaller.NewSteampipeImageRef(image)
    26  	fullPluginName := imageRef.DisplayImageRef()
    27  
    28  	// are any connections using this plugin???
    29  	conns := pluginConnections[fullPluginName]
    30  
    31  	installedTo := filepath.Join(filepaths.EnsurePluginDir(), filepath.FromSlash(fullPluginName))
    32  	_, err := os.Stat(installedTo)
    33  	if os.IsNotExist(err) {
    34  		return nil, fmt.Errorf("plugin '%s' not found", image)
    35  	}
    36  	// remove from file system
    37  	err = os.RemoveAll(installedTo)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	// update the version file
    43  	v, err := versionfile.LoadPluginVersionFile(ctx)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	delete(v.Plugins, fullPluginName)
    48  	err = v.Save()
    49  
    50  	return &steampipeconfig.PluginRemoveReport{Connections: conns, Image: imageRef}, err
    51  }
    52  
    53  // Exists looks up the version file and reports whether a plugin is already installed
    54  func Exists(ctx context.Context, plugin string) (bool, error) {
    55  	versionData, err := versionfile.LoadPluginVersionFile(ctx)
    56  	if err != nil {
    57  		return false, err
    58  	}
    59  
    60  	imageRef := ociinstaller.NewSteampipeImageRef(plugin)
    61  
    62  	// lookup in the version data
    63  	_, found := versionData.Plugins[imageRef.DisplayImageRef()]
    64  	return found, nil
    65  }
    66  
    67  // Install installs a plugin in the local file system
    68  func Install(ctx context.Context, plugin ResolvedPluginVersion, sub chan struct{}, opts ...ociinstaller.PluginInstallOption) (*ociinstaller.SteampipeImage, error) {
    69  	// Note: we pass the plugin info as strings here rather than passing the ResolvedPluginVersion struct as that causes circular dependency
    70  	image, err := ociinstaller.InstallPlugin(ctx, plugin.GetVersionTag(), plugin.Constraint, sub, opts...)
    71  	return image, err
    72  }
    73  
    74  // PluginListItem is a struct representing an item in the list of plugins
    75  type PluginListItem struct {
    76  	Name        string
    77  	Version     *modconfig.PluginVersionString
    78  	Connections []string
    79  }
    80  
    81  // List returns all installed plugins
    82  func List(ctx context.Context, pluginConnectionMap map[string][]*modconfig.Connection) ([]PluginListItem, error) {
    83  	var items []PluginListItem
    84  
    85  	// retrieve the plugin version data from steampipe config
    86  	pluginVersions := steampipeconfig.GlobalConfig.PluginVersions
    87  
    88  	pluginBinaries, err := files.ListFilesWithContext(ctx, filepaths.EnsurePluginDir(), &files.ListOptions{
    89  		Include: []string{"**/*.plugin"},
    90  		Flags:   files.AllRecursive,
    91  	})
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	// we have the plugin binary paths
    97  	for _, pluginBinary := range pluginBinaries {
    98  		parent := filepath.Dir(pluginBinary)
    99  		fullPluginName, err := filepath.Rel(filepaths.EnsurePluginDir(), parent)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  		// for local plugin
   104  		item := PluginListItem{
   105  			Name:    fullPluginName,
   106  			Version: modconfig.LocalPluginVersionString(),
   107  		}
   108  		// check if this plugin is recorded in plugin versions
   109  		installation, found := pluginVersions[fullPluginName]
   110  		if found {
   111  			// if not a local plugin, get the semver version
   112  			if !detectLocalPlugin(installation, pluginBinary) {
   113  				item.Version, err = modconfig.NewPluginVersionString(installation.Version)
   114  				if err != nil {
   115  					return nil, sperr.WrapWithMessage(err, "could not evaluate plugin version %s", installation.Version)
   116  				}
   117  			}
   118  
   119  			if pluginConnectionMap != nil {
   120  				// extract only the connection names
   121  				var connectionNames []string
   122  				for _, connection := range pluginConnectionMap[fullPluginName] {
   123  					connectionName := connection.Name
   124  					if connection.ImportDisabled() {
   125  						connectionName = fmt.Sprintf("%s(disabled)", connectionName)
   126  					}
   127  					connectionNames = append(connectionNames, connectionName)
   128  				}
   129  				item.Connections = connectionNames
   130  			}
   131  
   132  			items = append(items, item)
   133  		}
   134  	}
   135  
   136  	return items, nil
   137  }
   138  
   139  // detectLocalPlugin returns true if the modTime of the `pluginBinary` is after the installation date as recorded in the installation data
   140  // this may happen when a plugin is installed from the registry, but is then compiled from source
   141  func detectLocalPlugin(installation *versionfile.InstalledVersion, pluginBinary string) bool {
   142  	installDate, err := time.Parse(time.RFC3339, installation.InstallDate)
   143  	if err != nil {
   144  		log.Printf("[WARN] could not parse install date for %s: %s", installation.Name, installation.InstallDate)
   145  		return false
   146  	}
   147  
   148  	// truncate to second
   149  	// otherwise, comparisons may get skewed because of the
   150  	// underlying monotonic clock
   151  	installDate = installDate.Truncate(time.Second)
   152  
   153  	// get the modtime of the plugin binary
   154  	stat, err := os.Lstat(pluginBinary)
   155  	if err != nil {
   156  		log.Printf("[WARN] could not parse install date for %s: %s", installation.Name, installation.InstallDate)
   157  		return false
   158  	}
   159  	modTime := stat.ModTime().
   160  		// truncate to second
   161  		// otherwise, comparisons may get skewed because of the
   162  		// underlying monotonic clock
   163  		Truncate(time.Second)
   164  
   165  	return installDate.Before(modTime)
   166  }