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 }