<commit_before>package client

import (
	"crypto/tls"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"strconv"
	"strings"

	"github.com/caarlos0/log"
	"github.com/google/go-github/v48/github"
	"github.com/goreleaser/goreleaser/internal/artifact"
	"github.com/goreleaser/goreleaser/internal/tmpl"
	"github.com/goreleaser/goreleaser/pkg/config"
	"github.com/goreleaser/goreleaser/pkg/context"
	"golang.org/x/oauth2"
)

const DefaultGitHubDownloadURL = "https://github.com"

type githubClient struct {
	client *github.Client
}

// NewGitHub returns a github client implementation.
func NewGitHub(ctx *context.Context, token string) (GitHubClient, error) {
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: token},
	)

	httpClient := oauth2.NewClient(ctx, ts)
	base := httpClient.Transport.(*oauth2.Transport).Base
	if base == nil || reflect.ValueOf(base).IsNil() {
		base = http.DefaultTransport
	}
	// nolint: gosec
	base.(*http.Transport).TLSClientConfig = &tls.Config{
		InsecureSkipVerify: ctx.Config.GitHubURLs.SkipTLSVerify,
	}
	base.(*http.Transport).Proxy = http.ProxyFromEnvironment
	httpClient.Transport.(*oauth2.Transport).Base = base

	client := github.NewClient(httpClient)
	err := overrideGitHubClientAPI(ctx, client)
	if err != nil {
		return &githubClient{}, err
	}

	return &githubClient{client: client}, nil
}

func (c *githubClient) GenerateReleaseNotes(ctx *context.Context, repo Repo, prev, current string) (string, error) {
	notes, _, err := c.client.Repositories.GenerateReleaseNotes(ctx, repo.Owner, repo.Name, &github.GenerateNotesOptions{
		TagName:         current,
		PreviousTagName: github.String(prev),
	})
	if err != nil {
		return "", err
	}
	return notes.Body, err
}

func (c *githubClient) Changelog(ctx *context.Context, repo Repo, prev, current string) (string, error) {
	var log []string
	opts := &github.ListOptions{PerPage: 100}

	for {
		result, resp, err := c.client.Repositories.CompareCommits(ctx, repo.Owner, repo.Name, prev, current, opts)
		if err != nil {
			return "", err
		}
		for _, commit := range result.Commits {
			log = append(log, fmt.Sprintf(
				"%s: %s (@%s)",
				commit.GetSHA(),
				strings.Split(commit.Commit.GetMessage(), "\n")[0],
				commit.GetAuthor().GetLogin(),
			))
		}
		if resp.NextPage == 0 {
			break
		}
		opts.Page = resp.NextPage
	}

	return strings.Join(log, "\n"), nil
}

// GetDefaultBranch returns the default branch of a github repo
func (c *githubClient) GetDefaultBranch(ctx *context.Context, repo Repo) (string, error) {
	p, res, err := c.client.Repositories.Get(ctx, repo.Owner, repo.Name)
	if err != nil {
		log.WithFields(log.Fields{
			"projectID":  repo.String(),
			"statusCode": res.StatusCode,
			"err":        err.Error(),
		}).Warn("error checking for default branch")
		return "", err
	}
	return p.GetDefaultBranch(), nil
}

// CloseMilestone closes a given milestone.
func (c *githubClient) CloseMilestone(ctx *context.Context, repo Repo, title string) error {
	milestone, err := c.getMilestoneByTitle(ctx, repo, title)
	if err != nil {
		return err
	}

	if milestone == nil {
		return ErrNoMilestoneFound{Title: title}
	}

	closedState := "closed"
	milestone.State = &closedState

	_, _, err = c.client.Issues.EditMilestone(
		ctx,
		repo.Owner,
		repo.Name,
		*milestone.Number,
		milestone,
	)

	return err
}

func (c *githubClient) CreateFile(
	ctx *context.Context,
	commitAuthor config.CommitAuthor,
	repo Repo,
	content []byte,
	path,
	message string,
) error {
	var branch string
	var err error
	if repo.Branch != "" {
		branch = repo.Branch
	} else {
		branch, err = c.GetDefaultBranch(ctx, repo)
		if err != nil {
			// Fall back to sdk default
			log.WithFields(log.Fields{
				"fileName":        path,
				"projectID":       repo.String(),
				"requestedBranch": branch,
				"err":             err.Error(),
			}).Warn("error checking for default branch, using master")
		}
	}
	options := &github.RepositoryContentFileOptions{
		Committer: &github.CommitAuthor{
			Name:  github.String(commitAuthor.Name),
			Email: github.String(commitAuthor.Email),
		},
		Content: content,
		Message: github.String(message),
	}

	// Set the branch if we got it above...otherwise, just default to
	// whatever the SDK does auto-magically
	if branch != "" {
		options.Branch = &branch
	}

	file, _, res, err := c.client.Repositories.GetContents(
		ctx,
		repo.Owner,
		repo.Name,
		path,
		&github.RepositoryContentGetOptions{},
	)
	if err != nil && (res == nil || res.StatusCode != 404) {
		return err
	}

	if res.StatusCode == 404 {
		_, _, err = c.client.Repositories.CreateFile(
			ctx,
			repo.Owner,
			repo.Name,
			path,
			options,
		)
		return err
	}
	options.SHA = file.SHA
	_, _, err = c.client.Repositories.UpdateFile(
		ctx,
		repo.Owner,
		repo.Name,
		path,
		options,
	)
	return err
}

func (c *githubClient) CreateRelease(ctx *context.Context, body string) (string, error) {
	title, err := tmpl.New(ctx).Apply(ctx.Config.Release.NameTemplate)
	if err != nil {
		return "", err
	}

	if ctx.Config.Release.Draft && ctx.Config.Release.ReplaceExistingDraft {
		if err := c.deleteExistingDraftRelease(ctx, title); err != nil {
			return "", err
		}
	}

	// Truncate the release notes if it's too long (github doesn't allow more than 125000 characters)
	body = truncateReleaseBody(body)

	data := &github.RepositoryRelease{
		Name:       github.String(title),
		TagName:    github.String(ctx.Git.CurrentTag),
		Body:       github.String(body),
		Draft:      github.Bool(ctx.Config.Release.Draft),
		Prerelease: github.Bool(ctx.PreRelease),
	}

	if ctx.Config.Release.DiscussionCategoryName != "" {
		data.DiscussionCategoryName = github.String(ctx.Config.Release.DiscussionCategoryName)
	}

	if target := ctx.Config.Release.TargetCommitish; target != "" {
		target, err := tmpl.New(ctx).Apply(target)
		if err != nil {
			return "", err
		}
		if target != "" {
			data.TargetCommitish = github.String(target)
		}
	}

	release, err := c.createOrUpdateRelease(ctx, data, body)
	if err != nil {
		return "", fmt.Errorf("could not release: %w", err)
	}

	if release.GetDraft() != ctx.Config.Release.Draft {
		// sometimes, for unknown reasons, the release is created as a draft, even though it isn't.
		// This should publish it.
		release.Draft = github.Bool(ctx.Config.Release.Draft)
		if _, err := c.updateRelease(ctx, release.GetID(), release); err != nil {
			return "", fmt.Errorf("could not update existing release: %w", err)
		}
	}

	return strconv.FormatInt(release.GetID(), 10), nil
}

func (c *githubClient) createOrUpdateRelease(ctx *context.Context, data *github.RepositoryRelease, body string) (*github.RepositoryRelease, error) {
	release, _, err := c.client.Repositories.GetReleaseByTag(
		ctx,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
		data.GetTagName(),
	)
	if err != nil {
		release, _, err = c.client.Repositories.CreateRelease(
			ctx,
			ctx.Config.Release.GitHub.Owner,
			ctx.Config.Release.GitHub.Name,
			data,
		)
		return release, err
	}

	data.Body = github.String(getReleaseNotes(release.GetBody(), body, ctx.Config.Release.ReleaseNotesMode))
	return c.updateRelease(ctx, release.GetID(), data)
}

func (c *githubClient) updateRelease(ctx *context.Context, id int64, data *github.RepositoryRelease) (*github.RepositoryRelease, error) {
	repo, _, err := c.client.Repositories.EditRelease(
		ctx,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
		id,
		data,
	)
	return repo, err
}

func (c *githubClient) ReleaseURLTemplate(ctx *context.Context) (string, error) {
	downloadURL, err := tmpl.New(ctx).Apply(ctx.Config.GitHubURLs.Download)
	if err != nil {
		return "", fmt.Errorf("templating GitHub download URL: %w", err)
	}

	return fmt.Sprintf(
		"%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}",
		downloadURL,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
	), nil
}

func (c *githubClient) Upload(
	ctx *context.Context,
	releaseID string,
	artifact *artifact.Artifact,
	file *os.File,
) error {
	githubReleaseID, err := strconv.ParseInt(releaseID, 10, 64)
	if err != nil {
		return err
	}
	_, resp, err := c.client.Repositories.UploadReleaseAsset(
		ctx,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
		githubReleaseID,
		&github.UploadOptions{
			Name: artifact.Name,
		},
		file,
	)
	if err == nil {
		return nil
	}
	if resp != nil && resp.StatusCode == 422 {
		return err
	}
	return RetriableError{err}
}

// getMilestoneByTitle returns a milestone by title.
func (c *githubClient) getMilestoneByTitle(ctx *context.Context, repo Repo, title string) (*github.Milestone, error) {
	// The GitHub API/SDK does not provide lookup by title functionality currently.
	opts := &github.MilestoneListOptions{
		ListOptions: github.ListOptions{PerPage: 100},
	}

	for {
		milestones, resp, err := c.client.Issues.ListMilestones(
			ctx,
			repo.Owner,
			repo.Name,
			opts,
		)
		if err != nil {
			return nil, err
		}

		for _, m := range milestones {
			if m != nil && m.Title != nil && *m.Title == title {
				return m, nil
			}
		}

		if resp.NextPage == 0 {
			break
		}

		opts.Page = resp.NextPage
	}

	return nil, nil
}

func overrideGitHubClientAPI(ctx *context.Context, client *github.Client) error {
	if ctx.Config.GitHubURLs.API == "" {
		return nil
	}

	apiURL, err := tmpl.New(ctx).Apply(ctx.Config.GitHubURLs.API)
	if err != nil {
		return fmt.Errorf("templating GitHub API URL: %w", err)
	}
	api, err := url.Parse(apiURL)
	if err != nil {
		return err
	}

	uploadURL, err := tmpl.New(ctx).Apply(ctx.Config.GitHubURLs.Upload)
	if err != nil {
		return fmt.Errorf("templating GitHub upload URL: %w", err)
	}
	upload, err := url.Parse(uploadURL)
	if err != nil {
		return err
	}

	client.BaseURL = api
	client.UploadURL = upload

	return nil
}

func (c *githubClient) deleteExistingDraftRelease(ctx *context.Context, name string) error {
	opt := github.ListOptions{PerPage: 50}
	for {
		releases, resp, err := c.client.Repositories.ListReleases(
			ctx,
			ctx.Config.Release.GitHub.Owner,
			ctx.Config.Release.GitHub.Name,
			&opt,
		)
		if err != nil {
			return fmt.Errorf("could not delete existing drafts: %w", err)
		}
		for _, r := range releases {
			if r.GetDraft() && r.GetName() == name {
				if _, err := c.client.Repositories.DeleteRelease(
					ctx,
					ctx.Config.Release.GitHub.Owner,
					ctx.Config.Release.GitHub.Name,
					r.GetID(),
				); err != nil {
					return fmt.Errorf("could not delete previous draft release: %w", err)
				}

				log.WithFields(log.Fields{
					"commit": r.GetTargetCommitish(),
					"tag":    r.GetTagName(),
					"name":   r.GetName(),
				}).Info("deleted previous draft release")

				// in theory, there should be only 1 release matching, so we can just return
				return nil
			}
		}
		if resp.NextPage == 0 {
			return nil
		}
		opt.Page = resp.NextPage
	}
}
<commit_msg>revert: unneeded release update<commit_after>package client

import (
	"crypto/tls"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"strconv"
	"strings"

	"github.com/caarlos0/log"
	"github.com/google/go-github/v48/github"
	"github.com/goreleaser/goreleaser/internal/artifact"
	"github.com/goreleaser/goreleaser/internal/tmpl"
	"github.com/goreleaser/goreleaser/pkg/config"
	"github.com/goreleaser/goreleaser/pkg/context"
	"golang.org/x/oauth2"
)

const DefaultGitHubDownloadURL = "https://github.com"

type githubClient struct {
	client *github.Client
}

// NewGitHub returns a github client implementation.
func NewGitHub(ctx *context.Context, token string) (GitHubClient, error) {
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: token},
	)

	httpClient := oauth2.NewClient(ctx, ts)
	base := httpClient.Transport.(*oauth2.Transport).Base
	if base == nil || reflect.ValueOf(base).IsNil() {
		base = http.DefaultTransport
	}
	// nolint: gosec
	base.(*http.Transport).TLSClientConfig = &tls.Config{
		InsecureSkipVerify: ctx.Config.GitHubURLs.SkipTLSVerify,
	}
	base.(*http.Transport).Proxy = http.ProxyFromEnvironment
	httpClient.Transport.(*oauth2.Transport).Base = base

	client := github.NewClient(httpClient)
	err := overrideGitHubClientAPI(ctx, client)
	if err != nil {
		return &githubClient{}, err
	}

	return &githubClient{client: client}, nil
}

func (c *githubClient) GenerateReleaseNotes(ctx *context.Context, repo Repo, prev, current string) (string, error) {
	notes, _, err := c.client.Repositories.GenerateReleaseNotes(ctx, repo.Owner, repo.Name, &github.GenerateNotesOptions{
		TagName:         current,
		PreviousTagName: github.String(prev),
	})
	if err != nil {
		return "", err
	}
	return notes.Body, err
}

func (c *githubClient) Changelog(ctx *context.Context, repo Repo, prev, current string) (string, error) {
	var log []string
	opts := &github.ListOptions{PerPage: 100}

	for {
		result, resp, err := c.client.Repositories.CompareCommits(ctx, repo.Owner, repo.Name, prev, current, opts)
		if err != nil {
			return "", err
		}
		for _, commit := range result.Commits {
			log = append(log, fmt.Sprintf(
				"%s: %s (@%s)",
				commit.GetSHA(),
				strings.Split(commit.Commit.GetMessage(), "\n")[0],
				commit.GetAuthor().GetLogin(),
			))
		}
		if resp.NextPage == 0 {
			break
		}
		opts.Page = resp.NextPage
	}

	return strings.Join(log, "\n"), nil
}

// GetDefaultBranch returns the default branch of a github repo
func (c *githubClient) GetDefaultBranch(ctx *context.Context, repo Repo) (string, error) {
	p, res, err := c.client.Repositories.Get(ctx, repo.Owner, repo.Name)
	if err != nil {
		log.WithFields(log.Fields{
			"projectID":  repo.String(),
			"statusCode": res.StatusCode,
			"err":        err.Error(),
		}).Warn("error checking for default branch")
		return "", err
	}
	return p.GetDefaultBranch(), nil
}

// CloseMilestone closes a given milestone.
func (c *githubClient) CloseMilestone(ctx *context.Context, repo Repo, title string) error {
	milestone, err := c.getMilestoneByTitle(ctx, repo, title)
	if err != nil {
		return err
	}

	if milestone == nil {
		return ErrNoMilestoneFound{Title: title}
	}

	closedState := "closed"
	milestone.State = &closedState

	_, _, err = c.client.Issues.EditMilestone(
		ctx,
		repo.Owner,
		repo.Name,
		*milestone.Number,
		milestone,
	)

	return err
}

func (c *githubClient) CreateFile(
	ctx *context.Context,
	commitAuthor config.CommitAuthor,
	repo Repo,
	content []byte,
	path,
	message string,
) error {
	var branch string
	var err error
	if repo.Branch != "" {
		branch = repo.Branch
	} else {
		branch, err = c.GetDefaultBranch(ctx, repo)
		if err != nil {
			// Fall back to sdk default
			log.WithFields(log.Fields{
				"fileName":        path,
				"projectID":       repo.String(),
				"requestedBranch": branch,
				"err":             err.Error(),
			}).Warn("error checking for default branch, using master")
		}
	}
	options := &github.RepositoryContentFileOptions{
		Committer: &github.CommitAuthor{
			Name:  github.String(commitAuthor.Name),
			Email: github.String(commitAuthor.Email),
		},
		Content: content,
		Message: github.String(message),
	}

	// Set the branch if we got it above...otherwise, just default to
	// whatever the SDK does auto-magically
	if branch != "" {
		options.Branch = &branch
	}

	file, _, res, err := c.client.Repositories.GetContents(
		ctx,
		repo.Owner,
		repo.Name,
		path,
		&github.RepositoryContentGetOptions{},
	)
	if err != nil && (res == nil || res.StatusCode != 404) {
		return err
	}

	if res.StatusCode == 404 {
		_, _, err = c.client.Repositories.CreateFile(
			ctx,
			repo.Owner,
			repo.Name,
			path,
			options,
		)
		return err
	}
	options.SHA = file.SHA
	_, _, err = c.client.Repositories.UpdateFile(
		ctx,
		repo.Owner,
		repo.Name,
		path,
		options,
	)
	return err
}

func (c *githubClient) CreateRelease(ctx *context.Context, body string) (string, error) {
	title, err := tmpl.New(ctx).Apply(ctx.Config.Release.NameTemplate)
	if err != nil {
		return "", err
	}

	if ctx.Config.Release.Draft && ctx.Config.Release.ReplaceExistingDraft {
		if err := c.deleteExistingDraftRelease(ctx, title); err != nil {
			return "", err
		}
	}

	// Truncate the release notes if it's too long (github doesn't allow more than 125000 characters)
	body = truncateReleaseBody(body)

	data := &github.RepositoryRelease{
		Name:       github.String(title),
		TagName:    github.String(ctx.Git.CurrentTag),
		Body:       github.String(body),
		Draft:      github.Bool(ctx.Config.Release.Draft),
		Prerelease: github.Bool(ctx.PreRelease),
	}

	if ctx.Config.Release.DiscussionCategoryName != "" {
		data.DiscussionCategoryName = github.String(ctx.Config.Release.DiscussionCategoryName)
	}

	if target := ctx.Config.Release.TargetCommitish; target != "" {
		target, err := tmpl.New(ctx).Apply(target)
		if err != nil {
			return "", err
		}
		if target != "" {
			data.TargetCommitish = github.String(target)
		}
	}

	release, err := c.createOrUpdateRelease(ctx, data, body)
	if err != nil {
		return "", fmt.Errorf("could not release: %w", err)
	}

	return strconv.FormatInt(release.GetID(), 10), nil
}

func (c *githubClient) createOrUpdateRelease(ctx *context.Context, data *github.RepositoryRelease, body string) (*github.RepositoryRelease, error) {
	release, _, err := c.client.Repositories.GetReleaseByTag(
		ctx,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
		data.GetTagName(),
	)
	if err != nil {
		release, _, err = c.client.Repositories.CreateRelease(
			ctx,
			ctx.Config.Release.GitHub.Owner,
			ctx.Config.Release.GitHub.Name,
			data,
		)
		return release, err
	}

	data.Body = github.String(getReleaseNotes(release.GetBody(), body, ctx.Config.Release.ReleaseNotesMode))
	return c.updateRelease(ctx, release.GetID(), data)
}

func (c *githubClient) updateRelease(ctx *context.Context, id int64, data *github.RepositoryRelease) (*github.RepositoryRelease, error) {
	repo, _, err := c.client.Repositories.EditRelease(
		ctx,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
		id,
		data,
	)
	return repo, err
}

func (c *githubClient) ReleaseURLTemplate(ctx *context.Context) (string, error) {
	downloadURL, err := tmpl.New(ctx).Apply(ctx.Config.GitHubURLs.Download)
	if err != nil {
		return "", fmt.Errorf("templating GitHub download URL: %w", err)
	}

	return fmt.Sprintf(
		"%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}",
		downloadURL,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
	), nil
}

func (c *githubClient) Upload(
	ctx *context.Context,
	releaseID string,
	artifact *artifact.Artifact,
	file *os.File,
) error {
	githubReleaseID, err := strconv.ParseInt(releaseID, 10, 64)
	if err != nil {
		return err
	}
	_, resp, err := c.client.Repositories.UploadReleaseAsset(
		ctx,
		ctx.Config.Release.GitHub.Owner,
		ctx.Config.Release.GitHub.Name,
		githubReleaseID,
		&github.UploadOptions{
			Name: artifact.Name,
		},
		file,
	)
	if err == nil {
		return nil
	}
	if resp != nil && resp.StatusCode == 422 {
		return err
	}
	return RetriableError{err}
}

// getMilestoneByTitle returns a milestone by title.
func (c *githubClient) getMilestoneByTitle(ctx *context.Context, repo Repo, title string) (*github.Milestone, error) {
	// The GitHub API/SDK does not provide lookup by title functionality currently.
	opts := &github.MilestoneListOptions{
		ListOptions: github.ListOptions{PerPage: 100},
	}

	for {
		milestones, resp, err := c.client.Issues.ListMilestones(
			ctx,
			repo.Owner,
			repo.Name,
			opts,
		)
		if err != nil {
			return nil, err
		}

		for _, m := range milestones {
			if m != nil && m.Title != nil && *m.Title == title {
				return m, nil
			}
		}

		if resp.NextPage == 0 {
			break
		}

		opts.Page = resp.NextPage
	}

	return nil, nil
}

func overrideGitHubClientAPI(ctx *context.Context, client *github.Client) error {
	if ctx.Config.GitHubURLs.API == "" {
		return nil
	}

	apiURL, err := tmpl.New(ctx).Apply(ctx.Config.GitHubURLs.API)
	if err != nil {
		return fmt.Errorf("templating GitHub API URL: %w", err)
	}
	api, err := url.Parse(apiURL)
	if err != nil {
		return err
	}

	uploadURL, err := tmpl.New(ctx).Apply(ctx.Config.GitHubURLs.Upload)
	if err != nil {
		return fmt.Errorf("templating GitHub upload URL: %w", err)
	}
	upload, err := url.Parse(uploadURL)
	if err != nil {
		return err
	}

	client.BaseURL = api
	client.UploadURL = upload

	return nil
}

func (c *githubClient) deleteExistingDraftRelease(ctx *context.Context, name string) error {
	opt := github.ListOptions{PerPage: 50}
	for {
		releases, resp, err := c.client.Repositories.ListReleases(
			ctx,
			ctx.Config.Release.GitHub.Owner,
			ctx.Config.Release.GitHub.Name,
			&opt,
		)
		if err != nil {
			return fmt.Errorf("could not delete existing drafts: %w", err)
		}
		for _, r := range releases {
			if r.GetDraft() && r.GetName() == name {
				if _, err := c.client.Repositories.DeleteRelease(
					ctx,
					ctx.Config.Release.GitHub.Owner,
					ctx.Config.Release.GitHub.Name,
					r.GetID(),
				); err != nil {
					return fmt.Errorf("could not delete previous draft release: %w", err)
				}

				log.WithFields(log.Fields{
					"commit": r.GetTargetCommitish(),
					"tag":    r.GetTagName(),
					"name":   r.GetName(),
				}).Info("deleted previous draft release")

				// in theory, there should be only 1 release matching, so we can just return
				return nil
			}
		}
		if resp.NextPage == 0 {
			return nil
		}
		opt.Page = resp.NextPage
	}
}
<|endoftext|>


<commit_before>package docker

import (
	"os"
	"runtime"

	api "github.com/fsouza/go-dockerclient"
)

// Config represents the docker configuration.
type Config struct {
	Host string
}

// Docker represents a docker client.
type Docker struct {
	client *api.Client
	host   string
}

// NewDocker creates a new docker client.
func NewDocker(args ...*Config) (*Docker, error) {
	var client *api.Client
	var err error
	var host string

	// Find docker host for local machine.
	if len(args) > 0 {
		host = args[0].Host
	} else if os.Getenv("DOCKER_HOST") != "" {
		host = os.Getenv("DOCKER_HOST")
	} else if runtime.GOOS == "windows" {
		host = "http://localhost:2375"
	} else {
		host = "unix:///var/run/docker.sock"
	}

	client, err = api.NewClient(host)

	if err != nil {
		return nil, err
	}

	return &Docker{
		client: client,
		host:   host,
	}, nil
}

// Host will return the docker host that is used.
func (d *Docker) Host() string {
	return d.host
}

// Prune removes all unused containers, volumes, networks and images (both dangling and unreferenced).
func (d *Docker) Prune() error {
	if _, err := d.client.PruneContainers(api.PruneContainersOptions{}); err != nil {
		return err
	}

	if _, err := d.client.PruneImages(api.PruneImagesOptions{}); err != nil {
		return err
	}

	if _, err := d.client.PruneVolumes(api.PruneVolumesOptions{}); err != nil {
		return err
	}

	if _, err := d.client.PruneNetworks(api.PruneNetworksOptions{}); err != nil {
		return err
	}

	return nil
}

// RemoveContainer removes a container by name.
func (d *Docker) RemoveContainer(name string) error {
	container, err := d.findContainer(name)

	if err != nil {
		return err
	}

	return d.removeContainer(container)
}
<commit_msg>Remove prune method<commit_after>package docker

import (
	"os"
	"runtime"

	api "github.com/fsouza/go-dockerclient"
)

// Config represents the docker configuration.
type Config struct {
	Host string
}

// Docker represents a docker client.
type Docker struct {
	client *api.Client
	host   string
}

// NewDocker creates a new docker client.
func NewDocker(args ...*Config) (*Docker, error) {
	var client *api.Client
	var err error
	var host string

	// Find docker host for local machine.
	if len(args) > 0 {
		host = args[0].Host
	} else if os.Getenv("DOCKER_HOST") != "" {
		host = os.Getenv("DOCKER_HOST")
	} else if runtime.GOOS == "windows" {
		host = "http://localhost:2375"
	} else {
		host = "unix:///var/run/docker.sock"
	}

	client, err = api.NewClient(host)

	if err != nil {
		return nil, err
	}

	return &Docker{
		client: client,
		host:   host,
	}, nil
}

// Host will return the docker host that is used.
func (d *Docker) Host() string {
	return d.host
}

// RemoveContainer removes a container by name.
func (d *Docker) RemoveContainer(name string) error {
	container, err := d.findContainer(name)

	if err != nil {
		return err
	}

	return d.removeContainer(container)
}
<|endoftext|>


<commit_before>// Copyright 2020 The Ebiten 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 shader

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"regexp"
	"sort"
	"strings"
)

const (
	varyingStructName = "VertexOut"
)

var (
	kageTagRe = regexp.MustCompile("^`" + `kage:\"(.+)\"` + "`$")
)

type variable struct {
	name string
	typ  typ
	init string
}

type constant struct {
	name string
	typ  typ
	init string
}

type function struct {
	name string
	args []variable
	rets []variable
	body *block
}

type Shader struct {
	fs *token.FileSet

	// position is the field name of VertexOut that represents a vertex position (gl_Position in GLSL).
	position variable

	// varyings is a collection of varying variables.
	varyings []variable

	// uniforms is a collection of uniform variables.
	uniforms []variable

	global block

	errs []string
}

type ParseError struct {
	errs []string
}

func (p *ParseError) Error() string {
	return strings.Join(p.errs, "\n")
}

func NewShader(src []byte) (*Shader, error) {
	fs := token.NewFileSet()
	f, err := parser.ParseFile(fs, "", src, parser.AllErrors)
	if err != nil {
		return nil, err
	}

	s := &Shader{
		fs: fs,
	}
	s.parse(f)

	if len(s.errs) > 0 {
		return nil, &ParseError{s.errs}
	}

	// TODO: Resolve identifiers?
	// TODO: Resolve constants

	// TODO: Make a call graph and reorder the elements.
	return s, nil
}

func (s *Shader) addError(pos token.Pos, str string) {
	p := s.fs.Position(pos)
	s.errs = append(s.errs, fmt.Sprintf("%s: %s", p, str))
}

func (sh *Shader) parse(f *ast.File) {
	for _, d := range f.Decls {
		sh.parseDecl(&sh.global, d)
	}

	vars := make([]variable, len(sh.global.vars))
	copy(vars, sh.global.vars)
	sh.global.vars = nil
	for _, v := range vars {
		if 'A' <= v.name[0] && v.name[0] <= 'Z' {
			sh.uniforms = append(sh.uniforms, v)
		} else {
			sh.global.vars = append(sh.global.vars, v)
		}
	}

	// TODO: This is duplicated with parseBlock.
	sort.Slice(sh.global.vars, func(a, b int) bool {
		return sh.global.vars[a].name < sh.global.vars[b].name
	})
	sort.Slice(sh.global.consts, func(a, b int) bool {
		return sh.global.consts[a].name < sh.global.consts[b].name
	})
	sort.Slice(sh.global.funcs, func(a, b int) bool {
		return sh.global.funcs[a].name < sh.global.funcs[b].name
	})
	sort.Slice(sh.varyings, func(a, b int) bool {
		return sh.varyings[a].name < sh.varyings[b].name
	})
	sort.Slice(sh.uniforms, func(a, b int) bool {
		return sh.uniforms[a].name < sh.uniforms[b].name
	})
}

func (sh *Shader) parseDecl(b *block, d ast.Decl) {
	switch d := d.(type) {
	case *ast.GenDecl:
		switch d.Tok {
		case token.TYPE:
			// TODO: Parse regular structs or other types
			for _, s := range d.Specs {
				s := s.(*ast.TypeSpec)
				if s.Name.Name == varyingStructName {
					sh.parseVaryingStruct(s)
				}
			}
		case token.CONST:
			for _, s := range d.Specs {
				s := s.(*ast.ValueSpec)
				cs := sh.parseConstant(s)
				b.consts = append(b.consts, cs...)
			}
		case token.VAR:
			for _, s := range d.Specs {
				s := s.(*ast.ValueSpec)
				vs := sh.parseVariable(s)
				b.vars = append(b.vars, vs...)
			}
		case token.IMPORT:
			sh.addError(d.Pos(), "import is forbidden")
		default:
			sh.addError(d.Pos(), "unexpected token")
		}
	case *ast.FuncDecl:
		b.funcs = append(b.funcs, sh.parseFunc(d))
	default:
		sh.addError(d.Pos(), "unexpected decl")
	}
}

func (sh *Shader) parseVaryingStruct(t *ast.TypeSpec) {
	s, ok := t.Type.(*ast.StructType)
	if !ok {
		sh.addError(t.Type.Pos(), fmt.Sprintf("%s must be a struct but not", t.Name))
		return
	}

	for _, f := range s.Fields.List {
		if f.Tag != nil {
			tag := f.Tag.Value
			m := kageTagRe.FindStringSubmatch(tag)
			if m == nil {
				sh.addError(f.Tag.Pos(), fmt.Sprintf("invalid struct tag: %s", tag))
				continue
			}
			if m[1] != "position" {
				sh.addError(f.Tag.Pos(), fmt.Sprintf("struct tag value must be position in %s but %s", varyingStructName, m[1]))
				continue
			}
			if len(f.Names) != 1 {
				sh.addError(f.Pos(), fmt.Sprintf("position members must be one"))
				continue
			}
			t, err := parseType(f.Type)
			if err != nil {
				sh.addError(f.Type.Pos(), err.Error())
				continue
			}
			if t != typVec4 {
				sh.addError(f.Type.Pos(), fmt.Sprintf("position must be vec4 but %s", t))
				continue
			}
			sh.position = variable{
				name: f.Names[0].Name,
				typ:  t,
			}
			continue
		}
		t, err := parseType(f.Type)
		if err != nil {
			sh.addError(f.Type.Pos(), err.Error())
			continue
		}
		if !t.numeric() {
			sh.addError(f.Type.Pos(), fmt.Sprintf("members in %s must be numeric but %s", varyingStructName, t))
			continue
		}
		for _, n := range f.Names {
			sh.varyings = append(sh.varyings, variable{
				name: n.Name,
				typ:  t,
			})
		}
	}
}

func (s *Shader) parseVariable(vs *ast.ValueSpec) []variable {
	var t typ
	if vs.Type != nil {
		var err error
		t, err = parseType(vs.Type)
		if err != nil {
			s.addError(vs.Type.Pos(), err.Error())
			return nil
		}
	}

	var vars []variable
	for _, n := range vs.Names {
		name := n.Name
		vars = append(vars, variable{
			name: name,
			typ:  t,
		})
	}
	return vars
}

func (s *Shader) parseConstant(vs *ast.ValueSpec) []constant {
	var t typ
	if vs.Type != nil {
		var err error
		t, err = parseType(vs.Type)
		if err != nil {
			s.addError(vs.Type.Pos(), err.Error())
			return nil
		}
	}

	var cs []constant
	for i, n := range vs.Names {
		v := vs.Values[i]
		var init string
		switch v := v.(type) {
		case *ast.BasicLit:
			if v.Kind != token.INT && v.Kind != token.FLOAT {
				s.addError(v.Pos(), fmt.Sprintf("literal must be int or float but %s", v.Kind))
				return cs
			}
			init = v.Value // TODO: This should be go/constant.Value
		default:
			// TODO: Parse the expression.
		}
		cs = append(cs, constant{
			name: n.Name,
			typ:  t,
			init: init,
		})
	}
	return cs
}

func (sh *Shader) parseFunc(d *ast.FuncDecl) function {
	if d.Name == nil {
		sh.addError(d.Pos(), "function must have a name")
		return function{}
	}
	if d.Body == nil {
		sh.addError(d.Pos(), "function must have a body")
		return function{}
	}

	var args []variable
	for _, f := range d.Type.Params.List {
		t, err := parseType(f.Type)
		if err != nil {
			sh.addError(f.Type.Pos(), err.Error())
			continue
		}
		for _, n := range f.Names {
			args = append(args, variable{
				name: n.Name,
				typ:  t,
			})
		}
	}

	var rets []variable
	for _, f := range d.Type.Results.List {
		t, err := parseType(f.Type)
		if err != nil {
			sh.addError(f.Type.Pos(), err.Error())
			continue
		}
		if len(f.Names) == 0 {
			rets = append(rets, variable{
				name: "",
				typ:  t,
			})
		} else {
			for _, n := range f.Names {
				rets = append(rets, variable{
					name: n.Name,
					typ:  t,
				})
			}
		}
	}

	return function{
		name: d.Name.Name,
		args: args,
		rets: rets,
		body: sh.parseBlock(d.Body),
	}
}

func (sh *Shader) parseBlock(b *ast.BlockStmt) *block {
	block := &block{}

	for _, l := range b.List {
		switch l := l.(type) {
		case *ast.AssignStmt:
			if l.Tok == token.DEFINE {
				for _, s := range l.Lhs {
					ident := s.(*ast.Ident)
					block.vars = append(block.vars, variable{
						name: ident.Name,
					})
				}
			} else {
				// TODO
			}
		case *ast.DeclStmt:
			sh.parseDecl(block, l.Decl)
		case *ast.ReturnStmt:
			var exprs []expr
			for _, r := range l.Results {
				exprs = append(exprs, sh.parseExpr(r))
			}
			block.stmts = append(block.stmts, stmt{
				stmtType: stmtReturn,
				exprs:    exprs,
			})
		default:
		}
	}

	sort.Slice(block.vars, func(a, b int) bool {
		return block.vars[a].name < block.vars[b].name
	})
	sort.Slice(block.consts, func(a, b int) bool {
		return block.consts[a].name < block.consts[b].name
	})
	sort.Slice(block.funcs, func(a, b int) bool {
		return block.funcs[a].name < block.funcs[b].name
	})

	return block
}

func (sh *Shader) parseExpr(e ast.Expr) expr {
	switch e := e.(type) {
	case *ast.Ident:
		return expr{
			exprType: exprIdent,
			value:    e.Name,
		}
	}
	return expr{}
}

// Dump dumps the shader state in an intermediate language.
func (s *Shader) Dump() string {
	var lines []string

	lines = append(lines, fmt.Sprintf("var %s varying %s // position", s.position.name, s.position.typ))
	for _, v := range s.varyings {
		lines = append(lines, fmt.Sprintf("var %s varying %s", v.name, v.typ))
	}

	for _, u := range s.uniforms {
		lines = append(lines, fmt.Sprintf("var %s uniform %s", u.name, u.typ))
	}

	lines = append(lines, s.global.dump(0)...)

	return strings.Join(lines, "\n") + "\n"
}

func (s *Shader) GlslVertex() string {
	var lines []string

	for _, v := range s.varyings {
		// TODO: variable names must be escaped not to conflict with keywords.
		lines = append(lines, fmt.Sprintf("varying %s %s;", v.typ.glslString(), v.name))
	}
	return strings.Join(lines, "\n") + "\n"
}
<commit_msg>shader: Do not sort variables since the order matters<commit_after>// Copyright 2020 The Ebiten 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 shader

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"regexp"
	"sort"
	"strings"
)

const (
	varyingStructName = "VertexOut"
)

var (
	kageTagRe = regexp.MustCompile("^`" + `kage:\"(.+)\"` + "`$")
)

type variable struct {
	name string
	typ  typ
	init string
}

type constant struct {
	name string
	typ  typ
	init string
}

type function struct {
	name string
	args []variable
	rets []variable
	body *block
}

type Shader struct {
	fs *token.FileSet

	// position is the field name of VertexOut that represents a vertex position (gl_Position in GLSL).
	position variable

	// varyings is a collection of varying variables.
	varyings []variable

	// uniforms is a collection of uniform variables.
	uniforms []variable

	global block

	errs []string
}

type ParseError struct {
	errs []string
}

func (p *ParseError) Error() string {
	return strings.Join(p.errs, "\n")
}

func NewShader(src []byte) (*Shader, error) {
	fs := token.NewFileSet()
	f, err := parser.ParseFile(fs, "", src, parser.AllErrors)
	if err != nil {
		return nil, err
	}

	s := &Shader{
		fs: fs,
	}
	s.parse(f)

	if len(s.errs) > 0 {
		return nil, &ParseError{s.errs}
	}

	// TODO: Resolve identifiers?
	// TODO: Resolve constants

	// TODO: Make a call graph and reorder the elements.
	return s, nil
}

func (s *Shader) addError(pos token.Pos, str string) {
	p := s.fs.Position(pos)
	s.errs = append(s.errs, fmt.Sprintf("%s: %s", p, str))
}

func (sh *Shader) parse(f *ast.File) {
	for _, d := range f.Decls {
		sh.parseDecl(&sh.global, d)
	}

	vars := make([]variable, len(sh.global.vars))
	copy(vars, sh.global.vars)
	sh.global.vars = nil
	for _, v := range vars {
		if 'A' <= v.name[0] && v.name[0] <= 'Z' {
			sh.uniforms = append(sh.uniforms, v)
		} else {
			sh.global.vars = append(sh.global.vars, v)
		}
	}

	// TODO: This is duplicated with parseBlock.
	sort.Slice(sh.global.consts, func(a, b int) bool {
		return sh.global.consts[a].name < sh.global.consts[b].name
	})
	sort.Slice(sh.global.funcs, func(a, b int) bool {
		return sh.global.funcs[a].name < sh.global.funcs[b].name
	})
	sort.Slice(sh.varyings, func(a, b int) bool {
		return sh.varyings[a].name < sh.varyings[b].name
	})
	sort.Slice(sh.uniforms, func(a, b int) bool {
		return sh.uniforms[a].name < sh.uniforms[b].name
	})
}

func (sh *Shader) parseDecl(b *block, d ast.Decl) {
	switch d := d.(type) {
	case *ast.GenDecl:
		switch d.Tok {
		case token.TYPE:
			// TODO: Parse regular structs or other types
			for _, s := range d.Specs {
				s := s.(*ast.TypeSpec)
				if s.Name.Name == varyingStructName {
					sh.parseVaryingStruct(s)
				}
			}
		case token.CONST:
			for _, s := range d.Specs {
				s := s.(*ast.ValueSpec)
				cs := sh.parseConstant(s)
				b.consts = append(b.consts, cs...)
			}
		case token.VAR:
			for _, s := range d.Specs {
				s := s.(*ast.ValueSpec)
				vs := sh.parseVariable(s)
				b.vars = append(b.vars, vs...)
			}
		case token.IMPORT:
			sh.addError(d.Pos(), "import is forbidden")
		default:
			sh.addError(d.Pos(), "unexpected token")
		}
	case *ast.FuncDecl:
		b.funcs = append(b.funcs, sh.parseFunc(d))
	default:
		sh.addError(d.Pos(), "unexpected decl")
	}
}

func (sh *Shader) parseVaryingStruct(t *ast.TypeSpec) {
	s, ok := t.Type.(*ast.StructType)
	if !ok {
		sh.addError(t.Type.Pos(), fmt.Sprintf("%s must be a struct but not", t.Name))
		return
	}

	for _, f := range s.Fields.List {
		if f.Tag != nil {
			tag := f.Tag.Value
			m := kageTagRe.FindStringSubmatch(tag)
			if m == nil {
				sh.addError(f.Tag.Pos(), fmt.Sprintf("invalid struct tag: %s", tag))
				continue
			}
			if m[1] != "position" {
				sh.addError(f.Tag.Pos(), fmt.Sprintf("struct tag value must be position in %s but %s", varyingStructName, m[1]))
				continue
			}
			if len(f.Names) != 1 {
				sh.addError(f.Pos(), fmt.Sprintf("position members must be one"))
				continue
			}
			t, err := parseType(f.Type)
			if err != nil {
				sh.addError(f.Type.Pos(), err.Error())
				continue
			}
			if t != typVec4 {
				sh.addError(f.Type.Pos(), fmt.Sprintf("position must be vec4 but %s", t))
				continue
			}
			sh.position = variable{
				name: f.Names[0].Name,
				typ:  t,
			}
			continue
		}
		t, err := parseType(f.Type)
		if err != nil {
			sh.addError(f.Type.Pos(), err.Error())
			continue
		}
		if !t.numeric() {
			sh.addError(f.Type.Pos(), fmt.Sprintf("members in %s must be numeric but %s", varyingStructName, t))
			continue
		}
		for _, n := range f.Names {
			sh.varyings = append(sh.varyings, variable{
				name: n.Name,
				typ:  t,
			})
		}
	}
}

func (s *Shader) parseVariable(vs *ast.ValueSpec) []variable {
	var t typ
	if vs.Type != nil {
		var err error
		t, err = parseType(vs.Type)
		if err != nil {
			s.addError(vs.Type.Pos(), err.Error())
			return nil
		}
	}

	var vars []variable
	for _, n := range vs.Names {
		name := n.Name
		vars = append(vars, variable{
			name: name,
			typ:  t,
		})
	}
	return vars
}

func (s *Shader) parseConstant(vs *ast.ValueSpec) []constant {
	var t typ
	if vs.Type != nil {
		var err error
		t, err = parseType(vs.Type)
		if err != nil {
			s.addError(vs.Type.Pos(), err.Error())
			return nil
		}
	}

	var cs []constant
	for i, n := range vs.Names {
		v := vs.Values[i]
		var init string
		switch v := v.(type) {
		case *ast.BasicLit:
			if v.Kind != token.INT && v.Kind != token.FLOAT {
				s.addError(v.Pos(), fmt.Sprintf("literal must be int or float but %s", v.Kind))
				return cs
			}
			init = v.Value // TODO: This should be go/constant.Value
		default:
			// TODO: Parse the expression.
		}
		cs = append(cs, constant{
			name: n.Name,
			typ:  t,
			init: init,
		})
	}
	return cs
}

func (sh *Shader) parseFunc(d *ast.FuncDecl) function {
	if d.Name == nil {
		sh.addError(d.Pos(), "function must have a name")
		return function{}
	}
	if d.Body == nil {
		sh.addError(d.Pos(), "function must have a body")
		return function{}
	}

	var args []variable
	for _, f := range d.Type.Params.List {
		t, err := parseType(f.Type)
		if err != nil {
			sh.addError(f.Type.Pos(), err.Error())
			continue
		}
		for _, n := range f.Names {
			args = append(args, variable{
				name: n.Name,
				typ:  t,
			})
		}
	}

	var rets []variable
	for _, f := range d.Type.Results.List {
		t, err := parseType(f.Type)
		if err != nil {
			sh.addError(f.Type.Pos(), err.Error())
			continue
		}
		if len(f.Names) == 0 {
			rets = append(rets, variable{
				name: "",
				typ:  t,
			})
		} else {
			for _, n := range f.Names {
				rets = append(rets, variable{
					name: n.Name,
					typ:  t,
				})
			}
		}
	}

	return function{
		name: d.Name.Name,
		args: args,
		rets: rets,
		body: sh.parseBlock(d.Body),
	}
}

func (sh *Shader) parseBlock(b *ast.BlockStmt) *block {
	block := &block{}

	for _, l := range b.List {
		switch l := l.(type) {
		case *ast.AssignStmt:
			if l.Tok == token.DEFINE {
				for _, s := range l.Lhs {
					ident := s.(*ast.Ident)
					block.vars = append(block.vars, variable{
						name: ident.Name,
					})
				}
			} else {
				// TODO
			}
		case *ast.DeclStmt:
			sh.parseDecl(block, l.Decl)
		case *ast.ReturnStmt:
			var exprs []expr
			for _, r := range l.Results {
				exprs = append(exprs, sh.parseExpr(r))
			}
			block.stmts = append(block.stmts, stmt{
				stmtType: stmtReturn,
				exprs:    exprs,
			})
		default:
		}
	}

	sort.Slice(block.consts, func(a, b int) bool {
		return block.consts[a].name < block.consts[b].name
	})
	sort.Slice(block.funcs, func(a, b int) bool {
		return block.funcs[a].name < block.funcs[b].name
	})

	return block
}

func (sh *Shader) parseExpr(e ast.Expr) expr {
	switch e := e.(type) {
	case *ast.Ident:
		return expr{
			exprType: exprIdent,
			value:    e.Name,
		}
	}
	return expr{}
}

// Dump dumps the shader state in an intermediate language.
func (s *Shader) Dump() string {
	var lines []string

	lines = append(lines, fmt.Sprintf("var %s varying %s // position", s.position.name, s.position.typ))
	for _, v := range s.varyings {
		lines = append(lines, fmt.Sprintf("var %s varying %s", v.name, v.typ))
	}

	for _, u := range s.uniforms {
		lines = append(lines, fmt.Sprintf("var %s uniform %s", u.name, u.typ))
	}

	lines = append(lines, s.global.dump(0)...)

	return strings.Join(lines, "\n") + "\n"
}

func (s *Shader) GlslVertex() string {
	var lines []string

	for _, v := range s.varyings {
		// TODO: variable names must be escaped not to conflict with keywords.
		lines = append(lines, fmt.Sprintf("varying %s %s;", v.typ.glslString(), v.name))
	}
	return strings.Join(lines, "\n") + "\n"
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Ceph-CSI 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 util

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"

	"github.com/ceph/go-ceph/rados"
	"k8s.io/klog"
)

// InvalidPoolID used to denote an invalid pool
const InvalidPoolID int64 = -1

// ExecCommand executes passed in program with args and returns separate stdout and stderr streams
func ExecCommand(program string, args ...string) (stdout, stderr []byte, err error) {
	var (
		cmd           = exec.Command(program, args...) // nolint: gosec, #nosec
		sanitizedArgs = StripSecretInArgs(args)
		stdoutBuf     bytes.Buffer
		stderrBuf     bytes.Buffer
	)

	cmd.Stdout = &stdoutBuf
	cmd.Stderr = &stderrBuf

	if err := cmd.Run(); err != nil {
		return stdoutBuf.Bytes(), stderrBuf.Bytes(), fmt.Errorf("an error (%v)"+
			" occurred while running %s args: %v", err, program, sanitizedArgs)
	}

	return stdoutBuf.Bytes(), nil, nil
}

// cephStoragePoolSummary strongly typed JSON spec for osd ls pools output
type cephStoragePoolSummary struct {
	Name   string `json:"poolname"`
	Number int64  `json:"poolnum"`
}

// GetPools fetches a list of pools from a cluster
func getPools(ctx context.Context, monitors string, cr *Credentials) ([]cephStoragePoolSummary, error) {
	// ceph <options> -f json osd lspools
	// JSON out: [{"poolnum":<int64>,"poolname":<string>}]

	stdout, _, err := ExecCommand(
		"ceph",
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile="+cr.KeyFile,
		"-c", CephConfigPath,
		"-f", "json",
		"osd", "lspools")
	if err != nil {
		klog.Errorf(Log(ctx, "failed getting pool list from cluster (%s)"), err)
		return nil, err
	}

	var pools []cephStoragePoolSummary
	err = json.Unmarshal(stdout, &pools)
	if err != nil {
		klog.Errorf(Log(ctx, "failed to parse JSON output of pool list from cluster (%s)"), err)
		return nil, fmt.Errorf("unmarshal of pool list failed: %+v.  raw buffer response: %s", err, string(stdout))
	}

	return pools, nil
}

// GetPoolID fetches the ID of the pool that matches the passed in poolName
// parameter
func GetPoolID(monitors string, cr *Credentials, poolName string) (int64, error) {
	conn, err := connPool.Get(monitors, cr.ID, cr.KeyFile)
	if err != nil {
		return 0, err
	}
	defer connPool.Put(conn)

	id, err := conn.GetPoolByName(poolName)
	if err == rados.ErrNotFound {
		return 0, ErrPoolNotFound{poolName, fmt.Errorf("pool (%s) not found in Ceph cluster", poolName)}
	} else if err != nil {
		return 0, err
	}

	return id, nil
}

// GetPoolName lists all pools in a ceph cluster, and matches the pool whose pool ID is equal to
// the requested poolID parameter
func GetPoolName(ctx context.Context, monitors string, cr *Credentials, poolID int64) (string, error) {
	pools, err := getPools(ctx, monitors, cr)
	if err != nil {
		return "", err
	}

	for _, p := range pools {
		if poolID == p.Number {
			return p.Name, nil
		}
	}

	return "", ErrPoolNotFound{string(poolID), fmt.Errorf("pool ID (%d) not found in Ceph cluster", poolID)}
}

// GetPoolIDs searches a list of pools in a cluster and returns the IDs of the pools that matches
// the passed in pools
// TODO this should take in a list and return a map[string(poolname)]int64(poolID)
func GetPoolIDs(ctx context.Context, monitors, journalPool, imagePool string, cr *Credentials) (int64, int64, error) {
	journalPoolID, err := GetPoolID(monitors, cr, journalPool)
	if err != nil {
		return InvalidPoolID, InvalidPoolID, err
	}

	imagePoolID := journalPoolID
	if imagePool != journalPool {
		imagePoolID, err = GetPoolID(monitors, cr, imagePool)
		if err != nil {
			return InvalidPoolID, InvalidPoolID, err
		}
	}

	return journalPoolID, imagePoolID, nil
}

// SetOMapKeyValue sets the given key and value into the provided Ceph omap name
func SetOMapKeyValue(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, oMapName, oMapKey, keyValue string) error {
	// Command: "rados <options> setomapval oMapName oMapKey keyValue"
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"setomapval", oMapName, oMapKey, keyValue,
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	_, _, err := ExecCommand("rados", args[:]...)
	if err != nil {
		klog.Errorf(Log(ctx, "failed adding key (%s with value %s), to omap (%s) in "+
			"pool (%s): (%v)"), oMapKey, keyValue, oMapName, poolName, err)
		return err
	}

	return nil
}

// GetOMapValue gets the value for the given key from the named omap
func GetOMapValue(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, oMapName, oMapKey string) (string, error) {
	// Command: "rados <options> getomapval oMapName oMapKey <outfile>"
	// No such key: replicapool/csi.volumes.directory.default/csi.volname
	tmpFile, err := ioutil.TempFile("", "omap-get-")
	if err != nil {
		klog.Errorf(Log(ctx, "failed creating a temporary file for key contents"))
		return "", err
	}
	defer tmpFile.Close()
	defer os.Remove(tmpFile.Name())

	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"getomapval", oMapName, oMapKey, tmpFile.Name(),
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	stdout, stderr, err := ExecCommand("rados", args[:]...)
	if err != nil {
		// no logs, as attempting to check for non-existent key/value is done even on
		// regular call sequences
		stdoutanderr := strings.Join([]string{string(stdout), string(stderr)}, " ")
		if strings.Contains(stdoutanderr, "No such key: "+poolName+"/"+oMapName+"/"+oMapKey) {
			return "", ErrKeyNotFound{poolName + "/" + oMapName + "/" + oMapKey, err}
		}

		if strings.Contains(stdoutanderr, "error getting omap value "+
			poolName+"/"+oMapName+"/"+oMapKey+": (2) No such file or directory") {
			return "", ErrKeyNotFound{poolName + "/" + oMapName + "/" + oMapKey, err}
		}

		if strings.Contains(stdoutanderr, "error opening pool "+
			poolName+": (2) No such file or directory") {
			return "", ErrPoolNotFound{poolName, err}
		}

		// log other errors for troubleshooting assistance
		klog.Errorf(Log(ctx, "failed getting omap value for key (%s) from omap (%s) in pool (%s): (%v)"),
			oMapKey, oMapName, poolName, err)

		return "", fmt.Errorf("error (%v) occurred, command output streams is (%s)",
			err.Error(), stdoutanderr)
	}

	keyValue, err := ioutil.ReadAll(tmpFile)
	return string(keyValue), err
}

// RemoveOMapKey removes the omap key from the given omap name
func RemoveOMapKey(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, oMapName, oMapKey string) error {
	// Command: "rados <options> rmomapkey oMapName oMapKey"
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"rmomapkey", oMapName, oMapKey,
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	_, _, err := ExecCommand("rados", args[:]...)
	if err != nil {
		// NOTE: Missing omap key removal does not return an error
		klog.Errorf(Log(ctx, "failed removing key (%s), from omap (%s) in "+
			"pool (%s): (%v)"), oMapKey, oMapName, poolName, err)
		return err
	}

	return nil
}

// CreateObject creates the object name passed in and returns ErrObjectExists if the provided object
// is already present in rados
func CreateObject(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, objectName string) error {
	// Command: "rados <options> create objectName"
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"create", objectName,
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	_, stderr, err := ExecCommand("rados", args[:]...)
	if err != nil {
		klog.Errorf(Log(ctx, "failed creating omap (%s) in pool (%s): (%v)"), objectName, poolName, err)
		if strings.Contains(string(stderr), "error creating "+poolName+"/"+objectName+
			": (17) File exists") {
			return ErrObjectExists{objectName, err}
		}
		return err
	}

	return nil
}

// RemoveObject removes the entire omap name passed in and returns ErrObjectNotFound is provided omap
// is not found in rados
func RemoveObject(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, oMapName string) error {
	// Command: "rados <options> rm oMapName"
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"rm", oMapName,
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	_, stderr, err := ExecCommand("rados", args[:]...)
	if err != nil {
		klog.Errorf(Log(ctx, "failed removing omap (%s) in pool (%s): (%v)"), oMapName, poolName, err)
		if strings.Contains(string(stderr), "error removing "+poolName+">"+oMapName+
			": (2) No such file or directory") {
			return ErrObjectNotFound{oMapName, err}
		}
		return err
	}

	return nil
}

// SetImageMeta sets image metadata
func SetImageMeta(ctx context.Context, cr *Credentials, monitors, imageSpec, key, value string) error {
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"image-meta", "set", imageSpec,
		key, value,
	}

	_, _, err := ExecCommand("rbd", args[:]...)
	if err != nil {
		klog.Errorf(Log(ctx, "failed setting image metadata (%s) for (%s): (%v)"), key, imageSpec, err)
		return err
	}

	return nil
}

// GetImageMeta gets image metadata
func GetImageMeta(ctx context.Context, cr *Credentials, monitors, imageSpec, key string) (string, error) {
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"image-meta", "get", imageSpec,
		key,
	}

	stdout, stderr, err := ExecCommand("rbd", args[:]...)
	if err != nil {
		stdoutanderr := strings.Join([]string{string(stdout), string(stderr)}, " ")
		if strings.Contains(stdoutanderr, "failed to get metadata "+key+" of image : (2) No such file or directory") {
			return "", ErrKeyNotFound{imageSpec + " " + key, err}
		}

		klog.Errorf(Log(ctx, "failed getting image metadata (%s) for (%s): (%v)"), key, imageSpec, err)
		return "", err
	}

	return string(stdout), nil
}
<commit_msg>cleanup: return InvalidPoolID on error in GetPoolID()<commit_after>/*
Copyright 2019 The Ceph-CSI 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 util

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"

	"github.com/ceph/go-ceph/rados"
	"k8s.io/klog"
)

// InvalidPoolID used to denote an invalid pool
const InvalidPoolID int64 = -1

// ExecCommand executes passed in program with args and returns separate stdout and stderr streams
func ExecCommand(program string, args ...string) (stdout, stderr []byte, err error) {
	var (
		cmd           = exec.Command(program, args...) // nolint: gosec, #nosec
		sanitizedArgs = StripSecretInArgs(args)
		stdoutBuf     bytes.Buffer
		stderrBuf     bytes.Buffer
	)

	cmd.Stdout = &stdoutBuf
	cmd.Stderr = &stderrBuf

	if err := cmd.Run(); err != nil {
		return stdoutBuf.Bytes(), stderrBuf.Bytes(), fmt.Errorf("an error (%v)"+
			" occurred while running %s args: %v", err, program, sanitizedArgs)
	}

	return stdoutBuf.Bytes(), nil, nil
}

// cephStoragePoolSummary strongly typed JSON spec for osd ls pools output
type cephStoragePoolSummary struct {
	Name   string `json:"poolname"`
	Number int64  `json:"poolnum"`
}

// GetPools fetches a list of pools from a cluster
func getPools(ctx context.Context, monitors string, cr *Credentials) ([]cephStoragePoolSummary, error) {
	// ceph <options> -f json osd lspools
	// JSON out: [{"poolnum":<int64>,"poolname":<string>}]

	stdout, _, err := ExecCommand(
		"ceph",
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile="+cr.KeyFile,
		"-c", CephConfigPath,
		"-f", "json",
		"osd", "lspools")
	if err != nil {
		klog.Errorf(Log(ctx, "failed getting pool list from cluster (%s)"), err)
		return nil, err
	}

	var pools []cephStoragePoolSummary
	err = json.Unmarshal(stdout, &pools)
	if err != nil {
		klog.Errorf(Log(ctx, "failed to parse JSON output of pool list from cluster (%s)"), err)
		return nil, fmt.Errorf("unmarshal of pool list failed: %+v.  raw buffer response: %s", err, string(stdout))
	}

	return pools, nil
}

// GetPoolID fetches the ID of the pool that matches the passed in poolName
// parameter
func GetPoolID(monitors string, cr *Credentials, poolName string) (int64, error) {
	conn, err := connPool.Get(monitors, cr.ID, cr.KeyFile)
	if err != nil {
		return InvalidPoolID, err
	}
	defer connPool.Put(conn)

	id, err := conn.GetPoolByName(poolName)
	if err == rados.ErrNotFound {
		return InvalidPoolID, ErrPoolNotFound{poolName, fmt.Errorf("pool (%s) not found in Ceph cluster", poolName)}
	} else if err != nil {
		return InvalidPoolID, err
	}

	return id, nil
}

// GetPoolName lists all pools in a ceph cluster, and matches the pool whose pool ID is equal to
// the requested poolID parameter
func GetPoolName(ctx context.Context, monitors string, cr *Credentials, poolID int64) (string, error) {
	pools, err := getPools(ctx, monitors, cr)
	if err != nil {
		return "", err
	}

	for _, p := range pools {
		if poolID == p.Number {
			return p.Name, nil
		}
	}

	return "", ErrPoolNotFound{string(poolID), fmt.Errorf("pool ID (%d) not found in Ceph cluster", poolID)}
}

// GetPoolIDs searches a list of pools in a cluster and returns the IDs of the pools that matches
// the passed in pools
// TODO this should take in a list and return a map[string(poolname)]int64(poolID)
func GetPoolIDs(ctx context.Context, monitors, journalPool, imagePool string, cr *Credentials) (int64, int64, error) {
	journalPoolID, err := GetPoolID(monitors, cr, journalPool)
	if err != nil {
		return InvalidPoolID, InvalidPoolID, err
	}

	imagePoolID := journalPoolID
	if imagePool != journalPool {
		imagePoolID, err = GetPoolID(monitors, cr, imagePool)
		if err != nil {
			return InvalidPoolID, InvalidPoolID, err
		}
	}

	return journalPoolID, imagePoolID, nil
}

// SetOMapKeyValue sets the given key and value into the provided Ceph omap name
func SetOMapKeyValue(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, oMapName, oMapKey, keyValue string) error {
	// Command: "rados <options> setomapval oMapName oMapKey keyValue"
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"setomapval", oMapName, oMapKey, keyValue,
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	_, _, err := ExecCommand("rados", args[:]...)
	if err != nil {
		klog.Errorf(Log(ctx, "failed adding key (%s with value %s), to omap (%s) in "+
			"pool (%s): (%v)"), oMapKey, keyValue, oMapName, poolName, err)
		return err
	}

	return nil
}

// GetOMapValue gets the value for the given key from the named omap
func GetOMapValue(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, oMapName, oMapKey string) (string, error) {
	// Command: "rados <options> getomapval oMapName oMapKey <outfile>"
	// No such key: replicapool/csi.volumes.directory.default/csi.volname
	tmpFile, err := ioutil.TempFile("", "omap-get-")
	if err != nil {
		klog.Errorf(Log(ctx, "failed creating a temporary file for key contents"))
		return "", err
	}
	defer tmpFile.Close()
	defer os.Remove(tmpFile.Name())

	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"getomapval", oMapName, oMapKey, tmpFile.Name(),
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	stdout, stderr, err := ExecCommand("rados", args[:]...)
	if err != nil {
		// no logs, as attempting to check for non-existent key/value is done even on
		// regular call sequences
		stdoutanderr := strings.Join([]string{string(stdout), string(stderr)}, " ")
		if strings.Contains(stdoutanderr, "No such key: "+poolName+"/"+oMapName+"/"+oMapKey) {
			return "", ErrKeyNotFound{poolName + "/" + oMapName + "/" + oMapKey, err}
		}

		if strings.Contains(stdoutanderr, "error getting omap value "+
			poolName+"/"+oMapName+"/"+oMapKey+": (2) No such file or directory") {
			return "", ErrKeyNotFound{poolName + "/" + oMapName + "/" + oMapKey, err}
		}

		if strings.Contains(stdoutanderr, "error opening pool "+
			poolName+": (2) No such file or directory") {
			return "", ErrPoolNotFound{poolName, err}
		}

		// log other errors for troubleshooting assistance
		klog.Errorf(Log(ctx, "failed getting omap value for key (%s) from omap (%s) in pool (%s): (%v)"),
			oMapKey, oMapName, poolName, err)

		return "", fmt.Errorf("error (%v) occurred, command output streams is (%s)",
			err.Error(), stdoutanderr)
	}

	keyValue, err := ioutil.ReadAll(tmpFile)
	return string(keyValue), err
}

// RemoveOMapKey removes the omap key from the given omap name
func RemoveOMapKey(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, oMapName, oMapKey string) error {
	// Command: "rados <options> rmomapkey oMapName oMapKey"
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"rmomapkey", oMapName, oMapKey,
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	_, _, err := ExecCommand("rados", args[:]...)
	if err != nil {
		// NOTE: Missing omap key removal does not return an error
		klog.Errorf(Log(ctx, "failed removing key (%s), from omap (%s) in "+
			"pool (%s): (%v)"), oMapKey, oMapName, poolName, err)
		return err
	}

	return nil
}

// CreateObject creates the object name passed in and returns ErrObjectExists if the provided object
// is already present in rados
func CreateObject(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, objectName string) error {
	// Command: "rados <options> create objectName"
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"create", objectName,
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	_, stderr, err := ExecCommand("rados", args[:]...)
	if err != nil {
		klog.Errorf(Log(ctx, "failed creating omap (%s) in pool (%s): (%v)"), objectName, poolName, err)
		if strings.Contains(string(stderr), "error creating "+poolName+"/"+objectName+
			": (17) File exists") {
			return ErrObjectExists{objectName, err}
		}
		return err
	}

	return nil
}

// RemoveObject removes the entire omap name passed in and returns ErrObjectNotFound is provided omap
// is not found in rados
func RemoveObject(ctx context.Context, monitors string, cr *Credentials, poolName, namespace, oMapName string) error {
	// Command: "rados <options> rm oMapName"
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"-p", poolName,
		"rm", oMapName,
	}

	if namespace != "" {
		args = append(args, "--namespace="+namespace)
	}

	_, stderr, err := ExecCommand("rados", args[:]...)
	if err != nil {
		klog.Errorf(Log(ctx, "failed removing omap (%s) in pool (%s): (%v)"), oMapName, poolName, err)
		if strings.Contains(string(stderr), "error removing "+poolName+">"+oMapName+
			": (2) No such file or directory") {
			return ErrObjectNotFound{oMapName, err}
		}
		return err
	}

	return nil
}

// SetImageMeta sets image metadata
func SetImageMeta(ctx context.Context, cr *Credentials, monitors, imageSpec, key, value string) error {
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"image-meta", "set", imageSpec,
		key, value,
	}

	_, _, err := ExecCommand("rbd", args[:]...)
	if err != nil {
		klog.Errorf(Log(ctx, "failed setting image metadata (%s) for (%s): (%v)"), key, imageSpec, err)
		return err
	}

	return nil
}

// GetImageMeta gets image metadata
func GetImageMeta(ctx context.Context, cr *Credentials, monitors, imageSpec, key string) (string, error) {
	args := []string{
		"-m", monitors,
		"--id", cr.ID,
		"--keyfile=" + cr.KeyFile,
		"-c", CephConfigPath,
		"image-meta", "get", imageSpec,
		key,
	}

	stdout, stderr, err := ExecCommand("rbd", args[:]...)
	if err != nil {
		stdoutanderr := strings.Join([]string{string(stdout), string(stderr)}, " ")
		if strings.Contains(stdoutanderr, "failed to get metadata "+key+" of image : (2) No such file or directory") {
			return "", ErrKeyNotFound{imageSpec + " " + key, err}
		}

		klog.Errorf(Log(ctx, "failed getting image metadata (%s) for (%s): (%v)"), key, imageSpec, err)
		return "", err
	}

	return string(stdout), nil
}
<|endoftext|>


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

package websocket

import (
	"bytes"
	"reflect"
	"testing"
)

func TestJSON(t *testing.T) {
	var buf bytes.Buffer
	c := fakeNetConn{&buf, &buf}
	wc := newConn(c, true, 1024, 1024)
	rc := newConn(c, false, 1024, 1024)

	var actual, expect struct {
		A int
		B string
	}
	expect.A = 1
	expect.B = "hello"

	if err := WriteJSON(wc, &expect); err != nil {
		t.Fatal("write", err)
	}

	if err := wc.WriteJSON(&expect); err != nil {
		t.Fatal("write", err)
	}

	if err := ReadJSON(rc, &expect); err != nil {
		t.Fatal("read", err)
	}

	if err := rc.ReadJSON(&actual); err != nil {
		t.Fatal("read", err)
	}

	if !reflect.DeepEqual(&actual, &expect) {
		t.Fatal("equal", actual, expect)
	}
}
<commit_msg>Fix test for deprecated JSON funcs.<commit_after>// Copyright 2013 Gary Burd. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package websocket

import (
	"bytes"
	"reflect"
	"testing"
)

func TestJSON(t *testing.T) {
	var buf bytes.Buffer
	c := fakeNetConn{&buf, &buf}
	wc := newConn(c, true, 1024, 1024)
	rc := newConn(c, false, 1024, 1024)

	var actual, expect struct {
		A int
		B string
	}
	expect.A = 1
	expect.B = "hello"

	if err := wc.WriteJSON(&expect); err != nil {
		t.Fatal("write", err)
	}

	if err := rc.ReadJSON(&actual); err != nil {
		t.Fatal("read", err)
	}

	if !reflect.DeepEqual(&actual, &expect) {
		t.Fatal("equal", actual, expect)
	}
}

func TestDeprecatedJSON(t *testing.T) {
	var buf bytes.Buffer
	c := fakeNetConn{&buf, &buf}
	wc := newConn(c, true, 1024, 1024)
	rc := newConn(c, false, 1024, 1024)

	var actual, expect struct {
		A int
		B string
	}
	expect.A = 1
	expect.B = "hello"

	if err := WriteJSON(wc, &expect); err != nil {
		t.Fatal("write", err)
	}

	if err := ReadJSON(rc, &actual); err != nil {
		t.Fatal("read", err)
	}

	if !reflect.DeepEqual(&actual, &expect) {
		t.Fatal("equal", actual, expect)
	}
}
<|endoftext|>


<commit_before>package buf

import (
	"io"
	"net"

	"v2ray.com/core/common/errors"
)

type MultiBufferWriter interface {
	WriteMultiBuffer(MultiBuffer) error
}

type MultiBufferReader interface {
	ReadMultiBuffer() (MultiBuffer, error)
}

func ReadAllToMultiBuffer(reader io.Reader) (MultiBuffer, error) {
	mb := NewMultiBuffer()

	for {
		b := New()
		err := b.AppendSupplier(ReadFrom(reader))
		if !b.IsEmpty() {
			mb.Append(b)
		}
		if err != nil {
			if errors.Cause(err) == io.EOF {
				return mb, nil
			}
			mb.Release()
			return nil, err
		}
	}
}

func ReadAllToBytes(reader io.Reader) ([]byte, error) {
	mb, err := ReadAllToMultiBuffer(reader)
	if err != nil {
		return nil, err
	}
	b := make([]byte, mb.Len())
	mb.Read(b)
	mb.Release()
	return b, nil
}

// MultiBuffer is a list of Buffers. The order of Buffer matters.
type MultiBuffer []*Buffer

// NewMultiBuffer creates a new MultiBuffer instance.
func NewMultiBuffer() MultiBuffer {
	return MultiBuffer(make([]*Buffer, 0, 128))
}

// NewMultiBufferValue wraps a list of Buffers into MultiBuffer.
func NewMultiBufferValue(b ...*Buffer) MultiBuffer {
	return MultiBuffer(b)
}

func (mb *MultiBuffer) Append(buf *Buffer) {
	*mb = append(*mb, buf)
}

func (mb *MultiBuffer) AppendMulti(buf MultiBuffer) {
	*mb = append(*mb, buf...)
}

func (mb MultiBuffer) Copy(b []byte) int {
	total := 0
	for _, bb := range mb {
		nBytes := copy(b[total:], bb.Bytes())
		total += nBytes
		if nBytes < bb.Len() {
			break
		}
	}
	return total
}

func (mb *MultiBuffer) Read(b []byte) (int, error) {
	endIndex := len(*mb)
	totalBytes := 0
	for i, bb := range *mb {
		nBytes, _ := bb.Read(b)
		totalBytes += nBytes
		b = b[nBytes:]
		if bb.IsEmpty() {
			bb.Release()
		} else {
			endIndex = i
			break
		}
	}
	*mb = (*mb)[endIndex:]
	return totalBytes, nil
}

func (mb *MultiBuffer) Write(b []byte) {
	n := len(*mb)
	if n > 0 && !(*mb)[n-1].IsFull() {
		nBytes, _ := (*mb)[n-1].Write(b)
		b = b[nBytes:]
	}

	for len(b) > 0 {
		bb := New()
		nBytes, _ := bb.Write(b)
		b = b[nBytes:]
		mb.Append(bb)
	}
}

// Len returns the total number of bytes in the MultiBuffer.
func (mb MultiBuffer) Len() int {
	size := 0
	for _, b := range mb {
		size += b.Len()
	}
	return size
}

// IsEmpty return true if the MultiBuffer has no content.
func (mb MultiBuffer) IsEmpty() bool {
	for _, b := range mb {
		if !b.IsEmpty() {
			return false
		}
	}
	return true
}

// Release releases all Buffers in the MultiBuffer.
func (mb MultiBuffer) Release() {
	for i, b := range mb {
		b.Release()
		mb[i] = nil
	}
}

// ToNetBuffers converts this MultiBuffer to net.Buffers. The return net.Buffers points to the same content of the MultiBuffer.
func (mb MultiBuffer) ToNetBuffers() net.Buffers {
	bs := make([][]byte, len(mb))
	for i, b := range mb {
		bs[i] = b.Bytes()
	}
	return bs
}

func (mb *MultiBuffer) SliceBySize(size int) MultiBuffer {
	slice := NewMultiBuffer()
	sliceSize := 0
	endIndex := len(*mb)
	for i, b := range *mb {
		if b.Len()+sliceSize > size {
			endIndex = i
			break
		}
		sliceSize += b.Len()
		slice.Append(b)
	}
	*mb = (*mb)[endIndex:]
	return slice
}

func (mb *MultiBuffer) SplitFirst() *Buffer {
	if len(*mb) == 0 {
		return nil
	}
	b := (*mb)[0]
	*mb = (*mb)[1:]
	return b
}
<commit_msg>fix buffer leak in multi buffer<commit_after>package buf

import (
	"io"
	"net"

	"v2ray.com/core/common"
	"v2ray.com/core/common/errors"
)

type MultiBufferWriter interface {
	WriteMultiBuffer(MultiBuffer) error
}

type MultiBufferReader interface {
	ReadMultiBuffer() (MultiBuffer, error)
}

func ReadAllToMultiBuffer(reader io.Reader) (MultiBuffer, error) {
	mb := NewMultiBuffer()

	for {
		b := New()
		err := b.AppendSupplier(ReadFrom(reader))
		if !b.IsEmpty() {
			mb.Append(b)
		}
		if err != nil {
			if errors.Cause(err) == io.EOF {
				return mb, nil
			}
			mb.Release()
			return nil, err
		}
	}
}

// ReadAllToBytes reads all content from the reader into a byte array, until EOF.
func ReadAllToBytes(reader io.Reader) ([]byte, error) {
	mb, err := ReadAllToMultiBuffer(reader)
	if err != nil {
		return nil, err
	}
	b := make([]byte, mb.Len())
	common.Must2(mb.Read(b))
	mb.Release()
	return b, nil
}

// MultiBuffer is a list of Buffers. The order of Buffer matters.
type MultiBuffer []*Buffer

// NewMultiBuffer creates a new MultiBuffer instance.
func NewMultiBuffer() MultiBuffer {
	return MultiBuffer(make([]*Buffer, 0, 128))
}

// NewMultiBufferValue wraps a list of Buffers into MultiBuffer.
func NewMultiBufferValue(b ...*Buffer) MultiBuffer {
	return MultiBuffer(b)
}

func (mb *MultiBuffer) Append(buf *Buffer) {
	*mb = append(*mb, buf)
}

func (mb *MultiBuffer) AppendMulti(buf MultiBuffer) {
	*mb = append(*mb, buf...)
}

func (mb MultiBuffer) Copy(b []byte) int {
	total := 0
	for _, bb := range mb {
		nBytes := copy(b[total:], bb.Bytes())
		total += nBytes
		if nBytes < bb.Len() {
			break
		}
	}
	return total
}

func (mb *MultiBuffer) Read(b []byte) (int, error) {
	endIndex := len(*mb)
	totalBytes := 0
	for i, bb := range *mb {
		nBytes, _ := bb.Read(b)
		totalBytes += nBytes
		b = b[nBytes:]
		if bb.IsEmpty() {
			bb.Release()
		} else {
			endIndex = i
			break
		}
	}
	*mb = (*mb)[endIndex:]
	return totalBytes, nil
}

func (mb *MultiBuffer) Write(b []byte) {
	n := len(*mb)
	if n > 0 && !(*mb)[n-1].IsFull() {
		nBytes, _ := (*mb)[n-1].Write(b)
		b = b[nBytes:]
	}

	for len(b) > 0 {
		bb := New()
		nBytes, _ := bb.Write(b)
		b = b[nBytes:]
		mb.Append(bb)
	}
}

// Len returns the total number of bytes in the MultiBuffer.
func (mb MultiBuffer) Len() int {
	size := 0
	for _, b := range mb {
		size += b.Len()
	}
	return size
}

// IsEmpty return true if the MultiBuffer has no content.
func (mb MultiBuffer) IsEmpty() bool {
	for _, b := range mb {
		if !b.IsEmpty() {
			return false
		}
	}
	return true
}

// Release releases all Buffers in the MultiBuffer.
func (mb *MultiBuffer) Release() {
	for i, b := range *mb {
		b.Release()
		(*mb)[i] = nil
	}
	*mb = (*mb)[:0]
}

// ToNetBuffers converts this MultiBuffer to net.Buffers. The return net.Buffers points to the same content of the MultiBuffer.
func (mb MultiBuffer) ToNetBuffers() net.Buffers {
	bs := make([][]byte, len(mb))
	for i, b := range mb {
		bs[i] = b.Bytes()
	}
	return bs
}

func (mb *MultiBuffer) SliceBySize(size int) MultiBuffer {
	slice := NewMultiBuffer()
	sliceSize := 0
	endIndex := len(*mb)
	for i, b := range *mb {
		if b.Len()+sliceSize > size {
			endIndex = i
			break
		}
		sliceSize += b.Len()
		slice.Append(b)
		(*mb)[i] = nil
	}
	*mb = (*mb)[endIndex:]
	return slice
}

func (mb *MultiBuffer) SplitFirst() *Buffer {
	if len(*mb) == 0 {
		return nil
	}
	b := (*mb)[0]
	(*mb)[0] = nil
	*mb = (*mb)[1:]
	return b
}
<|endoftext|>


<commit_before>package jsonstore

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"io"
	"io/ioutil"
	"os"
	"regexp"
	"strings"
	"sync"
)

// NoSuchKeyError is thrown when calling Get with invalid key
type NoSuchKeyError struct {
	key string
}

func (err NoSuchKeyError) Error() string {
	return "jsonstore: no such key \"" + err.key + "\""
}

// JSONStore is the basic store object.
type JSONStore struct {
	Data map[string]json.RawMessage
	sync.RWMutex
}

// OpenOld will load a jsonstore from a file.
// to be deprecated
func OpenOld(filename string) (*JSONStore, error) {
	b, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	if strings.HasSuffix(filename, ".gz") {
		r, err := gzip.NewReader(bytes.NewReader(b))
		if err != nil {
			return nil, err
		}
		b, err = ioutil.ReadAll(r)
		if err != nil {
			return nil, err
		}
	}
	ks := new(JSONStore)

	// First Unmarshal the strings
	toOpen := make(map[string]string)
	err = json.Unmarshal(b, &toOpen)
	if err != nil {
		return nil, err
	}
	// Save to the raw message
	ks.Data = make(map[string]json.RawMessage)
	for key := range toOpen {
		ks.Data[key] = json.RawMessage(toOpen[key])
	}
	return ks, nil
}

// Open will load a jsonstore from a file.
func Open(filename string) (*JSONStore, error) {
	f, err := os.Open(filename)
	defer f.Close()
	if err != nil {
		return nil, err
	}

	toOpen := make(map[string]string)
	if strings.HasSuffix(filename, ".gz") {
		f2, err := gzip.NewReader(f)
		if err != nil {
			return nil, err
		}
		err = json.NewDecoder(f2).Decode(&toOpen)
		if err != nil {
			return nil, err
		}
	} else {
		err = json.NewDecoder(f).Decode(&toOpen)
		if err != nil {
			return nil, err
		}
	}

	ks := new(JSONStore)
	ks.Data = make(map[string]json.RawMessage)
	for key := range toOpen {
		ks.Data[key] = json.RawMessage(toOpen[key])
	}
	return ks, nil
}

// Save writes the jsonstore to disk.
func Save(ks *JSONStore, filename string) (err error) {
	ks.RLock()
	defer ks.RUnlock()
	toSave := make(map[string]string)
	for key := range ks.Data {
		toSave[key] = string(ks.Data[key])
	}
	var w io.Writer
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()
	if strings.HasSuffix(filename, ".gz") {
		w = gzip.NewWriter(f)
	} else {
		w = f
	}
	encoder := json.NewEncoder(w)
	encoder.SetIndent("", " ")
	return encoder.Encode(toSave)
}

// Set saves a value at the given key.
func (s *JSONStore) Set(key string, value interface{}) error {
	s.Lock()
	defer s.Unlock()
	if s.Data == nil {
		s.Data = make(map[string]json.RawMessage)
	}
	b, err := json.Marshal(value)
	if err != nil {
		return err
	}
	s.Data[key] = json.RawMessage(b)
	return nil
}

// Get will return the value associated with a key.
func (s *JSONStore) Get(key string, v interface{}) error {
	s.RLock()
	defer s.RUnlock()
	b, ok := s.Data[key]
	if !ok {
		return NoSuchKeyError{key}
	}
	return json.Unmarshal(b, &v)
}

// GetAll is like a filter with a regexp.
func (s *JSONStore) GetAll(re *regexp.Regexp) map[string]json.RawMessage {
	s.RLock()
	defer s.RUnlock()
	results := make(map[string]json.RawMessage)
	for k, v := range s.Data {
		if re.MatchString(k) {
			results[k] = v
		}
	}
	return results
}

// Keys returns all the keys currently in map
func (s *JSONStore) Keys() []string {
	s.RLock()
	defer s.RUnlock()
	keys := make([]string, len(s.Data))
	i := 0
	for k := range s.Data {
		keys[i] = k
		i++
	}
	return keys
}

// Delete removes a key from the store.
func (s *JSONStore) Delete(key string) {
	s.Lock()
	defer s.Unlock()
	delete(s.Data, key)
}
<commit_msg>MOved new Save to new fnction<commit_after>package jsonstore

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"io"
	"io/ioutil"
	"os"
	"regexp"
	"strings"
	"sync"
)

// NoSuchKeyError is thrown when calling Get with invalid key
type NoSuchKeyError struct {
	key string
}

func (err NoSuchKeyError) Error() string {
	return "jsonstore: no such key \"" + err.key + "\""
}

// JSONStore is the basic store object.
type JSONStore struct {
	Data map[string]json.RawMessage
	sync.RWMutex
}

// OpenOld will load a jsonstore from a file.
// to be deprecated
func OpenOld(filename string) (*JSONStore, error) {
	b, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	if strings.HasSuffix(filename, ".gz") {
		r, err := gzip.NewReader(bytes.NewReader(b))
		if err != nil {
			return nil, err
		}
		b, err = ioutil.ReadAll(r)
		if err != nil {
			return nil, err
		}
	}
	ks := new(JSONStore)

	// First Unmarshal the strings
	toOpen := make(map[string]string)
	err = json.Unmarshal(b, &toOpen)
	if err != nil {
		return nil, err
	}
	// Save to the raw message
	ks.Data = make(map[string]json.RawMessage)
	for key := range toOpen {
		ks.Data[key] = json.RawMessage(toOpen[key])
	}
	return ks, nil
}

// Open will load a jsonstore from a file.
func Open(filename string) (*JSONStore, error) {
	f, err := os.Open(filename)
	defer f.Close()
	if err != nil {
		return nil, err
	}

	toOpen := make(map[string]string)
	if strings.HasSuffix(filename, ".gz") {
		f2, err := gzip.NewReader(f)
		if err != nil {
			return nil, err
		}
		err = json.NewDecoder(f2).Decode(&toOpen)
		if err != nil {
			return nil, err
		}
	} else {
		err = json.NewDecoder(f).Decode(&toOpen)
		if err != nil {
			return nil, err
		}
	}

	ks := new(JSONStore)
	ks.Data = make(map[string]json.RawMessage)
	for key := range toOpen {
		ks.Data[key] = json.RawMessage(toOpen[key])
	}
	return ks, nil
}

// Save writes the jsonstore to disk.
func Save(ks *JSONStore, filename string) (err error) {
	ks.RLock()
	defer ks.RUnlock()

	toSave := make(map[string]string)
	for key := range ks.Data {
		toSave[key] = string(ks.Data[key])
	}
	b, err := json.MarshalIndent(toSave, "", " ")
	if err != nil {
		return
	}
	if strings.HasSuffix(filename, ".gz") {
		var b2 bytes.Buffer
		w := gzip.NewWriter(&b2)
		w.Write(b)
		w.Close()
		b = b2.Bytes()
	}
	return ioutil.WriteFile(filename, b, 0644)
}

// SaveNew writes the jsonstore to disk (doesn't work?)
func SaveNew(ks *JSONStore, filename string) (err error) {
	ks.RLock()
	defer ks.RUnlock()
	toSave := make(map[string]string)
	for key := range ks.Data {
		toSave[key] = string(ks.Data[key])
	}
	var w io.Writer
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()
	if strings.HasSuffix(filename, ".gz") {
		w = gzip.NewWriter(f)
	} else {
		w = f
	}
	encoder := json.NewEncoder(w)
	encoder.SetIndent("", " ")
	return encoder.Encode(toSave)
}

// Set saves a value at the given key.
func (s *JSONStore) Set(key string, value interface{}) error {
	s.Lock()
	defer s.Unlock()
	if s.Data == nil {
		s.Data = make(map[string]json.RawMessage)
	}
	b, err := json.Marshal(value)
	if err != nil {
		return err
	}
	s.Data[key] = json.RawMessage(b)
	return nil
}

// Get will return the value associated with a key.
func (s *JSONStore) Get(key string, v interface{}) error {
	s.RLock()
	defer s.RUnlock()
	b, ok := s.Data[key]
	if !ok {
		return NoSuchKeyError{key}
	}
	return json.Unmarshal(b, &v)
}

// GetAll is like a filter with a regexp.
func (s *JSONStore) GetAll(re *regexp.Regexp) map[string]json.RawMessage {
	s.RLock()
	defer s.RUnlock()
	results := make(map[string]json.RawMessage)
	for k, v := range s.Data {
		if re.MatchString(k) {
			results[k] = v
		}
	}
	return results
}

// Keys returns all the keys currently in map
func (s *JSONStore) Keys() []string {
	s.RLock()
	defer s.RUnlock()
	keys := make([]string, len(s.Data))
	i := 0
	for k := range s.Data {
		keys[i] = k
		i++
	}
	return keys
}

// Delete removes a key from the store.
func (s *JSONStore) Delete(key string) {
	s.Lock()
	defer s.Unlock()
	delete(s.Data, key)
}
<|endoftext|>


<commit_before>// Tideland Go REST Server Library - JSON Web Token - Cache
//
// Copyright (C) 2016 Frank Mueller / Tideland / Oldenburg / Germany
//
// All rights reserved. Use of this source code is governed
// by the new BSD license.

package jwt

//--------------------
// IMPORTS
//--------------------

import (
	"sync"
	"time"
)

//--------------------
// CACHE
//--------------------

// Cache provides a caching for tokens so that these
// don't have to be decoded or verified multiple times.
type Cache interface {
	// Get tries to retrieve a token from the cache.
	Get(token string) (JWT, bool)

	// Put adds a token to the cache.
	Put(jwt JWT)
}

// cacheEntry manages a token and its access time.
type cacheEntry struct {
	jwt      JWT
	accessed time.Time
}

// cache implements Cache.
type cache struct {
	mutex   sync.RWMutex
	entries map[string]*cacheEntry
}

// Get implements the Cache interface.
func (c *cache) Get(token string) (JWT, bool) {
	c.mutex.RLock()
	defer c.mutex.RUnlock()
	entry, ok := c.entries[token]
	if !ok {
		return nil, false
	}
	// TODO Check claims and their validity.
	return entry.jwt, true
}

// Put implements the Cache interface.
func (c *cache) Put(jwt JWT) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	delete(c.entries, jwt.String())
}

// EOF
<commit_msg>Further cache implementation<commit_after>// Tideland Go REST Server Library - JSON Web Token - Cache
//
// Copyright (C) 2016 Frank Mueller / Tideland / Oldenburg / Germany
//
// All rights reserved. Use of this source code is governed
// by the new BSD license.

package jwt

//--------------------
// IMPORTS
//--------------------

import (
	"sync"
	"time"
)

//--------------------
// CACHE
//--------------------

// Cache provides a caching for tokens so that these
// don't have to be decoded or verified multiple times.
type Cache interface {
	// Get tries to retrieve a token from the cache.
	Get(token string) (JWT, bool)

	// Put adds a token to the cache.
	Put(jwt JWT)
}

// cacheEntry manages a token and its access time.
type cacheEntry struct {
	jwt      JWT
	accessed time.Time
}

// cache implements Cache.
type cache struct {
	mutex   sync.Mutex
	cleanup time.Duration
	leeway  time.Duration
	entries map[string]*cacheEntry
}

// NewCache creates a new JWT caching. It takes two
// durations. The first one is the time a token hasn't
// been used anymore before it is cleaned up. The second
// one is the leeway taken for token time validations.
func NewCache(cleanup, leeway time.Duration) Cache {
	c := &cache{
		cleanup: cleanup,
		leeway:  leeway,
		entries: map[string]*cacheEntry{},
	}
	// TODO Start cleanup goroutine.
	return c
}

// Get implements the Cache interface.
func (c *cache) Get(token string) (JWT, bool) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	entry, ok := c.entries[token]
	if !ok {
		return nil, false
	}
	if entry.jwt.IsValid(c.leeway) {
		entry.accessed = time.Now()
		return entry.jwt, true
	}
	// Remove invalid token.
	delete(c.entries, token)
	return nil, false
}

// Put implements the Cache interface.
func (c *cache) Put(jwt JWT) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if jwt.IsValid(c.leeway) {
		c.entries[jwt.String()] = &cacheEntry{jwt, time.Now()}
	}
}

// EOF
<|endoftext|>


<commit_before>package kami_test

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/zenazn/goji/web/mutil"
	"golang.org/x/net/context"

	"github.com/guregu/kami"
)

func TestKami(t *testing.T) {
	kami.Reset()

	expect := func(ctx context.Context, i int) context.Context {
		if prev := ctx.Value(i - 1).(int); prev != i-1 {
			t.Error("missing", i)
		}
		return context.WithValue(ctx, i, i)
	}

	kami.Use("/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = context.WithValue(ctx, 1, 1)
		ctx = context.WithValue(ctx, "handler", new(bool))
		ctx = context.WithValue(ctx, "done", new(bool))
		ctx = context.WithValue(ctx, "recovered", new(bool))
		return ctx
	})
	kami.Use("/a/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 2)
		return ctx
	})
	kami.Use("/a/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 3)
		return ctx
	})
	kami.Use("/a/b", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 4)
		return ctx
	})
	kami.Use("/a/*files", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 5)
		return ctx
	})
	kami.Get("/a/b", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		if prev := ctx.Value(5).(int); prev != 5 {
			t.Error("handler: missing", 5)
		}
		*(ctx.Value("handler").(*bool)) = true

		w.WriteHeader(http.StatusTeapot)
	})
	kami.After("/a/*files", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 6)
		if !*(ctx.Value("handler").(*bool)) {
			t.Error("ran before handler")
		}
		return ctx
	})
	kami.After("/a/b", kami.Afterware(func(ctx context.Context, w mutil.WriterProxy, r *http.Request) context.Context {
		ctx = expect(ctx, 7)
		return ctx
	}))
	kami.After("/a/", func(ctx context.Context) context.Context {
		ctx = expect(ctx, 9)
		return ctx
	})
	kami.After("/a/", func(ctx context.Context) context.Context {
		ctx = expect(ctx, 8)
		return ctx
	})
	kami.After("/", func(ctx context.Context, w mutil.WriterProxy, r *http.Request) context.Context {
		if status := w.Status(); status != http.StatusTeapot {
			t.Error("wrong status", status)
		}

		ctx = expect(ctx, 9)
		*(ctx.Value("done").(*bool)) = true
		panic("🍣")
		return nil
	})
	kami.PanicHandler = func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		if got := kami.Exception(ctx); got.(string) != "🍣" {
			t.Error("panic handler: expected sushi, got", got)
		}
		if !*(ctx.Value("done").(*bool)) {
			t.Error("didn't finish")
		}
		*(ctx.Value("recovered").(*bool)) = true
	}
	kami.LogHandler = func(ctx context.Context, w mutil.WriterProxy, r *http.Request) {
		if !*(ctx.Value("recovered").(*bool)) {
			t.Error("didn't recover")
		}
	}

	expectResponseCode(t, "GET", "/a/b", http.StatusTeapot)
}

func TestLoggerAndPanic(t *testing.T) {
	kami.Reset()
	// test logger with panic
	status := 0
	kami.LogHandler = func(ctx context.Context, w mutil.WriterProxy, r *http.Request) {
		status = w.Status()
	}
	kami.PanicHandler = kami.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		err := kami.Exception(ctx)
		if err != "test panic" {
			t.Error("unexpected exception:", err)
		}
		w.WriteHeader(http.StatusServiceUnavailable)
	})
	kami.Post("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		panic("test panic")
	})
	kami.Put("/ok", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	expectResponseCode(t, "POST", "/test", http.StatusServiceUnavailable)
	if status != http.StatusServiceUnavailable {
		t.Error("log handler received wrong status code", status, "≠", http.StatusServiceUnavailable)
	}

	// test loggers without panics
	expectResponseCode(t, "PUT", "/ok", http.StatusOK)
	if status != http.StatusOK {
		t.Error("log handler received wrong status code", status, "≠", http.StatusOK)
	}
}

func TestPanickingLogger(t *testing.T) {
	kami.Reset()
	kami.LogHandler = func(ctx context.Context, w mutil.WriterProxy, r *http.Request) {
		t.Log("log handler")
		panic("test panic")
	}
	kami.PanicHandler = kami.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		t.Log("panic handler")
		err := kami.Exception(ctx)
		if err != "test panic" {
			t.Error("unexpected exception:", err)
		}
		w.WriteHeader(http.StatusServiceUnavailable)
	})
	kami.Options("/test", noop)

	expectResponseCode(t, "OPTIONS", "/test", http.StatusServiceUnavailable)
}

func TestNotFound(t *testing.T) {
	kami.Reset()
	kami.Use("/missing/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		return context.WithValue(ctx, "ok", true)
	})
	kami.NotFound(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		ok, _ := ctx.Value("ok").(bool)
		if !ok {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.WriteHeader(http.StatusTeapot)
	})

	expectResponseCode(t, "GET", "/missing/hello", http.StatusTeapot)
}

func TestNotFoundDefault(t *testing.T) {
	kami.Reset()

	expectResponseCode(t, "GET", "/missing/hello", http.StatusNotFound)
}

func TestMethodNotAllowed(t *testing.T) {
	kami.Reset()
	kami.Use("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		return context.WithValue(ctx, "ok", true)
	})
	kami.Post("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		panic("test panic")
	})

	kami.MethodNotAllowed(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		ok, _ := ctx.Value("ok").(bool)
		if !ok {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.WriteHeader(http.StatusTeapot)
	})

	expectResponseCode(t, "GET", "/test", http.StatusTeapot)
}

func TestEnableMethodNotAllowed(t *testing.T) {
	kami.Reset()
	kami.Post("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		panic("test panic")
	})

	// Handling enabled by default
	expectResponseCode(t, "GET", "/test", http.StatusMethodNotAllowed)

	// Not found deals with it when handling disabled
	kami.EnableMethodNotAllowed(false)
	expectResponseCode(t, "GET", "/test", http.StatusNotFound)

	// And MethodNotAllowed status when handling enabled
	kami.EnableMethodNotAllowed(true)
	expectResponseCode(t, "GET", "/test", http.StatusMethodNotAllowed)
}

func TestMethodNotAllowedDefault(t *testing.T) {
	kami.Reset()
	kami.Post("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		panic("test panic")
	})

	expectResponseCode(t, "GET", "/test", http.StatusMethodNotAllowed)
}

func noop(ctx context.Context, w http.ResponseWriter, r *http.Request) {}

func noopMW(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
	return ctx
}

func expectResponseCode(t *testing.T, method, path string, expected int) {
	resp := httptest.NewRecorder()
	req, err := http.NewRequest(method, path, nil)
	if err != nil {
		t.Fatal(err)
	}

	kami.Handler().ServeHTTP(resp, req)

	if resp.Code != expected {
		t.Error("should return HTTP", http.StatusText(expected)+":", resp.Code, "≠", expected)
	}
}
<commit_msg>fix test<commit_after>package kami_test

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/zenazn/goji/web/mutil"
	"golang.org/x/net/context"

	"github.com/guregu/kami"
)

func TestKami(t *testing.T) {
	kami.Reset()

	expect := func(ctx context.Context, i int) context.Context {
		if prev := ctx.Value(i - 1).(int); prev != i-1 {
			t.Error("missing", i)
		}
		if curr := ctx.Value(i); curr != nil {
			t.Error("pre-existing", i)
		}
		return context.WithValue(ctx, i, i)
	}

	kami.Use("/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = context.WithValue(ctx, 1, 1)
		ctx = context.WithValue(ctx, "handler", new(bool))
		ctx = context.WithValue(ctx, "done", new(bool))
		ctx = context.WithValue(ctx, "recovered", new(bool))
		return ctx
	})
	kami.Use("/a/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 2)
		return ctx
	})
	kami.Use("/a/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 3)
		return ctx
	})
	kami.Use("/a/b", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 4)
		return ctx
	})
	kami.Use("/a/*files", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 5)
		return ctx
	})
	kami.Get("/a/b", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		if prev := ctx.Value(5).(int); prev != 5 {
			t.Error("handler: missing", 5)
		}
		*(ctx.Value("handler").(*bool)) = true

		w.WriteHeader(http.StatusTeapot)
	})
	kami.After("/a/*files", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		ctx = expect(ctx, 6)
		if !*(ctx.Value("handler").(*bool)) {
			t.Error("ran before handler")
		}
		return ctx
	})
	kami.After("/a/b", kami.Afterware(func(ctx context.Context, w mutil.WriterProxy, r *http.Request) context.Context {
		ctx = expect(ctx, 7)
		return ctx
	}))
	kami.After("/a/", func(ctx context.Context) context.Context {
		ctx = expect(ctx, 9)
		return ctx
	})
	kami.After("/a/", func(ctx context.Context) context.Context {
		ctx = expect(ctx, 8)
		return ctx
	})
	kami.After("/", func(ctx context.Context, w mutil.WriterProxy, r *http.Request) context.Context {
		if status := w.Status(); status != http.StatusTeapot {
			t.Error("wrong status", status)
		}

		ctx = expect(ctx, 10)
		*(ctx.Value("done").(*bool)) = true
		panic("🍣")
		return nil
	})
	kami.PanicHandler = func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		if got := kami.Exception(ctx); got.(string) != "🍣" {
			t.Error("panic handler: expected sushi, got", got)
		}
		if !*(ctx.Value("done").(*bool)) {
			t.Error("didn't finish")
		}
		*(ctx.Value("recovered").(*bool)) = true
	}
	kami.LogHandler = func(ctx context.Context, w mutil.WriterProxy, r *http.Request) {
		if !*(ctx.Value("recovered").(*bool)) {
			t.Error("didn't recover")
		}
	}

	expectResponseCode(t, "GET", "/a/b", http.StatusTeapot)
}

func TestLoggerAndPanic(t *testing.T) {
	kami.Reset()
	// test logger with panic
	status := 0
	kami.LogHandler = func(ctx context.Context, w mutil.WriterProxy, r *http.Request) {
		status = w.Status()
	}
	kami.PanicHandler = kami.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		err := kami.Exception(ctx)
		if err != "test panic" {
			t.Error("unexpected exception:", err)
		}
		w.WriteHeader(http.StatusServiceUnavailable)
	})
	kami.Post("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		panic("test panic")
	})
	kami.Put("/ok", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	expectResponseCode(t, "POST", "/test", http.StatusServiceUnavailable)
	if status != http.StatusServiceUnavailable {
		t.Error("log handler received wrong status code", status, "≠", http.StatusServiceUnavailable)
	}

	// test loggers without panics
	expectResponseCode(t, "PUT", "/ok", http.StatusOK)
	if status != http.StatusOK {
		t.Error("log handler received wrong status code", status, "≠", http.StatusOK)
	}
}

func TestPanickingLogger(t *testing.T) {
	kami.Reset()
	kami.LogHandler = func(ctx context.Context, w mutil.WriterProxy, r *http.Request) {
		t.Log("log handler")
		panic("test panic")
	}
	kami.PanicHandler = kami.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		t.Log("panic handler")
		err := kami.Exception(ctx)
		if err != "test panic" {
			t.Error("unexpected exception:", err)
		}
		w.WriteHeader(http.StatusServiceUnavailable)
	})
	kami.Options("/test", noop)

	expectResponseCode(t, "OPTIONS", "/test", http.StatusServiceUnavailable)
}

func TestNotFound(t *testing.T) {
	kami.Reset()
	kami.Use("/missing/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		return context.WithValue(ctx, "ok", true)
	})
	kami.NotFound(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		ok, _ := ctx.Value("ok").(bool)
		if !ok {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.WriteHeader(http.StatusTeapot)
	})

	expectResponseCode(t, "GET", "/missing/hello", http.StatusTeapot)
}

func TestNotFoundDefault(t *testing.T) {
	kami.Reset()

	expectResponseCode(t, "GET", "/missing/hello", http.StatusNotFound)
}

func TestMethodNotAllowed(t *testing.T) {
	kami.Reset()
	kami.Use("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		return context.WithValue(ctx, "ok", true)
	})
	kami.Post("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		panic("test panic")
	})

	kami.MethodNotAllowed(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		ok, _ := ctx.Value("ok").(bool)
		if !ok {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.WriteHeader(http.StatusTeapot)
	})

	expectResponseCode(t, "GET", "/test", http.StatusTeapot)
}

func TestEnableMethodNotAllowed(t *testing.T) {
	kami.Reset()
	kami.Post("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		panic("test panic")
	})

	// Handling enabled by default
	expectResponseCode(t, "GET", "/test", http.StatusMethodNotAllowed)

	// Not found deals with it when handling disabled
	kami.EnableMethodNotAllowed(false)
	expectResponseCode(t, "GET", "/test", http.StatusNotFound)

	// And MethodNotAllowed status when handling enabled
	kami.EnableMethodNotAllowed(true)
	expectResponseCode(t, "GET", "/test", http.StatusMethodNotAllowed)
}

func TestMethodNotAllowedDefault(t *testing.T) {
	kami.Reset()
	kami.Post("/test", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		panic("test panic")
	})

	expectResponseCode(t, "GET", "/test", http.StatusMethodNotAllowed)
}

func noop(ctx context.Context, w http.ResponseWriter, r *http.Request) {}

func noopMW(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
	return ctx
}

func expectResponseCode(t *testing.T, method, path string, expected int) {
	resp := httptest.NewRecorder()
	req, err := http.NewRequest(method, path, nil)
	if err != nil {
		t.Fatal(err)
	}

	kami.Handler().ServeHTTP(resp, req)

	if resp.Code != expected {
		t.Error("should return HTTP", http.StatusText(expected)+":", resp.Code, "≠", expected)
	}
}
<|endoftext|>