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 }