<commit_before>// Copyright 2017 Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	u "istio.io/test-infra/toolbox/util"
)

var (
	owner                              = flag.String("owner", "istio", "Github owner or org")
	tokenFile                          = flag.String("token_file", "", "File containing Github API Access Token")
	op                                 = flag.String("op", "", "Operation to be performed")
	repo                               = flag.String("repo", "", "Repository to which op is applied")
	baseBranch                         = flag.String("base_branch", "", "Branch to which op is applied")
	refSHA                             = flag.String("ref_sha", "", "Reference commit SHA used to update base branch")
	nextRelease                        = flag.String("next_release", "", "Tag of the next release")
	hub                                = flag.String("hub", "", "Hub of the docker images")
	tag                                = flag.String("tag", "", "Tag of the release candidate")
	releaseOrg                         = flag.String("rel_org", "istio-releases", "GitHub Release Org")
	gcsPath                            = flag.String("gcs_path", "", "The path to the GCS bucket")
	extraBranchesUpdateDownloadVersion = flag.String("update_rel_branches", "",
		"Extra branches where you want to update downloadIstioCandidate.sh, separated by comma")
	githubClnt *u.GithubClient
	ghClntRel  *u.GithubClient
)

const (
	// 0.2 release tooling
	istioVersionFile     = "istio.VERSION"
	istioDepsFile        = "istio.deps"
	releaseTagFile       = "istio.RELEASE"
	downloadScript       = "release/downloadIstioCandidate.sh"
	istioRepo            = "istio"
	masterBranch         = "master"
	export               = "export "
	dockerHub            = "docker.io/istio"
	releaseBaseDir       = "/tmp/release"
	releasePRTtilePrefix = "[Auto Release] "
	releaseBucketFmtStr  = "https://storage.googleapis.com/istio-release/releases/%s/%s"
	istioctlSuffix       = "istioctl"
	debianSuffix         = "deb"
	// release qualification trigger
	relQualificationPRTtilePrefix = "Release Qualification"
	greenBuildVersionFile         = "greenBuild.VERSION"
	dailyRepo                     = "daily-release"
)

func fastForward(repo, baseBranch, refSHA *string) error {
	u.AssertNotEmpty("repo", repo)
	u.AssertNotEmpty("base_branch", baseBranch)
	u.AssertNotEmpty("ref_sha", refSHA)
	isAncestor, err := githubClnt.SHAIsAncestorOfBranch(*repo, masterBranch, *refSHA)
	if err != nil {
		return err
	}
	if !isAncestor {
		log.Printf("SHA %s is not an ancestor of branch %s, resorts to no-op\n", *refSHA, masterBranch)
		return nil
	}
	return githubClnt.FastForward(*repo, *baseBranch, *refSHA)
}

func processIstioVersion(content *string) map[string]string {
	kv := make(map[string]string)
	lines := strings.Split(*content, "\n")
	for _, line := range lines {
		if idx := strings.Index(line, "="); idx != -1 {
			key := line[len(export):idx]
			value := strings.Trim(line[idx+1:], "\"")
			kv[key] = value
		}
	}
	return kv
}

func getReleaseTag() (string, error) {
	u.AssertNotEmpty("base_branch", baseBranch)
	releaseTag, err := githubClnt.GetFileContent(istioRepo, *baseBranch, releaseTagFile)
	if err != nil {
		return "", err
	}
	return strings.Trim(releaseTag, "\n"), nil
}

// TagIstioDepsForRelease creates release tag on each dependent repo of istio
func TagIstioDepsForRelease() error {
	u.AssertNotEmpty("base_branch", baseBranch)
	log.Printf("Fetching and processing istio.VERSION\n")
	istioVersion, err := githubClnt.GetFileContent(istioRepo, *baseBranch, istioVersionFile)
	if err != nil {
		return err
	}
	kv := processIstioVersion(&istioVersion)
	pickledDeps, err := githubClnt.GetFileContent(istioRepo, *baseBranch, istioDepsFile)
	if err != nil {
		return err
	}
	deps, err := u.DeserializeDepsFromString(pickledDeps)
	if err != nil {
		return err
	}
	log.Printf("Fetching release tag\n")
	releaseTag, err := getReleaseTag()
	if err != nil {
		return err
	}
	releaseMsg := "Istio Release " + releaseTag
	for _, dep := range deps {
		// use sha directly read from istio.VERSION in case updateVersion.sh was run
		// manually without also updating istio.deps
		log.Printf("Creating annotated tag [%s] on %s\n", releaseTag, dep.RepoName)
		ref, exists := kv[dep.Name]
		if !exists {
			return fmt.Errorf("ill-defined %s: unable to find %s", istioVersionFile, dep.Name)
		}
		// make sure ref is a SHA, special case where previous release is used in this release
		if u.ReleaseTagRegex.MatchString(ref) {
			ref, err = githubClnt.GetTagCommitSHA(dep.RepoName, ref)
			if err != nil {
				return err
			}
		}
		if err := githubClnt.CreateAnnotatedTag(
			dep.RepoName, releaseTag, ref, releaseMsg); err != nil {
			if strings.Contains(err.Error(), "Reference already exists") {
				log.Printf("Tag [%s] already exists on %s\n", releaseTag, dep.RepoName)
				prevTagSHA, err := githubClnt.GetTagCommitSHA(dep.RepoName, releaseTag)
				if err != nil {
					return err
				}
				if prevTagSHA == ref {
					log.Printf("Intended to tag [%s] at the same SHA, resort to no-op and continue\n", ref)
					continue
				} else {
					return fmt.Errorf("trying to tag [%s] at different SHA", ref)
				}
			}
		}
	}
	return nil
}

// UpdateIstioVersionAfterReleaseTagsMadeOnDeps runs updateVersion.sh to update
// istio.VERSION and then create a PR on istio
func UpdateIstioVersionAfterReleaseTagsMadeOnDeps() error {
	u.AssertNotEmpty("base_branch", baseBranch)
	releaseTag, err := getReleaseTag()
	if err != nil {
		return err
	}
	edit := func() error {
		hubCommaTag := fmt.Sprintf("%s,%s", dockerHub, releaseTag)
		istioctlURL := fmt.Sprintf(releaseBucketFmtStr, releaseTag, istioctlSuffix)
		debianURL := fmt.Sprintf(releaseBucketFmtStr, releaseTag, debianSuffix)
		cmd := fmt.Sprintf("./install/updateVersion.sh")
		// Auth
		cmd += fmt.Sprintf(" -c %s -A %s", hubCommaTag, debianURL)
		// Mixer
		cmd += fmt.Sprintf(" -x %s", hubCommaTag)
		// Pilot
		cmd += fmt.Sprintf(" -p %s -i %s -P %s", hubCommaTag, istioctlURL, debianURL)
		// Proxy
		cmd += fmt.Sprintf(" -r %s -E %s", releaseTag, debianURL)
		_, err := u.Shell(cmd)
		return err
	}
	releaseBranch := "Istio_Release_" + releaseTag
	body := "Update istio.Version"
	prTitle := releasePRTtilePrefix + body
	_, err = githubClnt.CreatePRUpdateRepo(releaseBranch, *baseBranch, istioRepo, prTitle, body, edit)
	return err
}

// CreateIstioReleaseUploadArtifacts creates a release on istio from the refSHA provided and uploads dependent artifacts
func CreateIstioReleaseUploadArtifacts() error {
	u.AssertNotEmpty("ref_sha", refSHA)
	u.AssertNotEmpty("base_branch", baseBranch)
	u.AssertNotEmpty("next_release", nextRelease)
	releaseTag, err := getReleaseTag()
	if releaseTag == *nextRelease {
		return fmt.Errorf("next_release should be greater than the current release")
	}
	if err != nil {
		return err
	}
	releaseBranch := "finalizeRelease-" + releaseTag
	prBody := fmt.Sprintf("Finalize release %s on istio", releaseTag)
	updateVersion := func() error {
		log.Printf("Updating downloadIstioCandidate.sh with latest release")
		return u.UpdateKeyValueInFile(
			downloadScript, "ISTIO_VERSION", fmt.Sprintf("${ISTIO_VERSION:-%s}", releaseTag))
	}
	edit := func() error {
		if _, err := u.Shell(
			"./release/create_release_archives.sh -d " + releaseBaseDir); err != nil {
			return err
		}
		archiveDir := releaseBaseDir + "/archives"
		if err := githubClnt.CreateReleaseUploadArchives(
			istioRepo, releaseTag, *refSHA, archiveDir); err != nil {
			return err
		}
		if err := u.WriteTextFile(releaseTagFile, *nextRelease); err != nil {
			return err
		}
		return updateVersion()
	}
	prTitle := releasePRTtilePrefix + prBody
	_, err = githubClnt.CreatePRUpdateRepo(releaseBranch, *baseBranch, istioRepo, prTitle, prBody, edit)
	if err != nil {
		return err
	}
	if *extraBranchesUpdateDownloadVersion != "" {
		extraBranches := strings.Split(*extraBranchesUpdateDownloadVersion, ",")
		for _, branch := range extraBranches {
			localBranch := fmt.Sprintf("%s-local", branch)
			if _, err := githubClnt.CreatePRUpdateRepo(localBranch, branch, istioRepo, prTitle, prBody, updateVersion); err != nil {
				// Only log out errors if failing update extra branches
				log.Printf("Warning! Failed to update downloadIstioCandidate.sh in branch %s", branch)
			}
		}
	}
	return nil
}

// DailyReleaseQualification triggers test jobs buy creating a PR that generates
// a GitHub notification. It blocks until PR status is known and returns nonzero
// value if failure. Links to test logs will also be logged to console.
func DailyReleaseQualification() error {
	u.AssertNotEmpty("hub", hub) // TODO (chx) default value of hub
	u.AssertNotEmpty("tag", tag)
	u.AssertNotEmpty("gcs_path", gcsPath)
	log.Printf("Creating PR to trigger release qualifications\n")
	prTitle := "[DO NOT MANUAL MERGE] " + relQualificationPRTtilePrefix + *refSHA
	prBody := fmt.Sprintf("Trigger release qualification jobs")
	timestamp := fmt.Sprintf("%v", time.Now().UnixNano())
	newBranch := "relQual_" + timestamp
	edit := func() error {
		if err := u.UpdateKeyValueInFile(greenBuildVersionFile, "HUB", *hub); err != nil {
			return err
		}
		if err := u.UpdateKeyValueInFile(greenBuildVersionFile, "TAG", *tag); err != nil {
			return err
		}
		if err := u.UpdateKeyValueInFile(greenBuildVersionFile, "TIME", timestamp); err != nil {
			return err
		}
		if err := u.UpdateKeyValueInFile(greenBuildVersionFile, "ISTIOCTL_URL",
			fmt.Sprintf("https://storage.googleapis.com/%s", *gcsPath)); err != nil {
			return err
		}
		return nil
	}
	pr, err := ghClntRel.CreatePRUpdateRepo(newBranch, masterBranch, dailyRepo, prTitle, prBody, edit)
	if err != nil {
		return err
	}
	defer func() {
		log.Printf("Close the PR and delete its branch\n")
		if e := ghClntRel.ClosePRDeleteBranch(dailyRepo, pr); e != nil {
			log.Printf("Error in ClosePRDeleteBranch: %v\n", e)
		}
	}()

	verbose := true
	ci := u.NewCIState()
	retryDelay := 1 * time.Minute
	totalRetries := 60
	log.Printf("Waiting for all jobs starting. Results Polling starts in %v.\n", retryDelay)
	time.Sleep(retryDelay)
	err = u.Poll(retryDelay, totalRetries, func() (bool, error) {
		status, errPoll := ghClntRel.GetPRTestResults(dailyRepo, pr, verbose)
		verbose = false
		if errPoll != nil {
			return false, errPoll
		}
		exitPolling := false
		switch status {
		case ci.Success:
			log.Printf("Release qualification passed\n")
			exitPolling = true
		case ci.Failure:
			// All failures have been logged by GetPRTestResults()
			exitPolling = true
			errPoll = fmt.Errorf("release qualification failed")
		case ci.Pending:
			log.Printf("Results still pending. Will check again in %v.\n", retryDelay)
		}
		return exitPolling, errPoll
	})
	if err != nil { // qualification failed
		return err
	}
	log.Printf("Auto merging this PR to update daily release\n")
	return ghClntRel.MergePR(dailyRepo, pr)
}

func init() {
	flag.Parse()
	u.AssertNotEmpty("token_file", tokenFile)
	token, err := u.GetAPITokenFromFile(*tokenFile)
	if err != nil {
		log.Fatalf("Error accessing user supplied token_file: %v\n", err)
	}
	githubClnt = u.NewGithubClient(*owner, token)
	// a new github client is created for istio-releases org
	ghClntRel = u.NewGithubClient(*releaseOrg, token)
}

func main() {
	switch *op {
	case "fastForward":
		if err := fastForward(repo, baseBranch, refSHA); err != nil {
			log.Printf("Error during fastForward: %v\n", err)
		}
	case "tagIstioDepsForRelease":
		if err := TagIstioDepsForRelease(); err != nil {
			log.Printf("Error during TagIstioDepsForRelease: %v\n", err)
		}
	case "updateIstioVersion":
		if err := UpdateIstioVersionAfterReleaseTagsMadeOnDeps(); err != nil {
			log.Printf("Error during UpdateIstioVersionAfterReleaseTagsMadeOnDeps: %v\n", err)
		}
	case "uploadArtifacts":
		if err := CreateIstioReleaseUploadArtifacts(); err != nil {
			log.Printf("Error during CreateIstioReleaseUploadArtifacts: %v\n", err)
		}
	case "dailyRelQual":
		if err := DailyReleaseQualification(); err != nil {
			log.Printf("Error during DailyReleaseQualification: %v\n", err)
			os.Exit(1)
		}
	default:
		log.Printf("Unsupported operation: %s\n", *op)
	}
}
<commit_msg>Updated location of istioctl. (#633)<commit_after>// Copyright 2017 Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	u "istio.io/test-infra/toolbox/util"
)

var (
	owner                              = flag.String("owner", "istio", "Github owner or org")
	tokenFile                          = flag.String("token_file", "", "File containing Github API Access Token")
	op                                 = flag.String("op", "", "Operation to be performed")
	repo                               = flag.String("repo", "", "Repository to which op is applied")
	baseBranch                         = flag.String("base_branch", "", "Branch to which op is applied")
	refSHA                             = flag.String("ref_sha", "", "Reference commit SHA used to update base branch")
	nextRelease                        = flag.String("next_release", "", "Tag of the next release")
	hub                                = flag.String("hub", "", "Hub of the docker images")
	tag                                = flag.String("tag", "", "Tag of the release candidate")
	releaseOrg                         = flag.String("rel_org", "istio-releases", "GitHub Release Org")
	gcsPath                            = flag.String("gcs_path", "", "The path to the GCS bucket")
	extraBranchesUpdateDownloadVersion = flag.String("update_rel_branches", "",
		"Extra branches where you want to update downloadIstioCandidate.sh, separated by comma")
	githubClnt *u.GithubClient
	ghClntRel  *u.GithubClient
)

const (
	// 0.2 release tooling
	istioVersionFile     = "istio.VERSION"
	istioDepsFile        = "istio.deps"
	releaseTagFile       = "istio.RELEASE"
	downloadScript       = "release/downloadIstioCandidate.sh"
	istioRepo            = "istio"
	masterBranch         = "master"
	export               = "export "
	dockerHub            = "docker.io/istio"
	releaseBaseDir       = "/tmp/release"
	releasePRTtilePrefix = "[Auto Release] "
	releaseBucketFmtStr  = "https://storage.googleapis.com/istio-release/releases/%s/%s"
	istioctlSuffix       = "istioctl"
	debianSuffix         = "deb"
	// release qualification trigger
	relQualificationPRTtilePrefix = "Release Qualification"
	greenBuildVersionFile         = "greenBuild.VERSION"
	dailyRepo                     = "daily-release"
)

func fastForward(repo, baseBranch, refSHA *string) error {
	u.AssertNotEmpty("repo", repo)
	u.AssertNotEmpty("base_branch", baseBranch)
	u.AssertNotEmpty("ref_sha", refSHA)
	isAncestor, err := githubClnt.SHAIsAncestorOfBranch(*repo, masterBranch, *refSHA)
	if err != nil {
		return err
	}
	if !isAncestor {
		log.Printf("SHA %s is not an ancestor of branch %s, resorts to no-op\n", *refSHA, masterBranch)
		return nil
	}
	return githubClnt.FastForward(*repo, *baseBranch, *refSHA)
}

func processIstioVersion(content *string) map[string]string {
	kv := make(map[string]string)
	lines := strings.Split(*content, "\n")
	for _, line := range lines {
		if idx := strings.Index(line, "="); idx != -1 {
			key := line[len(export):idx]
			value := strings.Trim(line[idx+1:], "\"")
			kv[key] = value
		}
	}
	return kv
}

func getReleaseTag() (string, error) {
	u.AssertNotEmpty("base_branch", baseBranch)
	releaseTag, err := githubClnt.GetFileContent(istioRepo, *baseBranch, releaseTagFile)
	if err != nil {
		return "", err
	}
	return strings.Trim(releaseTag, "\n"), nil
}

// TagIstioDepsForRelease creates release tag on each dependent repo of istio
func TagIstioDepsForRelease() error {
	u.AssertNotEmpty("base_branch", baseBranch)
	log.Printf("Fetching and processing istio.VERSION\n")
	istioVersion, err := githubClnt.GetFileContent(istioRepo, *baseBranch, istioVersionFile)
	if err != nil {
		return err
	}
	kv := processIstioVersion(&istioVersion)
	pickledDeps, err := githubClnt.GetFileContent(istioRepo, *baseBranch, istioDepsFile)
	if err != nil {
		return err
	}
	deps, err := u.DeserializeDepsFromString(pickledDeps)
	if err != nil {
		return err
	}
	log.Printf("Fetching release tag\n")
	releaseTag, err := getReleaseTag()
	if err != nil {
		return err
	}
	releaseMsg := "Istio Release " + releaseTag
	for _, dep := range deps {
		// use sha directly read from istio.VERSION in case updateVersion.sh was run
		// manually without also updating istio.deps
		log.Printf("Creating annotated tag [%s] on %s\n", releaseTag, dep.RepoName)
		ref, exists := kv[dep.Name]
		if !exists {
			return fmt.Errorf("ill-defined %s: unable to find %s", istioVersionFile, dep.Name)
		}
		// make sure ref is a SHA, special case where previous release is used in this release
		if u.ReleaseTagRegex.MatchString(ref) {
			ref, err = githubClnt.GetTagCommitSHA(dep.RepoName, ref)
			if err != nil {
				return err
			}
		}
		if err := githubClnt.CreateAnnotatedTag(
			dep.RepoName, releaseTag, ref, releaseMsg); err != nil {
			if strings.Contains(err.Error(), "Reference already exists") {
				log.Printf("Tag [%s] already exists on %s\n", releaseTag, dep.RepoName)
				prevTagSHA, err := githubClnt.GetTagCommitSHA(dep.RepoName, releaseTag)
				if err != nil {
					return err
				}
				if prevTagSHA == ref {
					log.Printf("Intended to tag [%s] at the same SHA, resort to no-op and continue\n", ref)
					continue
				} else {
					return fmt.Errorf("trying to tag [%s] at different SHA", ref)
				}
			}
		}
	}
	return nil
}

// UpdateIstioVersionAfterReleaseTagsMadeOnDeps runs updateVersion.sh to update
// istio.VERSION and then create a PR on istio
func UpdateIstioVersionAfterReleaseTagsMadeOnDeps() error {
	u.AssertNotEmpty("base_branch", baseBranch)
	releaseTag, err := getReleaseTag()
	if err != nil {
		return err
	}
	edit := func() error {
		hubCommaTag := fmt.Sprintf("%s,%s", dockerHub, releaseTag)
		istioctlURL := fmt.Sprintf(releaseBucketFmtStr, releaseTag, istioctlSuffix)
		debianURL := fmt.Sprintf(releaseBucketFmtStr, releaseTag, debianSuffix)
		cmd := fmt.Sprintf("./install/updateVersion.sh")
		// Auth
		cmd += fmt.Sprintf(" -c %s -A %s", hubCommaTag, debianURL)
		// Mixer
		cmd += fmt.Sprintf(" -x %s", hubCommaTag)
		// Pilot
		cmd += fmt.Sprintf(" -p %s -i %s -P %s", hubCommaTag, istioctlURL, debianURL)
		// Proxy
		cmd += fmt.Sprintf(" -r %s -E %s", releaseTag, debianURL)
		_, err := u.Shell(cmd)
		return err
	}
	releaseBranch := "Istio_Release_" + releaseTag
	body := "Update istio.Version"
	prTitle := releasePRTtilePrefix + body
	_, err = githubClnt.CreatePRUpdateRepo(releaseBranch, *baseBranch, istioRepo, prTitle, body, edit)
	return err
}

// CreateIstioReleaseUploadArtifacts creates a release on istio from the refSHA provided and uploads dependent artifacts
func CreateIstioReleaseUploadArtifacts() error {
	u.AssertNotEmpty("ref_sha", refSHA)
	u.AssertNotEmpty("base_branch", baseBranch)
	u.AssertNotEmpty("next_release", nextRelease)
	releaseTag, err := getReleaseTag()
	if releaseTag == *nextRelease {
		return fmt.Errorf("next_release should be greater than the current release")
	}
	if err != nil {
		return err
	}
	releaseBranch := "finalizeRelease-" + releaseTag
	prBody := fmt.Sprintf("Finalize release %s on istio", releaseTag)
	updateVersion := func() error {
		log.Printf("Updating downloadIstioCandidate.sh with latest release")
		return u.UpdateKeyValueInFile(
			downloadScript, "ISTIO_VERSION", fmt.Sprintf("${ISTIO_VERSION:-%s}", releaseTag))
	}
	edit := func() error {
		if _, err := u.Shell(
			"./release/create_release_archives.sh -d " + releaseBaseDir); err != nil {
			return err
		}
		archiveDir := releaseBaseDir + "/archives"
		if err := githubClnt.CreateReleaseUploadArchives(
			istioRepo, releaseTag, *refSHA, archiveDir); err != nil {
			return err
		}
		if err := u.WriteTextFile(releaseTagFile, *nextRelease); err != nil {
			return err
		}
		return updateVersion()
	}
	prTitle := releasePRTtilePrefix + prBody
	_, err = githubClnt.CreatePRUpdateRepo(releaseBranch, *baseBranch, istioRepo, prTitle, prBody, edit)
	if err != nil {
		return err
	}
	if *extraBranchesUpdateDownloadVersion != "" {
		extraBranches := strings.Split(*extraBranchesUpdateDownloadVersion, ",")
		for _, branch := range extraBranches {
			localBranch := fmt.Sprintf("%s-local", branch)
			if _, err := githubClnt.CreatePRUpdateRepo(localBranch, branch, istioRepo, prTitle, prBody, updateVersion); err != nil {
				// Only log out errors if failing update extra branches
				log.Printf("Warning! Failed to update downloadIstioCandidate.sh in branch %s", branch)
			}
		}
	}
	return nil
}

// DailyReleaseQualification triggers test jobs buy creating a PR that generates
// a GitHub notification. It blocks until PR status is known and returns nonzero
// value if failure. Links to test logs will also be logged to console.
func DailyReleaseQualification() error {
	u.AssertNotEmpty("hub", hub) // TODO (chx) default value of hub
	u.AssertNotEmpty("tag", tag)
	u.AssertNotEmpty("gcs_path", gcsPath)
	log.Printf("Creating PR to trigger release qualifications\n")
	prTitle := "[DO NOT MANUAL MERGE] " + relQualificationPRTtilePrefix + *refSHA
	prBody := fmt.Sprintf("Trigger release qualification jobs")
	timestamp := fmt.Sprintf("%v", time.Now().UnixNano())
	newBranch := "relQual_" + timestamp
	edit := func() error {
		if err := u.UpdateKeyValueInFile(greenBuildVersionFile, "HUB", *hub); err != nil {
			return err
		}
		if err := u.UpdateKeyValueInFile(greenBuildVersionFile, "TAG", *tag); err != nil {
			return err
		}
		if err := u.UpdateKeyValueInFile(greenBuildVersionFile, "TIME", timestamp); err != nil {
			return err
		}
		if err := u.UpdateKeyValueInFile(greenBuildVersionFile, "ISTIOCTL_URL",
			fmt.Sprintf("https://storage.googleapis.com/%s/istioctl", *gcsPath)); err != nil {
			return err
		}
		return nil
	}
	pr, err := ghClntRel.CreatePRUpdateRepo(newBranch, masterBranch, dailyRepo, prTitle, prBody, edit)
	if err != nil {
		return err
	}
	defer func() {
		log.Printf("Close the PR and delete its branch\n")
		if e := ghClntRel.ClosePRDeleteBranch(dailyRepo, pr); e != nil {
			log.Printf("Error in ClosePRDeleteBranch: %v\n", e)
		}
	}()

	verbose := true
	ci := u.NewCIState()
	retryDelay := 1 * time.Minute
	totalRetries := 60
	log.Printf("Waiting for all jobs starting. Results Polling starts in %v.\n", retryDelay)
	time.Sleep(retryDelay)
	err = u.Poll(retryDelay, totalRetries, func() (bool, error) {
		status, errPoll := ghClntRel.GetPRTestResults(dailyRepo, pr, verbose)
		verbose = false
		if errPoll != nil {
			return false, errPoll
		}
		exitPolling := false
		switch status {
		case ci.Success:
			log.Printf("Release qualification passed\n")
			exitPolling = true
		case ci.Failure:
			// All failures have been logged by GetPRTestResults()
			exitPolling = true
			errPoll = fmt.Errorf("release qualification failed")
		case ci.Pending:
			log.Printf("Results still pending. Will check again in %v.\n", retryDelay)
		}
		return exitPolling, errPoll
	})
	if err != nil { // qualification failed
		return err
	}
	log.Printf("Auto merging this PR to update daily release\n")
	return ghClntRel.MergePR(dailyRepo, pr)
}

func init() {
	flag.Parse()
	u.AssertNotEmpty("token_file", tokenFile)
	token, err := u.GetAPITokenFromFile(*tokenFile)
	if err != nil {
		log.Fatalf("Error accessing user supplied token_file: %v\n", err)
	}
	githubClnt = u.NewGithubClient(*owner, token)
	// a new github client is created for istio-releases org
	ghClntRel = u.NewGithubClient(*releaseOrg, token)
}

func main() {
	switch *op {
	case "fastForward":
		if err := fastForward(repo, baseBranch, refSHA); err != nil {
			log.Printf("Error during fastForward: %v\n", err)
		}
	case "tagIstioDepsForRelease":
		if err := TagIstioDepsForRelease(); err != nil {
			log.Printf("Error during TagIstioDepsForRelease: %v\n", err)
		}
	case "updateIstioVersion":
		if err := UpdateIstioVersionAfterReleaseTagsMadeOnDeps(); err != nil {
			log.Printf("Error during UpdateIstioVersionAfterReleaseTagsMadeOnDeps: %v\n", err)
		}
	case "uploadArtifacts":
		if err := CreateIstioReleaseUploadArtifacts(); err != nil {
			log.Printf("Error during CreateIstioReleaseUploadArtifacts: %v\n", err)
		}
	case "dailyRelQual":
		if err := DailyReleaseQualification(); err != nil {
			log.Printf("Error during DailyReleaseQualification: %v\n", err)
			os.Exit(1)
		}
	default:
		log.Printf("Unsupported operation: %s\n", *op)
	}
}
<|endoftext|>


<commit_before>// Package natty provides a Go language wrapper to the natty NAT traversal
// utility.  See https://github.com/getlantern/t.
//
// See natty_test for an example of Natty in use, including debug logging
// showing the messages that are sent across the signaling channel.
package natty

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"os/exec"
	"strings"
	"sync"
	"time"

	"github.com/getlantern/byteexec"
	"github.com/getlantern/golog"
)

const (
	UDP = Protocol("udp")
	TCP = Protocol("tcp")
)

var (
	log = golog.LoggerFor("natty")

	reallyHighTimeout = 100000 * time.Hour

	nattybe *byteexec.Exec
)

func init() {
	nattyBytes, err := Asset("natty")
	if err != nil {
		panic(fmt.Errorf("Unable to read natty bytes: %s", err))
	}

	nattybe, err = byteexec.New(nattyBytes, "natty")
	if err != nil {
		panic(fmt.Errorf("Unable to construct byteexec for natty: %s", err))
	}
}

type Protocol string

// A FiveTuple is the result of a successful NAT traversal.
type FiveTuple struct {
	Proto  Protocol
	Local  string
	Remote string
}

// UDPAddrs returns a pair of UDPAddrs representing the Local and Remote
// addresses of this FiveTuple. If the FiveTuple's Proto is not UDP, this method
// returns an error.
func (ft *FiveTuple) UDPAddrs() (local *net.UDPAddr, remote *net.UDPAddr, err error) {
	if ft.Proto != UDP {
		err = fmt.Errorf("FiveTuple.Proto was not UDP!: %s", ft.Proto)
		return
	}
	local, err = net.ResolveUDPAddr("udp", ft.Local)
	if err != nil {
		err = fmt.Errorf("Unable to resolve local UDP address %s: %s", ft.Local)
		return
	}
	remote, err = net.ResolveUDPAddr("udp", ft.Remote)
	if err != nil {
		err = fmt.Errorf("Unable to resolve remote UDP address %s: %s", ft.Remote)
	}
	return
}

// Traversal represents a single NAT traversal using natty, whose result is
// available via the methods FiveTuple() and FiveTupleTimeout().
//
// Consumers should make sure to call Close() after finishing with this Natty
// in order to make sure the underlying natty process and associated resources
// are closed.
type Traversal struct {
	traceOut           io.Writer       // target for output from natty's stderr
	cmd                *exec.Cmd       // the natty command
	stdin              io.WriteCloser  // pipe to natty's stdin
	stdout             io.ReadCloser   // pipe from natty's stdout
	stdoutbuf          *bufio.Reader   // buffered stdout
	stderr             io.ReadCloser   // pipe from natty's stderr
	msgInCh            chan string     // channel for messages inbound to this Natty
	msgOutCh           chan string     // channel for messages outbound from this Natty
	peerGotFiveTupleCh chan bool       // channel to signal once we know that our peer received their own FiveTuple
	fiveTupleCh        chan *FiveTuple // intermediary channel for the FiveTuple emitted by the natty command
	errCh              chan error      // intermediary channel for any error encountered while running natty
	fiveTupleOutCh     chan *FiveTuple // channel for FiveTuple output
	errOutCh           chan error      // channel for error output
	fiveTupleOut       *FiveTuple      // the output FiveTuple
	errOut             error           // the output error
	outMutex           sync.Mutex      // mutex for synchronizing access to output variables
}

// Offer starts a Traversal as an Offerer, meaning that it will make an offer to
// initiate an ICE session. Call FiveTuple() or FiveTupleTimeout() to get the
// FiveTuple resulting from Traversal.
func Offer() *Traversal {
	log.Trace("Offering")
	t := &Traversal{
		traceOut: log.TraceOut(),
	}
	t.run([]string{"-offer"})
	return t
}

// Answer starts a Traversal as an Answerer, meaning that it will accept offers
// to initiate an ICE session. Call FiveTuple() or FiveTupleTimeout() to get the
// FiveTuple resulting from Traversal.
func Answer() *Traversal {
	log.Trace("Answering")
	t := &Traversal{
		traceOut: log.TraceOut(),
	}
	t.run([]string{})
	return t
}

// MsgIn is used to pass this Traversal a message from the peer t. This method
// is buffered and will typically not block.
func (t *Traversal) MsgIn(msg string) {
	log.Tracef("Got message: %s", msg)
	t.msgInCh <- msg
}

// NextMsgOut gets the next message to pass to the peer.  If done is true, there
// are no more messages to be read, and the currently returned message should be
// ignored.
func (t *Traversal) NextMsgOut() (msg string, done bool) {
	m, ok := <-t.msgOutCh
	log.Tracef("Returning out message: %s", m)
	return m, !ok
}

// FiveTuple gets the FiveTuple from the Traversal, blocking until such is
// available.
func (t *Traversal) FiveTuple() (*FiveTuple, error) {
	log.Trace("Getting FiveTuple")
	return t.FiveTupleTimeout(reallyHighTimeout)
}

// FiveTupleTimeout gets the FiveTuple from the Traversal, blocking until such
// is available or the timeout is hit.
func (t *Traversal) FiveTupleTimeout(timeout time.Duration) (*FiveTuple, error) {
	log.Trace("Getting FiveTupleTimeout")
	t.outMutex.Lock()
	defer t.outMutex.Unlock()

	if t.fiveTupleOut == nil && t.errOut == nil {
		log.Trace("We don't have a result yet, wait for one")
		select {
		case ft := <-t.fiveTupleOutCh:
			log.Tracef("FiveTuple is: %s", ft)
			t.fiveTupleOut = ft
		case err := <-t.errOutCh:
			log.Tracef("Error is: %s", err)
			t.errOut = err
		case <-time.After(timeout):
			log.Trace("Return an error, but don't store it (lets caller try again)")
			return nil, fmt.Errorf("Timed out waiting for five-tuple")
		}
	}

	log.Tracef("FiveTupleTimeout returns %s: %s", t.fiveTupleOut, t.errOut)
	return t.fiveTupleOut, t.errOut
}

// Close closes this Traversal, terminating any outstanding natty process by
// sending SIGKILL. Close blocks until the natty process has terminated, at
// which point any ports that it bound should be available for use.
func (t *Traversal) Close() error {
	if t.cmd != nil && t.cmd.Process != nil {
		log.Trace("Killing natty process")
		t.cmd.Process.Kill()
		t.cmd.Process.Wait()
		log.Trace("Done killing natty process")
	}
	return nil
}

// run runs the natty command to obtain a FiveTuple. The actual running of
// natty happens on a goroutine so that run itself doesn't block.
func (t *Traversal) run(params []string) {
	t.msgInCh = make(chan string, 100)
	t.msgOutCh = make(chan string, 100)

	// Note - these channels are a buffered in order to prevent deadlocks
	t.peerGotFiveTupleCh = make(chan bool, 10)
	t.fiveTupleCh = make(chan *FiveTuple, 10)
	t.errCh = make(chan error, 10)
	t.fiveTupleOutCh = make(chan *FiveTuple, 10)
	t.errOutCh = make(chan error, 10)

	err := t.initCommand(params)

	go func() {
		if err != nil {
			t.errOutCh <- err
			return
		}

		ft, err := t.doRun(params)
		log.Trace("doRun is finished, inform client of the FiveTuple")
		if err != nil {
			log.Tracef("Returning error: %s", err)
			t.errOutCh <- err
		} else {
			log.Tracef("Returning FiveTuple: %s", ft)
			t.fiveTupleOutCh <- ft
		}
	}()
}

// doRun does the running, including resource cleanup.  doRun blocks until
// Close() has finished, meaning that natty is no longer running and whatever
// port it returned in the FiveTuple can now be used for other things.
func (t *Traversal) doRun(params []string) (*FiveTuple, error) {
	defer t.Close()

	go t.processStdout()
	go t.processStderr()

	// Start the natty command
	t.errCh <- t.cmd.Start()

	// Process incoming messages
	go func() {
		for {
			msg := <-t.msgInCh
			log.Tracef("Got incoming message: %s", msg)

			if IsFiveTuple(msg) {
				log.Trace("Incoming message was a FiveTuple!")
				t.peerGotFiveTupleCh <- true
				continue
			}

			log.Trace("Forward message to natty process")
			_, err := t.stdin.Write([]byte(msg))
			if err == nil {
				_, err = t.stdin.Write([]byte("\n"))
			}
			if err != nil {
				log.Tracef("Unable to forward message to natty process: %s: %s", msg, err)
				t.errCh <- err
			} else {
				log.Tracef("Forwarded message to natty process: %s", msg)
			}
		}
	}()

	for {
		select {
		case result := <-t.fiveTupleCh:
			// Wait for peer to get FiveTuple before returning.  If we didn't do
			// this, our natty instance might stop running before the peer
			// finishes its work to get its own FiveTuple.
			log.Trace("Got our own FiveTuple, waiting for peer to get FiveTuple")
			<-t.peerGotFiveTupleCh
			log.Trace("Peer got FiveTuple!")
			return result, nil
		case err := <-t.errCh:
			if err != nil && err != io.EOF {
				return nil, err
			}
		}
	}
}

// initCommand sets up the natty command
func (t *Traversal) initCommand(params []string) (err error) {
	if log.IsTraceEnabled() {
		log.Trace("Telling natty to log debug output")
		params = append(params, "-debug")
	}

	t.cmd = nattybe.Command(params...)
	t.stdin, err = t.cmd.StdinPipe()
	if err != nil {
		return err
	}
	t.stdout, err = t.cmd.StdoutPipe()
	if err != nil {
		return err
	}
	t.stderr, err = t.cmd.StderrPipe()
	if err != nil {
		return err
	}

	t.stdoutbuf = bufio.NewReader(t.stdout)

	return nil
}

// processStdout reads the output from natty and sends it to the msgOutCh. If
// it finds a FiveTuple, it records that.
func (t *Traversal) processStdout() {
	for {
		// Read next message from natty
		msg, err := t.stdoutbuf.ReadString('\n')
		if err != nil {
			t.errCh <- err
			return
		}

		// Request send of message to peer
		t.msgOutCh <- msg

		if IsFiveTuple(msg) {
			// We got a FiveTuple!
			fiveTuple := &FiveTuple{}
			err = json.Unmarshal([]byte(msg), fiveTuple)
			if err != nil {
				t.errCh <- err
				return
			}
			t.fiveTupleCh <- fiveTuple
		}
	}
}

// processStderr copies the output from natty's stderr to the configured
// traceOut
func (t *Traversal) processStderr() {
	_, err := io.Copy(t.traceOut, t.stderr)
	t.errCh <- err
}

func IsFiveTuple(msg string) bool {
	return strings.Contains(msg, "\"type\":\"5-tuple\"")
}
<commit_msg>Fixed process killing on Windows<commit_after>// Package natty provides a Go language wrapper to the natty NAT traversal
// utility.  See https://github.com/getlantern/t.
//
// See natty_test for an example of Natty in use, including debug logging
// showing the messages that are sent across the signaling channel.
package natty

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"os/exec"
	"strings"
	"sync"
	"time"

	"github.com/getlantern/byteexec"
	"github.com/getlantern/golog"
)

const (
	UDP = Protocol("udp")
	TCP = Protocol("tcp")
)

var (
	log = golog.LoggerFor("natty")

	reallyHighTimeout = 100000 * time.Hour

	nattybe *byteexec.Exec
)

func init() {
	nattyBytes, err := Asset("natty")
	if err != nil {
		panic(fmt.Errorf("Unable to read natty bytes: %s", err))
	}

	nattybe, err = byteexec.New(nattyBytes, "natty")
	if err != nil {
		panic(fmt.Errorf("Unable to construct byteexec for natty: %s", err))
	}
}

type Protocol string

// A FiveTuple is the result of a successful NAT traversal.
type FiveTuple struct {
	Proto  Protocol
	Local  string
	Remote string
}

// UDPAddrs returns a pair of UDPAddrs representing the Local and Remote
// addresses of this FiveTuple. If the FiveTuple's Proto is not UDP, this method
// returns an error.
func (ft *FiveTuple) UDPAddrs() (local *net.UDPAddr, remote *net.UDPAddr, err error) {
	if ft.Proto != UDP {
		err = fmt.Errorf("FiveTuple.Proto was not UDP!: %s", ft.Proto)
		return
	}
	local, err = net.ResolveUDPAddr("udp", ft.Local)
	if err != nil {
		err = fmt.Errorf("Unable to resolve local UDP address %s: %s", ft.Local)
		return
	}
	remote, err = net.ResolveUDPAddr("udp", ft.Remote)
	if err != nil {
		err = fmt.Errorf("Unable to resolve remote UDP address %s: %s", ft.Remote)
	}
	return
}

// Traversal represents a single NAT traversal using natty, whose result is
// available via the methods FiveTuple() and FiveTupleTimeout().
//
// Consumers should make sure to call Close() after finishing with this Natty
// in order to make sure the underlying natty process and associated resources
// are closed.
type Traversal struct {
	traceOut           io.Writer       // target for output from natty's stderr
	cmd                *exec.Cmd       // the natty command
	stdin              io.WriteCloser  // pipe to natty's stdin
	stdout             io.ReadCloser   // pipe from natty's stdout
	stdoutbuf          *bufio.Reader   // buffered stdout
	stderr             io.ReadCloser   // pipe from natty's stderr
	msgInCh            chan string     // channel for messages inbound to this Natty
	msgOutCh           chan string     // channel for messages outbound from this Natty
	peerGotFiveTupleCh chan bool       // channel to signal once we know that our peer received their own FiveTuple
	fiveTupleCh        chan *FiveTuple // intermediary channel for the FiveTuple emitted by the natty command
	errCh              chan error      // intermediary channel for any error encountered while running natty
	fiveTupleOutCh     chan *FiveTuple // channel for FiveTuple output
	errOutCh           chan error      // channel for error output
	fiveTupleOut       *FiveTuple      // the output FiveTuple
	errOut             error           // the output error
	outMutex           sync.Mutex      // mutex for synchronizing access to output variables
}

// Offer starts a Traversal as an Offerer, meaning that it will make an offer to
// initiate an ICE session. Call FiveTuple() or FiveTupleTimeout() to get the
// FiveTuple resulting from Traversal.
func Offer() *Traversal {
	log.Trace("Offering")
	t := &Traversal{
		traceOut: log.TraceOut(),
	}
	t.run([]string{"-offer"})
	return t
}

// Answer starts a Traversal as an Answerer, meaning that it will accept offers
// to initiate an ICE session. Call FiveTuple() or FiveTupleTimeout() to get the
// FiveTuple resulting from Traversal.
func Answer() *Traversal {
	log.Trace("Answering")
	t := &Traversal{
		traceOut: log.TraceOut(),
	}
	t.run([]string{})
	return t
}

// MsgIn is used to pass this Traversal a message from the peer t. This method
// is buffered and will typically not block.
func (t *Traversal) MsgIn(msg string) {
	log.Tracef("Got message: %s", msg)
	t.msgInCh <- msg
}

// NextMsgOut gets the next message to pass to the peer.  If done is true, there
// are no more messages to be read, and the currently returned message should be
// ignored.
func (t *Traversal) NextMsgOut() (msg string, done bool) {
	m, ok := <-t.msgOutCh
	log.Tracef("Returning out message: %s", m)
	return m, !ok
}

// FiveTuple gets the FiveTuple from the Traversal, blocking until such is
// available.
func (t *Traversal) FiveTuple() (*FiveTuple, error) {
	log.Trace("Getting FiveTuple")
	return t.FiveTupleTimeout(reallyHighTimeout)
}

// FiveTupleTimeout gets the FiveTuple from the Traversal, blocking until such
// is available or the timeout is hit.
func (t *Traversal) FiveTupleTimeout(timeout time.Duration) (*FiveTuple, error) {
	log.Trace("Getting FiveTupleTimeout")
	t.outMutex.Lock()
	defer t.outMutex.Unlock()

	if t.fiveTupleOut == nil && t.errOut == nil {
		log.Trace("We don't have a result yet, wait for one")
		select {
		case ft := <-t.fiveTupleOutCh:
			log.Tracef("FiveTuple is: %s", ft)
			t.fiveTupleOut = ft
		case err := <-t.errOutCh:
			log.Tracef("Error is: %s", err)
			t.errOut = err
		case <-time.After(timeout):
			log.Trace("Return an error, but don't store it (lets caller try again)")
			return nil, fmt.Errorf("Timed out waiting for five-tuple")
		}
	}

	log.Tracef("FiveTupleTimeout returns %s: %s", t.fiveTupleOut, t.errOut)
	return t.fiveTupleOut, t.errOut
}

// Close closes this Traversal, terminating any outstanding natty process by
// sending SIGKILL. Close blocks until the natty process has terminated, at
// which point any ports that it bound should be available for use.
func (t *Traversal) Close() error {
	if t.cmd != nil && t.cmd.Process != nil {
		log.Trace("Killing natty process")
		t.cmd.Process.Kill()
		log.Trace("Waiting for natty process to die")
		t.cmd.Wait()
		log.Trace("Killed natty process")
	}
	return nil
}

// run runs the natty command to obtain a FiveTuple. The actual running of
// natty happens on a goroutine so that run itself doesn't block.
func (t *Traversal) run(params []string) {
	t.msgInCh = make(chan string, 100)
	t.msgOutCh = make(chan string, 100)

	// Note - these channels are a buffered in order to prevent deadlocks
	t.peerGotFiveTupleCh = make(chan bool, 10)
	t.fiveTupleCh = make(chan *FiveTuple, 10)
	t.errCh = make(chan error, 10)
	t.fiveTupleOutCh = make(chan *FiveTuple, 10)
	t.errOutCh = make(chan error, 10)

	err := t.initCommand(params)

	go func() {
		if err != nil {
			t.errOutCh <- err
			return
		}

		ft, err := t.doRun(params)
		log.Trace("doRun is finished, inform client of the FiveTuple")
		if err != nil {
			log.Tracef("Returning error: %s", err)
			t.errOutCh <- err
		} else {
			log.Tracef("Returning FiveTuple: %s", ft)
			t.fiveTupleOutCh <- ft
		}
	}()
}

// doRun does the running, including resource cleanup.  doRun blocks until
// Close() has finished, meaning that natty is no longer running and whatever
// port it returned in the FiveTuple can now be used for other things.
func (t *Traversal) doRun(params []string) (*FiveTuple, error) {
	defer t.Close()

	go t.processStdout()
	go t.processStderr()

	// Start the natty command
	t.errCh <- t.cmd.Start()

	// Process incoming messages
	go func() {
		for {
			msg := <-t.msgInCh
			log.Tracef("Got incoming message: %s", msg)

			if IsFiveTuple(msg) {
				log.Trace("Incoming message was a FiveTuple!")
				t.peerGotFiveTupleCh <- true
				continue
			}

			log.Trace("Forward message to natty process")
			_, err := t.stdin.Write([]byte(msg))
			if err == nil {
				_, err = t.stdin.Write([]byte("\n"))
			}
			if err != nil {
				log.Tracef("Unable to forward message to natty process: %s: %s", msg, err)
				t.errCh <- err
			} else {
				log.Tracef("Forwarded message to natty process: %s", msg)
			}
		}
	}()

	for {
		select {
		case result := <-t.fiveTupleCh:
			// Wait for peer to get FiveTuple before returning.  If we didn't do
			// this, our natty instance might stop running before the peer
			// finishes its work to get its own FiveTuple.
			log.Trace("Got our own FiveTuple, waiting for peer to get FiveTuple")
			<-t.peerGotFiveTupleCh
			log.Trace("Peer got FiveTuple!")
			return result, nil
		case err := <-t.errCh:
			if err != nil && err != io.EOF {
				return nil, err
			}
		}
	}
}

// initCommand sets up the natty command
func (t *Traversal) initCommand(params []string) (err error) {
	if log.IsTraceEnabled() {
		log.Trace("Telling natty to log debug output")
		params = append(params, "-debug")
	}

	t.cmd = nattybe.Command(params...)
	t.stdin, err = t.cmd.StdinPipe()
	if err != nil {
		return err
	}
	t.stdout, err = t.cmd.StdoutPipe()
	if err != nil {
		return err
	}
	t.stderr, err = t.cmd.StderrPipe()
	if err != nil {
		return err
	}

	t.stdoutbuf = bufio.NewReader(t.stdout)

	return nil
}

// processStdout reads the output from natty and sends it to the msgOutCh. If
// it finds a FiveTuple, it records that.
func (t *Traversal) processStdout() {
	for {
		// Read next message from natty
		msg, err := t.stdoutbuf.ReadString('\n')
		if err != nil {
			t.errCh <- err
			return
		}

		// Request send of message to peer
		t.msgOutCh <- msg

		if IsFiveTuple(msg) {
			// We got a FiveTuple!
			fiveTuple := &FiveTuple{}
			err = json.Unmarshal([]byte(msg), fiveTuple)
			if err != nil {
				t.errCh <- err
				return
			}
			t.fiveTupleCh <- fiveTuple
		}
	}
}

// processStderr copies the output from natty's stderr to the configured
// traceOut
func (t *Traversal) processStderr() {
	_, err := io.Copy(t.traceOut, t.stderr)
	t.errCh <- err
}

func IsFiveTuple(msg string) bool {
	return strings.Contains(msg, "\"type\":\"5-tuple\"")
}
<|endoftext|>


<commit_before>package todolist

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

type App struct {
	TodoStore *FileStore
	TodoList  *TodoList
}

func NewApp() *App {
	app := &App{TodoList: &TodoList{}, TodoStore: NewFileStore()}
	return app
}

func (a *App) InitializeRepo() {
	a.TodoStore.Initialize()
}

func (a *App) AddTodo(input string) {
	a.Load()
	parser := &Parser{}
	todo := parser.ParseNewTodo(input)
	if todo == nil {
		fmt.Println("I need more information. Try something like 'todo a chat with @bob due tom'")
		return
	}

	id := a.TodoList.NextId()
	a.TodoList.Add(todo)
	a.Save()
	fmt.Printf("Todo %d added.\n", id)
}

func (a *App) DeleteTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Delete(id)
	a.Save()
	fmt.Println("Todo deleted.")
}

func (a *App) CompleteTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Complete(id)
	a.Save()
	fmt.Println("Todo completed.")
}

func (a *App) UncompleteTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Uncomplete(id)
	a.Save()
	fmt.Println("Todo uncompleted.")
}

func (a *App) ArchiveTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Archive(id)
	a.Save()
	fmt.Println("Todo archived.")
}

func (a *App) UnarchiveTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Unarchive(id)
	a.Save()
	fmt.Println("Todo unarchived.")
}

func (a *App) EditTodo(input string) {
	a.Load()
	id, todo := a.getId(input)
	if id == -1 {
		return
	}
	parser := &Parser{}

	if (parser.ParseEditTodo(todo, input)) {
		a.Save()
		fmt.Println("Todo updated.")
	}
}

func (a *App) ExpandTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	parser := &Parser{}
	if id == -1 {
		return
	}

	commonProject := parser.ExpandProject(input)
	todos := strings.LastIndex(input, ":")
	if commonProject == "" || len(input) <= todos+1 || todos == -1 {
		fmt.Println("I'm expecting a format like \"todolist ex <project>: <todo1>, <todo2>, ...\"")
		return
	}

	newTodos := strings.Split(input[todos+1:], ",")

	for _, todo := range newTodos {
		args := []string{"add ", commonProject, " ", todo}
		a.AddTodo(strings.Join(args, ""))
	}

	a.TodoList.Delete(id)
	a.Save()
	fmt.Println("Todo expanded.")
}

func (a *App) ArchiveCompleted() {
	a.Load()
	for _, todo := range a.TodoList.Todos() {
		if todo.Completed {
			todo.Archived = true
		}
	}
	a.Save()
	fmt.Println("All completed todos have been archived.")
}

func (a *App) ListTodos(input string) {
	a.Load()
	filtered := NewFilter(a.TodoList.Todos()).Filter(input)
	grouped := a.getGroups(input, filtered)

	formatter := NewFormatter(grouped)
	formatter.Print()
}

func (a *App) PrioritizeTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Prioritize(id)
	a.Save()
	fmt.Println("Todo prioritized.")
}

func (a *App) UnprioritizeTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Unprioritize(id)
	a.Save()
	fmt.Println("Todo un-prioritized.")
}

func (a *App) getId(input string) (int, *Todo) {
	re, _ := regexp.Compile("\\d+")
	if re.MatchString(input) {
		id, _ := strconv.Atoi(re.FindString(input))
		todo := a.TodoList.FindById(id)
		if todo == nil {
			fmt.Println("No such id.")
			return -1, nil

		}
		return id, todo

	} else {
		fmt.Println("Invalid id.")
		return -1, nil

	}
}

func (a *App) getGroups(input string, todos []*Todo) *GroupedTodos {
	grouper := &Grouper{}
	contextRegex, _ := regexp.Compile(`by c.*$`)
	projectRegex, _ := regexp.Compile(`by p.*$`)

	var grouped *GroupedTodos

	if contextRegex.MatchString(input) {
		grouped = grouper.GroupByContext(todos)
	} else if projectRegex.MatchString(input) {
		grouped = grouper.GroupByProject(todos)
	} else {
		grouped = grouper.GroupByNothing(todos)
	}
	return grouped
}

func (a *App) GarbageCollect() {
	a.Load()
	a.TodoList.GarbageCollect()
	a.Save()
	fmt.Println("Garbage collection complete.")
}

func (a *App) Load() error {
	todos, err := a.TodoStore.Load()
	if err != nil {
		return err
	}
	a.TodoList.Load(todos)
	return nil
}

func (a *App) Save() {
	a.TodoStore.Save(a.TodoList.Data)
}
<commit_msg>Remove unnecessary parens<commit_after>package todolist

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

type App struct {
	TodoStore *FileStore
	TodoList  *TodoList
}

func NewApp() *App {
	app := &App{TodoList: &TodoList{}, TodoStore: NewFileStore()}
	return app
}

func (a *App) InitializeRepo() {
	a.TodoStore.Initialize()
}

func (a *App) AddTodo(input string) {
	a.Load()
	parser := &Parser{}
	todo := parser.ParseNewTodo(input)
	if todo == nil {
		fmt.Println("I need more information. Try something like 'todo a chat with @bob due tom'")
		return
	}

	id := a.TodoList.NextId()
	a.TodoList.Add(todo)
	a.Save()
	fmt.Printf("Todo %d added.\n", id)
}

func (a *App) DeleteTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Delete(id)
	a.Save()
	fmt.Println("Todo deleted.")
}

func (a *App) CompleteTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Complete(id)
	a.Save()
	fmt.Println("Todo completed.")
}

func (a *App) UncompleteTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Uncomplete(id)
	a.Save()
	fmt.Println("Todo uncompleted.")
}

func (a *App) ArchiveTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Archive(id)
	a.Save()
	fmt.Println("Todo archived.")
}

func (a *App) UnarchiveTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Unarchive(id)
	a.Save()
	fmt.Println("Todo unarchived.")
}

func (a *App) EditTodo(input string) {
	a.Load()
	id, todo := a.getId(input)
	if id == -1 {
		return
	}
	parser := &Parser{}

	if parser.ParseEditTodo(todo, input) {
		a.Save()
		fmt.Println("Todo updated.")
	}
}

func (a *App) ExpandTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	parser := &Parser{}
	if id == -1 {
		return
	}

	commonProject := parser.ExpandProject(input)
	todos := strings.LastIndex(input, ":")
	if commonProject == "" || len(input) <= todos+1 || todos == -1 {
		fmt.Println("I'm expecting a format like \"todolist ex <project>: <todo1>, <todo2>, ...\"")
		return
	}

	newTodos := strings.Split(input[todos+1:], ",")

	for _, todo := range newTodos {
		args := []string{"add ", commonProject, " ", todo}
		a.AddTodo(strings.Join(args, ""))
	}

	a.TodoList.Delete(id)
	a.Save()
	fmt.Println("Todo expanded.")
}

func (a *App) ArchiveCompleted() {
	a.Load()
	for _, todo := range a.TodoList.Todos() {
		if todo.Completed {
			todo.Archived = true
		}
	}
	a.Save()
	fmt.Println("All completed todos have been archived.")
}

func (a *App) ListTodos(input string) {
	a.Load()
	filtered := NewFilter(a.TodoList.Todos()).Filter(input)
	grouped := a.getGroups(input, filtered)

	formatter := NewFormatter(grouped)
	formatter.Print()
}

func (a *App) PrioritizeTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Prioritize(id)
	a.Save()
	fmt.Println("Todo prioritized.")
}

func (a *App) UnprioritizeTodo(input string) {
	a.Load()
	id, _ := a.getId(input)
	if id == -1 {
		return
	}
	a.TodoList.Unprioritize(id)
	a.Save()
	fmt.Println("Todo un-prioritized.")
}

func (a *App) getId(input string) (int, *Todo) {
	re, _ := regexp.Compile("\\d+")
	if re.MatchString(input) {
		id, _ := strconv.Atoi(re.FindString(input))
		todo := a.TodoList.FindById(id)
		if todo == nil {
			fmt.Println("No such id.")
			return -1, nil

		}
		return id, todo

	} else {
		fmt.Println("Invalid id.")
		return -1, nil

	}
}

func (a *App) getGroups(input string, todos []*Todo) *GroupedTodos {
	grouper := &Grouper{}
	contextRegex, _ := regexp.Compile(`by c.*$`)
	projectRegex, _ := regexp.Compile(`by p.*$`)

	var grouped *GroupedTodos

	if contextRegex.MatchString(input) {
		grouped = grouper.GroupByContext(todos)
	} else if projectRegex.MatchString(input) {
		grouped = grouper.GroupByProject(todos)
	} else {
		grouped = grouper.GroupByNothing(todos)
	}
	return grouped
}

func (a *App) GarbageCollect() {
	a.Load()
	a.TodoList.GarbageCollect()
	a.Save()
	fmt.Println("Garbage collection complete.")
}

func (a *App) Load() error {
	todos, err := a.TodoStore.Load()
	if err != nil {
		return err
	}
	a.TodoList.Load(todos)
	return nil
}

func (a *App) Save() {
	a.TodoStore.Save(a.TodoList.Data)
}
<|endoftext|>


<commit_before>package todotxt

import (
        "testing"
        "github.com/stretchr/testify/assert"
)

func TestLoadTaskList (t *testing.T) {
        tasklist := LoadTaskList("todo.txt")
        assert.Equal(t, tasklist.Len(), 8, "Something went wrong with LoadTaskList")
}

func TestLoadTaskListNonExistent (t *testing.T) {
        defer func(){
                if r:=recover(); r!=nil {
                        // recovered
                } else {
                        t.Errorf("Something went seriously wrong")
                }
        }()
        tasklist := LoadTaskList("nonexistent-file.txt")

        t.Errorf("Something is still wrong %v", tasklist)
}

func TestParseTask (t *testing.T) {
        task := ParseTask("(A) +funny task with prioity and project", 1)

        assert.Equal(t, task.id, 1, "id should be 1")
        assert.Equal(t, rune(task.priority), rune('A'), "priority should be A")

        projects := make([]string, 1)
        projects[0] = "+funny"

        assert.Equal(t, task.projects, projects, "there should be a project for sure")
        assert.Equal(t, task.todo, "+funny task with prioity and project", "todo should equal")
        assert.Equal(t, task.finished, false)

        finished_task := ParseTask("x This is a finished task", 1)

        assert.Equal(t, finished_task.id, 1)
        assert.Equal(t, finished_task.todo, "This is a finished task")
        assert.Equal(t, finished_task.finished, true)

        task_with_contexts := ParseTask("Some @task with @interesting contexts", 1)

        assert.Equal(t, task_with_contexts.id, 1)
        assert.Equal(t, task_with_contexts.finished, false)

        contexts := make([]string, 2)
        contexts[0] = "@task"
        contexts[1] = "@interesting"

        assert.Equal(t, task_with_contexts.contexts, contexts)

}
<commit_msg>test: Added test for task.Matches<commit_after>package todotxt

import (
        "testing"
        "github.com/stretchr/testify/assert"
)

func TestLoadTaskList (t *testing.T) {
        tasklist := LoadTaskList("todo.txt")
        assert.Equal(t, tasklist.Len(), 8, "Something went wrong with LoadTaskList")
}

func TestLoadTaskListNonExistent (t *testing.T) {
        defer func(){
                if r:=recover(); r!=nil {
                        // recovered
                } else {
                        t.Errorf("Something went seriously wrong")
                }
        }()
        tasklist := LoadTaskList("nonexistent-file.txt")

        t.Errorf("Something is still wrong %v", tasklist)
}

func TestParseTask (t *testing.T) {
        task := ParseTask("(A) +funny task with prioity and project", 1)

        assert.Equal(t, task.id, 1, "id should be 1")
        assert.Equal(t, rune(task.priority), rune('A'), "priority should be A")

        projects := make([]string, 1)
        projects[0] = "+funny"

        assert.Equal(t, task.projects, projects, "there should be a project for sure")
        assert.Equal(t, task.todo, "+funny task with prioity and project", "todo should equal")
        assert.Equal(t, task.finished, false)

        finished_task := ParseTask("x This is a finished task", 1)

        assert.Equal(t, finished_task.id, 1)
        assert.Equal(t, finished_task.todo, "This is a finished task")
        assert.Equal(t, finished_task.finished, true)

        task_with_contexts := ParseTask("Some @task with @interesting contexts", 1)

        assert.Equal(t, task_with_contexts.id, 1)
        assert.Equal(t, task_with_contexts.finished, false)

        contexts := make([]string, 2)
        contexts[0] = "@task"
        contexts[1] = "@interesting"

        assert.Equal(t, task_with_contexts.contexts, contexts)

}

func TestTaskMatches (t *testing.T) {
        task := ParseTask("(A) +funny task with prioity and project", 1)

        assert.Equal(t, task.Matches("funny"), true)
}
<|endoftext|>


<commit_before>package todotxt

import "testing"

func TestLoadTaskList (t *testing.T) {
        tasklist := LoadTaskList("todo.txt")
        if tasklist.Len() != 8 {
                t.Errorf("Something went wrong with LoadTaskList: is %v, want %v\n%v",
                          tasklist.Len(), 8, tasklist)
        }
}
<commit_msg>nonexistent file test<commit_after>package todotxt

import "testing"

func TestLoadTaskList (t *testing.T) {
        tasklist := LoadTaskList("todo.txt")
        if tasklist.Len() != 8 {
                t.Errorf("Something went wrong with LoadTaskList: is %v, want %v\n%v",
                          tasklist.Len(), 8, tasklist)
        }
}

func TestLoadTaskListNonExistent (t *testing.T) {
        defer func(){
                if r:=recover(); r!=nil {
                        // recovered
                } else {
                        t.Errorf("Something went seriously wrong")
                }
        }()
        tasklist := LoadTaskList("nonexistent-file.txt")

        t.Errorf("Something is still wrong %v", tasklist)
}
<|endoftext|>


<commit_before>package vault

import (
	"strings"
	"sync"
	"time"

	log "github.com/mgutz/logxi/v1"

	"github.com/armon/go-metrics"
	"github.com/hashicorp/vault/logical"
)

const (
	// rollbackPeriod is how often we attempt rollbacks for all the backends
	rollbackPeriod = time.Minute
)

// RollbackManager is responsible for performing rollbacks of partial
// secrets within logical backends.
//
// During normal operations, it is possible for logical backends to
// error partially through an operation. These are called "partial secrets":
// they are never sent back to a user, but they do need to be cleaned up.
// This manager handles that by periodically (on a timer) requesting that the
// backends clean up.
//
// The RollbackManager periodically initiates a logical.RollbackOperation
// on every mounted logical backend. It ensures that only one rollback operation
// is in-flight at any given time within a single seal/unseal phase.
type RollbackManager struct {
	logger log.Logger

	// This gives the current mount table of both logical and credential backends,
	// plus a RWMutex that is locked for reading. It is up to the caller to RUnlock
	// it when done with the mount table.
	backends func() []*MountEntry

	router *Router
	period time.Duration

	inflightAll  sync.WaitGroup
	inflight     map[string]*rollbackState
	inflightLock sync.RWMutex

	doneCh       chan struct{}
	shutdown     bool
	shutdownCh   chan struct{}
	shutdownLock sync.Mutex
}

// rollbackState is used to track the state of a single rollback attempt
type rollbackState struct {
	lastError error
	sync.WaitGroup
}

// NewRollbackManager is used to create a new rollback manager
func NewRollbackManager(logger log.Logger, backendsFunc func() []*MountEntry, router *Router) *RollbackManager {
	r := &RollbackManager{
		logger:     logger,
		backends:   backendsFunc,
		router:     router,
		period:     rollbackPeriod,
		inflight:   make(map[string]*rollbackState),
		doneCh:     make(chan struct{}),
		shutdownCh: make(chan struct{}),
	}
	return r
}

// Start starts the rollback manager
func (m *RollbackManager) Start() {
	go m.run()
}

// Stop stops the running manager. This will wait for any in-flight
// rollbacks to complete.
func (m *RollbackManager) Stop() {
	m.shutdownLock.Lock()
	defer m.shutdownLock.Unlock()
	if !m.shutdown {
		m.shutdown = true
		close(m.shutdownCh)
		<-m.doneCh
	}
	m.inflightAll.Wait()
}

// run is a long running routine to periodically invoke rollback
func (m *RollbackManager) run() {
	m.logger.Info("rollback: starting rollback manager")
	tick := time.NewTicker(m.period)
	defer tick.Stop()
	defer close(m.doneCh)
	for {
		select {
		case <-tick.C:
			m.triggerRollbacks()

		case <-m.shutdownCh:
			m.logger.Info("rollback: stopping rollback manager")
			return
		}
	}
}

// triggerRollbacks is used to trigger the rollbacks across all the backends
func (m *RollbackManager) triggerRollbacks() {

	backends := m.backends()

	for _, e := range backends {
		path := e.Path
		if e.Table == credentialTableType {
			path = "auth/" + path
		}
		m.inflightLock.RLock()
		_, ok := m.inflight[path]
		m.inflightLock.RUnlock()
		if !ok {
			m.startRollback(path)
		}
	}
}

// startRollback is used to start an async rollback attempt.
// This must be called with the inflightLock held.
func (m *RollbackManager) startRollback(path string) *rollbackState {
	rs := &rollbackState{}
	rs.Add(1)
	m.inflightAll.Add(1)
	m.inflightLock.Lock()
	m.inflight[path] = rs
	m.inflightLock.Unlock()
	go m.attemptRollback(path, rs)
	return rs
}

// attemptRollback invokes a RollbackOperation for the given path
func (m *RollbackManager) attemptRollback(path string, rs *rollbackState) (err error) {
	defer metrics.MeasureSince([]string{"rollback", "attempt", strings.Replace(path, "/", "-", -1)}, time.Now())
	if m.logger.IsDebug() {
		m.logger.Debug("rollback: attempting rollback", "path", path)
	}

	defer func() {
		rs.lastError = err
		rs.Done()
		m.inflightAll.Done()
		m.inflightLock.Lock()
		delete(m.inflight, path)
		m.inflightLock.Unlock()
	}()

	// Invoke a RollbackOperation
	req := &logical.Request{
		Operation: logical.RollbackOperation,
		Path:      path,
	}
	_, err = m.router.Route(req)

	// If the error is an unsupported operation, then it doesn't
	// matter, the backend doesn't support it.
	if err == logical.ErrUnsupportedOperation {
		err = nil
	}
	if err != nil {
		m.logger.Error("rollback: error rolling back", "path", path, "error", err)
	}
	return
}

// Rollback is used to trigger an immediate rollback of the path,
// or to join an existing rollback operation if in flight.
func (m *RollbackManager) Rollback(path string) error {
	// Check for an existing attempt and start one if none
	m.inflightLock.RLock()
	rs, ok := m.inflight[path]
	m.inflightLock.RUnlock()
	if !ok {
		rs = m.startRollback(path)
	}

	// Wait for the attempt to finish
	rs.Wait()

	// Return the last error
	return rs.lastError
}

// The methods below are the hooks from core that are called pre/post seal.

// startRollback is used to start the rollback manager after unsealing
func (c *Core) startRollback() error {
	backendsFunc := func() []*MountEntry {
		ret := []*MountEntry{}
		c.mountsLock.RLock()
		defer c.mountsLock.RUnlock()
		// During teardown/setup after a leader change or unseal there could be
		// something racy here so make sure the table isn't nil
		if c.mounts != nil {
			for _, entry := range c.mounts.Entries {
				ret = append(ret, entry)
			}
		}
		c.authLock.RLock()
		defer c.authLock.RUnlock()
		// During teardown/setup after a leader change or unseal there could be
		// something racy here so make sure the table isn't nil
		if c.auth != nil {
			for _, entry := range c.auth.Entries {
				ret = append(ret, entry)
			}
		}
		return ret
	}
	c.rollback = NewRollbackManager(c.logger, backendsFunc, c.router)
	c.rollback.Start()
	return nil
}

// stopRollback is used to stop running the rollback manager before sealing
func (c *Core) stopRollback() error {
	if c.rollback != nil {
		c.rollback.Stop()
		c.rollback = nil
	}
	return nil
}
<commit_msg>Make rollback attempts trace level instead of debug level<commit_after>package vault

import (
	"strings"
	"sync"
	"time"

	log "github.com/mgutz/logxi/v1"

	"github.com/armon/go-metrics"
	"github.com/hashicorp/vault/logical"
)

const (
	// rollbackPeriod is how often we attempt rollbacks for all the backends
	rollbackPeriod = time.Minute
)

// RollbackManager is responsible for performing rollbacks of partial
// secrets within logical backends.
//
// During normal operations, it is possible for logical backends to
// error partially through an operation. These are called "partial secrets":
// they are never sent back to a user, but they do need to be cleaned up.
// This manager handles that by periodically (on a timer) requesting that the
// backends clean up.
//
// The RollbackManager periodically initiates a logical.RollbackOperation
// on every mounted logical backend. It ensures that only one rollback operation
// is in-flight at any given time within a single seal/unseal phase.
type RollbackManager struct {
	logger log.Logger

	// This gives the current mount table of both logical and credential backends,
	// plus a RWMutex that is locked for reading. It is up to the caller to RUnlock
	// it when done with the mount table.
	backends func() []*MountEntry

	router *Router
	period time.Duration

	inflightAll  sync.WaitGroup
	inflight     map[string]*rollbackState
	inflightLock sync.RWMutex

	doneCh       chan struct{}
	shutdown     bool
	shutdownCh   chan struct{}
	shutdownLock sync.Mutex
}

// rollbackState is used to track the state of a single rollback attempt
type rollbackState struct {
	lastError error
	sync.WaitGroup
}

// NewRollbackManager is used to create a new rollback manager
func NewRollbackManager(logger log.Logger, backendsFunc func() []*MountEntry, router *Router) *RollbackManager {
	r := &RollbackManager{
		logger:     logger,
		backends:   backendsFunc,
		router:     router,
		period:     rollbackPeriod,
		inflight:   make(map[string]*rollbackState),
		doneCh:     make(chan struct{}),
		shutdownCh: make(chan struct{}),
	}
	return r
}

// Start starts the rollback manager
func (m *RollbackManager) Start() {
	go m.run()
}

// Stop stops the running manager. This will wait for any in-flight
// rollbacks to complete.
func (m *RollbackManager) Stop() {
	m.shutdownLock.Lock()
	defer m.shutdownLock.Unlock()
	if !m.shutdown {
		m.shutdown = true
		close(m.shutdownCh)
		<-m.doneCh
	}
	m.inflightAll.Wait()
}

// run is a long running routine to periodically invoke rollback
func (m *RollbackManager) run() {
	m.logger.Info("rollback: starting rollback manager")
	tick := time.NewTicker(m.period)
	defer tick.Stop()
	defer close(m.doneCh)
	for {
		select {
		case <-tick.C:
			m.triggerRollbacks()

		case <-m.shutdownCh:
			m.logger.Info("rollback: stopping rollback manager")
			return
		}
	}
}

// triggerRollbacks is used to trigger the rollbacks across all the backends
func (m *RollbackManager) triggerRollbacks() {

	backends := m.backends()

	for _, e := range backends {
		path := e.Path
		if e.Table == credentialTableType {
			path = "auth/" + path
		}
		m.inflightLock.RLock()
		_, ok := m.inflight[path]
		m.inflightLock.RUnlock()
		if !ok {
			m.startRollback(path)
		}
	}
}

// startRollback is used to start an async rollback attempt.
// This must be called with the inflightLock held.
func (m *RollbackManager) startRollback(path string) *rollbackState {
	rs := &rollbackState{}
	rs.Add(1)
	m.inflightAll.Add(1)
	m.inflightLock.Lock()
	m.inflight[path] = rs
	m.inflightLock.Unlock()
	go m.attemptRollback(path, rs)
	return rs
}

// attemptRollback invokes a RollbackOperation for the given path
func (m *RollbackManager) attemptRollback(path string, rs *rollbackState) (err error) {
	defer metrics.MeasureSince([]string{"rollback", "attempt", strings.Replace(path, "/", "-", -1)}, time.Now())
	if m.logger.IsTrace() {
		m.logger.Trace("rollback: attempting rollback", "path", path)
	}

	defer func() {
		rs.lastError = err
		rs.Done()
		m.inflightAll.Done()
		m.inflightLock.Lock()
		delete(m.inflight, path)
		m.inflightLock.Unlock()
	}()

	// Invoke a RollbackOperation
	req := &logical.Request{
		Operation: logical.RollbackOperation,
		Path:      path,
	}
	_, err = m.router.Route(req)

	// If the error is an unsupported operation, then it doesn't
	// matter, the backend doesn't support it.
	if err == logical.ErrUnsupportedOperation {
		err = nil
	}
	if err != nil {
		m.logger.Error("rollback: error rolling back", "path", path, "error", err)
	}
	return
}

// Rollback is used to trigger an immediate rollback of the path,
// or to join an existing rollback operation if in flight.
func (m *RollbackManager) Rollback(path string) error {
	// Check for an existing attempt and start one if none
	m.inflightLock.RLock()
	rs, ok := m.inflight[path]
	m.inflightLock.RUnlock()
	if !ok {
		rs = m.startRollback(path)
	}

	// Wait for the attempt to finish
	rs.Wait()

	// Return the last error
	return rs.lastError
}

// The methods below are the hooks from core that are called pre/post seal.

// startRollback is used to start the rollback manager after unsealing
func (c *Core) startRollback() error {
	backendsFunc := func() []*MountEntry {
		ret := []*MountEntry{}
		c.mountsLock.RLock()
		defer c.mountsLock.RUnlock()
		// During teardown/setup after a leader change or unseal there could be
		// something racy here so make sure the table isn't nil
		if c.mounts != nil {
			for _, entry := range c.mounts.Entries {
				ret = append(ret, entry)
			}
		}
		c.authLock.RLock()
		defer c.authLock.RUnlock()
		// During teardown/setup after a leader change or unseal there could be
		// something racy here so make sure the table isn't nil
		if c.auth != nil {
			for _, entry := range c.auth.Entries {
				ret = append(ret, entry)
			}
		}
		return ret
	}
	c.rollback = NewRollbackManager(c.logger, backendsFunc, c.router)
	c.rollback.Start()
	return nil
}

// stopRollback is used to stop running the rollback manager before sealing
func (c *Core) stopRollback() error {
	if c.rollback != nil {
		c.rollback.Stop()
		c.rollback = nil
	}
	return nil
}
<|endoftext|>


<commit_before>package edit

import (
	"container/list"
	"strings"
	"unicode/utf8"
)

// listing implements a listing mode that supports the notion of selecting an
// entry and filtering entries.
type listing struct {
	typ      ModeType
	provider listingProvider
	selected int
	filter   string
	pagesize int
}

type listingProvider interface {
	Len() int
	Show(i, w int) styled
	Filter(filter string) int
	Accept(i int, ed *Editor)
	ModeTitle(int) string
}

type Placeholderer interface {
	Placeholder() string
}

func newListing(t ModeType, p listingProvider) listing {
	l := listing{t, p, 0, "", 0}
	l.changeFilter("")
	return l
}

func (l *listing) Mode() ModeType {
	return l.typ
}

func (l *listing) ModeLine(width int) *buffer {
	title := l.provider.ModeTitle(l.selected)
	// TODO keep it one line.
	b := newBuffer(width)
	b.writes(TrimWcWidth(title, width), styleForMode)
	b.writes(" ", "")
	b.writes(l.filter, styleForFilter)
	b.dot = b.cursor()
	return b
}

func (l *listing) List(width, maxHeight int) *buffer {
	n := l.provider.Len()
	b := newBuffer(width)
	if n == 0 {
		var ph string
		if pher, ok := l.provider.(Placeholderer); ok {
			ph = pher.Placeholder()
		} else {
			ph = "(no result)"
		}
		b.writes(TrimWcWidth(ph, width), "")
		return b
	}

	// Collect the entries to show. We start from the selected entry and extend
	// in both directions alternatingly. The entries are collected in a list.
	low := l.selected
	if low == -1 {
		low = 0
	}
	high := low
	height := 0
	var entries list.List
	getEntry := func(i int) (styled, int) {
		s := l.provider.Show(i, width)
		return s, strings.Count(s.text, "\n") + 1
	}
	// We start by extending high, so that the first entry to include is
	// l.selected.
	extendLow := false
	for height < maxHeight && !(low == 0 && high == n) {
		if (extendLow && low > 0) || high == n {
			low--

			s, h := getEntry(low)
			height += h
			if height > maxHeight {
				// Elide leading lines.
				lines := strings.Split(s.text, "\n")
				s.text = strings.Join(lines[height-maxHeight:], "\n")
				height = maxHeight
			}
			entries.PushFront(s)
		} else {
			s, h := getEntry(high)
			height += h
			if height > maxHeight {
				// Elide trailing lines.
				lines := strings.Split(s.text, "\n")
				s.text = strings.Join(lines[:len(lines)-(height-maxHeight)], "\n")
				height = maxHeight
			}
			entries.PushBack(s)

			high++
		}
		extendLow = !extendLow
	}

	l.pagesize = high - low

	var scrollbar *buffer
	if low > 0 || high < n-1 {
		scrollbar = renderScrollbar(n, low, high, height)
		width--
	}

	p := entries.Front()
	for i := low; i < high; i++ {
		if i > low {
			b.newline()
		}
		s := p.Value.(styled)
		if i == l.selected {
			s.addStyle(styleForSelected)
		}
		b.writes(s.text, s.style)
		p = p.Next()
	}
	if scrollbar != nil {
		b.extendHorizontal(scrollbar, width)
	}
	return b
}

func writeHorizontalScrollbar(b *buffer, n, low, high, width int) {
	slow, shigh := findScrollInterval(n, low, high, width)
	for i := 0; i < width; i++ {
		if slow <= i && i < shigh {
			b.write(' ', styleForScrollBarThumb)
		} else {
			b.write('━', styleForScrollBarArea)
		}
	}
}

func renderScrollbar(n, low, high, height int) *buffer {
	slow, shigh := findScrollInterval(n, low, high, height)
	// Logger.Printf("low = %d, high = %d, n = %d, slow = %d, shigh = %d", low, high, n, slow, shigh)
	b := newBuffer(1)
	for i := 0; i < height; i++ {
		if i > 0 {
			b.newline()
		}
		if slow <= i && i < shigh {
			b.write(' ', styleForScrollBarThumb)
		} else {
			b.write('│', styleForScrollBarArea)
		}
	}
	return b
}

func findScrollInterval(n, low, high, height int) (int, int) {
	f := func(i int) int {
		return int(float64(i)/float64(n)*float64(height) + 0.5)
	}
	scrollLow, scrollHigh := f(low), f(high)
	if scrollLow == scrollHigh {
		if scrollHigh == high {
			scrollLow--
		} else {
			scrollHigh++
		}
	}
	return scrollLow, scrollHigh
}

func (l *listing) changeFilter(newfilter string) {
	l.filter = newfilter
	l.selected = l.provider.Filter(newfilter)
}

func (l *listing) backspace() bool {
	_, size := utf8.DecodeLastRuneInString(l.filter)
	if size > 0 {
		l.changeFilter(l.filter[:len(l.filter)-size])
		return true
	}
	return false
}

func (l *listing) up(cycle bool) {
	n := l.provider.Len()
	if n == 0 {
		return
	}
	l.selected--
	if l.selected == -1 {
		if cycle {
			l.selected += n
		} else {
			l.selected++
		}
	}
}

func (l *listing) pageUp() {
	n := l.provider.Len()
	if n == 0 {
		return
	}
	l.selected -= l.pagesize
	if l.selected < 0 {
		l.selected = 0
	}
}

func (l *listing) down(cycle bool) {
	n := l.provider.Len()
	if n == 0 {
		return
	}
	l.selected++
	if l.selected == n {
		if cycle {
			l.selected -= n
		} else {
			l.selected--
		}
	}
}

func (l *listing) pageDown() {
	n := l.provider.Len()
	if n == 0 {
		return
	}
	l.selected += l.pagesize
	if l.selected >= n {
		l.selected = n - 1
	}
}

func (l *listing) accept(ed *Editor) {
	if l.selected >= 0 {
		l.provider.Accept(l.selected, ed)
	}
}

func (l *listing) handleFilterKey(k Key) bool {
	if likeChar(k) {
		l.changeFilter(l.filter + string(k.Rune))
		return true
	}
	return false
}

func (l *listing) defaultBinding(ed *Editor) {
	if !l.handleFilterKey(ed.lastKey) {
		startInsert(ed)
		ed.nextAction = action{typ: reprocessKey}
	}
}

func addListingBuiltins(prefix string, l func(*Editor) *listing) {
	add := func(name string, f func(*Editor)) {
		builtins = append(builtins, Builtin{prefix + name, f})
	}
	add("up", func(ed *Editor) { l(ed).up(false) })
	add("up-cycle", func(ed *Editor) { l(ed).up(true) })
	add("page-up", func(ed *Editor) { l(ed).pageUp() })
	add("down", func(ed *Editor) { l(ed).down(false) })
	add("down-cycle", func(ed *Editor) { l(ed).down(true) })
	add("page-down", func(ed *Editor) { l(ed).pageDown() })
	add("backspace", func(ed *Editor) { l(ed).backspace() })
	add("accept", func(ed *Editor) { l(ed).accept(ed) })
	add("default", func(ed *Editor) { l(ed).defaultBinding(ed) })
}

func addListingDefaultBindings(prefix string, m ModeType) {
	add := func(k Key, name string) {
		if _, ok := defaultBindings[m][k]; !ok {
			defaultBindings[m][k] = prefix + name
		}
	}
	add(Key{Up, 0}, "up")
	add(Key{PageUp, 0}, "page-up")
	add(Key{Down, 0}, "down")
	add(Key{PageDown, 0}, "page-down")
	add(Key{Tab, 0}, "down-cycle")
	add(Key{Backspace, 0}, "backspace")
	add(Key{Enter, 0}, "accept")
	add(Default, "default")
	defaultBindings[m][Key{'[', Ctrl}] = "start-insert"
}
<commit_msg>Fix the condition of showing scrollbar in nav mode.<commit_after>package edit

import (
	"container/list"
	"strings"
	"unicode/utf8"
)

// listing implements a listing mode that supports the notion of selecting an
// entry and filtering entries.
type listing struct {
	typ      ModeType
	provider listingProvider
	selected int
	filter   string
	pagesize int
}

type listingProvider interface {
	Len() int
	Show(i, w int) styled
	Filter(filter string) int
	Accept(i int, ed *Editor)
	ModeTitle(int) string
}

type Placeholderer interface {
	Placeholder() string
}

func newListing(t ModeType, p listingProvider) listing {
	l := listing{t, p, 0, "", 0}
	l.changeFilter("")
	return l
}

func (l *listing) Mode() ModeType {
	return l.typ
}

func (l *listing) ModeLine(width int) *buffer {
	title := l.provider.ModeTitle(l.selected)
	// TODO keep it one line.
	b := newBuffer(width)
	b.writes(TrimWcWidth(title, width), styleForMode)
	b.writes(" ", "")
	b.writes(l.filter, styleForFilter)
	b.dot = b.cursor()
	return b
}

func (l *listing) List(width, maxHeight int) *buffer {
	n := l.provider.Len()
	b := newBuffer(width)
	if n == 0 {
		var ph string
		if pher, ok := l.provider.(Placeholderer); ok {
			ph = pher.Placeholder()
		} else {
			ph = "(no result)"
		}
		b.writes(TrimWcWidth(ph, width), "")
		return b
	}

	// Collect the entries to show. We start from the selected entry and extend
	// in both directions alternatingly. The entries are collected in a list.
	low := l.selected
	if low == -1 {
		low = 0
	}
	high := low
	height := 0
	var entries list.List
	getEntry := func(i int) (styled, int) {
		s := l.provider.Show(i, width)
		return s, strings.Count(s.text, "\n") + 1
	}
	// We start by extending high, so that the first entry to include is
	// l.selected.
	extendLow := false
	for height < maxHeight && !(low == 0 && high == n) {
		if (extendLow && low > 0) || high == n {
			low--

			s, h := getEntry(low)
			height += h
			if height > maxHeight {
				// Elide leading lines.
				lines := strings.Split(s.text, "\n")
				s.text = strings.Join(lines[height-maxHeight:], "\n")
				height = maxHeight
			}
			entries.PushFront(s)
		} else {
			s, h := getEntry(high)
			height += h
			if height > maxHeight {
				// Elide trailing lines.
				lines := strings.Split(s.text, "\n")
				s.text = strings.Join(lines[:len(lines)-(height-maxHeight)], "\n")
				height = maxHeight
			}
			entries.PushBack(s)

			high++
		}
		extendLow = !extendLow
	}

	l.pagesize = high - low

	var scrollbar *buffer
	if low > 0 || high < n {
		scrollbar = renderScrollbar(n, low, high, height)
		width--
	}

	p := entries.Front()
	for i := low; i < high; i++ {
		if i > low {
			b.newline()
		}
		s := p.Value.(styled)
		if i == l.selected {
			s.addStyle(styleForSelected)
		}
		b.writes(s.text, s.style)
		p = p.Next()
	}
	if scrollbar != nil {
		b.extendHorizontal(scrollbar, width)
	}
	return b
}

func writeHorizontalScrollbar(b *buffer, n, low, high, width int) {
	slow, shigh := findScrollInterval(n, low, high, width)
	for i := 0; i < width; i++ {
		if slow <= i && i < shigh {
			b.write(' ', styleForScrollBarThumb)
		} else {
			b.write('━', styleForScrollBarArea)
		}
	}
}

func renderScrollbar(n, low, high, height int) *buffer {
	slow, shigh := findScrollInterval(n, low, high, height)
	// Logger.Printf("low = %d, high = %d, n = %d, slow = %d, shigh = %d", low, high, n, slow, shigh)
	b := newBuffer(1)
	for i := 0; i < height; i++ {
		if i > 0 {
			b.newline()
		}
		if slow <= i && i < shigh {
			b.write(' ', styleForScrollBarThumb)
		} else {
			b.write('│', styleForScrollBarArea)
		}
	}
	return b
}

func findScrollInterval(n, low, high, height int) (int, int) {
	f := func(i int) int {
		return int(float64(i)/float64(n)*float64(height) + 0.5)
	}
	scrollLow, scrollHigh := f(low), f(high)
	if scrollLow == scrollHigh {
		if scrollHigh == high {
			scrollLow--
		} else {
			scrollHigh++
		}
	}
	return scrollLow, scrollHigh
}

func (l *listing) changeFilter(newfilter string) {
	l.filter = newfilter
	l.selected = l.provider.Filter(newfilter)
}

func (l *listing) backspace() bool {
	_, size := utf8.DecodeLastRuneInString(l.filter)
	if size > 0 {
		l.changeFilter(l.filter[:len(l.filter)-size])
		return true
	}
	return false
}

func (l *listing) up(cycle bool) {
	n := l.provider.Len()
	if n == 0 {
		return
	}
	l.selected--
	if l.selected == -1 {
		if cycle {
			l.selected += n
		} else {
			l.selected++
		}
	}
}

func (l *listing) pageUp() {
	n := l.provider.Len()
	if n == 0 {
		return
	}
	l.selected -= l.pagesize
	if l.selected < 0 {
		l.selected = 0
	}
}

func (l *listing) down(cycle bool) {
	n := l.provider.Len()
	if n == 0 {
		return
	}
	l.selected++
	if l.selected == n {
		if cycle {
			l.selected -= n
		} else {
			l.selected--
		}
	}
}

func (l *listing) pageDown() {
	n := l.provider.Len()
	if n == 0 {
		return
	}
	l.selected += l.pagesize
	if l.selected >= n {
		l.selected = n - 1
	}
}

func (l *listing) accept(ed *Editor) {
	if l.selected >= 0 {
		l.provider.Accept(l.selected, ed)
	}
}

func (l *listing) handleFilterKey(k Key) bool {
	if likeChar(k) {
		l.changeFilter(l.filter + string(k.Rune))
		return true
	}
	return false
}

func (l *listing) defaultBinding(ed *Editor) {
	if !l.handleFilterKey(ed.lastKey) {
		startInsert(ed)
		ed.nextAction = action{typ: reprocessKey}
	}
}

func addListingBuiltins(prefix string, l func(*Editor) *listing) {
	add := func(name string, f func(*Editor)) {
		builtins = append(builtins, Builtin{prefix + name, f})
	}
	add("up", func(ed *Editor) { l(ed).up(false) })
	add("up-cycle", func(ed *Editor) { l(ed).up(true) })
	add("page-up", func(ed *Editor) { l(ed).pageUp() })
	add("down", func(ed *Editor) { l(ed).down(false) })
	add("down-cycle", func(ed *Editor) { l(ed).down(true) })
	add("page-down", func(ed *Editor) { l(ed).pageDown() })
	add("backspace", func(ed *Editor) { l(ed).backspace() })
	add("accept", func(ed *Editor) { l(ed).accept(ed) })
	add("default", func(ed *Editor) { l(ed).defaultBinding(ed) })
}

func addListingDefaultBindings(prefix string, m ModeType) {
	add := func(k Key, name string) {
		if _, ok := defaultBindings[m][k]; !ok {
			defaultBindings[m][k] = prefix + name
		}
	}
	add(Key{Up, 0}, "up")
	add(Key{PageUp, 0}, "page-up")
	add(Key{Down, 0}, "down")
	add(Key{PageDown, 0}, "page-down")
	add(Key{Tab, 0}, "down-cycle")
	add(Key{Backspace, 0}, "backspace")
	add(Key{Enter, 0}, "accept")
	add(Default, "default")
	defaultBindings[m][Key{'[', Ctrl}] = "start-insert"
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tool

import (
	"io"
	"os"

	"v.io/jiri/gerrit"
	"v.io/jiri/gitutil"
	"v.io/jiri/jenkins"
	"v.io/jiri/runutil"
	"v.io/x/lib/cmdline"
	"v.io/x/lib/timing"
)

// Context represents an execution context of a tool command
// invocation. Its purpose is to enable sharing of state throughout
// the lifetime of a command invocation.
type Context struct {
	opts  ContextOpts
	run   *runutil.Run
	seq   *runutil.Sequence
	start *runutil.Start
}

// ContextOpts records the context options.
type ContextOpts struct {
	Color    *bool
	DryRun   *bool
	Env      map[string]string
	Manifest *string
	Stdin    io.Reader
	Stdout   io.Writer
	Stderr   io.Writer
	Verbose  *bool
	Timer    timing.Timer
}

// newContextOpts is the ContextOpts factory.
func newContextOpts() *ContextOpts {
	return &ContextOpts{
		Color:    &ColorFlag,
		DryRun:   &DryRunFlag,
		Env:      map[string]string{},
		Manifest: &ManifestFlag,
		Stdin:    os.Stdin,
		Stdout:   os.Stdout,
		Stderr:   os.Stderr,
		Verbose:  &VerboseFlag,
		Timer:    nil,
	}
}

// initOpts initializes all unset options to the given defaults.
func initOpts(defaultOpts, opts *ContextOpts) {
	if opts.Color == nil {
		opts.Color = defaultOpts.Color
	}
	if opts.DryRun == nil {
		opts.DryRun = defaultOpts.DryRun
	}
	if opts.Env == nil {
		opts.Env = defaultOpts.Env
	}
	if opts.Manifest == nil {
		opts.Manifest = defaultOpts.Manifest
	}
	if opts.Stdin == nil {
		opts.Stdin = defaultOpts.Stdin
	}
	if opts.Stdout == nil {
		opts.Stdout = defaultOpts.Stdout
	}
	if opts.Stderr == nil {
		opts.Stderr = defaultOpts.Stderr
	}
	if opts.Verbose == nil {
		opts.Verbose = defaultOpts.Verbose
	}
	if opts.Timer == nil {
		opts.Timer = defaultOpts.Timer
	}
}

// NewContext is the Context factory.
func NewContext(opts ContextOpts) *Context {
	initOpts(newContextOpts(), &opts)
	run := runutil.NewRun(opts.Env, opts.Stdin, opts.Stdout, opts.Stderr, *opts.Color, *opts.DryRun, *opts.Verbose)
	seq := runutil.NewSequence(opts.Env, opts.Stdin, opts.Stdout, opts.Stderr, *opts.Color, *opts.DryRun, *opts.Verbose)
	start := runutil.NewStart(opts.Env, opts.Stdin, opts.Stdout, opts.Stderr, *opts.Color, *opts.DryRun, *opts.Verbose)
	return &Context{
		opts:  opts,
		run:   run,
		seq:   seq,
		start: start,
	}
}

// NewContextFromEnv returns a new context instance based on the given
// cmdline environment.
func NewContextFromEnv(env *cmdline.Env) *Context {
	opts := ContextOpts{}
	initOpts(newContextOpts(), &opts)
	opts.Stdin = env.Stdin
	opts.Stdout = env.Stdout
	opts.Stderr = env.Stderr
	opts.Timer = env.Timer
	return NewContext(opts)
}

// NewDefaultContext returns a new default context.
func NewDefaultContext() *Context {
	return NewContext(ContextOpts{})
}

// Clone creates a clone of the given context, overriding select
// settings using the given options.
func (ctx Context) Clone(opts ContextOpts) *Context {
	initOpts(&ctx.opts, &opts)
	return NewContext(opts)
}

// Color returns the color setting of the context.
func (ctx Context) Color() bool {
	return *ctx.opts.Color
}

// DryRun returns the dry run setting of the context.
func (ctx Context) DryRun() bool {
	return *ctx.opts.DryRun
}

// Env returns the environment of the context.
func (ctx Context) Env() map[string]string {
	return ctx.opts.Env
}

// Gerrit returns the Gerrit instance of the context.
func (ctx Context) Gerrit(host string) *gerrit.Gerrit {
	return gerrit.New(ctx.run, host)
}

type gitOpt interface {
	gitOpt()
}
type AuthorDateOpt string
type CommitterDateOpt string
type RootDirOpt string

func (AuthorDateOpt) gitOpt()    {}
func (CommitterDateOpt) gitOpt() {}
func (RootDirOpt) gitOpt()       {}

// Git returns a new git instance.
//
// This method accepts one optional argument: the repository root to
// use for commands issued by the returned instance. If not specified,
// commands will use the current directory as the repository root.
func (ctx Context) Git(opts ...gitOpt) *gitutil.Git {
	rootDir := ""
	gitCtx := &ctx
	for _, opt := range opts {
		switch typedOpt := opt.(type) {
		case AuthorDateOpt:
			opts := ContextOpts{}
			opts.Env = ctx.Env()
			opts.Env["GIT_AUTHOR_DATE"] = string(typedOpt)
			gitCtx = ctx.Clone(opts)
		case CommitterDateOpt:
			opts := ContextOpts{}
			opts.Env = ctx.Env()
			opts.Env["GIT_COMMITTER_DATE"] = string(typedOpt)
			gitCtx = ctx.Clone(opts)
		case RootDirOpt:
			rootDir = string(typedOpt)
		}
	}
	return gitutil.New(gitCtx.run, rootDir)
}

// Jenkins returns a new Jenkins instance that can be used to
// communicate with a Jenkins server running at the given host.
func (ctx Context) Jenkins(host string) (*jenkins.Jenkins, error) {
	return jenkins.New(host)
}

// Manifest returns the manifest of the context.
func (ctx Context) Manifest() string {
	return *ctx.opts.Manifest
}

// Run returns the run instance of the context.
func (ctx Context) Run() *runutil.Run {
	return ctx.run
}

// Seq returns the sequence instance of the context.
func (ctx Context) Seq() *runutil.Sequence {
	return ctx.seq
}

// Start returns the start instance of the context.
func (ctx Context) Start() *runutil.Start {
	return ctx.start
}

// Stdin returns the standard input of the context.
func (ctx Context) Stdin() io.Reader {
	return ctx.opts.Stdin
}

// Stdout returns the standard output of the context.
func (ctx Context) Stdout() io.Writer {
	return ctx.opts.Stdout
}

// Stderr returns the standard error output of the context.
func (ctx Context) Stderr() io.Writer {
	return ctx.opts.Stderr
}

// Verbose returns the verbosity setting of the context.
func (ctx Context) Verbose() bool {
	return *ctx.opts.Verbose
}

// Timer returns the timer associated with the context, which may be nil.
func (ctx Context) Timer() timing.Timer {
	return ctx.opts.Timer
}

// TimerPush calls ctx.Timer().Push(name), only if the Timer is non-nil.
func (ctx Context) TimerPush(name string) {
	if ctx.opts.Timer != nil {
		ctx.opts.Timer.Push(name)
	}
}

// TimerPop calls ctx.Timer().Pop(), only if the Timer is non-nil.
func (ctx Context) TimerPop() {
	if ctx.opts.Timer != nil {
		ctx.opts.Timer.Pop()
	}
}
<commit_msg>jiri: Update v.io/x/lib/timing to a single implementation<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tool

import (
	"io"
	"os"

	"v.io/jiri/gerrit"
	"v.io/jiri/gitutil"
	"v.io/jiri/jenkins"
	"v.io/jiri/runutil"
	"v.io/x/lib/cmdline"
	"v.io/x/lib/timing"
)

// Context represents an execution context of a tool command
// invocation. Its purpose is to enable sharing of state throughout
// the lifetime of a command invocation.
type Context struct {
	opts  ContextOpts
	run   *runutil.Run
	seq   *runutil.Sequence
	start *runutil.Start
}

// ContextOpts records the context options.
type ContextOpts struct {
	Color    *bool
	DryRun   *bool
	Env      map[string]string
	Manifest *string
	Stdin    io.Reader
	Stdout   io.Writer
	Stderr   io.Writer
	Verbose  *bool
	Timer    *timing.Timer
}

// newContextOpts is the ContextOpts factory.
func newContextOpts() *ContextOpts {
	return &ContextOpts{
		Color:    &ColorFlag,
		DryRun:   &DryRunFlag,
		Env:      map[string]string{},
		Manifest: &ManifestFlag,
		Stdin:    os.Stdin,
		Stdout:   os.Stdout,
		Stderr:   os.Stderr,
		Verbose:  &VerboseFlag,
		Timer:    nil,
	}
}

// initOpts initializes all unset options to the given defaults.
func initOpts(defaultOpts, opts *ContextOpts) {
	if opts.Color == nil {
		opts.Color = defaultOpts.Color
	}
	if opts.DryRun == nil {
		opts.DryRun = defaultOpts.DryRun
	}
	if opts.Env == nil {
		opts.Env = defaultOpts.Env
	}
	if opts.Manifest == nil {
		opts.Manifest = defaultOpts.Manifest
	}
	if opts.Stdin == nil {
		opts.Stdin = defaultOpts.Stdin
	}
	if opts.Stdout == nil {
		opts.Stdout = defaultOpts.Stdout
	}
	if opts.Stderr == nil {
		opts.Stderr = defaultOpts.Stderr
	}
	if opts.Verbose == nil {
		opts.Verbose = defaultOpts.Verbose
	}
	if opts.Timer == nil {
		opts.Timer = defaultOpts.Timer
	}
}

// NewContext is the Context factory.
func NewContext(opts ContextOpts) *Context {
	initOpts(newContextOpts(), &opts)
	run := runutil.NewRun(opts.Env, opts.Stdin, opts.Stdout, opts.Stderr, *opts.Color, *opts.DryRun, *opts.Verbose)
	seq := runutil.NewSequence(opts.Env, opts.Stdin, opts.Stdout, opts.Stderr, *opts.Color, *opts.DryRun, *opts.Verbose)
	start := runutil.NewStart(opts.Env, opts.Stdin, opts.Stdout, opts.Stderr, *opts.Color, *opts.DryRun, *opts.Verbose)
	return &Context{
		opts:  opts,
		run:   run,
		seq:   seq,
		start: start,
	}
}

// NewContextFromEnv returns a new context instance based on the given
// cmdline environment.
func NewContextFromEnv(env *cmdline.Env) *Context {
	opts := ContextOpts{}
	initOpts(newContextOpts(), &opts)
	opts.Stdin = env.Stdin
	opts.Stdout = env.Stdout
	opts.Stderr = env.Stderr
	opts.Timer = env.Timer
	return NewContext(opts)
}

// NewDefaultContext returns a new default context.
func NewDefaultContext() *Context {
	return NewContext(ContextOpts{})
}

// Clone creates a clone of the given context, overriding select
// settings using the given options.
func (ctx Context) Clone(opts ContextOpts) *Context {
	initOpts(&ctx.opts, &opts)
	return NewContext(opts)
}

// Color returns the color setting of the context.
func (ctx Context) Color() bool {
	return *ctx.opts.Color
}

// DryRun returns the dry run setting of the context.
func (ctx Context) DryRun() bool {
	return *ctx.opts.DryRun
}

// Env returns the environment of the context.
func (ctx Context) Env() map[string]string {
	return ctx.opts.Env
}

// Gerrit returns the Gerrit instance of the context.
func (ctx Context) Gerrit(host string) *gerrit.Gerrit {
	return gerrit.New(ctx.run, host)
}

type gitOpt interface {
	gitOpt()
}
type AuthorDateOpt string
type CommitterDateOpt string
type RootDirOpt string

func (AuthorDateOpt) gitOpt()    {}
func (CommitterDateOpt) gitOpt() {}
func (RootDirOpt) gitOpt()       {}

// Git returns a new git instance.
//
// This method accepts one optional argument: the repository root to
// use for commands issued by the returned instance. If not specified,
// commands will use the current directory as the repository root.
func (ctx Context) Git(opts ...gitOpt) *gitutil.Git {
	rootDir := ""
	gitCtx := &ctx
	for _, opt := range opts {
		switch typedOpt := opt.(type) {
		case AuthorDateOpt:
			opts := ContextOpts{}
			opts.Env = ctx.Env()
			opts.Env["GIT_AUTHOR_DATE"] = string(typedOpt)
			gitCtx = ctx.Clone(opts)
		case CommitterDateOpt:
			opts := ContextOpts{}
			opts.Env = ctx.Env()
			opts.Env["GIT_COMMITTER_DATE"] = string(typedOpt)
			gitCtx = ctx.Clone(opts)
		case RootDirOpt:
			rootDir = string(typedOpt)
		}
	}
	return gitutil.New(gitCtx.run, rootDir)
}

// Jenkins returns a new Jenkins instance that can be used to
// communicate with a Jenkins server running at the given host.
func (ctx Context) Jenkins(host string) (*jenkins.Jenkins, error) {
	return jenkins.New(host)
}

// Manifest returns the manifest of the context.
func (ctx Context) Manifest() string {
	return *ctx.opts.Manifest
}

// Run returns the run instance of the context.
func (ctx Context) Run() *runutil.Run {
	return ctx.run
}

// Seq returns the sequence instance of the context.
func (ctx Context) Seq() *runutil.Sequence {
	return ctx.seq
}

// Start returns the start instance of the context.
func (ctx Context) Start() *runutil.Start {
	return ctx.start
}

// Stdin returns the standard input of the context.
func (ctx Context) Stdin() io.Reader {
	return ctx.opts.Stdin
}

// Stdout returns the standard output of the context.
func (ctx Context) Stdout() io.Writer {
	return ctx.opts.Stdout
}

// Stderr returns the standard error output of the context.
func (ctx Context) Stderr() io.Writer {
	return ctx.opts.Stderr
}

// Verbose returns the verbosity setting of the context.
func (ctx Context) Verbose() bool {
	return *ctx.opts.Verbose
}

// Timer returns the timer associated with the context, which may be nil.
func (ctx Context) Timer() *timing.Timer {
	return ctx.opts.Timer
}

// TimerPush calls ctx.Timer().Push(name), only if the Timer is non-nil.
func (ctx Context) TimerPush(name string) {
	if ctx.opts.Timer != nil {
		ctx.opts.Timer.Push(name)
	}
}

// TimerPop calls ctx.Timer().Pop(), only if the Timer is non-nil.
func (ctx Context) TimerPop() {
	if ctx.opts.Timer != nil {
		ctx.opts.Timer.Pop()
	}
}
<|endoftext|>