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 }
// 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()) } }
// Pull a docker image. // If we have an image matching the tag, and the given id matches the current // image, don't fetch a new one from the registry. func (s *ServiceRuntime) PullImage(version, id string) (*docker.Image, error) { image, err := s.InspectImage(version) if err != nil && err != docker.ErrNoSuchImage { return nil, err } if image != nil && utils.StripSHA(image.ID) == id { return image, nil } registry, repository, tag := utils.SplitDockerImage(version) // pull it down locally pullOpts := docker.PullImageOptions{ Repository: repository, Tag: tag, OutputStream: log.DefaultLogger} dockerAuth := findAuth(registry) if registry != "" { pullOpts.Repository = registry + "/" + repository } else { pullOpts.Repository = repository } pullOpts.Registry = registry pullOpts.Tag = tag retries := 0 for { retries += 1 err = s.dockerClient.PullImage(pullOpts, dockerAuth) if err != nil { if retries > 3 { return image, err } log.Errorf("ERROR: error pulling image %s. Attempt %d: %s", version, retries, err) continue } break } return s.InspectImage(version) }
func (s *ServiceRuntime) Start(env, pool string, appCfg config.App) (*docker.Container, error) { img := appCfg.Version() image, err := s.PullImage(img, appCfg.VersionID()) if err != nil { return nil, err } if utils.StripSHA(image.ID) != appCfg.VersionID() { log.Warnf("warning: ID for image %s doesn't match configuration", img) } // 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, }, LogConfig: docker.LogConfig{ Type: "syslog", Config: map[string]string{"syslog-tag": containerName}, }, } if s.dns != "" { config.DNS = []string{s.dns} } err = s.dockerClient.StartContainer(container.ID, config) return container, err }