func AppRestart(Store *config.Store, app, env string) error { err := Store.NotifyRestart(app, env) if err != nil { return fmt.Errorf("could not restart %s: %s", app, err) } return nil }
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 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 } fmt.Printf("%s=%s\n", k, cfg.Env()[k]) } return nil }
func RuntimeUnset(configStore *config.Store, app, env, pool string, options RuntimeOptions) (bool, error) { cfg, err := configStore.GetApp(app, env) if err != nil { return false, err } if options.Ps != 0 { cfg.SetProcesses(pool, -1) } if options.Memory != "" { cfg.SetMemory(pool, "") } vhosts := strings.Split(cfg.Env()["VIRTUAL_HOST"], ",") if options.VirtualHost != "" && utils.StringInSlice(options.VirtualHost, vhosts) { vhosts = utils.RemoveStringInSlice(options.VirtualHost, vhosts) cfg.EnvSet("VIRTUAL_HOST", strings.Join(vhosts, ",")) } if options.Port != "" { cfg.EnvSet("GALAXY_PORT", "") } return configStore.UpdateApp(cfg, env) }
func RuntimeSet(configStore *config.Store, app, env, pool string, options RuntimeOptions) (bool, error) { cfg, err := configStore.GetApp(app, env) if err != nil { return false, err } if options.Ps != 0 && options.Ps != cfg.GetProcesses(pool) { cfg.SetProcesses(pool, options.Ps) } if options.Memory != "" && options.Memory != cfg.GetMemory(pool) { cfg.SetMemory(pool, options.Memory) } vhosts := []string{} vhostsFromEnv := cfg.Env()["VIRTUAL_HOST"] if vhostsFromEnv != "" { vhosts = strings.Split(cfg.Env()["VIRTUAL_HOST"], ",") } if options.VirtualHost != "" && !utils.StringInSlice(options.VirtualHost, vhosts) { vhosts = append(vhosts, options.VirtualHost) cfg.EnvSet("VIRTUAL_HOST", strings.Join(vhosts, ",")) } if options.Port != "" { cfg.EnvSet("GALAXY_PORT", options.Port) } return configStore.UpdateApp(cfg, env) }
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 RuntimeList(configStore *config.Store, app, env, pool string) error { envs := []string{env} if env == "" { var err error envs, err = configStore.ListEnvs() if err != nil { return err } } columns := []string{"ENV | NAME | POOL | PS | MEM | VHOSTS | PORT | MAINT"} for _, env := range envs { appList, err := configStore.ListApps(env) if err != nil { return err } for _, appCfg := range appList { if app != "" && appCfg.Name() != app { continue } for _, p := range appCfg.RuntimePools() { if pool != "" && p != pool { continue } name := appCfg.Name() ps := appCfg.GetProcesses(p) mem := appCfg.GetMemory(p) columns = append(columns, strings.Join([]string{ env, name, p, strconv.FormatInt(int64(ps), 10), mem, appCfg.Env()["VIRTUAL_HOST"], appCfg.Env()["GALAXY_PORT"], fmt.Sprint(appCfg.GetMaintenanceMode(p)), }, " | ")) } } } output := columnize.SimpleFormat(columns) fmt.Println(output) return nil }
func ConfigGet(configStore *config.Store, app, env string, envVars []string) error { cfg, err := configStore.GetApp(app, env) if err != nil { return err } for _, arg := range envVars { fmt.Printf("%s=%s\n", strings.ToUpper(arg), cfg.Env()[strings.ToUpper(arg)]) } return nil }
func unregisterShuttle(configStore *config.Store, env, hostIP, shuttleAddr string) { if client == nil { return } registrations, err := configStore.ListRegistrations(env) if err != nil { log.Errorf("ERROR: Unable to list registrations: %s", err) return } backends := make(map[string]*shuttle.ServiceConfig) for _, r := range registrations { // Registration for a container on a different host? Skip it. if r.ExternalIP != hostIP { continue } // No service ports exposed on the host, skip it. if r.ExternalAddr() == "" || r.Port == "" { continue } service := backends[r.Name] if service == nil { service = &shuttle.ServiceConfig{ Name: r.Name, VirtualHosts: r.VirtualHosts, } if r.Port != "" { service.Addr = "0.0.0.0:" + r.Port } backends[r.Name] = service } b := shuttle.BackendConfig{ Name: r.ContainerID[0:12], Addr: r.ExternalAddr(), } service.Backends = append(service.Backends, b) } for _, service := range backends { err := client.RemoveService(service.Name) if err != nil { log.Errorf("ERROR: Unable to remove shuttle service: %s", err) } } }
func AppShell(configStore *config.Store, serviceRuntime *runtime.ServiceRuntime, app, env, pool string) error { appCfg, err := configStore.GetApp(app, env) if err != nil { return fmt.Errorf("unable to run command: %s.", err) } err = serviceRuntime.StartInteractive(env, pool, appCfg) if err != nil { return fmt.Errorf("could not start container: %s", err) } return nil }
func Status(serviceRuntime *runtime.ServiceRuntime, configStore *config.Store, env, pool, hostIP string) error { containers, err := serviceRuntime.ManagedContainers() if err != nil { panic(err) } //FIXME: addresses, port, and expires missing in output columns := []string{ "APP | CONTAINER ID | IMAGE | EXTERNAL | INTERNAL | PORT | CREATED | EXPIRES"} for _, container := range containers { name := serviceRuntime.EnvFor(container)["GALAXY_APP"] registered, err := configStore.GetServiceRegistration( env, pool, hostIP, container) if err != nil { return err } if registered != nil { columns = append(columns, strings.Join([]string{ registered.Name, registered.ContainerID[0:12], registered.Image, registered.ExternalAddr(), registered.InternalAddr(), registered.Port, utils.HumanDuration(time.Now().UTC().Sub(registered.StartedAt)) + " ago", "In " + utils.HumanDuration(registered.Expires.Sub(time.Now().UTC())), }, " | ")) } else { columns = append(columns, strings.Join([]string{ name, container.ID[0:12], container.Image, "", "", "", utils.HumanDuration(time.Now().Sub(container.Created)) + " ago", "", }, " | ")) } } result, _ := columnize.SimpleFormat(columns) log.Println(result) return nil }
func AppRun(configStore *config.Store, serviceRuntime *runtime.ServiceRuntime, app, env string, args []string) error { appCfg, err := configStore.GetApp(app, env) if err != nil { return fmt.Errorf("unable to run command: %s.", err) } _, err = serviceRuntime.RunCommand(env, appCfg, args) if err != nil { return fmt.Errorf("could not start container: %s", err) } return nil }
func AppList(configStore *config.Store, env string) error { envs := []string{env} if env == "" { var err error envs, err = configStore.ListEnvs() if err != nil { return err } } columns := []string{"NAME | ENV | VERSION | IMAGE ID | CONFIG | POOLS "} for _, env := range envs { appList, err := configStore.ListApps(env) if err != nil { return err } pools, err := configStore.ListPools(env) if err != nil { return err } for _, app := range appList { name := app.Name() versionDeployed := app.Version() versionID := app.VersionID() if len(versionID) > 12 { versionID = versionID[:12] } assignments := []string{} for _, pool := range pools { aa, err := configStore.ListAssignments(env, pool) if err != nil { return err } if utils.StringInSlice(app.Name(), aa) { assignments = append(assignments, pool) } } columns = append(columns, strings.Join([]string{ name, env, versionDeployed, versionID, strconv.FormatInt(app.ID(), 10), strings.Join(assignments, ","), }, " | ")) } } output := columnize.SimpleFormat(columns) fmt.Println(output) return nil }
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) } } }
// Create a pool for an environment func PoolCreate(configStore *config.Store, env, pool string) error { exists, err := configStore.PoolExists(env, pool) if err != nil { return err } else if exists { return fmt.Errorf("pool '%s' exists", pool) } _, err = configStore.CreatePool(pool, env) if err != nil { return err } return nil }
// Balanced returns the number of instances that should be run on the host // according to the desired state for the app in the given env and pool. The // number returned for the host represent an approximately equal distribution // across all hosts. func Balanced(configStore *config.Store, hostId, app, env, pool string) (int, error) { hosts, err := configStore.ListHosts(env, pool) if err != nil { return 0, err } cfg, err := configStore.GetApp(app, env) if err != nil { return 0, err } desired := cfg.GetProcesses(pool) if desired == 0 { return 0, nil } if desired == -1 { return 1, nil } hostIds := []string{} for _, h := range hosts { hostIds = append(hostIds, h.HostIP) } sort.Strings(hostIds) hostIdx := -1 for i, v := range hostIds { if v == hostId { hostIdx = i break } } if hostIdx < 0 { return 0, nil } count := 0 for i := 0; i < desired; i++ { if i%len(hosts) == hostIdx { count = count + 1 } } return count, nil }
func PoolDelete(configStore *config.Store, env, pool string) error { exists, err := configStore.PoolExists(env, pool) if err != nil { return err } else if !exists { return fmt.Errorf("pool '%s' does not exist", pool) } empty, err := configStore.DeletePool(pool, env) if err != nil { return err } if !empty { return fmt.Errorf("pool '%s' is not epmty", pool) } return nil }
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 }
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 }
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 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 HostsList(configStore *config.Store, env, pool string) error { envs := []string{env} if env == "" { var err error envs, err = configStore.ListEnvs() if err != nil { return err } } columns := []string{"ENV | POOL | HOST IP "} for _, env := range envs { var err error pools := []string{pool} if pool == "" { pools, err = configStore.ListPools(env) if err != nil { return err } } for _, pool := range pools { hosts, err := configStore.ListHosts(env, pool) if err != nil { return err } if len(hosts) == 0 { columns = append(columns, strings.Join([]string{ env, pool, "", }, " | ")) continue } for _, p := range hosts { columns = append(columns, strings.Join([]string{ env, pool, p.HostIP, }, " | ")) } } } output, _ := columnize.SimpleFormat(columns) log.Println(output) return nil }
// TODO: shouldn't the command cmd be printing the output, and not the package? // The app, config, host, and runtime sections all do this too. (otherwise we // should just combine the two packages). And why do we print the output here, // but print the error in main??? func ListPools(configStore *config.Store, env string) error { var envs []string var err error if env != "" { envs = []string{env} } else { envs, err = configStore.ListEnvs() if err != nil { return err } } columns := []string{"ENV | POOL | APPS "} for _, env := range envs { pools, err := configStore.ListPools(env) if err != nil { return fmt.Errorf("ERROR: cannot list pools: %s", err) } if len(pools) == 0 { columns = append(columns, strings.Join([]string{ env, "", ""}, " | ")) continue } for _, pool := range pools { assigments, err := configStore.ListAssignments(env, pool) if err != nil { fmt.Printf("ERROR: cannot list pool assignments for %s/%s: %s", env, pool, err) } columns = append(columns, strings.Join([]string{ env, pool, strings.Join(assigments, ",")}, " | ")) } } fmt.Println(columnize.SimpleFormat(columns)) return nil }
func pruneShuttleBackends(configStore *config.Store, env, shuttleAddr string) { if client == nil { return } config, err := client.GetConfig() if err != nil { log.Errorf("ERROR: Unable to get shuttle config: %s", err) return } registrations, err := configStore.ListRegistrations(env) if err != nil { log.Errorf("ERROR: Unable to list registrations: %s", err) return } // FIXME: THERE SHOULD HAVE BEEN AN ERROR IF `len(registrations) == 0` IS WRONG! if len(registrations) == 0 { // If there are no registrations, skip pruning it because we might be in a bad state and // don't want to inadvertently unregister everything. Shuttle will handle the down // nodes if they are really down. return } for _, service := range config.Services { app, err := configStore.GetApp(service.Name, env) if err != nil { log.Errorf("ERROR: Unable to load app %s: %s", app, err) continue } pools, err := configStore.ListAssignedPools(env, service.Name) if err != nil { log.Errorf("ERROR: Unable to list pool assignments for %s: %s", service.Name, err) continue } if app == nil || len(pools) == 0 { err := client.RemoveService(service.Name) if err != nil { log.Errorf("ERROR: Unable to remove service %s from shuttle: %s", service.Name, err) } log.Printf("Unregisterred shuttle service %s", service.Name) continue } for _, backend := range service.Backends { backendExists := false for _, r := range registrations { if backend.Name == r.ContainerID[0:12] { backendExists = true break } } if !backendExists { err := client.RemoveBackend(service.Name, backend.Name) if err != nil { log.Errorf("ERROR: Unable to remove backend %s from shuttle: %s", backend.Name, err) } log.Printf("Unregisterred shuttle backend %s", backend.Name) } } } }
func registerShuttle(configStore *config.Store, env, shuttleAddr string) { if client == nil { return } registrations, err := configStore.ListRegistrations(env) if err != nil { log.Errorf("ERROR: Unable to list registrations: %s", err) return } backends := make(map[string]*shuttle.ServiceConfig) for _, r := range registrations { // No service ports exposed on the host, skip it. if r.ExternalAddr() == "" { continue } service := backends[r.Name] if service == nil { service = &shuttle.ServiceConfig{ Name: r.Name, VirtualHosts: r.VirtualHosts, } if r.Port != "" { service.Addr = "0.0.0.0:" + r.Port } backends[r.Name] = service } b := shuttle.BackendConfig{ Name: r.ContainerID[0:12], Addr: r.ExternalAddr(), CheckAddr: r.ExternalAddr(), } service.Backends = append(service.Backends, b) // lookup the VIRTUAL_HOST_%d environment variables and load them into the ServiceConfig errorPages := make(map[string][]int) for vhostCode, url := range r.ErrorPages { code := 0 n, err := fmt.Sscanf(vhostCode, "VIRTUAL_HOST_%d", &code) if err != nil || n == 0 { continue } errorPages[url] = append(errorPages[url], code) } if len(errorPages) > 0 { service.ErrorPages = errorPages } } for _, service := range backends { err := client.UpdateService(service) if err != nil { log.Errorf("ERROR: Unable to register shuttle service: %s", err) } } }
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 }