<commit_before>package main

import (
	"bytes"
	"context"
	"crypto/rand"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/google/go-github/v24/github"
	"github.com/justinas/nosurf"
	"github.com/reviewdog/reviewdog/doghouse/server"
	"github.com/reviewdog/reviewdog/doghouse/server/cookieman"
	"github.com/reviewdog/reviewdog/doghouse/server/storage"
	"golang.org/x/oauth2"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
)

type GitHubHandler struct {
	clientID     string
	clientSecret string

	tokenStore     *cookieman.CookieStore
	redirURLStore  *cookieman.CookieStore // Redirect URL after login.
	authStateStore *cookieman.CookieStore

	repoTokenStore storage.GitHubRepositoryTokenStore

	privateKey    []byte
	integrationID int
}

func NewGitHubHandler(clientID, clientSecret string, c *cookieman.CookieMan, privateKey []byte, integrationID int) *GitHubHandler {
	return &GitHubHandler{
		clientID:       clientID,
		clientSecret:   clientSecret,
		tokenStore:     c.NewCookieStore("github-token", nil),
		redirURLStore:  c.NewCookieStore("github-redirect-url", nil),
		authStateStore: c.NewCookieStore("github-auth-state", nil),
		repoTokenStore: &storage.GitHubRepoTokenDatastore{},
		integrationID:  integrationID,
		privateKey:     privateKey,
	}
}

type ghTopTmplData struct {
	Title string
	User  tmplUser

	App struct {
		Name    string
		HTMLURL string
	}

	Installations []tmplInstallation
}

type tmplInstallation struct {
	Account        string
	AccountHTMLURL string
	AccountIconURL string
	HTMLURL        string
}

type ghRepoTmplData struct {
	Title     string
	Token     string
	User      tmplUser
	Repo      tmplRepo
	CSRFToken string
}

type tmplUser struct {
	Name      string
	IconURL   string
	GitHubURL string
}

type tmplRepo struct {
	Owner     string
	Name      string
	GitHubURL string
}

func (g *GitHubHandler) buildGithubAuthURL(r *http.Request, state string) string {
	redirURL := *r.URL
	redirURL.Path = "/gh/_auth/callback"
	redirURL.RawQuery = ""
	redirURL.Fragment = ""
	const baseURL = "https://github.com/login/oauth/authorize"
	authURL := fmt.Sprintf("%s?client_id=%s&redirect_url=%s&state=%s",
		baseURL, g.clientID, redirURL.RequestURI(), state)
	return authURL
}

func (g *GitHubHandler) HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	code, state := r.FormValue("code"), r.FormValue("state")
	if code == "" || state == "" {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, "code and state param is empty")
		return
	}

	// Verify state.
	cookieState, err := g.authStateStore.Get(r)
	if err != nil || state != string(cookieState) {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, "state is invalid")
		return
	}
	g.authStateStore.Clear(w)

	// Request and save access token.
	token, err := g.requestAccessToken(ctx, code, state)
	if err != nil {
		log.Errorf(ctx, "failed to get access token: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, "failed to get GitHub access token")
		return
	}
	g.tokenStore.Set(w, []byte(token))

	// Redirect.
	redirURL := "/gh/"
	if r, _ := g.redirURLStore.Get(r); err == nil {
		redirURL = string(r)
		g.redirURLStore.Clear(w)
	}
	http.Redirect(w, r, redirURL, http.StatusFound)
}

func (g *GitHubHandler) HandleLogout(w http.ResponseWriter, r *http.Request) {
	g.tokenStore.Clear(w)
	http.Redirect(w, r, "/", http.StatusFound)
}

func (g *GitHubHandler) LogInHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := appengine.NewContext(r)
		if g.isLoggedIn(r) {
			h.ServeHTTP(w, r)
			return
		}
		// Not logged in yet.
		log.Debugf(ctx, "Not logged in yet.")
		state := securerandom(16)
		g.redirURLStore.Set(w, []byte(r.URL.RequestURI()))
		g.authStateStore.Set(w, []byte(state))
		http.Redirect(w, r, g.buildGithubAuthURL(r, state), http.StatusFound)
	})
}

func (g *GitHubHandler) isLoggedIn(r *http.Request) bool {
	ok, _ := g.token(r)
	return ok
}

func securerandom(n int) string {
	b := make([]byte, n)
	io.ReadFull(rand.Reader, b[:])
	return fmt.Sprintf("%x", b)
}

// https://developer.github.com/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps/#2-users-are-redirected-back-to-your-site-by-github
// POST https://github.com/login/oauth/access_token
func (g *GitHubHandler) requestAccessToken(ctx context.Context, code, state string) (string, error) {
	const u = "https://github.com/login/oauth/access_token"
	cli := urlfetch.Client(ctx)
	data := url.Values{}
	data.Set("client_id", g.clientID)
	data.Set("client_secret", g.clientSecret)
	data.Set("code", code)
	data.Set("state", state)

	req, err := http.NewRequest("POST", u, strings.NewReader(data.Encode()))
	if err != nil {
		return "", fmt.Errorf("failed to create request: %v", err)
	}
	req = req.WithContext(ctx)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Accept", "application/vnd.github.machine-man-preview+json")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	res, err := cli.Do(req)
	if err != nil {
		return "", fmt.Errorf("failed to request access token: %v", err)
	}
	defer res.Body.Close()

	b, _ := ioutil.ReadAll(res.Body)

	var token struct {
		AccessToken string `json:"access_token"`
	}
	if err := json.NewDecoder(bytes.NewReader(b)).Decode(&token); err != nil {
		return "", fmt.Errorf("failed to decode response: %v", err)
	}

	if token.AccessToken == "" {
		log.Errorf(ctx, "response doesn't contain token (resopnse: %s)", b)
		return "", errors.New("response doesn't contain GitHub access token")
	}

	return token.AccessToken, nil
}

func (g *GitHubHandler) token(r *http.Request) (bool, string) {
	b, err := g.tokenStore.Get(r)
	if err != nil {
		return false, ""
	}
	return true, string(b)
}

func (g *GitHubHandler) HandleGitHubTop(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	ok, token := g.token(r)
	if !ok {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}

	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: token},
	)
	ghcli := github.NewClient(NewAuthClient(ctx, urlfetch.Client(ctx).Transport, ts))

	// /gh/{owner}/{repo}
	paths := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	switch len(paths) {
	case 1:
		g.handleTop(ctx, ghcli, w, r)
	case 3:
		g.handleRepo(ctx, ghcli, w, r, paths[1], paths[2])
	default:
		notfound(w)
	}
}

func notfound(w http.ResponseWriter) {
	w.WriteHeader(http.StatusNotFound)
	fmt.Fprintln(w, "404 Not Found")
}

func (g *GitHubHandler) getUserOrBadRequest(ctx context.Context, ghcli *github.Client, w http.ResponseWriter, r *http.Request) (bool, *github.User) {
	u, _, err := ghcli.Users.Get(ctx, "")
	if err != nil {
		// Token seeims invalid. Clear it before returning BadRequest status.
		g.tokenStore.Clear(w)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "Cannot get GitHub authenticated user. Please reload the page again.")
		return false, nil
	}
	return true, u
}

func (g *GitHubHandler) handleTop(ctx context.Context, ghcli *github.Client, w http.ResponseWriter, r *http.Request) {
	ok, u := g.getUserOrBadRequest(ctx, ghcli, w, r)
	if !ok {
		return
	}

	data := &ghTopTmplData{
		Title: "GitHub - reviewdog",
		User: tmplUser{
			Name:      u.GetName(),
			IconURL:   u.GetAvatarURL(),
			GitHubURL: u.GetHTMLURL(),
		},
	}

	ghAppCli, err := server.NewGitHubClient(ctx, &server.NewGitHubClientOption{
		Client:        urlfetch.Client(ctx),
		IntegrationID: g.integrationID,
		PrivateKey:    g.privateKey,
	})
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, err)
		return
	}
	app, _, err := ghAppCli.Apps.Get(ctx, "")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, err)
		return
	}
	data.App.Name = app.GetName()
	data.App.HTMLURL = app.GetHTMLURL()

	installations, _, err := ghcli.Apps.ListUserInstallations(ctx, nil)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, err)
		return
	}
	for _, inst := range installations {
		data.Installations = append(data.Installations, tmplInstallation{
			Account:        inst.GetAccount().GetLogin(),
			AccountHTMLURL: inst.GetAccount().GetHTMLURL(),
			AccountIconURL: inst.GetAccount().GetAvatarURL(),
			HTMLURL:        inst.GetHTMLURL(),
		})
	}

	ghTopTmpl.ExecuteTemplate(w, "base", data)
}

func (g *GitHubHandler) handleRepo(ctx context.Context, ghcli *github.Client, w http.ResponseWriter, r *http.Request, owner, repoName string) {
	repo, _, err := ghcli.Repositories.Get(ctx, owner, repoName)
	if err != nil {
		if err, ok := err.(*github.ErrorResponse); ok {
			if err.Response.StatusCode == http.StatusNotFound {
				notfound(w)
				return
			}
		}
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "failed to get repo: %#v", err)
		return
	}

	if !repo.GetPermissions()["push"] {
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprintf(w, "You don't have write permission for %s.", repo.GetHTMLURL())
		return
	}

	ok, u := g.getUserOrBadRequest(ctx, ghcli, w, r)
	if !ok {
		return
	}

	// Regenerate Token.
	if r.Method == "POST" {
		if _, err := server.RegenerateRepoToken(ctx, g.repoTokenStore, repo.Owner.GetLogin(), repo.GetName(), repo.GetID()); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "failed to update repository token: %v", err)
			return
		}
		http.Redirect(w, r, r.URL.String(), http.StatusFound)
	}

	repoToken, err := server.GetOrGenerateRepoToken(ctx, g.repoTokenStore, repo.Owner.GetLogin(), repo.GetName(), repo.GetID())
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "failed to get repository token for %s.", repo.GetHTMLURL())
		return
	}

	ghRepoTmpl.ExecuteTemplate(w, "base", &ghRepoTmplData{
		Title: fmt.Sprintf("%s/%s - reviewdog", repo.Owner.GetLogin(), repo.GetName()),
		Token: repoToken,
		User: tmplUser{
			Name:      u.GetName(),
			IconURL:   u.GetAvatarURL(),
			GitHubURL: u.GetHTMLURL(),
		},
		Repo: tmplRepo{
			Owner:     repo.Owner.GetLogin(),
			Name:      repo.GetName(),
			GitHubURL: repo.GetHTMLURL(),
		},
		CSRFToken: nosurf.Token(r),
	})
}

func NewAuthClient(ctx context.Context, base http.RoundTripper, token oauth2.TokenSource) *http.Client {
	tc := oauth2.NewClient(ctx, token)
	tr := tc.Transport.(*oauth2.Transport)
	tr.Base = base
	return tc
}
<commit_msg>Remove unused parameter<commit_after>package main

import (
	"bytes"
	"context"
	"crypto/rand"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/google/go-github/v24/github"
	"github.com/justinas/nosurf"
	"github.com/reviewdog/reviewdog/doghouse/server"
	"github.com/reviewdog/reviewdog/doghouse/server/cookieman"
	"github.com/reviewdog/reviewdog/doghouse/server/storage"
	"golang.org/x/oauth2"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
)

type GitHubHandler struct {
	clientID     string
	clientSecret string

	tokenStore     *cookieman.CookieStore
	redirURLStore  *cookieman.CookieStore // Redirect URL after login.
	authStateStore *cookieman.CookieStore

	repoTokenStore storage.GitHubRepositoryTokenStore

	privateKey    []byte
	integrationID int
}

func NewGitHubHandler(clientID, clientSecret string, c *cookieman.CookieMan, privateKey []byte, integrationID int) *GitHubHandler {
	return &GitHubHandler{
		clientID:       clientID,
		clientSecret:   clientSecret,
		tokenStore:     c.NewCookieStore("github-token", nil),
		redirURLStore:  c.NewCookieStore("github-redirect-url", nil),
		authStateStore: c.NewCookieStore("github-auth-state", nil),
		repoTokenStore: &storage.GitHubRepoTokenDatastore{},
		integrationID:  integrationID,
		privateKey:     privateKey,
	}
}

type ghTopTmplData struct {
	Title string
	User  tmplUser

	App struct {
		Name    string
		HTMLURL string
	}

	Installations []tmplInstallation
}

type tmplInstallation struct {
	Account        string
	AccountHTMLURL string
	AccountIconURL string
	HTMLURL        string
}

type ghRepoTmplData struct {
	Title     string
	Token     string
	User      tmplUser
	Repo      tmplRepo
	CSRFToken string
}

type tmplUser struct {
	Name      string
	IconURL   string
	GitHubURL string
}

type tmplRepo struct {
	Owner     string
	Name      string
	GitHubURL string
}

func (g *GitHubHandler) buildGithubAuthURL(r *http.Request, state string) string {
	redirURL := *r.URL
	redirURL.Path = "/gh/_auth/callback"
	redirURL.RawQuery = ""
	redirURL.Fragment = ""
	const baseURL = "https://github.com/login/oauth/authorize"
	authURL := fmt.Sprintf("%s?client_id=%s&redirect_url=%s&state=%s",
		baseURL, g.clientID, redirURL.RequestURI(), state)
	return authURL
}

func (g *GitHubHandler) HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	code, state := r.FormValue("code"), r.FormValue("state")
	if code == "" || state == "" {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, "code and state param is empty")
		return
	}

	// Verify state.
	cookieState, err := g.authStateStore.Get(r)
	if err != nil || state != string(cookieState) {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, "state is invalid")
		return
	}
	g.authStateStore.Clear(w)

	// Request and save access token.
	token, err := g.requestAccessToken(ctx, code, state)
	if err != nil {
		log.Errorf(ctx, "failed to get access token: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, "failed to get GitHub access token")
		return
	}
	g.tokenStore.Set(w, []byte(token))

	// Redirect.
	redirURL := "/gh/"
	if r, _ := g.redirURLStore.Get(r); err == nil {
		redirURL = string(r)
		g.redirURLStore.Clear(w)
	}
	http.Redirect(w, r, redirURL, http.StatusFound)
}

func (g *GitHubHandler) HandleLogout(w http.ResponseWriter, r *http.Request) {
	g.tokenStore.Clear(w)
	http.Redirect(w, r, "/", http.StatusFound)
}

func (g *GitHubHandler) LogInHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := appengine.NewContext(r)
		if g.isLoggedIn(r) {
			h.ServeHTTP(w, r)
			return
		}
		// Not logged in yet.
		log.Debugf(ctx, "Not logged in yet.")
		state := securerandom(16)
		g.redirURLStore.Set(w, []byte(r.URL.RequestURI()))
		g.authStateStore.Set(w, []byte(state))
		http.Redirect(w, r, g.buildGithubAuthURL(r, state), http.StatusFound)
	})
}

func (g *GitHubHandler) isLoggedIn(r *http.Request) bool {
	ok, _ := g.token(r)
	return ok
}

func securerandom(n int) string {
	b := make([]byte, n)
	io.ReadFull(rand.Reader, b[:])
	return fmt.Sprintf("%x", b)
}

// https://developer.github.com/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps/#2-users-are-redirected-back-to-your-site-by-github
// POST https://github.com/login/oauth/access_token
func (g *GitHubHandler) requestAccessToken(ctx context.Context, code, state string) (string, error) {
	const u = "https://github.com/login/oauth/access_token"
	cli := urlfetch.Client(ctx)
	data := url.Values{}
	data.Set("client_id", g.clientID)
	data.Set("client_secret", g.clientSecret)
	data.Set("code", code)
	data.Set("state", state)

	req, err := http.NewRequest("POST", u, strings.NewReader(data.Encode()))
	if err != nil {
		return "", fmt.Errorf("failed to create request: %v", err)
	}
	req = req.WithContext(ctx)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Accept", "application/vnd.github.machine-man-preview+json")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	res, err := cli.Do(req)
	if err != nil {
		return "", fmt.Errorf("failed to request access token: %v", err)
	}
	defer res.Body.Close()

	b, _ := ioutil.ReadAll(res.Body)

	var token struct {
		AccessToken string `json:"access_token"`
	}
	if err := json.NewDecoder(bytes.NewReader(b)).Decode(&token); err != nil {
		return "", fmt.Errorf("failed to decode response: %v", err)
	}

	if token.AccessToken == "" {
		log.Errorf(ctx, "response doesn't contain token (resopnse: %s)", b)
		return "", errors.New("response doesn't contain GitHub access token")
	}

	return token.AccessToken, nil
}

func (g *GitHubHandler) token(r *http.Request) (bool, string) {
	b, err := g.tokenStore.Get(r)
	if err != nil {
		return false, ""
	}
	return true, string(b)
}

func (g *GitHubHandler) HandleGitHubTop(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	ok, token := g.token(r)
	if !ok {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}

	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: token},
	)
	ghcli := github.NewClient(NewAuthClient(ctx, urlfetch.Client(ctx).Transport, ts))

	// /gh/{owner}/{repo}
	paths := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	switch len(paths) {
	case 1:
		g.handleTop(ctx, ghcli, w, r)
	case 3:
		g.handleRepo(ctx, ghcli, w, r, paths[1], paths[2])
	default:
		notfound(w)
	}
}

func notfound(w http.ResponseWriter) {
	w.WriteHeader(http.StatusNotFound)
	fmt.Fprintln(w, "404 Not Found")
}

func (g *GitHubHandler) getUserOrBadRequest(ctx context.Context, ghcli *github.Client, w http.ResponseWriter) (bool, *github.User) {
	u, _, err := ghcli.Users.Get(ctx, "")
	if err != nil {
		// Token seeims invalid. Clear it before returning BadRequest status.
		g.tokenStore.Clear(w)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "Cannot get GitHub authenticated user. Please reload the page again.")
		return false, nil
	}
	return true, u
}

func (g *GitHubHandler) handleTop(ctx context.Context, ghcli *github.Client, w http.ResponseWriter, r *http.Request) {
	ok, u := g.getUserOrBadRequest(ctx, ghcli, w)
	if !ok {
		return
	}

	data := &ghTopTmplData{
		Title: "GitHub - reviewdog",
		User: tmplUser{
			Name:      u.GetName(),
			IconURL:   u.GetAvatarURL(),
			GitHubURL: u.GetHTMLURL(),
		},
	}

	ghAppCli, err := server.NewGitHubClient(ctx, &server.NewGitHubClientOption{
		Client:        urlfetch.Client(ctx),
		IntegrationID: g.integrationID,
		PrivateKey:    g.privateKey,
	})
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, err)
		return
	}
	app, _, err := ghAppCli.Apps.Get(ctx, "")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, err)
		return
	}
	data.App.Name = app.GetName()
	data.App.HTMLURL = app.GetHTMLURL()

	installations, _, err := ghcli.Apps.ListUserInstallations(ctx, nil)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, err)
		return
	}
	for _, inst := range installations {
		data.Installations = append(data.Installations, tmplInstallation{
			Account:        inst.GetAccount().GetLogin(),
			AccountHTMLURL: inst.GetAccount().GetHTMLURL(),
			AccountIconURL: inst.GetAccount().GetAvatarURL(),
			HTMLURL:        inst.GetHTMLURL(),
		})
	}

	ghTopTmpl.ExecuteTemplate(w, "base", data)
}

func (g *GitHubHandler) handleRepo(ctx context.Context, ghcli *github.Client, w http.ResponseWriter, r *http.Request, owner, repoName string) {
	repo, _, err := ghcli.Repositories.Get(ctx, owner, repoName)
	if err != nil {
		if err, ok := err.(*github.ErrorResponse); ok {
			if err.Response.StatusCode == http.StatusNotFound {
				notfound(w)
				return
			}
		}
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "failed to get repo: %#v", err)
		return
	}

	if !repo.GetPermissions()["push"] {
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprintf(w, "You don't have write permission for %s.", repo.GetHTMLURL())
		return
	}

	ok, u := g.getUserOrBadRequest(ctx, ghcli, w)
	if !ok {
		return
	}

	// Regenerate Token.
	if r.Method == "POST" {
		if _, err := server.RegenerateRepoToken(ctx, g.repoTokenStore, repo.Owner.GetLogin(), repo.GetName(), repo.GetID()); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "failed to update repository token: %v", err)
			return
		}
		http.Redirect(w, r, r.URL.String(), http.StatusFound)
	}

	repoToken, err := server.GetOrGenerateRepoToken(ctx, g.repoTokenStore, repo.Owner.GetLogin(), repo.GetName(), repo.GetID())
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "failed to get repository token for %s.", repo.GetHTMLURL())
		return
	}

	ghRepoTmpl.ExecuteTemplate(w, "base", &ghRepoTmplData{
		Title: fmt.Sprintf("%s/%s - reviewdog", repo.Owner.GetLogin(), repo.GetName()),
		Token: repoToken,
		User: tmplUser{
			Name:      u.GetName(),
			IconURL:   u.GetAvatarURL(),
			GitHubURL: u.GetHTMLURL(),
		},
		Repo: tmplRepo{
			Owner:     repo.Owner.GetLogin(),
			Name:      repo.GetName(),
			GitHubURL: repo.GetHTMLURL(),
		},
		CSRFToken: nosurf.Token(r),
	})
}

func NewAuthClient(ctx context.Context, base http.RoundTripper, token oauth2.TokenSource) *http.Client {
	tc := oauth2.NewClient(ctx, token)
	tr := tc.Transport.(*oauth2.Transport)
	tr.Base = base
	return tc
}
<|endoftext|>


<commit_before>package controller

import (
	"time"

	"github.com/ninjasphere/app-scheduler/model"
)

type window struct {
	model *model.Window
	from  Event
	until Event
}

func (w *window) init(m *model.Window) error {
	var err error
	w.model = m
	w.from, err = newEvent(m.From, false)
	if err != nil {
		w.until, err = newEvent(m.Until, true)
	}
	return err
}

// Answer true if the window is open with respect to the specified time.
func (w *window) isOpen(ref time.Time) bool {
	openWaitsForTime := w.from.hasTimestamp()
	closeWaitsForTime := w.until.hasTimestamp()

	if openWaitsForTime && closeWaitsForTime {

		// when both events are timestamp based, check
		// that the reference timestamp is within the boundaries
		// of those timestamp

		openTimestamp := w.from.asTimestamp(ref)
		closeTimestamp := w.until.asTimestamp(openTimestamp)

		return openTimestamp.Sub(ref) < 0 &&
			ref.Sub(closeTimestamp) < 0 &&
			openTimestamp.Sub(closeTimestamp) > 0
	} else if !openWaitsForTime && !closeWaitsForTime {

		// when neither events are timestamp based, we have to
		// wait to wait for the open event to know we are open

		return false
	} else if closeWaitsForTime {

		// when only the close event is timestamp based we
		// the reference time is in the window, only if
		// it is less than the close event

		closeTimestamp := w.until.asTimestamp(ref)
		return ref.Sub(closeTimestamp) < 0
	} else { // if openWaitsForTime

		// when only the open event is timestamp basedd
		// we are in the window, only if reference
		// timestamp is greater than the open timestamp

		openTimestamp := w.until.asTimestamp(ref)
		return ref.Sub(openTimestamp) >= 0
	}
}

// Answer a channel that will receive an event when the next open event occurs.
func (w *window) whenOpen(ref time.Time) chan time.Time {
	return w.from.waiter(ref)
}

// Answer a channel that will receive an event when the next close event after the specified open event occurs.
func (w *window) whenClosed(opened time.Time) chan time.Time {
	return w.until.waiter(opened)
}
<commit_msg>Remove unnecessary structure member.<commit_after>package controller

import (
	"time"

	"github.com/ninjasphere/app-scheduler/model"
)

type window struct {
	from  Event
	until Event
}

func (w *window) init(m *model.Window) error {
	var err error
	w.from, err = newEvent(m.From, false)
	if err != nil {
		w.until, err = newEvent(m.Until, true)
	}
	return err
}

// Answer true if the window is open with respect to the specified time.
func (w *window) isOpen(ref time.Time) bool {
	openWaitsForTime := w.from.hasTimestamp()
	closeWaitsForTime := w.until.hasTimestamp()

	if openWaitsForTime && closeWaitsForTime {

		// when both events are timestamp based, check
		// that the reference timestamp is within the boundaries
		// of those timestamp

		openTimestamp := w.from.asTimestamp(ref)
		closeTimestamp := w.until.asTimestamp(openTimestamp)

		return openTimestamp.Sub(ref) < 0 &&
			ref.Sub(closeTimestamp) < 0 &&
			openTimestamp.Sub(closeTimestamp) > 0
	} else if !openWaitsForTime && !closeWaitsForTime {

		// when neither events are timestamp based, we have to
		// wait to wait for the open event to know we are open

		return false
	} else if closeWaitsForTime {

		// when only the close event is timestamp based we
		// the reference time is in the window, only if
		// it is less than the close event

		closeTimestamp := w.until.asTimestamp(ref)
		return ref.Sub(closeTimestamp) < 0
	} else { // if openWaitsForTime

		// when only the open event is timestamp basedd
		// we are in the window, only if reference
		// timestamp is greater than the open timestamp

		openTimestamp := w.until.asTimestamp(ref)
		return ref.Sub(openTimestamp) >= 0
	}
}

// Answer a channel that will receive an event when the next open event occurs.
func (w *window) whenOpen(ref time.Time) chan time.Time {
	return w.from.waiter(ref)
}

// Answer a channel that will receive an event when the next close event after the specified open event occurs.
func (w *window) whenClosed(opened time.Time) chan time.Time {
	return w.until.waiter(opened)
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"fmt"
	"net/http"
	"net/url"
	"io"
	"io/ioutil"
	"strings"
	"bytes"
	"encoding/json"
	"path"
)

// Result of a bundle lookup from cache server.
type Result struct {
	Complete bool
	Found    bool
	Bundle   []byte
	// Should add a Created field for the date created
}

// Configuration struct to be replaced by a decoded JSON file's contents.
var Configuration = struct {
	PortNumber     string
	CacheServer    string
	RequestServer  string
	ErrorMsg       string
	PleaseWaitPage string
} {}

func pleaseWait(url string) []byte {
	content, _ := ioutil.ReadFile(Configuration.PleaseWaitPage)
	return bytes.Replace(content, []byte("{{REDIRECT}}"), []byte(url), 1)
}

// Check with the local cache server to find a bundle for a given URL.
func lookup(lookupURL string) Result {
	response, err := http.Get(Configuration.CacheServer + "?url=" + url.QueryEscape(lookupURL))
	defer response.Body.Close()

	if err != nil || response.StatusCode != 200 {
		return Result{false, false, nil}
	}
	decoder := json.NewDecoder(response.Body)
	var result Result
	if err := decoder.Decode(&result); err == io.EOF {
		return Result{false, false, nil}
	}
	return result
}

// POST to the request server to have it start making a new bundle.
func requestNewBundle(lookupURL string) {
	// We can ignore the content of the response since it is not used.
	response, err := http.Post(
		Configuration.RequestServer + "?url=" + url.QueryEscape(lookupURL),
		"text/plain",
		strings.NewReader(lookupURL))
	defer response.Body.Close()
	if err != nil || response.StatusCode != 200 {
		fmt.Println("Got error POSTing to request server or request did not return status 200")
		fmt.Println(err)
	}
}

// Handle incoming requests for bundles.
// 1. Initiate bundle lookup process
// 2. Initiate bundle creation process when no bundle exists anywhere
func proxyHandler(w http.ResponseWriter, r *http.Request) {
	url := r.URL.String()
	result := lookup(url)
	if result.Complete {
		if result.Found {
			w.Write(result.Bundle)
		} else {
			requestNewBundle(url)
			w.Write(pleaseWait(url))
		}
	} else {
		w.Write(pleaseWait(url))
	}
}

// Create an HTTP proxy server to listen on port 3090
func main() {
	// Read the configuration JSON file into the global Configuration
	configPath := path.Join("..", "config", "client.json")
	file, _ := os.Open(configPath)
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&Configuration)
	if err != nil {
		fmt.Println("Could not read configuration file at " + configPath + "\nExiting.")
		return
	}
	http.HandleFunc("/", proxyHandler)
	fmt.Println("CeNo proxy server listening at http://localhost" + Configuration.PortNumber)
	http.ListenAndServe(Configuration.PortNumber, nil)
}<commit_msg>Fixed bug referencing GET response.Body<commit_after>package main

import (
	"os"
	"fmt"
	"net/http"
	"net/url"
	"io"
	"io/ioutil"
	"strings"
	"bytes"
	"encoding/json"
	"path"
)

// Result of a bundle lookup from cache server.
type Result struct {
	Complete bool
	Found    bool
	Bundle   []byte
	// Should add a Created field for the date created
}

// Configuration struct to be replaced by a decoded JSON file's contents.
var Configuration = struct {
	PortNumber     string
	CacheServer    string
	RequestServer  string
	ErrorMsg       string
	PleaseWaitPage string
} {}

func pleaseWait(url string) []byte {
	content, _ := ioutil.ReadFile(Configuration.PleaseWaitPage)
	return bytes.Replace(content, []byte("{{REDIRECT}}"), []byte(url), 1)
}

// Check with the local cache server to find a bundle for a given URL.
func lookup(lookupURL string) Result {
	response, err := http.Get(Configuration.CacheServer + "?url=" + url.QueryEscape(lookupURL))
	//defer response.Body.Close()

	if err != nil || response.StatusCode != 200 {
		return Result{false, false, nil}
	}
	decoder := json.NewDecoder(response.Body)
	var result Result
	if err := decoder.Decode(&result); err == io.EOF {
		return Result{false, false, nil}
	}
	return result
}

// POST to the request server to have it start making a new bundle.
func requestNewBundle(lookupURL string) {
	// We can ignore the content of the response since it is not used.
	response, err := http.Post(
		Configuration.RequestServer + "?url=" + url.QueryEscape(lookupURL),
		"text/plain",
		strings.NewReader(lookupURL))
	defer response.Body.Close()
	if err != nil || response.StatusCode != 200 {
		fmt.Println("Got error POSTing to request server or request did not return status 200")
		fmt.Println(err)
	}
}

// Handle incoming requests for bundles.
// 1. Initiate bundle lookup process
// 2. Initiate bundle creation process when no bundle exists anywhere
func proxyHandler(w http.ResponseWriter, r *http.Request) {
	url := r.URL.String()
	result := lookup(url)
	if result.Complete {
		if result.Found {
			w.Write(result.Bundle)
		} else {
			requestNewBundle(url)
			w.Write(pleaseWait(url))
		}
	} else {
		w.Write(pleaseWait(url))
	}
}

// Create an HTTP proxy server to listen on port 3090
func main() {
	// Read the configuration JSON file into the global Configuration
	configPath := path.Join("..", "config", "client.json")
	file, _ := os.Open(configPath)
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&Configuration)
	if err != nil {
		fmt.Println("Could not read configuration file at " + configPath + "\nExiting.")
		return
	}
	http.HandleFunc("/", proxyHandler)
	fmt.Println("CeNo proxy server listening at http://localhost" + Configuration.PortNumber)
	http.ListenAndServe(Configuration.PortNumber, nil)
}<|endoftext|>


<commit_before>// Package duration provides a partial implementation of ISO8601 durations. (no months)
package duration

import (
	"bytes"
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"text/template"
	"time"
)

var (
	// ErrBadFormat is returned when parsing fails
	ErrBadFormat = errors.New("bad format string")

	// ErrNoMonth is raised when a month is in the format string
	ErrNoMonth = errors.New("no months allowed")

	tmpl = template.Must(template.New("duration").Parse(`P{{if .Years}}{{.Years}}Y{{end}}{{if .Weeks}}{{.Weeks}}W{{end}}{{if .Days}}{{.Days}}D{{end}}{{if .HasTimePart}}T{{end }}{{if .Hours}}{{.Hours}}H{{end}}{{if .Minutes}}{{.Minutes}}M{{end}}{{if .Seconds}}{{.Seconds}}S{{end}}`))

	full = regexp.MustCompile(`P((?P<year>\d+)Y)?((?P<month>\d+)M)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?`)
	week = regexp.MustCompile(`P((?P<week>\d+)W)`)
)

type Duration struct {
	Years   int
	Month   int
	Weeks   int
	Days    int
	Hours   int
	Minutes int
	Seconds int
}

func FromString(dur string) (*Duration, error) {
	return fromStringWithTime(dur, false)
}

func fromStringWithTime(dur string, withMonth bool) (*Duration, error) {
	var (
		match []string
		re    *regexp.Regexp
	)

	if week.MatchString(dur) {
		match = week.FindStringSubmatch(dur)
		re = week
	} else if full.MatchString(dur) {
		match = full.FindStringSubmatch(dur)
		re = full
	} else {
		return nil, ErrBadFormat
	}

	d := &Duration{}

	for i, name := range re.SubexpNames() {
		part := match[i]
		if i == 0 || name == "" || part == "" {
			continue
		}

		val, err := strconv.Atoi(part)
		if err != nil {
			return nil, err
		}
		switch name {
		case "year":
			d.Years = val
		case "month":
			if !withMonth {
				return nil, ErrNoMonth
			}
			d.Month = val
		case "week":
			d.Weeks = val
		case "day":
			d.Days = val
		case "hour":
			d.Hours = val
		case "minute":
			d.Minutes = val
		case "second":
			d.Seconds = val
		default:
			return nil, errors.New(fmt.Sprintf("unknown field %s", name))
		}
	}

	return d, nil
}

// String prints out the value passed in. It's not strictly according to the
// ISO spec, but it's pretty close. In particular, to completely conform it
// would need to round up to the next largest unit. 61 seconds to 1 minute 1
// second, for example. It would also need to disallow weeks mingling with
// other units.
func (d *Duration) String() string {
	var s bytes.Buffer

	err := tmpl.Execute(&s, d)
	if err != nil {
		panic(err)
	}

	return s.String()
}

func (d *Duration) HasTimePart() bool {
	return d.Hours != 0 || d.Minutes != 0 || d.Seconds != 0
}

func (d *Duration) ToDuration() time.Duration {
	day := time.Hour * 24
	year := day * 365

	tot := time.Duration(0)

	tot += year * time.Duration(d.Years)
	tot += day * 7 * time.Duration(d.Weeks)
	tot += day * time.Duration(d.Days)
	tot += time.Hour * time.Duration(d.Hours)
	tot += time.Minute * time.Duration(d.Minutes)
	tot += time.Second * time.Duration(d.Seconds)

	return tot
}

// Returns time.Duration based on parsed duration
// and some base time value used to calculate actual
// values for duration in days, months, and years
func StringToTimeDuration(dur string, from time.Time) (time.Duration, error) {
	internalDur, err := fromStringWithTime(dur, true)
	if err != nil {
		return time.Duration(0), err
	}
	to := from.AddDate(internalDur.Years, internalDur.Month, internalDur.Days)
	internalDur.Years = 0
	internalDur.Month = 0
	internalDur.Days = 0
	to.Add(internalDur.ToDuration())
	return to.Sub(from), nil
}
<commit_msg>fix StringToTimeDuration<commit_after>// Package duration provides a partial implementation of ISO8601 durations. (no months)
package duration

import (
	"bytes"
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"text/template"
	"time"
)

var (
	// ErrBadFormat is returned when parsing fails
	ErrBadFormat = errors.New("bad format string")

	// ErrNoMonth is raised when a month is in the format string
	ErrNoMonth = errors.New("no months allowed")

	tmpl = template.Must(template.New("duration").Parse(`P{{if .Years}}{{.Years}}Y{{end}}{{if .Weeks}}{{.Weeks}}W{{end}}{{if .Days}}{{.Days}}D{{end}}{{if .HasTimePart}}T{{end }}{{if .Hours}}{{.Hours}}H{{end}}{{if .Minutes}}{{.Minutes}}M{{end}}{{if .Seconds}}{{.Seconds}}S{{end}}`))

	full = regexp.MustCompile(`P((?P<year>\d+)Y)?((?P<month>\d+)M)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?`)
	week = regexp.MustCompile(`P((?P<week>\d+)W)`)
)

type Duration struct {
	Years   int
	Month   int
	Weeks   int
	Days    int
	Hours   int
	Minutes int
	Seconds int
}

func FromString(dur string) (*Duration, error) {
	return fromStringWithTime(dur, false)
}

func fromStringWithTime(dur string, withMonth bool) (*Duration, error) {
	var (
		match []string
		re    *regexp.Regexp
	)

	if week.MatchString(dur) {
		match = week.FindStringSubmatch(dur)
		re = week
	} else if full.MatchString(dur) {
		match = full.FindStringSubmatch(dur)
		re = full
	} else {
		return nil, ErrBadFormat
	}

	d := &Duration{}

	for i, name := range re.SubexpNames() {
		part := match[i]
		if i == 0 || name == "" || part == "" {
			continue
		}

		val, err := strconv.Atoi(part)
		if err != nil {
			return nil, err
		}
		switch name {
		case "year":
			d.Years = val
		case "month":
			if !withMonth {
				return nil, ErrNoMonth
			}
			d.Month = val
		case "week":
			d.Weeks = val
		case "day":
			d.Days = val
		case "hour":
			d.Hours = val
		case "minute":
			d.Minutes = val
		case "second":
			d.Seconds = val
		default:
			return nil, errors.New(fmt.Sprintf("unknown field %s", name))
		}
	}

	return d, nil
}

// String prints out the value passed in. It's not strictly according to the
// ISO spec, but it's pretty close. In particular, to completely conform it
// would need to round up to the next largest unit. 61 seconds to 1 minute 1
// second, for example. It would also need to disallow weeks mingling with
// other units.
func (d *Duration) String() string {
	var s bytes.Buffer

	err := tmpl.Execute(&s, d)
	if err != nil {
		panic(err)
	}

	return s.String()
}

func (d *Duration) HasTimePart() bool {
	return d.Hours != 0 || d.Minutes != 0 || d.Seconds != 0
}

func (d *Duration) ToDuration() time.Duration {
	day := time.Hour * 24
	year := day * 365

	tot := time.Duration(0)

	tot += year * time.Duration(d.Years)
	tot += day * 7 * time.Duration(d.Weeks)
	tot += day * time.Duration(d.Days)
	tot += time.Hour * time.Duration(d.Hours)
	tot += time.Minute * time.Duration(d.Minutes)
	tot += time.Second * time.Duration(d.Seconds)

	return tot
}

// Returns time.Duration based on parsed duration
// and some base time value used to calculate actual
// values for duration in days, months, and years
func StringToTimeDuration(dur string, from time.Time) (time.Duration, error) {
	internalDur, err := fromStringWithTime(dur, true)
	if err != nil {
		return time.Duration(0), err
	}
	to := from.AddDate(internalDur.Years, internalDur.Month, internalDur.Days)
	internalDur.Years = 0
	internalDur.Month = 0
	internalDur.Days = 0
	to = to.Add(internalDur.ToDuration())
	return to.Sub(from), nil
}
<|endoftext|>


<commit_before>package core

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"github.ibm.com/almaden-containers/ubiquity/model"
	"github.ibm.com/almaden-containers/ubiquity/remote"
)

//Controller this is a structure that controls volume management
type Controller struct {
	Client model.StorageClient
	logger *log.Logger
}

//NewController allows to instantiate a controller
func NewController(logger *log.Logger, storageApiURL, backendName string, config model.UbiquityPluginConfig) (*Controller, error) {

	remoteClient, err := remote.NewRemoteClient(logger, backendName, storageApiURL, config)
	if err != nil {
		return nil, err
	}
	return &Controller{logger: logger, Client: remoteClient}, nil
}

//NewControllerWithClient is made for unit testing purposes where we can pass a fake client
func NewControllerWithClient(logger *log.Logger, client model.StorageClient) *Controller {
	return &Controller{logger: logger, Client: client}
}

//Init method is to initialize the flexvolume, it is a no op right now
func (c *Controller) Init() model.FlexVolumeResponse {
	c.logger.Println("controller-activate-start")
	defer c.logger.Println("controller-activate-end")

	err := c.Client.Activate()
	if err != nil {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Plugin init failed %#v ", err),
			Device:  "",
		}

	}

	return model.FlexVolumeResponse{
		Status:  "Success",
		Message: "Plugin init successfully",
		Device:  "",
	}
}

//Attach method attaches a volume/ fileset to a pod
func (c *Controller) Attach(attachRequest map[string]string) model.FlexVolumeResponse {
	c.logger.Println("controller-attach-start")
	defer c.logger.Println("controller-attach-end")
	c.logger.Printf("attach-details %#v\n", attachRequest)
	//var opts map[string]interface{}
	//opts = map[string]interface{}{"fileset": attachRequest.VolumeId, "filesystem": attachRequest.Filesystem}
	volumeName := attachRequest["Name"]
	var attachResponse model.FlexVolumeResponse
	err := c.Client.CreateVolume(volumeName, attachRequest)
	if err != nil && err.Error() != "Volume already exists" {
		attachResponse = model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed to attach volume: %#v", err),
			Device:  volumeName,
		}
		c.logger.Printf("Failed-to-attach-volume %#v ", err)
	} else if err != nil && err.Error() == "Volume already exists" {
		attachResponse = model.FlexVolumeResponse{
			Status:  "Success",
			Message: "Volume already attached",
			Device:  volumeName,
		}

	} else {
		attachResponse = model.FlexVolumeResponse{
			Status:  "Success",
			Message: "Volume attached successfully",
			Device:  volumeName,
		}
	}
	return attachResponse
}

//Detach detaches the volume/ fileset from the pod
func (c *Controller) Detach(detachRequest model.FlexVolumeDetachRequest) model.FlexVolumeResponse {
	c.logger.Println("controller-detach-start")
	defer c.logger.Println("controller-detach-end")

	c.logger.Printf("detach-details %#v\n", detachRequest)

	err := c.Client.RemoveVolume(detachRequest.Name, false)
	if err != nil {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed to detach volume %#v", err),
			Device:  detachRequest.Name,
		}
	}

	return model.FlexVolumeResponse{
		Status:  "Success",
		Message: "Volume detached successfully",
		Device:  detachRequest.Name,
	}
}

//Mount method allows to mount the volume/fileset to a given location for a pod
func (c *Controller) Mount(mountRequest model.FlexVolumeMountRequest) model.FlexVolumeResponse {
	c.logger.Println("controller-mount-start")
	defer c.logger.Println("controller-mount-end")

	mountedPath, err := c.Client.Attach(mountRequest.MountDevice)

	if err != nil {
		c.logger.Printf("Failed to mount volume %#v", err)
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed to mount volume %#v", err),
			Device:  "",
		}
	}
	dir := filepath.Dir(mountRequest.MountPath)

	c.logger.Printf("volume/ fileset mounted at %s", mountedPath)

	c.logger.Printf("creating volume directory %s", dir)
	err = os.MkdirAll(dir, 0777)
	if err != nil && !os.IsExist(err) {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed creating volume directory %#v", err),
			Device:  "",
		}

	}

	symLinkCommand := "/bin/ln"
	args := []string{"-s", mountedPath, mountRequest.MountPath}
	cmd := exec.Command(symLinkCommand, args...)
	_, err = cmd.Output()
	if err != nil {
		c.logger.Printf("Controller: mount failed to symlink %#v", err)
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed running ln command %#v", err),
			Device:  "",
		}

	}

	return model.FlexVolumeResponse{
		Status:  "Success",
		Message: fmt.Sprintf("Volume mounted successfully to %s", mountedPath),
		Device:  "",
	}
}

//Unmount methods unmounts the volume/ fileset from the pod
func (c *Controller) Unmount(unmountRequest model.FlexVolumeUnmountRequest) model.FlexVolumeResponse {
	c.logger.Println("Controller: unmount start")
	defer c.logger.Println("Controller: unmount end")

	volumes, err := c.Client.ListVolumes()
	if err != nil {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Error finding the volume %#v", err),
			Device:  "",
		}
	}

	volume, err := getVolumeForMountpoint(unmountRequest.MountPath, volumes)
	if err != nil {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Error finding the volume %#v", err),
			Device:  "",
		}
	}

	err = c.Client.Detach(volume.Name)
	if err != nil && err.Error() != "fileset not linked" {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed to unmount volume %#v", err),
			Device:  "",
		}
	}

	return model.FlexVolumeResponse{
		Status:  "Success",
		Message: "Volume unmounted successfully",
		Device:  "",
	}
}

func getVolumeForMountpoint(mountpoint string, volumes []model.VolumeMetadata) (model.VolumeMetadata, error) {

	for _, volume := range volumes {
		if volume.Mountpoint == mountpoint {
			return volume, nil
		}
	}
	return model.VolumeMetadata{}, fmt.Errorf("Volume not found")
}
<commit_msg>Fixed controller error<commit_after>package core

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"github.ibm.com/almaden-containers/ubiquity/model"
	"github.ibm.com/almaden-containers/ubiquity/remote"
)

//Controller this is a structure that controls volume management
type Controller struct {
	Client model.StorageClient
	logger *log.Logger
}

//NewController allows to instantiate a controller
func NewController(logger *log.Logger, storageApiURL, backendName string, config model.UbiquityPluginConfig) (*Controller, error) {

	remoteClient, err := remote.NewRemoteClient(logger, backendName, storageApiURL, config)
	if err != nil {
		return nil, err
	}
	return &Controller{logger: logger, Client: remoteClient}, nil
}

//NewControllerWithClient is made for unit testing purposes where we can pass a fake client
func NewControllerWithClient(logger *log.Logger, client model.StorageClient) *Controller {
	return &Controller{logger: logger, Client: client}
}

//Init method is to initialize the flexvolume, it is a no op right now
func (c *Controller) Init() model.FlexVolumeResponse {
	c.logger.Println("controller-activate-start")
	defer c.logger.Println("controller-activate-end")

	err := c.Client.Activate()
	if err != nil {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Plugin init failed %#v ", err),
			Device:  "",
		}

	}

	return model.FlexVolumeResponse{
		Status:  "Success",
		Message: "Plugin init successfully",
		Device:  "",
	}
}

//Attach method attaches a volume/ fileset to a pod
func (c *Controller) Attach(attachRequest map[string]string) model.FlexVolumeResponse {
	c.logger.Println("controller-attach-start")
	defer c.logger.Println("controller-attach-end")
	c.logger.Printf("attach-details %#v\n", attachRequest)

	volumeName := attachRequest["Name"]
	var opts map[string]interface{}
	opts = map[string]interface{}{"fileset": volumeName, "filesystem": attachRequest["Filesystem"]}

	var attachResponse model.FlexVolumeResponse
	err := c.Client.CreateVolume(volumeName, opts)
	if err != nil && err.Error() != "Volume already exists" {
		attachResponse = model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed to attach volume: %#v", err),
			Device:  volumeName,
		}
		c.logger.Printf("Failed-to-attach-volume %#v ", err)
	} else if err != nil && err.Error() == "Volume already exists" {
		attachResponse = model.FlexVolumeResponse{
			Status:  "Success",
			Message: "Volume already attached",
			Device:  volumeName,
		}

	} else {
		attachResponse = model.FlexVolumeResponse{
			Status:  "Success",
			Message: "Volume attached successfully",
			Device:  volumeName,
		}
	}
	return attachResponse
}

//Detach detaches the volume/ fileset from the pod
func (c *Controller) Detach(detachRequest model.FlexVolumeDetachRequest) model.FlexVolumeResponse {
	c.logger.Println("controller-detach-start")
	defer c.logger.Println("controller-detach-end")

	c.logger.Printf("detach-details %#v\n", detachRequest)

	err := c.Client.RemoveVolume(detachRequest.Name, false)
	if err != nil {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed to detach volume %#v", err),
			Device:  detachRequest.Name,
		}
	}

	return model.FlexVolumeResponse{
		Status:  "Success",
		Message: "Volume detached successfully",
		Device:  detachRequest.Name,
	}
}

//Mount method allows to mount the volume/fileset to a given location for a pod
func (c *Controller) Mount(mountRequest model.FlexVolumeMountRequest) model.FlexVolumeResponse {
	c.logger.Println("controller-mount-start")
	defer c.logger.Println("controller-mount-end")

	mountedPath, err := c.Client.Attach(mountRequest.MountDevice)

	if err != nil {
		c.logger.Printf("Failed to mount volume %#v", err)
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed to mount volume %#v", err),
			Device:  "",
		}
	}
	dir := filepath.Dir(mountRequest.MountPath)

	c.logger.Printf("volume/ fileset mounted at %s", mountedPath)

	c.logger.Printf("creating volume directory %s", dir)
	err = os.MkdirAll(dir, 0777)
	if err != nil && !os.IsExist(err) {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed creating volume directory %#v", err),
			Device:  "",
		}

	}

	symLinkCommand := "/bin/ln"
	args := []string{"-s", mountedPath, mountRequest.MountPath}
	cmd := exec.Command(symLinkCommand, args...)
	_, err = cmd.Output()
	if err != nil {
		c.logger.Printf("Controller: mount failed to symlink %#v", err)
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed running ln command %#v", err),
			Device:  "",
		}

	}

	return model.FlexVolumeResponse{
		Status:  "Success",
		Message: fmt.Sprintf("Volume mounted successfully to %s", mountedPath),
		Device:  "",
	}
}

//Unmount methods unmounts the volume/ fileset from the pod
func (c *Controller) Unmount(unmountRequest model.FlexVolumeUnmountRequest) model.FlexVolumeResponse {
	c.logger.Println("Controller: unmount start")
	defer c.logger.Println("Controller: unmount end")

	volumes, err := c.Client.ListVolumes()
	if err != nil {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Error finding the volume %#v", err),
			Device:  "",
		}
	}

	volume, err := getVolumeForMountpoint(unmountRequest.MountPath, volumes)
	if err != nil {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Error finding the volume %#v", err),
			Device:  "",
		}
	}

	err = c.Client.Detach(volume.Name)
	if err != nil && err.Error() != "fileset not linked" {
		return model.FlexVolumeResponse{
			Status:  "Failure",
			Message: fmt.Sprintf("Failed to unmount volume %#v", err),
			Device:  "",
		}
	}

	return model.FlexVolumeResponse{
		Status:  "Success",
		Message: "Volume unmounted successfully",
		Device:  "",
	}
}

func getVolumeForMountpoint(mountpoint string, volumes []model.VolumeMetadata) (model.VolumeMetadata, error) {

	for _, volume := range volumes {
		if volume.Mountpoint == mountpoint {
			return volume, nil
		}
	}
	return model.VolumeMetadata{}, fmt.Errorf("Volume not found")
}
<|endoftext|>


<commit_before>package effects

import (
	"github.com/stellar/horizon/db"
	"github.com/stellar/horizon/render/hal"
)

var TypeNames = map[int32]string{
	db.EffectAccountCreated:           "account_created",
	db.EffectAccountRemoved:           "account_removed",
	db.EffectAccountCredited:          "account_credited",
	db.EffectAccountDebited:           "account_debited",
	db.EffectAccountThresholdsUpdated: "account_thresholds_updated",
	db.EffectAccountHomeDomainUpdated: "account_home_domain_updated",
	db.EffectAccountFlagsUpdated:      "account_flags_updated",
	db.EffectSignerCreated:            "signer_created",
	db.EffectSignerRemoved:            "signer_removed",
	db.EffectSignerUpdated:            "signer_updated",
	db.EffectTrustlineCreated:         "trustline_created",
	db.EffectTrustlineRemoved:         "trustline_removed",
	db.EffectTrustlineUpdated:         "trustline_updated",
	db.EffectTrustlineAuthorized:      "trustline_authorized",
	db.EffectTrustlineDeauthorized:    "trustline_deauthorized",
	db.EffectOfferCreated:             "offer_created",
	db.EffectOfferRemoved:             "offer_removed",
	db.EffectOfferUpdated:             "offer_updated",
	db.EffectTrade:                    "trade",
}

func New(row db.EffectRecord) (result hal.Pageable, err error) {

	switch row.Type {
	case db.EffectAccountCreated:
		e := AccountCreated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountCredited:
		e := AccountCredited{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountDebited:
		e := AccountDebited{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountThresholdsUpdated:
		e := AccountThresholdsUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountHomeDomainUpdated:
		e := AccountHomeDomainUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountFlagsUpdated:
		e := AccountFlagsUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectSignerCreated:
		e := SignerCreated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectSignerUpdated:
		e := SignerUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectSignerRemoved:
		e := SignerRemoved{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineCreated:
		e := TrustlineCreated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineUpdated:
		e := TrustlineUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineRemoved:
		e := TrustlineRemoved{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineAuthorized:
		e := TrustlineAuthorized{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineDeauthorized:
		e := TrustlineDeauthorized{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectOfferCreated:
		e := OfferCreated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectOfferUpdated:
		e := OfferUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectOfferRemoved:
		e := OfferRemoved{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrade:
		e := Trade{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	default:
		e := Base{}
		e.Populate(row)
		result = e
	}

	return
}

type Base struct {
	Links struct {
		Operation hal.Link `json:"operation"`
		Succeeds  hal.Link `json:"succeeds"`
		Precedes  hal.Link `json:"precedes"`
	} `json:"_links"`

	ID      string `json:"id"`
	PT      string `json:"paging_token"`
	Account string `json:"account"`
	Type    string `json:"type"`
	TypeI   int32  `json:"type_i"`
}

type AccountCreated struct {
	Base
	StartingBalance string `json:"starting_balance"`
}

type AccountCredited struct {
	Base
	Amount      string `json:"amount"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type AccountDebited struct {
	Base
	Amount      string `json:"amount"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type AccountThresholdsUpdated struct {
	Base
	LowThreshold  int32 `json:"low_threshold"`
	MedThreshold  int32 `json:"med_threshold"`
	HighThreshold int32 `json:"high_threshold"`
}

type AccountHomeDomainUpdated struct {
	Base
	HomeDomain string `json:"home_domain"`
}

type AccountFlagsUpdated struct {
	Base
	AuthRequired  *bool `json:"auth_required_flag,omitempty"`
	AuthRevokable *bool `json:"auth_revokable_flag,omitempty"`
}

type SignerCreated struct {
	Base
	Weight    int32  `json:"weight"`
	PublicKey string `json:"public_key"`
}

type SignerRemoved struct {
	Base
	Weight    int32  `json:"weight"`
	PublicKey string `json:"public_key"`
}

type SignerUpdated struct {
	Base
	Weight    int32  `json:"weight"`
	PublicKey string `json:"public_key"`
}

type TrustlineCreated struct {
	Base
	Limit       string `json:"limit"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type TrustlineRemoved struct {
	Base
	Limit       string `json:"limit"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type TrustlineUpdated struct {
	Base
	Limit       string `json:"limit"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type TrustlineAuthorized struct {
	Base
	Trustor   string `json:"trustor"`
	AssetType string `json:"asset_type"`
	AssetCode string `json:"asset_code,omitempty"`
}

type TrustlineDeauthorized struct {
	Base
	Trustor   string `json:"trustor"`
	AssetType string `json:"asset_type"`
	AssetCode string `json:"asset_code,omitempty"`
}

type OfferCreated struct {
	Base
}

type OfferRemoved struct {
	Base
}

type OfferUpdated struct {
	Base
}

type Trade struct {
	Base
	Seller            string `json:"seller"`
	OfferID           int64  `json:"offer_id"`
	SoldAmount        string `json:"sold_amount"`
	SoldAssetType     string `json:"sold_asset_type"`
	SoldAssetCode     string `json:"sold_asset_code,omitempty"`
	SoldAssetIssuer   string `json:"sold_asset_issuer,omitempty"`
	BoughtAmount      string `json:"bought_amount"`
	BoughtAssetType   string `json:"bought_asset_type"`
	BoughtAssetCode   string `json:"bought_asset_code,omitempty"`
	BoughtAssetIssuer string `json:"bought_asset_issuer,omitempty"`
}
<commit_msg>Remove offer effect stubs (since they are not imported yet)<commit_after>package effects

import (
	"github.com/stellar/horizon/db"
	"github.com/stellar/horizon/render/hal"
)

var TypeNames = map[int32]string{
	db.EffectAccountCreated:           "account_created",
	db.EffectAccountRemoved:           "account_removed",
	db.EffectAccountCredited:          "account_credited",
	db.EffectAccountDebited:           "account_debited",
	db.EffectAccountThresholdsUpdated: "account_thresholds_updated",
	db.EffectAccountHomeDomainUpdated: "account_home_domain_updated",
	db.EffectAccountFlagsUpdated:      "account_flags_updated",
	db.EffectSignerCreated:            "signer_created",
	db.EffectSignerRemoved:            "signer_removed",
	db.EffectSignerUpdated:            "signer_updated",
	db.EffectTrustlineCreated:         "trustline_created",
	db.EffectTrustlineRemoved:         "trustline_removed",
	db.EffectTrustlineUpdated:         "trustline_updated",
	db.EffectTrustlineAuthorized:      "trustline_authorized",
	db.EffectTrustlineDeauthorized:    "trustline_deauthorized",
	db.EffectOfferCreated:             "offer_created",
	db.EffectOfferRemoved:             "offer_removed",
	db.EffectOfferUpdated:             "offer_updated",
	db.EffectTrade:                    "trade",
}

func New(row db.EffectRecord) (result hal.Pageable, err error) {

	switch row.Type {
	case db.EffectAccountCreated:
		e := AccountCreated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountCredited:
		e := AccountCredited{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountDebited:
		e := AccountDebited{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountThresholdsUpdated:
		e := AccountThresholdsUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountHomeDomainUpdated:
		e := AccountHomeDomainUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectAccountFlagsUpdated:
		e := AccountFlagsUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectSignerCreated:
		e := SignerCreated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectSignerUpdated:
		e := SignerUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectSignerRemoved:
		e := SignerRemoved{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineCreated:
		e := TrustlineCreated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineUpdated:
		e := TrustlineUpdated{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineRemoved:
		e := TrustlineRemoved{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineAuthorized:
		e := TrustlineAuthorized{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrustlineDeauthorized:
		e := TrustlineDeauthorized{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	case db.EffectTrade:
		e := Trade{}
		e.Populate(row)
		err = row.UnmarshalDetails(&e)
		result = e
	default:
		e := Base{}
		e.Populate(row)
		result = e
	}

	return
}

type Base struct {
	Links struct {
		Operation hal.Link `json:"operation"`
		Succeeds  hal.Link `json:"succeeds"`
		Precedes  hal.Link `json:"precedes"`
	} `json:"_links"`

	ID      string `json:"id"`
	PT      string `json:"paging_token"`
	Account string `json:"account"`
	Type    string `json:"type"`
	TypeI   int32  `json:"type_i"`
}

type AccountCreated struct {
	Base
	StartingBalance string `json:"starting_balance"`
}

type AccountCredited struct {
	Base
	Amount      string `json:"amount"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type AccountDebited struct {
	Base
	Amount      string `json:"amount"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type AccountThresholdsUpdated struct {
	Base
	LowThreshold  int32 `json:"low_threshold"`
	MedThreshold  int32 `json:"med_threshold"`
	HighThreshold int32 `json:"high_threshold"`
}

type AccountHomeDomainUpdated struct {
	Base
	HomeDomain string `json:"home_domain"`
}

type AccountFlagsUpdated struct {
	Base
	AuthRequired  *bool `json:"auth_required_flag,omitempty"`
	AuthRevokable *bool `json:"auth_revokable_flag,omitempty"`
}

type SignerCreated struct {
	Base
	Weight    int32  `json:"weight"`
	PublicKey string `json:"public_key"`
}

type SignerRemoved struct {
	Base
	Weight    int32  `json:"weight"`
	PublicKey string `json:"public_key"`
}

type SignerUpdated struct {
	Base
	Weight    int32  `json:"weight"`
	PublicKey string `json:"public_key"`
}

type TrustlineCreated struct {
	Base
	Limit       string `json:"limit"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type TrustlineRemoved struct {
	Base
	Limit       string `json:"limit"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type TrustlineUpdated struct {
	Base
	Limit       string `json:"limit"`
	AssetType   string `json:"asset_type"`
	AssetCode   string `json:"asset_code,omitempty"`
	AssetIssuer string `json:"asset_issuer,omitempty"`
}

type TrustlineAuthorized struct {
	Base
	Trustor   string `json:"trustor"`
	AssetType string `json:"asset_type"`
	AssetCode string `json:"asset_code,omitempty"`
}

type TrustlineDeauthorized struct {
	Base
	Trustor   string `json:"trustor"`
	AssetType string `json:"asset_type"`
	AssetCode string `json:"asset_code,omitempty"`
}

type Trade struct {
	Base
	Seller            string `json:"seller"`
	OfferID           int64  `json:"offer_id"`
	SoldAmount        string `json:"sold_amount"`
	SoldAssetType     string `json:"sold_asset_type"`
	SoldAssetCode     string `json:"sold_asset_code,omitempty"`
	SoldAssetIssuer   string `json:"sold_asset_issuer,omitempty"`
	BoughtAmount      string `json:"bought_amount"`
	BoughtAssetType   string `json:"bought_asset_type"`
	BoughtAssetCode   string `json:"bought_asset_code,omitempty"`
	BoughtAssetIssuer string `json:"bought_asset_issuer,omitempty"`
}
<|endoftext|>


<commit_before>// Copyright 2014 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package core

import (
	"crypto/x509"
	"github.com/letsencrypt/boulder/jose"
	"net/http"
)

// A WebFrontEnd object supplies methods that can be hooked into
// the Go http module's server functions, principally http.HandleFunc()
//
// It also provides methods to configure the base for authorization and
// certificate URLs.
//
// It is assumed that the ACME server is laid out as follows:
// * One URL for new-authorization -> NewAuthz
// * One URL for new-certificate -> NewCert
// * One path for authorizations -> Authz
// * One path for certificates -> Cert
type WebFrontEnd interface {
	// Set the base URL for authorizations
	SetAuthzBase(path string)

	// Set the base URL for certificates
	SetCertBase(path string)

	// This method represents the ACME new-registration resource
	NewRegistration(response http.ResponseWriter, request *http.Request)

	// This method represents the ACME new-authorization resource
	NewAuthz(response http.ResponseWriter, request *http.Request)

	// This method represents the ACME new-certificate resource
	NewCert(response http.ResponseWriter, request *http.Request)

	// Provide access to requests for registration resources
	Registration(response http.ResponseWriter, request *http.Request)

	// Provide access to requests for authorization resources
	Authz(response http.ResponseWriter, request *http.Request)

	// Provide access to requests for authorization resources
	Cert(response http.ResponseWriter, request *http.Request)
}

type RegistrationAuthority interface {
	// [WebFrontEnd]
	NewRegistration(Registration, jose.JsonWebKey) (Registration, error)

	// [WebFrontEnd]
	NewAuthorization(Authorization, jose.JsonWebKey) (Authorization, error)

	// [WebFrontEnd]
	NewCertificate(CertificateRequest, jose.JsonWebKey) (Certificate, error)

	// [WebFrontEnd]
	UpdateRegistration(Registration, Registration) (Registration, error)

	// [WebFrontEnd]
	UpdateAuthorization(Authorization, int, Challenge) (Authorization, error)

	// [WebFrontEnd]
	RevokeCertificate(x509.Certificate) error

	// [ValidationAuthority]
	OnValidationUpdate(Authorization)
}

type ValidationAuthority interface {
	// [RegistrationAuthority]
	UpdateValidations(Authorization) error
}

type CertificateAuthority interface {
	// [RegistrationAuthority]
	IssueCertificate(x509.CertificateRequest) (Certificate, error)
}

type PolicyAuthority interface {
	WillingToIssue(AcmeIdentifier) error
	ChallengesFor(AcmeIdentifier) ([]Challenge, [][]int)
}

type StorageGetter interface {
	GetRegistration(string) (Registration, error)
	GetAuthorization(string) (Authorization, error)
	GetCertificate(string) ([]byte, error)
	GetCertificateByShortSerial(string) ([]byte, error)
}

type StorageAdder interface {
	NewRegistration() (string, error)
	UpdateRegistration(Registration) error

	NewPendingAuthorization() (string, error)
	UpdatePendingAuthorization(Authorization) error
	FinalizeAuthorization(Authorization) error
	MarkCertificateRevoked(serial string, ocspResponse []byte, reasonCode int) error

	AddCertificate([]byte) (string, error)
}

// StorageAuthority interface represents a simple key/value
// store.  It is divided into StorageGetter and StorageUpdater
// interfaces for privilege separation.
type StorageAuthority interface {
	StorageGetter
	StorageAdder
}

// CertificateAuthorityDatabase represents an atomic sequence source
type CertificateAuthorityDatabase interface {
	Begin() error
	Commit() error
	Rollback() error

	IncrementAndGetSerial() (int, error)
}
<commit_msg>Add interface for RevokeCertificate.<commit_after>// Copyright 2014 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package core

import (
	"crypto/x509"
	"github.com/letsencrypt/boulder/jose"
	"net/http"
)

// A WebFrontEnd object supplies methods that can be hooked into
// the Go http module's server functions, principally http.HandleFunc()
//
// It also provides methods to configure the base for authorization and
// certificate URLs.
//
// It is assumed that the ACME server is laid out as follows:
// * One URL for new-authorization -> NewAuthz
// * One URL for new-certificate -> NewCert
// * One path for authorizations -> Authz
// * One path for certificates -> Cert
type WebFrontEnd interface {
	// Set the base URL for authorizations
	SetAuthzBase(path string)

	// Set the base URL for certificates
	SetCertBase(path string)

	// This method represents the ACME new-registration resource
	NewRegistration(response http.ResponseWriter, request *http.Request)

	// This method represents the ACME new-authorization resource
	NewAuthz(response http.ResponseWriter, request *http.Request)

	// This method represents the ACME new-certificate resource
	NewCert(response http.ResponseWriter, request *http.Request)

	// Provide access to requests for registration resources
	Registration(response http.ResponseWriter, request *http.Request)

	// Provide access to requests for authorization resources
	Authz(response http.ResponseWriter, request *http.Request)

	// Provide access to requests for authorization resources
	Cert(response http.ResponseWriter, request *http.Request)
}

type RegistrationAuthority interface {
	// [WebFrontEnd]
	NewRegistration(Registration, jose.JsonWebKey) (Registration, error)

	// [WebFrontEnd]
	NewAuthorization(Authorization, jose.JsonWebKey) (Authorization, error)

	// [WebFrontEnd]
	NewCertificate(CertificateRequest, jose.JsonWebKey) (Certificate, error)

	// [WebFrontEnd]
	UpdateRegistration(Registration, Registration) (Registration, error)

	// [WebFrontEnd]
	UpdateAuthorization(Authorization, int, Challenge) (Authorization, error)

	// [WebFrontEnd]
	RevokeCertificate(x509.Certificate) error

	// [ValidationAuthority]
	OnValidationUpdate(Authorization)
}

type ValidationAuthority interface {
	// [RegistrationAuthority]
	UpdateValidations(Authorization) error
}

type CertificateAuthority interface {
	// [RegistrationAuthority]
	IssueCertificate(x509.CertificateRequest) (Certificate, error)
	RevokeCertificate(serial string) error
}

type PolicyAuthority interface {
	WillingToIssue(AcmeIdentifier) error
	ChallengesFor(AcmeIdentifier) ([]Challenge, [][]int)
}

type StorageGetter interface {
	GetRegistration(string) (Registration, error)
	GetAuthorization(string) (Authorization, error)
	GetCertificate(string) ([]byte, error)
	GetCertificateByShortSerial(string) ([]byte, error)
}

type StorageAdder interface {
	NewRegistration() (string, error)
	UpdateRegistration(Registration) error

	NewPendingAuthorization() (string, error)
	UpdatePendingAuthorization(Authorization) error
	FinalizeAuthorization(Authorization) error
	MarkCertificateRevoked(serial string, ocspResponse []byte, reasonCode int) error

	AddCertificate([]byte) (string, error)
}

// StorageAuthority interface represents a simple key/value
// store.  It is divided into StorageGetter and StorageUpdater
// interfaces for privilege separation.
type StorageAuthority interface {
	StorageGetter
	StorageAdder
}

// CertificateAuthorityDatabase represents an atomic sequence source
type CertificateAuthorityDatabase interface {
	Begin() error
	Commit() error
	Rollback() error

	IncrementAndGetSerial() (int, error)
}
<|endoftext|>


<commit_before>// Copyright 2013 The Go 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 imports

import (
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"

	"code.google.com/p/go.tools/astutil"
)

// importToGroup is a list of functions which map from an import path to
// a group number.
var importToGroup = []func(importPath string) (num int, ok bool){
	func(importPath string) (num int, ok bool) {
		if strings.HasPrefix(importPath, "appengine") {
			return 2, true
		}
		return
	},
	func(importPath string) (num int, ok bool) {
		if strings.Contains(importPath, ".") {
			return 1, true
		}
		return
	},
}

func importGroup(importPath string) int {
	for _, fn := range importToGroup {
		if n, ok := fn(importPath); ok {
			return n
		}
	}
	return 0
}

func fixImports(fset *token.FileSet, f *ast.File) (added []string, err error) {
	// refs are a set of possible package references currently unsatisified by imports.
	// first key: either base package (e.g. "fmt") or renamed package
	// second key: referenced package symbol (e.g. "Println")
	refs := make(map[string]map[string]bool)

	// decls are the current package imports. key is base package or renamed package.
	decls := make(map[string]*ast.ImportSpec)

	// collect potential uses of packages.
	var visitor visitFn
	visitor = visitFn(func(node ast.Node) ast.Visitor {
		if node == nil {
			return visitor
		}
		switch v := node.(type) {
		case *ast.ImportSpec:
			if v.Name != nil {
				decls[v.Name.Name] = v
			} else {
				local := importPathToName(strings.Trim(v.Path.Value, `\"`))
				decls[local] = v
			}
		case *ast.SelectorExpr:
			xident, ok := v.X.(*ast.Ident)
			if !ok {
				break
			}
			if xident.Obj != nil {
				// if the parser can resolve it, it's not a package ref
				break
			}
			pkgName := xident.Name
			if refs[pkgName] == nil {
				refs[pkgName] = make(map[string]bool)
			}
			if decls[pkgName] == nil {
				refs[pkgName][v.Sel.Name] = true
			}
		}
		return visitor
	})
	ast.Walk(visitor, f)

	// Search for imports matching potential package references.
	searches := 0
	type result struct {
		ipath string
		name  string
		err   error
	}
	results := make(chan result)
	for pkgName, symbols := range refs {
		if len(symbols) == 0 {
			continue // skip over packages already imported
		}
		go func(pkgName string, symbols map[string]bool) {
			ipath, rename, err := findImport(pkgName, symbols)
			r := result{ipath: ipath, err: err}
			if rename {
				r.name = pkgName
			}
			results <- r
		}(pkgName, symbols)
		searches++
	}
	for i := 0; i < searches; i++ {
		result := <-results
		if result.err != nil {
			return nil, result.err
		}
		if result.ipath != "" {
			if result.name != "" {
				astutil.AddNamedImport(fset, f, result.name, result.ipath)
			} else {
				astutil.AddImport(fset, f, result.ipath)
			}
			added = append(added, result.ipath)
		}
	}

	// Nil out any unused ImportSpecs, to be removed in following passes
	unusedImport := map[string]bool{}
	for pkg, is := range decls {
		if refs[pkg] == nil && pkg != "_" && pkg != "." {
			unusedImport[strings.Trim(is.Path.Value, `"`)] = true
		}
	}
	for ipath := range unusedImport {
		if ipath == "C" {
			// Don't remove cgo stuff.
			continue
		}
		astutil.DeleteImport(fset, f, ipath)
	}

	return added, nil
}

// importPathToName returns the package name for the given import path.
var importPathToName = importPathToNameGoPath

// importPathToNameBasic assumes the package name is the base of import path.
func importPathToNameBasic(importPath string) (packageName string) {
	return path.Base(importPath)
}

// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
// If there's a problem, it falls back to using importPathToNameBasic.
func importPathToNameGoPath(importPath string) (packageName string) {
	if buildPkg, err := build.Import(importPath, "", 0); err == nil {
		return buildPkg.Name
	} else {
		return importPathToNameBasic(importPath)
	}
}

type pkg struct {
	importpath string // full pkg import path, e.g. "net/http"
	dir        string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
}

var pkgIndexOnce sync.Once

var pkgIndex struct {
	sync.Mutex
	m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
}

// gate is a semaphore for limiting concurrency.
type gate chan bool

func (g gate) enter() { g <- true }
func (g gate) leave() { <-g }

// fsgate protects the OS & filesystem from too much concurrency.
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
var fsgate = make(gate, 8)

func loadPkgIndex() {
	pkgIndex.Lock()
	pkgIndex.m = make(map[string][]pkg)
	pkgIndex.Unlock()

	var wg sync.WaitGroup
	for _, path := range build.Default.SrcDirs() {
		fsgate.enter()
		f, err := os.Open(path)
		if err != nil {
			fsgate.leave()
			fmt.Fprint(os.Stderr, err)
			continue
		}
		children, err := f.Readdir(-1)
		f.Close()
		fsgate.leave()
		if err != nil {
			fmt.Fprint(os.Stderr, err)
			continue
		}
		for _, child := range children {
			if child.IsDir() {
				wg.Add(1)
				go func(path, name string) {
					defer wg.Done()
					loadPkg(&wg, path, name)
				}(path, child.Name())
			}
		}
	}
	wg.Wait()
}

func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
	importpath := filepath.ToSlash(pkgrelpath)
	dir := filepath.Join(root, importpath)

	fsgate.enter()
	defer fsgate.leave()
	pkgDir, err := os.Open(dir)
	if err != nil {
		return
	}
	children, err := pkgDir.Readdir(-1)
	pkgDir.Close()
	if err != nil {
		return
	}
	// hasGo tracks whether a directory actually appears to be a
	// Go source code directory. If $GOPATH == $HOME, and
	// $HOME/src has lots of other large non-Go projects in it,
	// then the calls to importPathToName below can be expensive.
	hasGo := false
	for _, child := range children {
		name := child.Name()
		if name == "" {
			continue
		}
		if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
			continue
		}
		if strings.HasSuffix(name, ".go") {
			hasGo = true
		}
		if child.IsDir() {
			wg.Add(1)
			go func(root, name string) {
				defer wg.Done()
				loadPkg(wg, root, name)
			}(root, filepath.Join(importpath, name))
		}
	}
	if hasGo {
		shortName := importPathToName(importpath)
		pkgIndex.Lock()
		pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
			importpath: importpath,
			dir:        dir,
		})
		pkgIndex.Unlock()
	}

}

// loadExports returns a list exports for a package.
var loadExports = loadExportsGoPath

func loadExportsGoPath(dir string) map[string]bool {
	exports := make(map[string]bool)
	buildPkg, err := build.ImportDir(dir, 0)
	if err != nil {
		if strings.Contains(err.Error(), "no buildable Go source files in") {
			return nil
		}
		fmt.Fprintf(os.Stderr, "could not import %q: %v", dir, err)
		return nil
	}
	fset := token.NewFileSet()
	for _, file := range buildPkg.GoFiles {
		f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
		if err != nil {
			fmt.Fprintf(os.Stderr, "could not parse %q: %v", file, err)
			continue
		}
		for name := range f.Scope.Objects {
			if ast.IsExported(name) {
				exports[name] = true
			}
		}
	}
	return exports
}

// findImport searches for a package with the given symbols.
// If no package is found, findImport returns "".
// Declared as a variable rather than a function so goimports can be easily
// extended by adding a file with an init function.
var findImport = findImportGoPath

func findImportGoPath(pkgName string, symbols map[string]bool) (string, bool, error) {
	// Fast path for the standard library.
	// In the common case we hopefully never have to scan the GOPATH, which can
	// be slow with moving disks.
	if pkg, rename, ok := findImportStdlib(pkgName, symbols); ok {
		return pkg, rename, nil
	}

	// TODO(sameer): look at the import lines for other Go files in the
	// local directory, since the user is likely to import the same packages
	// in the current Go file.  Return rename=true when the other Go files
	// use a renamed package that's also used in the current file.

	pkgIndexOnce.Do(loadPkgIndex)

	// Collect exports for packages with matching names.
	var wg sync.WaitGroup
	var pkgsMu sync.Mutex // guards pkgs
	// full importpath => exported symbol => True
	// e.g. "net/http" => "Client" => True
	pkgs := make(map[string]map[string]bool)
	pkgIndex.Lock()
	for _, pkg := range pkgIndex.m[pkgName] {
		wg.Add(1)
		go func(importpath, dir string) {
			defer wg.Done()
			exports := loadExports(dir)
			if exports != nil {
				pkgsMu.Lock()
				pkgs[importpath] = exports
				pkgsMu.Unlock()
			}
		}(pkg.importpath, pkg.dir)
	}
	pkgIndex.Unlock()
	wg.Wait()

	// Filter out packages missing required exported symbols.
	for symbol := range symbols {
		for importpath, exports := range pkgs {
			if !exports[symbol] {
				delete(pkgs, importpath)
			}
		}
	}
	if len(pkgs) == 0 {
		return "", false, nil
	}

	// If there are multiple candidate packages, the shortest one wins.
	// This is a heuristic to prefer the standard library (e.g. "bytes")
	// over e.g. "github.com/foo/bar/bytes".
	shortest := ""
	for importPath := range pkgs {
		if shortest == "" || len(importPath) < len(shortest) {
			shortest = importPath
		}
	}
	return shortest, false, nil
}

type visitFn func(node ast.Node) ast.Visitor

func (fn visitFn) Visit(node ast.Node) ast.Visitor {
	return fn(node)
}

func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) {
	for symbol := range symbols {
		path := stdlib[shortPkg+"."+symbol]
		if path == "" {
			return "", false, false
		}
		if importPath != "" && importPath != path {
			// Ambiguous. Symbols pointed to different things.
			return "", false, false
		}
		importPath = path
	}
	return importPath, false, importPath != ""
}
<commit_msg>imports: use chan struct{} for disk semaphore<commit_after>// Copyright 2013 The Go 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 imports

import (
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"

	"code.google.com/p/go.tools/astutil"
)

// importToGroup is a list of functions which map from an import path to
// a group number.
var importToGroup = []func(importPath string) (num int, ok bool){
	func(importPath string) (num int, ok bool) {
		if strings.HasPrefix(importPath, "appengine") {
			return 2, true
		}
		return
	},
	func(importPath string) (num int, ok bool) {
		if strings.Contains(importPath, ".") {
			return 1, true
		}
		return
	},
}

func importGroup(importPath string) int {
	for _, fn := range importToGroup {
		if n, ok := fn(importPath); ok {
			return n
		}
	}
	return 0
}

func fixImports(fset *token.FileSet, f *ast.File) (added []string, err error) {
	// refs are a set of possible package references currently unsatisified by imports.
	// first key: either base package (e.g. "fmt") or renamed package
	// second key: referenced package symbol (e.g. "Println")
	refs := make(map[string]map[string]bool)

	// decls are the current package imports. key is base package or renamed package.
	decls := make(map[string]*ast.ImportSpec)

	// collect potential uses of packages.
	var visitor visitFn
	visitor = visitFn(func(node ast.Node) ast.Visitor {
		if node == nil {
			return visitor
		}
		switch v := node.(type) {
		case *ast.ImportSpec:
			if v.Name != nil {
				decls[v.Name.Name] = v
			} else {
				local := importPathToName(strings.Trim(v.Path.Value, `\"`))
				decls[local] = v
			}
		case *ast.SelectorExpr:
			xident, ok := v.X.(*ast.Ident)
			if !ok {
				break
			}
			if xident.Obj != nil {
				// if the parser can resolve it, it's not a package ref
				break
			}
			pkgName := xident.Name
			if refs[pkgName] == nil {
				refs[pkgName] = make(map[string]bool)
			}
			if decls[pkgName] == nil {
				refs[pkgName][v.Sel.Name] = true
			}
		}
		return visitor
	})
	ast.Walk(visitor, f)

	// Search for imports matching potential package references.
	searches := 0
	type result struct {
		ipath string
		name  string
		err   error
	}
	results := make(chan result)
	for pkgName, symbols := range refs {
		if len(symbols) == 0 {
			continue // skip over packages already imported
		}
		go func(pkgName string, symbols map[string]bool) {
			ipath, rename, err := findImport(pkgName, symbols)
			r := result{ipath: ipath, err: err}
			if rename {
				r.name = pkgName
			}
			results <- r
		}(pkgName, symbols)
		searches++
	}
	for i := 0; i < searches; i++ {
		result := <-results
		if result.err != nil {
			return nil, result.err
		}
		if result.ipath != "" {
			if result.name != "" {
				astutil.AddNamedImport(fset, f, result.name, result.ipath)
			} else {
				astutil.AddImport(fset, f, result.ipath)
			}
			added = append(added, result.ipath)
		}
	}

	// Nil out any unused ImportSpecs, to be removed in following passes
	unusedImport := map[string]bool{}
	for pkg, is := range decls {
		if refs[pkg] == nil && pkg != "_" && pkg != "." {
			unusedImport[strings.Trim(is.Path.Value, `"`)] = true
		}
	}
	for ipath := range unusedImport {
		if ipath == "C" {
			// Don't remove cgo stuff.
			continue
		}
		astutil.DeleteImport(fset, f, ipath)
	}

	return added, nil
}

// importPathToName returns the package name for the given import path.
var importPathToName = importPathToNameGoPath

// importPathToNameBasic assumes the package name is the base of import path.
func importPathToNameBasic(importPath string) (packageName string) {
	return path.Base(importPath)
}

// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
// If there's a problem, it falls back to using importPathToNameBasic.
func importPathToNameGoPath(importPath string) (packageName string) {
	if buildPkg, err := build.Import(importPath, "", 0); err == nil {
		return buildPkg.Name
	} else {
		return importPathToNameBasic(importPath)
	}
}

type pkg struct {
	importpath string // full pkg import path, e.g. "net/http"
	dir        string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
}

var pkgIndexOnce sync.Once

var pkgIndex struct {
	sync.Mutex
	m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
}

// gate is a semaphore for limiting concurrency.
type gate chan struct{}

func (g gate) enter() { g <- struct{}{} }
func (g gate) leave() { <-g }

// fsgate protects the OS & filesystem from too much concurrency.
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
var fsgate = make(gate, 8)

func loadPkgIndex() {
	pkgIndex.Lock()
	pkgIndex.m = make(map[string][]pkg)
	pkgIndex.Unlock()

	var wg sync.WaitGroup
	for _, path := range build.Default.SrcDirs() {
		fsgate.enter()
		f, err := os.Open(path)
		if err != nil {
			fsgate.leave()
			fmt.Fprint(os.Stderr, err)
			continue
		}
		children, err := f.Readdir(-1)
		f.Close()
		fsgate.leave()
		if err != nil {
			fmt.Fprint(os.Stderr, err)
			continue
		}
		for _, child := range children {
			if child.IsDir() {
				wg.Add(1)
				go func(path, name string) {
					defer wg.Done()
					loadPkg(&wg, path, name)
				}(path, child.Name())
			}
		}
	}
	wg.Wait()
}

func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
	importpath := filepath.ToSlash(pkgrelpath)
	dir := filepath.Join(root, importpath)

	fsgate.enter()
	defer fsgate.leave()
	pkgDir, err := os.Open(dir)
	if err != nil {
		return
	}
	children, err := pkgDir.Readdir(-1)
	pkgDir.Close()
	if err != nil {
		return
	}
	// hasGo tracks whether a directory actually appears to be a
	// Go source code directory. If $GOPATH == $HOME, and
	// $HOME/src has lots of other large non-Go projects in it,
	// then the calls to importPathToName below can be expensive.
	hasGo := false
	for _, child := range children {
		name := child.Name()
		if name == "" {
			continue
		}
		if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
			continue
		}
		if strings.HasSuffix(name, ".go") {
			hasGo = true
		}
		if child.IsDir() {
			wg.Add(1)
			go func(root, name string) {
				defer wg.Done()
				loadPkg(wg, root, name)
			}(root, filepath.Join(importpath, name))
		}
	}
	if hasGo {
		shortName := importPathToName(importpath)
		pkgIndex.Lock()
		pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
			importpath: importpath,
			dir:        dir,
		})
		pkgIndex.Unlock()
	}

}

// loadExports returns a list exports for a package.
var loadExports = loadExportsGoPath

func loadExportsGoPath(dir string) map[string]bool {
	exports := make(map[string]bool)
	buildPkg, err := build.ImportDir(dir, 0)
	if err != nil {
		if strings.Contains(err.Error(), "no buildable Go source files in") {
			return nil
		}
		fmt.Fprintf(os.Stderr, "could not import %q: %v", dir, err)
		return nil
	}
	fset := token.NewFileSet()
	for _, file := range buildPkg.GoFiles {
		f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
		if err != nil {
			fmt.Fprintf(os.Stderr, "could not parse %q: %v", file, err)
			continue
		}
		for name := range f.Scope.Objects {
			if ast.IsExported(name) {
				exports[name] = true
			}
		}
	}
	return exports
}

// findImport searches for a package with the given symbols.
// If no package is found, findImport returns "".
// Declared as a variable rather than a function so goimports can be easily
// extended by adding a file with an init function.
var findImport = findImportGoPath

func findImportGoPath(pkgName string, symbols map[string]bool) (string, bool, error) {
	// Fast path for the standard library.
	// In the common case we hopefully never have to scan the GOPATH, which can
	// be slow with moving disks.
	if pkg, rename, ok := findImportStdlib(pkgName, symbols); ok {
		return pkg, rename, nil
	}

	// TODO(sameer): look at the import lines for other Go files in the
	// local directory, since the user is likely to import the same packages
	// in the current Go file.  Return rename=true when the other Go files
	// use a renamed package that's also used in the current file.

	pkgIndexOnce.Do(loadPkgIndex)

	// Collect exports for packages with matching names.
	var wg sync.WaitGroup
	var pkgsMu sync.Mutex // guards pkgs
	// full importpath => exported symbol => True
	// e.g. "net/http" => "Client" => True
	pkgs := make(map[string]map[string]bool)
	pkgIndex.Lock()
	for _, pkg := range pkgIndex.m[pkgName] {
		wg.Add(1)
		go func(importpath, dir string) {
			defer wg.Done()
			exports := loadExports(dir)
			if exports != nil {
				pkgsMu.Lock()
				pkgs[importpath] = exports
				pkgsMu.Unlock()
			}
		}(pkg.importpath, pkg.dir)
	}
	pkgIndex.Unlock()
	wg.Wait()

	// Filter out packages missing required exported symbols.
	for symbol := range symbols {
		for importpath, exports := range pkgs {
			if !exports[symbol] {
				delete(pkgs, importpath)
			}
		}
	}
	if len(pkgs) == 0 {
		return "", false, nil
	}

	// If there are multiple candidate packages, the shortest one wins.
	// This is a heuristic to prefer the standard library (e.g. "bytes")
	// over e.g. "github.com/foo/bar/bytes".
	shortest := ""
	for importPath := range pkgs {
		if shortest == "" || len(importPath) < len(shortest) {
			shortest = importPath
		}
	}
	return shortest, false, nil
}

type visitFn func(node ast.Node) ast.Visitor

func (fn visitFn) Visit(node ast.Node) ast.Visitor {
	return fn(node)
}

func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) {
	for symbol := range symbols {
		path := stdlib[shortPkg+"."+symbol]
		if path == "" {
			return "", false, false
		}
		if importPath != "" && importPath != path {
			// Ambiguous. Symbols pointed to different things.
			return "", false, false
		}
		importPath = path
	}
	return importPath, false, importPath != ""
}
<|endoftext|>