internal/cli/deploy/deploy.go

Summary

Maintainability
A
2 hrs
Test Coverage
package deploy

import (
    "os"
    "time"

    "github.com/pkg/errors"
    "github.com/tj/go/git"
    "github.com/tj/go/term"
    "github.com/tj/kingpin"

    "github.com/apex/up"
    "github.com/apex/up/internal/cli/root"
    "github.com/apex/up/internal/setup"
    "github.com/apex/up/internal/stats"
    "github.com/apex/up/internal/util"
    "github.com/apex/up/internal/validate"
)

func init() {
    cmd := root.Command("deploy", "Deploy the project.").Default()
    stage := cmd.Arg("stage", "Target stage name.").Default("staging").String()
    noBuild := cmd.Flag("no-build", "Disable build related hooks.").Bool()

    cmd.Example(`up deploy`, "Deploy to the staging environment.")
    cmd.Example(`up deploy production`, "Deploy to the production environment.")
    cmd.Example(`up deploy --no-build`, "Skip build hooks, useful in CI when a separate build step is used.")

    cmd.Action(func(_ *kingpin.ParseContext) error {
        return deploy(*stage, !*noBuild)
    })
}

func deploy(stage string, build bool) error {
retry:
    c, p, err := root.Init()

    // missing up.json non-interactive
    if isMissingConfig(err) && !term.IsTerminal(os.Stdin.Fd()) {
        return errors.New("Cannot find ./up.json configuration file.")
    }

    // missing up.json interactive
    if isMissingConfig(err) {
        err := setup.Create()

        if err == setup.ErrNoCredentials {
            return errors.New("Cannot find credentials, visit https://apex.sh/docs/up/credentials/ for help.")
        }

        if err != nil {
            return errors.Wrap(err, "setup")
        }

        util.Log("Deploying the project and creating resources.")
        goto retry
    }

    // unrelated error
    if err != nil {
        return errors.Wrap(err, "initializing")
    }

    // validate stage name
    if err := validate.List(stage, c.Stages.RemoteNames()); err != nil {
        return err
    }

    // stage overrides
    if err := c.Override(stage); err != nil {
        return errors.Wrap(err, "overriding")
    }

    // git information
    commit, err := getCommit()
    if err != nil {
        return errors.Wrap(err, "fetching git commit")
    }

    defer util.Pad()()
    start := time.Now()

    if err := p.Init(stage); err != nil {
        return errors.Wrap(err, "initializing")
    }

    if err := p.Deploy(up.Deploy{
        Stage:  stage,
        Commit: util.StripLerna(commit.Describe()),
        Author: commit.Author.Name,
        Build:  build,
    }); err != nil {
        return err
    }

    stats.Track("Deploy", map[string]interface{}{
        "duration":             util.MillisecondsSince(start),
        "type":                 c.Type,
        "regions":              c.Regions,
        "stage":                stage,
        "proxy_timeout":        c.Proxy.Timeout,
        "header_rules_count":   len(c.Headers),
        "redirect_rules_count": len(c.Redirects),
        "inject_rules_count":   len(c.Inject),
        "environment_count":    len(c.Environment),
        "dns_zone_count":       len(c.DNS.Zones),
        "stage_count":          len(c.Stages.List()),
        "stage_domain_count":   len(c.Stages.Domains()),
        "lambda_memory":        c.Lambda.Memory,
        "has_cors":             c.CORS != nil,
        "has_logs":             !c.Logs.Disable,
        "has_profile":          c.Profile != "",
        "has_error_pages":      c.ErrorPages.Enable,
        "app_name_hash":        util.Md5(c.Name),
        "is_git":               commit.Author.Name != "",
    })

    stats.Flush()
    return nil
}

// isMissingConfig returns true if the error represents a missing up.json.
func isMissingConfig(err error) bool {
    err = errors.Cause(err)
    e, ok := err.(*os.PathError)
    return ok && e.Path == "up.json"
}

// getCommit returns the git information when available.
func getCommit() (git.Commit, error) {
    c, err := git.GetCommit(".", "HEAD")
    if err != nil && !isIgnorable(err) {
        return git.Commit{}, err
    }

    if c == nil {
        return git.Commit{}, nil
    }

    return *c, nil
}

// isIgnorable returns true if the GIT error is ignorable.
func isIgnorable(err error) bool {
    switch err {
    case git.ErrLookup, git.ErrNoRepo, git.ErrDirty:
        return true
    default:
        return false
    }
}