-
Notifications
You must be signed in to change notification settings - Fork 112
/
status.go
243 lines (215 loc) · 7.61 KB
/
status.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package cmd
import (
"fmt"
"io"
"os"
"strings"
"github.com/Masterminds/semver"
"github.com/fatih/color"
"github.com/olekukonko/tablewriter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/elastic/elastic-package/internal/cobraext"
"github.com/elastic/elastic-package/internal/packages"
"github.com/elastic/elastic-package/internal/packages/changelog"
"github.com/elastic/elastic-package/internal/packages/status"
"github.com/elastic/elastic-package/internal/registry"
)
const statusLongDescription = `Use this command to display the current deployment status of a package.
If a package name is specified, then information about that package is
returned, otherwise this command checks if the current directory is a
package directory and reports its status.`
func setupStatusCommand() *cobraext.Command {
cmd := &cobra.Command{
Use: "status [package]",
Short: "Show package status",
Args: cobra.MaximumNArgs(1),
Long: statusLongDescription,
RunE: statusCommandAction,
}
cmd.Flags().BoolP(cobraext.ShowAllFlagName, "a", false, cobraext.ShowAllFlagDescription)
cmd.Flags().String(cobraext.StatusKibanaVersionFlagName, "", cobraext.StatusKibanaVersionFlagDescription)
return cobraext.NewCommand(cmd, cobraext.ContextPackage)
}
func statusCommandAction(cmd *cobra.Command, args []string) error {
var packageName string
showAll, err := cmd.Flags().GetBool(cobraext.ShowAllFlagName)
if err != nil {
return cobraext.FlagParsingError(err, cobraext.ShowAllFlagName)
}
if len(args) > 0 {
packageName = args[0]
}
kibanaVersion, err := cmd.Flags().GetString(cobraext.StatusKibanaVersionFlagName)
if err != nil {
return cobraext.FlagParsingError(err, cobraext.StatusKibanaVersionFlagName)
}
options := registry.SearchOptions{
All: showAll,
KibanaVersion: kibanaVersion,
Prerelease: true,
// Deprecated, keeping for compatibility with older versions of the registry.
Experimental: true,
}
packageStatus, err := getPackageStatus(packageName, options)
if err != nil {
return err
}
return print(packageStatus, os.Stdout)
}
func getPackageStatus(packageName string, options registry.SearchOptions) (*status.PackageStatus, error) {
if packageName != "" {
return status.RemotePackage(packageName, options)
}
packageRootPath, found, err := packages.FindPackageRoot()
if !found {
return nil, errors.New("no package specified and package root not found")
}
if err != nil {
return nil, errors.Wrap(err, "locating package root failed")
}
return status.LocalPackage(packageRootPath, options)
}
// print formats and prints package information into a table
func print(p *status.PackageStatus, w io.Writer) error {
bold := color.New(color.Bold)
red := color.New(color.FgRed, color.Bold)
cyan := color.New(color.FgCyan, color.Bold)
bold.Fprint(w, "Package: ")
cyan.Fprintln(w, p.Name)
var environmentTable [][]string
if p.Local != nil {
bold.Fprint(w, "Owner: ")
cyan.Fprintln(w, formatOwner(p))
environmentTable = append(environmentTable, formatManifest("Local", *p.Local, nil))
}
environmentTable = append(environmentTable, formatManifests("Snapshot", p.Snapshot))
environmentTable = append(environmentTable, formatManifests("Staging", p.Staging))
environmentTable = append(environmentTable, formatManifests("Production", p.Production))
if p.PendingChanges != nil {
bold.Fprint(w, "Next Version: ")
red.Fprintln(w, p.PendingChanges.Version)
bold.Fprintln(w, "Pending Changes:")
var changelogTable [][]string
for _, change := range p.PendingChanges.Changes {
changelogTable = append(changelogTable, formatChangelogEntry(change))
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Type", "Description", "Link"})
table.SetHeaderColor(
twColor(tablewriter.Colors{tablewriter.Bold}),
twColor(tablewriter.Colors{tablewriter.Bold}),
twColor(tablewriter.Colors{tablewriter.Bold}),
)
table.SetColumnColor(
twColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgCyanColor}),
tablewriter.Colors{},
tablewriter.Colors{},
)
table.SetRowLine(true)
table.AppendBulk(changelogTable)
table.Render()
}
bold.Fprintln(w, "Package Versions:")
table := tablewriter.NewWriter(w)
table.SetHeader([]string{"Environment", "Version", "Release", "Title", "Description"})
table.SetHeaderColor(
twColor(tablewriter.Colors{tablewriter.Bold}),
twColor(tablewriter.Colors{tablewriter.Bold}),
twColor(tablewriter.Colors{tablewriter.Bold}),
twColor(tablewriter.Colors{tablewriter.Bold}),
twColor(tablewriter.Colors{tablewriter.Bold}),
)
table.SetColumnColor(
twColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgCyanColor}),
twColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgRedColor}),
tablewriter.Colors{},
tablewriter.Colors{},
tablewriter.Colors{},
)
table.SetRowLine(true)
table.AppendBulk(environmentTable)
table.Render()
return nil
}
// formatOwner returns the name of the package owner
func formatOwner(p *status.PackageStatus) string {
if p.Local != nil && p.Local.Owner.Github != "" {
return p.Local.Owner.Github
}
return "-"
}
// formatChangelogEntry returns a row of changelog data
func formatChangelogEntry(change changelog.Entry) []string {
return []string{change.Type, change.Description, change.Link}
}
// formatManifests returns a row of data ffor a set of versioned packaged manifests
func formatManifests(environment string, manifests []packages.PackageManifest) []string {
if len(manifests) == 0 {
return []string{environment, "-", "-", "-", "-"}
}
var extraVersions []string
for i, m := range manifests {
if i != len(manifests)-1 {
extraVersions = append(extraVersions, m.Version)
}
}
return formatManifest(environment, manifests[len(manifests)-1], extraVersions)
}
// formatManifest returns a row of data for a given package manifest
func formatManifest(environment string, manifest packages.PackageManifest, extraVersions []string) []string {
version := manifest.Version
if len(extraVersions) > 0 {
version = fmt.Sprintf("%s (%s)", version, strings.Join(extraVersions, ", "))
}
return []string{environment, version, releaseFromVersion(manifest.Version), manifest.Title, manifest.Description}
}
// twColor no-ops the color setting if we don't want to colorize the output
func twColor(colors tablewriter.Colors) tablewriter.Colors {
if color.NoColor {
return tablewriter.Colors{}
}
return colors
}
// releaseFromVersion returns the human-friendly release level based on semantic versioning conventions.
// It does a best-effort mapping, it doesn't do validation.
func releaseFromVersion(version string) string {
const (
previewVersionText = "Technical Preview"
betaVersionText = "Beta"
releaseCandidateText = "Release Candidate"
gaVersion = "GA"
defaultText = betaVersionText
)
conventionPrereleasePrefixes := []struct {
prefix string
text string
}{
{"beta", betaVersionText},
{"rc", releaseCandidateText},
{"preview", previewVersionText},
}
sv, err := semver.NewVersion(version)
if err != nil {
// Ignoring errors on version parsing here, use best-effort defaults.
if strings.HasPrefix(version, "0.") {
return previewVersionText
}
return defaultText
}
if sv.Major() == 0 {
return previewVersionText
}
if sv.Prerelease() == "" {
return gaVersion
}
for _, convention := range conventionPrereleasePrefixes {
if strings.HasPrefix(sv.Prerelease(), convention.prefix) {
return convention.text
}
}
return defaultText
}