Esempio n. 1
0
func main() {
	var opt exec.Options

	// parses command line flags
	flag.BoolVar(&opt.Cache, "cache", false, "")
	flag.BoolVar(&opt.Clone, "clone", false, "")
	flag.BoolVar(&opt.Build, "build", false, "")
	flag.BoolVar(&opt.Deploy, "deploy", false, "")
	flag.BoolVar(&opt.Notify, "notify", false, "")
	flag.BoolVar(&opt.Debug, "debug", false, "")
	flag.BoolVar(&opt.Force, "pull", false, "")
	flag.StringVar(&opt.Mount, "mount", "", "")
	flag.Parse()

	// unmarshal the json payload via stdin or
	// via the command line args (whichever was used)
	var payload exec.Payload
	if err := plugin.MustUnmarshal(&payload); err != nil {
		log.Fatalln(err)
	}

	// configure the default log format and
	// log levels
	debugFlag := yaml.ParseDebugString(payload.Yaml)
	if debugFlag {
		log.SetLevel(log.DebugLevel)
	}
	log.SetFormatter(new(formatter))

	err := exec.Exec(payload, opt, os.Stdout, os.Stdout)
	if err != nil {
		log.Println(err)
		switch err := err.(type) {
		case *exec.Error:
			os.Exit(err.ExitCode)
		}
		os.Exit(1)
	}
}
Esempio n. 2
0
// Exec executes a build with the given payload and options. If the
// build fails, an *Error is returned.
func Exec(payload Payload, opt Options, outw, errw io.Writer) error {
	var sec *secure.Secure
	if payload.Keys != nil && len(payload.YamlEnc) != 0 {
		var err error
		sec, err = secure.Parse(payload.YamlEnc, payload.Keys.Private)
		if err != nil {
			return fmt.Errorf("decrypting encrypted secrets: %s", err)
		}
		log.Debugln("Successfully decrypted secrets")
	}

	// TODO This block of code (and the above block) need to be cleaned
	//      up and written in a manner that facilitates better unit testing.
	if sec != nil {
		verified := shasum.Check(payload.Yaml, sec.Checksum)

		// the checksum should be invalidated if the repository is
		// public, and the build is a pull request, and the checksum
		// value was not provided.
		if payload.Build.Event == plugin.EventPull && !payload.Repo.IsPrivate && len(sec.Checksum) == 0 {
			verified = false
		}

		switch {
		case verified && payload.Build.Event == plugin.EventPull:
			log.Debugln("Injected secrets into Yaml safely")
			var err error
			payload.Yaml, err = inject.InjectSafe(payload.Yaml, sec.Environment.Map())
			if err != nil {
				return fmt.Errorf("injecting yaml secrets: %s", err)
			}
		case verified:
			log.Debugln("Injected secrets into Yaml")
			payload.Yaml = inject.Inject(payload.Yaml, sec.Environment.Map())
		case !verified:
			// if we can't validate the Yaml file we don't inject
			// secrets, and therefore shouldn't bother running the
			// deploy and notify tests.
			opt.Deploy = false
			opt.Notify = false
			log.Errorln("Unable to validate Yaml checksum.", sec.Checksum)
		}
	}

	// injects the matrix configuration parameters
	// into the yaml prior to parsing.
	injectParams := map[string]string{
		"COMMIT_SHORT": payload.Build.Commit, // DEPRECATED
		"COMMIT":       payload.Build.Commit,
		"BRANCH":       payload.Build.Branch,
		"BUILD_NUMBER": strconv.Itoa(payload.Build.Number),
	}
	if payload.Build.Event == plugin.EventTag {
		injectParams["TAG"] = strings.TrimPrefix(payload.Build.Ref, "refs/tags/")
	}
	payload.Yaml = inject.Inject(payload.Yaml, payload.Job.Environment)
	payload.Yaml = inject.Inject(payload.Yaml, injectParams)

	// safely inject global variables
	var globals = map[string]string{}
	for _, s := range payload.System.Globals {
		parts := strings.SplitN(s, "=", 2)
		if len(parts) != 2 {
			continue
		}
		globals[parts[0]] = parts[1]
	}
	if payload.Repo.IsPrivate {
		payload.Yaml = inject.Inject(payload.Yaml, globals)
	} else {
		payload.Yaml, _ = inject.InjectSafe(payload.Yaml, globals)
	}

	// extracts the clone path from the yaml. If
	// the clone path doesn't exist it uses a path
	// derrived from the repository uri.
	payload.Workspace = &plugin.Workspace{Keys: payload.Keys, Netrc: payload.Netrc}
	payload.Workspace.Path = path.Parse(payload.Yaml, payload.Repo.Link)
	payload.Workspace.Root = "/drone/src"
	log.Debugf("Using workspace %s", payload.Workspace.Path)

	rules := []parser.RuleFunc{
		parser.ImageName,
		parser.ImageMatchFunc(payload.System.Plugins),
		parser.ImagePullFunc(opt.Force),
		parser.SanitizeFunc(payload.Repo.IsTrusted), //&& !plugin.PullRequest(payload.Build)
		parser.CacheFunc(payload.Repo.FullName),
		parser.DebugFunc(yaml.ParseDebugString(payload.Yaml)),
		parser.Escalate,
		parser.HttpProxy,
		parser.DefaultNotifyFilter,
	}
	if len(opt.Mount) != 0 {
		log.Debugf("Mounting %s as workspace %s",
			opt.Mount,
			payload.Workspace.Path,
		)
		rules = append(rules, parser.MountFunc(
			opt.Mount,
			payload.Workspace.Path,
		))
	}
	tree, err := parser.Parse(payload.Yaml, rules)
	if err != nil {
		// TODO(sqs): There was a comment here saying "print error
		// messages in debug mode only". Is this because of security
		// (e.g., the decrypted YAML secrets could leak in the error
		// message)? If so, don't return the err here; instead, return
		// a simple error message such as "error parsing yaml".
		return err
	}
	r := runner.Load(tree)

	client, err := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil)
	if err != nil {
		return err
	}

	// // creates a wrapper Docker client that uses an ambassador
	// // container to create a pod-like environment.
	controller, err := docker.NewClient(client)
	if err != nil {
		return fmt.Errorf("creating docker ambassador container: %s", err)
	}
	defer controller.Destroy()

	// watch for sigkill (timeout or cancel build)
	killc := make(chan os.Signal, 1)
	signal.Notify(killc, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		<-killc
		log.Println("Cancel request received, killing process")
		controller.Destroy() // possibe race here. implement lock on the other end
		os.Exit(130)         // cancel is treated like ctrl+c
	}()

	go func() {
		var timeout = payload.Repo.Timeout
		if timeout == 0 {
			timeout = 60
		}
		<-time.After(time.Duration(timeout) * time.Minute)
		log.Println("Timeout request received, killing process")
		controller.Destroy() // possibe race here. implement lock on the other end
		os.Exit(128)         // cancel is treated like ctrl+c
	}()

	state := &runner.State{
		Client:    controller,
		Stdout:    outw,
		Stderr:    errw,
		Repo:      payload.Repo,
		Build:     payload.Build,
		BuildLast: payload.BuildLast,
		Job:       payload.Job,
		System:    payload.System,
		Workspace: payload.Workspace,
	}
	if opt.Cache {
		log.Debugln("Running Cache step")
		err = r.RunNode(state, parser.NodeCache)
		if err != nil {
			log.Debugln(err)
		}
	}
	if opt.Clone {
		log.Debugln("Running Clone step")
		err = r.RunNode(state, parser.NodeClone)
		if err != nil {
			log.Debugln(err)
		}
	}
	if opt.Build && !state.Failed() {
		log.Debugln("Running Build and Compose steps")
		err = r.RunNode(state, parser.NodeCompose|parser.NodeBuild)
		if err != nil {
			log.Debugln(err)
		}
	}
	if opt.Deploy && !state.Failed() {
		log.Debugln("Running Publish and Deploy steps")
		err = r.RunNode(state, parser.NodePublish|parser.NodeDeploy)
		if err != nil {
			log.Debugln(err)
		}
	}

	// if the build is not failed, at this point
	// we can mark as successful
	if !state.Failed() {
		state.Job.Status = plugin.StateSuccess
		state.Build.Status = plugin.StateSuccess
	}

	if opt.Cache {
		log.Debugln("Running post-Build Cache steps")
		err = r.RunNode(state, parser.NodeCache)
		if err != nil {
			log.Debugln(err)
		}
	}
	if opt.Notify {
		log.Debugln("Running Notify steps")
		err = r.RunNode(state, parser.NodeNotify)
		if err != nil {
			log.Debugln(err)
		}
	}

	if state.Failed() {
		controller.Destroy()
		return &Error{ExitCode: state.ExitCode()}
	}

	return nil
}