github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/cmd/clicmds/cfgcli.go (about)

     1  // Copyright 2016 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package clicmds
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path"
    21  
    22  	"github.com/nmiyake/pkg/dirs"
    23  	"github.com/palantir/amalgomate/amalgomated"
    24  	"github.com/palantir/checks/gocd/cmd/gocd"
    25  	"github.com/palantir/checks/gogenerate/cmd/gogenerate"
    26  	"github.com/palantir/checks/golicense/cmd/golicense"
    27  	"github.com/palantir/pkg/cli"
    28  	"github.com/palantir/pkg/cli/cfgcli"
    29  	"github.com/pkg/errors"
    30  
    31  	"github.com/palantir/godel/apps/distgo"
    32  	"github.com/palantir/godel/apps/gonform"
    33  	"github.com/palantir/godel/apps/gunit"
    34  	"github.com/palantir/godel/apps/okgo"
    35  	"github.com/palantir/godel/cmd"
    36  )
    37  
    38  func CfgCliCmdSet(gödelPath string) (amalgomated.CmdLibrary, error) {
    39  	cmds := make([]*amalgomated.CmdWithRunner, len(cfgCliCmds))
    40  	for i := range cfgCliCmds {
    41  		supplier := gödelRunnerSupplier(gödelPath, cfgCliCmds[i].name)
    42  		cmd, err := cfgCliCmds[i].namedCmd(supplier)
    43  		if err != nil {
    44  			return nil, errors.Wrapf(err, "failed to create command")
    45  		}
    46  		cmds[i] = cmd
    47  	}
    48  
    49  	cmdSet, err := amalgomated.NewStringCmdSetForRunners(cmds...)
    50  	if err != nil {
    51  		return nil, errors.Wrapf(err, "failed to create StringCmdSet for runners")
    52  	}
    53  
    54  	return amalgomated.NewCmdLibrary(cmdSet), nil
    55  }
    56  
    57  func CfgCliCommands(gödelPath string) []cli.Command {
    58  	var cmds []cli.Command
    59  	for _, cmd := range cfgCliCmds {
    60  		supplier := gödelRunnerSupplier(gödelPath, cmd.name)
    61  		cmds = append(cmds, cmd.cliCmd(supplier))
    62  	}
    63  	return cmds
    64  }
    65  
    66  func gödelRunnerSupplier(gödelPath, cmdName string) amalgomated.CmderSupplier {
    67  	return func(cmd amalgomated.Cmd) (amalgomated.Cmder, error) {
    68  		// first underscore indicates to gödel that it is running in impersonation mode, while second underscore
    69  		// signals this to the command itself (handled by "processHiddenCommand" in
    70  		// amalgomated.cmdSetApp.RunApp)
    71  		return amalgomated.PathCmder(gödelPath, amalgomated.ProxyCmdPrefix+cmdName, amalgomated.ProxyCmdPrefix+cmd.Name()), nil
    72  	}
    73  }
    74  
    75  var (
    76  	distgoCreator = func(supplier amalgomated.CmderSupplier) *cli.App {
    77  		return distgo.App()
    78  	}
    79  	distgoDecorator = func(f func(ctx cli.Context) error) func(ctx cli.Context) error {
    80  		return func(ctx cli.Context) error {
    81  			cfgDirPath, err := cmd.ConfigDirPath(ctx)
    82  			if err != nil {
    83  				return err
    84  			}
    85  			// use the project directory for the configuration as the working directory for distgo commands
    86  			projectPath := path.Join(cfgDirPath, "..", "..")
    87  			if err := os.Chdir(projectPath); err != nil {
    88  				return err
    89  			}
    90  			return f(ctx)
    91  		}
    92  	}
    93  	cfgCliCmds = []*goRunnerCmd{
    94  		{
    95  			name:   "format",
    96  			app:    gonform.App,
    97  			runApp: gonform.RunApp,
    98  		},
    99  		{
   100  			name:   "check",
   101  			app:    okgo.App,
   102  			runApp: okgo.RunApp,
   103  		},
   104  		{
   105  			name:   "test",
   106  			app:    gunit.App,
   107  			runApp: gunit.RunApp,
   108  		},
   109  		{
   110  			name: "generate",
   111  			app: func(supplier amalgomated.CmderSupplier) *cli.App {
   112  				return gogenerate.App()
   113  			},
   114  			pathToCfg: []string{"generate.yml"},
   115  		},
   116  		{
   117  			name: "imports",
   118  			app: func(supplier amalgomated.CmderSupplier) *cli.App {
   119  				return gocd.App()
   120  			},
   121  			pathToCfg: []string{"imports.yml"},
   122  		},
   123  		{
   124  			name: "license",
   125  			app: func(supplier amalgomated.CmderSupplier) *cli.App {
   126  				return golicense.App()
   127  			},
   128  			pathToCfg: []string{"license.yml"},
   129  		},
   130  		{
   131  			name:           "run",
   132  			app:            distgoCreator,
   133  			decorator:      distgoDecorator,
   134  			subcommandPath: []string{"run"},
   135  			pathToCfg:      []string{"dist.yml"},
   136  		},
   137  		{
   138  			name:           "project-version",
   139  			app:            distgoCreator,
   140  			decorator:      distgoDecorator,
   141  			subcommandPath: []string{"project-version"},
   142  			pathToCfg:      []string{"dist.yml"},
   143  		},
   144  		{
   145  			name:           "build",
   146  			app:            distgoCreator,
   147  			decorator:      distgoDecorator,
   148  			subcommandPath: []string{"build"},
   149  			pathToCfg:      []string{"dist.yml"},
   150  		},
   151  		{
   152  			name:           "dist",
   153  			app:            distgoCreator,
   154  			decorator:      distgoDecorator,
   155  			subcommandPath: []string{"dist"},
   156  			pathToCfg:      []string{"dist.yml"},
   157  		},
   158  		{
   159  			name:           "clean",
   160  			app:            distgoCreator,
   161  			decorator:      distgoDecorator,
   162  			subcommandPath: []string{"clean"},
   163  			pathToCfg:      []string{"dist.yml"},
   164  		},
   165  		{
   166  			name:           "artifacts",
   167  			app:            distgoCreator,
   168  			decorator:      distgoDecorator,
   169  			subcommandPath: []string{"artifacts"},
   170  			pathToCfg:      []string{"dist.yml"},
   171  		},
   172  		{
   173  			name:           "products",
   174  			app:            distgoCreator,
   175  			decorator:      distgoDecorator,
   176  			subcommandPath: []string{"products"},
   177  			pathToCfg:      []string{"dist.yml"},
   178  		},
   179  		{
   180  			name:           "docker",
   181  			app:            distgoCreator,
   182  			decorator:      distgoDecorator,
   183  			subcommandPath: []string{"docker"},
   184  			pathToCfg:      []string{"dist.yml"},
   185  		},
   186  		{
   187  			name:           "publish",
   188  			app:            distgoCreator,
   189  			decorator:      distgoDecorator,
   190  			subcommandPath: []string{"publish"},
   191  			pathToCfg:      []string{"dist.yml"},
   192  		},
   193  	}
   194  )
   195  
   196  type appSupplier func(supplier amalgomated.CmderSupplier) *cli.App
   197  
   198  // locator that returns the subcommand
   199  func mustAppSubcommand(app *cli.App, subcommands ...string) cli.Command {
   200  	currCmd := app.Command
   201  	for _, currDesiredSubcmd := range subcommands {
   202  		found := false
   203  		for _, currActualSubcmd := range currCmd.Subcommands {
   204  			if currActualSubcmd.Name == currDesiredSubcmd {
   205  				currCmd = currActualSubcmd
   206  				found = true
   207  				break
   208  			}
   209  		}
   210  		if !found {
   211  			panic(fmt.Sprintf("subcommand %v not present in command %v (full path: %v, top-level command: %v)", currDesiredSubcmd, currCmd.Subcommands, subcommands, app))
   212  		}
   213  	}
   214  	return currCmd
   215  }
   216  
   217  type goRunnerCmd struct {
   218  	name           string
   219  	app            appSupplier
   220  	runApp         func(args []string, supplier amalgomated.CmderSupplier) int
   221  	subcommandPath []string
   222  	pathToCfg      []string
   223  	// optional decorator for actions
   224  	decorator func(func(ctx cli.Context) error) func(ctx cli.Context) error
   225  }
   226  
   227  func (c *goRunnerCmd) cliCmd(supplier amalgomated.CmderSupplier) cli.Command {
   228  	// create the cliCmd app for this command
   229  	app := c.app(supplier)
   230  
   231  	// get the app command
   232  	cmdToRun := mustAppSubcommand(app, c.subcommandPath...)
   233  
   234  	// update name of command to be specified name
   235  	cmdToRun.Name = c.name
   236  
   237  	// decorate all actions so that it sets cfgcli config variables before command is run
   238  	c.decorateAllActions(&cmdToRun)
   239  
   240  	return cmdToRun
   241  }
   242  
   243  func (c *goRunnerCmd) decorateAllActions(cmd *cli.Command) {
   244  	if cmd != nil {
   245  		if cmd.Action != nil {
   246  			cmd.Action = c.decorateAction(cmd.Action)
   247  		}
   248  		for i := range cmd.Subcommands {
   249  			c.decorateAllActions(&cmd.Subcommands[i])
   250  		}
   251  	}
   252  }
   253  
   254  func (c *goRunnerCmd) namedCmd(supplier amalgomated.CmderSupplier) (*amalgomated.CmdWithRunner, error) {
   255  	return amalgomated.NewCmdWithRunner(c.name, func() {
   256  		c.runApp(os.Args, supplier)
   257  	})
   258  }
   259  
   260  func (c *goRunnerCmd) decorateAction(f func(ctx cli.Context) error) func(ctx cli.Context) error {
   261  	return func(ctx cli.Context) error {
   262  		wd, err := dirs.GetwdEvalSymLinks()
   263  		if err != nil {
   264  			return err
   265  		}
   266  		pathToCfg := c.name + ".yml"
   267  		if len(c.pathToCfg) != 0 {
   268  			pathToCfg = path.Join(c.pathToCfg...)
   269  		}
   270  		cfgFilePath, cfgJSON, err := cmd.Config(ctx, pathToCfg, wd)
   271  		if err != nil {
   272  			return err
   273  		}
   274  		cfgcli.ConfigPath = cfgFilePath
   275  		cfgcli.ConfigJSON = cfgJSON
   276  
   277  		if c.decorator != nil {
   278  			// if goRunnerCmd declares a decorator, decorate action with it before invoking
   279  			f = c.decorator(f)
   280  		}
   281  		return f(ctx)
   282  	}
   283  }