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

     1  package task
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"sort"
     9  
    10  	"github.com/Masterminds/semver/v3"
    11  	"github.com/fatih/color"
    12  	"github.com/olekukonko/tablewriter"
    13  	"github.com/turbot/steampipe/pkg/constants"
    14  	"github.com/turbot/steampipe/pkg/plugin"
    15  	"github.com/turbot/steampipe/pkg/utils"
    16  )
    17  
    18  type AvailableVersionCache struct {
    19  	StructVersion uint32                               `json:"struct_version"`
    20  	CliCache      *CLIVersionCheckResponse             `json:"cli_version"`
    21  	PluginCache   map[string]plugin.VersionCheckReport `json:"plugin_version"`
    22  }
    23  
    24  func (av *AvailableVersionCache) asTable(ctx context.Context) (*bytes.Buffer, error) {
    25  	notificationLines, err := av.buildNotification(ctx)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  	notificationTable := utils.Map(notificationLines, func(line string) []string {
    30  		return []string{line}
    31  	})
    32  
    33  	if len(notificationLines) == 0 {
    34  		return nil, nil
    35  	}
    36  
    37  	// create a buffer writer to pass to the tablewriter
    38  	// so that we can capture the output
    39  	var buffer bytes.Buffer // c
    40  
    41  	table := tablewriter.NewWriter(&buffer)
    42  	table.SetHeader([]string{})                // no headers please
    43  	table.SetAlignment(tablewriter.ALIGN_LEFT) // we align to the left
    44  	table.SetAutoWrapText(false)               // let's not wrap the text
    45  	table.SetBorder(true)                      // there needs to be a border to provide the dialog feel
    46  	table.AppendBulk(notificationTable)        // Add Bulk Data
    47  
    48  	// render the table into the buffer
    49  	table.Render()
    50  	return &buffer, nil
    51  }
    52  
    53  func (av *AvailableVersionCache) buildNotification(ctx context.Context) ([]string, error) {
    54  	cliLines, err := av.cliNotificationMessage()
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	pluginLines := av.pluginNotificationMessage(ctx)
    59  	// convert notificationLines into an array of arrays
    60  	// since that's what our table renderer expects
    61  	return append(cliLines, pluginLines...), nil
    62  }
    63  
    64  func (av *AvailableVersionCache) cliNotificationMessage() ([]string, error) {
    65  	info := av.CliCache
    66  	if info == nil {
    67  		return nil, nil
    68  	}
    69  
    70  	if info.NewVersion == "" {
    71  		return nil, nil
    72  	}
    73  
    74  	newVersion, err := semver.NewVersion(info.NewVersion)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	currentVersion, err := semver.NewVersion(currentVersion)
    80  	if err != nil {
    81  		fmt.Println(fmt.Errorf("there's something wrong with the Current Version"))
    82  		fmt.Println(err)
    83  	}
    84  
    85  	if newVersion.GreaterThan(currentVersion) {
    86  		var downloadURLColor = color.New(color.FgYellow)
    87  		var notificationLines = []string{
    88  			"",
    89  			fmt.Sprintf("A new version of Steampipe is available! %s → %s", constants.Bold(currentVersion), constants.Bold(newVersion)),
    90  			fmt.Sprintf("You can update by downloading from %s", downloadURLColor.Sprint("https://steampipe.io/downloads")),
    91  			"",
    92  		}
    93  		return notificationLines, nil
    94  	}
    95  	return nil, nil
    96  }
    97  
    98  func ppNotificationLines() []string {
    99  	var notificationLines = []string{
   100  		"",
   101  		fmt.Sprintf("%s Steampipe mods and dashboards are now separately available in Powerpipe (%s), a new open-source project (%s).", constants.Bold("Introducing Powerpipe:"), color.YellowString("https://powerpipe.io"), color.YellowString("https://github.com/turbot/powerpipe")),
   102  		"",
   103  		fmt.Sprintf("The steampipe mod, check and dashboard commands %s in a future version. Migration guide - %s", constants.Bold("will be removed"), color.YellowString("https://powerpipe.io/blog/migrating-from-steampipe")),
   104  		"",
   105  	}
   106  	return notificationLines
   107  }
   108  
   109  func (av *AvailableVersionCache) pluginNotificationMessage(ctx context.Context) []string {
   110  	var pluginsToUpdate []plugin.VersionCheckReport
   111  
   112  	for _, r := range av.PluginCache {
   113  		if plugin.UpdateRequired(r) {
   114  			pluginsToUpdate = append(pluginsToUpdate, r)
   115  		}
   116  	}
   117  	notificationLines := []string{}
   118  	if len(pluginsToUpdate) > 0 {
   119  		notificationLines = av.getPluginNotificationLines(pluginsToUpdate)
   120  	}
   121  	return notificationLines
   122  }
   123  
   124  func (av *AvailableVersionCache) getPluginNotificationLines(reports []plugin.VersionCheckReport) []string {
   125  	var notificationLines = []string{
   126  		"",
   127  		"Updated versions of the following plugins are available:",
   128  		"",
   129  	}
   130  	longestNameLength := 0
   131  	for _, report := range reports {
   132  		thisName := report.ShortName()
   133  		if len(thisName) > longestNameLength {
   134  			longestNameLength = len(thisName)
   135  		}
   136  	}
   137  
   138  	// sort alphabetically
   139  	sort.Slice(reports, func(i, j int) bool {
   140  		return reports[i].ShortName() < reports[j].ShortName()
   141  	})
   142  
   143  	for _, report := range reports {
   144  		thisName := report.ShortName()
   145  		line := ""
   146  		if len(report.Plugin.Version) == 0 {
   147  			format := fmt.Sprintf("  %%-%ds @ %%-10s  →  %%10s", longestNameLength)
   148  			line = fmt.Sprintf(
   149  				format,
   150  				thisName,
   151  				report.CheckResponse.Constraint,
   152  				constants.Bold(report.CheckResponse.Version),
   153  			)
   154  		} else {
   155  			version := report.CheckResponse.Version
   156  			format := fmt.Sprintf("  %%-%ds @ %%-10s       %%10s → %%-10s", longestNameLength)
   157  			// an arm64 binary of the plugin might exist for the same version
   158  			if report.Plugin.Version == report.CheckResponse.Version {
   159  				version = fmt.Sprintf("%s (arm64)", version)
   160  			}
   161  			line = fmt.Sprintf(
   162  				format,
   163  				thisName,
   164  				report.CheckResponse.Constraint,
   165  				constants.Bold(report.Plugin.Version),
   166  				constants.Bold(version),
   167  			)
   168  		}
   169  		notificationLines = append(notificationLines, line)
   170  	}
   171  	notificationLines = append(notificationLines, "")
   172  	notificationLines = append(notificationLines, fmt.Sprintf("You can update by running %s", constants.Bold("steampipe plugin update --all")))
   173  	notificationLines = append(notificationLines, "")
   174  
   175  	return notificationLines
   176  }
   177  
   178  func ppNoptificationAsTable(colWidth int) (*tablewriter.Table, error) {
   179  	notificationLines := ppNotificationLines()
   180  
   181  	notificationTable := utils.Map(notificationLines, func(line string) []string {
   182  		return []string{line}
   183  	})
   184  
   185  	if len(notificationLines) == 0 {
   186  		return nil, nil
   187  	}
   188  
   189  	table := tablewriter.NewWriter(os.Stdout)
   190  	table.SetHeader([]string{})                // no headers please
   191  	table.SetAlignment(tablewriter.ALIGN_LEFT) // we align to the left
   192  	table.SetAutoWrapText(true)                // let's wrap the text
   193  	table.SetBorder(true)                      // there needs to be a border to provide the dialog feel
   194  	table.SetColWidth(colWidth - 4)            // set the column width which matches the steampipe notification table width
   195  	table.AppendBulk(notificationTable)        // Add Bulk Data
   196  
   197  	return table, nil
   198  }