<commit_before>//  Copyright Project Harbor 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 handler

import (
	"context"
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"

	"github.com/go-openapi/runtime/middleware"
	"github.com/go-openapi/strfmt"

	"github.com/goharbor/harbor/src/common/rbac"
	"github.com/goharbor/harbor/src/common/utils"
	"github.com/goharbor/harbor/src/controller/robot"
	"github.com/goharbor/harbor/src/lib"
	"github.com/goharbor/harbor/src/lib/config"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/log"
	pkg "github.com/goharbor/harbor/src/pkg/robot/model"
	"github.com/goharbor/harbor/src/server/v2.0/handler/model"
	"github.com/goharbor/harbor/src/server/v2.0/models"
	operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/robot"
)

func newRobotAPI() *robotAPI {
	return &robotAPI{
		robotCtl: robot.Ctl,
	}
}

type robotAPI struct {
	BaseAPI
	robotCtl robot.Controller
}

func (rAPI *robotAPI) CreateRobot(ctx context.Context, params operation.CreateRobotParams) middleware.Responder {
	if err := validateName(params.Robot.Name); err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.validate(params.Robot.Duration, params.Robot.Level, params.Robot.Permissions); err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.requireAccess(ctx, params.Robot.Level, params.Robot.Permissions[0].Namespace, rbac.ActionCreate); err != nil {
		return rAPI.SendError(ctx, err)
	}

	r := &robot.Robot{
		Robot: pkg.Robot{
			Name:        params.Robot.Name,
			Description: params.Robot.Description,
			Duration:    params.Robot.Duration,
			Visible:     true,
		},
		Level: params.Robot.Level,
	}

	if err := lib.JSONCopy(&r.Permissions, params.Robot.Permissions); err != nil {
		log.Warningf("failed to call JSONCopy on robot permission when CreateRobot, error: %v", err)
	}

	rid, pwd, err := rAPI.robotCtl.Create(ctx, r)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	created, err := rAPI.robotCtl.Get(ctx, rid, nil)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	location := fmt.Sprintf("%s/%d", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), created.ID)
	return operation.NewCreateRobotCreated().WithLocation(location).WithPayload(&models.RobotCreated{
		ID:           created.ID,
		Name:         created.Name,
		Secret:       pwd,
		CreationTime: strfmt.DateTime(created.CreationTime),
		ExpiresAt:    created.ExpiresAt,
	})
}

func (rAPI *robotAPI) DeleteRobot(ctx context.Context, params operation.DeleteRobotParams) middleware.Responder {
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}

	r, err := rAPI.robotCtl.Get(ctx, params.RobotID, nil)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.requireAccess(ctx, r.Level, r.ProjectID, rbac.ActionDelete); err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.robotCtl.Delete(ctx, params.RobotID); err != nil {
		// for the version 1 robot account, has to ignore the no permission error.
		if !r.Editable && errors.IsNotFoundErr(err) {
			return operation.NewDeleteRobotOK()
		}
		return rAPI.SendError(ctx, err)
	}
	return operation.NewDeleteRobotOK()
}

func (rAPI *robotAPI) ListRobot(ctx context.Context, params operation.ListRobotParams) middleware.Responder {
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}

	query, err := rAPI.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	var projectID int64
	var level string
	// GET /api/v2.0/robots or GET /api/v2.0/robots?level=system to get all of system level robots.
	// GET /api/v2.0/robots?level=project&project_id=1
	if _, ok := query.Keywords["Level"]; ok {
		if !isValidLevel(query.Keywords["Level"].(string)) {
			return rAPI.SendError(ctx, errors.New(nil).WithMessage("bad request error level input").WithCode(errors.BadRequestCode))
		}
		level = query.Keywords["Level"].(string)
		if level == robot.LEVELPROJECT {
			if _, ok := query.Keywords["ProjectID"]; !ok {
				return rAPI.SendError(ctx, errors.BadRequestError(nil).WithMessage("must with project ID when to query project robots"))
			}
			pid, err := strconv.ParseInt(query.Keywords["ProjectID"].(string), 10, 64)
			if err != nil {
				return rAPI.SendError(ctx, errors.BadRequestError(nil).WithMessage("Project ID must be int type."))
			}
			projectID = pid
		}
	} else {
		level = robot.LEVELSYSTEM
		query.Keywords["ProjectID"] = 0
	}
	query.Keywords["Visible"] = true

	if err := rAPI.requireAccess(ctx, level, projectID, rbac.ActionList); err != nil {
		return rAPI.SendError(ctx, err)
	}

	total, err := rAPI.robotCtl.Count(ctx, query)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	robots, err := rAPI.robotCtl.List(ctx, query, &robot.Option{
		WithPermission: true,
	})
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	var results []*models.Robot
	for _, r := range robots {
		results = append(results, model.NewRobot(r).ToSwagger())
	}

	return operation.NewListRobotOK().
		WithXTotalCount(total).
		WithLink(rAPI.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
		WithPayload(results)
}

func (rAPI *robotAPI) GetRobotByID(ctx context.Context, params operation.GetRobotByIDParams) middleware.Responder {
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}

	r, err := rAPI.robotCtl.Get(ctx, params.RobotID, &robot.Option{
		WithPermission: true,
	})
	if err != nil {
		return rAPI.SendError(ctx, err)
	}
	if err := rAPI.requireAccess(ctx, r.Level, r.ProjectID, rbac.ActionRead); err != nil {
		return rAPI.SendError(ctx, err)
	}

	return operation.NewGetRobotByIDOK().WithPayload(model.NewRobot(r).ToSwagger())
}

func (rAPI *robotAPI) UpdateRobot(ctx context.Context, params operation.UpdateRobotParams) middleware.Responder {
	var err error
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}
	r, err := rAPI.robotCtl.Get(ctx, params.RobotID, &robot.Option{
		WithPermission: true,
	})
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	if !r.Editable {
		err = errors.DeniedError(nil).WithMessage("editing of legacy robot is not allowed")
	} else {
		err = rAPI.updateV2Robot(ctx, params, r)
	}
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	return operation.NewUpdateRobotOK()
}

func (rAPI *robotAPI) RefreshSec(ctx context.Context, params operation.RefreshSecParams) middleware.Responder {
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}

	r, err := rAPI.robotCtl.Get(ctx, params.RobotID, nil)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.requireAccess(ctx, r.Level, r.ProjectID, rbac.ActionUpdate); err != nil {
		return rAPI.SendError(ctx, err)
	}

	var secret string
	robotSec := &models.RobotSec{}
	if params.RobotSec.Secret != "" {
		if !robot.IsValidSec(params.RobotSec.Secret) {
			return rAPI.SendError(ctx, errors.New("the secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number").WithCode(errors.BadRequestCode))
		}
		secret = utils.Encrypt(params.RobotSec.Secret, r.Salt, utils.SHA256)
		robotSec.Secret = ""
	} else {
		sec, pwd, _, err := robot.CreateSec(r.Salt)
		if err != nil {
			return rAPI.SendError(ctx, err)
		}
		secret = sec
		robotSec.Secret = pwd
	}

	r.Secret = secret
	if err := rAPI.robotCtl.Update(ctx, r, nil); err != nil {
		return rAPI.SendError(ctx, err)
	}

	return operation.NewRefreshSecOK().WithPayload(robotSec)
}

func (rAPI *robotAPI) requireAccess(ctx context.Context, level string, projectIDOrName interface{}, action rbac.Action) error {
	if level == robot.LEVELSYSTEM {
		return rAPI.RequireSystemAccess(ctx, action, rbac.ResourceRobot)
	} else if level == robot.LEVELPROJECT {
		return rAPI.RequireProjectAccess(ctx, projectIDOrName, action, rbac.ResourceRobot)
	}
	return errors.ForbiddenError(nil)
}

// more validation
func (rAPI *robotAPI) validate(d int64, level string, permissions []*models.RobotPermission) error {
	if !isValidDuration(d) {
		return errors.New(nil).WithMessage("bad request error duration input: %d", d).WithCode(errors.BadRequestCode)
	}

	if !isValidLevel(level) {
		return errors.New(nil).WithMessage("bad request error level input: %s", level).WithCode(errors.BadRequestCode)
	}

	if len(permissions) == 0 {
		return errors.New(nil).WithMessage("bad request empty permission").WithCode(errors.BadRequestCode)
	}

	for _, perm := range permissions {
		if len(perm.Access) == 0 {
			return errors.New(nil).WithMessage("bad request empty access").WithCode(errors.BadRequestCode)
		}
	}

	// to create a project robot, the permission must be only one project scope.
	if level == robot.LEVELPROJECT && len(permissions) > 1 {
		return errors.New(nil).WithMessage("bad request permission").WithCode(errors.BadRequestCode)
	}
	return nil
}

func (rAPI *robotAPI) updateV2Robot(ctx context.Context, params operation.UpdateRobotParams, r *robot.Robot) error {
	if err := rAPI.validate(params.Robot.Duration, params.Robot.Level, params.Robot.Permissions); err != nil {
		return err
	}
	projectID, err := getProjectID(ctx, params.Robot.Permissions[0].Namespace)
	if err != nil {
		return err
	}
	if r.Level != robot.LEVELSYSTEM && r.ProjectID != projectID {
		return errors.BadRequestError(nil).WithMessage("cannot update the project id of robot")
	}
	if err := rAPI.requireAccess(ctx, params.Robot.Level, projectID, rbac.ActionUpdate); err != nil {
		return err
	}
	if params.Robot.Level != r.Level || params.Robot.Name != r.Name {
		return errors.BadRequestError(nil).WithMessage("cannot update the level or name of robot")
	}

	if r.Duration != params.Robot.Duration {
		r.Duration = params.Robot.Duration
		if params.Robot.Duration == -1 {
			r.ExpiresAt = -1
		} else if params.Robot.Duration == 0 {
			r.Duration = int64(config.RobotTokenDuration(ctx))
			r.ExpiresAt = r.CreationTime.AddDate(0, 0, config.RobotTokenDuration(ctx)).Unix()
		} else {
			r.ExpiresAt = r.CreationTime.AddDate(0, 0, int(params.Robot.Duration)).Unix()
		}
	}

	r.Description = params.Robot.Description
	r.Disabled = params.Robot.Disable
	if len(params.Robot.Permissions) != 0 {
		if err := lib.JSONCopy(&r.Permissions, params.Robot.Permissions); err != nil {
			log.Warningf("failed to call JSONCopy on robot permission when updateV2Robot, error: %v", err)
		}
	}

	if err := rAPI.robotCtl.Update(ctx, r, &robot.Option{
		WithPermission: true,
	}); err != nil {
		return err
	}
	return nil
}

func isValidLevel(l string) bool {
	return l == robot.LEVELSYSTEM || l == robot.LEVELPROJECT
}

func isValidDuration(d int64) bool {
	return d >= int64(-1) && d < math.MaxInt32
}

// validateName validates the robot name, especially '+' cannot be a valid character
func validateName(name string) error {
	robotNameReg := `^[a-z0-9]+(?:[._-][a-z0-9]+)*$`
	legal := regexp.MustCompile(robotNameReg).MatchString(name)
	if !legal {
		return errors.BadRequestError(nil).WithMessage("robot name is not in lower case or contains illegal characters")
	}
	return nil
}
<commit_msg>fix update robot regression (#17248)<commit_after>//  Copyright Project Harbor 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 handler

import (
	"context"
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"

	"github.com/go-openapi/runtime/middleware"
	"github.com/go-openapi/strfmt"

	"github.com/goharbor/harbor/src/common/rbac"
	"github.com/goharbor/harbor/src/common/utils"
	"github.com/goharbor/harbor/src/controller/robot"
	"github.com/goharbor/harbor/src/lib"
	"github.com/goharbor/harbor/src/lib/config"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/log"
	pkg "github.com/goharbor/harbor/src/pkg/robot/model"
	"github.com/goharbor/harbor/src/server/v2.0/handler/model"
	"github.com/goharbor/harbor/src/server/v2.0/models"
	operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/robot"
)

func newRobotAPI() *robotAPI {
	return &robotAPI{
		robotCtl: robot.Ctl,
	}
}

type robotAPI struct {
	BaseAPI
	robotCtl robot.Controller
}

func (rAPI *robotAPI) CreateRobot(ctx context.Context, params operation.CreateRobotParams) middleware.Responder {
	if err := validateName(params.Robot.Name); err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.validate(params.Robot.Duration, params.Robot.Level, params.Robot.Permissions); err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.requireAccess(ctx, params.Robot.Level, params.Robot.Permissions[0].Namespace, rbac.ActionCreate); err != nil {
		return rAPI.SendError(ctx, err)
	}

	r := &robot.Robot{
		Robot: pkg.Robot{
			Name:        params.Robot.Name,
			Description: params.Robot.Description,
			Duration:    params.Robot.Duration,
			Visible:     true,
		},
		Level: params.Robot.Level,
	}

	if err := lib.JSONCopy(&r.Permissions, params.Robot.Permissions); err != nil {
		log.Warningf("failed to call JSONCopy on robot permission when CreateRobot, error: %v", err)
	}

	rid, pwd, err := rAPI.robotCtl.Create(ctx, r)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	created, err := rAPI.robotCtl.Get(ctx, rid, nil)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	location := fmt.Sprintf("%s/%d", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), created.ID)
	return operation.NewCreateRobotCreated().WithLocation(location).WithPayload(&models.RobotCreated{
		ID:           created.ID,
		Name:         created.Name,
		Secret:       pwd,
		CreationTime: strfmt.DateTime(created.CreationTime),
		ExpiresAt:    created.ExpiresAt,
	})
}

func (rAPI *robotAPI) DeleteRobot(ctx context.Context, params operation.DeleteRobotParams) middleware.Responder {
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}

	r, err := rAPI.robotCtl.Get(ctx, params.RobotID, nil)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.requireAccess(ctx, r.Level, r.ProjectID, rbac.ActionDelete); err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.robotCtl.Delete(ctx, params.RobotID); err != nil {
		// for the version 1 robot account, has to ignore the no permission error.
		if !r.Editable && errors.IsNotFoundErr(err) {
			return operation.NewDeleteRobotOK()
		}
		return rAPI.SendError(ctx, err)
	}
	return operation.NewDeleteRobotOK()
}

func (rAPI *robotAPI) ListRobot(ctx context.Context, params operation.ListRobotParams) middleware.Responder {
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}

	query, err := rAPI.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	var projectID int64
	var level string
	// GET /api/v2.0/robots or GET /api/v2.0/robots?level=system to get all of system level robots.
	// GET /api/v2.0/robots?level=project&project_id=1
	if _, ok := query.Keywords["Level"]; ok {
		if !isValidLevel(query.Keywords["Level"].(string)) {
			return rAPI.SendError(ctx, errors.New(nil).WithMessage("bad request error level input").WithCode(errors.BadRequestCode))
		}
		level = query.Keywords["Level"].(string)
		if level == robot.LEVELPROJECT {
			if _, ok := query.Keywords["ProjectID"]; !ok {
				return rAPI.SendError(ctx, errors.BadRequestError(nil).WithMessage("must with project ID when to query project robots"))
			}
			pid, err := strconv.ParseInt(query.Keywords["ProjectID"].(string), 10, 64)
			if err != nil {
				return rAPI.SendError(ctx, errors.BadRequestError(nil).WithMessage("Project ID must be int type."))
			}
			projectID = pid
		}
	} else {
		level = robot.LEVELSYSTEM
		query.Keywords["ProjectID"] = 0
	}
	query.Keywords["Visible"] = true

	if err := rAPI.requireAccess(ctx, level, projectID, rbac.ActionList); err != nil {
		return rAPI.SendError(ctx, err)
	}

	total, err := rAPI.robotCtl.Count(ctx, query)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	robots, err := rAPI.robotCtl.List(ctx, query, &robot.Option{
		WithPermission: true,
	})
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	var results []*models.Robot
	for _, r := range robots {
		results = append(results, model.NewRobot(r).ToSwagger())
	}

	return operation.NewListRobotOK().
		WithXTotalCount(total).
		WithLink(rAPI.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
		WithPayload(results)
}

func (rAPI *robotAPI) GetRobotByID(ctx context.Context, params operation.GetRobotByIDParams) middleware.Responder {
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}

	r, err := rAPI.robotCtl.Get(ctx, params.RobotID, &robot.Option{
		WithPermission: true,
	})
	if err != nil {
		return rAPI.SendError(ctx, err)
	}
	if err := rAPI.requireAccess(ctx, r.Level, r.ProjectID, rbac.ActionRead); err != nil {
		return rAPI.SendError(ctx, err)
	}

	return operation.NewGetRobotByIDOK().WithPayload(model.NewRobot(r).ToSwagger())
}

func (rAPI *robotAPI) UpdateRobot(ctx context.Context, params operation.UpdateRobotParams) middleware.Responder {
	var err error
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}
	r, err := rAPI.robotCtl.Get(ctx, params.RobotID, &robot.Option{
		WithPermission: true,
	})
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	if !r.Editable {
		err = errors.DeniedError(nil).WithMessage("editing of legacy robot is not allowed")
	} else {
		err = rAPI.updateV2Robot(ctx, params, r)
	}
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	return operation.NewUpdateRobotOK()
}

func (rAPI *robotAPI) RefreshSec(ctx context.Context, params operation.RefreshSecParams) middleware.Responder {
	if err := rAPI.RequireAuthenticated(ctx); err != nil {
		return rAPI.SendError(ctx, err)
	}

	r, err := rAPI.robotCtl.Get(ctx, params.RobotID, nil)
	if err != nil {
		return rAPI.SendError(ctx, err)
	}

	if err := rAPI.requireAccess(ctx, r.Level, r.ProjectID, rbac.ActionUpdate); err != nil {
		return rAPI.SendError(ctx, err)
	}

	var secret string
	robotSec := &models.RobotSec{}
	if params.RobotSec.Secret != "" {
		if !robot.IsValidSec(params.RobotSec.Secret) {
			return rAPI.SendError(ctx, errors.New("the secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number").WithCode(errors.BadRequestCode))
		}
		secret = utils.Encrypt(params.RobotSec.Secret, r.Salt, utils.SHA256)
		robotSec.Secret = ""
	} else {
		sec, pwd, _, err := robot.CreateSec(r.Salt)
		if err != nil {
			return rAPI.SendError(ctx, err)
		}
		secret = sec
		robotSec.Secret = pwd
	}

	r.Secret = secret
	if err := rAPI.robotCtl.Update(ctx, r, nil); err != nil {
		return rAPI.SendError(ctx, err)
	}

	return operation.NewRefreshSecOK().WithPayload(robotSec)
}

func (rAPI *robotAPI) requireAccess(ctx context.Context, level string, projectIDOrName interface{}, action rbac.Action) error {
	if level == robot.LEVELSYSTEM {
		return rAPI.RequireSystemAccess(ctx, action, rbac.ResourceRobot)
	} else if level == robot.LEVELPROJECT {
		return rAPI.RequireProjectAccess(ctx, projectIDOrName, action, rbac.ResourceRobot)
	}
	return errors.ForbiddenError(nil)
}

// more validation
func (rAPI *robotAPI) validate(d int64, level string, permissions []*models.RobotPermission) error {
	if !isValidDuration(d) {
		return errors.New(nil).WithMessage("bad request error duration input: %d", d).WithCode(errors.BadRequestCode)
	}

	if !isValidLevel(level) {
		return errors.New(nil).WithMessage("bad request error level input: %s", level).WithCode(errors.BadRequestCode)
	}

	if len(permissions) == 0 {
		return errors.New(nil).WithMessage("bad request empty permission").WithCode(errors.BadRequestCode)
	}

	for _, perm := range permissions {
		if len(perm.Access) == 0 {
			return errors.New(nil).WithMessage("bad request empty access").WithCode(errors.BadRequestCode)
		}
	}

	// to create a project robot, the permission must be only one project scope.
	if level == robot.LEVELPROJECT && len(permissions) > 1 {
		return errors.New(nil).WithMessage("bad request permission").WithCode(errors.BadRequestCode)
	}
	return nil
}

func (rAPI *robotAPI) updateV2Robot(ctx context.Context, params operation.UpdateRobotParams, r *robot.Robot) error {
	if err := rAPI.validate(params.Robot.Duration, params.Robot.Level, params.Robot.Permissions); err != nil {
		return err
	}
	if r.Level != robot.LEVELSYSTEM {
		projectID, err := getProjectID(ctx, params.Robot.Permissions[0].Namespace)
		if err != nil {
			return err
		}
		if r.ProjectID != projectID {
			return errors.BadRequestError(nil).WithMessage("cannot update the project id of robot")
		}
	}
	if err := rAPI.requireAccess(ctx, params.Robot.Level, params.Robot.Permissions[0].Namespace, rbac.ActionUpdate); err != nil {
		return err
	}
	if params.Robot.Level != r.Level || params.Robot.Name != r.Name {
		return errors.BadRequestError(nil).WithMessage("cannot update the level or name of robot")
	}

	if r.Duration != params.Robot.Duration {
		r.Duration = params.Robot.Duration
		if params.Robot.Duration == -1 {
			r.ExpiresAt = -1
		} else if params.Robot.Duration == 0 {
			r.Duration = int64(config.RobotTokenDuration(ctx))
			r.ExpiresAt = r.CreationTime.AddDate(0, 0, config.RobotTokenDuration(ctx)).Unix()
		} else {
			r.ExpiresAt = r.CreationTime.AddDate(0, 0, int(params.Robot.Duration)).Unix()
		}
	}

	r.Description = params.Robot.Description
	r.Disabled = params.Robot.Disable
	if len(params.Robot.Permissions) != 0 {
		if err := lib.JSONCopy(&r.Permissions, params.Robot.Permissions); err != nil {
			log.Warningf("failed to call JSONCopy on robot permission when updateV2Robot, error: %v", err)
		}
	}

	if err := rAPI.robotCtl.Update(ctx, r, &robot.Option{
		WithPermission: true,
	}); err != nil {
		return err
	}
	return nil
}

func isValidLevel(l string) bool {
	return l == robot.LEVELSYSTEM || l == robot.LEVELPROJECT
}

func isValidDuration(d int64) bool {
	return d >= int64(-1) && d < math.MaxInt32
}

// validateName validates the robot name, especially '+' cannot be a valid character
func validateName(name string) error {
	robotNameReg := `^[a-z0-9]+(?:[._-][a-z0-9]+)*$`
	legal := regexp.MustCompile(robotNameReg).MatchString(name)
	if !legal {
		return errors.BadRequestError(nil).WithMessage("robot name is not in lower case or contains illegal characters")
	}
	return nil
}
<|endoftext|>


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

package rt_test

import (
	"fmt"
	"os"
	"reflect"
	"strings"
	"testing"
	"time"

	"v.io/v23"
	"v.io/v23/context"
	"v.io/v23/rpc"
	"v.io/v23/services/appcycle"
	"v.io/x/ref/lib/mgmt"
	"v.io/x/ref/lib/security/securityflag"
	_ "v.io/x/ref/runtime/factories/generic"
	"v.io/x/ref/services/device"
	"v.io/x/ref/test"
	"v.io/x/ref/test/expect"
	"v.io/x/ref/test/modules"
)

// TestBasic verifies that the basic plumbing works: LocalStop calls result in
// stop messages being sent on the channel passed to WaitForStop.
func TestBasic(t *testing.T) {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	ch := make(chan string, 1)
	m.WaitForStop(ctx, ch)
	for i := 0; i < 10; i++ {
		m.Stop(ctx)
		if want, got := v23.LocalStop, <-ch; want != got {
			t.Errorf("WaitForStop want %q got %q", want, got)
		}
		select {
		case s := <-ch:
			t.Errorf("channel expected to be empty, got %q instead", s)
		default:
		}
	}
}

// TestMultipleWaiters verifies that the plumbing works with more than one
// registered wait channel.
func TestMultipleWaiters(t *testing.T) {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	ch1 := make(chan string, 1)
	m.WaitForStop(ctx, ch1)
	ch2 := make(chan string, 1)
	m.WaitForStop(ctx, ch2)
	for i := 0; i < 10; i++ {
		m.Stop(ctx)
		if want, got := v23.LocalStop, <-ch1; want != got {
			t.Errorf("WaitForStop want %q got %q", want, got)
		}
		if want, got := v23.LocalStop, <-ch2; want != got {
			t.Errorf("WaitForStop want %q got %q", want, got)
		}
	}
}

// TestMultipleStops verifies that LocalStop does not block even if the wait
// channel is not being drained: once the channel's buffer fills up, future
// Stops become no-ops.
func TestMultipleStops(t *testing.T) {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	ch := make(chan string, 1)
	m.WaitForStop(ctx, ch)
	for i := 0; i < 10; i++ {
		m.Stop(ctx)
	}
	if want, got := v23.LocalStop, <-ch; want != got {
		t.Errorf("WaitForStop want %q got %q", want, got)
	}
	select {
	case s := <-ch:
		t.Errorf("channel expected to be empty, got %q instead", s)
	default:
	}
}

var noWaiters = modules.Register(func(env *modules.Env, args ...string) error {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	fmt.Fprintf(env.Stdout, "ready\n")
	modules.WaitForEOF(env.Stdin)
	m.Stop(ctx)
	os.Exit(42) // This should not be reached.
	return nil
}, "noWaiters")

// TestNoWaiters verifies that the child process exits in the absence of any
// wait channel being registered with its runtime.
func TestNoWaiters(t *testing.T) {
	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	defer sh.Cleanup(os.Stderr, os.Stderr)
	h, err := sh.Start(nil, noWaiters)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	h.Expect("ready")
	want := fmt.Sprintf("exit status %d", testUnhandledStopExitCode)
	if err = h.Shutdown(os.Stderr, os.Stderr); err == nil || err.Error() != want {
		t.Errorf("got %v, want %s", err, want)
	}
}

var forceStop = modules.Register(func(env *modules.Env, args ...string) error {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	fmt.Fprintf(env.Stdout, "ready\n")
	modules.WaitForEOF(env.Stdin)
	m.WaitForStop(ctx, make(chan string, 1))
	m.ForceStop(ctx)
	os.Exit(42) // This should not be reached.
	return nil
}, "forceStop")

// TestForceStop verifies that ForceStop causes the child process to exit
// immediately.
func TestForceStop(t *testing.T) {
	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	defer sh.Cleanup(os.Stderr, os.Stderr)
	h, err := sh.Start(nil, forceStop)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	h.Expect("ready")
	err = h.Shutdown(os.Stderr, os.Stderr)
	want := fmt.Sprintf("exit status %d", testForceStopExitCode)
	if err == nil || err.Error() != want {
		t.Errorf("got %v, want %s", err, want)
	}
}

func checkProgress(t *testing.T, ch <-chan v23.Task, progress, goal int32) {
	if want, got := (v23.Task{Progress: progress, Goal: goal}), <-ch; !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected progress: want %+v, got %+v", want, got)
	}
}

func checkNoProgress(t *testing.T, ch <-chan v23.Task) {
	select {
	case p := <-ch:
		t.Errorf("channel expected to be empty, got %+v instead", p)
	default:
	}
}

// TestProgress verifies that the ticker update/track logic works for a single
// tracker.
func TestProgress(t *testing.T) {
	ctx, shutdown := test.V23Init()

	m := v23.GetAppCycle(ctx)
	m.AdvanceGoal(50)
	ch := make(chan v23.Task, 1)
	m.TrackTask(ch)
	checkNoProgress(t, ch)
	m.AdvanceProgress(10)
	checkProgress(t, ch, 10, 50)
	checkNoProgress(t, ch)
	m.AdvanceProgress(5)
	checkProgress(t, ch, 15, 50)
	m.AdvanceGoal(50)
	checkProgress(t, ch, 15, 100)
	m.AdvanceProgress(1)
	checkProgress(t, ch, 16, 100)
	m.AdvanceGoal(10)
	checkProgress(t, ch, 16, 110)
	m.AdvanceProgress(-13)
	checkNoProgress(t, ch)
	m.AdvanceGoal(0)
	checkNoProgress(t, ch)
	shutdown()
	if _, ok := <-ch; ok {
		t.Errorf("Expected channel to be closed")
	}
}

// TestProgressMultipleTrackers verifies that the ticker update/track logic
// works for more than one tracker.  It also ensures that the runtime doesn't
// block when the tracker channels are full.
func TestProgressMultipleTrackers(t *testing.T) {
	ctx, shutdown := test.V23Init()

	m := v23.GetAppCycle(ctx)
	// ch1 is 1-buffered, ch2 is 2-buffered.
	ch1, ch2 := make(chan v23.Task, 1), make(chan v23.Task, 2)
	m.TrackTask(ch1)
	m.TrackTask(ch2)
	checkNoProgress(t, ch1)
	checkNoProgress(t, ch2)
	m.AdvanceProgress(1)
	checkProgress(t, ch1, 1, 0)
	checkNoProgress(t, ch1)
	checkProgress(t, ch2, 1, 0)
	checkNoProgress(t, ch2)
	for i := 0; i < 10; i++ {
		m.AdvanceProgress(1)
	}
	checkProgress(t, ch1, 2, 0)
	checkNoProgress(t, ch1)
	checkProgress(t, ch2, 2, 0)
	checkProgress(t, ch2, 3, 0)
	checkNoProgress(t, ch2)
	m.AdvanceGoal(4)
	checkProgress(t, ch1, 11, 4)
	checkProgress(t, ch2, 11, 4)
	shutdown()
	if _, ok := <-ch1; ok {
		t.Errorf("Expected channel to be closed")
	}
	if _, ok := <-ch2; ok {
		t.Errorf("Expected channel to be closed")
	}
}

var app = modules.Register(func(env *modules.Env, args ...string) error {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	ch := make(chan string, 1)
	m.WaitForStop(ctx, ch)
	fmt.Fprintln(env.Stdout, "ready")
	fmt.Fprintf(env.Stdout, "Got %s\n", <-ch)
	m.AdvanceGoal(10)
	fmt.Fprintf(env.Stdout, "Doing some work\n")
	m.AdvanceProgress(2)
	fmt.Fprintf(env.Stdout, "Doing some more work\n")
	m.AdvanceProgress(5)
	return nil
}, "app")

type configServer struct {
	ch chan<- string
}

func (c *configServer) Set(_ *context.T, _ rpc.ServerCall, key, value string) error {
	if key != mgmt.AppCycleManagerConfigKey {
		return fmt.Errorf("Unexpected key: %v", key)
	}
	c.ch <- value
	return nil

}

// TODO(caprita): Rewrite this test to not use the modules package. It can't use
// v23test because v23test.Shell doesn't support the exec protocol.
func setupRemoteAppCycleMgr(t *testing.T) (*context.T, modules.Handle, appcycle.AppCycleClientMethods, func()) {
	ctx, shutdown := test.V23Init()

	ch := make(chan string)
	service := device.ConfigServer(&configServer{ch})
	authorizer := securityflag.NewAuthorizerOrDie()
	_, configServer, err := v23.WithNewServer(ctx, "", service, authorizer)
	if err != nil {
		t.Fatalf("Got error: %v", err)
	}
	configServiceName := configServer.Status().Endpoints[0].Name()

	sh, err := modules.NewShell(ctx, v23.GetPrincipal(ctx), testing.Verbose(), t)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	sh.SetConfigKey(mgmt.ParentNameConfigKey, configServiceName)
	sh.SetConfigKey(mgmt.ProtocolConfigKey, "tcp")
	sh.SetConfigKey(mgmt.AddressConfigKey, "127.0.0.1:0")
	h, err := sh.Start(nil, app)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	appCycleName := ""
	select {
	case appCycleName = <-ch:
	case <-time.After(time.Minute):
		t.Errorf("timeout")
	}
	appCycle := appcycle.AppCycleClient(appCycleName)
	return ctx, h, appCycle, func() {
		configServer.Stop()
		sh.Cleanup(os.Stderr, os.Stderr)
		shutdown()
	}
}

// TestRemoteForceStop verifies that the child process exits when sending it
// a remote ForceStop rpc.
func TestRemoteForceStop(t *testing.T) {
	ctx, h, appCycle, cleanup := setupRemoteAppCycleMgr(t)
	defer cleanup()
	s := expect.NewSession(t, h.Stdout(), time.Minute)
	s.Expect("ready")
	if err := appCycle.ForceStop(ctx); err == nil || !strings.Contains(err.Error(), "EOF") {
		t.Fatalf("Expected EOF error, got %v instead", err)
	}
	s.ExpectEOF()
	err := h.Shutdown(os.Stderr, os.Stderr)
	want := fmt.Sprintf("exit status %d", testForceStopExitCode)
	if err == nil || err.Error() != want {
		t.Errorf("got %v, want %s", err, want)
	}
}

// TestRemoteStop verifies that the child shuts down cleanly when sending it
// a remote Stop rpc.
func TestRemoteStop(t *testing.T) {
	ctx, h, appCycle, cleanup := setupRemoteAppCycleMgr(t)
	defer cleanup()
	s := expect.NewSession(t, h.Stdout(), time.Minute)
	s.Expect("ready")
	stream, err := appCycle.Stop(ctx)
	if err != nil {
		t.Fatalf("Got error: %v", err)
	}
	rStream := stream.RecvStream()
	expectTask := func(progress, goal int32) {
		if !rStream.Advance() {
			t.Fatalf("unexpected streaming error: %v", rStream.Err())
		}
		task := rStream.Value()
		if task.Progress != progress || task.Goal != goal {
			t.Errorf("Got (%d, %d), want (%d, %d)", task.Progress, task.Goal, progress, goal)
		}
	}
	expectTask(0, 10)
	expectTask(2, 10)
	expectTask(7, 10)
	if rStream.Advance() || rStream.Err() != nil {
		t.Errorf("Expected EOF, got (%v, %v) instead", rStream.Value(), rStream.Err())
	}
	if err := stream.Finish(); err != nil {
		t.Errorf("Got error %v", err)
	}
	s.Expect(fmt.Sprintf("Got %s", v23.RemoteStop))
	s.Expect("Doing some work")
	s.Expect("Doing some more work")
	s.ExpectEOF()
	if err := h.Shutdown(os.Stderr, os.Stderr); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
}
<commit_msg>runtime/internal/rt: remove modules from mgmt_test<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package rt_test

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"reflect"
	"strings"
	"testing"
	"time"

	"v.io/v23"
	"v.io/v23/context"
	"v.io/v23/rpc"
	"v.io/v23/services/appcycle"
	"v.io/x/lib/envvar"
	"v.io/x/lib/gosh"
	"v.io/x/ref"
	vexec "v.io/x/ref/lib/exec"
	"v.io/x/ref/lib/mgmt"
	"v.io/x/ref/lib/security/securityflag"
	"v.io/x/ref/lib/v23test"
	_ "v.io/x/ref/runtime/factories/generic"
	"v.io/x/ref/services/device"
	"v.io/x/ref/test"
	"v.io/x/ref/test/expect"
)

// TestBasic verifies that the basic plumbing works: LocalStop calls result in
// stop messages being sent on the channel passed to WaitForStop.
func TestBasic(t *testing.T) {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	ch := make(chan string, 1)
	m.WaitForStop(ctx, ch)
	for i := 0; i < 10; i++ {
		m.Stop(ctx)
		if want, got := v23.LocalStop, <-ch; want != got {
			t.Errorf("WaitForStop want %q got %q", want, got)
		}
		select {
		case s := <-ch:
			t.Errorf("channel expected to be empty, got %q instead", s)
		default:
		}
	}
}

// TestMultipleWaiters verifies that the plumbing works with more than one
// registered wait channel.
func TestMultipleWaiters(t *testing.T) {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	ch1 := make(chan string, 1)
	m.WaitForStop(ctx, ch1)
	ch2 := make(chan string, 1)
	m.WaitForStop(ctx, ch2)
	for i := 0; i < 10; i++ {
		m.Stop(ctx)
		if want, got := v23.LocalStop, <-ch1; want != got {
			t.Errorf("WaitForStop want %q got %q", want, got)
		}
		if want, got := v23.LocalStop, <-ch2; want != got {
			t.Errorf("WaitForStop want %q got %q", want, got)
		}
	}
}

// TestMultipleStops verifies that LocalStop does not block even if the wait
// channel is not being drained: once the channel's buffer fills up, future
// Stops become no-ops.
func TestMultipleStops(t *testing.T) {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	ch := make(chan string, 1)
	m.WaitForStop(ctx, ch)
	for i := 0; i < 10; i++ {
		m.Stop(ctx)
	}
	if want, got := v23.LocalStop, <-ch; want != got {
		t.Errorf("WaitForStop want %q got %q", want, got)
	}
	select {
	case s := <-ch:
		t.Errorf("channel expected to be empty, got %q instead", s)
	default:
	}
}

func waitForEOF(r io.Reader) {
	io.Copy(ioutil.Discard, r)
}

var noWaiters = gosh.Register("noWaiters", func() {
	ctx, shutdown := test.V23Init()
	defer shutdown()
	v23.GetAppCycle(ctx).Stop(ctx)
	os.Exit(42) // This should not be reached.
})

// TestNoWaiters verifies that the child process exits in the absence of any
// wait channel being registered with its runtime.
func TestNoWaiters(t *testing.T) {
	sh := v23test.NewShell(t, v23test.Opts{})
	defer sh.Cleanup()

	cmd := sh.Fn(noWaiters)
	cmd.ExitErrorIsOk = true

	cmd.Run()
	want := fmt.Sprintf("exit status %d", testUnhandledStopExitCode)
	if err := cmd.Err; err == nil || err.Error() != want {
		t.Errorf("got %v, want %s", err, want)
	}
}

var forceStop = gosh.Register("forceStop", func() {
	ctx, shutdown := test.V23Init()
	defer shutdown()
	m := v23.GetAppCycle(ctx)
	m.WaitForStop(ctx, make(chan string, 1))
	m.ForceStop(ctx)
	os.Exit(42) // This should not be reached.
})

// TestForceStop verifies that ForceStop causes the child process to exit
// immediately.
func TestForceStop(t *testing.T) {
	sh := v23test.NewShell(t, v23test.Opts{})
	defer sh.Cleanup()

	cmd := sh.Fn(forceStop)
	cmd.ExitErrorIsOk = true

	cmd.Run()
	want := fmt.Sprintf("exit status %d", testForceStopExitCode)
	if err := cmd.Err; err == nil || err.Error() != want {
		t.Errorf("got %v, want %s", err, want)
	}
}

func checkProgress(t *testing.T, ch <-chan v23.Task, progress, goal int32) {
	if want, got := (v23.Task{Progress: progress, Goal: goal}), <-ch; !reflect.DeepEqual(want, got) {
		t.Errorf("Unexpected progress: want %+v, got %+v", want, got)
	}
}

func checkNoProgress(t *testing.T, ch <-chan v23.Task) {
	select {
	case p := <-ch:
		t.Errorf("channel expected to be empty, got %+v instead", p)
	default:
	}
}

// TestProgress verifies that the ticker update/track logic works for a single
// tracker.
func TestProgress(t *testing.T) {
	ctx, shutdown := test.V23Init()

	m := v23.GetAppCycle(ctx)
	m.AdvanceGoal(50)
	ch := make(chan v23.Task, 1)
	m.TrackTask(ch)
	checkNoProgress(t, ch)
	m.AdvanceProgress(10)
	checkProgress(t, ch, 10, 50)
	checkNoProgress(t, ch)
	m.AdvanceProgress(5)
	checkProgress(t, ch, 15, 50)
	m.AdvanceGoal(50)
	checkProgress(t, ch, 15, 100)
	m.AdvanceProgress(1)
	checkProgress(t, ch, 16, 100)
	m.AdvanceGoal(10)
	checkProgress(t, ch, 16, 110)
	m.AdvanceProgress(-13)
	checkNoProgress(t, ch)
	m.AdvanceGoal(0)
	checkNoProgress(t, ch)
	shutdown()
	if _, ok := <-ch; ok {
		t.Errorf("Expected channel to be closed")
	}
}

// TestProgressMultipleTrackers verifies that the ticker update/track logic
// works for more than one tracker.  It also ensures that the runtime doesn't
// block when the tracker channels are full.
func TestProgressMultipleTrackers(t *testing.T) {
	ctx, shutdown := test.V23Init()

	m := v23.GetAppCycle(ctx)
	// ch1 is 1-buffered, ch2 is 2-buffered.
	ch1, ch2 := make(chan v23.Task, 1), make(chan v23.Task, 2)
	m.TrackTask(ch1)
	m.TrackTask(ch2)
	checkNoProgress(t, ch1)
	checkNoProgress(t, ch2)
	m.AdvanceProgress(1)
	checkProgress(t, ch1, 1, 0)
	checkNoProgress(t, ch1)
	checkProgress(t, ch2, 1, 0)
	checkNoProgress(t, ch2)
	for i := 0; i < 10; i++ {
		m.AdvanceProgress(1)
	}
	checkProgress(t, ch1, 2, 0)
	checkNoProgress(t, ch1)
	checkProgress(t, ch2, 2, 0)
	checkProgress(t, ch2, 3, 0)
	checkNoProgress(t, ch2)
	m.AdvanceGoal(4)
	checkProgress(t, ch1, 11, 4)
	checkProgress(t, ch2, 11, 4)
	shutdown()
	if _, ok := <-ch1; ok {
		t.Errorf("Expected channel to be closed")
	}
	if _, ok := <-ch2; ok {
		t.Errorf("Expected channel to be closed")
	}
}

var app = gosh.Register("app", func() {
	ctx, shutdown := test.V23Init()
	defer shutdown()

	m := v23.GetAppCycle(ctx)
	ch := make(chan string, 1)
	m.WaitForStop(ctx, ch)
	fmt.Println("ready")
	fmt.Printf("Got %s\n", <-ch)
	m.AdvanceGoal(10)
	fmt.Println("Doing some work")
	m.AdvanceProgress(2)
	fmt.Println("Doing some more work")
	m.AdvanceProgress(5)
})

type configServer struct {
	ch chan<- string
}

func (c *configServer) Set(_ *context.T, _ rpc.ServerCall, key, value string) error {
	if key != mgmt.AppCycleManagerConfigKey {
		return fmt.Errorf("Unexpected key: %v", key)
	}
	c.ch <- value
	return nil

}

func setupRemoteAppCycleMgr(t *testing.T) (*context.T, *vexec.ParentHandle, *expect.Session, appcycle.AppCycleClientMethods, func()) {
	sh := v23test.NewShell(t, v23test.Opts{})
	ctx := sh.Ctx
	failed := true
	defer func() {
		if failed {
			sh.Cleanup()
		}
	}()
	goshCmd := sh.Fn(app)

	ch := make(chan string, 1)
	service := device.ConfigServer(&configServer{ch})
	authorizer := securityflag.NewAuthorizerOrDie()
	_, configServer, err := v23.WithNewServer(ctx, "", service, authorizer)
	if err != nil {
		t.Fatalf("WithNewServer failed: %v", err)
	}
	configServiceName := configServer.Status().Endpoints[0].Name()

	config := vexec.NewConfig()
	config.Set(mgmt.ParentNameConfigKey, configServiceName)
	config.Set(mgmt.ProtocolConfigKey, "tcp")
	config.Set(mgmt.AddressConfigKey, "127.0.0.1:0")
	config.Set(mgmt.SecurityAgentPathConfigKey, goshCmd.Vars[ref.EnvAgentPath])

	cmd := exec.Command(goshCmd.Args[0], goshCmd.Args[1:]...)
	cmd.Env = envvar.MapToSlice(goshCmd.Vars)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		t.Fatalf("StdoutPipe failed: %v", err)
	}
	handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{Config: config})
	if err := handle.Start(); err != nil {
		t.Fatalf("Start failed: %v", err)
	}
	if err := handle.WaitForReady(20 * time.Second); err != nil {
		t.Fatalf("WaitForReady failed: %v", err)
	}

	appCycleName := ""
	select {
	case appCycleName = <-ch:
	case <-time.After(time.Minute):
		t.Errorf("timeout")
	}
	s := expect.NewSession(t, stdout, time.Minute)
	s.Expect("ready")
	appCycle := appcycle.AppCycleClient(appCycleName)
	failed = false
	return ctx, handle, s, appCycle, sh.Cleanup
}

// TestRemoteForceStop verifies that the child process exits when sending it
// a remote ForceStop rpc.
func TestRemoteForceStop(t *testing.T) {
	ctx, h, s, appCycle, cleanup := setupRemoteAppCycleMgr(t)
	defer cleanup()
	if err := appCycle.ForceStop(ctx); err == nil || !strings.Contains(err.Error(), "EOF") {
		t.Fatalf("Expected EOF error, got %v instead", err)
	}
	s.ExpectEOF()
	want := fmt.Sprintf("exit status %d", testForceStopExitCode)
	if err := h.Wait(0); err == nil || err.Error() != want {
		t.Errorf("got %v, want %s", err, want)
	}
}

// TestRemoteStop verifies that the child shuts down cleanly when sending it
// a remote Stop rpc.
func TestRemoteStop(t *testing.T) {
	ctx, h, s, appCycle, cleanup := setupRemoteAppCycleMgr(t)
	defer cleanup()
	stream, err := appCycle.Stop(ctx)
	if err != nil {
		t.Fatalf("Got error: %v", err)
	}
	rStream := stream.RecvStream()
	expectTask := func(progress, goal int32) {
		if !rStream.Advance() {
			t.Fatalf("unexpected streaming error: %v", rStream.Err())
		}
		task := rStream.Value()
		if task.Progress != progress || task.Goal != goal {
			t.Errorf("Got (%d, %d), want (%d, %d)", task.Progress, task.Goal, progress, goal)
		}
	}
	expectTask(0, 10)
	expectTask(2, 10)
	expectTask(7, 10)
	if rStream.Advance() || rStream.Err() != nil {
		t.Errorf("Expected EOF, got (%v, %v) instead", rStream.Value(), rStream.Err())
	}
	if err := stream.Finish(); err != nil {
		t.Errorf("Got error %v", err)
	}
	s.Expect(fmt.Sprintf("Got %s", v23.RemoteStop))
	s.Expect("Doing some work")
	s.Expect("Doing some more work")
	s.ExpectEOF()
	if err := h.Wait(0); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
}
<|endoftext|>


<commit_before>package maptiles

import (
	"fmt"
	"strings"
	"io/ioutil"
	"net/http"
	"errors"
	"time"
	"math/rand"

	"tileserver/mapnik"
	"tileserver/ligneous"

	log "github.com/cihub/seelog"
)

func init() {
	logger, _ := ligneous.InitLogger()
	log.UseLogger(logger)
}

func randomNum(min, max int) int {
    rand.Seed(time.Now().Unix())
    return rand.Intn(max - min) + min
}

type TileCoord struct {
	X, Y, Zoom uint64
	Tms        bool
	Layer      string
}

func (c TileCoord) OSMFilename() string {
	return fmt.Sprintf("%d/%d/%d.png", c.Zoom, c.X, c.Y)
}

type TileFetchResult struct {
	Coord   TileCoord
	BlobPNG []byte
}

type TileFetchRequest struct {
	Coord   TileCoord
	OutChan chan<- TileFetchResult
}

func (c *TileCoord) setTMS(tms bool) {
	if c.Tms != tms {
		c.Y = (1 << c.Zoom) - c.Y - 1
		c.Tms = tms
	}
}

func NewTileRendererChan(stylesheet string) chan<- TileFetchRequest {
	c := make(chan TileFetchRequest)

	go func(requestChan <-chan TileFetchRequest) {
		var err error
		t := NewTileRenderer(stylesheet)
		for request := range requestChan {
			result := TileFetchResult{request.Coord, nil}
			result.BlobPNG, err = t.RenderTile(request.Coord)
			if err != nil {
				log.Error("Error while rendering", request.Coord, ":", err.Error())
				result.BlobPNG = nil
			}
			request.OutChan <- result
		}
	}(c)

	return c
}

// Renders images as Web Mercator tiles
type TileRenderer struct {
	m  *mapnik.Map
	mp mapnik.Projection
	proxy bool
	s string
}

func NewTileRenderer(stylesheet string) *TileRenderer {
	t := new(TileRenderer)
	var err error
	if err != nil {
		log.Critical(err)
	}
	t.m = mapnik.NewMap(256, 256)
	t.m.Load(stylesheet)
	t.mp = t.m.Projection()

	if strings.Contains(stylesheet, ".xml") {
		t.proxy = false
		t.s = stylesheet
	} else if strings.Contains(stylesheet, "http") && strings.Contains(stylesheet, "{z}/{x}/{y}.png") {
		t.proxy = true
		t.s = stylesheet
	}

	return t
}

func (t *TileRenderer) RenderTile(c TileCoord) ([]byte, error) {
	c.setTMS(false)
	if t.proxy {
		return t.HttpGetTileZXY(c.Zoom, c.X, c.Y)
	} else {
		return t.RenderTileZXY(c.Zoom, c.X, c.Y)
	}
}

// Render a tile with coordinates in Google tile format.
// Most upper left tile is always 0,0. Method is not thread-safe,
// so wrap with a mutex when accessing the same renderer by multiple
// threads or setup multiple goroutinesand communicate with channels,
// see NewTileRendererChan.
func (t *TileRenderer) RenderTileZXY(zoom, x, y uint64) ([]byte, error) {
	// Calculate pixel positions of bottom left & top right
	p0 := [2]float64{float64(x) * 256, (float64(y) + 1) * 256}
	p1 := [2]float64{(float64(x) + 1) * 256, float64(y) * 256}

	// Convert to LatLong(EPSG:4326)
	l0 := fromPixelToLL(p0, zoom)
	l1 := fromPixelToLL(p1, zoom)

	// Convert to map projection (e.g. mercartor co-ords EPSG:3857)
	c0 := t.mp.Forward(mapnik.Coord{l0[0], l0[1]})
	c1 := t.mp.Forward(mapnik.Coord{l1[0], l1[1]})

	// Bounding box for the Tile
	t.m.Resize(256, 256)
	t.m.ZoomToMinMax(c0.X, c0.Y, c1.X, c1.Y)
	t.m.SetBufferSize(128)

	blob, err := t.m.RenderToMemoryPng()

	log.Debug( fmt.Sprintf("RENDER BLOB %v %v %v %v", t.s zoom, x, y) )

	return blob, err
}

func (t *TileRenderer) subDomain() string {
	subs := []string{"a","b","c"}
	n := randomNum(0,3);
	return subs[n]
}

func (t *TileRenderer) HttpGetTileZXY(zoom, x, y uint64) ([]byte, error) {
	tileUrl := strings.Replace(t.s, "{z}", fmt.Sprintf("%v", zoom), -1);
	tileUrl = strings.Replace(tileUrl, "{x}", fmt.Sprintf("%v", x), -1);
	tileUrl = strings.Replace(tileUrl, "{y}", fmt.Sprintf("%v", y), -1);
	tileUrl = strings.Replace(tileUrl, "{s}", t.subDomain(), -1);

	resp, err := http.Get(tileUrl)
	blob, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()

	log.Debug( fmt.Sprintf("PROXY GET %v %v", tileUrl, resp.StatusCode) )

	if 200 != resp.StatusCode {
		err := errors.New("Request error: "+ string(blob))
		return []byte{}, err
	}

	return blob, err
}
<commit_msg>logging fix<commit_after>package maptiles

import (
	"fmt"
	"strings"
	"io/ioutil"
	"net/http"
	"errors"
	"time"
	"math/rand"

	"tileserver/mapnik"
	"tileserver/ligneous"

	log "github.com/cihub/seelog"
)

func init() {
	logger, _ := ligneous.InitLogger()
	log.UseLogger(logger)
}

func randomNum(min, max int) int {
    rand.Seed(time.Now().Unix())
    return rand.Intn(max - min) + min
}

type TileCoord struct {
	X, Y, Zoom uint64
	Tms        bool
	Layer      string
}

func (c TileCoord) OSMFilename() string {
	return fmt.Sprintf("%d/%d/%d.png", c.Zoom, c.X, c.Y)
}

type TileFetchResult struct {
	Coord   TileCoord
	BlobPNG []byte
}

type TileFetchRequest struct {
	Coord   TileCoord
	OutChan chan<- TileFetchResult
}

func (c *TileCoord) setTMS(tms bool) {
	if c.Tms != tms {
		c.Y = (1 << c.Zoom) - c.Y - 1
		c.Tms = tms
	}
}

func NewTileRendererChan(stylesheet string) chan<- TileFetchRequest {
	c := make(chan TileFetchRequest)

	go func(requestChan <-chan TileFetchRequest) {
		var err error
		t := NewTileRenderer(stylesheet)
		for request := range requestChan {
			result := TileFetchResult{request.Coord, nil}
			result.BlobPNG, err = t.RenderTile(request.Coord)
			if err != nil {
				log.Error("Error while rendering", request.Coord, ":", err.Error())
				result.BlobPNG = nil
			}
			request.OutChan <- result
		}
	}(c)

	return c
}

// Renders images as Web Mercator tiles
type TileRenderer struct {
	m  *mapnik.Map
	mp mapnik.Projection
	proxy bool
	s string
}

func NewTileRenderer(stylesheet string) *TileRenderer {
	t := new(TileRenderer)
	var err error
	if err != nil {
		log.Critical(err)
	}
	t.m = mapnik.NewMap(256, 256)
	t.m.Load(stylesheet)
	t.mp = t.m.Projection()

	if strings.Contains(stylesheet, ".xml") {
		t.proxy = false
		t.s = stylesheet
	} else if strings.Contains(stylesheet, "http") && strings.Contains(stylesheet, "{z}/{x}/{y}.png") {
		t.proxy = true
		t.s = stylesheet
	}

	return t
}

func (t *TileRenderer) RenderTile(c TileCoord) ([]byte, error) {
	c.setTMS(false)
	if t.proxy {
		return t.HttpGetTileZXY(c.Zoom, c.X, c.Y)
	} else {
		return t.RenderTileZXY(c.Zoom, c.X, c.Y)
	}
}

// Render a tile with coordinates in Google tile format.
// Most upper left tile is always 0,0. Method is not thread-safe,
// so wrap with a mutex when accessing the same renderer by multiple
// threads or setup multiple goroutinesand communicate with channels,
// see NewTileRendererChan.
func (t *TileRenderer) RenderTileZXY(zoom, x, y uint64) ([]byte, error) {
	// Calculate pixel positions of bottom left & top right
	p0 := [2]float64{float64(x) * 256, (float64(y) + 1) * 256}
	p1 := [2]float64{(float64(x) + 1) * 256, float64(y) * 256}

	// Convert to LatLong(EPSG:4326)
	l0 := fromPixelToLL(p0, zoom)
	l1 := fromPixelToLL(p1, zoom)

	// Convert to map projection (e.g. mercartor co-ords EPSG:3857)
	c0 := t.mp.Forward(mapnik.Coord{l0[0], l0[1]})
	c1 := t.mp.Forward(mapnik.Coord{l1[0], l1[1]})

	// Bounding box for the Tile
	t.m.Resize(256, 256)
	t.m.ZoomToMinMax(c0.X, c0.Y, c1.X, c1.Y)
	t.m.SetBufferSize(128)

	blob, err := t.m.RenderToMemoryPng()

	log.Debug( fmt.Sprintf("RENDER BLOB %v %v %v %v", t.s, zoom, x, y) )

	return blob, err
}

func (t *TileRenderer) subDomain() string {
	subs := []string{"a","b","c"}
	n := randomNum(0,3);
	return subs[n]
}

func (t *TileRenderer) HttpGetTileZXY(zoom, x, y uint64) ([]byte, error) {
	tileUrl := strings.Replace(t.s, "{z}", fmt.Sprintf("%v", zoom), -1);
	tileUrl = strings.Replace(tileUrl, "{x}", fmt.Sprintf("%v", x), -1);
	tileUrl = strings.Replace(tileUrl, "{y}", fmt.Sprintf("%v", y), -1);
	tileUrl = strings.Replace(tileUrl, "{s}", t.subDomain(), -1);

	resp, err := http.Get(tileUrl)
	blob, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()

	log.Debug( fmt.Sprintf("PROXY GET %v %v", tileUrl, resp.StatusCode) )

	if 200 != resp.StatusCode {
		err := errors.New("Request error: "+ string(blob))
		return []byte{}, err
	}

	return blob, err
}
<|endoftext|>


<commit_before>// Package ostype is define OS type of SakuraCloud public archive
package ostype

//go:generate stringer -type=ArchiveOSTypes

// ArchiveOSTypes パブリックアーカイブOS種別
type ArchiveOSTypes int

const (
	// CentOS OS種別:CentOS
	CentOS ArchiveOSTypes = iota
	// Ubuntu OS種別:Ubuntu
	Ubuntu
	// Debian OS種別:Debian
	Debian
	// VyOS OS種別:VyOS
	VyOS
	// CoreOS OS種別:CoreOS
	CoreOS
	// RancherOS OS種別:RancherOS
	RancherOS
	// Kusanagi OS種別:Kusanagi(CentOS)
	Kusanagi
	// SiteGuard OS種別:SiteGuard(CentOS)
	SiteGuard
	// Plesk OS種別:Plesk(CentOS)
	Plesk
	// FreeBSD OS種別:FreeBSD
	FreeBSD
	// Windows2012 OS種別:Windows Server 2012 R2 Datacenter Edition
	Windows2012
	// Windows2012RDS OS種別:Windows Server 2012 R2 for RDS
	Windows2012RDS
	// Windows2012RDSOffice OS種別:Windows Server 2012 R2 for RDS(Office)
	Windows2012RDSOffice
	// Windows2016 OS種別:Windows Server 2016 Datacenter Edition
	Windows2016
	// Windows2016RDS OS種別:Windows Server 2016 RDS
	Windows2016RDS
	// Windows2016RDSOffice OS種別:Windows Server 2016 RDS(Office)
	Windows2016RDSOffice
	// Windows2016SQLServerWeb OS種別:Windows Server 2016 SQLServer(Web)
	Windows2016SQLServerWeb
	// Windows2016SQLServerStandard OS種別:Windows Server 2016 SQLServer(Standard)
	Windows2016SQLServerStandard
	// Custom OS種別:カスタム
	Custom
)

// IsWindows Windowsか
func (o ArchiveOSTypes) IsWindows() bool {
	switch o {
	case Windows2012, Windows2012RDS, Windows2012RDSOffice,
		Windows2016, Windows2016RDS, Windows2016RDSOffice,
		Windows2016SQLServerWeb, Windows2016SQLServerStandard:
		return true
	default:
		return false
	}
}

// IsSupportDiskEdit ディスクの修正機能をフルサポートしているか(Windowsは一部サポートのためfalseを返す)
func (o ArchiveOSTypes) IsSupportDiskEdit() bool {
	switch o {
	case CentOS, Ubuntu, Debian, VyOS, CoreOS, RancherOS, Kusanagi, SiteGuard, Plesk, FreeBSD:
		return true
	default:
		return false
	}
}
<commit_msg>Add StrToOSType func<commit_after>// Package ostype is define OS type of SakuraCloud public archive
package ostype

//go:generate stringer -type=ArchiveOSTypes

// ArchiveOSTypes パブリックアーカイブOS種別
type ArchiveOSTypes int

const (
	// CentOS OS種別:CentOS
	CentOS ArchiveOSTypes = iota
	// Ubuntu OS種別:Ubuntu
	Ubuntu
	// Debian OS種別:Debian
	Debian
	// VyOS OS種別:VyOS
	VyOS
	// CoreOS OS種別:CoreOS
	CoreOS
	// RancherOS OS種別:RancherOS
	RancherOS
	// Kusanagi OS種別:Kusanagi(CentOS)
	Kusanagi
	// SiteGuard OS種別:SiteGuard(CentOS)
	SiteGuard
	// Plesk OS種別:Plesk(CentOS)
	Plesk
	// FreeBSD OS種別:FreeBSD
	FreeBSD
	// Windows2012 OS種別:Windows Server 2012 R2 Datacenter Edition
	Windows2012
	// Windows2012RDS OS種別:Windows Server 2012 R2 for RDS
	Windows2012RDS
	// Windows2012RDSOffice OS種別:Windows Server 2012 R2 for RDS(Office)
	Windows2012RDSOffice
	// Windows2016 OS種別:Windows Server 2016 Datacenter Edition
	Windows2016
	// Windows2016RDS OS種別:Windows Server 2016 RDS
	Windows2016RDS
	// Windows2016RDSOffice OS種別:Windows Server 2016 RDS(Office)
	Windows2016RDSOffice
	// Windows2016SQLServerWeb OS種別:Windows Server 2016 SQLServer(Web)
	Windows2016SQLServerWeb
	// Windows2016SQLServerStandard OS種別:Windows Server 2016 SQLServer(Standard)
	Windows2016SQLServerStandard
	// Custom OS種別:カスタム
	Custom
)

// IsWindows Windowsか
func (o ArchiveOSTypes) IsWindows() bool {
	switch o {
	case Windows2012, Windows2012RDS, Windows2012RDSOffice,
		Windows2016, Windows2016RDS, Windows2016RDSOffice,
		Windows2016SQLServerWeb, Windows2016SQLServerStandard:
		return true
	default:
		return false
	}
}

// IsSupportDiskEdit ディスクの修正機能をフルサポートしているか(Windowsは一部サポートのためfalseを返す)
func (o ArchiveOSTypes) IsSupportDiskEdit() bool {
	switch o {
	case CentOS, Ubuntu, Debian, VyOS, CoreOS, RancherOS, Kusanagi, SiteGuard, Plesk, FreeBSD:
		return true
	default:
		return false
	}
}

func StrToOSType(osType string) ArchiveOSTypes {
	switch osType {
	case "centos":
		return CentOS
	case "ubuntu":
		return Ubuntu
	case "debian":
		return Debian
	case "vyos":
		return VyOS
	case "coreos":
		return CoreOS
	case "rancheros":
		return RancherOS
	case "kusanagi":
		return Kusanagi
	case "site-guard":
		return SiteGuard
	case "plesk":
		return Plesk
	case "freebsd":
		return FreeBSD
	case "windows2012":
		return Windows2012
	case "windows2012-rds":
		return Windows2012RDS
	case "windows2012-rds-office":
		return Windows2012RDSOffice
	case "windows2016":
		return Windows2016
	case "windows2016-rds":
		return Windows2016RDS
	case "windows2016-rds-office":
		return Windows2016RDSOffice
	case "windows2016-sql-web":
		return Windows2016SQLServerWeb
	case "windows2016-sql-standard":
		return Windows2016SQLServerStandard
	default:
		return Custom
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 flushfs_test

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path"
	"sync"
	"testing"

	"github.com/jacobsa/fuse/samples"
	"github.com/jacobsa/fuse/samples/flushfs"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestFlushFS(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type FlushFSTest struct {
	samples.SampleTest

	mu sync.Mutex

	// GUARDED_BY(mu)
	flushes  []string
	flushErr error

	// GUARDED_BY(mu)
	fsyncs   []string
	fsyncErr error
}

func init() { RegisterTestSuite(&FlushFSTest{}) }

func (t *FlushFSTest) SetUp(ti *TestInfo) {
	var err error

	// Set up a file system.
	reportTo := func(slice *[]string, err *error) func(string) error {
		return func(s string) error {
			t.mu.Lock()
			defer t.mu.Unlock()

			*slice = append(*slice, s)
			return *err
		}
	}

	t.FileSystem, err = flushfs.NewFileSystem(
		reportTo(&t.flushes, &t.flushErr),
		reportTo(&t.fsyncs, &t.fsyncErr))

	if err != nil {
		panic(err)
	}

	// Mount it.
	t.SampleTest.SetUp(ti)
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Match byte slices equal to the supplied string.
func byteSliceEq(expected string) Matcher {
	pred := func(c interface{}) error {
		slice, ok := c.([]byte)
		if !ok {
			return errors.New("which is not []byte")
		}

		if string(slice) != expected {
			return fmt.Errorf("which is string \"%s\"", string(slice))
		}

		return nil
	}

	return NewMatcher(
		pred,
		fmt.Sprintf("byte slice equal to string \"%s\"", expected))
}

// Return a copy of the current contents of t.flushes.
//
// LOCKS_EXCLUDED(t.mu)
func (t *FlushFSTest) getFlushes() (p []string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	p = make([]string, len(t.flushes))
	copy(p, t.flushes)
	return
}

// Return a copy of the current contents of t.fsyncs.
//
// LOCKS_EXCLUDED(t.mu)
func (t *FlushFSTest) getFsyncs() (p []string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	p = make([]string, len(t.fsyncs))
	copy(p, t.fsyncs)
	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *FlushFSTest) CloseReports_ReadWrite() {
	var n int
	var off int64
	var err error
	buf := make([]byte, 1024)

	// Open the file.
	f, err := os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDWR, 0)
	AssertEq(nil, err)

	defer func() {
		if f != nil {
			ExpectEq(nil, f.Close())
		}
	}()

	// Write some contents to the file.
	n, err = f.Write([]byte("taco"))
	AssertEq(nil, err)
	AssertEq(4, n)

	// Seek and read them back.
	off, err = f.Seek(0, 0)
	AssertEq(nil, err)
	AssertEq(0, off)

	n, err = f.Read(buf)
	AssertThat(err, AnyOf(nil, io.EOF))
	AssertEq("taco", string(buf[:n]))

	// At this point, no flushes or fsyncs should have happened.
	AssertThat(t.getFlushes(), ElementsAre())
	AssertThat(t.getFsyncs(), ElementsAre())

	// Close the file.
	err = f.Close()
	f = nil
	AssertEq(nil, err)

	// Now we should have received the flush operation (but still no fsync).
	ExpectThat(t.getFlushes(), ElementsAre(byteSliceEq("taco")))
	ExpectThat(t.getFsyncs(), ElementsAre())
}

func (t *FlushFSTest) CloseReports_ReadOnly() {
	var err error

	// Open the file.
	f, err := os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDONLY, 0)
	AssertEq(nil, err)

	defer func() {
		if f != nil {
			ExpectEq(nil, f.Close())
		}
	}()

	// At this point, no flushes or fsyncs should have happened.
	AssertThat(t.getFlushes(), ElementsAre())
	AssertThat(t.getFsyncs(), ElementsAre())

	// Close the file.
	err = f.Close()
	f = nil
	AssertEq(nil, err)

	// Now we should have received the flush operation (but still no fsync).
	ExpectThat(t.getFlushes(), ElementsAre(byteSliceEq("")))
	ExpectThat(t.getFsyncs(), ElementsAre())
}

func (t *FlushFSTest) CloseReports_WriteOnly() {
	var n int
	var err error

	// Open the file.
	f, err := os.OpenFile(path.Join(t.Dir, "foo"), os.O_WRONLY, 0)
	AssertEq(nil, err)

	defer func() {
		if f != nil {
			ExpectEq(nil, f.Close())
		}
	}()

	// Write some contents to the file.
	n, err = f.Write([]byte("taco"))
	AssertEq(nil, err)
	AssertEq(4, n)

	// At this point, no flushes or fsyncs should have happened.
	AssertThat(t.getFlushes(), ElementsAre())
	AssertThat(t.getFsyncs(), ElementsAre())

	// Close the file.
	err = f.Close()
	f = nil
	AssertEq(nil, err)

	// Now we should have received the flush operation (but still no fsync).
	ExpectThat(t.getFlushes(), ElementsAre(byteSliceEq("taco")))
	ExpectThat(t.getFsyncs(), ElementsAre())
}

func (t *FlushFSTest) CloseReports_MultipleTimes_NonOverlappingFileHandles() {
	AssertTrue(false, "TODO")
}

func (t *FlushFSTest) CloseReports_MultipleTimes_OverlappingFileHandles() {
	AssertTrue(false, "TODO")
}

func (t *FlushFSTest) CloseError() {
	AssertTrue(false, "TODO")
}

func (t *FlushFSTest) FsyncReports() {
	AssertTrue(false, "TODO")
}

func (t *FlushFSTest) FsyncError() {
	AssertTrue(false, "TODO")
}
<commit_msg>FlushFSTest.CloseReports_MultipleTimes_NonOverlappingFileHandles<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 flushfs_test

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path"
	"sync"
	"testing"

	"github.com/jacobsa/fuse/samples"
	"github.com/jacobsa/fuse/samples/flushfs"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestFlushFS(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type FlushFSTest struct {
	samples.SampleTest

	mu sync.Mutex

	// GUARDED_BY(mu)
	flushes  []string
	flushErr error

	// GUARDED_BY(mu)
	fsyncs   []string
	fsyncErr error
}

func init() { RegisterTestSuite(&FlushFSTest{}) }

func (t *FlushFSTest) SetUp(ti *TestInfo) {
	var err error

	// Set up a file system.
	reportTo := func(slice *[]string, err *error) func(string) error {
		return func(s string) error {
			t.mu.Lock()
			defer t.mu.Unlock()

			*slice = append(*slice, s)
			return *err
		}
	}

	t.FileSystem, err = flushfs.NewFileSystem(
		reportTo(&t.flushes, &t.flushErr),
		reportTo(&t.fsyncs, &t.fsyncErr))

	if err != nil {
		panic(err)
	}

	// Mount it.
	t.SampleTest.SetUp(ti)
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Match byte slices equal to the supplied string.
func byteSliceEq(expected string) Matcher {
	pred := func(c interface{}) error {
		slice, ok := c.([]byte)
		if !ok {
			return errors.New("which is not []byte")
		}

		if string(slice) != expected {
			return fmt.Errorf("which is string \"%s\"", string(slice))
		}

		return nil
	}

	return NewMatcher(
		pred,
		fmt.Sprintf("byte slice equal to string \"%s\"", expected))
}

// Return a copy of the current contents of t.flushes.
//
// LOCKS_EXCLUDED(t.mu)
func (t *FlushFSTest) getFlushes() (p []string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	p = make([]string, len(t.flushes))
	copy(p, t.flushes)
	return
}

// Return a copy of the current contents of t.fsyncs.
//
// LOCKS_EXCLUDED(t.mu)
func (t *FlushFSTest) getFsyncs() (p []string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	p = make([]string, len(t.fsyncs))
	copy(p, t.fsyncs)
	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *FlushFSTest) CloseReports_ReadWrite() {
	var n int
	var off int64
	var err error
	buf := make([]byte, 1024)

	// Open the file.
	f, err := os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDWR, 0)
	AssertEq(nil, err)

	defer func() {
		if f != nil {
			ExpectEq(nil, f.Close())
		}
	}()

	// Write some contents to the file.
	n, err = f.Write([]byte("taco"))
	AssertEq(nil, err)
	AssertEq(4, n)

	// Seek and read them back.
	off, err = f.Seek(0, 0)
	AssertEq(nil, err)
	AssertEq(0, off)

	n, err = f.Read(buf)
	AssertThat(err, AnyOf(nil, io.EOF))
	AssertEq("taco", string(buf[:n]))

	// At this point, no flushes or fsyncs should have happened.
	AssertThat(t.getFlushes(), ElementsAre())
	AssertThat(t.getFsyncs(), ElementsAre())

	// Close the file.
	err = f.Close()
	f = nil
	AssertEq(nil, err)

	// Now we should have received the flush operation (but still no fsync).
	ExpectThat(t.getFlushes(), ElementsAre(byteSliceEq("taco")))
	ExpectThat(t.getFsyncs(), ElementsAre())
}

func (t *FlushFSTest) CloseReports_ReadOnly() {
	var err error

	// Open the file.
	f, err := os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDONLY, 0)
	AssertEq(nil, err)

	defer func() {
		if f != nil {
			ExpectEq(nil, f.Close())
		}
	}()

	// At this point, no flushes or fsyncs should have happened.
	AssertThat(t.getFlushes(), ElementsAre())
	AssertThat(t.getFsyncs(), ElementsAre())

	// Close the file.
	err = f.Close()
	f = nil
	AssertEq(nil, err)

	// Now we should have received the flush operation (but still no fsync).
	ExpectThat(t.getFlushes(), ElementsAre(byteSliceEq("")))
	ExpectThat(t.getFsyncs(), ElementsAre())
}

func (t *FlushFSTest) CloseReports_WriteOnly() {
	var n int
	var err error

	// Open the file.
	f, err := os.OpenFile(path.Join(t.Dir, "foo"), os.O_WRONLY, 0)
	AssertEq(nil, err)

	defer func() {
		if f != nil {
			ExpectEq(nil, f.Close())
		}
	}()

	// Write some contents to the file.
	n, err = f.Write([]byte("taco"))
	AssertEq(nil, err)
	AssertEq(4, n)

	// At this point, no flushes or fsyncs should have happened.
	AssertThat(t.getFlushes(), ElementsAre())
	AssertThat(t.getFsyncs(), ElementsAre())

	// Close the file.
	err = f.Close()
	f = nil
	AssertEq(nil, err)

	// Now we should have received the flush operation (but still no fsync).
	ExpectThat(t.getFlushes(), ElementsAre(byteSliceEq("taco")))
	ExpectThat(t.getFsyncs(), ElementsAre())
}

func (t *FlushFSTest) CloseReports_MultipleTimes_NonOverlappingFileHandles() {
	var n int
	var err error

	// Open the file.
	f, err := os.OpenFile(path.Join(t.Dir, "foo"), os.O_WRONLY, 0)
	AssertEq(nil, err)

	defer func() {
		if f != nil {
			ExpectEq(nil, f.Close())
		}
	}()

	// Write some contents to the file.
	n, err = f.Write([]byte("taco"))
	AssertEq(nil, err)
	AssertEq(4, n)

	// At this point, no flushes or fsyncs should have happened.
	AssertThat(t.getFlushes(), ElementsAre())
	AssertThat(t.getFsyncs(), ElementsAre())

	// Close the file.
	err = f.Close()
	f = nil
	AssertEq(nil, err)

	// Now we should have received the flush operation (but still no fsync).
	AssertThat(t.getFlushes(), ElementsAre(byteSliceEq("taco")))
	AssertThat(t.getFsyncs(), ElementsAre())

	// Open the file again.
	f, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_WRONLY, 0)
	AssertEq(nil, err)

	// Write again; expect no further flushes.
	n, err = f.Write([]byte("p"))
	AssertEq(nil, err)
	AssertEq(1, n)

	AssertThat(t.getFlushes(), ElementsAre(byteSliceEq("taco")))
	AssertThat(t.getFsyncs(), ElementsAre())

	// Close the file. Now the new contents should be flushed.
	err = f.Close()
	f = nil
	AssertEq(nil, err)

	AssertThat(t.getFlushes(), ElementsAre(byteSliceEq("taco"), byteSliceEq("paco")))
	AssertThat(t.getFsyncs(), ElementsAre())
}

func (t *FlushFSTest) CloseReports_MultipleTimes_OverlappingFileHandles() {
	AssertTrue(false, "TODO")
}

func (t *FlushFSTest) CloseError() {
	AssertTrue(false, "TODO")
}

func (t *FlushFSTest) FsyncReports() {
	AssertTrue(false, "TODO")
}

func (t *FlushFSTest) FsyncError() {
	AssertTrue(false, "TODO")
}
<|endoftext|>


<commit_before>package lechan

import (
	"time"

	"sync/atomic"
)

type Lechan struct {
	size   int32
	items  int32
	queue  chan interface{}
	input  chan interface{}
	output chan interface{}
	close  chan struct{}
}

func New(size int) *Lechan {
	ch := &Lechan{
		size:  int32(size),
		queue: make(chan interface{}, size),
		close: make(chan struct{}, 1),
	}
	go ch.run()
	return ch
}

func (ch *Lechan) Put(value interface{}) {
	ch.queue <- value
	atomic.AddInt32(&ch.items, 1)
}

func (ch *Lechan) TryPut(value interface{}) bool {
	if atomic.LoadInt32(&ch.items) == ch.size {
		return false
	}
	ch.queue <- value
	atomic.AddInt32(&ch.items, 1)
	return true
}

func (ch *Lechan) TryCountedPut(value interface{}, count int) bool {
	for i := 0; i < count; i++ {
		if ch.TryPut(value) {
			return true
		}
	}
	return false
}

func (ch *Lechan) TryTimedPut(value interface{}, duration time.Duration) bool {
	for {
		select {
		case <-time.After(duration):
			return false
		default:
			if ch.TryPut(value) {
				return true
			}
		}
	}
}

func (ch *Lechan) TryLimitedPut(value interface{}, count int, delay time.Duration) bool {
	for i := 0; i < count; i++ {
		if ch.TryTimedPut(value, delay) {
			return true
		}
	}
	return false
}

func (ch *Lechan) Get() (value interface{}, ok bool) {
	if atomic.LoadInt32(&ch.items) == 0 {
		return nil, false
	}
	value = <-ch.queue
	atomic.AddInt32(&ch.items, -1)
	return value, true
}

func (ch *Lechan) In() chan<- interface{} {
	return ch.input
}

func (ch *Lechan) Out() <-chan interface{} {
	return ch.output
}

func (ch *Lechan) Close() {
	ch.close <- struct{}{}
}

func (ch *Lechan) run() {
	var next interface{}

	for {
		select {
		case ch.output <- next:

		default:
			select {
			default:
				tmp, ok := ch.Get()
				if ok {
					next = tmp
				}

			case value := <-ch.input:
				ch.TryPut(value)

			case <-ch.close:
				close(ch.input)
				close(ch.output)
				close(ch.queue)
				close(ch.close)
				return
			}
		}
	}
}
<commit_msg>lechan: fix<commit_after>package lechan

import (
	"time"

	"sync/atomic"
)

type Lechan struct {
	size   int32
	items  int32
	queue  chan interface{}
	input  chan interface{}
	output chan interface{}
	close  chan struct{}
}

func New(size int) *Lechan {
	ch := &Lechan{
		size:  int32(size),
		queue: make(chan interface{}, size),
		close: make(chan struct{}, 1),
	}
	go ch.run()
	return ch
}

func (ch *Lechan) Put(value interface{}) {
	ch.queue <- value
	atomic.AddInt32(&ch.items, 1)
}

func (ch *Lechan) TryPut(value interface{}) bool {
	if atomic.LoadInt32(&ch.items) == ch.size {
		return false
	}
	ch.queue <- value
	atomic.AddInt32(&ch.items, 1)
	return true
}

func (ch *Lechan) TryCountedPut(value interface{}, count int) bool {
	for i := 0; i < count; i++ {
		if ch.TryPut(value) {
			return true
		}
	}
	return false
}

func (ch *Lechan) TryTimedPut(value interface{}, duration time.Duration) bool {
	for {
		select {
		case <-time.After(duration):
			return false
		default:
			if ch.TryPut(value) {
				return true
			}
		}
	}
}

func (ch *Lechan) TryLimitedPut(value interface{}, count int, delay time.Duration) bool {
	for i := 0; i < count; i++ {
		if ch.TryTimedPut(value, delay) {
			return true
		}
	}
	return false
}

func (ch *Lechan) Get() (value interface{}, ok bool) {
	if atomic.LoadInt32(&ch.items) == 0 {
		return nil, false
	}
	value = <-ch.queue
	atomic.AddInt32(&ch.items, -1)
	return value, true
}

func (ch *Lechan) In() chan<- interface{} {
	return ch.input
}

func (ch *Lechan) Out() <-chan interface{} {
	return ch.output
}

func (ch *Lechan) Close() {
	ch.close <- struct{}{}
}

func (ch *Lechan) run() {
	var next interface{}

	for {
		select {
		case ch.output <- next:

		default:
			select {
			case next = <-ch.queue:
				atomic.AddInt32(&ch.items, -1)

			default:
				select {
				case value := <-ch.input:
					ch.TryPut(value)

				case <-ch.close:
					close(ch.input)
					close(ch.output)
					close(ch.queue)
					close(ch.close)
					return
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package blackjack

import (
	"fmt"
	"github.com/jkomoros/boardgame"
)

type Suit string

const (
	SuitSpades   Suit = "Spades"
	SuitHearts        = "Hearts"
	SuitClubs         = "Clubs"
	SuitDiamonds      = "Diamonds"
	SuitJokers        = "Jokers"
)

type Rank int

const (
	RankJoker Rank = iota
	RankAce
	Rank2
	Rank3
	Rank4
	Rank5
	Rank6
	Rank7
	Rank8
	Rank9
	Rank10
	RankJack
	RankQueen
	RankKing
)

type Card struct {
	Suit Suit
	Rank Rank
}

func (c *Card) Props() []string {
	return boardgame.PropertyReaderPropsImpl(c)
}

func (c *Card) Prop(name string) interface{} {
	return boardgame.PropertyReaderPropImpl(c, name)
}

func (c *Card) String() string {
	return fmt.Sprintf("%s %d", c.Suit, c.Rank)
}

//Designed to be used with stack.ComponentValues()
func cards(in []boardgame.PropertyReader) []*Card {
	result := make([]*Card, len(in))
	for i := 0; i < len(in); i++ {
		c := in[i]
		if c == nil {
			result[i] = nil
			continue
		}
		result[i] = c.(*Card)
	}
	return result
}
<commit_msg>Switch the card String constants to be the unicode suit characters. Part of #16.<commit_after>package blackjack

import (
	"fmt"
	"github.com/jkomoros/boardgame"
)

type Suit string

const (
	SuitSpades   Suit = "\u2660"
	SuitHearts        = "\u2665"
	SuitClubs         = "\u2663"
	SuitDiamonds      = "\u2666"
	SuitJokers        = "Jokers"
)

type Rank int

const (
	RankJoker Rank = iota
	RankAce
	Rank2
	Rank3
	Rank4
	Rank5
	Rank6
	Rank7
	Rank8
	Rank9
	Rank10
	RankJack
	RankQueen
	RankKing
)

type Card struct {
	Suit Suit
	Rank Rank
}

func (c *Card) Props() []string {
	return boardgame.PropertyReaderPropsImpl(c)
}

func (c *Card) Prop(name string) interface{} {
	return boardgame.PropertyReaderPropImpl(c, name)
}

func (c *Card) String() string {
	return fmt.Sprintf("%s %d", c.Suit, c.Rank)
}

//Designed to be used with stack.ComponentValues()
func cards(in []boardgame.PropertyReader) []*Card {
	result := make([]*Card, len(in))
	for i := 0; i < len(in); i++ {
		c := in[i]
		if c == nil {
			result[i] = nil
			continue
		}
		result[i] = c.(*Card)
	}
	return result
}
<|endoftext|>


<commit_before>/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package main

import (
	"github.com/vmware/harbor/api"
	"github.com/vmware/harbor/controllers"
	"github.com/vmware/harbor/service"
	"github.com/vmware/harbor/service/token"

	"github.com/astaxie/beego"
)

func initRouters() {

	beego.SetStaticPath("registry/static/i18n", "static/i18n")
	beego.SetStaticPath("registry/static/resources", "static/resources")
	beego.SetStaticPath("registry/static/vendors", "static/vendors")

	beego.Router("/login", &controllers.CommonController{}, "post:Login")
	beego.Router("/logout", &controllers.CommonController{}, "get:Logout")
	beego.Router("/language", &controllers.CommonController{}, "get:SwitchLanguage")
	beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists")
	beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword")
	beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendEmail")

	beego.Router("/", &controllers.IndexController{})
	beego.Router("/signIn", &controllers.SignInController{})
	beego.Router("/register", &controllers.RegisterController{})
	beego.Router("/addUser", &controllers.AddUserController{})
	beego.Router("/forgotPassword", &controllers.ForgotPasswordController{})
	beego.Router("/resetPassword", &controllers.ResetPasswordController{})
	beego.Router("/changePassword", &controllers.ChangePasswordController{})

	beego.Router("/registry/project", &controllers.ProjectController{})
	beego.Router("/registry/detail", &controllers.ItemDetailController{})

	beego.Router("/search", &controllers.SearchController{})

	//API:
	beego.Router("/api/search", &api.SearchAPI{})
	beego.Router("/api/projects/:pid/members/?:mid", &api.ProjectMemberAPI{})
	beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List")
	beego.Router("/api/projects/?:id", &api.ProjectAPI{})
	beego.Router("/api/projects/:id/toggle_project_public", &api.ProjectAPI{}, "post:ToggleProjectPublic")
	beego.Router("/api/statistics", &api.StatisticAPI{})
	beego.Router("/api/projects/:id/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
	beego.Router("/api/users", &api.UserAPI{})
	beego.Router("/api/users/?:id", &api.UserAPI{})
	beego.Router("/api/users/:id/password", &api.UserAPI{}, "put:ChangePassword")
	beego.Router("/api/users/:id/toggle_user_admin", &api.UserAPI{}, "post:ToggleUserAdminRole")
	beego.Router("/api/repositories", &api.RepositoryAPI{})
	beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags")
	beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")
	beego.Router("api/logs", &api.LogAPI{})

	//external service that hosted on harbor process:
	beego.Router("/service/notifications", &service.NotificationHandler{})
	beego.Router("/service/token", &token.Handler{})
}
<commit_msg>modify router<commit_after>/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package main

import (
	"github.com/vmware/harbor/api"
	"github.com/vmware/harbor/controllers"
	"github.com/vmware/harbor/service"
	"github.com/vmware/harbor/service/token"

	"github.com/astaxie/beego"
)

func initRouters() {

	beego.SetStaticPath("registry/static/i18n", "static/i18n")
	beego.SetStaticPath("registry/static/resources", "static/resources")
	beego.SetStaticPath("registry/static/vendors", "static/vendors")

	beego.Router("/login", &controllers.CommonController{}, "post:Login")
	beego.Router("/logout", &controllers.CommonController{}, "get:Logout")
	beego.Router("/language", &controllers.CommonController{}, "get:SwitchLanguage")
	beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists")
	beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword")
	beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendEmail")

	beego.Router("/", &controllers.IndexController{})
	beego.Router("/signIn", &controllers.SignInController{})
	beego.Router("/register", &controllers.RegisterController{})
	beego.Router("/addUser", &controllers.AddUserController{})
	beego.Router("/forgotPassword", &controllers.ForgotPasswordController{})
	beego.Router("/resetPassword", &controllers.ResetPasswordController{})
	beego.Router("/changePassword", &controllers.ChangePasswordController{})

	beego.Router("/registry/project", &controllers.ProjectController{})
	beego.Router("/registry/detail", &controllers.ItemDetailController{})

	beego.Router("/search", &controllers.SearchController{})

	//API:
	beego.Router("/api/search", &api.SearchAPI{})
	beego.Router("/api/projects/:pid/members/?:mid", &api.ProjectMemberAPI{})
	beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List")
	beego.Router("/api/projects/?:id", &api.ProjectAPI{})
	beego.Router("/api/projects/:id/toggle_project_public", &api.ProjectAPI{}, "post:ToggleProjectPublic")
	beego.Router("/api/statistics", &api.StatisticAPI{})
	beego.Router("/api/projects/:id/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
	beego.Router("/api/users/?:id", &api.UserAPI{})
	beego.Router("/api/users/:id/password", &api.UserAPI{}, "put:ChangePassword")
	beego.Router("/api/users/:id/toggle_user_admin", &api.UserAPI{}, "post:ToggleUserAdminRole")
	beego.Router("/api/repositories", &api.RepositoryAPI{})
	beego.Router("/api/repositories/tags", &api.RepositoryAPI{}, "get:GetTags")
	beego.Router("/api/repositories/manifests", &api.RepositoryAPI{}, "get:GetManifests")
	beego.Router("api/logs", &api.LogAPI{})

	//external service that hosted on harbor process:
	beego.Router("/service/notifications", &service.NotificationHandler{})
	beego.Router("/service/token", &token.Handler{})
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha1"
	"encoding/base64"
	"encoding/binary"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"

	"code.google.com/p/go.crypto/pbkdf2"
	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pb "../proto"
)

const (
	bnotifyPackageName = "cc.bran.bnotify"
	gcmSendAddress     = "https://android.googleapis.com/gcm/send"
	aesKeySize         = 16
	pbkdfIterCount     = 400000
	serverIDSize       = 16
)

var (
	port             = flag.Int("port", 50051, "port to listen to RPCs on")
	settingsFilename = flag.String("settings", "bnotify.conf", "filename of settings file")
	stateFilename    = flag.String("state", "bnotify.state", "filename of state file")

	uint64Size = binary.Size(uint64(0))

	stateMu sync.Mutex
)

type notificationService struct {
	httpClient     *http.Client
	apiKey         string
	registrationID string
	gcmCipher      cipher.AEAD
}

func (ns *notificationService) SendNotification(ctx context.Context, req *pb.SendNotificationRequest) (*pb.SendNotificationResponse, error) {
	log.Printf("Got notification request")

	// Verify request.
	if req.Notification.Title == "" {
		return nil, errors.New("notification missing title")
	}
	if req.Notification.Text == "" {
		return nil, errors.New("notification missing text")
	}

	// Read state.
	serverID, seq, err := getServerIDAndSeq(true)
	if err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}

	// Marshal request notification & encrypt.
	plaintextMessage, err := proto.Marshal(&pb.Message{
		ServerId:     serverID,
		Seq:          seq,
		Notification: req.Notification,
	})
	if err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}
	nonce := make([]byte, ns.gcmCipher.NonceSize())
	binary.BigEndian.PutUint64(nonce, seq) // Ensure we do not reuse nonce.
	if _, err := rand.Read(nonce[uint64Size:]); err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}
	message := ns.gcmCipher.Seal(nil, nonce, plaintextMessage, nil)

	// Fill out final envelope proto & base-64 encode into a payload.
	envelopeData, err := proto.Marshal(&pb.Envelope{
		Message: message,
		Nonce:   nonce,
	})
	if err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}
	payload := base64.StdEncoding.EncodeToString(envelopeData)

	// Post the notification.
	if err := ns.postNotification(payload); err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}
	return &pb.SendNotificationResponse{}, nil
}

func (ns *notificationService) postNotification(payload string) error {
	// Set up request.
	values := url.Values{}
	values.Set("restricted_package_name", bnotifyPackageName)
	values.Set("registration_id", ns.registrationID)
	values.Set("data.payload", payload)

	req, err := http.NewRequest("POST", gcmSendAddress, strings.NewReader(values.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
	req.Header.Add("Authorization", fmt.Sprintf("key=%s", ns.apiKey))

	// Make request to GCM server.
	resp, err := ns.httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Check for HTTP error code.
	if resp.StatusCode != 200 {
		return fmt.Errorf("GCM HTTP error: %v", resp.Status)
	}

	// Read the first line of the response and figure out if it indicates a GCM-level error.
	bodyReader := bufio.NewReader(resp.Body)
	lineBytes, _, err := bodyReader.ReadLine()
	if err != nil {
		return err
	}
	line := string(lineBytes)
	if strings.HasPrefix(line, "Error=") {
		return fmt.Errorf("GCM error: %v", strings.TrimPrefix(line, "Error="))
	}
	return nil
}

func getServerIDAndSeq(increment bool) (serverID string, seq uint64, _ error) {
	// TODO(bran): consider using a library (e.g. SQLite) to handle intricacies of handling files on disk
	//             (this is probably subtly buggy under certain failure modes, & nonce reuse is disastrous...)
	stateMu.Lock()
	defer stateMu.Unlock()

	// Get current state.
	var state *pb.BNotifyServerState
	inFile, err := os.Open(*stateFilename)
	if err == nil {
		if err := func() error {
			defer inFile.Close()
			data, err := ioutil.ReadAll(inFile)
			if err != nil {
				return fmt.Errorf("could not read state file: %v", err)
			}
			state = &pb.BNotifyServerState{}
			if err := proto.Unmarshal(data, state); err != nil {
				return fmt.Errorf("could not deserialize state: %v", err)
			}
			return nil
		}(); err != nil {
			return "", 0, err
		}
	} else {
		log.Printf("Could not open state file (%v); continuing with fresh state", err)
		serverIDBytes := make([]byte, serverIDSize)
		if _, err := rand.Read(serverIDBytes); err != nil {
			return "", 0, fmt.Errorf("could not generate server ID: %v", err)
		}
		state = &pb.BNotifyServerState{
			ServerId: base64.RawStdEncoding.EncodeToString(serverIDBytes),
			NextSeq:  1,
		}
	}

	// Get result & update state if requested.
	serverID, nextSeq := state.ServerId, state.NextSeq
	if nextSeq == 0 {
		return "", 0, errors.New("out of message sequence numbers")
	}
	if increment {
		state.NextSeq++
	}

	// Write state back.
	// (do this even if we aren't changing the state to make sure that we
	// can write state--allows us to fail early if state flag is improperly
	// set)
	stateBytes, err := proto.Marshal(state)
	if err != nil {
		return "", 0, fmt.Errorf("could not serialize state: %v", err)
	}
	outFile, err := ioutil.TempFile("", "bnotifyd_")
	if err != nil {
		return "", 0, fmt.Errorf("could not open temporary file: %v", err)
	}
	tempFilename := outFile.Name()
	if err := func() error {
		defer outFile.Close()
		if err := outFile.Chmod(0640); err != nil {
			return fmt.Errorf("could not chmod tempfile: %v", err)
		}
		if err := outFile.Truncate(0); err != nil {
			return fmt.Errorf("could not truncate tempfile: %v", err)
		}
		if _, err := io.Copy(outFile, bytes.NewReader(stateBytes)); err != nil {
			return fmt.Errorf("could not write to tempfile: %v", err)
		}
		if err := outFile.Sync(); err != nil {
			return fmt.Errorf("could not sync tempfile: %v", err)
		}
		if err := outFile.Close(); err != nil {
			return fmt.Errorf("could not close tempfile: %v", err)
		}
		return nil
	}(); err != nil {
		return "", 0, err
	}
	if err := os.Rename(tempFilename, *stateFilename); err != nil {
		return "", 0, fmt.Errorf("could not rename tempfile: %v", err)
	}

	return serverID, nextSeq, nil
}

func main() {
	// Parse flags.
	flag.Parse()

	// Read settings.
	settingsBytes, err := ioutil.ReadFile(*settingsFilename)
	if err != nil {
		log.Fatalf("Error reading settings file: %v", err)
	}
	settings := &pb.BNotifySettings{}
	err = proto.UnmarshalText(string(settingsBytes), settings)
	if err != nil {
		log.Fatalf("Error reading settings file: %v", err)
	}

	// Read state file to make sure we can (fail fast for bad config).
	if _, _, err := getServerIDAndSeq(false); err != nil {
		log.Fatalf("Error checking state file: %v", err)
	}

	// Derive key from password & salt (registration ID).
	key := pbkdf2.Key([]byte(settings.Password), []byte(settings.RegistrationId), pbkdfIterCount, aesKeySize, sha1.New)

	// Initialize cipher based on key.
	blockCipher, err := aes.NewCipher(key)
	if err != nil {
		log.Fatalf("Error initializing block cipher: %v", err)
	}
	gcmCipher, err := cipher.NewGCM(blockCipher)
	if err != nil {
		log.Fatalf("Error initializing GCM cipher: %v", err)
	}

	// Sanity check nonce size.
	if gcmCipher.NonceSize() < uint64Size {
		// This should be impossible, but may as well panic with a useful message.
		log.Fatalf(fmt.Sprintf("cipher nonce size too small (%d < %d)", gcmCipher.NonceSize(), uint64Size))
	}

	// Create service, socket, and gRPC server objects.
	service := &notificationService{
		httpClient:     new(http.Client),
		apiKey:         settings.ApiKey,
		registrationID: settings.RegistrationId,
		gcmCipher:      gcmCipher,
	}
	listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *port))
	if err != nil {
		log.Fatalf("Error listening on port %d: %v", *port, err)
	}
	defer listener.Close()
	server := grpc.NewServer()
	pb.RegisterNotificationServiceServer(server, service)

	// Begin serving.
	log.Printf("Listening for requests on port %d...", *port)
	server.Serve(listener)
}
<commit_msg>Create temp file in same directory as state file.<commit_after>package main

import (
	"bufio"
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha1"
	"encoding/base64"
	"encoding/binary"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"sync"

	"code.google.com/p/go.crypto/pbkdf2"
	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pb "../proto"
)

const (
	bnotifyPackageName = "cc.bran.bnotify"
	gcmSendAddress     = "https://android.googleapis.com/gcm/send"
	aesKeySize         = 16
	pbkdfIterCount     = 400000
	serverIDSize       = 16
)

var (
	port             = flag.Int("port", 50051, "port to listen to RPCs on")
	settingsFilename = flag.String("settings", "bnotify.conf", "filename of settings file")
	stateFilename    = flag.String("state", "bnotify.state", "filename of state file")

	uint64Size = binary.Size(uint64(0))

	stateMu sync.Mutex
)

type notificationService struct {
	httpClient     *http.Client
	apiKey         string
	registrationID string
	gcmCipher      cipher.AEAD
}

func (ns *notificationService) SendNotification(ctx context.Context, req *pb.SendNotificationRequest) (*pb.SendNotificationResponse, error) {
	log.Printf("Got notification request")

	// Verify request.
	if req.Notification.Title == "" {
		return nil, errors.New("notification missing title")
	}
	if req.Notification.Text == "" {
		return nil, errors.New("notification missing text")
	}

	// Read state.
	serverID, seq, err := getServerIDAndSeq(true)
	if err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}

	// Marshal request notification & encrypt.
	plaintextMessage, err := proto.Marshal(&pb.Message{
		ServerId:     serverID,
		Seq:          seq,
		Notification: req.Notification,
	})
	if err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}
	nonce := make([]byte, ns.gcmCipher.NonceSize())
	binary.BigEndian.PutUint64(nonce, seq) // Ensure we do not reuse nonce.
	if _, err := rand.Read(nonce[uint64Size:]); err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}
	message := ns.gcmCipher.Seal(nil, nonce, plaintextMessage, nil)

	// Fill out final envelope proto & base-64 encode into a payload.
	envelopeData, err := proto.Marshal(&pb.Envelope{
		Message: message,
		Nonce:   nonce,
	})
	if err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}
	payload := base64.StdEncoding.EncodeToString(envelopeData)

	// Post the notification.
	if err := ns.postNotification(payload); err != nil {
		log.Printf("Error while posting notification: %v", err)
		return nil, errors.New("internal error")
	}
	return &pb.SendNotificationResponse{}, nil
}

func (ns *notificationService) postNotification(payload string) error {
	// Set up request.
	values := url.Values{}
	values.Set("restricted_package_name", bnotifyPackageName)
	values.Set("registration_id", ns.registrationID)
	values.Set("data.payload", payload)

	req, err := http.NewRequest("POST", gcmSendAddress, strings.NewReader(values.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
	req.Header.Add("Authorization", fmt.Sprintf("key=%s", ns.apiKey))

	// Make request to GCM server.
	resp, err := ns.httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Check for HTTP error code.
	if resp.StatusCode != 200 {
		return fmt.Errorf("GCM HTTP error: %v", resp.Status)
	}

	// Read the first line of the response and figure out if it indicates a GCM-level error.
	bodyReader := bufio.NewReader(resp.Body)
	lineBytes, _, err := bodyReader.ReadLine()
	if err != nil {
		return err
	}
	line := string(lineBytes)
	if strings.HasPrefix(line, "Error=") {
		return fmt.Errorf("GCM error: %v", strings.TrimPrefix(line, "Error="))
	}
	return nil
}

func getServerIDAndSeq(increment bool) (serverID string, seq uint64, _ error) {
	// TODO(bran): consider using a library (e.g. SQLite) to handle files on disk
	//             (sadly, I can't find a non-cgo SQLite package. oh well.)
	stateMu.Lock()
	defer stateMu.Unlock()

	// Get current state.
	var state *pb.BNotifyServerState
	inFile, err := os.Open(*stateFilename)
	if err == nil {
		if err := func() error {
			defer inFile.Close()
			data, err := ioutil.ReadAll(inFile)
			if err != nil {
				return fmt.Errorf("could not read state file: %v", err)
			}
			state = &pb.BNotifyServerState{}
			if err := proto.Unmarshal(data, state); err != nil {
				return fmt.Errorf("could not deserialize state: %v", err)
			}
			return nil
		}(); err != nil {
			return "", 0, err
		}
	} else {
		log.Printf("Could not open state file (%v); continuing with fresh state", err)
		serverIDBytes := make([]byte, serverIDSize)
		if _, err := rand.Read(serverIDBytes); err != nil {
			return "", 0, fmt.Errorf("could not generate server ID: %v", err)
		}
		state = &pb.BNotifyServerState{
			ServerId: base64.RawStdEncoding.EncodeToString(serverIDBytes),
			NextSeq:  1,
		}
	}

	// Get result & update state if requested.
	serverID, nextSeq := state.ServerId, state.NextSeq
	if nextSeq == 0 {
		return "", 0, errors.New("out of message sequence numbers")
	}
	if increment {
		state.NextSeq++
	}

	// Write state back.
	// (do this even if we aren't changing the state to make sure that we
	// can write state--allows us to fail early if state flag is improperly
	// set)
	stateBytes, err := proto.Marshal(state)
	if err != nil {
		return "", 0, fmt.Errorf("could not serialize state: %v", err)
	}
	outFile, err := ioutil.TempFile(path.Dir(*stateFilename), "bnotifyd_")
	if err != nil {
		return "", 0, fmt.Errorf("could not open temporary file: %v", err)
	}
	tempFilename := outFile.Name()
	if err := func() error {
		defer outFile.Close()
		if err := outFile.Chmod(0640); err != nil {
			return fmt.Errorf("could not chmod tempfile: %v", err)
		}
		if err := outFile.Truncate(0); err != nil {
			return fmt.Errorf("could not truncate tempfile: %v", err)
		}
		if _, err := io.Copy(outFile, bytes.NewReader(stateBytes)); err != nil {
			return fmt.Errorf("could not write to tempfile: %v", err)
		}
		if err := outFile.Sync(); err != nil {
			return fmt.Errorf("could not sync tempfile: %v", err)
		}
		if err := outFile.Close(); err != nil {
			return fmt.Errorf("could not close tempfile: %v", err)
		}
		return nil
	}(); err != nil {
		return "", 0, err
	}
	if err := os.Rename(tempFilename, *stateFilename); err != nil {
		return "", 0, fmt.Errorf("could not rename tempfile: %v", err)
	}

	return serverID, nextSeq, nil
}

func main() {
	// Parse flags.
	flag.Parse()

	// Read settings.
	settingsBytes, err := ioutil.ReadFile(*settingsFilename)
	if err != nil {
		log.Fatalf("Error reading settings file: %v", err)
	}
	settings := &pb.BNotifySettings{}
	err = proto.UnmarshalText(string(settingsBytes), settings)
	if err != nil {
		log.Fatalf("Error reading settings file: %v", err)
	}

	// Read state file to make sure we can (fail fast for bad config).
	if _, _, err := getServerIDAndSeq(false); err != nil {
		log.Fatalf("Error checking state file: %v", err)
	}

	// Derive key from password & salt (registration ID).
	key := pbkdf2.Key([]byte(settings.Password), []byte(settings.RegistrationId), pbkdfIterCount, aesKeySize, sha1.New)

	// Initialize cipher based on key.
	blockCipher, err := aes.NewCipher(key)
	if err != nil {
		log.Fatalf("Error initializing block cipher: %v", err)
	}
	gcmCipher, err := cipher.NewGCM(blockCipher)
	if err != nil {
		log.Fatalf("Error initializing GCM cipher: %v", err)
	}

	// Sanity check nonce size.
	if gcmCipher.NonceSize() < uint64Size {
		// This should be impossible, but may as well panic with a useful message.
		log.Fatalf(fmt.Sprintf("cipher nonce size too small (%d < %d)", gcmCipher.NonceSize(), uint64Size))
	}

	// Create service, socket, and gRPC server objects.
	service := &notificationService{
		httpClient:     new(http.Client),
		apiKey:         settings.ApiKey,
		registrationID: settings.RegistrationId,
		gcmCipher:      gcmCipher,
	}
	listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *port))
	if err != nil {
		log.Fatalf("Error listening on port %d: %v", *port, err)
	}
	defer listener.Close()
	server := grpc.NewServer()
	pb.RegisterNotificationServiceServer(server, service)

	// Begin serving.
	log.Printf("Listening for requests on port %d...", *port)
	server.Serve(listener)
}
<|endoftext|>