Updates to plugin operations output including async update and install. Closes #1780. Closes #1778. Closes #1777. Closes #1776

This commit is contained in:
Binaek Sarkar
2022-04-19 16:32:14 +05:30
committed by GitHub
parent 3e019ebedd
commit 259a88f191
8 changed files with 358 additions and 190 deletions

View File

@@ -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}

View File

@@ -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
View 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
View File

@@ -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
View File

@@ -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=

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}