Skip to content

Commit

Permalink
Merge pull request #385 from rast1025/feature/interactive
Browse files Browse the repository at this point in the history
feat: add interactive flag
  • Loading branch information
jenkins-x-bot authored Sep 24, 2021
2 parents b3e97ec + 6649235 commit 51f8afc
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Dockerfile-preview
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
FROM gcr.io/jenkinsxio/jx-boot:3.0.739
FROM ghcr.io/jenkins-x/jx-boot:3.2.199

COPY ./build/linux/jx-promote /usr/bin/jx-promote
72 changes: 64 additions & 8 deletions pkg/promote/promote.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const (
optionApplication = "app"
optionTimeout = "timeout"
optionPullRequestPollTime = "pull-request-poll-time"
optionInteractive = "interactive"

// DefaultChartRepo default URL for charts repository
DefaultChartRepo = "http://jenkins-x-chartmuseum:8080"
Expand Down Expand Up @@ -96,6 +97,7 @@ type Options struct {
NoGroupPullRequest bool
IgnoreLocalFiles bool
DisableGitConfig bool // to disable git init in unit tests
Interactive bool
Timeout string
PullRequestPollTime string
Filter string
Expand Down Expand Up @@ -175,8 +177,8 @@ func NewCmdPromote() (*cobra.Command, *Options) {
cmd.Flags().StringArrayP("promotion-environments", "", options.PromoteEnvironments, "The environments considered for promotion")
cmd.Flags().BoolVarP(&options.AllAutomatic, "all-auto", "", false, "Promote to all automatic environments in order")
cmd.Flags().BoolVarP(&options.All, "all", "", false, "Promote to all automatic and manual environments in order using a draft PR for manual promotion environments")

cmd.Flags().BoolVarP(&options.BatchMode, "batch-mode", "b", false, "Enables batch mode which avoids prompting for user input")
cmd.Flags().BoolVarP(&options.Interactive, optionInteractive, "", false, "Enables interactive mode")

options.AddOptions(cmd)
return cmd, options
Expand All @@ -197,7 +199,6 @@ func (o *Options) AddOptions(cmd *cobra.Command) {
cmd.Flags().StringVarP(&o.ReleaseName, "release", "", "", "The name of the helm release")
cmd.Flags().StringVarP(&o.Timeout, optionTimeout, "t", "1h", "The timeout to wait for the promotion to succeed in the underlying Environment. The command fails if the timeout is exceeded or the promotion does not complete")
cmd.Flags().StringVarP(&o.PullRequestPollTime, optionPullRequestPollTime, "", "20s", "Poll time when waiting for a Pull Request to merge")

cmd.Flags().StringVarP(&o.DevEnvContext.GitUsername, "git-user", "", "", "Git username used to clone the development environment. If not specified its loaded from the git credentials file")
cmd.Flags().StringVarP(&o.DevEnvContext.GitToken, "git-token", "", "", "Git token used to clone the development environment. If not specified its loaded from the git credentials file")

Expand Down Expand Up @@ -268,9 +269,22 @@ func (o *Options) setApplicationNameFromDiscoveredAppName(discoverAppName discov
return nil
}

type interactiveFn func() (string, error)

func (o *Options) setApplicationNameFromInteractive(interactive interactiveFn) error {
app, err := interactive()
if err != nil {
return errors.Wrap(err, "choosing app name from interactive window failed")
}

o.Application = app

return nil
}

// EnsureApplicationNameIsDefined validates if an application name flag was provided by the user. If missing it will
// try to set it up or return an error
func (o *Options) EnsureApplicationNameIsDefined(sf searchForChartFn, df discoverAppNameFn) error {
func (o *Options) EnsureApplicationNameIsDefined(sf searchForChartFn, df discoverAppNameFn, ifn interactiveFn) error {
if !o.hasApplicationFlag() && o.hasArgs() {
o.setApplicationNameFromArgs()
}
Expand All @@ -280,7 +294,9 @@ func (o *Options) EnsureApplicationNameIsDefined(sf searchForChartFn, df discove
return err
}
}

if !o.hasApplicationFlag() && o.Interactive {
return o.setApplicationNameFromInteractive(ifn)
}
if !o.hasApplicationFlag() {
return o.setApplicationNameFromDiscoveredAppName(df)
}
Expand Down Expand Up @@ -315,7 +331,7 @@ func (o *Options) Run() error {
}

// TODO move to validate
err = o.EnsureApplicationNameIsDefined(o.SearchForChart, o.DiscoverAppName)
err = o.EnsureApplicationNameIsDefined(o.SearchForChart, o.DiscoverAppName, o.ChooseChart)
if err != nil {
return err
}
Expand Down Expand Up @@ -343,10 +359,19 @@ func (o *Options) Run() error {
}
}
if o.Version == "" && o.Application != "" {
o.Version, err = o.findLatestVersion(o.Application)
if err != nil {
return errors.Wrapf(err, "failed to find latest version of app %s", o.Application)
if o.Interactive {
versions, err := o.getAllVersions(o.Application)
o.Version, err = o.Input.PickNameWithDefault(versions, "Pick version:", "", "please select a version")
if err != nil {
return errors.Wrapf(err, "failed to pick a version")
}
} else {
o.Version, err = o.findLatestVersion(o.Application)
if err != nil {
return errors.Wrapf(err, "failed to find latest version of app %s", o.Application)
}
}

}

ns := o.Namespace
Expand Down Expand Up @@ -1082,6 +1107,28 @@ func (o *Options) findLatestVersion(app string) (string, error) {
return maxString, nil
}

func (o *Options) getAllVersions(app string) ([]string, error) {
charts, err := o.Helm().SearchCharts(app, true)
if err != nil {
return nil, err
}

versions := []string{}
for _, chart := range charts {
sv, err := semver.Parse(chart.ChartVersion)
if err != nil {
log.Logger().Warnf("Invalid semantic version: %s %s", chart.ChartVersion, err)
} else {
versions = append(versions, sv.String())
}
}
if len(versions) > 0 {
return versions, nil
}
return nil, fmt.Errorf("Could not find a version of app %s in the helm repositories", app)

}

// Helm lazily create a helmer
func (o *Options) Helm() helm.Helmer {
if o.Helmer == nil {
Expand Down Expand Up @@ -1414,6 +1461,15 @@ func (o *Options) SearchForChart(filter string) (string, error) {
return appName, nil
}

func (o *Options) ChooseChart() (string, error) {
appName, err := o.SearchForChart("")
if err != nil {
return appName, fmt.Errorf("No charts available")
}
o.Version = "" // remove version to choose it later
return appName, nil
}

func (o *Options) InitGitConfigAndUser() error {
_, so := setup.NewCmdGitSetup()

Expand Down
26 changes: 21 additions & 5 deletions pkg/promote/promote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ func fakeDiscoverAppName() (string, error) {
return "myDiscoveredApp", nil
}

func fakeChooseChart() (string, error) {
return "myChosenApp", nil
}

func TestEnsureApplicationNameIsDefinedWithoutApplicationFlagWithArgs(t *testing.T) {
promoteOptions := &promote.Options{
Environment: "production", // --env production
}

promoteOptions.Args = []string{"myArgumentApp"}

err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName)
err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName, fakeChooseChart)
assert.NoError(t, err)

assert.Equal(t, "myArgumentApp", promoteOptions.Application)
Expand All @@ -43,7 +47,7 @@ func TestEnsureApplicationNameIsDefinedWithoutApplicationFlagWithFilterFlag(t *t
Filter: "something",
}

err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName)
err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName, fakeChooseChart)
assert.NoError(t, err)

assert.Equal(t, "mySearchedApp", promoteOptions.Application)
Expand All @@ -56,12 +60,24 @@ func TestEnsureApplicationNameIsDefinedWithoutApplicationFlagWithBatchFlag(t *te

promoteOptions.BatchMode = true // --batch-mode

err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName)
err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName, fakeChooseChart)
assert.NoError(t, err)

assert.Equal(t, "myDiscoveredApp", promoteOptions.Application)
}

func TestEnsureApplicationNameIsDefinedWithoutApplicationFlagWithInteractiveFlag(t *testing.T) {
promoteOptions := &promote.Options{
Environment: "production", // --env production
Interactive: true,
}

err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName, fakeChooseChart)
assert.NoError(t, err)

assert.Equal(t, "myChosenApp", promoteOptions.Application)
}

func TestEnsureApplicationNameIsDefinedWithoutApplicationFlag(t *testing.T) {
testhelpers.SkipForWindows(t, "go-expect does not work on windows")

Expand All @@ -73,7 +89,7 @@ func TestEnsureApplicationNameIsDefinedWithoutApplicationFlag(t *testing.T) {
Values: map[string]string{"Are you sure you want to promote the application named: myDiscoveredApp?": "Y"},
}

err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName)
err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName, fakeChooseChart)

assert.NoError(t, err)
assert.Equal(t, "myDiscoveredApp", promoteOptions.Application)
Expand All @@ -89,7 +105,7 @@ func TestEnsureApplicationNameIsDefinedWithoutApplicationFlagUserSaysNo(t *testi
},
}

err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName)
err := promoteOptions.EnsureApplicationNameIsDefined(fakeSearchForChart, fakeDiscoverAppName, fakeChooseChart)
assert.Error(t, err)
assert.Equal(t, "", promoteOptions.Application)
}
Expand Down

0 comments on commit 51f8afc

Please sign in to comment.