Skip to content

Commit

Permalink
feat(cli): add showing package version in list cmd (glasskube#200)
Browse files Browse the repository at this point in the history
Signed-off-by: Jakob Steiner <[email protected]>
  • Loading branch information
kosmoz committed Feb 20, 2024
1 parent 13f9e60 commit fe856bc
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 70 deletions.
111 changes: 86 additions & 25 deletions cmd/glasskube/cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ package cmd
import (
"fmt"
"os"
"strings"

"github.com/glasskube/glasskube/internal/cliutils"
"github.com/glasskube/glasskube/internal/config"
"github.com/glasskube/glasskube/pkg/client"
"github.com/glasskube/glasskube/pkg/list"
"github.com/spf13/cobra"
)

var listCmdOptions = struct {
ListInstalledOnly bool
ShowDescription bool
ShowLatestVersion bool
More bool
}{}

var listCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls", "l"},
Expand All @@ -19,9 +26,14 @@ var listCmd = &cobra.Command{
"as well as their installation status in your cluster.\nYou can choose to only show installed packages.",
PreRun: cliutils.SetupClientContext(true),
Run: func(cmd *cobra.Command, args []string) {
if listCmdOptions.More {
listCmdOptions.ShowLatestVersion = true
listCmdOptions.ShowDescription = true
}

pkgClient := client.FromContext(cmd.Context())
listOptions := list.DefaultListOptions
if config.ListInstalledOnly {
if listCmdOptions.ListInstalledOnly {
listOptions |= list.OnlyInstalled
}
pkgs, err := list.GetPackagesWithStatus(pkgClient, cmd.Context(), listOptions)
Expand All @@ -30,46 +42,95 @@ var listCmd = &cobra.Command{
os.Exit(1)
return
}
if config.ListInstalledOnly && len(pkgs) == 0 {
if listCmdOptions.ListInstalledOnly && len(pkgs) == 0 {
fmt.Println("There are currently no packages installed in your cluster.\n" +
"Run \"glasskube help install\" to get started.")
} else {
printPackageTable(pkgs, config.Verbose)
printPackageTable(pkgs)
}
},
}

func init() {
listCmd.PersistentFlags().BoolVarP(&config.ListInstalledOnly, "installed", "i", false,
listCmd.PersistentFlags().BoolVarP(&listCmdOptions.ListInstalledOnly, "installed", "i", false,
"list only installed packages")
listCmd.PersistentFlags().BoolVar(&config.Verbose, "show-description", false,
"show additional information to the packages")
listCmd.PersistentFlags().BoolVar(&listCmdOptions.ShowDescription, "show-description", false,
"show the package description")
listCmd.PersistentFlags().BoolVar(&listCmdOptions.ShowLatestVersion, "show-latest", false,
"show the latest version of packages if available")
listCmd.PersistentFlags().BoolVarP(&listCmdOptions.More, "more", "m", false,
"show additional information about packages (like --show-description --show-latest)")

listCmd.MarkFlagsMutuallyExclusive("show-description", "more")
listCmd.MarkFlagsMutuallyExclusive("show-latest", "more")

RootCmd.AddCommand(listCmd)
}

func printPackageTable(packages []*list.PackageTeaserWithStatus, verbose bool) {
header := make([]string, 3)
header[0] = "NAME"
header[1] = "STATUS"
if verbose {
header[2] = "DESCRIPTION"
func printPackageTable(packages []*list.PackageWithStatus) {
header := []string{"NAME", "STATUS", "VERSION"}
if listCmdOptions.ShowLatestVersion {
header = append(header, "LATEST VERSION")
}
if listCmdOptions.ShowDescription {
header = append(header, "DESCRIPTION")
}
cliutils.PrintPackageTable(os.Stdout,
err := cliutils.PrintPackageTable(os.Stdout,
packages,
header,
func(pkg *list.PackageTeaserWithStatus) []string {
row := make([]string, 3)
row[0] = pkg.PackageName
var statusStr string
if pkg.Status == nil {
statusStr = "Not installed"
} else {
statusStr = pkg.Status.Status
func(pkg *list.PackageWithStatus) []string {
row := []string{pkg.Name, statusString(*pkg), versionString(*pkg)}
if listCmdOptions.ShowLatestVersion {
row = append(row, pkg.LatestVersion)
}
row[1] = statusStr
if verbose {
row[2] = pkg.ShortDescription
if listCmdOptions.ShowDescription {
row = append(row, pkg.ShortDescription)
}
return row
})
if err != nil {
fmt.Fprintf(os.Stderr, "There was an error displaying the package table:\n%v\n(This is a bug)\n", err)
os.Exit(1)
}
}

func statusString(pkg list.PackageWithStatus) string {
if pkg.Status != nil {
return pkg.Status.Status
} else {
return "Not installed"
}
}

func versionString(pkg list.PackageWithStatus) string {
if pkg.Package != nil {
specVersion := pkg.Package.Spec.PackageInfo.Version
statusVersion := pkg.Package.Status.Version
repoVersion := pkg.LatestVersion

if statusVersion != "" {
versionAddons := []string{}
if specVersion != "" && statusVersion != specVersion {
versionAddons = append(versionAddons, fmt.Sprintf("%v desired", specVersion))
}
if repoVersion != "" && statusVersion != repoVersion {
versionAddons = append(versionAddons, fmt.Sprintf("%v available", repoVersion))
}
if len(versionAddons) > 0 {
return fmt.Sprintf("%v (%v)", statusVersion, strings.Join(versionAddons, ", "))
} else {
return statusVersion
}
} else if specVersion != "" {
if specVersion != repoVersion {
return fmt.Sprintf("%v (%v available)", specVersion, repoVersion)
} else {
return specVersion
}
} else {
return "n/a"
}
} else {
return ""
}
}
9 changes: 6 additions & 3 deletions cmd/glasskube/cmd/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import (
"os"

"github.com/glasskube/glasskube/internal/cliutils"
"github.com/glasskube/glasskube/internal/config"
pkgClient "github.com/glasskube/glasskube/pkg/client"
"github.com/glasskube/glasskube/pkg/list"
"github.com/glasskube/glasskube/pkg/uninstall"
"github.com/spf13/cobra"
)

var uninstallCmdOptions = struct {
ForceUninstall bool
}{}

var uninstallCmd = &cobra.Command{
Use: "uninstall [package-name]",
Short: "Uninstall a package",
Expand All @@ -27,7 +30,7 @@ var uninstallCmd = &cobra.Command{
os.Exit(1)
return
}
proceed := config.ForceUninstall || cliutils.YesNoPrompt(
proceed := uninstallCmdOptions.ForceUninstall || cliutils.YesNoPrompt(
fmt.Sprintf(
"%v will be removed from your cluster (%v). Are you sure?",
pkgName,
Expand All @@ -51,7 +54,7 @@ var uninstallCmd = &cobra.Command{
}

func init() {
uninstallCmd.PersistentFlags().BoolVar(&config.ForceUninstall, "force", false,
uninstallCmd.PersistentFlags().BoolVar(&uninstallCmdOptions.ForceUninstall, "force", false,
"skip the confirmation question and uninstall right away")
RootCmd.AddCommand(uninstallCmd)
}
20 changes: 12 additions & 8 deletions internal/cliutils/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,28 @@ import (
"github.com/glasskube/glasskube/pkg/list"
)

var tabSep = "\t"

func PrintPackageTable(
w io.Writer,
packages []*list.PackageTeaserWithStatus,
packages []*list.PackageWithStatus,
cols []string,
getColsOfRow func(pkg *list.PackageTeaserWithStatus) []string,
) {
getColsOfRow func(pkg *list.PackageWithStatus) []string,
) error {
tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
sep := "\t"
fmt.Fprintf(tw, "%s\n", strings.Join(cols, sep))
fmt.Fprintln(tw, strings.Join(cols, tabSep))
var sb strings.Builder
for _, pkg := range packages {
colsOfRow := getColsOfRow(pkg)
if len(colsOfRow) != len(cols) {
return fmt.Errorf("column mapping func returned %v columns instead of %v", len(colsOfRow), len(cols))
}
for _, col := range colsOfRow {
sb.WriteString(col)
sb.WriteString(sep)
sb.WriteString(tabSep)
}
fmt.Fprintf(tw, "%s\n", sb.String())
fmt.Fprintln(tw, sb.String())
sb.Reset()
}
_ = tw.Flush()
return tw.Flush()
}
11 changes: 4 additions & 7 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package config

var (
Kubeconfig string
ForceUninstall bool
ListInstalledOnly bool
Verbose bool
Version = "dev"
Commit = "none"
Date = "unknown"
Kubeconfig string
Version = "dev"
Commit = "none"
Date = "unknown"
)
1 change: 1 addition & 0 deletions internal/repo/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ type PackageRepoIndexItem struct {
Name string `json:"name"`
ShortDescription string `json:"shortDescription,omitempty"`
IconUrl string `json:"iconUrl,omitempty"`
LatestVersion string `json:"latestVersion,omitempty"`
}
6 changes: 3 additions & 3 deletions internal/web/components/pkg_overview_btn/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ func Render(w io.Writer, tmpl *template.Template, pkgName string, status *client
})
}

func ForPkgOverviewBtn(pkgTeaser *list.PackageTeaserWithStatus) *pkgOverviewBtnInput {
buttonId := getButtonId(pkgTeaser.PackageName)
func ForPkgOverviewBtn(pkgTeaser *list.PackageWithStatus) *pkgOverviewBtnInput {
buttonId := getButtonId(pkgTeaser.Name)
return &pkgOverviewBtnInput{
ButtonId: buttonId,
Swap: "",
PackageName: pkgTeaser.PackageName,
PackageName: pkgTeaser.Name,
Status: pkgTeaser.Status,
Manifest: pkgTeaser.InstalledManifest,
}
Expand Down
10 changes: 5 additions & 5 deletions internal/web/templates/pages/packages.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
<div class="card-body p-0 d-flex flex-column">
<div hx-boost="true" class="d-flex align-items-center">
<div class="flex-shrink-0 px-1 py-1 align-self-center">
<a class="text-decoration-none" href="/packages/{{ .PackageName }}">
<a class="text-decoration-none" href="/packages/{{ .Name }}">
{{if eq .IconUrl ""}}
<!-- TODO the glasskube logo as fallback is probably not the best idea? -->
<img src="/static/assets/glasskube-logo.svg" alt="{{ .PackageName}}" style="width: 3.25rem; height:auto">
<img src="/static/assets/glasskube-logo.svg" alt="{{ .Name}}" style="width: 3.25rem; height:auto">
{{else}}
<img src="{{ .IconUrl }}" alt="{{ .PackageName}}" style="width: 3.25rem; height:auto">
<img src="{{ .IconUrl }}" alt="{{ .Name }}" style="width: 3.25rem; height:auto">
{{end}}
</a>
</div>
<div class="flex-grow-1 card-body ps-0 pe-1 py-1 align-self-start">
<a class="text-decoration-none" href="/packages/{{ .PackageName }}">
<h6 class="text-dark m-0">{{ .PackageName }}</h6>
<a class="text-decoration-none" href="/packages/{{ .Name }}">
<h6 class="text-dark m-0">{{ .Name }}</h6>
<span class="lh-sm text-dark overflow-hidden" style="
font-size: small;
display: -webkit-box;
Expand Down
39 changes: 20 additions & 19 deletions pkg/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import (
"go.uber.org/multierr"
)

type PackageTeaserWithStatus struct {
PackageName string
ShortDescription string
IconUrl string
type PackageWithStatus struct {
repo.PackageRepoIndexItem
Status *client.PackageStatus
Package *v1alpha1.Package
InstalledManifest *v1alpha1.PackageManifest
}

Expand Down Expand Up @@ -43,36 +42,38 @@ func GetPackagesWithStatus(
pkgClient *client.PackageV1Alpha1Client,
ctx context.Context,
options listOptions,
) ([]*PackageTeaserWithStatus, error) {
) ([]*PackageWithStatus, error) {
onlyInstalled := options&OnlyInstalled != 0

index, err := fetchRepoAndInstalled(pkgClient, ctx, options)
if err != nil {
return nil, err
}

result := make([]*PackageTeaserWithStatus, 0, len(index))
result := make([]*PackageWithStatus, 0, len(index))
for _, item := range index {
pkgWithStatus := PackageTeaserWithStatus{
PackageName: item.Teaser.Name,
ShortDescription: item.Teaser.ShortDescription,
IconUrl: item.Teaser.IconUrl,
pkgWithStatus := PackageWithStatus{
PackageRepoIndexItem: *item.IndexItem,
}
if item.Package != nil {
pkgWithStatus.Status = client.GetStatusOrPending(&item.Package.Status)
}
if item.PackageInfo != nil {
pkgWithStatus.InstalledManifest = item.PackageInfo.Status.Manifest
}
if !onlyInstalled || pkgWithStatus.Status != nil {

if !onlyInstalled || item.Package != nil {
if item.Package != nil {
pkgWithStatus.Package = item.Package
pkgWithStatus.Status = client.GetStatusOrPending(&item.Package.Status)
}

if item.PackageInfo != nil {
pkgWithStatus.InstalledManifest = item.PackageInfo.Status.Manifest
}

result = append(result, &pkgWithStatus)
}
}
return result, nil
}

type listResultTuple struct {
Teaser *repo.PackageRepoIndexItem
IndexItem *repo.PackageRepoIndexItem
Package *v1alpha1.Package
PackageInfo *v1alpha1.PackageInfo
}
Expand Down Expand Up @@ -121,7 +122,7 @@ func fetchRepoAndInstalled(pkgClient *client.PackageV1Alpha1Client, ctx context.

result := make([]listResultTuple, len(index.Packages))
for i, indexPackage := range index.Packages {
result[i].Teaser = &index.Packages[i]
result[i].IndexItem = &index.Packages[i]
for j, clusterPackage := range packages.Items {
if indexPackage.Name == clusterPackage.Name {
result[i].Package = &packages.Items[j]
Expand Down
3 changes: 3 additions & 0 deletions website/static/schemas/v1/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
},
"iconUrl": {
"type": "string"
},
"latestVersion": {
"type": "string"
}
},
"additionalProperties": false,
Expand Down

0 comments on commit fe856bc

Please sign in to comment.