<commit_before>/*
   Copyright The containerd 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 containerd

import (
	"context"
	"strings"
	"syscall"
	"time"

	"github.com/containerd/containerd/api/services/tasks/v1"
	"github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/errdefs"
	"github.com/pkg/errors"
)

// Process represents a system process
type Process interface {
	// ID of the process
	ID() string
	// Pid is the system specific process id
	Pid() uint32
	// Start starts the process executing the user's defined binary
	Start(context.Context) error
	// Delete removes the process and any resources allocated returning the exit status
	Delete(context.Context, ...ProcessDeleteOpts) (*ExitStatus, error)
	// Kill sends the provided signal to the process
	Kill(context.Context, syscall.Signal, ...KillOpts) error
	// Wait asynchronously waits for the process to exit, and sends the exit code to the returned channel
	Wait(context.Context) (<-chan ExitStatus, error)
	// CloseIO allows various pipes to be closed on the process
	CloseIO(context.Context, ...IOCloserOpts) error
	// Resize changes the width and height of the process's terminal
	Resize(ctx context.Context, w, h uint32) error
	// IO returns the io set for the process
	IO() cio.IO
	// Status returns the executing status of the process
	Status(context.Context) (Status, error)
}

// NewExitStatus populates an ExitStatus
func NewExitStatus(code uint32, t time.Time, err error) *ExitStatus {
	return &ExitStatus{
		code:     code,
		exitedAt: t,
		err:      err,
	}
}

// ExitStatus encapsulates a process's exit status.
// It is used by `Wait()` to return either a process exit code or an error
type ExitStatus struct {
	code     uint32
	exitedAt time.Time
	err      error
}

// Result returns the exit code and time of the exit status.
// An error may be returned here to which indicates there was an error
//   at some point while waiting for the exit status. It does not signify
//   an error with the process itself.
// If an error is returned, the process may still be running.
func (s ExitStatus) Result() (uint32, time.Time, error) {
	return s.code, s.exitedAt, s.err
}

// ExitCode returns the exit code of the process.
// This is only valid is Error() returns nil
func (s ExitStatus) ExitCode() uint32 {
	return s.code
}

// ExitTime returns the exit time of the process
// This is only valid is Error() returns nil
func (s ExitStatus) ExitTime() time.Time {
	return s.exitedAt
}

// Error returns the error, if any, that occurred while waiting for the
// process.
func (s ExitStatus) Error() error {
	return s.err
}

type process struct {
	id   string
	task *task
	pid  uint32
	io   cio.IO
}

func (p *process) ID() string {
	return p.id
}

// Pid returns the pid of the process
// The pid is not set until start is called and returns
func (p *process) Pid() uint32 {
	return p.pid
}

// Start starts the exec process
func (p *process) Start(ctx context.Context) error {
	r, err := p.task.client.TaskService().Start(ctx, &tasks.StartRequest{
		ContainerID: p.task.id,
		ExecID:      p.id,
	})
	if err != nil {
		if p.io != nil {
			p.io.Cancel()
			p.io.Wait()
			p.io.Close()
		}
		return errdefs.FromGRPC(err)
	}
	p.pid = r.Pid
	return nil
}

func (p *process) Kill(ctx context.Context, s syscall.Signal, opts ...KillOpts) error {
	var i KillInfo
	for _, o := range opts {
		if err := o(ctx, &i); err != nil {
			return err
		}
	}
	_, err := p.task.client.TaskService().Kill(ctx, &tasks.KillRequest{
		Signal:      uint32(s),
		ContainerID: p.task.id,
		ExecID:      p.id,
		All:         i.All,
	})
	return errdefs.FromGRPC(err)
}

func (p *process) Wait(ctx context.Context) (<-chan ExitStatus, error) {
	c := make(chan ExitStatus, 1)
	go func() {
		defer close(c)
		r, err := p.task.client.TaskService().Wait(ctx, &tasks.WaitRequest{
			ContainerID: p.task.id,
			ExecID:      p.id,
		})
		if err != nil {
			c <- ExitStatus{
				code: UnknownExitStatus,
				err:  err,
			}
			return
		}
		c <- ExitStatus{
			code:     r.ExitStatus,
			exitedAt: r.ExitedAt,
		}
	}()
	return c, nil
}

func (p *process) CloseIO(ctx context.Context, opts ...IOCloserOpts) error {
	r := &tasks.CloseIORequest{
		ContainerID: p.task.id,
		ExecID:      p.id,
	}
	var i IOCloseInfo
	for _, o := range opts {
		o(&i)
	}
	r.Stdin = i.Stdin
	_, err := p.task.client.TaskService().CloseIO(ctx, r)
	return errdefs.FromGRPC(err)
}

func (p *process) IO() cio.IO {
	return p.io
}

func (p *process) Resize(ctx context.Context, w, h uint32) error {
	_, err := p.task.client.TaskService().ResizePty(ctx, &tasks.ResizePtyRequest{
		ContainerID: p.task.id,
		Width:       w,
		Height:      h,
		ExecID:      p.id,
	})
	return errdefs.FromGRPC(err)
}

func (p *process) Delete(ctx context.Context, opts ...ProcessDeleteOpts) (*ExitStatus, error) {
	for _, o := range opts {
		if err := o(ctx, p); err != nil {
			return nil, err
		}
	}
	status, err := p.Status(ctx)
	if err != nil {
		return nil, err
	}
	switch status.Status {
	case Running, Paused, Pausing:
		return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "process must be stopped before deletion")
	}
	r, err := p.task.client.TaskService().DeleteProcess(ctx, &tasks.DeleteProcessRequest{
		ContainerID: p.task.id,
		ExecID:      p.id,
	})
	if err != nil {
		return nil, errdefs.FromGRPC(err)
	}
	if p.io != nil {
		p.io.Cancel()
		p.io.Wait()
		p.io.Close()
	}
	return &ExitStatus{code: r.ExitStatus, exitedAt: r.ExitedAt}, nil
}

func (p *process) Status(ctx context.Context) (Status, error) {
	r, err := p.task.client.TaskService().Get(ctx, &tasks.GetRequest{
		ContainerID: p.task.id,
		ExecID:      p.id,
	})
	if err != nil {
		return Status{}, errdefs.FromGRPC(err)
	}
	return Status{
		Status:     ProcessStatus(strings.ToLower(r.Process.Status.String())),
		ExitStatus: r.Process.ExitStatus,
	}, nil
}
<commit_msg>add current process state to the error message<commit_after>/*
   Copyright The containerd 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 containerd

import (
	"context"
	"strings"
	"syscall"
	"time"

	"github.com/containerd/containerd/api/services/tasks/v1"
	"github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/errdefs"
	"github.com/pkg/errors"
)

// Process represents a system process
type Process interface {
	// ID of the process
	ID() string
	// Pid is the system specific process id
	Pid() uint32
	// Start starts the process executing the user's defined binary
	Start(context.Context) error
	// Delete removes the process and any resources allocated returning the exit status
	Delete(context.Context, ...ProcessDeleteOpts) (*ExitStatus, error)
	// Kill sends the provided signal to the process
	Kill(context.Context, syscall.Signal, ...KillOpts) error
	// Wait asynchronously waits for the process to exit, and sends the exit code to the returned channel
	Wait(context.Context) (<-chan ExitStatus, error)
	// CloseIO allows various pipes to be closed on the process
	CloseIO(context.Context, ...IOCloserOpts) error
	// Resize changes the width and height of the process's terminal
	Resize(ctx context.Context, w, h uint32) error
	// IO returns the io set for the process
	IO() cio.IO
	// Status returns the executing status of the process
	Status(context.Context) (Status, error)
}

// NewExitStatus populates an ExitStatus
func NewExitStatus(code uint32, t time.Time, err error) *ExitStatus {
	return &ExitStatus{
		code:     code,
		exitedAt: t,
		err:      err,
	}
}

// ExitStatus encapsulates a process's exit status.
// It is used by `Wait()` to return either a process exit code or an error
type ExitStatus struct {
	code     uint32
	exitedAt time.Time
	err      error
}

// Result returns the exit code and time of the exit status.
// An error may be returned here to which indicates there was an error
//   at some point while waiting for the exit status. It does not signify
//   an error with the process itself.
// If an error is returned, the process may still be running.
func (s ExitStatus) Result() (uint32, time.Time, error) {
	return s.code, s.exitedAt, s.err
}

// ExitCode returns the exit code of the process.
// This is only valid is Error() returns nil
func (s ExitStatus) ExitCode() uint32 {
	return s.code
}

// ExitTime returns the exit time of the process
// This is only valid is Error() returns nil
func (s ExitStatus) ExitTime() time.Time {
	return s.exitedAt
}

// Error returns the error, if any, that occurred while waiting for the
// process.
func (s ExitStatus) Error() error {
	return s.err
}

type process struct {
	id   string
	task *task
	pid  uint32
	io   cio.IO
}

func (p *process) ID() string {
	return p.id
}

// Pid returns the pid of the process
// The pid is not set until start is called and returns
func (p *process) Pid() uint32 {
	return p.pid
}

// Start starts the exec process
func (p *process) Start(ctx context.Context) error {
	r, err := p.task.client.TaskService().Start(ctx, &tasks.StartRequest{
		ContainerID: p.task.id,
		ExecID:      p.id,
	})
	if err != nil {
		if p.io != nil {
			p.io.Cancel()
			p.io.Wait()
			p.io.Close()
		}
		return errdefs.FromGRPC(err)
	}
	p.pid = r.Pid
	return nil
}

func (p *process) Kill(ctx context.Context, s syscall.Signal, opts ...KillOpts) error {
	var i KillInfo
	for _, o := range opts {
		if err := o(ctx, &i); err != nil {
			return err
		}
	}
	_, err := p.task.client.TaskService().Kill(ctx, &tasks.KillRequest{
		Signal:      uint32(s),
		ContainerID: p.task.id,
		ExecID:      p.id,
		All:         i.All,
	})
	return errdefs.FromGRPC(err)
}

func (p *process) Wait(ctx context.Context) (<-chan ExitStatus, error) {
	c := make(chan ExitStatus, 1)
	go func() {
		defer close(c)
		r, err := p.task.client.TaskService().Wait(ctx, &tasks.WaitRequest{
			ContainerID: p.task.id,
			ExecID:      p.id,
		})
		if err != nil {
			c <- ExitStatus{
				code: UnknownExitStatus,
				err:  err,
			}
			return
		}
		c <- ExitStatus{
			code:     r.ExitStatus,
			exitedAt: r.ExitedAt,
		}
	}()
	return c, nil
}

func (p *process) CloseIO(ctx context.Context, opts ...IOCloserOpts) error {
	r := &tasks.CloseIORequest{
		ContainerID: p.task.id,
		ExecID:      p.id,
	}
	var i IOCloseInfo
	for _, o := range opts {
		o(&i)
	}
	r.Stdin = i.Stdin
	_, err := p.task.client.TaskService().CloseIO(ctx, r)
	return errdefs.FromGRPC(err)
}

func (p *process) IO() cio.IO {
	return p.io
}

func (p *process) Resize(ctx context.Context, w, h uint32) error {
	_, err := p.task.client.TaskService().ResizePty(ctx, &tasks.ResizePtyRequest{
		ContainerID: p.task.id,
		Width:       w,
		Height:      h,
		ExecID:      p.id,
	})
	return errdefs.FromGRPC(err)
}

func (p *process) Delete(ctx context.Context, opts ...ProcessDeleteOpts) (*ExitStatus, error) {
	for _, o := range opts {
		if err := o(ctx, p); err != nil {
			return nil, err
		}
	}
	status, err := p.Status(ctx)
	if err != nil {
		return nil, err
	}
	switch status.Status {
	case Running, Paused, Pausing:
		return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "current process state: %s, process must be stopped before deletion", status.Status)
	}
	r, err := p.task.client.TaskService().DeleteProcess(ctx, &tasks.DeleteProcessRequest{
		ContainerID: p.task.id,
		ExecID:      p.id,
	})
	if err != nil {
		return nil, errdefs.FromGRPC(err)
	}
	if p.io != nil {
		p.io.Cancel()
		p.io.Wait()
		p.io.Close()
	}
	return &ExitStatus{code: r.ExitStatus, exitedAt: r.ExitedAt}, nil
}

func (p *process) Status(ctx context.Context) (Status, error) {
	r, err := p.task.client.TaskService().Get(ctx, &tasks.GetRequest{
		ContainerID: p.task.id,
		ExecID:      p.id,
	})
	if err != nil {
		return Status{}, errdefs.FromGRPC(err)
	}
	return Status{
		Status:     ProcessStatus(strings.ToLower(r.Process.Status.String())),
		ExitStatus: r.Process.ExitStatus,
	}, nil
}
<|endoftext|>


<commit_before><commit_msg>Updated DFS for new struct<commit_after><|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 dco

import (
	"fmt"
	"strings"

	"github.com/sirupsen/logrus"

	"regexp"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/pluginhelp"
	"k8s.io/test-infra/prow/plugins"
)

const (
	pluginName               = "dco"
	dcoContextName           = "dco"
	dcoContextMessageFailed  = "A commit in PR is missing Signed-off-by"
	dcoContextMessageSuccess = "All commits have Signed-off-by"

	dcoYesLabel        = "dco-signoff: yes"
	dcoNoLabel         = "dco-signoff: no"
	dcoMsgPruneMatch   = "Thanks for your pull request. Before we can look at your pull request, you'll need to add a 'DCO signoff' to your commits."
	dcoNotFoundMessage = `Thanks for your pull request. Before we can look at your pull request, you'll need to add a 'DCO signoff' to your commits.

<details>

%s
</details>
`
)

var (
	checkDCORe = regexp.MustCompile(`(?mi)^/check-dco\s*$`)
	testRe     = regexp.MustCompile(`(?mi)^signed-off-by:`)
)

func init() {
	plugins.RegisterPullRequestHandler(pluginName, handlePullRequestEvent, helpProvider)
	plugins.RegisterGenericCommentHandler(pluginName, handleCommentEvent, helpProvider)
}

func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
	// The {WhoCanUse, Usage, Examples, Config} fields are omitted because this plugin cannot be
	// manually triggered and is not configurable.
	pluginHelp := &pluginhelp.PluginHelp{
		Description: "The dco plugin checks pull request commits for 'DCO sign off' and maintains the '" + dcoContextName + "' status context, as well as the 'dco' label.",
	}
	pluginHelp.AddCommand(pluginhelp.Command{
		Usage:       "/check-dco",
		Description: "Forces rechecking of the DCO status.",
		Featured:    true,
		WhoCanUse:   "Anyone",
		Examples:    []string{"/check-dco"},
	})
	return pluginHelp, nil
}

type gitHubClient interface {
	BotName() (string, error)
	CreateComment(owner, repo string, number int, comment string) error
	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
	AddLabel(owner, repo string, number int, label string) error
	RemoveLabel(owner, repo string, number int, label string) error
	ListStatuses(org, repo, ref string) ([]github.Status, error)
	CreateStatus(owner, repo, ref string, status github.Status) error
	ListPRCommits(org, repo string, number int) ([]github.RepositoryCommit, error)
	GetPullRequest(owner, repo string, number int) (*github.PullRequest, error)
}

type commentPruner interface {
	PruneComments(shouldPrune func(github.IssueComment) bool)
}

// checkCommitMessages will perform the actual DCO check by retrieving all
// commits contained within the PR with the given number.
// *All* commits in the pull request *must* match the 'testRe' in order to pass.
func checkCommitMessages(gc gitHubClient, l *logrus.Entry, org, repo string, number int) (bool, error) {
	allCommits, err := gc.ListPRCommits(org, repo, number)
	if err != nil {
		return false, fmt.Errorf("error listing commits for pull request: %v", err)
	}
	l.Infof("Found %d commits in PR", len(allCommits))

	signMissing := false
	for _, commit := range allCommits {
		if !testRe.MatchString(*commit.Commit.Message) {
			signMissing = true
			break
		}
	}

	l.Infof("All commits in PR have DCO signoff: %t", !signMissing)
	return !signMissing, nil
}

// checkExistingStatus will retrieve the current status of the DCO context for
// the provided SHA.
func checkExistingStatus(gc gitHubClient, l *logrus.Entry, org, repo, sha string) (string, error) {
	statuses, err := gc.ListStatuses(org, repo, sha)
	if err != nil {
		return "", fmt.Errorf("error listing pull request statuses: %v", err)
	}

	existingStatus := ""
	for _, status := range statuses {
		if status.Context != dcoContextName {
			continue
		}
		existingStatus = status.State
		break
	}
	l.Infof("Existing DCO status context status is %q", existingStatus)
	return existingStatus, nil
}

// checkExistingLabels will check the provided PR for the dco sign off labels,
// returning bool's indicating whether the 'yes' and the 'no' label are present.
func checkExistingLabels(gc gitHubClient, l *logrus.Entry, org, repo string, number int) (hasYesLabel, hasNoLabel bool, err error) {
	labels, err := gc.GetIssueLabels(org, repo, number)
	if err != nil {
		return false, false, fmt.Errorf("error getting pull request labels: %v", err)
	}

	for _, l := range labels {
		if l.Name == dcoYesLabel {
			hasYesLabel = true
		}
		if l.Name == dcoNoLabel {
			hasNoLabel = true
		}
	}

	return hasYesLabel, hasNoLabel, nil
}

// takeAction will take appropriate action on the pull request according to its
// current state.
func takeAction(gc gitHubClient, cp commentPruner, l *logrus.Entry, org, repo string, pr github.PullRequest, signedOff bool, existingStatus string, hasYesLabel, hasNoLabel bool) error {
	targetURL := fmt.Sprintf("https://github.com/%s/%s/blob/master/CONTRIBUTING.md", org, repo)
	botName, err := gc.BotName()
	if err != nil {
		return fmt.Errorf("failed to get bot name: %v", err)
	}

	// handle the 'all commits signed off' case by adding appropriate labels
	// TODO: clean-up old comments?
	if signedOff {
		if hasNoLabel {
			l.Infof("Removing %q label", dcoNoLabel)
			// remove 'dco-signoff: no' label
			if err := gc.RemoveLabel(org, repo, pr.Number, dcoNoLabel); err != nil {
				return fmt.Errorf("error removing label: %v", err)
			}
		}
		if !hasYesLabel {
			l.Infof("Adding %q label", dcoYesLabel)
			// add 'dco-signoff: yes' label
			if err := gc.AddLabel(org, repo, pr.Number, dcoYesLabel); err != nil {
				return fmt.Errorf("error adding label: %v", err)
			}
		}
		if existingStatus != github.StatusSuccess {
			l.Infof("Setting DCO status context to succeeded")
			if err := gc.CreateStatus(org, repo, pr.Head.SHA, github.Status{
				Context:     dcoContextName,
				State:       github.StatusSuccess,
				TargetURL:   targetURL,
				Description: dcoContextMessageSuccess,
			}); err != nil {
				return fmt.Errorf("error setting pull request status: %v", err)
			}
		}

		cp.PruneComments(shouldPrune(l, botName))
		return nil
	}

	// handle the 'not all commits signed off' case

	// we handle this 'base case' early on to avoid adding more comments when
	// they are not needed.
	if hasNoLabel && !hasYesLabel && existingStatus == github.StatusFailure {
		l.Infof("DCO status context and label already up to date")
		return nil
	}

	if !hasNoLabel {
		l.Infof("Adding %q label", dcoNoLabel)
		// add 'dco-signoff: no' label
		if err := gc.AddLabel(org, repo, pr.Number, dcoNoLabel); err != nil {
			return fmt.Errorf("error adding label: %v", err)
		}
	}
	if hasYesLabel {
		l.Infof("Removing %q label", dcoYesLabel)
		// remove 'dco-signoff: yes' label
		if err := gc.RemoveLabel(org, repo, pr.Number, dcoYesLabel); err != nil {
			return fmt.Errorf("error removing label: %v", err)
		}
	}
	if existingStatus != github.StatusFailure {
		l.Infof("Setting DCO status context to failed")
		if err := gc.CreateStatus(org, repo, pr.Head.SHA, github.Status{
			Context:     dcoContextName,
			State:       github.StatusFailure,
			TargetURL:   targetURL,
			Description: dcoContextMessageFailed,
		}); err != nil {
			return fmt.Errorf("error setting pull request status: %v", err)
		}
	}

	cp.PruneComments(shouldPrune(l, botName))
	l.Infof("Commenting on PR to advise users of DCO check")
	if err := gc.CreateComment(org, repo, pr.Number, fmt.Sprintf(dcoNotFoundMessage, plugins.AboutThisBot)); err != nil {
		l.WithError(err).Warning("Could not create DCO not found comment.")
	}

	return nil
}

// 1. Check commit messages in the pull request for the sign-off string
// 2. Check the existing status context value
// 3. Check the existing PR labels
// 4. If signed off, apply appropriate labels and status context.
// 5. If not signed off, apply appropriate labels and status context and add a comment.
func handle(gc gitHubClient, cp commentPruner, log *logrus.Entry, org, repo string, pr github.PullRequest) error {
	l := log.WithField("pr", pr.Number)

	signedOff, err := checkCommitMessages(gc, l, org, repo, pr.Number)
	if err != nil {
		l.WithError(err).Infof("Error running DCO check against commits in PR")
		return err
	}

	existingStatus, err := checkExistingStatus(gc, l, org, repo, pr.Head.SHA)
	if err != nil {
		l.WithError(err).Infof("Error checking existing PR status")
		return err
	}

	hasYesLabel, hasNoLabel, err := checkExistingLabels(gc, l, org, repo, pr.Number)
	if err != nil {
		l.WithError(err).Infof("Error checking existing PR labels")
		return err
	}

	return takeAction(gc, cp, l, org, repo, pr, signedOff, existingStatus, hasYesLabel, hasNoLabel)
}

// shouldPrune finds comments left by this plugin.
func shouldPrune(log *logrus.Entry, botName string) func(github.IssueComment) bool {
	return func(comment github.IssueComment) bool {
		if comment.User.Login != botName {
			return false
		}
		return strings.Contains(comment.Body, dcoMsgPruneMatch)
	}
}

func handlePullRequestEvent(pc plugins.PluginClient, pe github.PullRequestEvent) error {
	org := pe.Repo.Owner.Login
	repo := pe.Repo.Name

	// we only reprocess on label, unlabel, open, reopen and synchronize events
	// this will reduce our API token usage and save processing of unrelated events
	switch pe.Action {
	case github.PullRequestActionLabeled,
		github.PullRequestActionUnlabeled,
		github.PullRequestActionOpened,
		github.PullRequestActionReopened,
		github.PullRequestActionSynchronize:
	default:
		return nil
	}

	return handle(pc.GitHubClient, pc.CommentPruner, pc.Logger, org, repo, pe.PullRequest)
}

func handleCommentEvent(pc plugins.PluginClient, ce github.GenericCommentEvent) error {
	// Only consider open PRs and new comments.
	if ce.IssueState != "open" || ce.Action != github.GenericCommentActionCreated || !ce.IsPR {
		return nil
	}
	// Only consider "/check-dco" comments.
	if !checkDCORe.MatchString(ce.Body) {
		return nil
	}

	gc := pc.GitHubClient
	org := ce.Repo.Owner.Login
	repo := ce.Repo.Name

	pr, err := gc.GetPullRequest(org, repo, ce.Number)
	if err != nil {
		return fmt.Errorf("error getting pull request for comment: %v", err)
	}

	return handle(gc, pc.CommentPruner, pc.Logger, org, repo, *pr)

}
<commit_msg>Print list of SHAs missing DCO in comment response<commit_after>/*
Copyright 2016 The Kubernetes 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 dco

import (
	"fmt"
	"strings"

	"github.com/sirupsen/logrus"

	"regexp"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/pluginhelp"
	"k8s.io/test-infra/prow/plugins"
)

const (
	pluginName               = "dco"
	dcoContextName           = "dco"
	dcoContextMessageFailed  = "Commits in PR missing Signed-off-by"
	dcoContextMessageSuccess = "All commits have Signed-off-by"

	dcoYesLabel        = "dco-signoff: yes"
	dcoNoLabel         = "dco-signoff: no"
	dcoMsgPruneMatch   = "Thanks for your pull request. Before we can look at your pull request, you'll need to add a 'DCO signoff' to your commits."
	dcoNotFoundMessage = `Thanks for your pull request. Before we can look at your pull request, you'll need to add a 'DCO signoff' to your commits.

:memo: **Please follow instructions in the [contributing guide](%s) to update your commits with the DCO**

Full details of the Developer Certificate of Origin can be found at [developercertificate.org](https://developercertificate.org/).

**The list of commits missing DCO signoff**:

%s

<details>

%s
</details>
`
)

var (
	checkDCORe = regexp.MustCompile(`(?mi)^/check-dco\s*$`)
	testRe     = regexp.MustCompile(`(?mi)^signed-off-by:`)
)

func init() {
	plugins.RegisterPullRequestHandler(pluginName, handlePullRequestEvent, helpProvider)
	plugins.RegisterGenericCommentHandler(pluginName, handleCommentEvent, helpProvider)
}

func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
	// The {WhoCanUse, Usage, Examples, Config} fields are omitted because this plugin cannot be
	// manually triggered and is not configurable.
	pluginHelp := &pluginhelp.PluginHelp{
		Description: "The dco plugin checks pull request commits for 'DCO sign off' and maintains the '" + dcoContextName + "' status context, as well as the 'dco' label.",
	}
	pluginHelp.AddCommand(pluginhelp.Command{
		Usage:       "/check-dco",
		Description: "Forces rechecking of the DCO status.",
		Featured:    true,
		WhoCanUse:   "Anyone",
		Examples:    []string{"/check-dco"},
	})
	return pluginHelp, nil
}

type gitHubClient interface {
	BotName() (string, error)
	CreateComment(owner, repo string, number int, comment string) error
	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
	AddLabel(owner, repo string, number int, label string) error
	RemoveLabel(owner, repo string, number int, label string) error
	ListStatuses(org, repo, ref string) ([]github.Status, error)
	CreateStatus(owner, repo, ref string, status github.Status) error
	ListPRCommits(org, repo string, number int) ([]github.RepositoryCommit, error)
	GetPullRequest(owner, repo string, number int) (*github.PullRequest, error)
}

type commentPruner interface {
	PruneComments(shouldPrune func(github.IssueComment) bool)
}

// checkCommitMessages will perform the actual DCO check by retrieving all
// commits contained within the PR with the given number.
// *All* commits in the pull request *must* match the 'testRe' in order to pass.
func checkCommitMessages(gc gitHubClient, l *logrus.Entry, org, repo string, number int) ([]github.GitCommit, error) {
	allCommits, err := gc.ListPRCommits(org, repo, number)
	if err != nil {
		return nil, fmt.Errorf("error listing commits for pull request: %v", err)
	}
	l.Infof("Found %d commits in PR", len(allCommits))

	var commitsMissingDCO []github.GitCommit
	for _, commit := range allCommits {
		if !testRe.MatchString(*commit.Commit.Message) {
			c := *commit.Commit
			c.SHA = &(*commit.SHA)
			commitsMissingDCO = append(commitsMissingDCO, c)
		}
	}

	l.Infof("All commits in PR have DCO signoff: %t", len(commitsMissingDCO) == 0)
	return commitsMissingDCO, nil
}

// checkExistingStatus will retrieve the current status of the DCO context for
// the provided SHA.
func checkExistingStatus(gc gitHubClient, l *logrus.Entry, org, repo, sha string) (string, error) {
	statuses, err := gc.ListStatuses(org, repo, sha)
	if err != nil {
		return "", fmt.Errorf("error listing pull request statuses: %v", err)
	}

	existingStatus := ""
	for _, status := range statuses {
		if status.Context != dcoContextName {
			continue
		}
		existingStatus = status.State
		break
	}
	l.Infof("Existing DCO status context status is %q", existingStatus)
	return existingStatus, nil
}

// checkExistingLabels will check the provided PR for the dco sign off labels,
// returning bool's indicating whether the 'yes' and the 'no' label are present.
func checkExistingLabels(gc gitHubClient, l *logrus.Entry, org, repo string, number int) (hasYesLabel, hasNoLabel bool, err error) {
	labels, err := gc.GetIssueLabels(org, repo, number)
	if err != nil {
		return false, false, fmt.Errorf("error getting pull request labels: %v", err)
	}

	for _, l := range labels {
		if l.Name == dcoYesLabel {
			hasYesLabel = true
		}
		if l.Name == dcoNoLabel {
			hasNoLabel = true
		}
	}

	return hasYesLabel, hasNoLabel, nil
}

// takeAction will take appropriate action on the pull request according to its
// current state.
func takeAction(gc gitHubClient, cp commentPruner, l *logrus.Entry, org, repo string, pr github.PullRequest, commitsMissingDCO []github.GitCommit, existingStatus string, hasYesLabel, hasNoLabel bool) error {
	targetURL := fmt.Sprintf("https://github.com/%s/%s/blob/master/CONTRIBUTING.md", org, repo)
	botName, err := gc.BotName()
	if err != nil {
		return fmt.Errorf("failed to get bot name: %v", err)
	}

	signedOff := len(commitsMissingDCO) == 0

	// handle the 'all commits signed off' case by adding appropriate labels
	// TODO: clean-up old comments?
	if signedOff {
		if hasNoLabel {
			l.Infof("Removing %q label", dcoNoLabel)
			// remove 'dco-signoff: no' label
			if err := gc.RemoveLabel(org, repo, pr.Number, dcoNoLabel); err != nil {
				return fmt.Errorf("error removing label: %v", err)
			}
		}
		if !hasYesLabel {
			l.Infof("Adding %q label", dcoYesLabel)
			// add 'dco-signoff: yes' label
			if err := gc.AddLabel(org, repo, pr.Number, dcoYesLabel); err != nil {
				return fmt.Errorf("error adding label: %v", err)
			}
		}
		if existingStatus != github.StatusSuccess {
			l.Infof("Setting DCO status context to succeeded")
			if err := gc.CreateStatus(org, repo, pr.Head.SHA, github.Status{
				Context:     dcoContextName,
				State:       github.StatusSuccess,
				TargetURL:   targetURL,
				Description: dcoContextMessageSuccess,
			}); err != nil {
				return fmt.Errorf("error setting pull request status: %v", err)
			}
		}

		cp.PruneComments(shouldPrune(l, botName))
		return nil
	}

	// handle the 'not all commits signed off' case
	if !hasNoLabel {
		l.Infof("Adding %q label", dcoNoLabel)
		// add 'dco-signoff: no' label
		if err := gc.AddLabel(org, repo, pr.Number, dcoNoLabel); err != nil {
			return fmt.Errorf("error adding label: %v", err)
		}
	}
	if hasYesLabel {
		l.Infof("Removing %q label", dcoYesLabel)
		// remove 'dco-signoff: yes' label
		if err := gc.RemoveLabel(org, repo, pr.Number, dcoYesLabel); err != nil {
			return fmt.Errorf("error removing label: %v", err)
		}
	}
	if existingStatus != github.StatusFailure {
		l.Infof("Setting DCO status context to failed")
		if err := gc.CreateStatus(org, repo, pr.Head.SHA, github.Status{
			Context:     dcoContextName,
			State:       github.StatusFailure,
			TargetURL:   targetURL,
			Description: dcoContextMessageFailed,
		}); err != nil {
			return fmt.Errorf("error setting pull request status: %v", err)
		}
	}

	// we always prune and create a comment if the check is failing, to ensure
	// the list of failing commits is up to date.
	cp.PruneComments(shouldPrune(l, botName))
	l.Infof("Commenting on PR to advise users of DCO check")
	if err := gc.CreateComment(org, repo, pr.Number, fmt.Sprintf(dcoNotFoundMessage, targetURL, markdownSHAList(org, repo, commitsMissingDCO), plugins.AboutThisBot)); err != nil {
		l.WithError(err).Warning("Could not create DCO not found comment.")
	}

	return nil
}

// 1. Check commit messages in the pull request for the sign-off string
// 2. Check the existing status context value
// 3. Check the existing PR labels
// 4. If signed off, apply appropriate labels and status context.
// 5. If not signed off, apply appropriate labels and status context and add a comment.
func handle(gc gitHubClient, cp commentPruner, log *logrus.Entry, org, repo string, pr github.PullRequest) error {
	l := log.WithField("pr", pr.Number)

	commitsMissingDCO, err := checkCommitMessages(gc, l, org, repo, pr.Number)
	if err != nil {
		l.WithError(err).Infof("Error running DCO check against commits in PR")
		return err
	}

	existingStatus, err := checkExistingStatus(gc, l, org, repo, pr.Head.SHA)
	if err != nil {
		l.WithError(err).Infof("Error checking existing PR status")
		return err
	}

	hasYesLabel, hasNoLabel, err := checkExistingLabels(gc, l, org, repo, pr.Number)
	if err != nil {
		l.WithError(err).Infof("Error checking existing PR labels")
		return err
	}

	return takeAction(gc, cp, l, org, repo, pr, commitsMissingDCO, existingStatus, hasYesLabel, hasNoLabel)
}

func markdownSHAList(org, repo string, list []github.GitCommit) string {
	lines := make([]string, len(list))
	lineFmt := "- [%s](https://github.com/%s/%s/commits/%s) %s"
	for i, commit := range list {
		if commit.SHA == nil {
			continue
		}
		// if we somehow encounter a SHA that's less than 7 characters, we will
		// just use it as is.
		shortSHA := *commit.SHA
		if len(shortSHA) > 7 {
			shortSHA = shortSHA[:7]
		}
		message := ""
		if commit.Message != nil {
			message = *commit.Message
		}

		// get the first line of the commit
		message = strings.Split(message, "\n")[0]

		lines[i] = fmt.Sprintf(lineFmt, shortSHA, org, repo, *commit.SHA, message)
	}
	return strings.Join(lines, "\n")
}

// shouldPrune finds comments left by this plugin.
func shouldPrune(log *logrus.Entry, botName string) func(github.IssueComment) bool {
	return func(comment github.IssueComment) bool {
		if comment.User.Login != botName {
			return false
		}
		return strings.Contains(comment.Body, dcoMsgPruneMatch)
	}
}

func handlePullRequestEvent(pc plugins.PluginClient, pe github.PullRequestEvent) error {
	org := pe.Repo.Owner.Login
	repo := pe.Repo.Name

	// we only reprocess on label, unlabel, open, reopen and synchronize events
	// this will reduce our API token usage and save processing of unrelated events
	switch pe.Action {
	case github.PullRequestActionLabeled,
		github.PullRequestActionUnlabeled,
		github.PullRequestActionOpened,
		github.PullRequestActionReopened,
		github.PullRequestActionSynchronize:
	default:
		return nil
	}

	return handle(pc.GitHubClient, pc.CommentPruner, pc.Logger, org, repo, pe.PullRequest)
}

func handleCommentEvent(pc plugins.PluginClient, ce github.GenericCommentEvent) error {
	// Only consider open PRs and new comments.
	if ce.IssueState != "open" || ce.Action != github.GenericCommentActionCreated || !ce.IsPR {
		return nil
	}
	// Only consider "/check-dco" comments.
	if !checkDCORe.MatchString(ce.Body) {
		return nil
	}

	gc := pc.GitHubClient
	org := ce.Repo.Owner.Login
	repo := ce.Repo.Name

	pr, err := gc.GetPullRequest(org, repo, ce.Number)
	if err != nil {
		return fmt.Errorf("error getting pull request for comment: %v", err)
	}

	return handle(gc, pc.CommentPruner, pc.Logger, org, repo, *pr)
}
<|endoftext|>


<commit_before>package s3api

import (
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
	"io/ioutil"
	"net/http"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
	xhttp "github.com/chrislusf/seaweedfs/weed/s3api/http"
	"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
)

type Action string

type Iam interface {
	Check(f http.HandlerFunc, actions ...Action) http.HandlerFunc
}

type IdentityAccessManagement struct {
	identities []*Identity
	domain     string
}

type Identity struct {
	Name        string
	Credentials []*Credential
	Actions     []Action
}

type Credential struct {
	AccessKey string
	SecretKey string
}

func NewIdentityAccessManagement(option *S3ApiServerOption) *IdentityAccessManagement {
	iam := &IdentityAccessManagement{
		domain: option.DomainName,
	}
	if option.Config != "" {
		if err := iam.loadS3ApiConfigurationFromFile(option.Config); err != nil {
			glog.Fatalf("fail to load config file %s: %v", option.Config, err)
		}
	} else {
		if err := iam.loadS3ApiConfigurationFromFiler(option); err != nil {
			glog.Warningf("fail to load config: %v", err)
		}
	}
	return iam
}

func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFiler(option *S3ApiServerOption) error {
	content, err := filer.ReadContent(option.Filer, filer.IamConfigDirecotry, filer.IamIdentityFile)
	if err != nil {
		return fmt.Errorf("read S3 config: %v", err)
	}
	return iam.loadS3ApiConfigurationFromBytes(content)
}

func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFile(fileName string) error {
	content, readErr := ioutil.ReadFile(fileName)
	if readErr != nil {
		glog.Warningf("fail to read %s : %v", fileName, readErr)
		return fmt.Errorf("fail to read %s : %v", fileName, readErr)
	}
	return iam.loadS3ApiConfigurationFromBytes(content)
}

func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromBytes(content []byte) error {
	s3ApiConfiguration := &iam_pb.S3ApiConfiguration{}
	if err := filer.ParseS3ConfigurationFromBytes(content, s3ApiConfiguration); err != nil {
		glog.Warningf("unmarshal error: %v", err)
		return fmt.Errorf("unmarshal error: %v", err)
	}
	if err := iam.loadS3ApiConfiguration(s3ApiConfiguration); err != nil {
		return err
	}
	return nil
}

func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3ApiConfiguration) error {
	var identities []*Identity
	for _, ident := range config.Identities {
		t := &Identity{
			Name:        ident.Name,
			Credentials: nil,
			Actions:     nil,
		}
		for _, action := range ident.Actions {
			t.Actions = append(t.Actions, Action(action))
		}
		for _, cred := range ident.Credentials {
			t.Credentials = append(t.Credentials, &Credential{
				AccessKey: cred.AccessKey,
				SecretKey: cred.SecretKey,
			})
		}
		identities = append(identities, t)
	}

	// atomically switch
	iam.identities = identities
	return nil
}

func (iam *IdentityAccessManagement) isEnabled() bool {

	return len(iam.identities) > 0
}

func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identity *Identity, cred *Credential, found bool) {

	for _, ident := range iam.identities {
		for _, cred := range ident.Credentials {
			if cred.AccessKey == accessKey {
				return ident, cred, true
			}
		}
	}
	return nil, nil, false
}

func (iam *IdentityAccessManagement) lookupAnonymous() (identity *Identity, found bool) {

	for _, ident := range iam.identities {
		if ident.Name == "anonymous" {
			return ident, true
		}
	}
	return nil, false
}

func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) http.HandlerFunc {

	if !iam.isEnabled() {
		return f
	}

	return func(w http.ResponseWriter, r *http.Request) {
		identity, errCode := iam.authRequest(r, action)
		if errCode == s3err.ErrNone {
			if identity != nil && identity.Name != "" {
				r.Header.Set(xhttp.AmzIdentityId, identity.Name)
				if identity.isAdmin() {
					r.Header.Set(xhttp.AmzIsAdmin, "true")
				}
			}
			f(w, r)
			return
		}
		writeErrorResponse(w, errCode, r.URL)
	}
}

// check whether the request has valid access keys
func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action) (*Identity, s3err.ErrorCode) {
	identity, s3Err := iam.authUser(r)
	if s3Err != s3err.ErrNone {
		return identity, s3Err
	}

	glog.V(3).Infof("user name: %v actions: %v", identity.Name, identity.Actions)

	bucket, _ := getBucketAndObject(r)

	if !identity.canDo(action, bucket) {
		return identity, s3err.ErrAccessDenied
	}

	return identity, s3err.ErrNone

}

func (iam *IdentityAccessManagement) authUser(r *http.Request) (*Identity, s3err.ErrorCode) {
	var identity *Identity
	var s3Err s3err.ErrorCode
	var found bool
	switch getRequestAuthType(r) {
	case authTypeStreamingSigned:
		return identity, s3err.ErrNone
	case authTypeUnknown:
		glog.V(3).Infof("unknown auth type")
		return identity, s3err.ErrAccessDenied
	case authTypePresignedV2, authTypeSignedV2:
		glog.V(3).Infof("v2 auth type")
		identity, s3Err = iam.isReqAuthenticatedV2(r)
	case authTypeSigned, authTypePresigned:
		glog.V(3).Infof("v4 auth type")
		identity, s3Err = iam.reqSignatureV4Verify(r)
	case authTypePostPolicy:
		glog.V(3).Infof("post policy auth type")
		return identity, s3err.ErrNone
	case authTypeJWT:
		glog.V(3).Infof("jwt auth type")
		return identity, s3err.ErrNotImplemented
	case authTypeAnonymous:
		identity, found = iam.lookupAnonymous()
		if !found {
			return identity, s3err.ErrAccessDenied
		}
	default:
		return identity, s3err.ErrNotImplemented
	}

	glog.V(3).Infof("auth error: %v", s3Err)
	if s3Err != s3err.ErrNone {
		return identity, s3Err
	}
	return identity, s3err.ErrNone
}

func (identity *Identity) canDo(action Action, bucket string) bool {
	if identity.isAdmin() {
		return true
	}
	for _, a := range identity.Actions {
		if a == action {
			return true
		}
	}
	if bucket == "" {
		return false
	}
	limitedByBucket := string(action) + ":" + bucket
	adminLimitedByBucket := s3_constants.ACTION_ADMIN + ":" + bucket
	for _, a := range identity.Actions {
		if string(a) == limitedByBucket {
			return true
		}
		if string(a) == adminLimitedByBucket {
			return true
		}
	}
	return false
}

func (identity *Identity) isAdmin() bool {
	for _, a := range identity.Actions {
		if a == "Admin" {
			return true
		}
	}
	return false
}
<commit_msg>s3: fix regression<commit_after>package s3api

import (
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
	"io/ioutil"
	"net/http"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
	xhttp "github.com/chrislusf/seaweedfs/weed/s3api/http"
	"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
)

type Action string

type Iam interface {
	Check(f http.HandlerFunc, actions ...Action) http.HandlerFunc
}

type IdentityAccessManagement struct {
	identities []*Identity
	domain     string
}

type Identity struct {
	Name        string
	Credentials []*Credential
	Actions     []Action
}

type Credential struct {
	AccessKey string
	SecretKey string
}

func NewIdentityAccessManagement(option *S3ApiServerOption) *IdentityAccessManagement {
	iam := &IdentityAccessManagement{
		domain: option.DomainName,
	}
	if option.Config != "" {
		if err := iam.loadS3ApiConfigurationFromFile(option.Config); err != nil {
			glog.Fatalf("fail to load config file %s: %v", option.Config, err)
		}
	} else {
		if err := iam.loadS3ApiConfigurationFromFiler(option); err != nil {
			glog.Warningf("fail to load config: %v", err)
		}
	}
	return iam
}

func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFiler(option *S3ApiServerOption) error {
	content, err := filer.ReadContent(option.Filer, filer.IamConfigDirecotry, filer.IamIdentityFile)
	if err != nil {
		return fmt.Errorf("read S3 config: %v", err)
	}
	return iam.loadS3ApiConfigurationFromBytes(content)
}

func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFile(fileName string) error {
	content, readErr := ioutil.ReadFile(fileName)
	if readErr != nil {
		glog.Warningf("fail to read %s : %v", fileName, readErr)
		return fmt.Errorf("fail to read %s : %v", fileName, readErr)
	}
	return iam.loadS3ApiConfigurationFromBytes(content)
}

func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromBytes(content []byte) error {
	s3ApiConfiguration := &iam_pb.S3ApiConfiguration{}
	if err := filer.ParseS3ConfigurationFromBytes(content, s3ApiConfiguration); err != nil {
		glog.Warningf("unmarshal error: %v", err)
		return fmt.Errorf("unmarshal error: %v", err)
	}
	if err := iam.loadS3ApiConfiguration(s3ApiConfiguration); err != nil {
		return err
	}
	return nil
}

func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3ApiConfiguration) error {
	var identities []*Identity
	for _, ident := range config.Identities {
		t := &Identity{
			Name:        ident.Name,
			Credentials: nil,
			Actions:     nil,
		}
		for _, action := range ident.Actions {
			t.Actions = append(t.Actions, Action(action))
		}
		for _, cred := range ident.Credentials {
			t.Credentials = append(t.Credentials, &Credential{
				AccessKey: cred.AccessKey,
				SecretKey: cred.SecretKey,
			})
		}
		identities = append(identities, t)
	}

	// atomically switch
	iam.identities = identities
	return nil
}

func (iam *IdentityAccessManagement) isEnabled() bool {

	return len(iam.identities) > 0
}

func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identity *Identity, cred *Credential, found bool) {

	for _, ident := range iam.identities {
		for _, cred := range ident.Credentials {
			if cred.AccessKey == accessKey {
				return ident, cred, true
			}
		}
	}
	return nil, nil, false
}

func (iam *IdentityAccessManagement) lookupAnonymous() (identity *Identity, found bool) {

	for _, ident := range iam.identities {
		if ident.Name == "anonymous" {
			return ident, true
		}
	}
	return nil, false
}

func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) http.HandlerFunc {

	if !iam.isEnabled() {
		return f
	}

	return func(w http.ResponseWriter, r *http.Request) {
		identity, errCode := iam.authRequest(r, action)
		if errCode == s3err.ErrNone {
			if identity != nil && identity.Name != "" {
				r.Header.Set(xhttp.AmzIdentityId, identity.Name)
				if identity.isAdmin() {
					r.Header.Set(xhttp.AmzIsAdmin, "true")
				}
			}
			f(w, r)
			return
		}
		writeErrorResponse(w, errCode, r.URL)
	}
}

// check whether the request has valid access keys
func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action) (*Identity, s3err.ErrorCode) {
	var identity *Identity
	var s3Err s3err.ErrorCode
	var found bool
	switch getRequestAuthType(r) {
	case authTypeStreamingSigned:
		return identity, s3err.ErrNone
	case authTypeUnknown:
		glog.V(3).Infof("unknown auth type")
		return identity, s3err.ErrAccessDenied
	case authTypePresignedV2, authTypeSignedV2:
		glog.V(3).Infof("v2 auth type")
		identity, s3Err = iam.isReqAuthenticatedV2(r)
	case authTypeSigned, authTypePresigned:
		glog.V(3).Infof("v4 auth type")
		identity, s3Err = iam.reqSignatureV4Verify(r)
	case authTypePostPolicy:
		glog.V(3).Infof("post policy auth type")
		return identity, s3err.ErrNone
	case authTypeJWT:
		glog.V(3).Infof("jwt auth type")
		return identity, s3err.ErrNotImplemented
	case authTypeAnonymous:
		identity, found = iam.lookupAnonymous()
		if !found {
			return identity, s3err.ErrAccessDenied
		}
	default:
		return identity, s3err.ErrNotImplemented
	}

	if s3Err != s3err.ErrNone {
		return identity, s3Err
	}

	glog.V(3).Infof("user name: %v actions: %v", identity.Name, identity.Actions)

	bucket, _ := getBucketAndObject(r)

	if !identity.canDo(action, bucket) {
		return identity, s3err.ErrAccessDenied
	}

	return identity, s3err.ErrNone

}

func (iam *IdentityAccessManagement) authUser(r *http.Request) (*Identity, s3err.ErrorCode) {
	var identity *Identity
	var s3Err s3err.ErrorCode
	var found bool
	switch getRequestAuthType(r) {
	case authTypeStreamingSigned:
		return identity, s3err.ErrNone
	case authTypeUnknown:
		glog.V(3).Infof("unknown auth type")
		return identity, s3err.ErrAccessDenied
	case authTypePresignedV2, authTypeSignedV2:
		glog.V(3).Infof("v2 auth type")
		identity, s3Err = iam.isReqAuthenticatedV2(r)
	case authTypeSigned, authTypePresigned:
		glog.V(3).Infof("v4 auth type")
		identity, s3Err = iam.reqSignatureV4Verify(r)
	case authTypePostPolicy:
		glog.V(3).Infof("post policy auth type")
		return identity, s3err.ErrNone
	case authTypeJWT:
		glog.V(3).Infof("jwt auth type")
		return identity, s3err.ErrNotImplemented
	case authTypeAnonymous:
		identity, found = iam.lookupAnonymous()
		if !found {
			return identity, s3err.ErrAccessDenied
		}
	default:
		return identity, s3err.ErrNotImplemented
	}

	glog.V(3).Infof("auth error: %v", s3Err)
	if s3Err != s3err.ErrNone {
		return identity, s3Err
	}
	return identity, s3err.ErrNone
}

func (identity *Identity) canDo(action Action, bucket string) bool {
	if identity.isAdmin() {
		return true
	}
	for _, a := range identity.Actions {
		if a == action {
			return true
		}
	}
	if bucket == "" {
		return false
	}
	limitedByBucket := string(action) + ":" + bucket
	adminLimitedByBucket := s3_constants.ACTION_ADMIN + ":" + bucket
	for _, a := range identity.Actions {
		if string(a) == limitedByBucket {
			return true
		}
		if string(a) == adminLimitedByBucket {
			return true
		}
	}
	return false
}

func (identity *Identity) isAdmin() bool {
	for _, a := range identity.Actions {
		if a == "Admin" {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package s3api

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/golang/protobuf/jsonpb"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
)

type Action string

const (
	ACTION_READ  = "Read"
	ACTION_WRITE = "Write"
	ACTION_ADMIN = "Admin"
)

type Iam interface {
	Check(f http.HandlerFunc, actions ...Action) http.HandlerFunc
}

type IdentityAccessManagement struct {
	identities []*Identity
	domain     string
}

type Identity struct {
	Name        string
	Credentials []*Credential
	Actions     []Action
}

type Credential struct {
	AccessKey string
	SecretKey string
}

func NewIdentityAccessManagement(fileName string, domain string) *IdentityAccessManagement {
	iam := &IdentityAccessManagement{
		domain: domain,
	}
	if fileName == "" {
		return iam
	}
	if err := iam.loadS3ApiConfiguration(fileName); err != nil {
		glog.Fatalf("fail to load config file %s: %v", fileName, err)
	}
	return iam
}

func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) error {

	s3ApiConfiguration := &iam_pb.S3ApiConfiguration{}

	rawData, readErr := ioutil.ReadFile(fileName)
	if readErr != nil {
		glog.Warningf("fail to read %s : %v", fileName, readErr)
		return fmt.Errorf("fail to read %s : %v", fileName, readErr)
	}

	glog.V(1).Infof("maybeLoadVolumeInfo Unmarshal volume info %v", fileName)
	if err := jsonpb.Unmarshal(bytes.NewReader(rawData), s3ApiConfiguration); err != nil {
		glog.Warningf("unmarshal error: %v", err)
		return fmt.Errorf("unmarshal %s error: %v", fileName, err)
	}

	for _, ident := range s3ApiConfiguration.Identities {
		t := &Identity{
			Name:        ident.Name,
			Credentials: nil,
			Actions:     nil,
		}
		for _, action := range ident.Actions {
			t.Actions = append(t.Actions, Action(action))
		}
		for _, cred := range ident.Credentials {
			t.Credentials = append(t.Credentials, &Credential{
				AccessKey: cred.AccessKey,
				SecretKey: cred.SecretKey,
			})
		}
		iam.identities = append(iam.identities, t)
	}

	return nil
}

func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identity *Identity, cred *Credential, found bool) {
	for _, ident := range iam.identities {
		for _, cred := range ident.Credentials {
			if cred.AccessKey == accessKey {
				return ident, cred, true
			}
		}
	}
	return nil, nil, false
}

func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, actions ...Action) http.HandlerFunc {

	if len(iam.identities) == 0 {
		return f
	}

	return func(w http.ResponseWriter, r *http.Request) {
		errCode := iam.authRequest(r, actions)
		if errCode == ErrNone {
			f(w, r)
			return
		}
		writeErrorResponse(w, errCode, r.URL)
	}
}

// check whether the request has valid access keys
func (iam *IdentityAccessManagement) authRequest(r *http.Request, actions []Action) ErrorCode {
	var identity *Identity
	var s3Err ErrorCode
	switch getRequestAuthType(r) {
	case authTypeStreamingSigned:
		return ErrNone
	case authTypeUnknown:
		glog.V(3).Infof("unknown auth type")
		return ErrAccessDenied
	case authTypePresignedV2, authTypeSignedV2:
		glog.V(3).Infof("v2 auth type")
		identity, s3Err = iam.isReqAuthenticatedV2(r)
	case authTypeSigned, authTypePresigned:
		glog.V(3).Infof("v4 auth type")
		identity, s3Err = iam.reqSignatureV4Verify(r)
	}

	glog.V(3).Infof("auth error: %v", s3Err)
	if s3Err != ErrNone {
		return s3Err
	}

	glog.V(3).Infof("user name: %v actions: %v", identity.Name, identity.Actions)

	if !identity.canDo(actions) {
		return ErrAccessDenied
	}

	return ErrNone

}

func (identity *Identity) canDo(actions []Action) bool {
	for _, a := range identity.Actions {
		for _, b := range actions {
			if a == b {
				return true
			}
		}
	}
	return false
}
<commit_msg>add unhandled request auth type<commit_after>package s3api

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/golang/protobuf/jsonpb"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
)

type Action string

const (
	ACTION_READ  = "Read"
	ACTION_WRITE = "Write"
	ACTION_ADMIN = "Admin"
)

type Iam interface {
	Check(f http.HandlerFunc, actions ...Action) http.HandlerFunc
}

type IdentityAccessManagement struct {
	identities []*Identity
	domain     string
}

type Identity struct {
	Name        string
	Credentials []*Credential
	Actions     []Action
}

type Credential struct {
	AccessKey string
	SecretKey string
}

func NewIdentityAccessManagement(fileName string, domain string) *IdentityAccessManagement {
	iam := &IdentityAccessManagement{
		domain: domain,
	}
	if fileName == "" {
		return iam
	}
	if err := iam.loadS3ApiConfiguration(fileName); err != nil {
		glog.Fatalf("fail to load config file %s: %v", fileName, err)
	}
	return iam
}

func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) error {

	s3ApiConfiguration := &iam_pb.S3ApiConfiguration{}

	rawData, readErr := ioutil.ReadFile(fileName)
	if readErr != nil {
		glog.Warningf("fail to read %s : %v", fileName, readErr)
		return fmt.Errorf("fail to read %s : %v", fileName, readErr)
	}

	glog.V(1).Infof("maybeLoadVolumeInfo Unmarshal volume info %v", fileName)
	if err := jsonpb.Unmarshal(bytes.NewReader(rawData), s3ApiConfiguration); err != nil {
		glog.Warningf("unmarshal error: %v", err)
		return fmt.Errorf("unmarshal %s error: %v", fileName, err)
	}

	for _, ident := range s3ApiConfiguration.Identities {
		t := &Identity{
			Name:        ident.Name,
			Credentials: nil,
			Actions:     nil,
		}
		for _, action := range ident.Actions {
			t.Actions = append(t.Actions, Action(action))
		}
		for _, cred := range ident.Credentials {
			t.Credentials = append(t.Credentials, &Credential{
				AccessKey: cred.AccessKey,
				SecretKey: cred.SecretKey,
			})
		}
		iam.identities = append(iam.identities, t)
	}

	return nil
}

func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identity *Identity, cred *Credential, found bool) {
	for _, ident := range iam.identities {
		for _, cred := range ident.Credentials {
			if cred.AccessKey == accessKey {
				return ident, cred, true
			}
		}
	}
	return nil, nil, false
}

func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, actions ...Action) http.HandlerFunc {

	if len(iam.identities) == 0 {
		return f
	}

	return func(w http.ResponseWriter, r *http.Request) {
		errCode := iam.authRequest(r, actions)
		if errCode == ErrNone {
			f(w, r)
			return
		}
		writeErrorResponse(w, errCode, r.URL)
	}
}

// check whether the request has valid access keys
func (iam *IdentityAccessManagement) authRequest(r *http.Request, actions []Action) ErrorCode {
	var identity *Identity
	var s3Err ErrorCode
	switch getRequestAuthType(r) {
	case authTypeStreamingSigned:
		return ErrNone
	case authTypeUnknown:
		glog.V(3).Infof("unknown auth type")
		return ErrAccessDenied
	case authTypePresignedV2, authTypeSignedV2:
		glog.V(3).Infof("v2 auth type")
		identity, s3Err = iam.isReqAuthenticatedV2(r)
	case authTypeSigned, authTypePresigned:
		glog.V(3).Infof("v4 auth type")
		identity, s3Err = iam.reqSignatureV4Verify(r)
	case authTypePostPolicy:
		return ErrNotImplemented;
	case authTypeJWT:
		return ErrNotImplemented;
	case authTypeAnonymous:
		return ErrNotImplemented
	}

	glog.V(3).Infof("auth error: %v", s3Err)
	if s3Err != ErrNone {
		return s3Err
	}

	glog.V(3).Infof("user name: %v actions: %v", identity.Name, identity.Actions)

	if !identity.canDo(actions) {
		return ErrAccessDenied
	}

	return ErrNone

}

func (identity *Identity) canDo(actions []Action) bool {
	for _, a := range identity.Actions {
		for _, b := range actions {
			if a == b {
				return true
			}
		}
	}
	return false
}
<|endoftext|>


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

// This file implements initialization and assignment checks.

package types

import (
	"fmt"

	"github.com/tcard/sgo/sgo/ast"
	"github.com/tcard/sgo/sgo/constant"
	"github.com/tcard/sgo/sgo/token"
)

// assignment reports whether x can be assigned to a variable of type T,
// if necessary by attempting to convert untyped values to the appropriate
// type. If x.mode == invalid upon return, then assignment has already
// issued an error message and the caller doesn't have to report another.
// Use T == nil to indicate assignment to an untyped blank identifier.
//
// TODO(gri) Should find a better way to handle in-band errors.
//
func (check *Checker) assignment(x *operand, T Type) bool {
	switch x.mode {
	case invalid:
		return true // error reported before
	case constant_, variable, mapindex, value, commaok:
		// ok
	default:
		unreachable()
	}

	// x must be a single value
	// (tuple types are never named - no need for underlying type)
	if t, _ := x.typ.(*Tuple); t != nil {
		assert(t.Len() > 1)
		check.errorf(x.pos(), "%d-valued expression %s used as single value", t.Len(), x)
		x.mode = invalid
		return false
	}

	if isUntyped(x.typ) {
		target := T
		// spec: "If an untyped constant is assigned to a variable of interface
		// type or the blank identifier, the constant is first converted to type
		// bool, rune, int, float64, complex128 or string respectively, depending
		// on whether the value is a boolean, rune, integer, floating-point, complex,
		// or string constant."
		if T == nil || IsInterface(T) {
			if T == nil && x.typ == Typ[UntypedNil] {
				check.errorf(x.pos(), "use of untyped nil")
				x.mode = invalid
				return false
			}
			target = defaultType(x.typ)
		}
		check.convertUntyped(x, target)
		if x.mode == invalid {
			return false
		}
	}

	// spec: "If a left-hand side is the blank identifier, any typed or
	// non-constant value except for the predeclared identifier nil may
	// be assigned to it."
	return T == nil || x.assignableTo(check.conf, T)
}

func (check *Checker) initConst(lhs *Const, x *operand) {
	if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] {
		if lhs.typ == nil {
			lhs.typ = Typ[Invalid]
		}
		return
	}

	// rhs must be a constant
	if x.mode != constant_ {
		check.errorf(x.pos(), "%s is not constant", x)
		if lhs.typ == nil {
			lhs.typ = Typ[Invalid]
		}
		return
	}
	assert(isConstType(x.typ))

	// If the lhs doesn't have a type yet, use the type of x.
	if lhs.typ == nil {
		lhs.typ = x.typ
	}

	if !check.assignment(x, lhs.typ) {
		if x.mode != invalid {
			check.errorf(x.pos(), "cannot define constant %s (type %s) as %s", lhs.Name(), lhs.typ, x)
		}
		return
	}

	lhs.val = x.val
}

// If result is set, lhs is a function result parameter and x is a return result.
func (check *Checker) initVar(lhs *Var, x *operand, result bool) Type {
	if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] {
		if lhs.typ == nil {
			lhs.typ = Typ[Invalid]
		}
		return nil
	}

	// If the lhs doesn't have a type yet, use the type of x.
	if lhs.typ == nil {
		typ := x.typ
		if isUntyped(typ) {
			// convert untyped types to default types
			if typ == Typ[UntypedNil] {
				check.errorf(x.pos(), "use of untyped nil")
				lhs.typ = Typ[Invalid]
				return nil
			}
			typ = defaultType(typ)
		}
		lhs.setType(typ)
		lhs.usable = true
		if debugUsable {
			fmt.Println("USABLE initVar:", lhs.name, fmt.Sprintf("%p", lhs), lhs.usable)
		}
	}

	if !check.assignment(x, lhs.typ) {
		if x.mode != invalid {
			if result {
				// don't refer to lhs.name because it may be an anonymous result parameter
				check.errorf(x.pos(), "cannot return %s as value of type %s", x, lhs.typ)
			} else {
				check.errorf(x.pos(), "cannot initialize %s with %s", lhs, x)
			}
		}
		return nil
	}

	return x.typ
}

func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type {
	if x.mode == invalid || x.typ == Typ[Invalid] {
		return nil
	}

	// Determine if the lhs is a (possibly parenthesized) identifier.
	ident, _ := unparen(lhs).(*ast.Ident)

	// Don't evaluate lhs if it is the blank identifier.
	if ident != nil && ident.Name == "_" {
		check.recordDef(ident, nil)
		if !check.assignment(x, nil) {
			assert(x.mode == invalid)
			x.typ = nil
		}
		return x.typ
	}

	// If the lhs is an identifier denoting a variable v, this assignment
	// is not a 'use' of v. Remember current value of v.used and restore
	// after evaluating the lhs via check.expr.
	var v *Var
	var v_used bool
	if ident != nil {
		if _, obj := check.scope.LookupParent(ident.Name, token.NoPos); obj != nil {
			v, _ = obj.(*Var)
			if v != nil {
				v_used = v.used
				v.usable = true
				if debugUsable {
					fmt.Println("USABLE assignVar:", v.name, fmt.Sprintf("%p", v), v.usable)
				}
			}
		}
	}

	var z operand
	check.expr(&z, lhs)
	if v != nil {
		v.used = v_used // restore v.used
	}

	if z.mode == invalid || z.typ == Typ[Invalid] {
		return nil
	}

	// spec: "Each left-hand side operand must be addressable, a map index
	// expression, or the blank identifier. Operands may be parenthesized."
	switch z.mode {
	case invalid:
		return nil
	case variable, mapindex:
		// ok
	default:
		check.errorf(z.pos(), "cannot assign to %s", &z)
		return nil
	}

	if !check.assignment(x, z.typ) {
		if x.mode != invalid {
			check.errorf(x.pos(), "cannot assign %s to %s", x, &z)
		}
		return nil
	}

	return x.typ
}

// If returnPos is valid, initVars is called to type-check the assignment of
// return expressions, and returnPos is the position of the return statement.
func (check *Checker) initVars(lhs []*Var, rhs *ast.ExprList, returnPos token.Pos, entangledLhs *Var) {
	var l int

	rhsIsEntangled := false
	if rhs.EntangledPos == -1 {
		var x operand
		check.expr(&x, rhs.List[0])
		if t, ok := x.typ.(*Tuple); ok {
			if t.entangled != nil {
				// a, b \ c := f()
				l = len(lhs) + 1
				rhsIsEntangled = true
			} else {
				// a, b, c := f()
				l = len(lhs)
			}
		} else {
			// a, b, c := x, y, z
			l = len(lhs)
		}
	} else if rhs.EntangledPos == 0 {
		// a, b \ c := \ z
		rhsIsEntangled = true
		l = 1
	} else if rhs.EntangledPos == len(rhs.List) {
		// a, b \ c := x, y \
		rhsIsEntangled = true
		l = len(lhs)
	} else {
		rhsIsEntangled = true
		check.error(rhs.List[0].Pos(), "must have values at either side of \\, not both")
	}

	if !rhsIsEntangled && entangledLhs != nil {
		check.error(rhs.List[0].Pos(), "expected entangled assignment, but right-hand side is not entangled")
	} else if rhsIsEntangled && entangledLhs == nil {
		check.error(lhs[0].Pos(), "expected entangled assignment, but left-hand side is not entangled")
	}

	get, r, commaOk := unpack(func(x *operand, i int) {
		check.expr(x, rhs.List[i])
		if isBoolean(x.typ) && (!isBooleanConst(*x) || constant.BoolVal(x.val) != false) {
			check.error(rhs.List[i].Pos(), "entangled bool must be the false constant")
		}
	}, len(rhs.List), l == 2 && entangledLhs == nil && !returnPos.IsValid())
	if get == nil || l != r {
		// invalidate lhs and use rhs
		for _, obj := range lhs {
			if obj.typ == nil {
				obj.typ = Typ[Invalid]
			}
		}
		if get == nil {
			return // error reported by unpack
		}
		check.useGetter(get, r)
		// TODO: Error reporting for entangled could be better.
		if returnPos.IsValid() {
			check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r)
			return
		}
		check.errorf(rhs.List[0].Pos(), "assignment count mismatch (%d vs %d)", l, r)
		return
	}

	var x operand
	if commaOk {
		var a [2]Type
		for i := range a {
			get(&x, i)
			a[i] = check.initVar(lhs[i], &x, returnPos.IsValid())
		}
		check.recordCommaOkTypes(rhs.List[0], a)
		return
	}

	for i, v := range append(lhs, entangledLhs) {
		if v == nil {
			continue
		}
		if rhs.EntangledPos == 0 && i != len(lhs) {
			continue
		} else if rhs.EntangledPos == len(lhs) && i == len(lhs) {
			continue
		}
		get(&x, i)
		check.initVar(v, &x, returnPos.IsValid())
	}
}

func (check *Checker) assignVars(lhs, rhs []ast.Expr) {
	l := len(lhs)
	get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2)
	if get == nil {
		return // error reported by unpack
	}
	if l != r {
		check.useGetter(get, r)
		check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r)
		return
	}

	var x operand
	if commaOk {
		var a [2]Type
		for i := range a {
			get(&x, i)
			a[i] = check.assignVar(lhs[i], &x)
		}
		check.recordCommaOkTypes(rhs[0], a)
		return
	}

	for i, lhs := range lhs {
		get(&x, i)
		check.assignVar(lhs, &x)
	}
}

func (check *Checker) shortVarDecl(pos token.Pos, lhs, rhs *ast.ExprList) {
	scope := check.scope
	entangledPos := lhs.EntangledPos

	// collect lhs variables
	var newVars []*Var
	var lhsVars = make([]*Var, 0, len(lhs.List))
	var entangledLhs *Var
	for i, lhs := range append(lhs.List) {
		isEntangled := entangledPos > 0 && i == entangledPos
		if isEntangled && lhs == nil {
			break
		}
		var obj *Var
		if ident, _ := lhs.(*ast.Ident); ident != nil {
			// Use the correct obj if the ident is redeclared. The
			// variable's scope starts after the declaration; so we
			// must use Scope.Lookup here and call Scope.Insert
			// (via check.declare) later.
			name := ident.Name
			if alt := scope.Lookup(name); alt != nil {
				// redeclared object must be a variable
				if alt, _ := alt.(*Var); alt != nil {
					obj = alt
				} else {
					check.errorf(lhs.Pos(), "cannot assign to %s", lhs)
				}
				check.recordUse(ident, alt)
			} else {
				// declare new variable, possibly a blank (_) variable
				obj = NewVar(ident.Pos(), check.pkg, name, nil)
				if name != "_" {
					newVars = append(newVars, obj)
				}
				check.recordDef(ident, obj)
			}
		} else {
			check.errorf(lhs.Pos(), "cannot declare %s", lhs)
		}
		if obj == nil {
			obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable
		}
		if isEntangled {
			entangledLhs = obj
		} else {
			lhsVars = append(lhsVars, obj)
		}
	}

	check.initVars(lhsVars, rhs, token.NoPos, entangledLhs)

	// declare new variables
	if len(newVars) > 0 {
		// spec: "The scope of a constant or variable identifier declared inside
		// a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl
		// for short variable declarations) and ends at the end of the innermost
		// containing block."
		scopePos := rhs.List[len(rhs.List)-1].End()
		for _, obj := range newVars {
			if obj == nil {
				break
			}
			check.declare(scope, nil, obj, scopePos) // recordObject already called
		}
	} else {
		check.softErrorf(pos, "no new variables on left side of :=")
	}

	if entangledLhs != nil {
		entangledLhs.collapses = lhsVars
		for _, v := range lhsVars {
			v.usable = false
			if debugUsable {
				fmt.Println("USABLE shortVarDecl:", v.name, fmt.Sprintf("%p", v), v.usable)
			}
		}
	}
}
<commit_msg>CommaOK-able ops now return entangled bool.<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file implements initialization and assignment checks.

package types

import (
	"fmt"

	"github.com/tcard/sgo/sgo/ast"
	"github.com/tcard/sgo/sgo/constant"
	"github.com/tcard/sgo/sgo/token"
)

// assignment reports whether x can be assigned to a variable of type T,
// if necessary by attempting to convert untyped values to the appropriate
// type. If x.mode == invalid upon return, then assignment has already
// issued an error message and the caller doesn't have to report another.
// Use T == nil to indicate assignment to an untyped blank identifier.
//
// TODO(gri) Should find a better way to handle in-band errors.
//
func (check *Checker) assignment(x *operand, T Type) bool {
	switch x.mode {
	case invalid:
		return true // error reported before
	case constant_, variable, mapindex, value, commaok:
		// ok
	default:
		unreachable()
	}

	// x must be a single value
	// (tuple types are never named - no need for underlying type)
	if t, _ := x.typ.(*Tuple); t != nil {
		assert(t.Len() > 1)
		check.errorf(x.pos(), "%d-valued expression %s used as single value", t.Len(), x)
		x.mode = invalid
		return false
	}

	if isUntyped(x.typ) {
		target := T
		// spec: "If an untyped constant is assigned to a variable of interface
		// type or the blank identifier, the constant is first converted to type
		// bool, rune, int, float64, complex128 or string respectively, depending
		// on whether the value is a boolean, rune, integer, floating-point, complex,
		// or string constant."
		if T == nil || IsInterface(T) {
			if T == nil && x.typ == Typ[UntypedNil] {
				check.errorf(x.pos(), "use of untyped nil")
				x.mode = invalid
				return false
			}
			target = defaultType(x.typ)
		}
		check.convertUntyped(x, target)
		if x.mode == invalid {
			return false
		}
	}

	// spec: "If a left-hand side is the blank identifier, any typed or
	// non-constant value except for the predeclared identifier nil may
	// be assigned to it."
	return T == nil || x.assignableTo(check.conf, T)
}

func (check *Checker) initConst(lhs *Const, x *operand) {
	if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] {
		if lhs.typ == nil {
			lhs.typ = Typ[Invalid]
		}
		return
	}

	// rhs must be a constant
	if x.mode != constant_ {
		check.errorf(x.pos(), "%s is not constant", x)
		if lhs.typ == nil {
			lhs.typ = Typ[Invalid]
		}
		return
	}
	assert(isConstType(x.typ))

	// If the lhs doesn't have a type yet, use the type of x.
	if lhs.typ == nil {
		lhs.typ = x.typ
	}

	if !check.assignment(x, lhs.typ) {
		if x.mode != invalid {
			check.errorf(x.pos(), "cannot define constant %s (type %s) as %s", lhs.Name(), lhs.typ, x)
		}
		return
	}

	lhs.val = x.val
}

// If result is set, lhs is a function result parameter and x is a return result.
func (check *Checker) initVar(lhs *Var, x *operand, result bool) Type {
	if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] {
		if lhs.typ == nil {
			lhs.typ = Typ[Invalid]
		}
		return nil
	}

	// If the lhs doesn't have a type yet, use the type of x.
	if lhs.typ == nil {
		typ := x.typ
		if isUntyped(typ) {
			// convert untyped types to default types
			if typ == Typ[UntypedNil] {
				check.errorf(x.pos(), "use of untyped nil")
				lhs.typ = Typ[Invalid]
				return nil
			}
			typ = defaultType(typ)
		}
		lhs.setType(typ)
		lhs.usable = true
		if debugUsable {
			fmt.Println("USABLE initVar:", lhs.name, fmt.Sprintf("%p", lhs), lhs.usable)
		}
	}

	if !check.assignment(x, lhs.typ) {
		if x.mode != invalid {
			if result {
				// don't refer to lhs.name because it may be an anonymous result parameter
				check.errorf(x.pos(), "cannot return %s as value of type %s", x, lhs.typ)
			} else {
				check.errorf(x.pos(), "cannot initialize %s with %s", lhs, x)
			}
		}
		return nil
	}

	return x.typ
}

func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type {
	if x.mode == invalid || x.typ == Typ[Invalid] {
		return nil
	}

	// Determine if the lhs is a (possibly parenthesized) identifier.
	ident, _ := unparen(lhs).(*ast.Ident)

	// Don't evaluate lhs if it is the blank identifier.
	if ident != nil && ident.Name == "_" {
		check.recordDef(ident, nil)
		if !check.assignment(x, nil) {
			assert(x.mode == invalid)
			x.typ = nil
		}
		return x.typ
	}

	// If the lhs is an identifier denoting a variable v, this assignment
	// is not a 'use' of v. Remember current value of v.used and restore
	// after evaluating the lhs via check.expr.
	var v *Var
	var v_used bool
	if ident != nil {
		if _, obj := check.scope.LookupParent(ident.Name, token.NoPos); obj != nil {
			v, _ = obj.(*Var)
			if v != nil {
				v_used = v.used
				v.usable = true
				if debugUsable {
					fmt.Println("USABLE assignVar:", v.name, fmt.Sprintf("%p", v), v.usable)
				}
			}
		}
	}

	var z operand
	check.expr(&z, lhs)
	if v != nil {
		v.used = v_used // restore v.used
	}

	if z.mode == invalid || z.typ == Typ[Invalid] {
		return nil
	}

	// spec: "Each left-hand side operand must be addressable, a map index
	// expression, or the blank identifier. Operands may be parenthesized."
	switch z.mode {
	case invalid:
		return nil
	case variable, mapindex:
		// ok
	default:
		check.errorf(z.pos(), "cannot assign to %s", &z)
		return nil
	}

	if !check.assignment(x, z.typ) {
		if x.mode != invalid {
			check.errorf(x.pos(), "cannot assign %s to %s", x, &z)
		}
		return nil
	}

	return x.typ
}

// If returnPos is valid, initVars is called to type-check the assignment of
// return expressions, and returnPos is the position of the return statement.
func (check *Checker) initVars(lhs []*Var, rhs *ast.ExprList, returnPos token.Pos, entangledLhs *Var) {
	var l int

	rhsIsEntangled := false
	if rhs.EntangledPos == -1 {
		var x operand
		check.expr(&x, rhs.List[0])
		if t, ok := x.typ.(*Tuple); ok {
			if t.entangled != nil {
				// a, b \ c := f()
				l = len(lhs) + 1
				rhsIsEntangled = true
			} else {
				// a, b, c := f()
				l = len(lhs)
			}
		} else {
			// a, b, c := x, y, z
			l = len(lhs)
			if entangledLhs != nil {
				// v \ ok := m[123]
				l += 1
			}
		}
	} else if rhs.EntangledPos == 0 {
		// a, b \ c := \ z
		rhsIsEntangled = true
		l = 1
	} else if rhs.EntangledPos == len(rhs.List) {
		// a, b \ c := x, y \
		rhsIsEntangled = true
		l = len(lhs)
	} else {
		rhsIsEntangled = true
		check.error(rhs.List[0].Pos(), "must have values at either side of \\, not both")
	}

	if rhsIsEntangled && entangledLhs == nil {
		check.error(lhs[0].Pos(), "expected entangled assignment, but left-hand side is not entangled")
	}

	get, r, commaOk := unpack(func(x *operand, i int) {
		check.expr(x, rhs.List[i])
		if isBoolean(x.typ) && (!isBooleanConst(*x) || constant.BoolVal(x.val) != false) {
			check.error(rhs.List[i].Pos(), "entangled bool must be the false constant")
		}
	}, len(rhs.List), l == 2 && entangledLhs != nil && !returnPos.IsValid())
	if !commaOk && (!rhsIsEntangled && entangledLhs != nil) {
		check.error(rhs.List[0].Pos(), "expected entangled assignment, but right-hand side is not entangled")
	}
	if get == nil || l != r {
		// invalidate lhs and use rhs
		for _, obj := range lhs {
			if obj.typ == nil {
				obj.typ = Typ[Invalid]
			}
		}
		if get == nil {
			return // error reported by unpack
		}
		check.useGetter(get, r)
		// TODO: Error reporting for entangled could be better.
		if returnPos.IsValid() {
			check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r)
			return
		}
		check.errorf(rhs.List[0].Pos(), "assignment count mismatch (%d vs %d)", l, r)
		return
	}

	var x operand
	if commaOk {
		var a [2]Type
		lhs := []*Var{lhs[0], entangledLhs}
		for i := range a {
			get(&x, i)
			a[i] = check.initVar(lhs[i], &x, returnPos.IsValid())
		}
		check.recordCommaOkTypes(rhs.List[0], a)
		return
	}

	for i, v := range append(lhs, entangledLhs) {
		if v == nil {
			continue
		}
		if rhs.EntangledPos == 0 && i != len(lhs) {
			continue
		} else if rhs.EntangledPos == len(lhs) && i == len(lhs) {
			continue
		}
		get(&x, i)
		check.initVar(v, &x, returnPos.IsValid())
	}
}

func (check *Checker) assignVars(lhs, rhs []ast.Expr) {
	l := len(lhs)
	get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2)
	if get == nil {
		return // error reported by unpack
	}
	if l != r {
		check.useGetter(get, r)
		check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r)
		return
	}

	var x operand
	if commaOk {
		var a [2]Type
		for i := range a {
			get(&x, i)
			a[i] = check.assignVar(lhs[i], &x)
		}
		check.recordCommaOkTypes(rhs[0], a)
		return
	}

	for i, lhs := range lhs {
		get(&x, i)
		check.assignVar(lhs, &x)
	}
}

func (check *Checker) shortVarDecl(pos token.Pos, lhs, rhs *ast.ExprList) {
	scope := check.scope
	entangledPos := lhs.EntangledPos

	// collect lhs variables
	var newVars []*Var
	var lhsVars = make([]*Var, 0, len(lhs.List))
	var entangledLhs *Var
	for i, lhs := range append(lhs.List) {
		isEntangled := entangledPos > 0 && i == entangledPos
		if isEntangled && lhs == nil {
			break
		}
		var obj *Var
		if ident, _ := lhs.(*ast.Ident); ident != nil {
			// Use the correct obj if the ident is redeclared. The
			// variable's scope starts after the declaration; so we
			// must use Scope.Lookup here and call Scope.Insert
			// (via check.declare) later.
			name := ident.Name
			if alt := scope.Lookup(name); alt != nil {
				// redeclared object must be a variable
				if alt, _ := alt.(*Var); alt != nil {
					obj = alt
				} else {
					check.errorf(lhs.Pos(), "cannot assign to %s", lhs)
				}
				check.recordUse(ident, alt)
			} else {
				// declare new variable, possibly a blank (_) variable
				obj = NewVar(ident.Pos(), check.pkg, name, nil)
				if name != "_" {
					newVars = append(newVars, obj)
				}
				check.recordDef(ident, obj)
			}
		} else {
			check.errorf(lhs.Pos(), "cannot declare %s", lhs)
		}
		if obj == nil {
			obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable
		}
		if isEntangled {
			entangledLhs = obj
		} else {
			lhsVars = append(lhsVars, obj)
		}
	}

	check.initVars(lhsVars, rhs, token.NoPos, entangledLhs)

	// declare new variables
	if len(newVars) > 0 {
		// spec: "The scope of a constant or variable identifier declared inside
		// a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl
		// for short variable declarations) and ends at the end of the innermost
		// containing block."
		scopePos := rhs.List[len(rhs.List)-1].End()
		for _, obj := range newVars {
			if obj == nil {
				break
			}
			check.declare(scope, nil, obj, scopePos) // recordObject already called
		}
	} else {
		check.softErrorf(pos, "no new variables on left side of :=")
	}

	if entangledLhs != nil {
		entangledLhs.collapses = lhsVars
		for _, v := range lhsVars {
			v.usable = false
			if debugUsable {
				fmt.Println("USABLE shortVarDecl:", v.name, fmt.Sprintf("%p", v), v.usable)
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/spf13/cobra"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/gopkg.in/yaml.v2"
import "io/ioutil"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/tj/go-debug"
import "os"
import "os/user"
import "fmt"
import "crypto/sha1"

func init() {
	prjCmd := &cobra.Command{
		Use:   "project [command]",
		Short: "does project stuff",
	}

	//flg := prjCmd.Flags()

	prjCmd.AddCommand(&cobra.Command{
		Use:   "create [name]",
		Short: "creates a new project with the specified name & adds the current folder to it",
		RunE:  createProjectCmd,
	})

	prjCmd.AddCommand(&cobra.Command{
		Use:   "add [project]",
		Short: "adds the current folder to a project",
		RunE:  addToProjectCmd,
	})

	cmds = append(cmds, prjCmd)
}

// loads a project file or creates a new one
func getProject(pName string) (*Project, error) {
	var err error
	prjDir, err := createProjectDir()
	if err != nil {
		return nil, err
	}

	pFilePath := fmt.Sprintf("%s/%s.yml", prjDir, pName)

	pCfg, err := openProject(pFilePath)

	if err != nil {
		return nil, fmt.Errorf("Error getting project file:\n%s\n", err)
	}

	pCfg.fName = pFilePath
	return pCfg, nil
}

// defines the configuration for a project
type Project struct {
	// YAML filename for project
	fName     string
	Functions map[string]ProjectFunction
}

type ProjectFunction struct {
	Id     string
	Config *Config
	Path   string
}

// writes this to a yaml file
func (p *Project) writeToFile() error {
	return writeToYamlFile(p, p.fName)
}

// adds a lambda function to this project
// or maybe not if the config file path
// is
func (p *Project) addFunction(c *Config) *Project {
	debug := debug.Debug("project.addFunction")
	var nHash string

	if c.Name == nil {
		debug("bummer, your lambda function doesn't have a name")
		return p
	}

	nHash = fmt.Sprintf("%x", sha1.Sum([]byte(*c.Name+c.fName)))

	p.Functions[*c.Name] = ProjectFunction{
		Id:     nHash,
		Config: c,
		Path:   c.fName,
	}

	return p
}

// cobra command for creating project
func createProjectCmd(c *cobra.Command, args []string) error {
	if len(args) == 0 || args[0] == "" {
		return fmt.Errorf("You didn't give us a project name! Please give us one.")
	}
	pName := args[0]
	pCfg, err := getProject(pName)

	if err != nil {
		fmt.Printf("Error creating project:\n%s\n", err)
		return nil
	}

	// add the current function to the project
	pCfg.addFunction(cfg)
	err = pCfg.writeToFile()

	if err != nil {
		fmt.Printf("Error saving project:\n%s\n", err)
		return nil
	}

	fmt.Printf("Created project %s\n", pName)

	return nil
}

// adds the current config file to the project
func addToProjectCmd(c *cobra.Command, args []string) error {
	return nil
}

// parses or creates a project config file
func openProject(fName string) (*Project, error) {
	debug := debug.Debug("core.openProject")
	// read the project file
	b, err := ioutil.ReadFile(fName)

	if err != nil && err == os.ErrNotExist {
		return nil, fmt.Errorf("Error reading project file: %s\n", err)
	}

	var pCfg *Project

	if b != nil {
		debug("have project data from %s, trying to parse it", fName)
		// if we got data from the file, parse it
		err = yaml.Unmarshal(b, &pCfg)
		if err != nil {
			return nil, fmt.Errorf("Error parsing project file %s:\n %s\n", fName, err)
		}
	} else {
		debug("no project found, making empty struct")
		pCfg = new(Project)
		pCfg.fName = fName
		pCfg.Functions = make(map[string]ProjectFunction)
	}

	return pCfg, nil
}

// creates a directory for storing projects
// returns the path to that directory
// or an error if doing something failed
//
// TODO: wtf will this do on windows?
func createProjectDir() (string, error) {
	debug := debug.Debug("project.createProjectDir")
	u, err := user.Current()
	if err != nil {
		return "", err
	}

	debug("got user info; home directory is %s", u.HomeDir)

	cfgDir := u.HomeDir + `/.lambda_phage`
	prjDir := cfgDir + "/projects"

	f, err := os.Stat(prjDir)
	if f != nil && f.IsDir() {
		return prjDir, nil
	}

	err = os.Mkdir(prjDir, 0755)
	if err != nil {
		return "", err
	}

	prjDir += "/projects"
	err = os.Mkdir(prjDir, 0755)
	if err != nil {
		return "", err
	}

	return prjDir, nil
}
<commit_msg>add support for add cmd<commit_after>package main

import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/spf13/cobra"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/gopkg.in/yaml.v2"
import "io/ioutil"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/tj/go-debug"
import "os"
import "os/user"
import "fmt"
import "crypto/sha1"

func init() {
	prjCmd := &cobra.Command{
		Use:   "project [command]",
		Short: "does project stuff",
	}

	//flg := prjCmd.Flags()

	prjCmd.AddCommand(&cobra.Command{
		Use:   "create [name]",
		Short: "creates a new project with the specified name & adds the current folder to it",
		RunE:  createProjectCmd,
	})

	prjCmd.AddCommand(&cobra.Command{
		Use:   "add [project]",
		Short: "adds the current folder to a project",
		RunE:  addToProjectCmd,
	})

	cmds = append(cmds, prjCmd)
}

// loads a project file or creates a new one
func getProject(pName string) (*Project, error) {
	var err error
	prjDir, err := createProjectDir()
	if err != nil {
		return nil, err
	}

	pFilePath := fmt.Sprintf("%s/%s.yml", prjDir, pName)

	pCfg, err := openProject(pFilePath)

	if err != nil {
		return nil, fmt.Errorf("Error getting project file:\n%s\n", err)
	}

	pCfg.fName = pFilePath
	return pCfg, nil
}

// defines the configuration for a project
type Project struct {
	// YAML filename for project
	fName     string
	Functions map[string]ProjectFunction
}

type ProjectFunction struct {
	Id     string
	Config *Config
	Path   string
}

// writes this to a yaml file
func (p *Project) writeToFile() error {
	return writeToYamlFile(p, p.fName)
}

// adds a lambda function to this project
// or maybe not if the config file path
// is
func (p *Project) addFunction(c *Config) *Project {
	debug := debug.Debug("project.addFunction")
	var nHash string

	if c.Name == nil {
		debug("bummer, your lambda function doesn't have a name")
		return p
	}

	nHash = fmt.Sprintf("%x", sha1.Sum([]byte(*c.Name+c.fName)))

	p.Functions[*c.Name] = ProjectFunction{
		Id:     nHash,
		Config: c,
		Path:   c.fName,
	}

	return p
}

// cobra command for creating project
func createProjectCmd(c *cobra.Command, args []string) error {
	if len(args) == 0 || args[0] == "" {
		return fmt.Errorf("You didn't give us a project name! Please give us one.")
	}
	pName := args[0]
	pCfg, err := getProject(pName)

	if err != nil {
		fmt.Printf("Error creating or opening project:\n%s\n", err)
		return nil
	}

	// add the current function to the project
	pCfg.addFunction(cfg)
	err = pCfg.writeToFile()

	if err != nil {
		fmt.Printf("Error saving project:\n%s\n", err)
		return nil
	}

	var action string

	switch c.Name() {
	case "add":
		action = fmt.Sprintf("added %s to", *cfg.Name)
	default:
		action = "created"
	}

	fmt.Printf("%s project %s\n", action, pName)

	return nil
}

// adds the current config file to the project
// uses createProjectCmd now, but will be separate from that
// so we reserve the right to change it whenever
func addToProjectCmd(c *cobra.Command, args []string) error {
	if cfg == nil || (cfg != nil && cfg.Name == nil) {
		return fmt.Errorf("No project configuration found! Please run `lambda-phage init` first!")
	}
	// just use create project for now
	return createProjectCmd(c, args)
}

// parses or creates a project config file
func openProject(fName string) (*Project, error) {
	debug := debug.Debug("core.openProject")
	// read the project file
	b, err := ioutil.ReadFile(fName)

	if err != nil && err == os.ErrNotExist {
		return nil, fmt.Errorf("Error reading project file: %s\n", err)
	}

	var pCfg *Project

	if b != nil {
		debug("have project data from %s, trying to parse it", fName)
		// if we got data from the file, parse it
		err = yaml.Unmarshal(b, &pCfg)
		if err != nil {
			return nil, fmt.Errorf("Error parsing project file %s:\n %s\n", fName, err)
		}
	} else {
		debug("no project found, making empty struct")
		pCfg = new(Project)
		pCfg.fName = fName
		pCfg.Functions = make(map[string]ProjectFunction)
	}

	return pCfg, nil
}

// creates a directory for storing projects
// returns the path to that directory
// or an error if doing something failed
//
// TODO: wtf will this do on windows?
func createProjectDir() (string, error) {
	debug := debug.Debug("project.createProjectDir")
	u, err := user.Current()
	if err != nil {
		return "", err
	}

	debug("got user info; home directory is %s", u.HomeDir)

	cfgDir := u.HomeDir + `/.lambda_phage`
	prjDir := cfgDir + "/projects"

	f, err := os.Stat(prjDir)
	if f != nil && f.IsDir() {
		return prjDir, nil
	}

	err = os.Mkdir(prjDir, 0755)
	if err != nil {
		return "", err
	}

	prjDir += "/projects"
	err = os.Mkdir(prjDir, 0755)
	if err != nil {
		return "", err
	}

	return prjDir, nil
}
<|endoftext|>


<commit_before>package cli

import (
	"os"

	"pkg.re/essentialkaos/ek.v8/arg"
	"pkg.re/essentialkaos/ek.v8/fmtc"
	"pkg.re/essentialkaos/ek.v8/terminal"
	"pkg.re/essentialkaos/ek.v8/usage"

	"github.com/gongled/vgrepo/repo"
	"pkg.re/essentialkaos/ek.v8/knf"
)

// ////////////////////////////////////////////////////////////////////////////////// //

const (
	APP  = "vgrepo"
	VER  = "2.0.0"
	DESC = "Simple CLI tool for managing Vagrant repositories"
)

const (
	CMD_ADD    = "add"
	CMD_DELETE = "delete"
	CMD_LIST   = "list"
	CMD_INFO   = "info"
	CMD_HELP   = "help"

	CMD_ADD_SHORTCUT    = "a"
	CMD_DELETE_SHORTCUT = "d"
	CMD_LIST_SHORTCUT   = "l"
	CMD_INFO_SHORTCUT   = "i"
)

const (
	KNF_STORAGE_URL  = "storage:url"
	KNF_STORAGE_PATH = "storage:path"
)

const (
	ARG_NO_COLOR = "nc:no-color"
	ARG_HELP     = "h:help"
	ARG_VER      = "v:version"
)

const (
	ERROR_UNSUPPORTED      = 1
	ERROR_INVALID_SETTINGS = 2
)

const CONFIG_FILE = "vgrepo.knf"

// ////////////////////////////////////////////////////////////////////////////////// //

var argMap = arg.Map{
	ARG_NO_COLOR: {Type: arg.BOOL},
	ARG_HELP:     {Type: arg.BOOL, Alias: "u:usage"},
	ARG_VER:      {Type: arg.BOOL, Alias: "ver"},
}

// ////////////////////////////////////////////////////////////////////////////////// //

func Init() {
	args, errs := arg.Parse(argMap)

	if len(errs) != 0 {
		fmtc.Println("Arguments parsing errors:")

		for _, err := range errs {
			fmtc.Printf("  %s\n", err.Error())
		}

		os.Exit(1)
	}

	if arg.GetB(ARG_NO_COLOR) {
		fmtc.DisableColors = true
	}

	if arg.GetB(ARG_VER) {
		showAbout()
		return
	}

	if arg.GetB(ARG_HELP) || len(args) == 0 {
		showUsage()
		return
	}

	switch len(args) {
	case 0:
		showUsage()
		return
	case 1:
		processCommand(args[0], nil)
	default:
		processCommand(args[0], args[1:])
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func prepare() {
	err := knf.Global(CONFIG_FILE)

	if err != nil {
		terminal.PrintErrorMessage(err.Error())
		os.Exit(ERROR_INVALID_SETTINGS)
	}
}

func processCommand(cmd string, args []string) {
	var err error

	prepare()

	switch cmd {
	case CMD_ADD, CMD_ADD_SHORTCUT:
		err = addCommand(args)
	case CMD_DELETE, CMD_DELETE_SHORTCUT:
		err = deleteCommand(args)
	case CMD_LIST, CMD_LIST_SHORTCUT:
		err = listCommand()
	case CMD_INFO, CMD_INFO_SHORTCUT:
		err = infoCommand(args)
	case CMD_HELP:
		showUsage()
	default:
		terminal.PrintErrorMessage("Unknown command")
		os.Exit(ERROR_UNSUPPORTED)
	}

	if err != nil {
		terminal.PrintErrorMessage(err.Error())
		os.Exit(ERROR_UNSUPPORTED)
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func addCommand(args []string) error {
	if len(args) != 3 {
		return fmtc.Errorf("Unable to handle %v arguments", len(args))
	}

	r := repo.NewRepository(
		knf.GetS(KNF_STORAGE_PATH),
		knf.GetS(KNF_STORAGE_URL),
		"openbox",
	)

	r.AddBox(args[0])

	fmtc.Println(r.Name)

	return nil
}

func deleteCommand(args []string) error {
	if len(args) != 1 {
		return fmtc.Errorf("Unable to handle %v arguments", len(args))
	} else {
		name := args[0]
		fmtc.Println(name)
	}

	return nil
}

func listCommand() error {
	return nil
}

func infoCommand(args []string) error {
	if len(args) != 1 {
		return fmtc.Errorf("Unable to handle %v arguments", len(args))
	} else {
		name := args[0]
		fmtc.Println(name)
	}

	return nil
}

// ////////////////////////////////////////////////////////////////////////////////// //

func showUsage() {
	info := usage.NewInfo(APP)

	info.AddCommand(CMD_ADD, "Add image to the Vagrant repository", "source", "name", "version")
	info.AddCommand(CMD_LIST, "Show the list of available images")
	info.AddCommand(CMD_DELETE, "Delete the image from the repository", "name", "version")
	info.AddCommand(CMD_INFO, "Display info of the particular repository", "name")
	info.AddCommand(CMD_HELP, "Display the current help message")

	info.AddOption(ARG_NO_COLOR, "Disable colors in output")
	info.AddOption(ARG_HELP, "Show this help message")
	info.AddOption(ARG_VER, "Show version")

	info.AddExample(
		"add $HOME/powerbox-1.0.0.box powerbox 1.1.0",
		"Add image to the Vagrant repository",
	)

	info.AddExample(
		"list",
		"Show the list of available images",
	)

	info.AddExample(
		"remove powerbox 1.1.0",
		"Remove the image from the repository",
	)

	info.AddExample(
		"info powerbox",
		"Remove the image from the repository",
	)

	info.Render()
}

func showAbout() {
	about := &usage.About{
		App:     APP,
		Version: VER,
		Desc:    DESC,
		Year:    2014,
		Owner:   "Gleb E Goncharov",
		License: "MIT License",
	}

	about.Render()
}

// ////////////////////////////////////////////////////////////////////////////////// //
<commit_msg>Changed order of imports for pretty looking view.<commit_after>package cli

import (
	"os"

	"pkg.re/essentialkaos/ek.v8/arg"
	"pkg.re/essentialkaos/ek.v8/fmtc"
	"pkg.re/essentialkaos/ek.v8/knf"
	"pkg.re/essentialkaos/ek.v8/terminal"
	"pkg.re/essentialkaos/ek.v8/usage"

	"github.com/gongled/vgrepo/repo"
)

// ////////////////////////////////////////////////////////////////////////////////// //

const (
	APP  = "vgrepo"
	VER  = "2.0.0"
	DESC = "Simple CLI tool for managing Vagrant repositories"
)

const (
	CMD_ADD    = "add"
	CMD_DELETE = "delete"
	CMD_LIST   = "list"
	CMD_INFO   = "info"
	CMD_HELP   = "help"

	CMD_ADD_SHORTCUT    = "a"
	CMD_DELETE_SHORTCUT = "d"
	CMD_LIST_SHORTCUT   = "l"
	CMD_INFO_SHORTCUT   = "i"
)

const (
	KNF_STORAGE_URL  = "storage:url"
	KNF_STORAGE_PATH = "storage:path"
)

const (
	ARG_NO_COLOR = "nc:no-color"
	ARG_HELP     = "h:help"
	ARG_VER      = "v:version"
)

const (
	ERROR_UNSUPPORTED      = 1
	ERROR_INVALID_SETTINGS = 2
)

const CONFIG_FILE = "vgrepo.knf"

// ////////////////////////////////////////////////////////////////////////////////// //

var argMap = arg.Map{
	ARG_NO_COLOR: {Type: arg.BOOL},
	ARG_HELP:     {Type: arg.BOOL, Alias: "u:usage"},
	ARG_VER:      {Type: arg.BOOL, Alias: "ver"},
}

// ////////////////////////////////////////////////////////////////////////////////// //

func Init() {
	args, errs := arg.Parse(argMap)

	if len(errs) != 0 {
		fmtc.Println("Arguments parsing errors:")

		for _, err := range errs {
			fmtc.Printf("  %s\n", err.Error())
		}

		os.Exit(1)
	}

	if arg.GetB(ARG_NO_COLOR) {
		fmtc.DisableColors = true
	}

	if arg.GetB(ARG_VER) {
		showAbout()
		return
	}

	if arg.GetB(ARG_HELP) || len(args) == 0 {
		showUsage()
		return
	}

	switch len(args) {
	case 0:
		showUsage()
		return
	case 1:
		processCommand(args[0], nil)
	default:
		processCommand(args[0], args[1:])
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func prepare() {
	err := knf.Global(CONFIG_FILE)

	if err != nil {
		terminal.PrintErrorMessage(err.Error())
		os.Exit(ERROR_INVALID_SETTINGS)
	}
}

func processCommand(cmd string, args []string) {
	var err error

	prepare()

	switch cmd {
	case CMD_ADD, CMD_ADD_SHORTCUT:
		err = addCommand(args)
	case CMD_DELETE, CMD_DELETE_SHORTCUT:
		err = deleteCommand(args)
	case CMD_LIST, CMD_LIST_SHORTCUT:
		err = listCommand()
	case CMD_INFO, CMD_INFO_SHORTCUT:
		err = infoCommand(args)
	case CMD_HELP:
		showUsage()
	default:
		terminal.PrintErrorMessage("Unknown command")
		os.Exit(ERROR_UNSUPPORTED)
	}

	if err != nil {
		terminal.PrintErrorMessage(err.Error())
		os.Exit(ERROR_UNSUPPORTED)
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func addCommand(args []string) error {
	if len(args) != 3 {
		return fmtc.Errorf("Unable to handle %v arguments", len(args))
	}

	r := repo.NewRepository(
		knf.GetS(KNF_STORAGE_PATH),
		knf.GetS(KNF_STORAGE_URL),
		"openbox",
	)

	r.AddBox(args[0])

	fmtc.Println(r.Name)

	return nil
}

func deleteCommand(args []string) error {
	if len(args) != 1 {
		return fmtc.Errorf("Unable to handle %v arguments", len(args))
	} else {
		name := args[0]
		fmtc.Println(name)
	}

	return nil
}

func listCommand() error {
	return nil
}

func infoCommand(args []string) error {
	if len(args) != 1 {
		return fmtc.Errorf("Unable to handle %v arguments", len(args))
	} else {
		name := args[0]
		fmtc.Println(name)
	}

	return nil
}

// ////////////////////////////////////////////////////////////////////////////////// //

func showUsage() {
	info := usage.NewInfo(APP)

	info.AddCommand(CMD_ADD, "Add image to the Vagrant repository", "source", "name", "version")
	info.AddCommand(CMD_LIST, "Show the list of available images")
	info.AddCommand(CMD_DELETE, "Delete the image from the repository", "name", "version")
	info.AddCommand(CMD_INFO, "Display info of the particular repository", "name")
	info.AddCommand(CMD_HELP, "Display the current help message")

	info.AddOption(ARG_NO_COLOR, "Disable colors in output")
	info.AddOption(ARG_HELP, "Show this help message")
	info.AddOption(ARG_VER, "Show version")

	info.AddExample(
		"add $HOME/powerbox-1.0.0.box powerbox 1.1.0",
		"Add image to the Vagrant repository",
	)

	info.AddExample(
		"list",
		"Show the list of available images",
	)

	info.AddExample(
		"remove powerbox 1.1.0",
		"Remove the image from the repository",
	)

	info.AddExample(
		"info powerbox",
		"Remove the image from the repository",
	)

	info.Render()
}

func showAbout() {
	about := &usage.About{
		App:     APP,
		Version: VER,
		Desc:    DESC,
		Year:    2014,
		Owner:   "Gleb E Goncharov",
		License: "MIT License",
	}

	about.Render()
}

// ////////////////////////////////////////////////////////////////////////////////// //
<|endoftext|>


<commit_before><commit_msg>Fix crash when parsing chromaprint 1.4 output<commit_after><|endoftext|>