mirror of
https://github.com/turbot/steampipe.git
synced 2025-12-22 03:18:58 -05:00
147 lines
4.9 KiB
Go
147 lines
4.9 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/turbot/go-kit/files"
|
|
"github.com/turbot/pipe-fittings/v2/filepaths"
|
|
"github.com/turbot/pipe-fittings/v2/ociinstaller"
|
|
"github.com/turbot/pipe-fittings/v2/plugin"
|
|
"github.com/turbot/pipe-fittings/v2/statushooks"
|
|
"github.com/turbot/pipe-fittings/v2/versionfile"
|
|
"github.com/turbot/steampipe-plugin-sdk/v5/sperr"
|
|
)
|
|
|
|
// Remove removes an installed plugin
|
|
func Remove(ctx context.Context, image string, pluginConnections map[string][]PluginConnection) (*PluginRemoveReport, error) {
|
|
statushooks.SetStatus(ctx, fmt.Sprintf("Removing plugin %s", image))
|
|
|
|
imageRef := ociinstaller.NewImageRef(image)
|
|
fullPluginName := imageRef.DisplayImageRef()
|
|
|
|
// are any connections using this plugin???
|
|
conns := pluginConnections[fullPluginName]
|
|
|
|
installedTo := filepath.Join(filepaths.EnsurePluginDir(), filepath.FromSlash(fullPluginName))
|
|
_, err := os.Stat(installedTo)
|
|
if os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("plugin '%s' not found", image)
|
|
}
|
|
// remove from file system
|
|
err = os.RemoveAll(installedTo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// update the version file
|
|
v, err := versionfile.LoadPluginVersionFile(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
delete(v.Plugins, fullPluginName)
|
|
err = v.Save()
|
|
|
|
return &PluginRemoveReport{Connections: conns, Image: imageRef}, err
|
|
}
|
|
|
|
// Install installs a plugin in the local file system
|
|
func Install(ctx context.Context, plugin plugin.ResolvedPluginVersion, sub chan struct{}, baseImageRef string, mediaTypesProvider ociinstaller.MediaTypeProvider, opts ...ociinstaller.PluginInstallOption) (*ociinstaller.OciImage[*ociinstaller.PluginImage, *ociinstaller.PluginImageConfig], error) {
|
|
// Note: we pass the plugin info as strings here rather than passing the ResolvedPluginVersion struct as that causes circular dependency
|
|
image, err := ociinstaller.InstallPlugin(ctx, plugin.GetVersionTag(), plugin.Constraint, sub, baseImageRef, mediaTypesProvider, opts...)
|
|
return image, err
|
|
}
|
|
|
|
// PluginListItem is a struct representing an item in the list of plugins
|
|
type PluginListItem struct {
|
|
Name string
|
|
Version *plugin.PluginVersionString
|
|
Connections []string
|
|
}
|
|
|
|
// List returns all installed plugins
|
|
func List(ctx context.Context, pluginConnectionMap map[string][]PluginConnection, pluginVersions map[string]*versionfile.InstalledVersion) ([]PluginListItem, error) {
|
|
var items []PluginListItem
|
|
|
|
pluginBinaries, err := files.ListFilesWithContext(ctx, filepaths.EnsurePluginDir(), &files.ListOptions{
|
|
Include: []string{"**/*.plugin"},
|
|
Flags: files.AllRecursive,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// we have the plugin binary paths
|
|
for _, pluginBinary := range pluginBinaries {
|
|
parent := filepath.Dir(pluginBinary)
|
|
fullPluginName, err := filepath.Rel(filepaths.EnsurePluginDir(), parent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// for local plugin
|
|
item := PluginListItem{
|
|
Name: fullPluginName,
|
|
Version: plugin.LocalPluginVersionString(),
|
|
}
|
|
// check if this plugin is recorded in plugin versions
|
|
installation, found := pluginVersions[fullPluginName]
|
|
if found {
|
|
// if not a local plugin, get the semver version
|
|
if !detectLocalPlugin(installation, pluginBinary) {
|
|
item.Version, err = plugin.NewPluginVersionString(installation.Version)
|
|
if err != nil {
|
|
return nil, sperr.WrapWithMessage(err, "could not evaluate plugin version %s", installation.Version)
|
|
}
|
|
}
|
|
|
|
if pluginConnectionMap != nil {
|
|
// extract only the connection names
|
|
var connectionNames []string
|
|
for _, connection := range pluginConnectionMap[fullPluginName] {
|
|
connectionName := connection.GetDisplayName()
|
|
|
|
connectionNames = append(connectionNames, connectionName)
|
|
}
|
|
item.Connections = connectionNames
|
|
}
|
|
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
// detectLocalPlugin returns true if the modTime of the `pluginBinary` is after the installation date as recorded in the installation data
|
|
// this may happen when a plugin is installed from the registry, but is then compiled from source
|
|
func detectLocalPlugin(installation *versionfile.InstalledVersion, pluginBinary string) bool {
|
|
installDate, err := time.Parse(time.RFC3339, installation.InstallDate)
|
|
if err != nil {
|
|
log.Printf("[WARN] could not parse install date for %s: %s", installation.Name, installation.InstallDate)
|
|
return false
|
|
}
|
|
|
|
// truncate to second
|
|
// otherwise, comparisons may get skewed because of the
|
|
// underlying monotonic clock
|
|
installDate = installDate.Truncate(time.Second)
|
|
|
|
// get the modtime of the plugin binary
|
|
stat, err := os.Lstat(pluginBinary)
|
|
if err != nil {
|
|
log.Printf("[WARN] could not parse install date for %s: %s", installation.Name, installation.InstallDate)
|
|
return false
|
|
}
|
|
modTime := stat.ModTime().
|
|
// truncate to second
|
|
// otherwise, comparisons may get skewed because of the
|
|
// underlying monotonic clock
|
|
Truncate(time.Second)
|
|
|
|
return installDate.Before(modTime)
|
|
}
|