<commit_before>/*
Copyright 2020 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 release

import (
	"strings"

	"github.com/blang/semver"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"sigs.k8s.io/release-sdk/git"
	"sigs.k8s.io/release-utils/util"
)

// GitObjectPusher is an object that pushes things to a gitrepo
type GitObjectPusher struct {
	repo git.Repo
	opts *GitObjectPusherOptions
}

var dryRunLabel = map[bool]string{true: " --dry-run", false: ""}

// GitObjectPusherOptions struct to hold the pusher options
type GitObjectPusherOptions struct {
	// Flago simulate pushes, passes --dry-run to git
	DryRun bool

	// Number of times to retry pushes
	MaxRetries int

	// Path to the repository
	RepoPath string
}

// NewGitPusher returns a new git object pusher
func NewGitPusher(opts *GitObjectPusherOptions) (*GitObjectPusher, error) {
	repo, err := git.OpenRepo(opts.RepoPath)
	if err != nil {
		return nil, errors.Wrap(err, "while opening repository")
	}

	logrus.Infof("Checkout %s branch to push objects", git.DefaultBranch)
	if err := repo.Checkout(git.DefaultBranch); err != nil {
		return nil, errors.Wrapf(err, "checking out %s branch", git.DefaultBranch)
	}

	// Pass the dry-run flag to the repo
	if opts.DryRun {
		logrus.Debug("Setting dry run flag to repository, pushing will be simuluated")
		repo.SetDry()
	}

	// Set the number of retries for the git operations:
	repo.SetMaxRetries(opts.MaxRetries)

	return &GitObjectPusher{
		repo: *repo,
		opts: opts,
	}, nil
}

// PushBranches Convenience method to push a list of branches
func (gp *GitObjectPusher) PushBranches(branchList []string) error {
	for _, branchName := range branchList {
		if err := gp.PushBranch(branchName); err != nil {
			return errors.Wrapf(err, "pushing %s branch", branchName)
		}
	}
	logrus.Infof("Successfully pushed %d branches", len(branchList))
	return nil
}

// PushBranch pushes a branch to the repository
//  this function is idempotent.
func (gp *GitObjectPusher) PushBranch(branchName string) error {
	// Check if the branch name is correct
	if err := gp.checkBranchName(branchName); err != nil {
		return errors.Wrap(err, "checking branch name")
	}

	// To be able to push a branch the ref has to exist in the local repo:
	branchExists, err := gp.repo.HasBranch(branchName)
	if err != nil {
		return errors.Wrap(err, "checking if branch already exists locally")
	}
	if !branchExists {
		return errors.Errorf("unable to push branch %s, it does not exist in the local repo", branchName)
	}

	// Checkout the branch before merging
	logrus.Infof("Checking out branch %s to merge upstream changes", branchName)
	if err := gp.repo.Checkout(branchName); err != nil {
		return errors.Wrapf(err, "checking out branch %s", git.Remotify(branchName))
	}

	if err := gp.mergeRemoteIfRequired(branchName); err != nil {
		return errors.Wrap(err, "merge remote if required")
	}

	logrus.Infof("Pushing%s %s branch:", dryRunLabel[gp.opts.DryRun], branchName)
	if err := gp.repo.Push(branchName); err != nil {
		return errors.Wrapf(err, "pushing branch %s", branchName)
	}
	logrus.Infof("Branch %s pushed successfully", branchName)
	return nil
}

// PushTags convenience method to push a list of tags to the remote repo
func (gp *GitObjectPusher) PushTags(tagList []string) (err error) {
	for _, tag := range tagList {
		if err := gp.PushTag(tag); err != nil {
			return errors.Wrapf(err, "while pushing %s tag", tag)
		}
	}
	logrus.Infof("Pushed %d tags to the remote repo", len(tagList))
	return nil
}

// PushTag pushes a tag to the master repo
func (gp *GitObjectPusher) PushTag(newTag string) (err error) {
	// Verify that the tag is a valid tag
	if err := gp.checkTagName(newTag); err != nil {
		return errors.Wrap(err, "parsing version tag")
	}

	// Check if tag already exists
	currentTags, err := gp.repo.Tags()
	if err != nil {
		return errors.Wrap(err, "checking if tag exists")
	}

	// verify that the tag exists locally before trying to push
	tagExists := false
	for _, tag := range currentTags {
		if tag == newTag {
			tagExists = true
			break
		}
	}
	if !tagExists {
		return errors.Errorf("unable to push tag %s, it does not exist in the repo yet", newTag)
	}

	// CHeck if tag already exists in the remote repo
	tagExists, err = gp.repo.HasRemoteTag(newTag)
	if err != nil {
		return errors.Wrapf(err, "checking of tag %s exists", newTag)
	}

	// If the tag already exists in the remote, we return success
	if tagExists {
		logrus.Infof("Tag %s already exists in remote. Noop.", newTag)
		return nil
	}

	logrus.Infof("Pushing%s tag for version %s", dryRunLabel[gp.opts.DryRun], newTag)

	// Push the new tag, retrying up to opts.MaxRetries times
	if err := gp.repo.Push(newTag); err != nil {
		return errors.Wrapf(err, "pushing tag %s", newTag)
	}

	logrus.Infof("Successfully pushed tag %s", newTag)
	return nil
}

// checkTagName verifies that the specified tag name is valid
func (gp *GitObjectPusher) checkTagName(tagName string) error {
	_, err := util.TagStringToSemver(tagName)
	if err != nil {
		return errors.Wrap(err, "tranforming tag into semver")
	}
	return nil
}

// checkBranchName verifies that the branch name is valid
func (gp *GitObjectPusher) checkBranchName(branchName string) error {
	if !strings.HasPrefix(branchName, "release-") {
		return errors.New("Branch name has to start with release-")
	}
	versionTag := strings.TrimPrefix(branchName, "release-")
	// Add .0 and check is we get a valid semver
	_, err := semver.Parse(versionTag + ".0")
	if err != nil {
		return errors.Wrap(err, "parsing semantic version in branchname")
	}
	return nil
}

// PushMain pushes the main branch to the origin
func (gp *GitObjectPusher) PushMain() error {
	logrus.Infof("Checkout %s branch to push objects", git.DefaultBranch)
	if err := gp.repo.Checkout(git.DefaultBranch); err != nil {
		return errors.Wrapf(err, "checking out %s branch", git.DefaultBranch)
	}

	// logrun -v git status -s || return 1
	status, err := gp.repo.Status()
	if err != nil {
		return errors.Wrap(err, "while reading the repository status")
	}
	if status.String() == "" {
		logrus.Info("Repository status: no modified paths")
	} else {
		logrus.Info(status.String())
	}

	// logrun -v git show || return 1
	lastLog, err := gp.repo.ShowLastCommit()
	if err != nil {
		return errors.Wrap(err, "getting last commit data from log")
	}
	logrus.Info(lastLog)

	if err := gp.mergeRemoteIfRequired(git.DefaultBranch); err != nil {
		return errors.Wrap(err, "merge remote if required")
	}

	logrus.Infof("Pushing%s %s branch", dryRunLabel[gp.opts.DryRun], git.DefaultBranch)

	// logrun -s git push$dryrun_flag origin master || return 1
	if err := gp.repo.Push(git.DefaultBranch); err != nil {
		return errors.Wrapf(err, "pushing %s branch", git.DefaultBranch)
	}
	return nil
}

func (gp *GitObjectPusher) mergeRemoteIfRequired(branch string) error {
	branch = git.Remotify(branch)
	branchParts := strings.Split(branch, "/")
	logrus.Infof("Merging %s branch if required", branch)

	logrus.Infof("Fetching from %s", git.DefaultRemote)
	if _, err := gp.repo.FetchRemote(git.DefaultRemote); err != nil {
		return errors.Wrap(err, "fetch remote")
	}

	branchExists, err := gp.repo.HasRemoteBranch(branchParts[1])
	if err != nil {
		return errors.Wrapf(
			err, "checking if branch %s exists in repo remote", branch,
		)
	}
	if !branchExists {
		logrus.Infof(
			"Git repository does not have remote branch %s, not attempting merge", branch,
		)
		return nil
	}

	logrus.Infof("Merging %s branch", branch)
	if err := gp.repo.Merge(branch); err != nil {
		return errors.Wrapf(
			err, "merging remote branch %s to local repo", branch,
		)
	}

	logrus.Info("Local branch is now up to date")
	return nil
}
<commit_msg>Rebase the main branch instead of merging it<commit_after>/*
Copyright 2020 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 release

import (
	"fmt"
	"strings"

	"github.com/blang/semver"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"sigs.k8s.io/release-sdk/git"
	"sigs.k8s.io/release-utils/util"
)

// GitObjectPusher is an object that pushes things to a gitrepo
type GitObjectPusher struct {
	repo git.Repo
	opts *GitObjectPusherOptions
}

var dryRunLabel = map[bool]string{true: " --dry-run", false: ""}

// GitObjectPusherOptions struct to hold the pusher options
type GitObjectPusherOptions struct {
	// Flago simulate pushes, passes --dry-run to git
	DryRun bool

	// Number of times to retry pushes
	MaxRetries int

	// Path to the repository
	RepoPath string
}

// NewGitPusher returns a new git object pusher
func NewGitPusher(opts *GitObjectPusherOptions) (*GitObjectPusher, error) {
	repo, err := git.OpenRepo(opts.RepoPath)
	if err != nil {
		return nil, errors.Wrap(err, "while opening repository")
	}

	logrus.Infof("Checkout %s branch to push objects", git.DefaultBranch)
	if err := repo.Checkout(git.DefaultBranch); err != nil {
		return nil, errors.Wrapf(err, "checking out %s branch", git.DefaultBranch)
	}

	// Pass the dry-run flag to the repo
	if opts.DryRun {
		logrus.Debug("Setting dry run flag to repository, pushing will be simuluated")
		repo.SetDry()
	}

	// Set the number of retries for the git operations:
	repo.SetMaxRetries(opts.MaxRetries)

	return &GitObjectPusher{
		repo: *repo,
		opts: opts,
	}, nil
}

// PushBranches Convenience method to push a list of branches
func (gp *GitObjectPusher) PushBranches(branchList []string) error {
	for _, branchName := range branchList {
		if err := gp.PushBranch(branchName); err != nil {
			return errors.Wrapf(err, "pushing %s branch", branchName)
		}
	}
	logrus.Infof("Successfully pushed %d branches", len(branchList))
	return nil
}

// PushBranch pushes a branch to the repository
//  this function is idempotent.
func (gp *GitObjectPusher) PushBranch(branchName string) error {
	// Check if the branch name is correct
	if err := gp.checkBranchName(branchName); err != nil {
		return errors.Wrap(err, "checking branch name")
	}

	// To be able to push a branch the ref has to exist in the local repo:
	branchExists, err := gp.repo.HasBranch(branchName)
	if err != nil {
		return errors.Wrap(err, "checking if branch already exists locally")
	}
	if !branchExists {
		return errors.Errorf("unable to push branch %s, it does not exist in the local repo", branchName)
	}

	// Checkout the branch before merging
	logrus.Infof("Checking out branch %s to merge upstream changes", branchName)
	if err := gp.repo.Checkout(branchName); err != nil {
		return errors.Wrapf(err, "checking out branch %s", git.Remotify(branchName))
	}

	if err := gp.mergeRemoteIfRequired(branchName); err != nil {
		return errors.Wrap(err, "merge remote if required")
	}

	logrus.Infof("Pushing%s %s branch:", dryRunLabel[gp.opts.DryRun], branchName)
	if err := gp.repo.Push(branchName); err != nil {
		return errors.Wrapf(err, "pushing branch %s", branchName)
	}
	logrus.Infof("Branch %s pushed successfully", branchName)
	return nil
}

// PushTags convenience method to push a list of tags to the remote repo
func (gp *GitObjectPusher) PushTags(tagList []string) (err error) {
	for _, tag := range tagList {
		if err := gp.PushTag(tag); err != nil {
			return errors.Wrapf(err, "while pushing %s tag", tag)
		}
	}
	logrus.Infof("Pushed %d tags to the remote repo", len(tagList))
	return nil
}

// PushTag pushes a tag to the master repo
func (gp *GitObjectPusher) PushTag(newTag string) (err error) {
	// Verify that the tag is a valid tag
	if err := gp.checkTagName(newTag); err != nil {
		return errors.Wrap(err, "parsing version tag")
	}

	// Check if tag already exists
	currentTags, err := gp.repo.Tags()
	if err != nil {
		return errors.Wrap(err, "checking if tag exists")
	}

	// verify that the tag exists locally before trying to push
	tagExists := false
	for _, tag := range currentTags {
		if tag == newTag {
			tagExists = true
			break
		}
	}
	if !tagExists {
		return errors.Errorf("unable to push tag %s, it does not exist in the repo yet", newTag)
	}

	// CHeck if tag already exists in the remote repo
	tagExists, err = gp.repo.HasRemoteTag(newTag)
	if err != nil {
		return errors.Wrapf(err, "checking of tag %s exists", newTag)
	}

	// If the tag already exists in the remote, we return success
	if tagExists {
		logrus.Infof("Tag %s already exists in remote. Noop.", newTag)
		return nil
	}

	logrus.Infof("Pushing%s tag for version %s", dryRunLabel[gp.opts.DryRun], newTag)

	// Push the new tag, retrying up to opts.MaxRetries times
	if err := gp.repo.Push(newTag); err != nil {
		return errors.Wrapf(err, "pushing tag %s", newTag)
	}

	logrus.Infof("Successfully pushed tag %s", newTag)
	return nil
}

// checkTagName verifies that the specified tag name is valid
func (gp *GitObjectPusher) checkTagName(tagName string) error {
	_, err := util.TagStringToSemver(tagName)
	if err != nil {
		return errors.Wrap(err, "tranforming tag into semver")
	}
	return nil
}

// checkBranchName verifies that the branch name is valid
func (gp *GitObjectPusher) checkBranchName(branchName string) error {
	if !strings.HasPrefix(branchName, "release-") {
		return errors.New("Branch name has to start with release-")
	}
	versionTag := strings.TrimPrefix(branchName, "release-")
	// Add .0 and check is we get a valid semver
	_, err := semver.Parse(versionTag + ".0")
	if err != nil {
		return errors.Wrap(err, "parsing semantic version in branchname")
	}
	return nil
}

// PushMain pushes the main branch to the origin
func (gp *GitObjectPusher) PushMain() error {
	logrus.Infof("Checkout %s branch to push objects", git.DefaultBranch)
	if err := gp.repo.Checkout(git.DefaultBranch); err != nil {
		return errors.Wrapf(err, "checking out %s branch", git.DefaultBranch)
	}

	// logrun -v git status -s || return 1
	status, err := gp.repo.Status()
	if err != nil {
		return errors.Wrap(err, "while reading the repository status")
	}
	if status.String() == "" {
		logrus.Info("Repository status: no modified paths")
	} else {
		logrus.Info(status.String())
	}

	// logrun -v git show || return 1
	lastLog, err := gp.repo.ShowLastCommit()
	if err != nil {
		return errors.Wrap(err, "getting last commit data from log")
	}
	logrus.Info(lastLog)

	logrus.Info("Rebase master branch")

	_, err = gp.repo.FetchRemote(git.DefaultRemote)
	if err != nil {
		return errors.Wrap(err, "while fetching origin repository")
	}

	if err := gp.repo.Rebase(fmt.Sprintf("%s/%s", git.DefaultRemote, git.DefaultBranch)); err != nil {
		return errors.Wrap(err, "rebasing repository")
	}

	logrus.Infof("Pushing%s %s branch", dryRunLabel[gp.opts.DryRun], git.DefaultBranch)

	// logrun -s git push$dryrun_flag origin master || return 1
	if err := gp.repo.Push(git.DefaultBranch); err != nil {
		return errors.Wrapf(err, "pushing %s branch", git.DefaultBranch)
	}
	return nil
}

func (gp *GitObjectPusher) mergeRemoteIfRequired(branch string) error {
	branch = git.Remotify(branch)
	branchParts := strings.Split(branch, "/")
	logrus.Infof("Merging %s branch if required", branch)

	logrus.Infof("Fetching from %s", git.DefaultRemote)
	if _, err := gp.repo.FetchRemote(git.DefaultRemote); err != nil {
		return errors.Wrap(err, "fetch remote")
	}

	branchExists, err := gp.repo.HasRemoteBranch(branchParts[1])
	if err != nil {
		return errors.Wrapf(
			err, "checking if branch %s exists in repo remote", branch,
		)
	}
	if !branchExists {
		logrus.Infof(
			"Git repository does not have remote branch %s, not attempting merge", branch,
		)
		return nil
	}

	logrus.Infof("Merging %s branch", branch)
	if err := gp.repo.Merge(branch); err != nil {
		return errors.Wrapf(
			err, "merging remote branch %s to local repo", branch,
		)
	}

	logrus.Info("Local branch is now up to date")
	return nil
}
<|endoftext|>


<commit_before>package v1

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"time"

	"github.com/fritzpay/paymentd/pkg/metadata"
	"github.com/fritzpay/paymentd/pkg/paymentd/principal"
	"github.com/fritzpay/paymentd/pkg/service"
	"github.com/gorilla/mux"
	"gopkg.in/inconshreveable/log15.v2"
)

type PrincipalAdminAPIResponse struct {
	AdminAPIResponse
}

// handler to create or change a principal
//
// PUT creates new principal
// POST can be used to change the principals metadata
func (a *AdminAPI) PrincipalRequest() http.Handler {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		log := a.log.New(log15.Ctx{"method": "PrincipalRequest"})

		switch r.Method {
		case "PUT":
			a.putNewPrincipal(w, r)
		case "GET":
			a.getAllPrincipals(w, r)
		default:
			ErrMethod.Write(w)
			log.Info("http method not supported", log15.Ctx{"requestMethod": r.Method})
		}
	})
	return a.ctx.RateLimitHandler(h)
}

func (a *AdminAPI) PrincipalNameRequest() http.Handler {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "POST":
			a.ctx.RateLimitHandler(http.HandlerFunc(a.postChangePrincipal)).ServeHTTP(w, r)
		case "GET":
			a.getPrincipal(w, r)
		default:
			ErrMethod.Write(w)
		}
	})
	return h
}

// handler to display a specific existing principal
func (a *AdminAPI) getPrincipal(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	log := a.log.New(log15.Ctx{"method": "getPrincipal"})

	// get principal by name
	vars := mux.Vars(r)
	principalName := vars["name"]
	log = log.New(log15.Ctx{"principalName": principalName})

	db := a.ctx.PrincipalDB(service.ReadOnly)
	pr, err := principal.PrincipalByNameDB(db, principalName)
	if err == principal.ErrPrincipalNotFound {
		ErrNotFound.Write(w)
		log.Info("principal not found")
		return
	}
	if err != nil {
		ErrDatabase.Write(w)
		log.Error("DB get by name failed", log15.Ctx{"err": err})
		return
	}

	md, err := metadata.MetadataByPrimaryDB(db, principal.MetadataModel, pr.ID)
	if err != nil {
		ErrDatabase.Write(w)
		log.Error("get metadata failed", log15.Ctx{"err": err})
		return
	}
	if len(md) > 0 {
		pr.Metadata = md.Values()
	}

	// create service response object
	resp := PrincipalAdminAPIResponse{}
	resp.Status = StatusSuccess
	resp.Info = "principal " + pr.Name + " found"
	resp.Response = pr
	err = resp.Write(w)
	if err != nil {
		log.Error("write error", log15.Ctx{"err": err})
		return
	}
}

func (a *AdminAPI) getAllPrincipals(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "application/json")
	log := a.log.New(log15.Ctx{"method": "getAllPrincipals"})

	params := r.URL.Query()
	metadataCtrlParam := params.Get("metadata") != ""

	db := a.ctx.PrincipalDB(service.ReadOnly)
	pl, err := principal.PrincipalAllDB(db)
	if err == principal.ErrPrincipalNotFound {
		ErrNotFound.Write(w)
		log.Info("no principals found/ present")
		return
	}
	if err != nil {
		log.Warn("DB get all failed", log15.Ctx{"err": err})
	}
	if metadataCtrlParam == true {

		for _, pr := range pl {

			md, err := metadata.MetadataByPrimaryDB(db, principal.MetadataModel, pr.ID)
			if err != nil {
				ErrDatabase.Write(w)
				log.Error("get metadata failed", log15.Ctx{"err": err})
				return
			}
			if len(md) > 0 {
				pr.Metadata = md.Values()
			}
		}
	}

	// create service response object
	resp := PrincipalAdminAPIResponse{}
	resp.Status = StatusSuccess
	resp.Info = "principals found"
	resp.Response = pl
	err = resp.Write(w)
	if err != nil {
		log.Error("write error", log15.Ctx{"err": err})
		return
	}
}

func (a *AdminAPI) putNewPrincipal(w http.ResponseWriter, r *http.Request) {
	log := a.log.New(log15.Ctx{"method": "putNewPrincipal"})

	// create new principal
	jd := json.NewDecoder(r.Body)
	pr := principal.Principal{}
	err := jd.Decode(&pr)
	r.Body.Close()
	if err != nil {
		ErrReadJson.Write(w)
		log.Error("json decode failed", log15.Ctx{"err": err})
		return
	}
	if err := pr.ValidStatus(); err != nil {
		ErrInval.Write(w)
		return
	}

	log = log.New(log15.Ctx{"principalName": pr.Name})

	auth, err := getAuthContainer(r)
	if err != nil {
		log.Crit("context auth error", log15.Ctx{"err": err})
		ErrSystem.Write(w)
		return
	}
	pr.CreatedBy = auth[AuthUserIDKey].(string)
	// set created time
	pr.Created = time.Now().UTC().Round(time.Second)

	// insert pr if not exists
	// start dbtx to save metadata and pr together
	tx, err := a.ctx.PrincipalDB().Begin()
	if err != nil {
		log.Crit("Tx begin failed", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}

	// insert principal
	err = principal.InsertPrincipalTx(tx, &pr)
	if err != nil {
		tx.Rollback()
		ErrDatabase.Write(w)
		log.Error("TX insert failed.", log15.Ctx{"err": err})
		return
	}
	// insert principal status
	err = principal.InsertPrincipalStatusTx(tx, pr, pr.CreatedBy)
	if err != nil {
		tx.Rollback()
		ErrDatabase.Write(w)
		log.Error("error on insert principal status", log15.Ctx{"err": err})
		return
	}
	// insert Metadata
	err = insertPrincipalMetadata(tx, &pr)
	if err != nil {
		tx.Rollback()
		ErrDatabase.Write(w)
		log.Error("metadata insert failed.", log15.Ctx{"err": err})
		return
	}

	//commit tx
	err = tx.Commit()
	if err != nil {
		ErrDatabase.Write(w)
		log.Crit("TX commit failed.", log15.Ctx{"err": err})
		return
	}

	resp := PrincipalAdminAPIResponse{}
	resp.HttpStatus = http.StatusOK
	resp.Status = StatusSuccess
	resp.Response = pr
	resp.Info = "principal " + pr.Name + " created"
	err = resp.Write(w)
	if err != nil {
		log.Error("write error", log15.Ctx{"err": err})
		ErrSystem.Write(w)
		return
	}
}

// post method to add and change the metadata
func (a *AdminAPI) postChangePrincipal(w http.ResponseWriter, r *http.Request) {
	log := a.log.New(log15.Ctx{"method": "postChangePrincipal"})

	vars := mux.Vars(r)
	principalName := vars["name"]

	// get Metadata from post variables
	jd := json.NewDecoder(r.Body)
	pr := principal.Principal{}
	err := jd.Decode(&pr)
	r.Body.Close()
	if err != nil {
		log.Error("json decode failed", log15.Ctx{"err": err})
		ErrReadJson.Write(w)
		return
	}

	auth, err := getAuthContainer(r)
	if err != nil {
		log.Crit("error getting auth container", log15.Ctx{"err": err})
		ErrSystem.Write(w)
		return
	}
	pr.CreatedBy = auth[AuthUserIDKey].(string)
	pr.Created = time.Now().UTC().Round(time.Second)

	log = log.New(log15.Ctx{"principalID": pr.ID})

	// open transaction to add the posted metadata
	tx, err := a.ctx.PrincipalDB().Begin()
	if err != nil {
		log.Crit("Tx begin failed", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}

	// does principal exist
	prByName, err := principal.PrincipalByNameTx(tx, principalName)
	if err != nil {
		txErr := tx.Rollback()
		if txErr != nil {
			log.Crit("error on rollback", log15.Ctx{"err": err})
		}
		if err == principal.ErrPrincipalNotFound {
			log.Warn("principal not found")
			ErrNotFound.Write(w)
			return
		}
		log.Error("get principal from DB failed", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}
	pr.ID = prByName.ID

	// insert Metadata
	md := metadata.MetadataFromValues(pr.Metadata, pr.CreatedBy)
	err = insertPrincipalMetadata(tx, &pr)
	if err != nil {
		tx.Rollback()
		ErrDatabase.Write(w)
		log.Error("metadata insert failed", log15.Ctx{"err": err})
		return
	}

	// get stored and added metadata from db
	md, err = metadata.MetadataByPrimaryTx(tx, principal.MetadataModel, pr.ID)
	if err != nil {
		tx.Rollback()
		log.Error("get metadata failed", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}
	if len(md) > 0 {
		pr.Metadata = md.Values()
	}
	err = tx.Commit()
	if err != nil {
		log.Crit("error on commit", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}

	// create response
	resp := PrincipalAdminAPIResponse{}
	resp.Info = "principal " + pr.Name + " changed"
	resp.Status = StatusSuccess
	resp.Response = pr
	err = resp.Write(w)
	if err != nil {
		ErrSystem.Write(w)
		log.Error("write error", log15.Ctx{"err": err})
		return
	}
}

// adds metadata into the database
func insertPrincipalMetadata(tx *sql.Tx, pr *principal.Principal) error {

	// check if principal exists
	_, err := principal.PrincipalByNameTx(tx, pr.Name)
	if err != nil && err != principal.ErrPrincipalNotFound {
		return err
	}

	// insert metadata
	md := metadata.MetadataFromValues(pr.Metadata, pr.CreatedBy)
	err = metadata.InsertMetadataTx(tx, principal.MetadataModel, pr.ID, md)

	return err
}
<commit_msg>minor change in metadata param<commit_after>package v1

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"time"

	"github.com/fritzpay/paymentd/pkg/metadata"
	"github.com/fritzpay/paymentd/pkg/paymentd/principal"
	"github.com/fritzpay/paymentd/pkg/service"
	"github.com/gorilla/mux"
	"gopkg.in/inconshreveable/log15.v2"
)

type PrincipalAdminAPIResponse struct {
	AdminAPIResponse
}

// handler to create or change a principal
//
// PUT creates new principal
// POST can be used to change the principals metadata
func (a *AdminAPI) PrincipalRequest() http.Handler {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		log := a.log.New(log15.Ctx{"method": "PrincipalRequest"})

		switch r.Method {
		case "PUT":
			a.putNewPrincipal(w, r)
		case "GET":
			a.getAllPrincipals(w, r)
		default:
			ErrMethod.Write(w)
			log.Info("http method not supported", log15.Ctx{"requestMethod": r.Method})
		}
	})
	return a.ctx.RateLimitHandler(h)
}

func (a *AdminAPI) PrincipalNameRequest() http.Handler {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "POST":
			a.ctx.RateLimitHandler(http.HandlerFunc(a.postChangePrincipal)).ServeHTTP(w, r)
		case "GET":
			a.getPrincipal(w, r)
		default:
			ErrMethod.Write(w)
		}
	})
	return h
}

// handler to display a specific existing principal
func (a *AdminAPI) getPrincipal(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	log := a.log.New(log15.Ctx{"method": "getPrincipal"})

	// get principal by name
	vars := mux.Vars(r)
	principalName := vars["name"]
	log = log.New(log15.Ctx{"principalName": principalName})

	db := a.ctx.PrincipalDB(service.ReadOnly)
	pr, err := principal.PrincipalByNameDB(db, principalName)
	if err == principal.ErrPrincipalNotFound {
		ErrNotFound.Write(w)
		log.Info("principal not found")
		return
	}
	if err != nil {
		ErrDatabase.Write(w)
		log.Error("DB get by name failed", log15.Ctx{"err": err})
		return
	}

	md, err := metadata.MetadataByPrimaryDB(db, principal.MetadataModel, pr.ID)
	if err != nil {
		ErrDatabase.Write(w)
		log.Error("get metadata failed", log15.Ctx{"err": err})
		return
	}
	if len(md) > 0 {
		pr.Metadata = md.Values()
	}

	// create service response object
	resp := PrincipalAdminAPIResponse{}
	resp.Status = StatusSuccess
	resp.Info = "principal " + pr.Name + " found"
	resp.Response = pr
	err = resp.Write(w)
	if err != nil {
		log.Error("write error", log15.Ctx{"err": err})
		return
	}
}

func (a *AdminAPI) getAllPrincipals(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "application/json")
	log := a.log.New(log15.Ctx{"method": "getAllPrincipals"})

	params := r.URL.Query()
	metadataRequested := params.Get("metadata") != ""

	db := a.ctx.PrincipalDB(service.ReadOnly)
	pl, err := principal.PrincipalAllDB(db)
	if err == principal.ErrPrincipalNotFound {
		ErrNotFound.Write(w)
		log.Info("no principals found/ present")
		return
	}
	if err != nil {
		log.Warn("DB get all failed", log15.Ctx{"err": err})
	}

	if metadataRequested {
		for _, pr := range pl {
			md, err := metadata.MetadataByPrimaryDB(db, principal.MetadataModel, pr.ID)
			if err != nil {
				ErrDatabase.Write(w)
				log.Error("get metadata failed", log15.Ctx{"err": err})
				return
			}
			if len(md) > 0 {
				pr.Metadata = md.Values()
			}
		}
	}

	// create service response object
	resp := PrincipalAdminAPIResponse{}
	resp.Status = StatusSuccess
	resp.Info = "principals found"
	resp.Response = pl
	err = resp.Write(w)
	if err != nil {
		log.Error("write error", log15.Ctx{"err": err})
		return
	}
}

func (a *AdminAPI) putNewPrincipal(w http.ResponseWriter, r *http.Request) {
	log := a.log.New(log15.Ctx{"method": "putNewPrincipal"})

	// create new principal
	jd := json.NewDecoder(r.Body)
	pr := principal.Principal{}
	err := jd.Decode(&pr)
	r.Body.Close()
	if err != nil {
		ErrReadJson.Write(w)
		log.Error("json decode failed", log15.Ctx{"err": err})
		return
	}
	if err := pr.ValidStatus(); err != nil {
		ErrInval.Write(w)
		return
	}

	log = log.New(log15.Ctx{"principalName": pr.Name})

	auth, err := getAuthContainer(r)
	if err != nil {
		log.Crit("context auth error", log15.Ctx{"err": err})
		ErrSystem.Write(w)
		return
	}
	pr.CreatedBy = auth[AuthUserIDKey].(string)
	// set created time
	pr.Created = time.Now().UTC().Round(time.Second)

	// insert pr if not exists
	// start dbtx to save metadata and pr together
	tx, err := a.ctx.PrincipalDB().Begin()
	if err != nil {
		log.Crit("Tx begin failed", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}

	// insert principal
	err = principal.InsertPrincipalTx(tx, &pr)
	if err != nil {
		tx.Rollback()
		ErrDatabase.Write(w)
		log.Error("TX insert failed.", log15.Ctx{"err": err})
		return
	}
	// insert principal status
	err = principal.InsertPrincipalStatusTx(tx, pr, pr.CreatedBy)
	if err != nil {
		tx.Rollback()
		ErrDatabase.Write(w)
		log.Error("error on insert principal status", log15.Ctx{"err": err})
		return
	}
	// insert Metadata
	err = insertPrincipalMetadata(tx, &pr)
	if err != nil {
		tx.Rollback()
		ErrDatabase.Write(w)
		log.Error("metadata insert failed.", log15.Ctx{"err": err})
		return
	}

	//commit tx
	err = tx.Commit()
	if err != nil {
		ErrDatabase.Write(w)
		log.Crit("TX commit failed.", log15.Ctx{"err": err})
		return
	}

	resp := PrincipalAdminAPIResponse{}
	resp.HttpStatus = http.StatusOK
	resp.Status = StatusSuccess
	resp.Response = pr
	resp.Info = "principal " + pr.Name + " created"
	err = resp.Write(w)
	if err != nil {
		log.Error("write error", log15.Ctx{"err": err})
		ErrSystem.Write(w)
		return
	}
}

// post method to add and change the metadata
func (a *AdminAPI) postChangePrincipal(w http.ResponseWriter, r *http.Request) {
	log := a.log.New(log15.Ctx{"method": "postChangePrincipal"})

	vars := mux.Vars(r)
	principalName := vars["name"]

	// get Metadata from post variables
	jd := json.NewDecoder(r.Body)
	pr := principal.Principal{}
	err := jd.Decode(&pr)
	r.Body.Close()
	if err != nil {
		log.Error("json decode failed", log15.Ctx{"err": err})
		ErrReadJson.Write(w)
		return
	}

	auth, err := getAuthContainer(r)
	if err != nil {
		log.Crit("error getting auth container", log15.Ctx{"err": err})
		ErrSystem.Write(w)
		return
	}
	pr.CreatedBy = auth[AuthUserIDKey].(string)
	pr.Created = time.Now().UTC().Round(time.Second)

	log = log.New(log15.Ctx{"principalID": pr.ID})

	// open transaction to add the posted metadata
	tx, err := a.ctx.PrincipalDB().Begin()
	if err != nil {
		log.Crit("Tx begin failed", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}

	// does principal exist
	prByName, err := principal.PrincipalByNameTx(tx, principalName)
	if err != nil {
		txErr := tx.Rollback()
		if txErr != nil {
			log.Crit("error on rollback", log15.Ctx{"err": err})
		}
		if err == principal.ErrPrincipalNotFound {
			log.Warn("principal not found")
			ErrNotFound.Write(w)
			return
		}
		log.Error("get principal from DB failed", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}
	pr.ID = prByName.ID

	// insert Metadata
	md := metadata.MetadataFromValues(pr.Metadata, pr.CreatedBy)
	err = insertPrincipalMetadata(tx, &pr)
	if err != nil {
		tx.Rollback()
		ErrDatabase.Write(w)
		log.Error("metadata insert failed", log15.Ctx{"err": err})
		return
	}

	// get stored and added metadata from db
	md, err = metadata.MetadataByPrimaryTx(tx, principal.MetadataModel, pr.ID)
	if err != nil {
		tx.Rollback()
		log.Error("get metadata failed", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}
	if len(md) > 0 {
		pr.Metadata = md.Values()
	}
	err = tx.Commit()
	if err != nil {
		log.Crit("error on commit", log15.Ctx{"err": err})
		ErrDatabase.Write(w)
		return
	}

	// create response
	resp := PrincipalAdminAPIResponse{}
	resp.Info = "principal " + pr.Name + " changed"
	resp.Status = StatusSuccess
	resp.Response = pr
	err = resp.Write(w)
	if err != nil {
		ErrSystem.Write(w)
		log.Error("write error", log15.Ctx{"err": err})
		return
	}
}

// adds metadata into the database
func insertPrincipalMetadata(tx *sql.Tx, pr *principal.Principal) error {

	// check if principal exists
	_, err := principal.PrincipalByNameTx(tx, pr.Name)
	if err != nil && err != principal.ErrPrincipalNotFound {
		return err
	}

	// insert metadata
	md := metadata.MetadataFromValues(pr.Metadata, pr.CreatedBy)
	err = metadata.InsertMetadataTx(tx, principal.MetadataModel, pr.ID, md)

	return err
}
<|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 azure_dd

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/Azure/azure-sdk-for-go/arm/compute"
	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/kubernetes/pkg/cloudprovider"
	"k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
	"k8s.io/kubernetes/pkg/util/keymutex"
	"k8s.io/kubernetes/pkg/util/mount"
	"k8s.io/kubernetes/pkg/volume"
	volumeutil "k8s.io/kubernetes/pkg/volume/util"
	"k8s.io/kubernetes/pkg/volume/util/volumehelper"
)

type azureDiskDetacher struct {
	plugin *azureDataDiskPlugin
	cloud  *azure.Cloud
}

type azureDiskAttacher struct {
	plugin *azureDataDiskPlugin
	cloud  *azure.Cloud
}

var _ volume.Attacher = &azureDiskAttacher{}
var _ volume.Detacher = &azureDiskDetacher{}

// acquire lock to get an lun number
var getLunMutex = keymutex.NewKeyMutex()

// Attach attaches a volume.Spec to an Azure VM referenced by NodeName, returning the disk's LUN
func (a *azureDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
	volumeSource, err := getVolumeSource(spec)
	if err != nil {
		glog.Warningf("failed to get azure disk spec")
		return "", err
	}

	instanceid, err := a.cloud.InstanceID(nodeName)
	if err != nil {
		glog.Warningf("failed to get azure instance id")
		return "", fmt.Errorf("failed to get azure instance id for node %q", nodeName)
	}
	if ind := strings.LastIndex(instanceid, "/"); ind >= 0 {
		instanceid = instanceid[(ind + 1):]
	}

	diskController, err := getDiskController(a.plugin.host)
	if err != nil {
		return "", err
	}

	lun, err := diskController.GetDiskLun(volumeSource.DiskName, volumeSource.DataDiskURI, nodeName)
	if err == cloudprovider.InstanceNotFound {
		// Log error and continue with attach
		glog.Warningf(
			"Error checking if volume is already attached to current node (%q). Will continue and try attach anyway. err=%v",
			instanceid, err)
	}

	if err == nil {
		// Volume is already attached to node.
		glog.V(4).Infof("Attach operation is successful. volume %q is already attached to node %q at lun %d.", volumeSource.DiskName, instanceid, lun)
	} else {
		glog.V(4).Infof("GetDiskLun returned: %v. Initiating attaching volume %q to node %q.", err, volumeSource.DataDiskURI, nodeName)
		getLunMutex.LockKey(instanceid)
		defer getLunMutex.UnlockKey(instanceid)

		lun, err = diskController.GetNextDiskLun(nodeName)
		if err != nil {
			glog.Warningf("no LUN available for instance %q", nodeName)
			return "", fmt.Errorf("all LUNs are used, cannot attach volume %q to instance %q", volumeSource.DiskName, instanceid)
		}
		glog.V(4).Infof("Trying to attach volume %q lun %d to node %q.", volumeSource.DataDiskURI, lun, nodeName)
		isManagedDisk := (*volumeSource.Kind == v1.AzureManagedDisk)
		err = diskController.AttachDisk(isManagedDisk, volumeSource.DiskName, volumeSource.DataDiskURI, nodeName, lun, compute.CachingTypes(*volumeSource.CachingMode))
		if err == nil {
			glog.V(4).Infof("Attach operation successful: volume %q attached to node %q.", volumeSource.DataDiskURI, nodeName)
		} else {
			glog.V(2).Infof("Attach volume %q to instance %q failed with %v", volumeSource.DataDiskURI, instanceid, err)
			return "", fmt.Errorf("Attach volume %q to instance %q failed with %v", volumeSource.DiskName, instanceid, err)
		}
	}

	return strconv.Itoa(int(lun)), err
}

func (a *azureDiskAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
	volumesAttachedCheck := make(map[*volume.Spec]bool)
	volumeSpecMap := make(map[string]*volume.Spec)
	volumeIDList := []string{}
	for _, spec := range specs {
		volumeSource, err := getVolumeSource(spec)
		if err != nil {
			glog.Errorf("azureDisk - Error getting volume (%q) source : %v", spec.Name(), err)
			continue
		}

		volumeIDList = append(volumeIDList, volumeSource.DiskName)
		volumesAttachedCheck[spec] = true
		volumeSpecMap[volumeSource.DiskName] = spec
	}

	diskController, err := getDiskController(a.plugin.host)
	if err != nil {
		return nil, err
	}
	attachedResult, err := diskController.DisksAreAttached(volumeIDList, nodeName)
	if err != nil {
		// Log error and continue with attach
		glog.Errorf(
			"azureDisk - Error checking if volumes (%v) are attached to current node (%q). err=%v",
			volumeIDList, nodeName, err)
		return volumesAttachedCheck, err
	}

	for volumeID, attached := range attachedResult {
		if !attached {
			spec := volumeSpecMap[volumeID]
			volumesAttachedCheck[spec] = false
			glog.V(2).Infof("azureDisk - VolumesAreAttached: check volume %q (specName: %q) is no longer attached", volumeID, spec.Name())
		}
	}
	return volumesAttachedCheck, nil
}

func (a *azureDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) {
	var err error
	lun, err := strconv.Atoi(devicePath)
	if err != nil {
		return "", fmt.Errorf("azureDisk - Wait for attach expect device path as a lun number, instead got: %s", devicePath)
	}

	volumeSource, err := getVolumeSource(spec)
	if err != nil {
		return "", err
	}

	exec := a.plugin.host.GetExec(a.plugin.GetPluginName())

	io := &osIOHandler{}
	scsiHostRescan(io, exec)

	diskName := volumeSource.DiskName
	nodeName := a.plugin.host.GetHostName()
	newDevicePath := ""

	err = wait.Poll(1*time.Second, timeout, func() (bool, error) {
		if newDevicePath, err = findDiskByLun(lun, io, exec); err != nil {
			return false, fmt.Errorf("azureDisk - WaitForAttach ticker failed node (%s) disk (%s) lun(%v) err(%s)", nodeName, diskName, lun, err)
		}

		// did we find it?
		if newDevicePath != "" {
			// the curent sequence k8s uses for unformated disk (check-disk, mount, fail, mkfs.extX) hangs on
			// Azure Managed disk scsi interface. this is a hack and will be replaced once we identify and solve
			// the root case on Azure.
			formatIfNotFormatted(newDevicePath, *volumeSource.FSType, exec)
			return true, nil
		}

		return false, fmt.Errorf("azureDisk - WaitForAttach failed within timeout node (%s) diskId:(%s) lun:(%v)", nodeName, diskName, lun)
	})

	return newDevicePath, err
}

// to avoid name conflicts (similar *.vhd name)
// we use hash diskUri and we use it as device mount target.
// this is generalized for both managed and blob disks
// we also prefix the hash with m/b based on disk kind
func (a *azureDiskAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) {
	volumeSource, err := getVolumeSource(spec)
	if err != nil {
		return "", err
	}

	if volumeSource.Kind == nil { // this spec was constructed from info on the node
		pdPath := path.Join(a.plugin.host.GetPluginDir(azureDataDiskPluginName), mount.MountsInGlobalPDPath, volumeSource.DataDiskURI)
		return pdPath, nil
	}

	isManagedDisk := (*volumeSource.Kind == v1.AzureManagedDisk)
	return makeGlobalPDPath(a.plugin.host, volumeSource.DataDiskURI, isManagedDisk)
}

func (attacher *azureDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
	mounter := attacher.plugin.host.GetMounter(azureDataDiskPluginName)
	notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)

	if err != nil {
		if os.IsNotExist(err) {
			dir := deviceMountPath
			if runtime.GOOS == "windows" {
				// in windows, as we use mklink, only need to MkdirAll for parent directory
				dir = filepath.Dir(deviceMountPath)
			}
			if err := os.MkdirAll(dir, 0750); err != nil {
				return fmt.Errorf("azureDisk - mountDevice:CreateDirectory failed with %s", err)
			}
			notMnt = true
		} else {
			return fmt.Errorf("azureDisk - mountDevice:IsLikelyNotMountPoint failed with %s", err)
		}
	}

	volumeSource, err := getVolumeSource(spec)
	if err != nil {
		return err
	}

	options := []string{}
	if notMnt {
		diskMounter := volumehelper.NewSafeFormatAndMountFromHost(azureDataDiskPluginName, attacher.plugin.host)
		mountOptions := volume.MountOptionFromSpec(spec, options...)
		err = diskMounter.FormatAndMount(devicePath, deviceMountPath, *volumeSource.FSType, mountOptions)
		if err != nil {
			if cleanErr := os.Remove(deviceMountPath); cleanErr != nil {
				return fmt.Errorf("azureDisk - mountDevice:FormatAndMount failed with %s and clean up failed with :%v", err, cleanErr)
			}
			return fmt.Errorf("azureDisk - mountDevice:FormatAndMount failed with %s", err)
		}
	}
	return nil
}

// Detach detaches disk from Azure VM.
func (d *azureDiskDetacher) Detach(diskURI string, nodeName types.NodeName) error {
	if diskURI == "" {
		return fmt.Errorf("invalid disk to detach: %q", diskURI)
	}

	instanceid, err := d.cloud.InstanceID(nodeName)
	if err != nil {
		glog.Warningf("no instance id for node %q, skip detaching", nodeName)
		return nil
	}
	if ind := strings.LastIndex(instanceid, "/"); ind >= 0 {
		instanceid = instanceid[(ind + 1):]
	}

	glog.V(4).Infof("detach %v from node %q", diskURI, nodeName)

	diskController, err := getDiskController(d.plugin.host)
	if err != nil {
		return err
	}
	err = diskController.DetachDiskByName("", diskURI, nodeName)
	if err != nil {
		glog.Errorf("failed to detach azure disk %q, err %v", diskURI, err)
	}

	glog.V(2).Infof("azureDisk - disk:%s was detached from node:%v", diskURI, nodeName)
	return err
}

// UnmountDevice unmounts the volume on the node
func (detacher *azureDiskDetacher) UnmountDevice(deviceMountPath string) error {
	err := volumeutil.UnmountPath(deviceMountPath, detacher.plugin.host.GetMounter(detacher.plugin.GetPluginName()))
	if err == nil {
		glog.V(4).Infof("azureDisk - Device %s was unmounted", deviceMountPath)
	} else {
		glog.Infof("azureDisk - Device %s failed to unmount with error: %s", deviceMountPath, err.Error())
	}
	return err
}
<commit_msg>Print/return the text from a number of errors that were silent before.<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 azure_dd

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/Azure/azure-sdk-for-go/arm/compute"
	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/kubernetes/pkg/cloudprovider"
	"k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
	"k8s.io/kubernetes/pkg/util/keymutex"
	"k8s.io/kubernetes/pkg/util/mount"
	"k8s.io/kubernetes/pkg/volume"
	volumeutil "k8s.io/kubernetes/pkg/volume/util"
	"k8s.io/kubernetes/pkg/volume/util/volumehelper"
)

type azureDiskDetacher struct {
	plugin *azureDataDiskPlugin
	cloud  *azure.Cloud
}

type azureDiskAttacher struct {
	plugin *azureDataDiskPlugin
	cloud  *azure.Cloud
}

var _ volume.Attacher = &azureDiskAttacher{}
var _ volume.Detacher = &azureDiskDetacher{}

// acquire lock to get an lun number
var getLunMutex = keymutex.NewKeyMutex()

// Attach attaches a volume.Spec to an Azure VM referenced by NodeName, returning the disk's LUN
func (a *azureDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
	volumeSource, err := getVolumeSource(spec)
	if err != nil {
		glog.Warningf("failed to get azure disk spec (%v)", err)
		return "", err
	}

	instanceid, err := a.cloud.InstanceID(nodeName)
	if err != nil {
		glog.Warningf("failed to get azure instance id (%v)", err)
		return "", fmt.Errorf("failed to get azure instance id for node %q (%v)", nodeName, err)
	}
	if ind := strings.LastIndex(instanceid, "/"); ind >= 0 {
		instanceid = instanceid[(ind + 1):]
	}

	diskController, err := getDiskController(a.plugin.host)
	if err != nil {
		return "", err
	}

	lun, err := diskController.GetDiskLun(volumeSource.DiskName, volumeSource.DataDiskURI, nodeName)
	if err == cloudprovider.InstanceNotFound {
		// Log error and continue with attach
		glog.Warningf(
			"Error checking if volume is already attached to current node (%q). Will continue and try attach anyway. err=%v",
			instanceid, err)
	}

	if err == nil {
		// Volume is already attached to node.
		glog.V(4).Infof("Attach operation is successful. volume %q is already attached to node %q at lun %d.", volumeSource.DiskName, instanceid, lun)
	} else {
		glog.V(4).Infof("GetDiskLun returned: %v. Initiating attaching volume %q to node %q.", err, volumeSource.DataDiskURI, nodeName)
		getLunMutex.LockKey(instanceid)
		defer getLunMutex.UnlockKey(instanceid)

		lun, err = diskController.GetNextDiskLun(nodeName)
		if err != nil {
			glog.Warningf("no LUN available for instance %q (%v)", nodeName, err)
			return "", fmt.Errorf("all LUNs are used, cannot attach volume %q to instance %q (%v)", volumeSource.DiskName, instanceid, err)
		}
		glog.V(4).Infof("Trying to attach volume %q lun %d to node %q.", volumeSource.DataDiskURI, lun, nodeName)
		isManagedDisk := (*volumeSource.Kind == v1.AzureManagedDisk)
		err = diskController.AttachDisk(isManagedDisk, volumeSource.DiskName, volumeSource.DataDiskURI, nodeName, lun, compute.CachingTypes(*volumeSource.CachingMode))
		if err == nil {
			glog.V(4).Infof("Attach operation successful: volume %q attached to node %q.", volumeSource.DataDiskURI, nodeName)
		} else {
			glog.V(2).Infof("Attach volume %q to instance %q failed with %v", volumeSource.DataDiskURI, instanceid, err)
			return "", fmt.Errorf("Attach volume %q to instance %q failed with %v", volumeSource.DiskName, instanceid, err)
		}
	}

	return strconv.Itoa(int(lun)), err
}

func (a *azureDiskAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
	volumesAttachedCheck := make(map[*volume.Spec]bool)
	volumeSpecMap := make(map[string]*volume.Spec)
	volumeIDList := []string{}
	for _, spec := range specs {
		volumeSource, err := getVolumeSource(spec)
		if err != nil {
			glog.Errorf("azureDisk - Error getting volume (%q) source : %v", spec.Name(), err)
			continue
		}

		volumeIDList = append(volumeIDList, volumeSource.DiskName)
		volumesAttachedCheck[spec] = true
		volumeSpecMap[volumeSource.DiskName] = spec
	}

	diskController, err := getDiskController(a.plugin.host)
	if err != nil {
		return nil, err
	}
	attachedResult, err := diskController.DisksAreAttached(volumeIDList, nodeName)
	if err != nil {
		// Log error and continue with attach
		glog.Errorf(
			"azureDisk - Error checking if volumes (%v) are attached to current node (%q). err=%v",
			volumeIDList, nodeName, err)
		return volumesAttachedCheck, err
	}

	for volumeID, attached := range attachedResult {
		if !attached {
			spec := volumeSpecMap[volumeID]
			volumesAttachedCheck[spec] = false
			glog.V(2).Infof("azureDisk - VolumesAreAttached: check volume %q (specName: %q) is no longer attached", volumeID, spec.Name())
		}
	}
	return volumesAttachedCheck, nil
}

func (a *azureDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) {
	var err error
	lun, err := strconv.Atoi(devicePath)
	if err != nil {
		return "", fmt.Errorf("azureDisk - Wait for attach expect device path as a lun number, instead got: %s (%v)", devicePath, err)
	}

	volumeSource, err := getVolumeSource(spec)
	if err != nil {
		return "", err
	}

	exec := a.plugin.host.GetExec(a.plugin.GetPluginName())

	io := &osIOHandler{}
	scsiHostRescan(io, exec)

	diskName := volumeSource.DiskName
	nodeName := a.plugin.host.GetHostName()
	newDevicePath := ""

	err = wait.Poll(1*time.Second, timeout, func() (bool, error) {
		if newDevicePath, err = findDiskByLun(lun, io, exec); err != nil {
			return false, fmt.Errorf("azureDisk - WaitForAttach ticker failed node (%s) disk (%s) lun(%v) err(%s)", nodeName, diskName, lun, err)
		}

		// did we find it?
		if newDevicePath != "" {
			// the curent sequence k8s uses for unformated disk (check-disk, mount, fail, mkfs.extX) hangs on
			// Azure Managed disk scsi interface. this is a hack and will be replaced once we identify and solve
			// the root case on Azure.
			formatIfNotFormatted(newDevicePath, *volumeSource.FSType, exec)
			return true, nil
		}

		return false, fmt.Errorf("azureDisk - WaitForAttach failed within timeout node (%s) diskId:(%s) lun:(%v)", nodeName, diskName, lun)
	})

	return newDevicePath, err
}

// to avoid name conflicts (similar *.vhd name)
// we use hash diskUri and we use it as device mount target.
// this is generalized for both managed and blob disks
// we also prefix the hash with m/b based on disk kind
func (a *azureDiskAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) {
	volumeSource, err := getVolumeSource(spec)
	if err != nil {
		return "", err
	}

	if volumeSource.Kind == nil { // this spec was constructed from info on the node
		pdPath := path.Join(a.plugin.host.GetPluginDir(azureDataDiskPluginName), mount.MountsInGlobalPDPath, volumeSource.DataDiskURI)
		return pdPath, nil
	}

	isManagedDisk := (*volumeSource.Kind == v1.AzureManagedDisk)
	return makeGlobalPDPath(a.plugin.host, volumeSource.DataDiskURI, isManagedDisk)
}

func (attacher *azureDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
	mounter := attacher.plugin.host.GetMounter(azureDataDiskPluginName)
	notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)

	if err != nil {
		if os.IsNotExist(err) {
			dir := deviceMountPath
			if runtime.GOOS == "windows" {
				// in windows, as we use mklink, only need to MkdirAll for parent directory
				dir = filepath.Dir(deviceMountPath)
			}
			if err := os.MkdirAll(dir, 0750); err != nil {
				return fmt.Errorf("azureDisk - mountDevice:CreateDirectory failed with %s", err)
			}
			notMnt = true
		} else {
			return fmt.Errorf("azureDisk - mountDevice:IsLikelyNotMountPoint failed with %s", err)
		}
	}

	volumeSource, err := getVolumeSource(spec)
	if err != nil {
		return err
	}

	options := []string{}
	if notMnt {
		diskMounter := volumehelper.NewSafeFormatAndMountFromHost(azureDataDiskPluginName, attacher.plugin.host)
		mountOptions := volume.MountOptionFromSpec(spec, options...)
		err = diskMounter.FormatAndMount(devicePath, deviceMountPath, *volumeSource.FSType, mountOptions)
		if err != nil {
			if cleanErr := os.Remove(deviceMountPath); cleanErr != nil {
				return fmt.Errorf("azureDisk - mountDevice:FormatAndMount failed with %s and clean up failed with :%v", err, cleanErr)
			}
			return fmt.Errorf("azureDisk - mountDevice:FormatAndMount failed with %s", err)
		}
	}
	return nil
}

// Detach detaches disk from Azure VM.
func (d *azureDiskDetacher) Detach(diskURI string, nodeName types.NodeName) error {
	if diskURI == "" {
		return fmt.Errorf("invalid disk to detach: %q", diskURI)
	}

	instanceid, err := d.cloud.InstanceID(nodeName)
	if err != nil {
		glog.Warningf("no instance id for node %q, skip detaching (%v)", nodeName, err)
		return nil
	}
	if ind := strings.LastIndex(instanceid, "/"); ind >= 0 {
		instanceid = instanceid[(ind + 1):]
	}

	glog.V(4).Infof("detach %v from node %q", diskURI, nodeName)

	diskController, err := getDiskController(d.plugin.host)
	if err != nil {
		return err
	}
	err = diskController.DetachDiskByName("", diskURI, nodeName)
	if err != nil {
		glog.Errorf("failed to detach azure disk %q, err %v", diskURI, err)
	}

	glog.V(2).Infof("azureDisk - disk:%s was detached from node:%v", diskURI, nodeName)
	return err
}

// UnmountDevice unmounts the volume on the node
func (detacher *azureDiskDetacher) UnmountDevice(deviceMountPath string) error {
	err := volumeutil.UnmountPath(deviceMountPath, detacher.plugin.host.GetMounter(detacher.plugin.GetPluginName()))
	if err == nil {
		glog.V(4).Infof("azureDisk - Device %s was unmounted", deviceMountPath)
	} else {
		glog.Infof("azureDisk - Device %s failed to unmount with error: %s", deviceMountPath, err.Error())
	}
	return err
}
<|endoftext|>


<commit_before>/*
 * Mini Object Storage, (C) 2014 Minio, Inc.
 *
 * 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 minioapi

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/mux"
	mstorage "github.com/minio-io/minio/pkg/storage"
)

const (
	xmlType = iota
	jsonType
)

const (
	dateFormat = "2006-01-02T15:04:05.000Z"
)

type minioApi struct {
	storage mstorage.Storage
}

// No encoder interface exists, so we create one.
type encoder interface {
	Encode(v interface{}) error
}

func HttpHandler(storage mstorage.Storage) http.Handler {
	mux := mux.NewRouter()
	var api = minioApi{}
	api.storage = storage

	mux.HandleFunc("/", api.listBucketsHandler).Methods("GET")
	mux.HandleFunc("/{bucket}/", api.listObjectsHandler).Methods("GET")
	mux.HandleFunc("/{bucket}/", api.putBucketHandler).Methods("PUT")
	mux.HandleFunc("/{bucket}/{object:.*}", api.getObjectHandler).Methods("GET")
	mux.HandleFunc("/{bucket}/{object:.*}", api.headObjectHandler).Methods("HEAD")
	mux.HandleFunc("/{bucket}/{object:.*}", api.putObjectHandler).Methods("PUT")

	return mux
}

func (server *minioApi) listBucketsHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedBucketResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	prefix, ok := vars["prefix"]
	if !ok {
		prefix = ""
	}

	contentType := xmlType
	if _, ok := req.Header["Accept"]; ok {
		if req.Header["Accept"][0] == "application/json" {
			contentType = jsonType
		}
	}
	buckets, err := server.storage.ListBuckets(prefix)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	response := generateBucketsListResult(buckets)
	w.Write(writeObjectHeadersAndResponse(w, response, contentType))
}

func (server *minioApi) listObjectsHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedBucketResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	bucket := vars["bucket"]
	prefix, ok := vars["prefix"]
	if !ok {
		prefix = ""
	}

	contentType := xmlType
	if _, ok := req.Header["Accept"]; ok {
		if req.Header["Accept"][0] == "application/json" {
			contentType = jsonType
		}
	}

	objects, isTruncated, err := server.storage.ListObjects(bucket, prefix, 1000)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	response := generateObjectsListResult(bucket, objects, isTruncated)
	w.Write(writeObjectHeadersAndResponse(w, response, contentType))
}

func (server *minioApi) putBucketHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedBucketResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	bucket := vars["bucket"]
	err := server.storage.StoreBucket(bucket)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(err.Error()))
		return
	}
	w.Header().Set("Server", "Minio")
	w.Header().Set("Connection", "close")
}

func (server *minioApi) getObjectHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedObjectResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	bucket := vars["bucket"]
	object := vars["object"]

	metadata, err := server.storage.GetObjectMetadata(bucket, object)
	switch err := err.(type) {
	case nil: // success
		{
			log.Println("Found: " + bucket + "#" + object)
			writeObjectHeaders(w, metadata)
			if _, err := server.storage.CopyObjectToWriter(w, bucket, object); err != nil {
				log.Println(err)
			}
		}
	case mstorage.ObjectNotFound:
		{
			log.Println(err)
			w.WriteHeader(http.StatusNotFound)
		}
	default:
		{
			log.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
		}
	}
}

func (server *minioApi) headObjectHandler(w http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	bucket := vars["bucket"]
	object := vars["object"]

	metadata, err := server.storage.GetObjectMetadata(bucket, object)
	switch err := err.(type) {
	case nil:
		writeObjectHeaders(w, metadata)
	case mstorage.ObjectNotFound:
		log.Println(err)
		w.WriteHeader(http.StatusNotFound)
	default:
		log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
	}
}

func (server *minioApi) putObjectHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedBucketResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	bucket := vars["bucket"]
	object := vars["object"]
	err := server.storage.StoreObject(bucket, object, req.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(err.Error()))
		return
	}
	w.Header().Set("Server", "Minio")
	w.Header().Set("Connection", "close")
}

func writeObjectHeadersAndResponse(w http.ResponseWriter, response interface{}, contentType int) []byte {
	var bytesBuffer bytes.Buffer
	var encoder encoder
	if contentType == xmlType {
		w.Header().Set("Content-Type", "application/xml")
		encoder = xml.NewEncoder(&bytesBuffer)
	} else if contentType == jsonType {
		w.Header().Set("Content-Type", "application/json")
		encoder = json.NewEncoder(&bytesBuffer)
	}
	w.Header().Set("Server", "Minio")
	w.Header().Set("Connection", "close")
	encoder.Encode(response)
	return bytesBuffer.Bytes()
}

// Write Object Header helper
func writeObjectHeaders(w http.ResponseWriter, metadata mstorage.ObjectMetadata) {
	lastModified := metadata.Created.Format(time.RFC1123)
	w.Header().Set("ETag", metadata.ETag)
	w.Header().Set("Server", "Minio")
	w.Header().Set("Last-Modified", lastModified)
	w.Header().Set("Content-Length", strconv.FormatInt(metadata.Size, 10))
	w.Header().Set("Content-Type", "text/plain")
	w.Header().Set("Connection", "close")
}

func generateBucketsListResult(buckets []mstorage.BucketMetadata) BucketListResponse {
	var listbuckets []*Bucket
	var data = BucketListResponse{}
	var owner = Owner{}

	owner.ID = "minio"
	owner.DisplayName = "minio"

	for _, bucket := range buckets {
		var listbucket = &Bucket{}
		listbucket.Name = bucket.Name
		listbucket.CreationDate = bucket.Created.Format(dateFormat)
		listbuckets = append(listbuckets, listbucket)
	}

	data.Owner = owner
	data.Buckets.Bucket = listbuckets

	return data
}

//// Unimplemented Resources
func (server *minioApi) ignoreUnImplementedBucketResources(req *http.Request) bool {
	q := req.URL.Query()
	for name := range q {
		if unimplementedBucketResourceNames[name] {
			return true
		}
	}
	return false
}

func (server *minioApi) ignoreUnImplementedObjectResources(req *http.Request) bool {
	q := req.URL.Query()
	for name := range q {
		if unimplementedObjectResourceNames[name] {
			return true
		}
	}
	return false
}

//// helpers

// takes a set of objects and prepares the objects for serialization
// input:
// bucket name
// array of object metadata
// results truncated flag
//
// output:
// populated struct that can be serialized to match xml and json api spec output
func generateObjectsListResult(bucket string, objects []mstorage.ObjectMetadata, isTruncated bool) ObjectListResponse {
	var contents []*Item
	var owner = Owner{}
	var data = ObjectListResponse{}

	owner.ID = "minio"
	owner.DisplayName = "minio"

	for _, object := range objects {
		var content = &Item{}
		content.Key = object.Key
		content.LastModified = object.Created.Format(dateFormat)
		content.ETag = object.ETag
		content.Size = object.Size
		content.StorageClass = "STANDARD"
		content.Owner = owner
		contents = append(contents, content)
	}
	data.Name = bucket
	data.Contents = contents
	data.MaxKeys = MAX_OBJECT_LIST
	data.IsTruncated = isTruncated
	return data
}
<commit_msg>Reducing code duplication relating to content type<commit_after>/*
 * Mini Object Storage, (C) 2014 Minio, Inc.
 *
 * 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 minioapi

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/mux"
	mstorage "github.com/minio-io/minio/pkg/storage"
)

type contentType int

const (
	xmlType contentType = iota
	jsonType
)

const (
	dateFormat = "2006-01-02T15:04:05.000Z"
)

type minioApi struct {
	storage mstorage.Storage
}

// No encoder interface exists, so we create one.
type encoder interface {
	Encode(v interface{}) error
}

func HttpHandler(storage mstorage.Storage) http.Handler {
	mux := mux.NewRouter()
	var api = minioApi{}
	api.storage = storage

	mux.HandleFunc("/", api.listBucketsHandler).Methods("GET")
	mux.HandleFunc("/{bucket}/", api.listObjectsHandler).Methods("GET")
	mux.HandleFunc("/{bucket}/", api.putBucketHandler).Methods("PUT")
	mux.HandleFunc("/{bucket}/{object:.*}", api.getObjectHandler).Methods("GET")
	mux.HandleFunc("/{bucket}/{object:.*}", api.headObjectHandler).Methods("HEAD")
	mux.HandleFunc("/{bucket}/{object:.*}", api.putObjectHandler).Methods("PUT")

	return mux
}

func (server *minioApi) listBucketsHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedBucketResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	prefix, ok := vars["prefix"]
	if !ok {
		prefix = ""
	}

	acceptsContentType := getContentType(req)
	buckets, err := server.storage.ListBuckets(prefix)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	response := generateBucketsListResult(buckets)
	w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
}

func (server *minioApi) listObjectsHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedBucketResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	bucket := vars["bucket"]
	prefix, ok := vars["prefix"]
	if !ok {
		prefix = ""
	}

	acceptsContentType := getContentType(req)

	objects, isTruncated, err := server.storage.ListObjects(bucket, prefix, 1000)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	response := generateObjectsListResult(bucket, objects, isTruncated)
	w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
}

func (server *minioApi) putBucketHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedBucketResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	bucket := vars["bucket"]
	err := server.storage.StoreBucket(bucket)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(err.Error()))
		return
	}
	w.Header().Set("Server", "Minio")
	w.Header().Set("Connection", "close")
}

func (server *minioApi) getObjectHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedObjectResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	bucket := vars["bucket"]
	object := vars["object"]

	metadata, err := server.storage.GetObjectMetadata(bucket, object)
	switch err := err.(type) {
	case nil: // success
		{
			log.Println("Found: " + bucket + "#" + object)
			writeObjectHeaders(w, metadata)
			if _, err := server.storage.CopyObjectToWriter(w, bucket, object); err != nil {
				log.Println(err)
			}
		}
	case mstorage.ObjectNotFound:
		{
			log.Println(err)
			w.WriteHeader(http.StatusNotFound)
		}
	default:
		{
			log.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
		}
	}
}

func (server *minioApi) headObjectHandler(w http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	bucket := vars["bucket"]
	object := vars["object"]

	metadata, err := server.storage.GetObjectMetadata(bucket, object)
	switch err := err.(type) {
	case nil:
		writeObjectHeaders(w, metadata)
	case mstorage.ObjectNotFound:
		log.Println(err)
		w.WriteHeader(http.StatusNotFound)
	default:
		log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
	}
}

func (server *minioApi) putObjectHandler(w http.ResponseWriter, req *http.Request) {
	if server.ignoreUnImplementedBucketResources(req) {
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	vars := mux.Vars(req)
	bucket := vars["bucket"]
	object := vars["object"]
	err := server.storage.StoreObject(bucket, object, req.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(err.Error()))
		return
	}
	w.Header().Set("Server", "Minio")
	w.Header().Set("Connection", "close")
}

func writeObjectHeadersAndResponse(w http.ResponseWriter, response interface{}, acceptsType contentType) []byte {
	var bytesBuffer bytes.Buffer
	var encoder encoder
	if acceptsType == xmlType {
		w.Header().Set("Content-Type", "application/xml")
		encoder = xml.NewEncoder(&bytesBuffer)
	} else if acceptsType == jsonType {
		w.Header().Set("Content-Type", "application/json")
		encoder = json.NewEncoder(&bytesBuffer)
	}
	w.Header().Set("Server", "Minio")
	w.Header().Set("Connection", "close")
	encoder.Encode(response)
	return bytesBuffer.Bytes()
}

// Write Object Header helper
func writeObjectHeaders(w http.ResponseWriter, metadata mstorage.ObjectMetadata) {
	lastModified := metadata.Created.Format(time.RFC1123)
	w.Header().Set("ETag", metadata.ETag)
	w.Header().Set("Server", "Minio")
	w.Header().Set("Last-Modified", lastModified)
	w.Header().Set("Content-Length", strconv.FormatInt(metadata.Size, 10))
	w.Header().Set("Content-Type", "text/plain")
	w.Header().Set("Connection", "close")
}

func generateBucketsListResult(buckets []mstorage.BucketMetadata) BucketListResponse {
	var listbuckets []*Bucket
	var data = BucketListResponse{}
	var owner = Owner{}

	owner.ID = "minio"
	owner.DisplayName = "minio"

	for _, bucket := range buckets {
		var listbucket = &Bucket{}
		listbucket.Name = bucket.Name
		listbucket.CreationDate = bucket.Created.Format(dateFormat)
		listbuckets = append(listbuckets, listbucket)
	}

	data.Owner = owner
	data.Buckets.Bucket = listbuckets

	return data
}

//// helpers

// Checks requests for unimplemented resources
func (server *minioApi) ignoreUnImplementedBucketResources(req *http.Request) bool {
	q := req.URL.Query()
	for name := range q {
		if unimplementedBucketResourceNames[name] {
			return true
		}
	}
	return false
}

func (server *minioApi) ignoreUnImplementedObjectResources(req *http.Request) bool {
	q := req.URL.Query()
	for name := range q {
		if unimplementedObjectResourceNames[name] {
			return true
		}
	}
	return false
}

func getContentType(req *http.Request) contentType {
	if _, ok := req.Header["Accept"]; ok {
		if req.Header["Accept"][0] == "application/json" {
			return jsonType
		}
	}
	return xmlType
}

// takes a set of objects and prepares the objects for serialization
// input:
// bucket name
// array of object metadata
// results truncated flag
//
// output:
// populated struct that can be serialized to match xml and json api spec output
func generateObjectsListResult(bucket string, objects []mstorage.ObjectMetadata, isTruncated bool) ObjectListResponse {
	var contents []*Item
	var owner = Owner{}
	var data = ObjectListResponse{}

	owner.ID = "minio"
	owner.DisplayName = "minio"

	for _, object := range objects {
		var content = &Item{}
		content.Key = object.Key
		content.LastModified = object.Created.Format(dateFormat)
		content.ETag = object.ETag
		content.Size = object.Size
		content.StorageClass = "STANDARD"
		content.Owner = owner
		contents = append(contents, content)
	}
	data.Name = bucket
	data.Contents = contents
	data.MaxKeys = MAX_OBJECT_LIST
	data.IsTruncated = isTruncated
	return data
}
<|endoftext|>


<commit_before>package s3

import (
	"fmt"
	"github.com/mitchellh/goamz/aws"
	"github.com/mitchellh/goamz/s3"
	"github.com/smira/aptly/aptly"
	"github.com/smira/aptly/files"
	"os"
	"path/filepath"
	"strings"
)

// PublishedStorage abstract file system with published files (actually hosted on S3)
type PublishedStorage struct {
	s3     *s3.S3
	bucket *s3.Bucket
	acl    s3.ACL
	prefix string
}

// Check interface
var (
	_ aptly.PublishedStorage = (*PublishedStorage)(nil)
)

// NewPublishedStorageRaw creates published storage from raw aws credentials
func NewPublishedStorageRaw(auth aws.Auth, region aws.Region, bucket, defaultACL, prefix string) (*PublishedStorage, error) {
	if defaultACL == "" {
		defaultACL = "private"
	}

	result := &PublishedStorage{s3: s3.New(auth, region), acl: s3.ACL(defaultACL), prefix: prefix}
	result.bucket = result.s3.Bucket(bucket)

	return result, nil
}

// NewPublishedStorage creates new instance of PublishedStorage with specified S3 access
// keys, region and bucket name
func NewPublishedStorage(accessKey, secretKey, region, bucket, defaultACL, prefix string) (*PublishedStorage, error) {
	auth, err := aws.GetAuth(accessKey, secretKey)
	if err != nil {
		return nil, err
	}

	awsRegion, ok := aws.Regions[region]
	if !ok {
		return nil, fmt.Errorf("unknown region: %#v", region)
	}

	return NewPublishedStorageRaw(auth, awsRegion, bucket, defaultACL, prefix)
}

// String
func (storage *PublishedStorage) String() string {
	return fmt.Sprintf("S3: %s:%s/%s", storage.s3.Region.Name, storage.bucket.Name, storage.prefix)
}

// MkDir creates directory recursively under public path
func (storage *PublishedStorage) MkDir(path string) error {
	// no op for S3
	return nil
}

// PutFile puts file into published storage at specified path
func (storage *PublishedStorage) PutFile(path string, sourceFilename string) error {
	var (
		source *os.File
		err    error
		fi     os.FileInfo
	)
	source, err = os.Open(sourceFilename)
	if err != nil {
		return err
	}
	defer source.Close()

	fi, err = source.Stat()
	if err != nil {
		return err
	}

	err = storage.bucket.PutReader(filepath.Join(storage.prefix, path), source, fi.Size(), "binary/octet-stream", storage.acl)
	if err != nil {
		return fmt.Errorf("error uploading %s to %s: %s", sourceFilename, storage, err)
	}
	return nil
}

// Remove removes single file under public path
func (storage *PublishedStorage) Remove(path string) error {
	err := storage.bucket.Del(filepath.Join(storage.prefix, path))
	if err != nil {
		return fmt.Errorf("error deleting %s from %s: %s", path, storage, err)
	}
	return nil
}

// RemoveDirs removes directory structure under public path
func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error {
	const page = 1000

	filelist, err := storage.Filelist(path)
	if err != nil {
		return err
	}

	numParts := (len(filelist) + page - 1) / page

	for i := 0; i < numParts; i++ {
		var part []string
		if i == numParts-1 {
			part = filelist[i*page:]
		} else {
			part = filelist[i*page : (i+1)*page]
		}
		paths := make([]string, len(part))

		for i := range part {
			paths[i] = filepath.Join(storage.prefix, path, part[i])
		}

		err = storage.bucket.MultiDel(paths)
		if err != nil {
			return fmt.Errorf("error deleting multiple paths from %s: %s", storage, err)
		}
		if err != nil {
			return err
		}
	}

	return nil
}

// LinkFromPool links package file from pool to dist's pool location
//
// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/)
// sourcePool is instance of aptly.PackagePool
// sourcePath is filepath to package file in package pool
//
// LinkFromPool returns relative path for the published file to be included in package index
func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourcePool aptly.PackagePool, sourcePath, sourceMD5 string) error {
	// verify that package pool is local pool in filesystem
	_ = sourcePool.(*files.PackagePool)

	baseName := filepath.Base(sourcePath)
	relPath := filepath.Join(publishedDirectory, baseName)
	poolPath := filepath.Join(storage.prefix, relPath)

	var (
		dstKey *s3.Key
		err    error
	)

	dstKey, err = storage.bucket.GetKey(poolPath)
	if err != nil {
		if s3err, ok := err.(*s3.Error); !ok || s3err.StatusCode != 404 {
			return fmt.Errorf("error getting information about %s from %s: %s", poolPath, storage, err)
		}
	} else {
		fmt.Printf("dstKey.Etag = %#v, sourceMD5 = %#v\n", dstKey.ETag, sourceMD5)
		if strings.Replace(dstKey.ETag, "\"", "", -1) == sourceMD5 {
			fmt.Printf("skipping upload\n")
			return nil
		}
	}

	fmt.Printf("uploading\n")
	return storage.PutFile(relPath, sourcePath)
}

// Filelist returns list of files under prefix
func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
	result := []string{}
	marker := ""
	prefix = filepath.Join(storage.prefix, prefix)
	if prefix != "" {
		prefix += "/"
	}
	for {
		contents, err := storage.bucket.List(prefix, "", marker, 1000)
		if err != nil {
			return nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, storage, err)
		}
		lastKey := ""
		for _, key := range contents.Contents {
			if prefix == "" {
				result = append(result, key.Key)
			} else {
				result = append(result, key.Key[len(prefix):])
			}
			lastKey = key.Key
		}
		if contents.IsTruncated {
			marker = contents.NextMarker
			if marker == "" {
				// From the s3 docs: If response does not include the
				// NextMarker and it is truncated, you can use the value of the
				// last Key in the response as the marker in the subsequent
				// request to get the next set of object keys.
				marker = lastKey
			}
		} else {
			break
		}
	}

	return result, nil
}

// RenameFile renames (moves) file
func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
	panic("not implemented yet")
}
<commit_msg>Remove debugging output. #15<commit_after>package s3

import (
	"fmt"
	"github.com/mitchellh/goamz/aws"
	"github.com/mitchellh/goamz/s3"
	"github.com/smira/aptly/aptly"
	"github.com/smira/aptly/files"
	"os"
	"path/filepath"
	"strings"
)

// PublishedStorage abstract file system with published files (actually hosted on S3)
type PublishedStorage struct {
	s3     *s3.S3
	bucket *s3.Bucket
	acl    s3.ACL
	prefix string
}

// Check interface
var (
	_ aptly.PublishedStorage = (*PublishedStorage)(nil)
)

// NewPublishedStorageRaw creates published storage from raw aws credentials
func NewPublishedStorageRaw(auth aws.Auth, region aws.Region, bucket, defaultACL, prefix string) (*PublishedStorage, error) {
	if defaultACL == "" {
		defaultACL = "private"
	}

	result := &PublishedStorage{s3: s3.New(auth, region), acl: s3.ACL(defaultACL), prefix: prefix}
	result.bucket = result.s3.Bucket(bucket)

	return result, nil
}

// NewPublishedStorage creates new instance of PublishedStorage with specified S3 access
// keys, region and bucket name
func NewPublishedStorage(accessKey, secretKey, region, bucket, defaultACL, prefix string) (*PublishedStorage, error) {
	auth, err := aws.GetAuth(accessKey, secretKey)
	if err != nil {
		return nil, err
	}

	awsRegion, ok := aws.Regions[region]
	if !ok {
		return nil, fmt.Errorf("unknown region: %#v", region)
	}

	return NewPublishedStorageRaw(auth, awsRegion, bucket, defaultACL, prefix)
}

// String
func (storage *PublishedStorage) String() string {
	return fmt.Sprintf("S3: %s:%s/%s", storage.s3.Region.Name, storage.bucket.Name, storage.prefix)
}

// MkDir creates directory recursively under public path
func (storage *PublishedStorage) MkDir(path string) error {
	// no op for S3
	return nil
}

// PutFile puts file into published storage at specified path
func (storage *PublishedStorage) PutFile(path string, sourceFilename string) error {
	var (
		source *os.File
		err    error
		fi     os.FileInfo
	)
	source, err = os.Open(sourceFilename)
	if err != nil {
		return err
	}
	defer source.Close()

	fi, err = source.Stat()
	if err != nil {
		return err
	}

	err = storage.bucket.PutReader(filepath.Join(storage.prefix, path), source, fi.Size(), "binary/octet-stream", storage.acl)
	if err != nil {
		return fmt.Errorf("error uploading %s to %s: %s", sourceFilename, storage, err)
	}
	return nil
}

// Remove removes single file under public path
func (storage *PublishedStorage) Remove(path string) error {
	err := storage.bucket.Del(filepath.Join(storage.prefix, path))
	if err != nil {
		return fmt.Errorf("error deleting %s from %s: %s", path, storage, err)
	}
	return nil
}

// RemoveDirs removes directory structure under public path
func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error {
	const page = 1000

	filelist, err := storage.Filelist(path)
	if err != nil {
		return err
	}

	numParts := (len(filelist) + page - 1) / page

	for i := 0; i < numParts; i++ {
		var part []string
		if i == numParts-1 {
			part = filelist[i*page:]
		} else {
			part = filelist[i*page : (i+1)*page]
		}
		paths := make([]string, len(part))

		for i := range part {
			paths[i] = filepath.Join(storage.prefix, path, part[i])
		}

		err = storage.bucket.MultiDel(paths)
		if err != nil {
			return fmt.Errorf("error deleting multiple paths from %s: %s", storage, err)
		}
		if err != nil {
			return err
		}
	}

	return nil
}

// LinkFromPool links package file from pool to dist's pool location
//
// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/)
// sourcePool is instance of aptly.PackagePool
// sourcePath is filepath to package file in package pool
//
// LinkFromPool returns relative path for the published file to be included in package index
func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourcePool aptly.PackagePool, sourcePath, sourceMD5 string) error {
	// verify that package pool is local pool in filesystem
	_ = sourcePool.(*files.PackagePool)

	baseName := filepath.Base(sourcePath)
	relPath := filepath.Join(publishedDirectory, baseName)
	poolPath := filepath.Join(storage.prefix, relPath)

	var (
		dstKey *s3.Key
		err    error
	)

	dstKey, err = storage.bucket.GetKey(poolPath)
	if err != nil {
		if s3err, ok := err.(*s3.Error); !ok || s3err.StatusCode != 404 {
			return fmt.Errorf("error getting information about %s from %s: %s", poolPath, storage, err)
		}
	} else {
		if strings.Replace(dstKey.ETag, "\"", "", -1) == sourceMD5 {
			return nil
		}
	}

	return storage.PutFile(relPath, sourcePath)
}

// Filelist returns list of files under prefix
func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
	result := []string{}
	marker := ""
	prefix = filepath.Join(storage.prefix, prefix)
	if prefix != "" {
		prefix += "/"
	}
	for {
		contents, err := storage.bucket.List(prefix, "", marker, 1000)
		if err != nil {
			return nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, storage, err)
		}
		lastKey := ""
		for _, key := range contents.Contents {
			if prefix == "" {
				result = append(result, key.Key)
			} else {
				result = append(result, key.Key[len(prefix):])
			}
			lastKey = key.Key
		}
		if contents.IsTruncated {
			marker = contents.NextMarker
			if marker == "" {
				// From the s3 docs: If response does not include the
				// NextMarker and it is truncated, you can use the value of the
				// last Key in the response as the marker in the subsequent
				// request to get the next set of object keys.
				marker = lastKey
			}
		} else {
			break
		}
	}

	return result, nil
}

// RenameFile renames (moves) file
func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
	panic("not implemented yet")
}
<|endoftext|>


<commit_before>package u

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

var (
	ServerIPAddress  string
	IdentityFilePath string
)

func SshInteractive(user string) {
	panicIfServerInfoNotSet()
	cmd := exec.Command("ssh", "-i", IdentityFilePath, user)
	cmd.Stdin = os.Stdin
	RunCmdLoggedMust(cmd)
}

func LoginAsRoot() {
	user := fmt.Sprintf("root@%s", ServerIPAddress)
	SshInteractive(user)
}

// "-o StrictHostKeyChecking=no" is for the benefit of CI which start
// fresh environment
func ScpCopy(localSrcPath string, serverDstPath string) {
	panicIfServerInfoNotSet()
	cmd := exec.Command("scp", "-o", "StrictHostKeyChecking=no", "-i", IdentityFilePath, localSrcPath, serverDstPath)
	RunCmdLoggedMust(cmd)
}

// "-o StrictHostKeyChecking=no" is for the benefit of CI which start
// fresh environment
func SshExec(user string, script string) {
	panicIfServerInfoNotSet()
	cmd := exec.Command("ssh", "-o", "StrictHostKeyChecking=no", "-i", IdentityFilePath, user)
	r := bytes.NewBufferString(script)
	cmd.Stdin = r
	RunCmdLoggedMust(cmd)
}

func MakeExecScript(name string) string {
	script := fmt.Sprintf(`
chmod ug+x ./%s
./%s
rm ./%s
	`, name, name, name)
	return script
}

func panicIfServerInfoNotSet() {
	PanicIf(IdentityFilePath == "", "IdentityFilePath not set")
	PanicIf(!FileExists(IdentityFilePath), "IdentityFilePath '%s' doesn't exist", IdentityFilePath)
	PanicIf(ServerIPAddress == "", "ServerIPAddress not set")
}

// CopyAndExecServerScript copies a given script to the server and executes
// it under a given user name
func CopyAndExecServerScript(scriptPath, user string) {
	panicIfServerInfoNotSet()
	PanicIf(!FileExists(scriptPath), "script file '%s' doesn't exist", scriptPath)

	serverAndUser := fmt.Sprintf("%s@%s", user, ServerIPAddress)
	serverPath := "/root/" + filepath.Base(scriptPath)
	if user != "root" {
		serverPath = "/home/" + user + "/" + scriptPath
	}
	{
		serverDstPath := fmt.Sprintf("%s:%s", serverAndUser, serverPath)
		ScpCopy(scriptPath, serverDstPath)
	}
	{
		script := MakeExecScript(scriptPath)
		SshExec(serverAndUser, script)
	}
}
<commit_msg>more fixes to CopyAndExecServerScript()<commit_after>package u

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

var (
	ServerIPAddress  string
	IdentityFilePath string
)

func SshInteractive(user string) {
	panicIfServerInfoNotSet()
	cmd := exec.Command("ssh", "-i", IdentityFilePath, user)
	cmd.Stdin = os.Stdin
	RunCmdLoggedMust(cmd)
}

func LoginAsRoot() {
	user := fmt.Sprintf("root@%s", ServerIPAddress)
	SshInteractive(user)
}

// "-o StrictHostKeyChecking=no" is for the benefit of CI which start
// fresh environment
func ScpCopy(localSrcPath string, serverDstPath string) {
	panicIfServerInfoNotSet()
	cmd := exec.Command("scp", "-o", "StrictHostKeyChecking=no", "-i", IdentityFilePath, localSrcPath, serverDstPath)
	RunCmdLoggedMust(cmd)
}

// "-o StrictHostKeyChecking=no" is for the benefit of CI which start
// fresh environment
func SshExec(user string, script string) {
	panicIfServerInfoNotSet()
	cmd := exec.Command("ssh", "-o", "StrictHostKeyChecking=no", "-i", IdentityFilePath, user)
	r := bytes.NewBufferString(script)
	cmd.Stdin = r
	RunCmdLoggedMust(cmd)
}

func MakeExecScript(name string) string {
	script := fmt.Sprintf(`
chmod ug+x ./%s
./%s
rm ./%s
	`, name, name, name)
	return script
}

func panicIfServerInfoNotSet() {
	PanicIf(IdentityFilePath == "", "IdentityFilePath not set")
	PanicIf(!FileExists(IdentityFilePath), "IdentityFilePath '%s' doesn't exist", IdentityFilePath)
	PanicIf(ServerIPAddress == "", "ServerIPAddress not set")
}

// CopyAndExecServerScript copies a given script to the server and executes
// it under a given user name
func CopyAndExecServerScript(scriptLocalPath, user string) {
	panicIfServerInfoNotSet()
	PanicIf(!FileExists(scriptLocalPath), "script file '%s' doesn't exist", scriptLocalPath)

	serverAndUser := fmt.Sprintf("%s@%s", user, ServerIPAddress)
	scriptBaseName := filepath.Base(scriptLocalPath)
	scriptServerPath := "/root/" + scriptBaseName
	if user != "root" {
		scriptServerPath = "/home/" + user + "/" + scriptBaseName
	}
	{
		serverDstPath := fmt.Sprintf("%s:%s", serverAndUser, scriptServerPath)
		ScpCopy(scriptLocalPath, serverDstPath)
	}
	{
		script := MakeExecScript(scriptServerPath)
		SshExec(serverAndUser, script)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/go.crypto/ssh"
	"code.google.com/p/goauth2/oauth"
	"crypto"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"github.com/google/go-github/github"
	"github.com/gorilla/mux"
	"github.com/kylelemons/go-gypsy/yaml"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/user"
	"path"
)

var (
	port       = "8080"
	sshPort    = "22"
	configFile = "config.yml"
)

func getPrivateKey(filename string) []byte {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Panic("Failed to open private key file: " + err.Error())
	}
	return content
}

type keychain struct {
	key *rsa.PrivateKey
}

func (k *keychain) Key(i int) (interface{}, error) {
	if i != 0 {
		return nil, nil
	}
	return &k.key.PublicKey, nil
}

func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
	hashFunc := crypto.SHA1
	h := hashFunc.New()
	h.Write(data)
	digest := h.Sum(nil)
	return rsa.SignPKCS1v15(rand, k.key, hashFunc, digest)
}

//  Will return the latest commit hash, waiting on https://github.com/google/go-github/pull/49
func latestGitHubCommit(c *github.Client, repoName string) *github.Repository {
	repo, _, err := c.Repositories.Get("gengo", repoName)
	if err != nil {
		log.Panic(err)
	}
	return repo
}

func remoteCmdOutput(username, hostname, privateKey, cmd string) []byte {
	block, _ := pem.Decode([]byte(privateKey))
	rsakey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
	clientKey := &keychain{rsakey}
	clientConfig := &ssh.ClientConfig{
		User: username,
		Auth: []ssh.ClientAuth{
			ssh.ClientAuthKeyring(clientKey),
		},
	}
	client, err := ssh.Dial("tcp", hostname, clientConfig)
	if err != nil {
		log.Panic("Failed to dial: " + err.Error())
	}
	session, err := client.NewSession()
	if err != nil {
		log.Panic("Failed to create session: " + err.Error())
	}
	defer session.Close()
	output, err := session.Output(cmd)
	if err != nil {
		log.Panic("Failed to run cmd: " + err.Error())
	}
	return output
}

func latestDeployedCommit(username, hostname string, e Environment) []byte {
	usr, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}
	privateKey := string(getPrivateKey(path.Join(usr.HomeDir, "/.ssh/id_rsa")))
	output := remoteCmdOutput(username, hostname, privateKey, fmt.Sprintf("git --git-dir=%s rev-parse %s", e.RepoPath, e.Branch))

	return output
}

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	projects, deployUser := parseYAML()
	projects = retrieveCommits(projects, deployUser)
	t, _ := template.ParseFiles("templates/index.html")
	t.Execute(w, map[string]interface{}{"Projects": projects})
}

type Host struct {
	URI          string
	LatestCommit string
}

type Environment struct {
	Name     string
	RepoPath string
	Hosts    []Host
	Branch   string
}

type Project struct {
	Name         string
	GitHubURL    string
	Environments []Environment
}

func parseYAMLEnvironment(m yaml.Node) Environment {
	e := Environment{}
	for k, v := range m.(yaml.Map) {
		e.Name = k
		e.Branch = v.(yaml.Map)["branch"].(yaml.Scalar).String()
		e.RepoPath = v.(yaml.Map)["repo_path"].(yaml.Scalar).String()
		for _, v := range v.(yaml.Map)["hosts"].(yaml.List) {
			h := Host{URI: v.(yaml.Scalar).String()}
			e.Hosts = append(e.Hosts, h)
		}
	}
	return e
}

func parseYAML() (allProjects []Project, deployUser string) {
	config, err := yaml.ReadFile(configFile)
	if err != nil {
		log.Fatal(err)
	}
	deployUser, err = config.Get("deploy_user")
	if err != nil {
		log.Fatal("config.yml is missing deploy_user: " + err.Error())
	}
	configRoot, _ := config.Root.(yaml.Map)
	projects, _ := configRoot["projects"].(yaml.List)
	allProjects = []Project{}
	for _, p := range projects {
		for k, v := range p.(yaml.Map) {
			proj := Project{Name: k, GitHubURL: v.(yaml.Map)["github_url"].(yaml.Scalar).String()}
			for _, v := range v.(yaml.Map)["environments"].(yaml.List) {
				proj.Environments = append(proj.Environments, parseYAMLEnvironment(v))
			}
			allProjects = append(allProjects, proj)
		}
	}
	return allProjects, deployUser
}

func retrieveCommits(projects []Project, deployUser string) []Project {
	for i, p := range projects {
		for j, e := range p.Environments {
			for k, h := range e.Hosts {
				h.LatestCommit = string(latestDeployedCommit(deployUser, h.URI+":"+sshPort, e))
				projects[i].Environments[j].Hosts[k] = h
			}
			projects[i].Environments[j] = e
		}
	}
	return projects
}

func main() {
	githubToken := os.Getenv("GITHUB_API_TOKEN")
	t := &oauth.Transport{
		Token: &oauth.Token{AccessToken: githubToken},
	}
	client := github.NewClient(t.Client())
	fmt.Println(client)

	r := mux.NewRouter()
	r.HandleFunc("/", HomeHandler)
	fmt.Println("Running on localhost:" + port)
	http.ListenAndServe(":"+port, r)
}
<commit_msg>update variable names<commit_after>package main

import (
	"code.google.com/p/go.crypto/ssh"
	"code.google.com/p/goauth2/oauth"
	"crypto"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"github.com/google/go-github/github"
	"github.com/gorilla/mux"
	"github.com/kylelemons/go-gypsy/yaml"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/user"
	"path"
)

var (
	port       = "8080"
	sshPort    = "22"
	configFile = "config.yml"
)

func getPrivateKey(filename string) []byte {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Panic("Failed to open private key file: " + err.Error())
	}
	return content
}

type keychain struct {
	key *rsa.PrivateKey
}

func (k *keychain) Key(i int) (interface{}, error) {
	if i != 0 {
		return nil, nil
	}
	return &k.key.PublicKey, nil
}

func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
	hashFunc := crypto.SHA1
	h := hashFunc.New()
	h.Write(data)
	digest := h.Sum(nil)
	return rsa.SignPKCS1v15(rand, k.key, hashFunc, digest)
}

//  Will return the latest commit hash, waiting on https://github.com/google/go-github/pull/49
func latestGitHubCommit(c *github.Client, repoName string) *github.Repository {
	repo, _, err := c.Repositories.Get("gengo", repoName)
	if err != nil {
		log.Panic(err)
	}
	return repo
}

func remoteCmdOutput(username, hostname, privateKey, cmd string) []byte {
	block, _ := pem.Decode([]byte(privateKey))
	rsakey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
	clientKey := &keychain{rsakey}
	clientConfig := &ssh.ClientConfig{
		User: username,
		Auth: []ssh.ClientAuth{
			ssh.ClientAuthKeyring(clientKey),
		},
	}
	client, err := ssh.Dial("tcp", hostname, clientConfig)
	if err != nil {
		log.Panic("Failed to dial: " + err.Error())
	}
	session, err := client.NewSession()
	if err != nil {
		log.Panic("Failed to create session: " + err.Error())
	}
	defer session.Close()
	output, err := session.Output(cmd)
	if err != nil {
		log.Panic("Failed to run cmd: " + err.Error())
	}
	return output
}

func latestDeployedCommit(username, hostname string, e Environment) []byte {
	usr, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}
	privateKey := string(getPrivateKey(path.Join(usr.HomeDir, "/.ssh/id_rsa")))
	output := remoteCmdOutput(username, hostname, privateKey, fmt.Sprintf("git --git-dir=%s rev-parse %s", e.RepoPath, e.Branch))

	return output
}

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	projects, deployUser := parseYAML()
	projects = retrieveCommits(projects, deployUser)
	t, _ := template.ParseFiles("templates/index.html")
	t.Execute(w, map[string]interface{}{"Projects": projects})
}

type Host struct {
	URI          string
	LatestCommit string
}

type Environment struct {
	Name     string
	RepoPath string
	Hosts    []Host
	Branch   string
}

type Project struct {
	Name         string
	GitHubURL    string
	Environments []Environment
}

func parseYAMLEnvironment(m yaml.Node) Environment {
	e := Environment{}
	for k, v := range m.(yaml.Map) {
		e.Name = k
		e.Branch = v.(yaml.Map)["branch"].(yaml.Scalar).String()
		e.RepoPath = v.(yaml.Map)["repo_path"].(yaml.Scalar).String()
		for _, v := range v.(yaml.Map)["hosts"].(yaml.List) {
			h := Host{URI: v.(yaml.Scalar).String()}
			e.Hosts = append(e.Hosts, h)
		}
	}
	return e
}

func parseYAML() (allProjects []Project, deployUser string) {
	config, err := yaml.ReadFile(configFile)
	if err != nil {
		log.Fatal(err)
	}
	deployUser, err = config.Get("deploy_user")
	if err != nil {
		log.Fatal("config.yml is missing deploy_user: " + err.Error())
	}
	configRoot, _ := config.Root.(yaml.Map)
	projects, _ := configRoot["projects"].(yaml.List)
	allProjects = []Project{}
	for _, p := range projects {
		for k, v := range p.(yaml.Map) {
			proj := Project{Name: k, GitHubURL: v.(yaml.Map)["github_url"].(yaml.Scalar).String()}
			for _, v := range v.(yaml.Map)["environments"].(yaml.List) {
				proj.Environments = append(proj.Environments, parseYAMLEnvironment(v))
			}
			allProjects = append(allProjects, proj)
		}
	}
	return allProjects, deployUser
}

func retrieveCommits(projects []Project, deployUser string) []Project {
	for i, project := range projects {
		for j, environment := range project.Environments {
			for k, host := range environment.Hosts {
				host.LatestCommit = string(latestDeployedCommit(deployUser, host.URI+":"+sshPort, environment))
				projects[i].Environments[j].Hosts[k] = host
			}
			projects[i].Environments[j] = environment
		}
	}
	return projects
}

func main() {
	githubToken := os.Getenv("GITHUB_API_TOKEN")
	t := &oauth.Transport{
		Token: &oauth.Token{AccessToken: githubToken},
	}
	client := github.NewClient(t.Client())
	fmt.Println(client)

	r := mux.NewRouter()
	r.HandleFunc("/", HomeHandler)
	fmt.Println("Running on localhost:" + port)
	http.ListenAndServe(":"+port, r)
}
<|endoftext|>