Skip to content

Commit 52b0c65

Browse files
authored
Merge pull request #173 from dexhorthy/app-management
add app management capabilities
2 parents 85c1118 + 7c3f923 commit 52b0c65

File tree

14 files changed

+462
-69
lines changed

14 files changed

+462
-69
lines changed

cli/cmd/app_create.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package cmd
2+
3+
import (
4+
"github.com/pkg/errors"
5+
"github.com/replicatedhq/replicated/cli/print"
6+
"github.com/replicatedhq/replicated/pkg/kotsclient"
7+
"github.com/replicatedhq/replicated/pkg/types"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func (r *runners) InitAppCreate(parent *cobra.Command) *cobra.Command {
12+
cmd := &cobra.Command{
13+
Use: "create NAME",
14+
Short: "create kots apps",
15+
Long: `create kots apps`,
16+
RunE: r.createApp,
17+
SilenceUsage: true,
18+
}
19+
parent.AddCommand(cmd)
20+
21+
return cmd
22+
}
23+
24+
func (r *runners) createApp(_ *cobra.Command, args []string) error {
25+
if len(args) != 1 {
26+
return errors.New("missing app name")
27+
}
28+
appName := args[0]
29+
30+
kotsRestClient := kotsclient.VendorV3Client{HTTPClient: *r.platformAPI}
31+
32+
33+
app, err := kotsRestClient.CreateKOTSApp(appName)
34+
35+
if err != nil {
36+
return errors.Wrap(err, "list apps")
37+
}
38+
39+
apps := []types.AppAndChannels{
40+
{
41+
App: &types.App{
42+
ID: app.ID,
43+
Name: app.Name,
44+
Slug: app.Slug,
45+
Scheduler: "kots",
46+
},
47+
},
48+
}
49+
50+
return print.Apps(r.w, apps)
51+
}

cli/cmd/app_delete.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package cmd
2+
3+
import (
4+
"github.com/manifoldco/promptui"
5+
"github.com/pkg/errors"
6+
"github.com/replicatedhq/replicated/cli/print"
7+
"github.com/replicatedhq/replicated/pkg/types"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func (r *runners) InitAppDelete(parent *cobra.Command) *cobra.Command {
12+
cmd := &cobra.Command{
13+
Use: "delete NAME",
14+
Short: "delete kots apps",
15+
Long: `Delete a kots app. There is no undo for this operation, use with caution.`,
16+
RunE: r.deleteApp,
17+
SilenceUsage: true,
18+
}
19+
parent.AddCommand(cmd)
20+
cmd.Flags().BoolVarP(&r.args.deleteAppForceYes, "force", "f", false, "Skip confirmation prompt. There is no undo for this action.")
21+
22+
return cmd
23+
}
24+
25+
func (r *runners) deleteApp(_ *cobra.Command, args []string) error {
26+
log := print.NewLogger(r.w)
27+
if len(args) != 1 {
28+
return errors.New("missing app slug or id")
29+
}
30+
appName := args[0]
31+
32+
log.ActionWithSpinner("Fetching App")
33+
app, err := r.kotsAPI.GetApp(appName)
34+
if err != nil {
35+
log.FinishSpinnerWithError()
36+
return errors.Wrap(err, "list apps")
37+
}
38+
log.FinishSpinner()
39+
40+
apps := []types.AppAndChannels{{ App: app}}
41+
42+
err = print.Apps(r.w, apps)
43+
if err != nil {
44+
return errors.Wrap(err, "print app")
45+
}
46+
47+
if !r.args.deleteAppForceYes {
48+
answer, err := promptConfirmDelete()
49+
if err != nil {
50+
return errors.Wrap(err, "confirm deletion")
51+
}
52+
53+
if answer != "yes" {
54+
return errors.New("prompt declined")
55+
}
56+
}
57+
58+
59+
log.ActionWithSpinner("Deleting App")
60+
err = r.kotsAPI.DeleteKOTSApp(app.ID)
61+
if err != nil {
62+
log.FinishSpinnerWithError()
63+
return errors.Wrap(err, "delete app")
64+
}
65+
log.FinishSpinner()
66+
67+
return nil
68+
}
69+
70+
var templates = &promptui.PromptTemplates{
71+
Prompt: "{{ . | bold }} ",
72+
Valid: "{{ . | green }} ",
73+
Invalid: "{{ . | red }} ",
74+
Success: "{{ . | bold }} ",
75+
}
76+
77+
func promptConfirmDelete() (string, error) {
78+
79+
prompt := promptui.Prompt{
80+
Label: "Delete the above listed application? There is no undo:",
81+
Templates: templates,
82+
Default: "",
83+
Validate: func(input string) error {
84+
// "no" will exit with a "prompt declined" error, just in case they don't think to ctrl+c
85+
if input == "no" || input == "yes" {
86+
return nil
87+
}
88+
return errors.New(`only "yes" will be accepted`)
89+
},
90+
}
91+
92+
for {
93+
result, err := prompt.Run()
94+
if err != nil {
95+
if err == promptui.ErrInterrupt {
96+
return "", errors.New("interrupted")
97+
}
98+
continue
99+
}
100+
101+
return result, nil
102+
}
103+
}

cli/cmd/app_ls.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package cmd
2+
3+
import (
4+
"github.com/pkg/errors"
5+
"github.com/replicatedhq/replicated/cli/print"
6+
"github.com/replicatedhq/replicated/pkg/types"
7+
"github.com/spf13/cobra"
8+
"strings"
9+
)
10+
11+
func (r *runners) InitAppList(parent *cobra.Command) *cobra.Command {
12+
cmd := &cobra.Command{
13+
Use: "ls [NAME]",
14+
Short: "list kots apps",
15+
Long: `list kots apps, or a single app by name`,
16+
RunE: r.listApps,
17+
SilenceUsage: true,
18+
}
19+
parent.AddCommand(cmd)
20+
21+
return cmd
22+
}
23+
24+
func (r *runners) listApps(_ *cobra.Command, args []string) error {
25+
26+
kotsApps, err := r.kotsAPI.ListApps()
27+
if err != nil {
28+
return errors.Wrap(err, "list apps")
29+
}
30+
31+
if len(args) == 0 {
32+
return print.Apps(r.w, kotsApps)
33+
}
34+
35+
appSearch := args[0]
36+
var apps []types.AppAndChannels
37+
for _, app := range kotsApps {
38+
if strings.Contains(app.App.ID, appSearch) || strings.Contains(app.App.Slug, appSearch) {
39+
apps = append(apps, app)
40+
}
41+
}
42+
return print.Apps(r.w, apps)
43+
}

cli/cmd/apps.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
func (r *runners) InitAppCommand(parent *cobra.Command) *cobra.Command {
8+
cmd := &cobra.Command{
9+
Use: "app",
10+
Short: "Manage apps",
11+
Long: `app can be used to list apps and create new apps`,
12+
}
13+
parent.AddCommand(cmd)
14+
15+
return cmd
16+
}

cli/cmd/release_create.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -409,13 +409,6 @@ func readYAMLDir(yamlDir string) (string, error) {
409409

410410
func promptForConfirm() (string, error) {
411411

412-
templates := &promptui.PromptTemplates{
413-
Prompt: "{{ . | bold }} ",
414-
Valid: "{{ . | green }} ",
415-
Invalid: "{{ . | red }} ",
416-
Success: "{{ . | bold }} ",
417-
}
418-
419412
prompt := promptui.Prompt{
420413
Label: "Create with these properties? (default Yes) [Y/n]",
421414
Templates: templates,
@@ -433,7 +426,7 @@ func promptForConfirm() (string, error) {
433426
result, err := prompt.Run()
434427
if err != nil {
435428
if err == promptui.ErrInterrupt {
436-
os.Exit(-1)
429+
return "", errors.New("interrupted")
437430
}
438431
continue
439432
}

cli/cmd/root.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,14 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i
192192
runCmds.InitEnterpriseInstallerRM(enterpriseInstallerCmd)
193193
runCmds.InitEnterpriseInstallerAssign(enterpriseInstallerCmd)
194194

195+
appCmd := runCmds.InitAppCommand(runCmds.rootCmd)
196+
runCmds.InitAppList(appCmd)
197+
runCmds.InitAppCreate(appCmd)
198+
runCmds.InitAppDelete(appCmd)
199+
195200
runCmds.rootCmd.SetUsageTemplate(rootCmdUsageTmpl)
196201

197-
prerunCommand := func(cmd *cobra.Command, args []string) error {
202+
preRunSetupAPIs := func(_ *cobra.Command, _ []string) error {
198203
if apiToken == "" {
199204
apiToken = os.Getenv("REPLICATED_API_TOKEN")
200205
if apiToken == "" {
@@ -218,33 +223,40 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i
218223

219224
commonAPI := client.NewClient(platformOrigin, graphqlOrigin, apiToken, kurlDotSHOrigin)
220225
runCmds.api = commonAPI
226+
return nil
227+
}
228+
229+
prerunCommand := func(cmd *cobra.Command, args []string) error {
230+
if err := preRunSetupAPIs(cmd, args); err != nil {
231+
return errors.Wrap(err, "set up APIs")
232+
}
221233

222234
if appSlugOrID == "" {
223235
appSlugOrID = os.Getenv("REPLICATED_APP")
224236
}
225237

226-
appType, err := commonAPI.GetAppType(appSlugOrID)
238+
appType, err := runCmds.api.GetAppType(appSlugOrID)
227239
if err != nil {
228240
return err
229241
}
230242
runCmds.appType = appType
231243

232244
if appType == "platform" {
233-
app, err := platformAPI.GetApp(appSlugOrID)
245+
app, err := runCmds.platformAPI.GetApp(appSlugOrID)
234246
if err != nil {
235247
return err
236248
}
237249
runCmds.appID = app.Id
238250
runCmds.appSlug = app.Slug
239251
} else if appType == "ship" {
240-
app, err := shipAPI.GetApp(appSlugOrID)
252+
app, err := runCmds.shipAPI.GetApp(appSlugOrID)
241253
if err != nil {
242254
return err
243255
}
244256
runCmds.appID = app.ID
245257
runCmds.appSlug = app.Slug
246258
} else if appType == "kots" {
247-
app, err := kotsAPI.GetApp(appSlugOrID)
259+
app, err := runCmds.kotsAPI.GetApp(appSlugOrID)
248260
if err != nil {
249261
return err
250262
}
@@ -255,12 +267,14 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i
255267
return nil
256268
}
257269

270+
258271
channelCmd.PersistentPreRunE = prerunCommand
259272
releaseCmd.PersistentPreRunE = prerunCommand
260273
collectorsCmd.PersistentPreRunE = prerunCommand
261274
entitlementsCmd.PersistentPreRunE = prerunCommand
262275
customersCmd.PersistentPreRunE = prerunCommand
263276
installerCmd.PersistentPreRunE = prerunCommand
277+
appCmd.PersistentPreRunE = preRunSetupAPIs
264278

265279
runCmds.rootCmd.AddCommand(Version())
266280

cli/cmd/runner.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,15 @@ type runnerArgs struct {
6161
createReleasePromoteVersion string
6262
createReleasePromoteEnsureChannel bool
6363
// Add Create Release Lint
64-
createReleaseLint bool
65-
lintReleaseYamlDir string
66-
lintReleaseFailOn string
67-
releaseOptional bool
68-
releaseNotes string
69-
releaseVersion string
70-
updateReleaseYaml string
71-
updateReleaseYamlDir string
72-
updateReleaseYamlFile string
64+
createReleaseLint bool
65+
lintReleaseYamlDir string
66+
lintReleaseFailOn string
67+
releaseOptional bool
68+
releaseNotes string
69+
releaseVersion string
70+
updateReleaseYaml string
71+
updateReleaseYamlDir string
72+
updateReleaseYamlFile string
7373

7474
entitlementsAPIServer string
7575
entitlementsVerbose bool
@@ -144,4 +144,5 @@ type runnerArgs struct {
144144
releaseDownloadDest string
145145
createInstallerAutoDefaults bool
146146
createInstallerAutoDefaultsAccept bool
147+
deleteAppForceYes bool
147148
}

cli/print/apps.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,21 @@ import (
44
"text/tabwriter"
55
"text/template"
66

7-
apps "github.com/replicatedhq/replicated/gen/go/v1"
7+
"github.com/replicatedhq/replicated/pkg/types"
88
)
99

10-
var appsTmplSrc = `ID NAME SCHEDULER
10+
var appsTmplSrc = `ID NAME SLUG SCHEDULER
1111
{{ range . -}}
12-
{{ .ID }} {{ .Name }} {{ .Scheduler }}
12+
{{ .ID }} {{ .Name }} {{ .Slug}} {{ .Scheduler }}
1313
{{ end }}`
1414

1515
var appsTmpl = template.Must(template.New("apps").Funcs(funcs).Parse(appsTmplSrc))
1616

17-
func Apps(w *tabwriter.Writer, apps []apps.AppAndChannels) error {
18-
as := make([]map[string]interface{}, len(apps))
17+
func Apps(w *tabwriter.Writer, apps []types.AppAndChannels) error {
18+
var as []*types.App
1919

20-
for i, a := range apps {
21-
as[i] = map[string]interface{}{
22-
"ID": a.App.Id,
23-
"Name": a.App.Name,
24-
"Scheduler": a.App.Scheduler,
25-
}
20+
for _, a := range apps {
21+
as = append(as, a.App)
2622
}
2723

2824
if err := appsTmpl.Execute(w, as); err != nil {

0 commit comments

Comments
 (0)