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 }