Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 3aed762

Browse files
authored
[init] Add init survey provider (#14)
## Summary This splits out init survey into a provider. I think there's a few more follow ups we can do after this that will make it better, for example `answers.ClusterOption == provider.CreateJetpackCluster` condition can be removed (we could add a generic non error println to `answers`) ## How was it tested? * linter ## Is this change backwards-compatible? yes
1 parent 903fd4f commit 3aed762

File tree

7 files changed

+207
-202
lines changed

7 files changed

+207
-202
lines changed

padcli/command/build.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ const (
1717
localImageFlag = "local-image"
1818
)
1919

20-
const (
21-
imageRepositoryFlagHelpMsg = "Image repository to push the built image to. " +
22-
"Your kubernetes cluster must have the permissions to pull images from this repository."
23-
)
24-
2520
type embeddedBuildOptions struct {
2621
Platform string
2722
BuildArgs map[string]string

padcli/command/initcmd.go

Lines changed: 5 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ package command
22

33
import (
44
"context"
5-
"fmt"
65
"os"
76
"path/filepath"
87

9-
"github.com/AlecAivazis/survey/v2"
108
"github.com/pkg/errors"
119
"github.com/spf13/cobra"
1210
"go.jetpack.io/launchpad/goutil/errorutil"
@@ -16,23 +14,6 @@ import (
1614
"go.jetpack.io/launchpad/pkg/kubevalidate"
1715
)
1816

19-
type serviceTypeOption string
20-
21-
const (
22-
webServiceType serviceTypeOption = "Web Service"
23-
cronjobServiceType serviceTypeOption = "Cron Job"
24-
jetpackManagedCluster string = "Jetpack managed cluster"
25-
createJetpackCluster string = "Create a new cluster with Jetpack"
26-
)
27-
28-
type SurveyAnswers struct {
29-
AppName string
30-
AppType string
31-
ClusterOption string
32-
KubeContext string
33-
ImageRepositoryLocation string
34-
}
35-
3617
func initCmd() *cobra.Command {
3718
var initCmd = &cobra.Command{
3819
Use: "init [path]",
@@ -44,7 +25,6 @@ func initCmd() *cobra.Command {
4425
if err != nil {
4526
return errors.WithStack(err)
4627
}
47-
4828
return initConfig(cmd.Context(), authProvider, path)
4929
},
5030
}
@@ -102,15 +82,15 @@ func initConfig(ctx context.Context, authProvider provider.Auth, path string) er
10282
return errors.WithStack(err)
10383
}
10484

105-
answers, err := runConfigSurvey(ctx, authProvider, appName)
85+
answers, err := cmdOpts.InitSurveyProvider().Run(ctx, authProvider, appName)
10686
if err != nil {
10787
return errors.WithStack(err)
10888
}
10989
if answers == nil {
11090
return nil
11191
}
11292

113-
if answers.ClusterOption == createJetpackCluster {
93+
if answers.ClusterOption == provider.CreateJetpackCluster {
11494
// Ask users to create a cluster first.
11595
jetlog.Logger(ctx).Printf("\nTo create a new cluster, run `launchpad cluster create <cluster_name>`.\n")
11696
return nil
@@ -122,17 +102,17 @@ func initConfig(ctx context.Context, authProvider provider.Auth, path string) er
122102
Services: []jetconfig.Service{},
123103
}
124104

125-
if answers.AppType == string(webServiceType) {
105+
if answers.AppType == string(provider.WebServiceType) {
126106
jetCfg.AddNewWebService(answers.AppName + "-" + jetconfig.WebType)
127-
} else if answers.AppType == string(cronjobServiceType) {
107+
} else if answers.AppType == string(provider.CronjobServiceType) {
128108
jetCfg.AddNewCronService(
129109
answers.AppName+"-"+jetconfig.CronType,
130110
[]string{"/bin/sh", "-c", "date; echo Hello from Launchpad"},
131111
"* * * * *",
132112
)
133113
}
134114

135-
if answers.ClusterOption != "" && answers.ClusterOption != createJetpackCluster {
115+
if answers.ClusterOption != "" && answers.ClusterOption != provider.CreateJetpackCluster {
136116
jetCfg.Cluster = answers.ClusterOption
137117
}
138118
if answers.ImageRepositoryLocation != "" {
@@ -156,173 +136,6 @@ func initConfig(ctx context.Context, authProvider provider.Auth, path string) er
156136
return nil
157137
}
158138

159-
func runConfigSurvey(
160-
ctx context.Context,
161-
authProvider provider.Auth,
162-
appName string,
163-
) (*SurveyAnswers, error) {
164-
165-
clusters, err := cmdOpts.ClusterProvider().GetAll(ctx)
166-
if err != nil {
167-
return nil, errors.WithStack(err)
168-
}
169-
170-
clusterNames := []string{}
171-
for _, c := range clusters {
172-
clusterNames = append(clusterNames, c.GetName())
173-
}
174-
// In case user wants to log in and use a jetpack managed cluster.
175-
clusterNames = append(clusterNames, jetpackManagedCluster)
176-
// In case user wants to create a cluster.
177-
clusterNames = append(clusterNames, createJetpackCluster)
178-
179-
qs, err := surveyQuestions(ctx, appName, clusterNames)
180-
if err != nil {
181-
return nil, errors.WithStack(err)
182-
}
183-
184-
answers := &SurveyAnswers{}
185-
186-
err = survey.Ask([]*survey.Question{qs["AppName"]}, &answers.AppName)
187-
if err != nil {
188-
return nil, errors.WithStack(err)
189-
}
190-
191-
err = survey.Ask([]*survey.Question{qs["AppType"]}, &answers.AppType)
192-
if err != nil {
193-
return nil, errors.WithStack(err)
194-
}
195-
196-
err = survey.Ask([]*survey.Question{qs["ClusterOption"]}, &answers.ClusterOption)
197-
if err != nil {
198-
return nil, errors.WithStack(err)
199-
}
200-
201-
if answers.ClusterOption == jetpackManagedCluster {
202-
// Prompt users to log in.
203-
ctx, err := authProvider.Identify(ctx)
204-
if err != nil {
205-
return nil, errors.WithStack(err)
206-
}
207-
// Get all the cluster names again.
208-
clusters, err := cmdOpts.ClusterProvider().GetAll(ctx)
209-
if err != nil {
210-
return nil, errors.WithStack(err)
211-
}
212-
213-
// Only show the jetpack managed cluster names.
214-
clusterNames := []string{}
215-
for _, c := range clusters {
216-
if c.IsJetpackManaged() {
217-
clusterNames = append(clusterNames, c.GetName())
218-
}
219-
}
220-
if len(clusterNames) == 0 {
221-
answers.ClusterOption = createJetpackCluster
222-
} else {
223-
// Add option to select creating a new managed cluster.
224-
clusterNames = append(clusterNames, createJetpackCluster)
225-
additionalClusterSurvey := surveyJetpackManagedClusterQuestions(ctx, clusterNames)
226-
err = survey.Ask([]*survey.Question{additionalClusterSurvey["ClusterOption"]}, &answers.ClusterOption)
227-
if err != nil {
228-
return nil, errors.WithStack(err)
229-
}
230-
}
231-
}
232-
233-
return answers, nil
234-
}
235-
236-
func surveyQuestions(ctx context.Context, appName string, clusterNames []string) (map[string]*survey.Question, error) {
237-
238-
appTypes := jetconfig.GetServiceTypes()
239-
appTypeOptions := getAppTypeOptions(appTypes)
240-
241-
questions := map[string]*survey.Question{
242-
"AppName": {
243-
Name: "AppName",
244-
Prompt: &survey.Input{
245-
Message: "What is the name of this project?",
246-
Default: appName,
247-
},
248-
Validate: func(val any) error {
249-
if err := survey.MinLength(3)(val); err != nil {
250-
return err
251-
}
252-
253-
nameEntered := val.(string)
254-
if ok := kubevalidate.IsValidName(nameEntered); !ok {
255-
// NOTE: we can create an API `kubevalidate.IsValidNameWithReasons` that returns
256-
// the error messages from k8s.io/apimachinery/pkg/util/validation
257-
// However, those messages speak about DNS RFC 1035, which may be confusing
258-
// to our users.
259-
260-
// The default error text by the Survey lib is prefixed by:
261-
// X Sorry, your reply was invalid:
262-
// to which we are adding a suffix of the nameEntered via the %s below.
263-
// This gives feedback to the user that we actually processed their entered name.
264-
msg := "%s\n" +
265-
"For compatibility with kubernetes, we require app names to be:\n" +
266-
" - less than 64 characters\n" +
267-
" - consist of lower case alphanumeric characters or '-'\n" +
268-
" - start with an alphabetic character \n" +
269-
" - end with an alphanumeric character"
270-
return fmt.Errorf(msg, nameEntered)
271-
}
272-
return nil
273-
},
274-
},
275-
"AppType": {
276-
Name: "AppType",
277-
Prompt: &survey.Select{
278-
Message: "What type of service you would like to add to this project?",
279-
Options: appTypeOptions,
280-
},
281-
},
282-
"ClusterOption": {
283-
Name: "ClusterOption",
284-
Prompt: &survey.Select{
285-
Message: "To which cluster do you want to deploy this project?",
286-
Options: clusterNames,
287-
},
288-
},
289-
"ImageRepositoryLocation": {
290-
Name: "ImageRepositoryLocation",
291-
Prompt: &survey.Input{
292-
Message: imageRepositoryFlagHelpMsg,
293-
},
294-
Validate: survey.Required,
295-
},
296-
}
297-
298-
return questions, nil
299-
}
300-
301-
func surveyJetpackManagedClusterQuestions(ctx context.Context, clusterNames []string) map[string]*survey.Question {
302-
return map[string]*survey.Question{
303-
"ClusterOption": {
304-
Name: "ClusterOption",
305-
Prompt: &survey.Select{
306-
Message: "To which jetpack managed cluster do you want to deploy this project?",
307-
Options: clusterNames,
308-
},
309-
},
310-
}
311-
}
312-
313139
func appName(path string) (string, error) {
314140
return kubevalidate.ToValidName(filepath.Base(jetconfig.ConfigDir(path)))
315141
}
316-
317-
func getAppTypeOptions(appTypes []string) []string {
318-
var appTypeOptions []string
319-
for _, appType := range appTypes {
320-
switch appType {
321-
case jetconfig.WebType:
322-
appTypeOptions = append(appTypeOptions, string(webServiceType))
323-
case jetconfig.CronType:
324-
appTypeOptions = append(appTypeOptions, string(cronjobServiceType))
325-
}
326-
}
327-
return appTypeOptions
328-
}

padcli/command/mock/cmdopts.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ func (*MockCmdOptions) Hooks() *hook.Hooks {
5252
return hook.New()
5353
}
5454

55+
func (*MockCmdOptions) InitSurveyProvider() provider.InitSurveyProvider {
56+
return provider.DefaultInitSurveyProvider(&mockClusterProvider{})
57+
}
58+
5559
func (*MockCmdOptions) RepositoryProvider() provider.Repository {
5660
return provider.EmptyRepository()
5761
}

padcli/command/up.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func registerPublishFlags(cmd *cobra.Command, opts *publishOptions) {
155155
"image-repository",
156156
"i",
157157
"",
158-
imageRepositoryFlagHelpMsg,
158+
provider.ImageRepositoryFlagHelpMsg,
159159
)
160160
}
161161

padcli/padcli.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"go.jetpack.io/launchpad/padcli/provider"
1414
)
1515

16+
type CobraFunc func(cmd *cobra.Command, args []string) error
17+
1618
type Padcli struct {
1719
additionalCommands []*cobra.Command
1820
analyticsProvider provider.Analytics
@@ -21,8 +23,9 @@ type Padcli struct {
2123
envSecProvider provider.EnvSec
2224
errorLogger provider.ErrorLogger
2325
hooks *hook.Hooks
24-
persistentPreRunE func(cmd *cobra.Command, args []string) error
25-
persistentPostRunE func(cmd *cobra.Command, args []string) error
26+
initSurverProvider provider.InitSurveyProvider
27+
persistentPreRunE CobraFunc
28+
persistentPostRunE CobraFunc
2629
namespaceProvider provider.NamespaceProvider
2730
repositoryProvider provider.Repository
2831
rootCommand *cobra.Command
@@ -45,6 +48,11 @@ func New(opts ...padcliOption) *Padcli {
4548
for _, opt := range opts {
4649
opt(p)
4750
}
51+
52+
if p.initSurverProvider == nil {
53+
p.initSurverProvider = provider.DefaultInitSurveyProvider(p.clusterProvider)
54+
}
55+
4856
return p
4957
}
5058

@@ -76,6 +84,10 @@ func (p *Padcli) Hooks() *hook.Hooks {
7684
return p.hooks
7785
}
7886

87+
func (p *Padcli) InitSurveyProvider() provider.InitSurveyProvider {
88+
return p.initSurverProvider
89+
}
90+
7991
func (p *Padcli) NamespaceProvider() provider.NamespaceProvider {
8092
return p.namespaceProvider
8193
}
@@ -160,19 +172,25 @@ func WithHooks(hooks *hook.Hooks) padcliOption {
160172
}
161173
}
162174

175+
func WithInitSurveyProvider(provider provider.InitSurveyProvider) padcliOption {
176+
return func(p *Padcli) {
177+
p.initSurverProvider = provider
178+
}
179+
}
180+
163181
func WithNamespaceProvider(ns provider.NamespaceProvider) padcliOption {
164182
return func(p *Padcli) {
165183
p.namespaceProvider = ns
166184
}
167185
}
168186

169-
func WithPersistentPreRunE(r func(cmd *cobra.Command, args []string) error) padcliOption {
187+
func WithPersistentPreRunE(r CobraFunc) padcliOption {
170188
return func(p *Padcli) {
171189
p.persistentPreRunE = r
172190
}
173191
}
174192

175-
func WithPersistentPostRunE(r func(cmd *cobra.Command, args []string) error) padcliOption {
193+
func WithPersistentPostRunE(r CobraFunc) padcliOption {
176194
return func(p *Padcli) {
177195
p.persistentPostRunE = r
178196
}

0 commit comments

Comments
 (0)