up.go
package up
import (
"io"
"os"
"os/exec"
"time"
"github.com/apex/log"
"github.com/pkg/errors"
"github.com/apex/up/config"
"github.com/apex/up/internal/util"
"github.com/apex/up/platform/event"
)
// Config for a project.
type Config = config.Config
// ReadConfig reads the configuration from `path`.
var ReadConfig = config.ReadConfig
// ParseConfigString returns config from JSON string.
var ParseConfigString = config.ParseConfigString
// MustParseConfigString returns config from JSON string.
var MustParseConfigString = config.MustParseConfigString
// Project manager.
type Project struct {
Platform
config *Config
events event.Events
}
// New project.
func New(c *Config, events event.Events) *Project {
return &Project{
config: c,
events: events,
}
}
// WithPlatform to `platform`.
func (p *Project) WithPlatform(platform Platform) *Project {
p.Platform = platform
return p
}
// RunHook runs a hook by name.
func (p *Project) RunHook(name string) error {
hook := p.config.Hooks.Get(name)
if hook.IsEmpty() {
log.Debugf("hook %s is not defined", name)
return nil
}
defer p.events.Time("hook", event.Fields{
"name": name,
"hook": hook,
})()
for _, command := range hook {
log.Debugf("hook %q command %q", name, command)
cmd := exec.Command("sh", "-c", command)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, util.Env(p.config.Environment)...)
cmd.Env = append(cmd.Env, "PATH=node_modules/.bin:"+os.Getenv("PATH"))
b, err := cmd.CombinedOutput()
if err != nil {
return errors.Errorf("%q: %s", command, b)
}
}
return nil
}
// RunHooks runs hooks by name.
func (p *Project) RunHooks(names ...string) error {
for _, n := range names {
if err := p.RunHook(n); err != nil {
return errors.Wrapf(err, "%q hook", n)
}
}
return nil
}
// Build the project.
func (p *Project) Build(hooks bool) error {
defer p.events.Time("platform.build", nil)()
if hooks {
if err := p.RunHooks("prebuild", "build"); err != nil {
return err
}
}
if err := p.Platform.Build(); err != nil {
return errors.Wrap(err, "building")
}
if hooks {
return p.RunHooks("postbuild")
}
return nil
}
// Deploy the project.
func (p *Project) Deploy(d Deploy) error {
defer p.events.Time("deploy", event.Fields{
"commit": d.Commit,
"stage": d.Stage,
})()
if err := p.Build(d.Build); err != nil {
return errors.Wrap(err, "building")
}
if err := p.deploy(d); err != nil {
return errors.Wrap(err, "deploying")
}
if d.Build {
if err := p.RunHook("clean"); err != nil {
return errors.Wrap(err, "clean hook")
}
}
return nil
}
// deploy stage.
func (p *Project) deploy(d Deploy) error {
if err := p.RunHooks("predeploy", "deploy"); err != nil {
return err
}
if err := p.Platform.Deploy(d); err != nil {
return err
}
return p.RunHooks("postdeploy")
}
// Zip returns the zip if supported by the platform.
func (p *Project) Zip() (io.Reader, error) {
z, ok := p.Platform.(Zipper)
if !ok {
return nil, errors.Errorf("platform does not support zips")
}
return z.Zip(), nil
}
// Init initializes the runtime such as remote environment variables.
func (p *Project) Init(stage string) error {
r, ok := p.Platform.(Runtime)
if !ok {
return nil
}
return r.Init(stage)
}
// CreateStack implementation.
func (p *Project) CreateStack(region, version string) error {
defer p.events.Time("stack.create", event.Fields{
"region": region,
"version": version,
})()
return p.Platform.CreateStack(region, version)
}
// DeleteStack implementation.
func (p *Project) DeleteStack(region string, wait bool) error {
defer p.events.Time("stack.delete", event.Fields{
"region": region,
})()
return p.Platform.DeleteStack(region, wait)
}
// ShowStack implementation.
func (p *Project) ShowStack(region string) error {
defer p.events.Time("stack.show", event.Fields{
"region": region,
})()
return p.Platform.ShowStack(region)
}
// ShowMetrics implementation.
func (p *Project) ShowMetrics(region, stage string, start time.Time) error {
defer p.events.Time("metrics", event.Fields{
"region": region,
"stage": stage,
"start": start,
})()
return p.Platform.ShowMetrics(region, stage, start)
}
// PlanStack implementation.
func (p *Project) PlanStack(region string) error {
defer p.events.Time("stack.plan", event.Fields{
"region": region,
})()
return p.Platform.PlanStack(region)
}
// ApplyStack implementation.
func (p *Project) ApplyStack(region string) error {
defer p.events.Time("stack.apply", event.Fields{
"region": region,
})()
return p.Platform.ApplyStack(region)
}
// Prune implementation.
func (p *Project) Prune(region, stage string, versions int) error {
pruner, ok := p.Platform.(Pruner)
if !ok {
return errors.Errorf("platform does not support pruning")
}
return pruner.Prune(region, stage, versions)
}