Example #1
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. This means that secrets will not be injected. In addition the Deploy and Notify steps will not be executed. To resolve this please regenerate the secrets file.", 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
}
Example #2
0
func main() {

	// parses command line flags
	flag.BoolVar(&cache, "cache", false, "")
	flag.BoolVar(&clone, "clone", false, "")
	flag.BoolVar(&build, "build", false, "")
	flag.BoolVar(&deploy, "deploy", false, "")
	flag.BoolVar(&notify, "notify", false, "")
	flag.BoolVar(&debug, "debug", false, "")
	flag.BoolVar(&force, "pull", false, "")
	flag.StringVar(&mount, "mount", "", "")
	flag.Parse()

	// unmarshal the json payload via stdin or
	// via the command line args (whichever was used)
	plugin.MustUnmarshal(&payload)

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

	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 {
			log.Debugln("Unable to decrypt encrypted secrets", err)
		} else {
			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 {
				fmt.Println("Error injecting Yaml secrets")
				os.Exit(1)
			}
		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.
			deploy = false
			notify = false
			fmt.Println("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(force),
		parser.SanitizeFunc(payload.Repo.IsTrusted), //&& !plugin.PullRequest(payload.Build)
		parser.CacheFunc(payload.Repo.FullName),
		parser.DebugFunc(debugFlag),
		parser.Escalate,
		parser.HttpProxy,
		parser.DefaultNotifyFilter,
	}
	if len(mount) != 0 {
		log.Debugf("Mounting %s as workspace %s",
			mount,
			payload.Workspace.Path,
		)
		rules = append(rules, parser.MountFunc(
			mount,
			payload.Workspace.Path,
		))
	}
	tree, err := parser.Parse(payload.Yaml, rules)
	if err != nil {
		log.Debugln(err) // print error messages in debug mode only
		log.Fatalln("Error parsing the .drone.yml")
		os.Exit(1)
	}
	r := runner.Load(tree)

	client, err := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil)
	if err != nil {
		log.Debugln(err)
		log.Fatalln("Error creating the docker client.")
		os.Exit(1)
	}

	// // creates a wrapper Docker client that uses an ambassador
	// // container to create a pod-like environment.
	controller, err := docker.NewClient(client)
	if err != nil {
		log.Debugln(err)
		log.Fatalln("Error creating the docker ambassador.")
		os.Exit(1)
	}
	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:    os.Stdout,
		Stderr:    os.Stdout,
		Repo:      payload.Repo,
		Build:     payload.Build,
		BuildLast: payload.BuildLast,
		Job:       payload.Job,
		System:    payload.System,
		Workspace: payload.Workspace,
	}
	if cache {
		log.Debugln("Running Cache step")
		err = r.RunNode(state, parser.NodeCache)
		if err != nil {
			log.Debugln(err)
		}
	}
	if clone {
		log.Debugln("Running Clone step")
		err = r.RunNode(state, parser.NodeClone)
		if err != nil {
			log.Debugln(err)
		}
	}
	if 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 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 cache {
		log.Debugln("Running post-Build Cache steps")
		err = r.RunNode(state, parser.NodeCache)
		if err != nil {
			log.Debugln(err)
		}
	}
	if notify {
		log.Debugln("Running Notify steps")
		err = r.RunNode(state, parser.NodeNotify)
		if err != nil {
			log.Debugln(err)
		}
	}

	if state.Failed() {
		controller.Destroy()
		os.Exit(state.ExitCode())
	}
}