func newDeployOpts(ctx context.Context, w http.ResponseWriter, req *http.Request) (*empire.DeployOpts, error) { var form PostDeployForm if err := Decode(req, &form); err != nil { return nil, err } m, err := findMessage(req) if err != nil { return nil, err } w.Header().Set("Content-Type", "application/json; boundary=NL") if form.Image.Tag == "" && form.Image.Digest == "" { form.Image.Tag = "latest" } opts := empire.DeployOpts{ User: auth.UserFromContext(ctx), Image: form.Image, Output: empire.NewDeploymentStream(streamhttp.StreamingResponseWriter(w)), Message: m, Stream: form.Stream, } return &opts, nil }
// Deploy builds/determines the docker image to deploy, then deploys it with // Empire. func (d *EmpireDeployer) Deploy(ctx context.Context, event events.Deployment, w io.Writer) error { img, err := d.BuildImage(ctx, w, event) if err != nil { return err } // What we write to w should be plain text. `p` will get the jsonmessage // stream. p := dockerutil.DecodeJSONMessageStream(w) message := event.Deployment.Description if message == "" { message = fmt.Sprintf("GitHub deployment #%d of %s", event.Deployment.ID, event.Repository.FullName) } _, err = d.empire.Deploy(ctx, empire.DeployOpts{ Image: img, Output: empire.NewDeploymentStream(p), User: &empire.User{Name: event.Deployment.Creator.Login}, Stream: true, Message: message, }) if err != nil { return err } return p.Err() }
func TestEmpire_Deploy_ImageNotFound(t *testing.T) { e := empiretest.NewEmpire(t) s := new(mockScheduler) e.Scheduler = s e.ProcfileExtractor = empire.ProcfileExtractorFunc(func(ctx context.Context, img image.Image, w io.Writer) ([]byte, error) { return nil, errors.New("image not found") }) // Deploying an image to an app that doesn't exist will create a new // app. _, err := e.Deploy(context.Background(), empire.DeployOpts{ User: &empire.User{Name: "ejholmes"}, Output: empire.NewDeploymentStream(ioutil.Discard), Image: image.Image{Repository: "remind101/acme-inc"}, }) assert.Error(t, err) // If there's an error deploying, then the transaction should be rolled // backed and no apps should exist. apps, err := e.Apps(empire.AppsQuery{}) assert.NoError(t, err) assert.Equal(t, 0, len(apps)) s.AssertExpectations(t) }
func TestEmpire_Deploy(t *testing.T) { e := empiretest.NewEmpire(t) s := new(mockScheduler) e.Scheduler = s user := &empire.User{Name: "ejholmes"} app, err := e.Create(context.Background(), empire.CreateOpts{ User: user, Name: "acme-inc", }) assert.NoError(t, err) img := image.Image{Repository: "remind101/acme-inc"} s.On("Submit", &scheduler.App{ ID: app.ID, Name: "acme-inc", Release: "v1", Env: map[string]string{ "EMPIRE_APPID": app.ID, "EMPIRE_APPNAME": "acme-inc", "EMPIRE_RELEASE": "v1", }, Labels: map[string]string{ "empire.app.name": "acme-inc", "empire.app.id": app.ID, "empire.app.release": "v1", }, Processes: []*scheduler.Process{ { Type: "scheduled", Image: img, Command: []string{"./bin/scheduled"}, Schedule: scheduler.CRONSchedule("* * * * * *"), Instances: 0, MemoryLimit: 536870912, CPUShares: 256, Nproc: 256, Env: map[string]string{ "EMPIRE_PROCESS": "scheduled", "EMPIRE_PROCESS_SCALE": "0", "SOURCE": "acme-inc.scheduled.v1", }, Labels: map[string]string{ "empire.app.process": "scheduled", }, }, { Type: "web", Image: img, Command: []string{"./bin/web"}, Exposure: &scheduler.Exposure{ Type: &scheduler.HTTPExposure{}, }, Instances: 1, MemoryLimit: 536870912, CPUShares: 256, Nproc: 256, Env: map[string]string{ "EMPIRE_PROCESS": "web", "EMPIRE_PROCESS_SCALE": "1", "SOURCE": "acme-inc.web.v1", }, Labels: map[string]string{ "empire.app.process": "web", }, }, { Type: "worker", Image: img, Command: []string{"./bin/worker"}, Instances: 0, MemoryLimit: 536870912, CPUShares: 256, Nproc: 256, Env: map[string]string{ "EMPIRE_PROCESS": "worker", "EMPIRE_PROCESS_SCALE": "0", "SOURCE": "acme-inc.worker.v1", }, Labels: map[string]string{ "empire.app.process": "worker", }, }, }, }).Return(nil) _, err = e.Deploy(context.Background(), empire.DeployOpts{ App: app, User: user, Output: empire.NewDeploymentStream(ioutil.Discard), Image: img, }) assert.NoError(t, err) s.AssertExpectations(t) }
func TestEmpire_Set(t *testing.T) { e := empiretest.NewEmpire(t) s := new(mockScheduler) e.Scheduler = s e.ProcfileExtractor = empiretest.ExtractProcfile(procfile.ExtendedProcfile{ "web": procfile.Process{ Command: []string{"./bin/web"}, }, }) user := &empire.User{Name: "ejholmes"} // Create an app app, err := e.Create(context.Background(), empire.CreateOpts{ User: user, Name: "acme-inc", }) assert.NoError(t, err) // Add some environment variables to it. prod := "production" _, err = e.Set(context.Background(), empire.SetOpts{ User: user, App: app, Vars: empire.Vars{ "RAILS_ENV": &prod, }, }) assert.NoError(t, err) // Deploy a new image to the app. img := image.Image{Repository: "remind101/acme-inc"} s.On("Submit", &scheduler.App{ ID: app.ID, Name: "acme-inc", Release: "v1", Env: map[string]string{ "EMPIRE_APPID": app.ID, "EMPIRE_APPNAME": "acme-inc", "EMPIRE_RELEASE": "v1", "RAILS_ENV": "production", }, Labels: map[string]string{ "empire.app.name": "acme-inc", "empire.app.id": app.ID, "empire.app.release": "v1", }, Processes: []*scheduler.Process{ { Type: "web", Image: img, Command: []string{"./bin/web"}, Exposure: &scheduler.Exposure{ Type: &scheduler.HTTPExposure{}, }, Instances: 1, MemoryLimit: 536870912, CPUShares: 256, Nproc: 256, Env: map[string]string{ "EMPIRE_PROCESS": "web", "EMPIRE_PROCESS_SCALE": "1", "SOURCE": "acme-inc.web.v1", }, Labels: map[string]string{ "empire.app.process": "web", }, }, }, }).Once().Return(nil) _, err = e.Deploy(context.Background(), empire.DeployOpts{ App: app, User: user, Output: empire.NewDeploymentStream(ioutil.Discard), Image: img, }) assert.NoError(t, err) // Remove the environment variable s.On("Submit", &scheduler.App{ ID: app.ID, Name: "acme-inc", Release: "v2", Env: map[string]string{ "EMPIRE_APPID": app.ID, "EMPIRE_APPNAME": "acme-inc", "EMPIRE_RELEASE": "v2", }, Labels: map[string]string{ "empire.app.name": "acme-inc", "empire.app.id": app.ID, "empire.app.release": "v2", }, Processes: []*scheduler.Process{ { Type: "web", Image: img, Command: []string{"./bin/web"}, Exposure: &scheduler.Exposure{ Type: &scheduler.HTTPExposure{}, }, Instances: 1, MemoryLimit: 536870912, CPUShares: 256, Nproc: 256, Env: map[string]string{ "EMPIRE_PROCESS": "web", "EMPIRE_PROCESS_SCALE": "1", "SOURCE": "acme-inc.web.v2", }, Labels: map[string]string{ "empire.app.process": "web", }, }, }, }).Once().Return(nil) _, err = e.Set(context.Background(), empire.SetOpts{ User: user, App: app, Vars: empire.Vars{ "RAILS_ENV": nil, }, }) assert.NoError(t, err) s.AssertExpectations(t) }
func TestEmpire_Run_WithAllowCommandProcfile(t *testing.T) { e := empiretest.NewEmpire(t) e.AllowedCommands = empire.AllowCommandProcfile user := &empire.User{Name: "ejholmes"} app, err := e.Create(context.Background(), empire.CreateOpts{ User: user, Name: "acme-inc", }) assert.NoError(t, err) img := image.Image{Repository: "remind101/acme-inc"} _, err = e.Deploy(context.Background(), empire.DeployOpts{ App: app, User: user, Output: empire.NewDeploymentStream(ioutil.Discard), Image: img, }) assert.NoError(t, err) s := new(mockScheduler) e.Scheduler = s err = e.Run(context.Background(), empire.RunOpts{ User: user, App: app, Command: empire.MustParseCommand("bundle exec rake db:migrate"), // Detached Process Output: nil, Input: nil, Env: map[string]string{ "TERM": "xterm", }, }) assert.IsType(t, &empire.CommandNotAllowedError{}, err) s.On("Run", &scheduler.App{ ID: app.ID, Name: "acme-inc", Release: "v1", Env: map[string]string{ "EMPIRE_APPID": app.ID, "EMPIRE_APPNAME": "acme-inc", "EMPIRE_RELEASE": "v1", }, Labels: map[string]string{ "empire.app.id": app.ID, "empire.app.name": "acme-inc", "empire.app.release": "v1", }, }, &scheduler.Process{ Type: "rake", Image: img, Command: []string{"bundle", "exec", "rake", "db:migrate"}, Instances: 1, MemoryLimit: 536870912, CPUShares: 256, Nproc: 256, Env: map[string]string{ "EMPIRE_PROCESS": "rake", "EMPIRE_PROCESS_SCALE": "1", "SOURCE": "acme-inc.rake.v1", "TERM": "xterm", }, Labels: map[string]string{ "empire.app.process": "rake", "empire.user": "******", }, }, nil, nil).Return(nil) err = e.Run(context.Background(), empire.RunOpts{ User: user, App: app, Command: empire.MustParseCommand("rake db:migrate"), // Detached Process Output: nil, Input: nil, Env: map[string]string{ "TERM": "xterm", }, }) assert.NoError(t, err) s.AssertExpectations(t) }
func TestEmpire_Deploy_Concurrent(t *testing.T) { e := empiretest.NewEmpire(t) s := new(mockScheduler) e.Scheduler = scheduler.NewFakeScheduler() e.ProcfileExtractor = empiretest.ExtractProcfile(procfile.ExtendedProcfile{ "web": procfile.Process{ Command: []string{"./bin/web"}, }, }) user := &empire.User{Name: "ejholmes"} // Create the first release for this app. r, err := e.Deploy(context.Background(), empire.DeployOpts{ User: user, Output: empire.NewDeploymentStream(ioutil.Discard), Image: image.Image{Repository: "remind101/acme-inc"}, }) assert.NoError(t, err) assert.Equal(t, 1, r.Version) // We'll use the procfile extractor to synchronize two concurrent // deployments. v2Started, v3Started := make(chan struct{}), make(chan struct{}) e.ProcfileExtractor = empire.ProcfileExtractorFunc(func(ctx context.Context, img image.Image, w io.Writer) ([]byte, error) { switch img.Tag { case "v2": close(v2Started) <-v3Started case "v3": close(v3Started) } return procfile.Marshal(procfile.ExtendedProcfile{ "web": procfile.Process{ Command: []string{"./bin/web"}, }, }) }) v2Done := make(chan struct{}) go func() { r, err := e.Deploy(context.Background(), empire.DeployOpts{ User: user, Output: empire.NewDeploymentStream(ioutil.Discard), Image: image.Image{Repository: "remind101/acme-inc", Tag: "v2"}, }) assert.NoError(t, err) assert.Equal(t, 2, r.Version) close(v2Done) }() <-v2Started r, err = e.Deploy(context.Background(), empire.DeployOpts{ User: user, Output: empire.NewDeploymentStream(ioutil.Discard), Image: image.Image{Repository: "remind101/acme-inc", Tag: "v3"}, }) assert.NoError(t, err) assert.Equal(t, 3, r.Version) <-v2Done s.AssertExpectations(t) }