func AppAssign(configStore *config.Store, app, env, pool string) error { // Don't allow deleting runtime hosts entries if app == "hosts" || app == "pools" { return fmt.Errorf("invalid app name: %s", app) } exists, err := configStore.PoolExists(env, pool) if err != nil { return err } if !exists { log.Warnf("WARN: Pool %s does not exist.", pool) } created, err := configStore.AssignApp(app, env, pool) if err != nil { return err } if created { log.Printf("Assigned %s in env %s to pool %s.\n", app, env, pool) } else { log.Printf("%s already assigned to pool %s in env %s.\n", app, pool, env) } return nil }
func pullImage(appCfg config.App) (*docker.Image, error) { image, err := serviceRuntime.InspectImage(appCfg.Version()) if image != nil && image.ID == appCfg.VersionID() || appCfg.VersionID() == "" { return image, nil } log.Printf("Pulling %s version %s\n", appCfg.Name(), appCfg.Version()) image, err = serviceRuntime.PullImage(appCfg.Version(), appCfg.VersionID()) if image == nil || err != nil { log.Errorf("ERROR: Could not pull image %s: %s", appCfg.Version(), err) return nil, err } if image.ID != appCfg.VersionID() && len(appCfg.VersionID()) > 12 { log.Errorf("ERROR: Pulled image for %s does not match expected ID. Expected: %s: Got: %s", appCfg.Version(), image.ID[0:12], appCfg.VersionID()[0:12]) return nil, errors.New(fmt.Sprintf("failed to pull image ID %s", appCfg.VersionID()[0:12])) } log.Printf("Pulled %s\n", appCfg.Version()) return image, nil }
func (s *ServiceRuntime) stopContainer(container *docker.Container) error { if _, ok := blacklistedContainerId[container.ID]; ok { log.Printf("Container %s blacklisted. Won't try to stop.\n", container.ID) return nil } log.Printf("Stopping %s container %s\n", strings.TrimPrefix(container.Name, "/"), container.ID[0:12]) c := make(chan error, 1) go func() { c <- s.dockerClient.StopContainer(container.ID, 10) }() select { case err := <-c: if err != nil { log.Printf("ERROR: Unable to stop container: %s\n", container.ID) return err } case <-time.After(20 * time.Second): blacklistedContainerId[container.ID] = true log.Printf("ERROR: Timed out trying to stop container. Zombie?. Blacklisting: %s\n", container.ID) return nil } log.Printf("Stopped %s container %s\n", strings.TrimPrefix(container.Name, "/"), container.ID[0:12]) return nil // TODO: why is this commented out? // Should we verify that containers are actually removed somehow? /* return s.dockerClient.RemoveContainer(docker.RemoveContainerOptions{ ID: container.ID, RemoveVolumes: true, })*/ }
func poolCreate(c *cli.Context) { ensureEnvArg(c) ensurePoolArg(c) initStore(c) created, err := configStore.CreatePool(utils.GalaxyPool(c), utils.GalaxyEnv(c)) if err != nil { log.Fatalf("ERROR: Could not create pool: %s", err) return } if created { log.Printf("Pool %s created\n", utils.GalaxyPool(c)) } else { log.Printf("Pool %s already exists\n", utils.GalaxyPool(c)) } ec2host, err := runtime.EC2PublicHostname() if err != nil || ec2host == "" { log.Debug("not running from AWS, skipping pool creation") return } // now create the cloudformation stack // is this fails, the stack can be created separately with // stack:create_pool stackCreatePool(c) }
func AppDeploy(configStore *config.Store, serviceRuntime *runtime.ServiceRuntime, app, env, version string) error { log.Printf("Pulling image %s...", version) image, err := serviceRuntime.PullImage(version, "") if image == nil || err != nil { return fmt.Errorf("unable to pull %s. Has it been released yet?", version) } svcCfg, err := configStore.GetApp(app, env) if err != nil { return fmt.Errorf("unable to deploy app: %s.", err) } if svcCfg == nil { return fmt.Errorf("app %s does not exist. Create it first.", app) } svcCfg.SetVersion(version) svcCfg.SetVersionID(utils.StripSHA(image.ID)) updated, err := configStore.UpdateApp(svcCfg, env) if err != nil { return fmt.Errorf("could not store version: %s", err) } if !updated { return fmt.Errorf("%s NOT deployed.", version) } log.Printf("Deployed %s.\n", version) return nil }
func ConfigList(configStore *config.Store, app, env string) error { cfg, err := configStore.GetApp(app, env) if err != nil { return err } if cfg == nil { return fmt.Errorf("unable to list config for %s.", app) } keys := sort.StringSlice{"ENV"} for k, _ := range cfg.Env() { keys = append(keys, k) } keys.Sort() for _, k := range keys { if k == "ENV" { log.Printf("%s=%s\n", k, env) continue } log.Printf("%s=%s\n", k, cfg.Env()[k]) } return nil }
func pgPsql(c *cli.Context) { ensureEnvArg(c) initStore(c) app := ensureAppParam(c, "pg:psql") appCfg, err := configStore.GetApp(app, utils.GalaxyEnv(c)) if err != nil { log.Fatalf("ERROR: Unable to run command: %s.", err) return } database_url := appCfg.Env()["DATABASE_URL"] if database_url == "" { log.Printf("No DATABASE_URL configured. Set one with config:set first.") return } if !strings.HasPrefix(database_url, "postgres://") { log.Printf("DATABASE_URL is not a postgres database.") return } cmd := exec.Command("psql", database_url) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // Ignore SIGINT while the process is running ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt) defer func() { signal.Stop(ch) close(ch) }() go func() { for { _, ok := <-ch if !ok { break } } }() err = cmd.Start() if err != nil { log.Fatal(err) } err = cmd.Wait() if err != nil { fmt.Printf("Command finished with error: %v\n", err) } }
func monitorService(changedConfigs chan *config.ConfigChange) { for { var changedConfig *config.ConfigChange select { case changedConfig = <-changedConfigs: if changedConfig.Error != nil { log.Errorf("ERROR: Error watching changes: %s", changedConfig.Error) continue } if changedConfig.AppConfig == nil { continue } assigned, err := appAssigned(changedConfig.AppConfig.Name()) if err != nil { log.Errorf("ERROR: Error retrieving service config for %s: %s", changedConfig.AppConfig.Name(), err) if !loop { return } continue } if !assigned { continue } ch, ok := workerChans[changedConfig.AppConfig.Name()] if !ok { name := changedConfig.AppConfig.Name() ch := make(chan string) workerChans[name] = ch wg.Add(1) go restartContainers(name, ch) ch <- "deploy" log.Printf("Started new worker for %s\n", name) continue } if changedConfig.Restart { log.Printf("Restarting %s", changedConfig.AppConfig.Name()) ch <- "restart" } else { ch <- "deploy" } } } }
func (r *RedisBackend) subscribeChannel(key string, msgs chan string) { var wg sync.WaitGroup redisPool := redis.Pool{ MaxIdle: 1, IdleTimeout: 0, Dial: func() (redis.Conn, error) { return redis.DialTimeout("tcp", r.RedisHost, time.Second, 0, 0) }, // test every connection for now TestOnBorrow: r.testOnBorrow, } for { conn := redisPool.Get() // no defer, doesn't return if err := conn.Err(); err != nil { conn.Close() log.Printf("ERROR: %v\n", err) time.Sleep(5 * time.Second) continue } wg.Add(1) psc := redis.PubSubConn{Conn: conn} go func() { defer wg.Done() for { switch n := psc.Receive().(type) { case redis.Message: msg := string(n.Data) msgs <- msg case error: psc.Close() log.Printf("ERROR: %v\n", n) return } } }() wg.Add(1) go func() { defer wg.Done() psc.Subscribe(key) log.Printf("Monitoring for config changes on channel: %s\n", key) }() wg.Wait() conn.Close() } }
func Register(serviceRuntime *runtime.ServiceRuntime, configStore *config.Store, env, pool, hostIP, shuttleAddr string) { if shuttleAddr != "" { client = shuttle.NewClient(shuttleAddr) } RegisterAll(serviceRuntime, configStore, env, pool, hostIP, shuttleAddr, false) containerEvents := make(chan runtime.ContainerEvent) err := serviceRuntime.RegisterEvents(env, pool, hostIP, containerEvents) if err != nil { log.Printf("ERROR: Unable to register docker event listener: %s", err) } for { select { case ce := <-containerEvents: switch ce.Status { case "start": reg, err := configStore.RegisterService(env, pool, hostIP, ce.Container) if err != nil { log.Errorf("ERROR: Unable to register container: %s", err) continue } log.Printf("Registered %s running as %s for %s%s", strings.TrimPrefix(reg.ContainerName, "/"), reg.ContainerID[0:12], reg.Name, locationAt(reg)) registerShuttle(configStore, env, shuttleAddr) case "die", "stop": reg, err := configStore.UnRegisterService(env, pool, hostIP, ce.Container) if err != nil { log.Errorf("ERROR: Unable to unregister container: %s", err) continue } if reg != nil { log.Printf("Unregistered %s running as %s for %s%s", strings.TrimPrefix(reg.ContainerName, "/"), reg.ContainerID[0:12], reg.Name, locationAt(reg)) } RegisterAll(serviceRuntime, configStore, env, pool, hostIP, shuttleAddr, true) pruneShuttleBackends(configStore, env, shuttleAddr) } case <-time.After(10 * time.Second): RegisterAll(serviceRuntime, configStore, env, pool, hostIP, shuttleAddr, true) pruneShuttleBackends(configStore, env, shuttleAddr) } } }
func poolCreate(c *cli.Context) { ensureEnvArg(c) ensurePoolArg(c) initStore(c) created, err := configStore.CreatePool(utils.GalaxyPool(c), utils.GalaxyEnv(c)) if err != nil { log.Fatalf("ERROR: Could not create pool: %s", err) return } if created { log.Printf("Pool %s created\n", utils.GalaxyPool(c)) } else { log.Printf("Pool %s already exists\n", utils.GalaxyPool(c)) } }
func (s *ServiceRuntime) RegisterAll(env, pool, hostIP string) ([]*config.ServiceRegistration, error) { // make sure any old containers that shouldn't be running are gone // FIXME: I don't like how a "Register" function has the possible side // effect of stopping containers s.StopUnassigned(env, pool) containers, err := s.ManagedContainers() if err != nil { return nil, err } registrations := []*config.ServiceRegistration{} for _, container := range containers { name := s.EnvFor(container)["GALAXY_APP"] registration, err := s.configStore.RegisterService(env, pool, hostIP, container) if err != nil { log.Printf("ERROR: Could not register %s: %s\n", name, err.Error()) continue } registrations = append(registrations, registration) } return registrations, nil }
func poolDelete(c *cli.Context) { ensureEnvArg(c) ensurePoolArg(c) initStore(c) empty, err := configStore.DeletePool(utils.GalaxyPool(c), utils.GalaxyEnv(c)) if err != nil { log.Fatalf("ERROR: Could not delete pool: %s", err) return } if empty { log.Printf("Pool %s deleted\n", utils.GalaxyPool(c)) } else { log.Printf("Pool %s has apps assigned. Unassign them first.\n", utils.GalaxyPool(c)) } }
func AppUnassign(configStore *config.Store, app, env, pool string) error { // Don't allow deleting runtime hosts entries if app == "hosts" || app == "pools" { return fmt.Errorf("invalid app name: %s", app) } deleted, err := configStore.UnassignApp(app, env, pool) if err != nil { return err } if deleted { log.Printf("Unassigned %s in env %s from pool %s\n", app, env, pool) } else { log.Printf("%s could not be unassigned.\n", pool) } return nil }
// return --base, or try to find a base cloudformation stack func getBase(c *cli.Context) string { errNoBase := fmt.Errorf("could not identify a unique base stack") base := c.String("base") if base != "" { return base } descResp, err := stack.DescribeStacks("") if err != nil { log.Fatal(err) } for _, stack := range descResp.Stacks { // first check for galaxy:base tag baseTag := false for _, t := range stack.Tags { if t.Key == "galaxy" && t.Value == "base" { baseTag = true } } if baseTag { if base != "" { err = errNoBase } base = stack.Name continue } parts := strings.Split(stack.Name, "-") // check for "-base" in the name if parts[len(parts)-1] == "base" { if base != "" { err = errNoBase } base = stack.Name continue } // the best we can do for now is look for a stack with a single word if len(parts) == 1 { if base != "" { err = errNoBase } base = stack.Name log.Printf("Warning: guessing base stack: %s", base) } } if err != nil { log.Fatalf("%s: %s", err, "use --base") } return base }
func pullImage(appCfg config.App) (*docker.Image, error) { image, err := serviceRuntime.PullImage(appCfg.Version(), appCfg.VersionID()) if image == nil || err != nil { log.Errorf("ERROR: Could not pull image %s: %s", appCfg.Version(), err) return nil, err } log.Printf("Pulled %s version %s\n", appCfg.Name(), appCfg.Version()) return image, nil }
func AppCreate(configStore *config.Store, app, env string) error { // Don't allow creating runtime hosts entries if app == "hosts" { return fmt.Errorf("could not create app: %s", app) } created, err := configStore.CreateApp(app, env) if err != nil { return fmt.Errorf("could not create app: %s", err) } if created { log.Printf("Created %s in env %s.\n", app, env) } else { log.Printf("%s already exists in in env %s.", app, env) } return nil }
func AppDelete(configStore *config.Store, app, env string) error { // Don't allow deleting runtime hosts entries if app == "hosts" || app == "pools" { return fmt.Errorf("could not delete app: %s", app) } deleted, err := configStore.DeleteApp(app, env) if err != nil { return fmt.Errorf("could not delete app: %s", err) } if deleted { log.Printf("Deleted %s from env %s.\n", app, env) } else { log.Printf("%s does not exists in env %s.\n", app, env) } return nil }
// inspectImage checks that the running image matches the config. // We only use this to print warnings, since we likely need to deploy a new // config version to fix the inconsistency. func inspectImage(appCfg config.App) { image, err := serviceRuntime.InspectImage(appCfg.Version()) if err != nil { log.Println("error inspecting image", appCfg.Version()) return } if utils.StripSHA(image.ID) != appCfg.VersionID() { log.Printf("warning: %s image ID does not match config", appCfg.Name()) } }
func ConfigUnset(configStore *config.Store, app, env string, envVars []string) error { if len(envVars) == 0 { return fmt.Errorf("no config values specified.") } svcCfg, err := configStore.GetApp(app, env) if err != nil { return fmt.Errorf("unable to unset config: %s.", err) } updated := false for _, arg := range envVars { k := strings.ToUpper(strings.TrimSpace(arg)) if k == "ENV" || svcCfg.EnvGet(k) == "" { log.Warnf("%s cannot be unset.", k) continue } log.Printf("%s\n", k) svcCfg.EnvSet(strings.ToUpper(arg), "") updated = true } if !updated { return fmt.Errorf("Configuration NOT changed for %s", app) } updated, err = configStore.UpdateApp(svcCfg, env) if err != nil { return fmt.Errorf("ERROR: Unable to unset config: %s.", err) } if !updated { return fmt.Errorf("Configuration NOT changed for %s", app) } log.Printf("Configuration changed for %s. v%d.\n", app, svcCfg.ID()) return nil }
func (s *ServiceRuntime) UnRegisterAll(env, pool, hostIP string) ([]*docker.Container, error) { containers, err := s.ManagedContainers() if err != nil { return nil, err } removed := []*docker.Container{} for _, container := range containers { name := s.EnvFor(container)["GALAXY_APP"] _, err = s.configStore.UnRegisterService(env, pool, hostIP, container) if err != nil { log.Printf("ERROR: Could not unregister %s: %s\n", name, err) return removed, err } removed = append(removed, container) log.Printf("Unregistered %s as %s", container.ID[0:12], name) } return removed, nil }
func startService(appCfg config.App, logStatus bool) { desired, err := commander.Balanced(configStore, hostIP, appCfg.Name(), env, pool) if err != nil { log.Errorf("ERROR: Could not determine instance count: %s", err) return } running, err := serviceRuntime.InstanceCount(appCfg.Name(), strconv.FormatInt(appCfg.ID(), 10)) if err != nil { log.Errorf("ERROR: Could not determine running instance count: %s", err) return } for i := 0; i < desired-running; i++ { container, err := serviceRuntime.Start(env, pool, appCfg) if err != nil { log.Errorf("ERROR: Could not start containers: %s", err) return } log.Printf("Started %s version %s as %s\n", appCfg.Name(), appCfg.Version(), container.ID[0:12]) err = serviceRuntime.StopOldVersion(appCfg, 1) if err != nil { log.Errorf("ERROR: Could not stop containers: %s", err) } } running, err = serviceRuntime.InstanceCount(appCfg.Name(), strconv.FormatInt(appCfg.ID(), 10)) if err != nil { log.Errorf("ERROR: Could not determine running instance count: %s", err) return } for i := 0; i < running-desired; i++ { err := serviceRuntime.Stop(appCfg) if err != nil { log.Errorf("ERROR: Could not stop container: %s", err) } } err = serviceRuntime.StopOldVersion(appCfg, -1) if err != nil { log.Errorf("ERROR: Could not stop old containers: %s", err) } // check the image version, and log any inconsistencies inspectImage(appCfg) }
func (s *ServiceRuntime) StopOldVersion(appCfg config.App, limit int) error { containers, err := s.ManagedContainers() if err != nil { return err } stopped := 0 for _, container := range containers { if stopped == limit { return nil } env := s.EnvFor(container) // Container name does match one that would be started w/ this service config if env["GALAXY_APP"] != appCfg.Name() { continue } image, err := s.InspectImage(container.Image) if err != nil { log.Errorf("ERROR: Unable to inspect image: %s", container.Image) continue } if image == nil { log.Errorf("ERROR: Image for container %s does not exist!", container.ID[0:12]) continue } version := env["GALAXY_VERSION"] if version == "" { log.Printf("WARNING: %s missing GALAXY_VERSION", appCfg.ContainerName()) } if version != strconv.FormatInt(appCfg.ID(), 10) && version != "" { s.stopContainer(container) stopped = stopped + 1 } } return nil }
// FIXME: This can't be shut down. Make sure that's not a problem func (c *ConsulBackend) sub(key string, msgs chan string) { var events []*consul.UserEvent var meta *consul.QueryMeta var err error for { // No way to handle failure here, just keep trying to get our first set of events. // We need a successful query to get the last index to search from. events, meta, err = c.client.Event().List(key, nil) if err != nil { log.Println("Subscribe error:", err) time.Sleep(5 * time.Second) continue } // cache all old events c.seen.Filter(events) break } lastIndex := meta.LastIndex for { opts := &consul.QueryOptions{ WaitIndex: lastIndex, WaitTime: 30 * time.Second, } events, meta, err = c.client.Event().List(key, opts) if err != nil { log.Printf("Subscribe(%s): %s\n", key, err.Error()) continue } if meta.LastIndex == lastIndex { // no new events continue } for _, event := range c.seen.Filter(events) { msgs <- string(event.Payload) } lastIndex = meta.LastIndex } }
func (s *ServiceRuntime) ManagedContainers() ([]*docker.Container, error) { apps := []*docker.Container{} containers, err := s.dockerClient.ListContainers(docker.ListContainersOptions{ All: true, }) if err != nil { return apps, err } for _, c := range containers { container, err := s.dockerClient.InspectContainer(c.ID) if err != nil { log.Printf("ERROR: Unable to inspect container: %s\n", c.ID) continue } name := s.EnvFor(container)["GALAXY_APP"] if name != "" && (container.State.Running || container.State.Restarting) { apps = append(apps, container) } } return apps, nil }
func ConfigSet(configStore *config.Store, app, env string, envVars []string) error { if len(envVars) == 0 { bytes, err := ioutil.ReadAll(os.Stdin) if err != nil { return err } envVars = strings.Split(string(bytes), "\n") } if len(envVars) == 0 { return fmt.Errorf("no config values specified.") } svcCfg, err := configStore.GetApp(app, env) if err != nil { return fmt.Errorf("unable to set config: %s.", err) } if svcCfg == nil { svcCfg = configStore.NewAppConfig(app, "") } updated := false for _, arg := range envVars { if strings.TrimSpace(arg) == "" { continue } if !strings.Contains(arg, "=") { return fmt.Errorf("bad config variable format: %s", arg) } sep := strings.Index(arg, "=") k := strings.ToUpper(strings.TrimSpace(arg[0:sep])) v := strings.TrimSpace(arg[sep+1:]) if k == "ENV" { log.Warnf("%s cannot be updated.", k) continue } log.Printf("%s=%s\n", k, v) svcCfg.EnvSet(k, v) updated = true } if !updated { return fmt.Errorf("configuration NOT changed for %s", app) } updated, err = configStore.UpdateApp(svcCfg, env) if err != nil { return fmt.Errorf("unable to set config: %s.", err) } if !updated { return fmt.Errorf("configuration NOT changed for %s", app) } log.Printf("Configuration changed for %s. v%d\n", app, svcCfg.ID()) return nil }
// RegisterEvents monitors the docker daemon for events, and returns those // that require registration action over the listener chan. func (s *ServiceRuntime) RegisterEvents(env, pool, hostIP string, listener chan ContainerEvent) error { go func() { c := make(chan *docker.APIEvents) watching := false for { err := s.Ping() if err != nil { log.Errorf("ERROR: Unable to ping docker daemaon: %s", err) if watching { s.dockerClient.RemoveEventListener(c) watching = false } time.Sleep(10 * time.Second) continue } if !watching { err = s.dockerClient.AddEventListener(c) if err != nil && err != docker.ErrListenerAlreadyExists { log.Printf("ERROR: Error registering docker event listener: %s", err) time.Sleep(10 * time.Second) continue } watching = true } select { case e := <-c: if e.Status == "start" || e.Status == "stop" || e.Status == "die" { container, err := s.InspectContainer(e.ID) if err != nil { log.Printf("ERROR: Error inspecting container: %s", err) continue } if container == nil { log.Printf("WARN: Nil container returned for %s", e.ID[:12]) continue } name := s.EnvFor(container)["GALAXY_APP"] if name != "" { registration, err := s.configStore.GetServiceRegistration(env, pool, hostIP, container) if err != nil { log.Printf("WARN: Could not find service registration for %s/%s: %s", name, container.ID[:12], err) continue } if registration == nil && e.Status != "start" { continue } // if a container is restarting, don't continue re-registering the app if container.State.Restarting { continue } listener <- ContainerEvent{ Status: e.Status, Container: container, ServiceRegistration: registration, } } } case <-time.After(10 * time.Second): // check for docker liveness } } }() return nil }
func main() { flag.Int64Var(&stopCutoff, "cutoff", 10, "Seconds to wait before stopping old containers") flag.StringVar(®istryURL, "registry", utils.GetEnv("GALAXY_REGISTRY_URL", "redis://127.0.0.1:6379"), "registry URL") flag.StringVar(&env, "env", utils.GetEnv("GALAXY_ENV", ""), "Environment namespace") flag.StringVar(&pool, "pool", utils.GetEnv("GALAXY_POOL", ""), "Pool namespace") flag.StringVar(&hostIP, "host-ip", "127.0.0.1", "Host IP") flag.StringVar(&shuttleAddr, "shuttle-addr", "", "Shuttle API addr (127.0.0.1:9090)") flag.StringVar(&dns, "dns", "", "DNS addr to use for containers") flag.BoolVar(&debug, "debug", false, "verbose logging") flag.BoolVar(&version, "v", false, "display version info") flag.Usage = func() { println("Usage: commander [options] <command> [<args>]\n") println("Available commands are:") println(" agent Runs commander agent") println(" app List all apps") println(" app:assign Assign an app to a pool") println(" app:create Create an app") println(" app:deploy Deploy an app") println(" app:delete Delete an app") println(" app:restart Restart an app") println(" app:run Run a command within an app on this host") println(" app:shell Run a shell within an app on this host") println(" app:start Starts one or more apps") println(" app:stop Stops one or more apps") println(" app:unassign Unassign an app from a pool") println(" config List config for an app") println(" config:get Get config values for an app") println(" config:set Set config values for an app") println(" config:unset Unset config values for an app") println(" runtime List container runtime policies") println(" runtime:set Set container runtime policies") println(" hosts List hosts in an env and pool") println("\nOptions:\n") flag.PrintDefaults() } flag.Parse() if version { fmt.Println(buildVersion) return } log.DefaultLogger = log.New(os.Stdout, "", log.INFO) log.DefaultLogger.SetFlags(0) if debug { log.DefaultLogger.Level = log.DEBUG } if flag.NArg() < 1 { fmt.Println("Need a command") flag.Usage() os.Exit(1) } initOrDie() switch flag.Args()[0] { case "dump": if flag.NArg() < 2 { fmt.Println("Usage: commander dump ENV") os.Exit(1) } dump(flag.Arg(1)) return case "restore": if flag.NArg() < 2 { fmt.Println("Usage: commander dump ENV FILE") os.Exit(1) } restore(flag.Arg(1)) return case "agent": log.DefaultLogger.SetFlags(golog.LstdFlags) loop = true agentFs := flag.NewFlagSet("agent", flag.ExitOnError) agentFs.Usage = func() { println("Usage: commander agent [options]\n") println(" Runs commander continuously\n\n") println("Options:\n\n") agentFs.PrintDefaults() } agentFs.Parse(flag.Args()[1:]) ensureEnv() ensurePool() case "app": appFs := flag.NewFlagSet("app", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander app\n") println(" List all apps or apps in an environment\n") println("Options:\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) err := commander.AppList(configStore, env) if err != nil { log.Fatalf("ERROR: %s", err) } return case "app:assign": appFs := flag.NewFlagSet("app:assign", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander app:assign <app>\n") println(" Assign an app to a pool\n") println("Options:\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() ensurePool() if appFs.NArg() != 1 { appFs.Usage() os.Exit(1) } err := commander.AppAssign(configStore, appFs.Args()[0], env, pool) if err != nil { log.Fatalf("ERROR: %s", err) } return case "app:create": appFs := flag.NewFlagSet("app:create", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander app:create <app>\n") println(" Create an app in an environment\n") println("Options:\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() if appFs.NArg() == 0 { appFs.Usage() os.Exit(1) } err := commander.AppCreate(configStore, appFs.Args()[0], env) if err != nil { log.Fatalf("ERROR: %s", err) } return case "app:delete": appFs := flag.NewFlagSet("app:delete", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander app:delete <app>\n") println(" Delete an app in an environment\n") println("Options:\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() if appFs.NArg() == 0 { appFs.Usage() os.Exit(1) } err := commander.AppDelete(configStore, appFs.Args()[0], env) if err != nil { log.Fatalf("ERROR: %s", err) } return case "app:deploy": appFs := flag.NewFlagSet("app:delete", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander app:deploy [-force] <app> <version>\n") println(" Deploy an app in an environment\n") println("Options:\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() if appFs.NArg() != 2 { appFs.Usage() os.Exit(1) } err := commander.AppDeploy(configStore, serviceRuntime, appFs.Args()[0], env, appFs.Args()[1]) if err != nil { log.Fatalf("ERROR: %s", err) } return case "app:restart": appFs := flag.NewFlagSet("app:restart", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander app:restart <app>\n") println(" Restart an app in an environment\n") println("Options:\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() if appFs.NArg() == 0 { appFs.Usage() os.Exit(1) } err := commander.AppRestart(configStore, appFs.Args()[0], env) if err != nil { log.Fatalf("ERROR: %s", err) } return case "app:run": appFs := flag.NewFlagSet("app:run", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander app:run <app> <cmd>\n") println(" Restart an app in an environment\n") println("Options:\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() if appFs.NArg() < 2 { appFs.Usage() os.Exit(1) } err := commander.AppRun(configStore, serviceRuntime, appFs.Args()[0], env, appFs.Args()[1:]) if err != nil { log.Fatalf("ERROR: %s", err) } return case "app:shell": appFs := flag.NewFlagSet("app:shell", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander app:shell <app>\n") println(" Run a shell for an app\n") println("Options:\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() ensurePool() if appFs.NArg() != 1 { appFs.Usage() os.Exit(1) } err := commander.AppShell(configStore, serviceRuntime, appFs.Args()[0], env, pool) if err != nil { log.Fatalf("ERROR: %s", err) } return case "app:start": startFs := flag.NewFlagSet("app:start", flag.ExitOnError) startFs.Usage = func() { println("Usage: commander app:start [options] [<app>]*\n") println(" Starts one or more apps. If no apps are specified, starts all apps.\n") println("Options:\n") startFs.PrintDefaults() } startFs.Parse(flag.Args()[1:]) apps = startFs.Args() if len(apps) == 0 { acs, err := configStore.ListApps(env) if err != nil { log.Fatalf("ERROR: Unable to list apps: %s", err) } for _, ac := range acs { apps = append(apps, ac.Name()) } } break case "app:status": // FIXME: undocumented statusFs := flag.NewFlagSet("app:status", flag.ExitOnError) statusFs.Usage = func() { println("Usage: commander app:status [options] [<app>]*\n") println(" Lists status of running apps.\n") println("Options:\n") statusFs.PrintDefaults() } statusFs.Parse(flag.Args()[1:]) ensureEnv() ensurePool() err := discovery.Status(serviceRuntime, configStore, env, pool, hostIP) if err != nil { log.Fatalf("ERROR: Unable to list app status: %s", err) } return case "app:stop": stopFs := flag.NewFlagSet("app:stop", flag.ExitOnError) stopFs.Usage = func() { println("Usage: commander app:stop [options] [<app>]*\n") println(" Stops one or more apps. If no apps are specified, stops all apps.\n") println("Options:\n") stopFs.PrintDefaults() } stopFs.Parse(flag.Args()[1:]) apps = stopFs.Args() for _, app := range apps { err := serviceRuntime.StopAllMatching(app) if err != nil { log.Fatalf("ERROR: Unable able to stop all containers: %s", err) } } if len(apps) > 0 { return } err := serviceRuntime.StopAll(env) if err != nil { log.Fatalf("ERROR: Unable able to stop all containers: %s", err) } return case "app:unassign": appFs := flag.NewFlagSet("app:unassign", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander app:unassign <app>\n") println(" Unassign an app to a pool\n") println("Options:\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() ensurePool() if appFs.NArg() != 1 { appFs.Usage() os.Exit(1) } err := commander.AppUnassign(configStore, appFs.Args()[0], env, pool) if err != nil { log.Fatalf("ERROR: %s", err) } return case "hosts": hostFs := flag.NewFlagSet("hosts", flag.ExitOnError) hostFs.Usage = func() { println("Usage: commander hosts\n") println(" List hosts in an env and pool\n") println("Options:\n") hostFs.PrintDefaults() } err := hostFs.Parse(flag.Args()[1:]) if err != nil { log.Fatalf("ERROR: Bad command line options: %s", err) } ensureEnv() ensurePool() err = commander.HostsList(configStore, env, pool) if err != nil { log.Fatalf("ERROR: %s", err) } return case "config": configFs := flag.NewFlagSet("config", flag.ExitOnError) usage := "Usage: commander config <app>" configFs.Usage = func() { println(usage) println(" List config values for an app\n") println("Options:\n") configFs.PrintDefaults() } err := configFs.Parse(flag.Args()[1:]) if err != nil { log.Fatalf("ERROR: Bad command line options: %s", err) } ensureEnv() if configFs.NArg() != 1 { log.Error("ERROR: Missing app name argument") log.Printf("Usage: %s", usage) os.Exit(1) } app := configFs.Args()[0] err = commander.ConfigList(configStore, app, env) if err != nil { log.Fatalf("ERROR: %s", err) } return case "config:get": configFs := flag.NewFlagSet("config:get", flag.ExitOnError) configFs.Usage = func() { println("Usage: commander config <app> KEY [KEY]*\n") println(" Get config values for an app\n") println("Options:\n") configFs.PrintDefaults() } err := configFs.Parse(flag.Args()[1:]) if err != nil { log.Fatalf("ERROR: Bad command line options: %s", err) } ensureEnv() if configFs.NArg() == 0 { log.Errorf("ERROR: Missing app name") configFs.Usage() os.Exit(1) } app := configFs.Args()[0] err = commander.ConfigGet(configStore, app, env, configFs.Args()[1:]) if err != nil { log.Fatalf("ERROR: %s", err) } return case "config:set": configFs := flag.NewFlagSet("config:set", flag.ExitOnError) configFs.Usage = func() { println("Usage: commander config <app> KEY=VALUE [KEY=VALUE]*\n") println(" Set config values for an app\n") println("Options:\n") configFs.PrintDefaults() } err := configFs.Parse(flag.Args()[1:]) if err != nil { log.Fatalf("ERROR: Bad command line options: %s", err) } ensureEnv() if configFs.NArg() == 0 { log.Errorf("ERROR: Missing app name") configFs.Usage() os.Exit(1) } app := configFs.Args()[0] err = commander.ConfigSet(configStore, app, env, configFs.Args()[1:]) if err != nil { log.Fatalf("ERROR: %s", err) } return case "config:unset": configFs := flag.NewFlagSet("config:unset", flag.ExitOnError) configFs.Usage = func() { println("Usage: commander config <app> KEY [KEY]*\n") println(" Unset config values for an app\n") println("Options:\n") configFs.PrintDefaults() } err := configFs.Parse(flag.Args()[1:]) if err != nil { log.Fatalf("ERROR: Bad command line options: %s", err) } ensureEnv() if configFs.NArg() == 0 { log.Errorf("ERROR: Missing app name") configFs.Usage() os.Exit(1) } app := configFs.Args()[0] err = commander.ConfigUnset(configStore, app, env, configFs.Args()[1:]) if err != nil { log.Fatalf("ERROR: %s", err) } return case "runtime": runtimeFs := flag.NewFlagSet("runtime", flag.ExitOnError) runtimeFs.Usage = func() { println("Usage: commander runtime\n") println(" List container runtime policies\n") println("Options:\n") runtimeFs.PrintDefaults() } err := runtimeFs.Parse(flag.Args()[1:]) if err != nil { log.Fatalf("ERROR: Bad command line options: %s", err) } app := "" if runtimeFs.NArg() > 0 { app = runtimeFs.Args()[0] } err = commander.RuntimeList(configStore, app, env, pool) if err != nil { log.Fatalf("ERROR: %s", err) } return case "runtime:set": var ps int var m string var c string var vhost string var port string var maint string runtimeFs := flag.NewFlagSet("runtime:set", flag.ExitOnError) runtimeFs.IntVar(&ps, "ps", 0, "Number of instances to run across all hosts") runtimeFs.StringVar(&m, "m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)") runtimeFs.StringVar(&c, "c", "", "CPU shares (relative weight)") runtimeFs.StringVar(&vhost, "vhost", "", "Virtual host for HTTP routing") runtimeFs.StringVar(&port, "port", "", "Service port for service discovery") runtimeFs.StringVar(&maint, "maint", "", "Enable or disable maintenance mode") runtimeFs.Usage = func() { println("Usage: commander runtime:set [-ps 1] [-m 100m] [-c 512] [-vhost x.y.z] [-port 8000] [-maint false] <app>\n") println(" Set container runtime policies\n") println("Options:\n") runtimeFs.PrintDefaults() } err := runtimeFs.Parse(flag.Args()[1:]) if err != nil { log.Fatalf("ERROR: Bad command line options: %s", err) } ensureEnv() if ps != 0 || m != "" || c != "" || maint != "" { ensurePool() } if runtimeFs.NArg() != 1 { runtimeFs.Usage() os.Exit(1) } app := runtimeFs.Args()[0] _, err = utils.ParseMemory(m) if err != nil { log.Fatalf("ERROR: Bad memory option %s: %s", m, err) } updated, err := commander.RuntimeSet(configStore, app, env, pool, commander.RuntimeOptions{ Ps: ps, Memory: m, CPUShares: c, VirtualHost: vhost, Port: port, MaintenanceMode: maint, }) if err != nil { log.Fatalf("ERROR: %s", err) } if !updated { log.Fatalf("ERROR: Failed to set runtime options.") } if pool != "" { log.Printf("Runtime options updated for %s in %s running on %s", app, env, pool) } else { log.Printf("Runtime options updated for %s in %s", app, env) } return case "runtime:unset": var ps, m, c, port bool var vhost string runtimeFs := flag.NewFlagSet("runtime:unset", flag.ExitOnError) runtimeFs.BoolVar(&ps, "ps", false, "Number of instances to run across all hosts") runtimeFs.BoolVar(&m, "m", false, "Memory limit") runtimeFs.BoolVar(&c, "c", false, "CPU shares (relative weight)") runtimeFs.StringVar(&vhost, "vhost", "", "Virtual host for HTTP routing") runtimeFs.BoolVar(&port, "port", false, "Service port for service discovery") runtimeFs.Usage = func() { println("Usage: commander runtime:unset [-ps] [-m] [-c] [-vhost x.y.z] [-port] <app>\n") println(" Reset and removes container runtime policies to defaults\n") println("Options:\n") runtimeFs.PrintDefaults() } err := runtimeFs.Parse(flag.Args()[1:]) if err != nil { log.Fatalf("ERROR: Bad command line options: %s", err) } ensureEnv() if ps || m || c { ensurePool() } if runtimeFs.NArg() != 1 { runtimeFs.Usage() os.Exit(1) } app := runtimeFs.Args()[0] options := commander.RuntimeOptions{ VirtualHost: vhost, } if ps { options.Ps = -1 } if m { options.Memory = "-" } if c { options.CPUShares = "-" } if port { options.Port = "-" } updated, err := commander.RuntimeUnset(configStore, app, env, pool, options) if err != nil { log.Fatalf("ERROR: %s", err) } if !updated { log.Fatalf("ERROR: Failed to set runtime options.") } if pool != "" { log.Printf("Runtime options updated for %s in %s running on %s", app, env, pool) } else { log.Printf("Runtime options updated for %s in %s", app, env) } return case "pool": err := commander.ListPools(configStore, env) if err != nil { log.Fatal(err) } return case "pool:create": appFs := flag.NewFlagSet("pool:create", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander -env <env> pool:create <pool>\n") println(" Create a pool in <env>\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() if pool == "" && appFs.NArg() > 0 { pool = appFs.Arg(0) } else { ensurePool() } err := commander.PoolCreate(configStore, env, pool) if err != nil { log.Fatalf("ERROR: Could not create pool: %s", err) } fmt.Println("created pool:", pool) return case "pool:delete": appFs := flag.NewFlagSet("pool:delete", flag.ExitOnError) appFs.Usage = func() { println("Usage: commander -env <env> pool:delete <pool>\n") println(" Delete a pool from <env>\n") appFs.PrintDefaults() } appFs.Parse(flag.Args()[1:]) ensureEnv() if pool == "" && flag.NArg() > 1 { pool = flag.Arg(1) } else { ensurePool() } err := commander.PoolDelete(configStore, env, pool) if err != nil { log.Fatalf("ERROR: Could not delete pool: %s", err) return } fmt.Println("deleted pool:", pool) return default: fmt.Println("Unknown command") flag.Usage() os.Exit(1) } ensureEnv() ensurePool() log.Printf("Starting commander %s", buildVersion) log.Printf("env=%s pool=%s host-ip=%s registry=%s shuttle-addr=%s dns=%s cutoff=%ds", env, pool, hostIP, registryURL, shuttleAddr, dns, stopCutoff) defer func() { configStore.DeleteHost(env, pool, config.HostInfo{ HostIP: hostIP, }) }() for app, ch := range workerChans { if len(apps) == 0 || utils.StringInSlice(app, apps) { wg.Add(1) go restartContainers(app, ch) ch <- "deploy" } } if loop { wg.Add(1) go heartbeatHost() go discovery.Register(serviceRuntime, configStore, env, pool, hostIP, shuttleAddr) cancelChan := make(chan struct{}) // do we need to cancel ever? restartChan := configStore.Watch(env, cancelChan) monitorService(restartChan) } // TODO: do we still need a WaitGroup? wg.Wait() }
func (s *ServiceRuntime) Start(env, pool string, appCfg config.App) (*docker.Container, error) { img := appCfg.Version() imgIdRef := appCfg.Version() if appCfg.VersionID() != "" { imgIdRef = appCfg.VersionID() } // see if we have the image locally image, err := s.PullImage(img, imgIdRef) if err != nil { return nil, err } // setup env vars from etcd var envVars []string envVars = append(envVars, "ENV"+"="+env) for key, value := range appCfg.Env() { if key == "ENV" { continue } envVars = append(envVars, strings.ToUpper(key)+"="+s.replaceVarEnv(value, s.hostIP)) } instanceId, err := s.NextInstanceSlot(appCfg.Name(), strconv.FormatInt(appCfg.ID(), 10)) if err != nil { return nil, err } envVars = append(envVars, fmt.Sprintf("HOST_IP=%s", s.hostIP)) envVars = append(envVars, fmt.Sprintf("GALAXY_APP=%s", appCfg.Name())) envVars = append(envVars, fmt.Sprintf("GALAXY_VERSION=%s", strconv.FormatInt(appCfg.ID(), 10))) envVars = append(envVars, fmt.Sprintf("GALAXY_INSTANCE=%s", strconv.FormatInt(int64(instanceId), 10))) publicDns, err := EC2PublicHostname() if err != nil { log.Warnf("Unable to determine public hostname. Not on AWS? %s", err) publicDns = "127.0.0.1" } envVars = append(envVars, fmt.Sprintf("PUBLIC_HOSTNAME=%s", publicDns)) containerName := appCfg.ContainerName() + "." + strconv.FormatInt(int64(instanceId), 10) container, err := s.dockerClient.InspectContainer(containerName) _, ok := err.(*docker.NoSuchContainer) if err != nil && !ok { return nil, err } // Existing container is running or stopped. If the image has changed, stop // and re-create it. if container != nil && container.Image != image.ID { if container.State.Running || container.State.Restarting || container.State.Paused { log.Printf("Stopping %s version %s running as %s", appCfg.Name(), appCfg.Version(), container.ID[0:12]) err := s.dockerClient.StopContainer(container.ID, 10) if err != nil { return nil, err } } log.Printf("Removing %s version %s running as %s", appCfg.Name(), appCfg.Version(), container.ID[0:12]) err = s.dockerClient.RemoveContainer(docker.RemoveContainerOptions{ ID: container.ID, }) if err != nil { return nil, err } container = nil } if container == nil { config := &docker.Config{ Image: img, Env: envVars, } mem := appCfg.GetMemory(pool) if mem != "" { m, err := utils.ParseMemory(mem) if err != nil { return nil, err } config.Memory = m } cpu := appCfg.GetCPUShares(pool) if cpu != "" { if c, err := strconv.Atoi(cpu); err == nil { config.CPUShares = int64(c) } } log.Printf("Creating %s version %s", appCfg.Name(), appCfg.Version()) container, err = s.dockerClient.CreateContainer(docker.CreateContainerOptions{ Name: containerName, Config: config, }) if err != nil { return nil, err } } log.Printf("Starting %s version %s running as %s", appCfg.Name(), appCfg.Version(), container.ID[0:12]) config := &docker.HostConfig{ PublishAllPorts: true, RestartPolicy: docker.RestartPolicy{ Name: "on-failure", MaximumRetryCount: 16, }, } if s.dns != "" { config.DNS = []string{s.dns} } err = s.dockerClient.StartContainer(container.ID, config) return container, err }
func (s *ServiceRuntime) RunCommand(env string, appCfg config.App, cmd []string) (*docker.Container, error) { // see if we have the image locally fmt.Fprintf(os.Stderr, "Pulling latest image for %s\n", appCfg.Version()) _, err := s.PullImage(appCfg.Version(), appCfg.VersionID()) if err != nil { return nil, err } instanceId, err := s.NextInstanceSlot(appCfg.Name(), strconv.FormatInt(appCfg.ID(), 10)) if err != nil { return nil, err } envVars := []string{"ENV=" + env} for key, value := range appCfg.Env() { if key == "ENV" { continue } envVars = append(envVars, strings.ToUpper(key)+"="+s.replaceVarEnv(value, s.hostIP)) } envVars = append(envVars, "GALAXY_APP="+appCfg.Name()) envVars = append(envVars, "GALAXY_VERSION="+strconv.FormatInt(appCfg.ID(), 10)) envVars = append(envVars, fmt.Sprintf("GALAXY_INSTANCE=%s", strconv.FormatInt(int64(instanceId), 10))) runCmd := []string{"/bin/bash", "-c", strings.Join(cmd, " ")} container, err := s.dockerClient.CreateContainer(docker.CreateContainerOptions{ Config: &docker.Config{ Image: appCfg.Version(), Env: envVars, AttachStdout: true, AttachStderr: true, Cmd: runCmd, OpenStdin: false, }, }) if err != nil { return nil, err } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) go func(s *ServiceRuntime, containerId string) { <-c log.Println("Stopping container...") err := s.dockerClient.StopContainer(containerId, 3) if err != nil { log.Printf("ERROR: Unable to stop container: %s", err) } err = s.dockerClient.RemoveContainer(docker.RemoveContainerOptions{ ID: containerId, }) if err != nil { log.Printf("ERROR: Unable to stop container: %s", err) } }(s, container.ID) defer s.dockerClient.RemoveContainer(docker.RemoveContainerOptions{ ID: container.ID, }) config := &docker.HostConfig{} if s.dns != "" { config.DNS = []string{s.dns} } err = s.dockerClient.StartContainer(container.ID, config) if err != nil { return container, err } // FIXME: Hack to work around the race of attaching to a container before it's // actually running. Tried polling the container and then attaching but the // output gets lost sometimes if the command executes very quickly. Not sure // what's going on. time.Sleep(1 * time.Second) err = s.dockerClient.AttachToContainer(docker.AttachToContainerOptions{ Container: container.ID, OutputStream: os.Stdout, ErrorStream: os.Stderr, Logs: true, Stream: false, Stdout: true, Stderr: true, }) if err != nil { log.Printf("ERROR: Unable to attach to running container: %s", err.Error()) } s.dockerClient.WaitContainer(container.ID) return container, err }