github.com/jdhenke/godel@v0.0.0-20161213181855-abeb3861bf0d/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/golicense/cmd/golicense"
    26  	"github.com/palantir/pkg/cli"
    27  	"github.com/palantir/pkg/cli/cfgcli"
    28  	"github.com/pkg/errors"
    29  
    30  	"github.com/palantir/godel/apps/distgo"
    31  	"github.com/palantir/godel/apps/gonform"
    32  	"github.com/palantir/godel/apps/gunit"
    33  	"github.com/palantir/godel/apps/okgo"
    34  	"github.com/palantir/godel/cmd"
    35  )
    36  
    37  func CfgCliCmdSet(gödelPath string) (amalgomated.CmdLibrary, error) {
    38  	cmds := make([]*amalgomated.CmdWithRunner, len(cfgCliCmds))
    39  	for i := range cfgCliCmds {
    40  		supplier := gödelRunnerSupplier(gödelPath, cfgCliCmds[i].name)
    41  		cmd, err := cfgCliCmds[i].namedCmd(supplier)
    42  		if err != nil {
    43  			return nil, errors.Wrapf(err, "failed to create command")
    44  		}
    45  		cmds[i] = cmd
    46  	}
    47  
    48  	cmdSet, err := amalgomated.NewStringCmdSetForRunners(cmds...)
    49  	if err != nil {
    50  		return nil, errors.Wrapf(err, "failed to create StringCmdSet for runners")
    51  	}
    52  
    53  	return amalgomated.NewCmdLibrary(cmdSet), nil
    54  }
    55  
    56  func CfgCliCommands(gödelPath string) []cli.Command {
    57  	var cmds []cli.Command
    58  	for _, cmd := range cfgCliCmds {
    59  		supplier := gödelRunnerSupplier(gödelPath, cmd.name)
    60  		cmds = append(cmds, cmd.cliCmd(supplier))
    61  	}
    62  	return cmds
    63  }
    64  
    65  func gödelRunnerSupplier(gödelPath, cmdName string) amalgomated.CmderSupplier {
    66  	return func(cmd amalgomated.Cmd) (amalgomated.Cmder, error) {
    67  		// first underscore indicates to gödel that it is running in impersonation mode, while second underscore
    68  		// signals this to the command itself (handled by "processHiddenCommand" in
    69  		// amalgomated.cmdSetApp.RunApp)
    70  		return amalgomated.PathCmder(gödelPath, amalgomated.ProxyCmdPrefix+cmdName, amalgomated.ProxyCmdPrefix+cmd.Name()), nil
    71  	}
    72  }
    73  
    74  var (
    75  	distgoCreator = func(supplier amalgomated.CmderSupplier) *cli.App {
    76  		return distgo.App()
    77  	}
    78  	distgoDecorator = func(f func(ctx cli.Context) error) func(ctx cli.Context) error {
    79  		return func(ctx cli.Context) error {
    80  			cfgDirPath, err := cmd.ConfigDirPath(ctx)
    81  			if err != nil {
    82  				return err
    83  			}
    84  			// use the project directory for the configuration as the working directory for distgo commands
    85  			projectPath := path.Join(cfgDirPath, "..", "..")
    86  			if err := os.Chdir(projectPath); err != nil {
    87  				return err
    88  			}
    89  			return f(ctx)
    90  		}
    91  	}
    92  	cfgCliCmds = []*goRunnerCmd{
    93  		{
    94  			name:   "format",
    95  			app:    gonform.App,
    96  			runApp: gonform.RunApp,
    97  		},
    98  		{
    99  			name:   "check",
   100  			app:    okgo.App,
   101  			runApp: okgo.RunApp,
   102  		},
   103  		{
   104  			name:   "test",
   105  			app:    gunit.App,
   106  			runApp: gunit.RunApp,
   107  		},
   108  		{
   109  			name: "imports",
   110  			app: func(supplier amalgomated.CmderSupplier) *cli.App {
   111  				return gocd.App()
   112  			},
   113  			pathToCfg: []string{"imports.yml"},
   114  		},
   115  		{
   116  			name: "license",
   117  			app: func(supplier amalgomated.CmderSupplier) *cli.App {
   118  				return golicense.App()
   119  			},
   120  			pathToCfg: []string{"license.yml"},
   121  		},
   122  		{
   123  			name:           "run",
   124  			app:            distgoCreator,
   125  			decorator:      distgoDecorator,
   126  			subcommandPath: []string{"run"},
   127  			pathToCfg:      []string{"dist.yml"},
   128  		},
   129  		{
   130  			name:           "build",
   131  			app:            distgoCreator,
   132  			decorator:      distgoDecorator,
   133  			subcommandPath: []string{"build"},
   134  			pathToCfg:      []string{"dist.yml"},
   135  		},
   136  		{
   137  			name:           "dist",
   138  			app:            distgoCreator,
   139  			decorator:      distgoDecorator,
   140  			subcommandPath: []string{"dist"},
   141  			pathToCfg:      []string{"dist.yml"},
   142  		},
   143  		{
   144  			name:           "artifacts",
   145  			app:            distgoCreator,
   146  			decorator:      distgoDecorator,
   147  			subcommandPath: []string{"artifacts"},
   148  			pathToCfg:      []string{"dist.yml"},
   149  		},
   150  		{
   151  			name:           "products",
   152  			app:            distgoCreator,
   153  			decorator:      distgoDecorator,
   154  			subcommandPath: []string{"products"},
   155  			pathToCfg:      []string{"dist.yml"},
   156  		},
   157  		{
   158  			name:           "publish",
   159  			app:            distgoCreator,
   160  			decorator:      distgoDecorator,
   161  			subcommandPath: []string{"publish"},
   162  			pathToCfg:      []string{"dist.yml"},
   163  		},
   164  	}
   165  )
   166  
   167  type appSupplier func(supplier amalgomated.CmderSupplier) *cli.App
   168  
   169  // locator that returns the subcommand
   170  func mustAppSubcommand(app *cli.App, subcommands ...string) cli.Command {
   171  	currCmd := app.Command
   172  	for _, currDesiredSubcmd := range subcommands {
   173  		found := false
   174  		for _, currActualSubcmd := range currCmd.Subcommands {
   175  			if currActualSubcmd.Name == currDesiredSubcmd {
   176  				currCmd = currActualSubcmd
   177  				found = true
   178  				break
   179  			}
   180  		}
   181  		if !found {
   182  			panic(fmt.Sprintf("subcommand %v not present in command %v (full path: %v, top-level command: %v)", currDesiredSubcmd, currCmd.Subcommands, subcommands, app))
   183  		}
   184  	}
   185  	return currCmd
   186  }
   187  
   188  type goRunnerCmd struct {
   189  	name           string
   190  	app            appSupplier
   191  	runApp         func(args []string, supplier amalgomated.CmderSupplier) int
   192  	subcommandPath []string
   193  	pathToCfg      []string
   194  	// optional decorator for actions
   195  	decorator func(func(ctx cli.Context) error) func(ctx cli.Context) error
   196  }
   197  
   198  func (c *goRunnerCmd) cliCmd(supplier amalgomated.CmderSupplier) cli.Command {
   199  	// create the cliCmd app for this command
   200  	app := c.app(supplier)
   201  
   202  	// get the app command
   203  	cmdToRun := mustAppSubcommand(app, c.subcommandPath...)
   204  
   205  	// update name of command to be specified name
   206  	cmdToRun.Name = c.name
   207  
   208  	// decorate all actions so that it sets cfgcli config variables before command is run
   209  	c.decorateAllActions(&cmdToRun)
   210  
   211  	return cmdToRun
   212  }
   213  
   214  func (c *goRunnerCmd) decorateAllActions(cmd *cli.Command) {
   215  	if cmd != nil {
   216  		if cmd.Action != nil {
   217  			cmd.Action = c.decorateAction(cmd.Action)
   218  		}
   219  		for i := range cmd.Subcommands {
   220  			c.decorateAllActions(&cmd.Subcommands[i])
   221  		}
   222  	}
   223  }
   224  
   225  func (c *goRunnerCmd) namedCmd(supplier amalgomated.CmderSupplier) (*amalgomated.CmdWithRunner, error) {
   226  	return amalgomated.NewCmdWithRunner(c.name, func() {
   227  		c.runApp(os.Args, supplier)
   228  	})
   229  }
   230  
   231  func (c *goRunnerCmd) decorateAction(f func(ctx cli.Context) error) func(ctx cli.Context) error {
   232  	return func(ctx cli.Context) error {
   233  		wd, err := dirs.GetwdEvalSymLinks()
   234  		if err != nil {
   235  			return err
   236  		}
   237  		pathToCfg := c.name + ".yml"
   238  		if len(c.pathToCfg) != 0 {
   239  			pathToCfg = path.Join(c.pathToCfg...)
   240  		}
   241  		cfgFilePath, cfgJSON, err := cmd.Config(ctx, pathToCfg, wd)
   242  		if err != nil {
   243  			return err
   244  		}
   245  		cfgcli.ConfigPath = cfgFilePath
   246  		cfgcli.ConfigJSON = cfgJSON
   247  
   248  		if c.decorator != nil {
   249  			// if goRunnerCmd declares a decorator, decorate action with it before invoking
   250  			f = c.decorator(f)
   251  		}
   252  		return f(ctx)
   253  	}
   254  }