mirror of
https://github.com/turbot/steampipe.git
synced 2025-12-23 21:09:15 -05:00
Updates to plugin operations output including async update and install. Closes #1780. Closes #1778. Closes #1777. Closes #1776
This commit is contained in:
284
cmd/plugin.go
284
cmd/plugin.go
@@ -4,7 +4,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gosuri/uiprogress"
|
||||
"github.com/gosuri/uiprogress/util/strutil"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/turbot/go-kit/helpers"
|
||||
@@ -172,10 +175,15 @@ Example:
|
||||
return cmd
|
||||
}
|
||||
|
||||
// exitCode=1 For unknown errors resulting in panics
|
||||
// exitCode=2 For insufficient/wrong arguments passed in the command
|
||||
// exitCode=3 For errors related to loading state, loading version data or an issue contacting the update server.
|
||||
// exitCode=4 For plugin listing failures
|
||||
var pluginInstallSteps = []string{
|
||||
"Downloading",
|
||||
"Installing Plugin",
|
||||
"Installing Docs",
|
||||
"Installing Config",
|
||||
"Updating Steampipe",
|
||||
"Done",
|
||||
}
|
||||
|
||||
func runPluginInstallCmd(cmd *cobra.Command, args []string) {
|
||||
ctx := cmd.Context()
|
||||
utils.LogTime("runPluginInstallCmd install")
|
||||
@@ -191,7 +199,7 @@ func runPluginInstallCmd(cmd *cobra.Command, args []string) {
|
||||
// plugin names can be simple names ('aws') for "standard" plugins,
|
||||
// or full refs to the OCI image (us-docker.pkg.dev/steampipe/plugin/turbot/aws:1.0.0)
|
||||
plugins := append([]string{}, args...)
|
||||
installReports := make([]display.InstallReport, 0, len(plugins))
|
||||
installReports := make(display.PluginInstallReports, 0, len(plugins))
|
||||
|
||||
if len(plugins) == 0 {
|
||||
fmt.Println()
|
||||
@@ -207,60 +215,63 @@ func runPluginInstallCmd(cmd *cobra.Command, args []string) {
|
||||
fmt.Println()
|
||||
|
||||
statusSpinner := statushooks.NewStatusSpinner()
|
||||
progressBars := uiprogress.New()
|
||||
installWaitGroup := &sync.WaitGroup{}
|
||||
dataChannel := make(chan *display.PluginInstallReport, len(plugins))
|
||||
|
||||
for _, p := range plugins {
|
||||
isPluginExists, _ := plugin.Exists(p)
|
||||
if isPluginExists {
|
||||
installReports = append(installReports, display.InstallReport{
|
||||
Plugin: p,
|
||||
Skipped: true,
|
||||
SkipReason: constants.PluginAlreadyInstalled,
|
||||
IsUpdateReport: false,
|
||||
})
|
||||
continue
|
||||
}
|
||||
statusSpinner.SetStatus(fmt.Sprintf("Installing plugin: %s", p))
|
||||
image, err := plugin.Install(cmd.Context(), p)
|
||||
if err != nil {
|
||||
msg := ""
|
||||
if strings.HasSuffix(err.Error(), "not found") {
|
||||
msg = "Not found"
|
||||
} else {
|
||||
msg = err.Error()
|
||||
}
|
||||
installReports = append(installReports, display.InstallReport{
|
||||
Skipped: true,
|
||||
Plugin: p,
|
||||
SkipReason: msg,
|
||||
IsUpdateReport: false,
|
||||
})
|
||||
continue
|
||||
}
|
||||
versionString := ""
|
||||
if image.Config.Plugin.Version != "" {
|
||||
versionString = " v" + image.Config.Plugin.Version
|
||||
}
|
||||
org := image.Config.Plugin.Organization
|
||||
name := image.Config.Plugin.Name
|
||||
docURL := fmt.Sprintf("https://hub.steampipe.io/plugins/%s/%s", org, name)
|
||||
installReports = append(installReports, display.InstallReport{
|
||||
Skipped: false,
|
||||
Plugin: p,
|
||||
DocURL: docURL,
|
||||
Version: versionString,
|
||||
IsUpdateReport: false,
|
||||
})
|
||||
progressBars.Start()
|
||||
|
||||
for _, pluginName := range plugins {
|
||||
installWaitGroup.Add(1)
|
||||
bar := createProgressBar(pluginName, progressBars)
|
||||
go doPluginInstall(ctx, bar, pluginName, installWaitGroup, dataChannel)
|
||||
}
|
||||
go func() {
|
||||
installWaitGroup.Wait()
|
||||
close(dataChannel)
|
||||
}()
|
||||
for report := range dataChannel {
|
||||
installReports = append(installReports, report)
|
||||
}
|
||||
|
||||
progressBars.Stop()
|
||||
statusSpinner.UpdateSpinnerMessage("Refreshing connections...")
|
||||
refreshConnectionsIfNecessary(ctx, installReports, true)
|
||||
statusSpinner.Done()
|
||||
|
||||
refreshConnectionsIfNecessary(cmd.Context(), installReports, true)
|
||||
display.PrintInstallReports(installReports, false)
|
||||
|
||||
// a concluding blank line - since we always output multiple lines
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func doPluginInstall(ctx context.Context, bar *uiprogress.Bar, pluginName string, wg *sync.WaitGroup, returnChannel chan *display.PluginInstallReport) {
|
||||
var report *display.PluginInstallReport
|
||||
|
||||
pluginExists, _ := plugin.Exists(pluginName)
|
||||
if pluginExists {
|
||||
// set the bar to MAX
|
||||
bar.Set(len(pluginInstallSteps))
|
||||
// let the bar append itself with "Already Installed"
|
||||
bar.AppendFunc(func(b *uiprogress.Bar) string {
|
||||
return strutil.Resize(constants.PluginAlreadyInstalled, 20)
|
||||
})
|
||||
report = &display.PluginInstallReport{
|
||||
Plugin: pluginName,
|
||||
Skipped: true,
|
||||
SkipReason: constants.PluginAlreadyInstalled,
|
||||
IsUpdateReport: false,
|
||||
}
|
||||
} else {
|
||||
// let the bar append itself with the current installation step
|
||||
bar.AppendFunc(func(b *uiprogress.Bar) string {
|
||||
return strutil.Resize(pluginInstallSteps[b.Current()-1], 20)
|
||||
})
|
||||
report = installPlugin(ctx, pluginName, false, bar)
|
||||
}
|
||||
returnChannel <- report
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func runPluginUpdateCmd(cmd *cobra.Command, args []string) {
|
||||
ctx := cmd.Context()
|
||||
utils.LogTime("runPluginUpdateCmd install")
|
||||
@@ -302,7 +313,7 @@ func runPluginUpdateCmd(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
var runUpdatesFor []*versionfile.InstalledVersion
|
||||
updateReports := make([]display.InstallReport, 0, len(plugins))
|
||||
updateResults := make(display.PluginInstallReports, 0, len(plugins))
|
||||
|
||||
// a leading blank line - since we always output multiple lines
|
||||
fmt.Println()
|
||||
@@ -324,7 +335,7 @@ func runPluginUpdateCmd(cmd *cobra.Command, args []string) {
|
||||
if isExists {
|
||||
runUpdatesFor = append(runUpdatesFor, versionData.Plugins[ref.DisplayImageRef()])
|
||||
} else {
|
||||
updateReports = append(updateReports, display.InstallReport{
|
||||
updateResults = append(updateResults, &display.PluginInstallReport{
|
||||
Skipped: true,
|
||||
Plugin: p,
|
||||
SkipReason: constants.PluginNotInstalled,
|
||||
@@ -334,15 +345,14 @@ func runPluginUpdateCmd(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(plugins) == len(updateReports) {
|
||||
if len(plugins) == len(updateResults) {
|
||||
// we have report for all
|
||||
// this may happen if all given plugins are
|
||||
// not installed
|
||||
display.PrintInstallReports(updateReports, true)
|
||||
display.PrintInstallReports(updateResults, true)
|
||||
fmt.Println()
|
||||
return
|
||||
}
|
||||
|
||||
statusSpinner := statushooks.NewStatusSpinner(statushooks.WithMessage("Checking for available updates"))
|
||||
reports := plugin.GetUpdateReport(state.InstallationID, runUpdatesFor)
|
||||
statusSpinner.Done()
|
||||
@@ -355,60 +365,122 @@ func runPluginUpdateCmd(cmd *cobra.Command, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, report := range reports {
|
||||
shouldSkipUpdate, skipReason := plugin.SkipUpdate(report)
|
||||
if shouldSkipUpdate {
|
||||
updateReports = append(updateReports, display.InstallReport{
|
||||
Plugin: fmt.Sprintf("%s@%s", report.CheckResponse.Name, report.CheckResponse.Stream),
|
||||
Skipped: true,
|
||||
SkipReason: skipReason,
|
||||
IsUpdateReport: true,
|
||||
})
|
||||
continue
|
||||
}
|
||||
updateWaitGroup := &sync.WaitGroup{}
|
||||
dataChannel := make(chan *display.PluginInstallReport, len(reports))
|
||||
progressBars := uiprogress.New()
|
||||
progressBars.Start()
|
||||
|
||||
statusSpinner.SetStatus(fmt.Sprintf("Updating plugin %s...", report.CheckResponse.Name))
|
||||
image, err := plugin.Install(cmd.Context(), report.Plugin.Name)
|
||||
statusSpinner.Done()
|
||||
if err != nil {
|
||||
msg := ""
|
||||
if strings.HasSuffix(err.Error(), "not found") {
|
||||
msg = "Not found"
|
||||
} else {
|
||||
msg = err.Error()
|
||||
}
|
||||
updateReports = append(updateReports, display.InstallReport{
|
||||
Plugin: fmt.Sprintf("%s@%s", report.CheckResponse.Name, report.CheckResponse.Stream),
|
||||
Skipped: true,
|
||||
SkipReason: msg,
|
||||
IsUpdateReport: true,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
versionString := ""
|
||||
if image.Config.Plugin.Version != "" {
|
||||
versionString = " v" + image.Config.Plugin.Version
|
||||
}
|
||||
org := image.Config.Plugin.Organization
|
||||
name := image.Config.Plugin.Name
|
||||
docURL := fmt.Sprintf("https://hub.steampipe.io/plugins/%s/%s", org, name)
|
||||
updateReports = append(updateReports, display.InstallReport{
|
||||
Plugin: fmt.Sprintf("%s@%s", report.CheckResponse.Name, report.CheckResponse.Stream),
|
||||
Skipped: false,
|
||||
Version: versionString,
|
||||
DocURL: docURL,
|
||||
IsUpdateReport: true,
|
||||
})
|
||||
sorted := utils.SortedStringKeys(reports)
|
||||
for _, key := range sorted {
|
||||
report := reports[key]
|
||||
updateWaitGroup.Add(1)
|
||||
bar := createProgressBar(report.ShortName(), progressBars)
|
||||
go doPluginUpdate(ctx, bar, report, updateWaitGroup, dataChannel)
|
||||
}
|
||||
go func() {
|
||||
updateWaitGroup.Wait()
|
||||
close(dataChannel)
|
||||
}()
|
||||
for updateResult := range dataChannel {
|
||||
updateResults = append(updateResults, updateResult)
|
||||
}
|
||||
|
||||
refreshConnectionsIfNecessary(cmd.Context(), updateReports, false)
|
||||
display.PrintInstallReports(updateReports, true)
|
||||
refreshConnectionsIfNecessary(cmd.Context(), updateResults, false)
|
||||
progressBars.Stop()
|
||||
fmt.Println()
|
||||
display.PrintInstallReports(updateResults, true)
|
||||
|
||||
// a concluding blank line - since we always output multiple lines
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func doPluginUpdate(ctx context.Context, bar *uiprogress.Bar, pvr plugin.VersionCheckReport, wg *sync.WaitGroup, returnChannel chan *display.PluginInstallReport) {
|
||||
var report *display.PluginInstallReport
|
||||
|
||||
if skip, skipReason := plugin.SkipUpdate(pvr); skip {
|
||||
bar.AppendFunc(func(b *uiprogress.Bar) string {
|
||||
// set the progress bar to append itself with "Already Installed"
|
||||
return strutil.Resize(skipReason, 30)
|
||||
})
|
||||
// set the progress bar to the maximum
|
||||
bar.Set(len(pluginInstallSteps))
|
||||
report = &display.PluginInstallReport{
|
||||
Plugin: fmt.Sprintf("%s@%s", pvr.CheckResponse.Name, pvr.CheckResponse.Stream),
|
||||
Skipped: true,
|
||||
SkipReason: skipReason,
|
||||
IsUpdateReport: true,
|
||||
}
|
||||
} else {
|
||||
bar.AppendFunc(func(b *uiprogress.Bar) string {
|
||||
// set the progress bar to append itself with the step underway
|
||||
return strutil.Resize(pluginInstallSteps[b.Current()-1], 20)
|
||||
})
|
||||
report = installPlugin(ctx, pvr.Plugin.Name, true, bar)
|
||||
}
|
||||
returnChannel <- report
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func createProgressBar(plugin string, parentProgressBars *uiprogress.Progress) *uiprogress.Bar {
|
||||
bar := parentProgressBars.AddBar(len(pluginInstallSteps))
|
||||
bar.PrependFunc(func(b *uiprogress.Bar) string {
|
||||
return strutil.Resize(plugin, 20)
|
||||
})
|
||||
return bar
|
||||
}
|
||||
|
||||
func installPlugin(ctx context.Context, pluginName string, isUpdate bool, bar *uiprogress.Bar) *display.PluginInstallReport {
|
||||
// start a channel for progress publications from plugin.Install
|
||||
progress := make(chan struct{}, 5)
|
||||
defer func() {
|
||||
// close the progress channel
|
||||
close(progress)
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
// wait for a message on the progress channel
|
||||
<-progress
|
||||
// increment the progress bar
|
||||
bar.Incr()
|
||||
}
|
||||
}()
|
||||
|
||||
image, err := plugin.Install(ctx, pluginName, progress)
|
||||
org, name, stream := image.ImageRef.GetOrgNameAndStream()
|
||||
|
||||
if err != nil {
|
||||
msg := ""
|
||||
if isPluginNotFoundErr(err) {
|
||||
msg = "Not found"
|
||||
} else {
|
||||
msg = err.Error()
|
||||
}
|
||||
return &display.PluginInstallReport{
|
||||
Plugin: fmt.Sprintf("%s@%s", name, stream),
|
||||
Skipped: true,
|
||||
SkipReason: msg,
|
||||
IsUpdateReport: isUpdate,
|
||||
}
|
||||
}
|
||||
|
||||
versionString := ""
|
||||
if image.Config.Plugin.Version != "" {
|
||||
versionString = " v" + image.Config.Plugin.Version
|
||||
}
|
||||
docURL := fmt.Sprintf("https://hub.steampipe.io/plugins/%s/%s", org, name)
|
||||
return &display.PluginInstallReport{
|
||||
Plugin: fmt.Sprintf("%s@%s", name, stream),
|
||||
Skipped: false,
|
||||
Version: versionString,
|
||||
DocURL: docURL,
|
||||
IsUpdateReport: isUpdate,
|
||||
}
|
||||
}
|
||||
|
||||
func isPluginNotFoundErr(err error) bool {
|
||||
return strings.HasSuffix(err.Error(), "not found")
|
||||
}
|
||||
|
||||
func resolveUpdatePluginsFromArgs(args []string) ([]string, error) {
|
||||
plugins := append([]string{}, args...)
|
||||
|
||||
@@ -425,7 +497,7 @@ func resolveUpdatePluginsFromArgs(args []string) ([]string, error) {
|
||||
}
|
||||
|
||||
// start service if necessary and refresh connections
|
||||
func refreshConnectionsIfNecessary(ctx context.Context, reports []display.InstallReport, shouldReload bool) error {
|
||||
func refreshConnectionsIfNecessary(ctx context.Context, reports display.PluginInstallReports, shouldReload bool) error {
|
||||
// get count of skipped reports
|
||||
skipped := 0
|
||||
for _, report := range reports {
|
||||
@@ -523,11 +595,19 @@ func runPluginUninstallCmd(cmd *cobra.Command, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
reports := display.PluginRemoveReports{}
|
||||
spinner := statushooks.NewStatusSpinner(statushooks.WithMessage(fmt.Sprintf("Uninstalling %s", utils.Pluralize("plugin", len(args)))))
|
||||
for _, p := range args {
|
||||
if err := plugin.Remove(ctx, p, connectionMap); err != nil {
|
||||
spinner.SetStatus(fmt.Sprintf("Uninstalling %s", p))
|
||||
if report, err := plugin.Remove(ctx, p, connectionMap); err != nil {
|
||||
utils.ShowErrorWithMessage(ctx, err, fmt.Sprintf("Failed to uninstall plugin '%s'", p))
|
||||
} else {
|
||||
report.ShortName = p
|
||||
reports = append(reports, *report)
|
||||
}
|
||||
}
|
||||
spinner.Done()
|
||||
reports.Print()
|
||||
}
|
||||
|
||||
// returns a map of pluginFullName -> []{connections using pluginFullName}
|
||||
|
||||
@@ -2,6 +2,7 @@ package display
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/turbot/steampipe/constants"
|
||||
@@ -9,7 +10,14 @@ import (
|
||||
"github.com/turbot/steampipe/utils"
|
||||
)
|
||||
|
||||
type InstallReport struct {
|
||||
type PluginInstallReports []*PluginInstallReport
|
||||
|
||||
// making the type compatible with sort.Interface so that we can use the sort package utilities
|
||||
func (i PluginInstallReports) Len() int { return len(i) }
|
||||
func (i PluginInstallReports) Swap(lIdx, rIdx int) { i[lIdx], i[rIdx] = i[rIdx], i[lIdx] }
|
||||
func (i PluginInstallReports) Less(lIdx, rIdx int) bool { return i[lIdx].Plugin < i[rIdx].Plugin }
|
||||
|
||||
type PluginInstallReport struct {
|
||||
Skipped bool
|
||||
Plugin string
|
||||
SkipReason string
|
||||
@@ -18,14 +26,14 @@ type InstallReport struct {
|
||||
IsUpdateReport bool
|
||||
}
|
||||
|
||||
func (i *InstallReport) skipString() string {
|
||||
func (i *PluginInstallReport) skipString() string {
|
||||
ref := ociinstaller.NewSteampipeImageRef(i.Plugin)
|
||||
_, name, stream := ref.GetOrgNameAndStream()
|
||||
|
||||
return fmt.Sprintf("Plugin: %s\nReason: %s", fmt.Sprintf("%s@%s", name, stream), i.SkipReason)
|
||||
}
|
||||
|
||||
func (i *InstallReport) installString() string {
|
||||
func (i *PluginInstallReport) installString() string {
|
||||
thisReport := []string{}
|
||||
if i.IsUpdateReport {
|
||||
thisReport = append(
|
||||
@@ -54,7 +62,7 @@ func (i *InstallReport) installString() string {
|
||||
return strings.Join(thisReport, "\n")
|
||||
}
|
||||
|
||||
func (i *InstallReport) String() string {
|
||||
func (i *PluginInstallReport) String() string {
|
||||
if !i.Skipped {
|
||||
return i.installString()
|
||||
} else {
|
||||
@@ -63,10 +71,10 @@ func (i *InstallReport) String() string {
|
||||
}
|
||||
|
||||
// PrintInstallReports Prints out the installation reports onto the console
|
||||
func PrintInstallReports(reports []InstallReport, isUpdateReport bool) {
|
||||
installedOrUpdated := []InstallReport{}
|
||||
canBeInstalled := []InstallReport{}
|
||||
canBeUpdated := []InstallReport{}
|
||||
func PrintInstallReports(reports PluginInstallReports, isUpdateReport bool) {
|
||||
installedOrUpdated := PluginInstallReports{}
|
||||
canBeInstalled := PluginInstallReports{}
|
||||
canBeUpdated := PluginInstallReports{}
|
||||
|
||||
for _, report := range reports {
|
||||
report.IsUpdateReport = isUpdateReport
|
||||
@@ -79,7 +87,10 @@ func PrintInstallReports(reports []InstallReport, isUpdateReport bool) {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Stable(reports)
|
||||
|
||||
if len(installedOrUpdated) > 0 {
|
||||
fmt.Println()
|
||||
asString := []string{}
|
||||
for _, report := range installedOrUpdated {
|
||||
asString = append(asString, report.installString())
|
||||
@@ -91,19 +102,18 @@ func PrintInstallReports(reports []InstallReport, isUpdateReport bool) {
|
||||
skipCount := len(reports) - len(installedOrUpdated)
|
||||
asString := []string{}
|
||||
for _, report := range reports {
|
||||
if report.Skipped {
|
||||
if report.Skipped && report.SkipReason == constants.PluginNotInstalled {
|
||||
asString = append(asString, report.skipString())
|
||||
}
|
||||
}
|
||||
// some have skipped
|
||||
if len(installedOrUpdated) > 0 {
|
||||
fmt.Println()
|
||||
|
||||
if (len(canBeInstalled) + len(canBeUpdated)) > 0 {
|
||||
fmt.Printf(
|
||||
"\nSkipped the following %s:\n\n%s\n",
|
||||
utils.Pluralize("plugin", skipCount),
|
||||
strings.Join(asString, "\n\n"),
|
||||
)
|
||||
}
|
||||
fmt.Printf(
|
||||
"Skipped the following %s:\n\n%s\n",
|
||||
utils.Pluralize("plugin", skipCount),
|
||||
strings.Join(asString, "\n\n"),
|
||||
)
|
||||
|
||||
if len(canBeInstalled) > 0 {
|
||||
asString := []string{}
|
||||
73
display/plugin_remove.go
Normal file
73
display/plugin_remove.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/turbot/steampipe/constants"
|
||||
"github.com/turbot/steampipe/ociinstaller"
|
||||
"github.com/turbot/steampipe/steampipeconfig/modconfig"
|
||||
"github.com/turbot/steampipe/utils"
|
||||
)
|
||||
|
||||
type PluginRemoveReport struct {
|
||||
Image *ociinstaller.SteampipeImageRef
|
||||
ShortName string
|
||||
Connections []modconfig.Connection
|
||||
}
|
||||
|
||||
type PluginRemoveReports []PluginRemoveReport
|
||||
|
||||
func (r PluginRemoveReports) Print() {
|
||||
length := len(r)
|
||||
staleConnections := []modconfig.Connection{}
|
||||
if length > 0 {
|
||||
fmt.Printf("\nUninstalled %s:\n", utils.Pluralize("plugin", length))
|
||||
for _, report := range r {
|
||||
org, name, _ := report.Image.GetOrgNameAndStream()
|
||||
fmt.Printf("* %s/%s\n", org, name)
|
||||
staleConnections = append(staleConnections, report.Connections...)
|
||||
|
||||
// sort the connections by line number while we are at it!
|
||||
sort.SliceStable(report.Connections, func(i, j int) bool {
|
||||
left := report.Connections[i]
|
||||
right := report.Connections[j]
|
||||
return left.DeclRange.Start.Line < right.DeclRange.Start.Line
|
||||
})
|
||||
}
|
||||
fmt.Println()
|
||||
staleLength := len(staleConnections)
|
||||
uniqueFiles := map[string]bool{}
|
||||
// get the unique files
|
||||
if staleLength > 0 {
|
||||
for _, report := range r {
|
||||
for _, conn := range report.Connections {
|
||||
uniqueFiles[conn.DeclRange.Filename] = true
|
||||
}
|
||||
}
|
||||
|
||||
str := append([]string{}, fmt.Sprintf(
|
||||
"Please remove %s %s to continue using steampipe:",
|
||||
utils.Pluralize("this", len(uniqueFiles)),
|
||||
utils.Pluralize("connection", len(uniqueFiles)),
|
||||
))
|
||||
|
||||
str = append(str, "")
|
||||
|
||||
for file := range uniqueFiles {
|
||||
str = append(str, fmt.Sprintf(" * %s", constants.Bold(file)))
|
||||
for _, report := range r {
|
||||
for _, conn := range report.Connections {
|
||||
if conn.DeclRange.Filename == file {
|
||||
str = append(str, fmt.Sprintf(" '%s' (line %2d)", conn.Name, conn.DeclRange.Start.Line))
|
||||
}
|
||||
}
|
||||
}
|
||||
str = append(str, "")
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(str, "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
58
go.mod
58
go.mod
@@ -54,31 +54,6 @@ require (
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require github.com/rivo/uniseg v0.2.0 // indirect
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.22 // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||
github.com/containerd/continuity v0.2.0 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/prometheus/client_golang v1.7.1 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/turbot/steampipe-plugin-sdk/v3 v3.0.1
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
rsc.io/letsencrypt v0.0.3 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
@@ -107,11 +82,12 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.4.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
|
||||
@@ -131,7 +107,7 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||
github.com/leodido/go-urn v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect; indirectmake
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mattn/go-tty v0.0.3 // indirect
|
||||
@@ -149,6 +125,7 @@ require (
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/term v1.1.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
@@ -165,9 +142,36 @@ require (
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.22 // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||
github.com/containerd/continuity v0.2.0 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/prometheus/client_golang v1.7.1 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/turbot/steampipe-plugin-sdk/v3 v3.0.1
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
|
||||
rsc.io/letsencrypt v0.0.3 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gosuri/uilive v0.0.4 // indirect
|
||||
github.com/gosuri/uiprogress v0.0.1
|
||||
)
|
||||
|
||||
replace github.com/c-bata/go-prompt => github.com/turbot/go-prompt v0.2.6-steampipe.0.20211124090719-0709bc8d8ce2
|
||||
|
||||
7
go.sum
7
go.sum
@@ -450,6 +450,10 @@ github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY=
|
||||
github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI=
|
||||
github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJSxw=
|
||||
github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
@@ -483,9 +487,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
|
||||
github.com/hashicorp/go-plugin v1.4.1/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
|
||||
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.2 h1:AoISa4P4IsW0/m4T6St8Yw38gTl5GtBAgfkhYh1xAz4=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
|
||||
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/turbot/steampipe/filepaths"
|
||||
@@ -17,35 +18,49 @@ import (
|
||||
)
|
||||
|
||||
// InstallPlugin installs a plugin from an OCI Image
|
||||
func InstallPlugin(ctx context.Context, imageRef string) (*SteampipeImage, error) {
|
||||
func InstallPlugin(ctx context.Context, imageRef string, sub chan struct{}) (*SteampipeImage, error) {
|
||||
tempDir := NewTempDir(filepaths.EnsurePluginDir())
|
||||
defer tempDir.Delete()
|
||||
defer func() {
|
||||
// send a last beacon to signal completion
|
||||
sub <- struct{}{}
|
||||
tempDir.Delete()
|
||||
}()
|
||||
|
||||
ref := NewSteampipeImageRef(imageRef)
|
||||
imageDownloader := NewOciDownloader()
|
||||
|
||||
sub <- struct{}{}
|
||||
image, err := imageDownloader.Download(ctx, ref, ImageTypePlugin, tempDir.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sub <- struct{}{}
|
||||
if err = installPluginBinary(image, tempDir.Path); err != nil {
|
||||
return nil, fmt.Errorf("plugin installation failed: %s", err)
|
||||
}
|
||||
sub <- struct{}{}
|
||||
if err = installPluginDocs(image, tempDir.Path); err != nil {
|
||||
return nil, fmt.Errorf("plugin installation failed: %s", err)
|
||||
}
|
||||
sub <- struct{}{}
|
||||
if err = installPluginConfigFiles(image, tempDir.Path); err != nil {
|
||||
return nil, fmt.Errorf("plugin installation failed: %s", err)
|
||||
}
|
||||
|
||||
sub <- struct{}{}
|
||||
if err := updateVersionFilePlugin(image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
|
||||
var versionFileUpdateLock = &sync.Mutex{}
|
||||
|
||||
func updateVersionFilePlugin(image *SteampipeImage) error {
|
||||
versionFileUpdateLock.Lock()
|
||||
defer versionFileUpdateLock.Unlock()
|
||||
|
||||
timeNow := versionfile.FormatTime(time.Now())
|
||||
v, err := versionfile.LoadPluginVersionFile()
|
||||
if err != nil {
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/turbot/steampipe/display"
|
||||
"github.com/turbot/steampipe/filepaths"
|
||||
"github.com/turbot/steampipe/ociinstaller"
|
||||
"github.com/turbot/steampipe/ociinstaller/versionfile"
|
||||
"github.com/turbot/steampipe/statushooks"
|
||||
"github.com/turbot/steampipe/steampipeconfig/modconfig"
|
||||
"github.com/turbot/steampipe/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -22,11 +22,12 @@ const (
|
||||
)
|
||||
|
||||
// Remove removes an installed plugin
|
||||
func Remove(ctx context.Context, image string, pluginConnections map[string][]modconfig.Connection) error {
|
||||
func Remove(ctx context.Context, image string, pluginConnections map[string][]modconfig.Connection) (*display.PluginRemoveReport, error) {
|
||||
statushooks.SetStatus(ctx, fmt.Sprintf("Removing plugin %s", image))
|
||||
defer statushooks.Done(ctx)
|
||||
|
||||
fullPluginName := ociinstaller.NewSteampipeImageRef(image).DisplayImageRef()
|
||||
imageRef := ociinstaller.NewSteampipeImageRef(image)
|
||||
fullPluginName := imageRef.DisplayImageRef()
|
||||
|
||||
// are any connections using this plugin???
|
||||
conns := pluginConnections[fullPluginName]
|
||||
@@ -34,54 +35,23 @@ func Remove(ctx context.Context, image string, pluginConnections map[string][]mo
|
||||
installedTo := filepath.Join(filepaths.EnsurePluginDir(), filepath.FromSlash(fullPluginName))
|
||||
_, err := os.Stat(installedTo)
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("plugin '%s' not found", image)
|
||||
return nil, fmt.Errorf("plugin '%s' not found", image)
|
||||
}
|
||||
// remove from file system
|
||||
err = os.RemoveAll(installedTo)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update the version file
|
||||
v, err := versionfile.LoadPluginVersionFile()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
delete(v.Plugins, fullPluginName)
|
||||
err = v.Save()
|
||||
|
||||
// store the filenames of the config files, that have the connections
|
||||
var files = map[int]string{}
|
||||
if len(conns) > 0 {
|
||||
for i, con := range conns {
|
||||
files[i] = con.DeclRange.Filename
|
||||
}
|
||||
}
|
||||
connFiles := Unique(files)
|
||||
|
||||
if len(connFiles) > 0 {
|
||||
|
||||
str := []string{fmt.Sprintf("\nUninstalled plugin %s\n\nNote: the following %s %s %s steampipe %s using the '%s' plugin:", image, utils.Pluralize("file", len(connFiles)), utils.Pluralize("has", len(connFiles)), utils.Pluralize("a", len(conns)), utils.Pluralize("connection", len(conns)), image)}
|
||||
for _, file := range connFiles {
|
||||
str = append(str, fmt.Sprintf("\n \t* file: %s", file))
|
||||
for _, conn := range conns {
|
||||
if conn.DeclRange.Filename == file {
|
||||
str = append(
|
||||
str,
|
||||
fmt.Sprintf(
|
||||
"\t connection: '%s' (line %d)",
|
||||
conn.Name,
|
||||
conn.DeclRange.Start.Line,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
str = append(str, fmt.Sprintf("\nPlease remove %s to continue using steampipe", utils.Pluralize("it", len(connFiles))))
|
||||
statushooks.Message(ctx, str...)
|
||||
fmt.Println()
|
||||
}
|
||||
return err
|
||||
return &display.PluginRemoveReport{Connections: conns, Image: imageRef}, err
|
||||
}
|
||||
|
||||
// Exists looks up the version file and reports whether a plugin is already installed
|
||||
@@ -99,8 +69,8 @@ func Exists(plugin string) (bool, error) {
|
||||
}
|
||||
|
||||
// Install installs a plugin in the local file system
|
||||
func Install(ctx context.Context, plugin string) (*ociinstaller.SteampipeImage, error) {
|
||||
image, err := ociinstaller.InstallPlugin(ctx, plugin)
|
||||
func Install(ctx context.Context, plugin string, sub chan struct{}) (*ociinstaller.SteampipeImage, error) {
|
||||
image, err := ociinstaller.InstallPlugin(ctx, plugin, sub)
|
||||
return image, err
|
||||
}
|
||||
|
||||
|
||||
13
utils/map.go
13
utils/map.go
@@ -1,5 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// MergeStringMaps merges 'new' onto old. Any vakue existing in new but not old is added to old
|
||||
// NOTE this mutates old
|
||||
func MergeStringMaps(old, new map[string]string) map[string]string {
|
||||
@@ -17,3 +21,12 @@ func MergeStringMaps(old, new map[string]string) map[string]string {
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
func SortedStringKeys[V any](m map[string]V) []string {
|
||||
keys := []string{}
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user