github.com/cpuid/libcompose@v0.4.0/cli/app/app.go (about) 1 package app 2 3 import ( 4 "fmt" 5 "os" 6 "os/signal" 7 "strconv" 8 "strings" 9 "syscall" 10 11 "golang.org/x/net/context" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/libcompose/project" 15 "github.com/docker/libcompose/project/options" 16 "github.com/docker/libcompose/version" 17 "github.com/urfave/cli" 18 ) 19 20 // ProjectAction is an adapter to allow the use of ordinary functions as libcompose actions. 21 // Any function that has the appropriate signature can be register as an action on a codegansta/cli command. 22 // 23 // cli.Command{ 24 // Name: "ps", 25 // Usage: "List containers", 26 // Action: app.WithProject(factory, app.ProjectPs), 27 // } 28 type ProjectAction func(project project.APIProject, c *cli.Context) error 29 30 // BeforeApp is an action that is executed before any cli command. 31 func BeforeApp(c *cli.Context) error { 32 if c.GlobalBool("verbose") { 33 logrus.SetLevel(logrus.DebugLevel) 34 } 35 36 if version.ShowWarning() { 37 logrus.Warning("Note: This is an experimental alternate implementation of the Compose CLI (https://github.com/docker/compose)") 38 } 39 return nil 40 } 41 42 // WithProject is a helper function to create a cli.Command action with a ProjectFactory. 43 func WithProject(factory ProjectFactory, action ProjectAction) func(context *cli.Context) error { 44 return func(context *cli.Context) error { 45 p, err := factory.Create(context) 46 if err != nil { 47 logrus.Fatalf("Failed to read project: %v", err) 48 } 49 return action(p, context) 50 } 51 } 52 53 // ProjectPs lists the containers. 54 func ProjectPs(p project.APIProject, c *cli.Context) error { 55 qFlag := c.Bool("q") 56 allInfo, err := p.Ps(context.Background(), c.Args()...) 57 if err != nil { 58 return cli.NewExitError(err.Error(), 1) 59 } 60 columns := []string{"Name", "Command", "State", "Ports"} 61 if qFlag { 62 columns = []string{"Id"} 63 } 64 os.Stdout.WriteString(allInfo.String(columns, !qFlag)) 65 return nil 66 } 67 68 // ProjectPort prints the public port for a port binding. 69 func ProjectPort(p project.APIProject, c *cli.Context) error { 70 if len(c.Args()) != 2 { 71 return cli.NewExitError("Please pass arguments in the form: SERVICE PORT", 1) 72 } 73 74 index := c.Int("index") 75 protocol := c.String("protocol") 76 serviceName := c.Args()[0] 77 privatePort := c.Args()[1] 78 79 port, err := p.Port(context.Background(), index, protocol, serviceName, privatePort) 80 if err != nil { 81 return cli.NewExitError(err.Error(), 1) 82 } 83 fmt.Println(port) 84 return nil 85 } 86 87 // ProjectStop stops all services. 88 func ProjectStop(p project.APIProject, c *cli.Context) error { 89 err := p.Stop(context.Background(), c.Int("timeout"), c.Args()...) 90 if err != nil { 91 return cli.NewExitError(err.Error(), 1) 92 } 93 return nil 94 } 95 96 // ProjectDown brings all services down (stops and clean containers). 97 func ProjectDown(p project.APIProject, c *cli.Context) error { 98 options := options.Down{ 99 RemoveVolume: c.Bool("volumes"), 100 RemoveImages: options.ImageType(c.String("rmi")), 101 RemoveOrphans: c.Bool("remove-orphans"), 102 } 103 err := p.Down(context.Background(), options, c.Args()...) 104 if err != nil { 105 return cli.NewExitError(err.Error(), 1) 106 } 107 return nil 108 } 109 110 // ProjectBuild builds or rebuilds services. 111 func ProjectBuild(p project.APIProject, c *cli.Context) error { 112 config := options.Build{ 113 NoCache: c.Bool("no-cache"), 114 ForceRemove: c.Bool("force-rm"), 115 Pull: c.Bool("pull"), 116 } 117 err := p.Build(context.Background(), config, c.Args()...) 118 if err != nil { 119 return cli.NewExitError(err.Error(), 1) 120 } 121 return nil 122 } 123 124 // ProjectCreate creates all services but do not start them. 125 func ProjectCreate(p project.APIProject, c *cli.Context) error { 126 options := options.Create{ 127 NoRecreate: c.Bool("no-recreate"), 128 ForceRecreate: c.Bool("force-recreate"), 129 NoBuild: c.Bool("no-build"), 130 } 131 err := p.Create(context.Background(), options, c.Args()...) 132 if err != nil { 133 return cli.NewExitError(err.Error(), 1) 134 } 135 return nil 136 } 137 138 // ProjectUp brings all services up. 139 func ProjectUp(p project.APIProject, c *cli.Context) error { 140 options := options.Up{ 141 Create: options.Create{ 142 NoRecreate: c.Bool("no-recreate"), 143 ForceRecreate: c.Bool("force-recreate"), 144 NoBuild: c.Bool("no-build"), 145 ForceBuild: c.Bool("build"), 146 }, 147 } 148 ctx, cancelFun := context.WithCancel(context.Background()) 149 err := p.Up(ctx, options, c.Args()...) 150 if err != nil { 151 return cli.NewExitError(err.Error(), 1) 152 } 153 if !c.Bool("d") { 154 signalChan := make(chan os.Signal, 1) 155 cleanupDone := make(chan bool) 156 signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 157 errChan := make(chan error) 158 go func() { 159 errChan <- p.Log(ctx, true, c.Args()...) 160 }() 161 go func() { 162 select { 163 case <-signalChan: 164 fmt.Printf("\nGracefully stopping...\n") 165 cancelFun() 166 ProjectStop(p, c) 167 cleanupDone <- true 168 case err := <-errChan: 169 if err != nil { 170 logrus.Fatal(err) 171 } 172 cleanupDone <- true 173 } 174 }() 175 <-cleanupDone 176 return nil 177 } 178 return nil 179 } 180 181 // ProjectRun runs a given command within a service's container. 182 func ProjectRun(p project.APIProject, c *cli.Context) error { 183 if len(c.Args()) == 0 { 184 logrus.Fatal("No service specified") 185 } 186 187 serviceName := c.Args()[0] 188 commandParts := c.Args()[1:] 189 190 options := options.Run{ 191 Detached: c.Bool("d"), 192 } 193 194 exitCode, err := p.Run(context.Background(), serviceName, commandParts, options) 195 if err != nil { 196 return cli.NewExitError(err.Error(), 1) 197 } 198 return cli.NewExitError("", exitCode) 199 } 200 201 // ProjectStart starts services. 202 func ProjectStart(p project.APIProject, c *cli.Context) error { 203 err := p.Start(context.Background(), c.Args()...) 204 if err != nil { 205 return cli.NewExitError(err.Error(), 1) 206 } 207 return nil 208 } 209 210 // ProjectRestart restarts services. 211 func ProjectRestart(p project.APIProject, c *cli.Context) error { 212 err := p.Restart(context.Background(), c.Int("timeout"), c.Args()...) 213 if err != nil { 214 return cli.NewExitError(err.Error(), 1) 215 } 216 return nil 217 } 218 219 // ProjectLog gets services logs. 220 func ProjectLog(p project.APIProject, c *cli.Context) error { 221 err := p.Log(context.Background(), c.Bool("follow"), c.Args()...) 222 if err != nil { 223 return cli.NewExitError(err.Error(), 1) 224 } 225 return nil 226 } 227 228 // ProjectPull pulls images for services. 229 func ProjectPull(p project.APIProject, c *cli.Context) error { 230 err := p.Pull(context.Background(), c.Args()...) 231 if err != nil && !c.Bool("ignore-pull-failures") { 232 return cli.NewExitError(err.Error(), 1) 233 } 234 return nil 235 } 236 237 // ProjectDelete deletes services. 238 func ProjectDelete(p project.APIProject, c *cli.Context) error { 239 options := options.Delete{ 240 RemoveVolume: c.Bool("v"), 241 } 242 if !c.Bool("force") { 243 stoppedContainers, err := p.Containers(context.Background(), project.Filter{ 244 State: project.Stopped, 245 }, c.Args()...) 246 if err != nil { 247 return cli.NewExitError(err.Error(), 1) 248 } 249 if len(stoppedContainers) == 0 { 250 fmt.Println("No stopped containers") 251 return nil 252 } 253 fmt.Printf("Going to remove %v\nAre you sure? [yN]\n", strings.Join(stoppedContainers, ", ")) 254 var answer string 255 _, err = fmt.Scanln(&answer) 256 if err != nil { 257 return cli.NewExitError(err.Error(), 1) 258 } 259 if answer != "y" && answer != "Y" { 260 return nil 261 } 262 } 263 err := p.Delete(context.Background(), options, c.Args()...) 264 if err != nil { 265 return cli.NewExitError(err.Error(), 1) 266 } 267 return nil 268 } 269 270 // ProjectKill forces stop service containers. 271 func ProjectKill(p project.APIProject, c *cli.Context) error { 272 err := p.Kill(context.Background(), c.String("signal"), c.Args()...) 273 if err != nil { 274 return cli.NewExitError(err.Error(), 1) 275 } 276 return nil 277 } 278 279 // ProjectConfig validates and print the compose file. 280 func ProjectConfig(p project.APIProject, c *cli.Context) error { 281 yaml, err := p.Config() 282 if err != nil { 283 return cli.NewExitError(err.Error(), 1) 284 } 285 if !c.Bool("quiet") { 286 fmt.Println(yaml) 287 } 288 return nil 289 } 290 291 // ProjectPause pauses service containers. 292 func ProjectPause(p project.APIProject, c *cli.Context) error { 293 err := p.Pause(context.Background(), c.Args()...) 294 if err != nil { 295 return cli.NewExitError(err.Error(), 1) 296 } 297 return nil 298 } 299 300 // ProjectUnpause unpauses service containers. 301 func ProjectUnpause(p project.APIProject, c *cli.Context) error { 302 err := p.Unpause(context.Background(), c.Args()...) 303 if err != nil { 304 return cli.NewExitError(err.Error(), 1) 305 } 306 return nil 307 } 308 309 // ProjectScale scales services. 310 func ProjectScale(p project.APIProject, c *cli.Context) error { 311 servicesScale := map[string]int{} 312 for _, arg := range c.Args() { 313 kv := strings.SplitN(arg, "=", 2) 314 if len(kv) != 2 { 315 return cli.NewExitError(fmt.Sprintf("Invalid scale parameter: %s", arg), 2) 316 } 317 318 name := kv[0] 319 320 count, err := strconv.Atoi(kv[1]) 321 if err != nil { 322 return cli.NewExitError(fmt.Sprintf("Invalid scale parameter: %v", err), 2) 323 } 324 325 servicesScale[name] = count 326 } 327 328 err := p.Scale(context.Background(), c.Int("timeout"), servicesScale) 329 if err != nil { 330 return cli.NewExitError(err.Error(), 0) 331 } 332 return nil 333 }