<commit_before>// Copyright 2016 The go-github 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 github

import (
	"context"
	"errors"
	"fmt"
	"time"
)

var ErrMixedCommentStyles = errors.New("cannot use both position and side/line form comments")

// PullRequestReview represents a review of a pull request.
type PullRequestReview struct {
	ID             *int64     `json:"id,omitempty"`
	NodeID         *string    `json:"node_id,omitempty"`
	User           *User      `json:"user,omitempty"`
	Body           *string    `json:"body,omitempty"`
	SubmittedAt    *time.Time `json:"submitted_at,omitempty"`
	CommitID       *string    `json:"commit_id,omitempty"`
	HTMLURL        *string    `json:"html_url,omitempty"`
	PullRequestURL *string    `json:"pull_request_url,omitempty"`
	State          *string    `json:"state,omitempty"`
	// AuthorAssociation is the comment author's relationship to the issue's repository.
	// Possible values are "COLLABORATOR", "CONTRIBUTOR", "FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR", "MEMBER", "OWNER", or "NONE".
	AuthorAssociation *string `json:"author_association,omitempty"`
}

func (p PullRequestReview) String() string {
	return Stringify(p)
}

// DraftReviewComment represents a comment part of the review.
type DraftReviewComment struct {
	Path     *string `json:"path,omitempty"`
	Position *int    `json:"position,omitempty"`
	Body     *string `json:"body,omitempty"`

	// The new comfort-fade-preview fields
	StartSide *string `json:"start_side,omitempty"`
	Side      *string `json:"side,omitempty"`
	StartLine *int    `json:"start_line,omitempty"`
	Line      *int    `json:"line,omitempty"`
}

func (c DraftReviewComment) String() string {
	return Stringify(c)
}

// PullRequestReviewRequest represents a request to create a review.
type PullRequestReviewRequest struct {
	NodeID   *string               `json:"node_id,omitempty"`
	CommitID *string               `json:"commit_id,omitempty"`
	Body     *string               `json:"body,omitempty"`
	Event    *string               `json:"event,omitempty"`
	Comments []*DraftReviewComment `json:"comments,omitempty"`
}

func (r PullRequestReviewRequest) String() string {
	return Stringify(r)
}

func (r *PullRequestReviewRequest) isComfortFadePreview() (bool, error) {
	var isCF *bool
	for _, comment := range r.Comments {
		if comment == nil {
			continue
		}
		hasPos := comment.Position != nil
		hasComfortFade := (comment.StartSide != nil) || (comment.Side != nil) ||
			(comment.StartLine != nil) || (comment.Line != nil)

		switch {
		case hasPos && hasComfortFade:
			return false, ErrMixedCommentStyles
		case hasPos && isCF != nil && *isCF:
			return false, ErrMixedCommentStyles
		case hasComfortFade && isCF != nil && !*isCF:
			return false, ErrMixedCommentStyles
		}
		isCF = &hasComfortFade
	}
	if isCF != nil {
		return *isCF, nil
	}
	return false, nil
}

// PullRequestReviewDismissalRequest represents a request to dismiss a review.
type PullRequestReviewDismissalRequest struct {
	Message *string `json:"message,omitempty"`
}

func (r PullRequestReviewDismissalRequest) String() string {
	return Stringify(r)
}

// ListReviews lists all reviews on the specified pull request.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#list-reviews-for-a-pull-request
func (s *PullRequestsService) ListReviews(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var reviews []*PullRequestReview
	resp, err := s.client.Do(ctx, req, &reviews)
	if err != nil {
		return nil, resp, err
	}

	return reviews, resp, nil
}

// GetReview fetches the specified pull request review.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#get-a-review-for-a-pull-request
func (s *PullRequestsService) GetReview(ctx context.Context, owner, repo string, number int, reviewID int64) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	review := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, review)
	if err != nil {
		return nil, resp, err
	}

	return review, resp, nil
}

// DeletePendingReview deletes the specified pull request pending review.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#delete-a-pending-review-for-a-pull-request
func (s *PullRequestsService) DeletePendingReview(ctx context.Context, owner, repo string, number int, reviewID int64) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("DELETE", u, nil)
	if err != nil {
		return nil, nil, err
	}

	review := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, review)
	if err != nil {
		return nil, resp, err
	}

	return review, resp, nil
}

// ListReviewComments lists all the comments for the specified review.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#list-comments-for-a-pull-request-review
func (s *PullRequestsService) ListReviewComments(ctx context.Context, owner, repo string, number int, reviewID int64, opts *ListOptions) ([]*PullRequestComment, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/comments", owner, repo, number, reviewID)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var comments []*PullRequestComment
	resp, err := s.client.Do(ctx, req, &comments)
	if err != nil {
		return nil, resp, err
	}

	return comments, resp, nil
}

// CreateReview creates a new review on the specified pull request.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#create-a-review-for-a-pull-request
//
// In order to use multi-line comments, you must use the "comfort fade" preview.
// This replaces the use of the "Position" field in comments with 4 new fields:
//   [Start]Side, and [Start]Line.
// These new fields must be used for ALL comments (including single-line),
// with the following restrictions (empirically observed, so subject to change).
//
// For single-line "comfort fade" comments, you must use:
//
//    Path:  &path,  // as before
//    Body:  &body,  // as before
//    Side:  &"RIGHT" (or "LEFT")
//    Line:  &123,  // NOT THE SAME AS POSITION, this is an actual line number.
//
// If StartSide or StartLine is used with single-line comments, a 422 is returned.
//
// For multi-line "comfort fade" comments, you must use:
//
//    Path:      &path,  // as before
//    Body:      &body,  // as before
//    StartSide: &"RIGHT" (or "LEFT")
//    Side:      &"RIGHT" (or "LEFT")
//    StartLine: &120,
//    Line:      &125,
//
// Suggested edits are made by commenting on the lines to replace, and including the
// suggested edit in a block like this (it may be surrounded in non-suggestion markdown):
//
//    ```suggestion
//    Use this instead.
//    It is waaaaaay better.
//    ```
func (s *PullRequestsService) CreateReview(ctx context.Context, owner, repo string, number int, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number)

	req, err := s.client.NewRequest("POST", u, review)
	if err != nil {
		return nil, nil, err
	}

	// Detect which style of review comment is being used.
	if isCF, err := review.isComfortFadePreview(); err != nil {
		return nil, nil, err
	} else if isCF {
		// If the review comments are using the comfort fade preview fields,
		// then pass the comfort fade header.
		req.Header.Set("Accept", mediaTypeMultiLineCommentsPreview)
	}

	r := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, nil
}

// UpdateReview updates the review summary on the specified pull request.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#update-a-review-for-a-pull-request
func (s *PullRequestsService) UpdateReview(ctx context.Context, owner, repo string, number int, reviewID int64, body string) (*PullRequestReview, *Response, error) {
	opts := &struct {
		Body string `json:"body"`
	}{Body: body}
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("PUT", u, opts)
	if err != nil {
		return nil, nil, err
	}

	review := &PullRequestReview{}
	resp, err := s.client.Do(ctx, req, review)
	if err != nil {
		return nil, resp, err
	}

	return review, resp, nil
}

// SubmitReview submits a specified review on the specified pull request.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#submit-a-review-for-a-pull-request
func (s *PullRequestsService) SubmitReview(ctx context.Context, owner, repo string, number int, reviewID int64, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/events", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("POST", u, review)
	if err != nil {
		return nil, nil, err
	}

	r := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, nil
}

// DismissReview dismisses a specified review on the specified pull request.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#dismiss-a-review-for-a-pull-request
func (s *PullRequestsService) DismissReview(ctx context.Context, owner, repo string, number int, reviewID int64, review *PullRequestReviewDismissalRequest) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/dismissals", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("PUT", u, review)
	if err != nil {
		return nil, nil, err
	}

	r := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, nil
}
<commit_msg>Remove outdated todo (#1824)<commit_after>// Copyright 2016 The go-github 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 github

import (
	"context"
	"errors"
	"fmt"
	"time"
)

var ErrMixedCommentStyles = errors.New("cannot use both position and side/line form comments")

// PullRequestReview represents a review of a pull request.
type PullRequestReview struct {
	ID             *int64     `json:"id,omitempty"`
	NodeID         *string    `json:"node_id,omitempty"`
	User           *User      `json:"user,omitempty"`
	Body           *string    `json:"body,omitempty"`
	SubmittedAt    *time.Time `json:"submitted_at,omitempty"`
	CommitID       *string    `json:"commit_id,omitempty"`
	HTMLURL        *string    `json:"html_url,omitempty"`
	PullRequestURL *string    `json:"pull_request_url,omitempty"`
	State          *string    `json:"state,omitempty"`
	// AuthorAssociation is the comment author's relationship to the issue's repository.
	// Possible values are "COLLABORATOR", "CONTRIBUTOR", "FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR", "MEMBER", "OWNER", or "NONE".
	AuthorAssociation *string `json:"author_association,omitempty"`
}

func (p PullRequestReview) String() string {
	return Stringify(p)
}

// DraftReviewComment represents a comment part of the review.
type DraftReviewComment struct {
	Path     *string `json:"path,omitempty"`
	Position *int    `json:"position,omitempty"`
	Body     *string `json:"body,omitempty"`

	// The new comfort-fade-preview fields
	StartSide *string `json:"start_side,omitempty"`
	Side      *string `json:"side,omitempty"`
	StartLine *int    `json:"start_line,omitempty"`
	Line      *int    `json:"line,omitempty"`
}

func (c DraftReviewComment) String() string {
	return Stringify(c)
}

// PullRequestReviewRequest represents a request to create a review.
type PullRequestReviewRequest struct {
	NodeID   *string               `json:"node_id,omitempty"`
	CommitID *string               `json:"commit_id,omitempty"`
	Body     *string               `json:"body,omitempty"`
	Event    *string               `json:"event,omitempty"`
	Comments []*DraftReviewComment `json:"comments,omitempty"`
}

func (r PullRequestReviewRequest) String() string {
	return Stringify(r)
}

func (r *PullRequestReviewRequest) isComfortFadePreview() (bool, error) {
	var isCF *bool
	for _, comment := range r.Comments {
		if comment == nil {
			continue
		}
		hasPos := comment.Position != nil
		hasComfortFade := (comment.StartSide != nil) || (comment.Side != nil) ||
			(comment.StartLine != nil) || (comment.Line != nil)

		switch {
		case hasPos && hasComfortFade:
			return false, ErrMixedCommentStyles
		case hasPos && isCF != nil && *isCF:
			return false, ErrMixedCommentStyles
		case hasComfortFade && isCF != nil && !*isCF:
			return false, ErrMixedCommentStyles
		}
		isCF = &hasComfortFade
	}
	if isCF != nil {
		return *isCF, nil
	}
	return false, nil
}

// PullRequestReviewDismissalRequest represents a request to dismiss a review.
type PullRequestReviewDismissalRequest struct {
	Message *string `json:"message,omitempty"`
}

func (r PullRequestReviewDismissalRequest) String() string {
	return Stringify(r)
}

// ListReviews lists all reviews on the specified pull request.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#list-reviews-for-a-pull-request
func (s *PullRequestsService) ListReviews(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var reviews []*PullRequestReview
	resp, err := s.client.Do(ctx, req, &reviews)
	if err != nil {
		return nil, resp, err
	}

	return reviews, resp, nil
}

// GetReview fetches the specified pull request review.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#get-a-review-for-a-pull-request
func (s *PullRequestsService) GetReview(ctx context.Context, owner, repo string, number int, reviewID int64) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	review := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, review)
	if err != nil {
		return nil, resp, err
	}

	return review, resp, nil
}

// DeletePendingReview deletes the specified pull request pending review.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#delete-a-pending-review-for-a-pull-request
func (s *PullRequestsService) DeletePendingReview(ctx context.Context, owner, repo string, number int, reviewID int64) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("DELETE", u, nil)
	if err != nil {
		return nil, nil, err
	}

	review := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, review)
	if err != nil {
		return nil, resp, err
	}

	return review, resp, nil
}

// ListReviewComments lists all the comments for the specified review.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#list-comments-for-a-pull-request-review
func (s *PullRequestsService) ListReviewComments(ctx context.Context, owner, repo string, number int, reviewID int64, opts *ListOptions) ([]*PullRequestComment, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/comments", owner, repo, number, reviewID)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var comments []*PullRequestComment
	resp, err := s.client.Do(ctx, req, &comments)
	if err != nil {
		return nil, resp, err
	}

	return comments, resp, nil
}

// CreateReview creates a new review on the specified pull request.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#create-a-review-for-a-pull-request
//
// In order to use multi-line comments, you must use the "comfort fade" preview.
// This replaces the use of the "Position" field in comments with 4 new fields:
//   [Start]Side, and [Start]Line.
// These new fields must be used for ALL comments (including single-line),
// with the following restrictions (empirically observed, so subject to change).
//
// For single-line "comfort fade" comments, you must use:
//
//    Path:  &path,  // as before
//    Body:  &body,  // as before
//    Side:  &"RIGHT" (or "LEFT")
//    Line:  &123,  // NOT THE SAME AS POSITION, this is an actual line number.
//
// If StartSide or StartLine is used with single-line comments, a 422 is returned.
//
// For multi-line "comfort fade" comments, you must use:
//
//    Path:      &path,  // as before
//    Body:      &body,  // as before
//    StartSide: &"RIGHT" (or "LEFT")
//    Side:      &"RIGHT" (or "LEFT")
//    StartLine: &120,
//    Line:      &125,
//
// Suggested edits are made by commenting on the lines to replace, and including the
// suggested edit in a block like this (it may be surrounded in non-suggestion markdown):
//
//    ```suggestion
//    Use this instead.
//    It is waaaaaay better.
//    ```
func (s *PullRequestsService) CreateReview(ctx context.Context, owner, repo string, number int, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number)

	req, err := s.client.NewRequest("POST", u, review)
	if err != nil {
		return nil, nil, err
	}

	// Detect which style of review comment is being used.
	if isCF, err := review.isComfortFadePreview(); err != nil {
		return nil, nil, err
	} else if isCF {
		// If the review comments are using the comfort fade preview fields,
		// then pass the comfort fade header.
		req.Header.Set("Accept", mediaTypeMultiLineCommentsPreview)
	}

	r := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, nil
}

// UpdateReview updates the review summary on the specified pull request.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#update-a-review-for-a-pull-request
func (s *PullRequestsService) UpdateReview(ctx context.Context, owner, repo string, number int, reviewID int64, body string) (*PullRequestReview, *Response, error) {
	opts := &struct {
		Body string `json:"body"`
	}{Body: body}
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("PUT", u, opts)
	if err != nil {
		return nil, nil, err
	}

	review := &PullRequestReview{}
	resp, err := s.client.Do(ctx, req, review)
	if err != nil {
		return nil, resp, err
	}

	return review, resp, nil
}

// SubmitReview submits a specified review on the specified pull request.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#submit-a-review-for-a-pull-request
func (s *PullRequestsService) SubmitReview(ctx context.Context, owner, repo string, number int, reviewID int64, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/events", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("POST", u, review)
	if err != nil {
		return nil, nil, err
	}

	r := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, nil
}

// DismissReview dismisses a specified review on the specified pull request.
//
// TODO: Follow up with GitHub support about an issue with this method's
// returned error format and remove this comment once it's fixed.
// Read more about it here - https://github.com/google/go-github/issues/540
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#dismiss-a-review-for-a-pull-request
func (s *PullRequestsService) DismissReview(ctx context.Context, owner, repo string, number int, reviewID int64, review *PullRequestReviewDismissalRequest) (*PullRequestReview, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/dismissals", owner, repo, number, reviewID)

	req, err := s.client.NewRequest("PUT", u, review)
	if err != nil {
		return nil, nil, err
	}

	r := new(PullRequestReview)
	resp, err := s.client.Do(ctx, req, r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, nil
}
<|endoftext|>


<commit_before>/*
   Copyright 2015 Shlomi Noach, courtesy Booking.com

   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 inst

import (
	"fmt"
	"github.com/github/orchestrator/go/config"
	"github.com/github/orchestrator/go/db"
	"github.com/openark/golib/log"
)

// BeginDowntime will make mark an instance as downtimed (or override existing downtime period)
func BeginDowntime(instanceKey *InstanceKey, owner string, reason string, durationSeconds uint) error {
	if durationSeconds == 0 {
		durationSeconds = config.Config.MaintenanceExpireMinutes * 60
	}
	_, err := db.ExecOrchestrator(`
			insert
				into database_instance_downtime (
					hostname, port, downtime_active, begin_timestamp, end_timestamp, owner, reason
				) VALUES (
					?, ?, 1, NOW(), NOW() + INTERVAL ? SECOND, ?, ?
				)
				on duplicate key update
					downtime_active=values(downtime_active),
					begin_timestamp=values(begin_timestamp),
					end_timestamp=values(end_timestamp),
					owner=values(owner),
					reason=values(reason)
			`,
		instanceKey.Hostname,
		instanceKey.Port,
		durationSeconds,
		owner,
		reason,
	)
	if err != nil {
		return log.Errore(err)
	}

	AuditOperation("begin-downtime", instanceKey, fmt.Sprintf("owner: %s, reason: %s", owner, reason))

	return nil
}

// EndDowntime will remove downtime flag from an instance
func EndDowntime(instanceKey *InstanceKey) (wasDowntimed bool, err error) {
	res, err := db.ExecOrchestrator(`
			update
				database_instance_downtime
			set
				downtime_active = NULL,
				end_timestamp = NOW()
			where
				hostname = ?
				and port = ?
				and downtime_active = 1
			`,
		instanceKey.Hostname,
		instanceKey.Port,
	)
	if err != nil {
		return wasDowntimed, log.Errore(err)
	}

	if affected, _ := res.RowsAffected(); affected > 0 {
		wasDowntimed = true
		AuditOperation("end-downtime", instanceKey, "")
	}
	return wasDowntimed, err
}

// renewLostInRecoveryDowntime renews hosts who are downtimed due to being lost in recovery, such that
// their downtime never expires.
func renewLostInRecoveryDowntime() error {
	_, err := db.ExecOrchestrator(`
			update
				database_instance_downtime
			set
				end_timestamp = NOW() + INTERVAL ? SECOND
			where
				end_timestamp > NOW()
				and reason = ?
			`,
		config.LostInRecoveryDowntimeSeconds,
		DowntimeLostInRecoveryMessage,
	)

	return err
}

// expireLostInRecoveryDowntime expires downtime for servers who have been lost in recovery in the last,
// but are now replicating.
func expireLostInRecoveryDowntime() error {
	instances, err := ReadLostInRecoveryInstances("")
	if err != nil {
		return err
	}
	for _, instance := range instances {
		if instance.IsLastCheckValid && instance.ReplicaRunning() {
			_, err := db.ExecOrchestrator(`
				delete from
					database_instance_downtime
				where
					hostname = ?
					and port = ?
					`,
				instance.Key.Hostname, instance.Key.Port,
			)
			if err != nil {
				return err
			}
		}
	}

	return err
}

// ExpireDowntime will remove the maintenance flag on old downtimes
func ExpireDowntime() error {
	if err := renewLostInRecoveryDowntime(); err != nil {
		return log.Errore(err)
	}
	if err := expireLostInRecoveryDowntime(); err != nil {
		return log.Errore(err)
	}

	{
		res, err := db.ExecOrchestrator(`
			delete from
				database_instance_downtime
			where
				downtime_active is null
				and end_timestamp < NOW() - INTERVAL ? DAY
			`,
			config.Config.MaintenancePurgeDays,
		)
		if err != nil {
			return log.Errore(err)
		}
		if rowsAffected, _ := res.RowsAffected(); rowsAffected > 0 {
			AuditOperation("expire-downtime", nil, fmt.Sprintf("Purged %d historical entries", rowsAffected))
		}
	}
	{
		res, err := db.ExecOrchestrator(`
			update
				database_instance_downtime
			set
				downtime_active = NULL
			where
				downtime_active = 1
				and end_timestamp < NOW()
			`,
		)
		if err != nil {
			return log.Errore(err)
		}
		if rowsAffected, _ := res.RowsAffected(); rowsAffected > 0 {
			AuditOperation("expire-downtime", nil, fmt.Sprintf("Expired %d entries", rowsAffected))
		}
	}

	return nil
}
<commit_msg>simplified downtime logic<commit_after>/*
   Copyright 2015 Shlomi Noach, courtesy Booking.com

   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 inst

import (
	"fmt"
	"github.com/github/orchestrator/go/config"
	"github.com/github/orchestrator/go/db"
	"github.com/openark/golib/log"
)

// BeginDowntime will make mark an instance as downtimed (or override existing downtime period)
func BeginDowntime(instanceKey *InstanceKey, owner string, reason string, durationSeconds uint) error {
	if durationSeconds == 0 {
		durationSeconds = config.Config.MaintenanceExpireMinutes * 60
	}
	_, err := db.ExecOrchestrator(`
			insert
				into database_instance_downtime (
					hostname, port, downtime_active, begin_timestamp, end_timestamp, owner, reason
				) VALUES (
					?, ?, 1, NOW(), NOW() + INTERVAL ? SECOND, ?, ?
				)
				on duplicate key update
					downtime_active=values(downtime_active),
					begin_timestamp=values(begin_timestamp),
					end_timestamp=values(end_timestamp),
					owner=values(owner),
					reason=values(reason)
			`,
		instanceKey.Hostname,
		instanceKey.Port,
		durationSeconds,
		owner,
		reason,
	)
	if err != nil {
		return log.Errore(err)
	}

	AuditOperation("begin-downtime", instanceKey, fmt.Sprintf("owner: %s, reason: %s", owner, reason))

	return nil
}

// EndDowntime will remove downtime flag from an instance
func EndDowntime(instanceKey *InstanceKey) (wasDowntimed bool, err error) {
	res, err := db.ExecOrchestrator(`
			delete from
				database_instance_downtime
			where
				hostname = ?
				and port = ?
			`,
		instanceKey.Hostname,
		instanceKey.Port,
	)
	if err != nil {
		return wasDowntimed, log.Errore(err)
	}

	if affected, _ := res.RowsAffected(); affected > 0 {
		wasDowntimed = true
		AuditOperation("end-downtime", instanceKey, "")
	}
	return wasDowntimed, err
}

// renewLostInRecoveryDowntime renews hosts who are downtimed due to being lost in recovery, such that
// their downtime never expires.
func renewLostInRecoveryDowntime() error {
	_, err := db.ExecOrchestrator(`
			update
				database_instance_downtime
			set
				end_timestamp = NOW() + INTERVAL ? SECOND
			where
				end_timestamp > NOW()
				and reason = ?
			`,
		config.LostInRecoveryDowntimeSeconds,
		DowntimeLostInRecoveryMessage,
	)

	return err
}

// expireLostInRecoveryDowntime expires downtime for servers who have been lost in recovery in the last,
// but are now replicating.
func expireLostInRecoveryDowntime() error {
	instances, err := ReadLostInRecoveryInstances("")
	if err != nil {
		return err
	}
	for _, instance := range instances {
		if instance.IsLastCheckValid && instance.ReplicaRunning() {
			if _, err := EndDowntime(&instance.Key); err != nil {
				return err
			}
		}
	}
	return nil
}

// ExpireDowntime will remove the maintenance flag on old downtimes
func ExpireDowntime() error {
	if err := renewLostInRecoveryDowntime(); err != nil {
		return log.Errore(err)
	}
	if err := expireLostInRecoveryDowntime(); err != nil {
		return log.Errore(err)
	}
	{
		res, err := db.ExecOrchestrator(`
			delete from
				database_instance_downtime
			where
				end_timestamp < NOW()
			`,
		)
		if err != nil {
			return log.Errore(err)
		}
		if rowsAffected, _ := res.RowsAffected(); rowsAffected > 0 {
			AuditOperation("expire-downtime", nil, fmt.Sprintf("Expired %d entries", rowsAffected))
		}
	}

	return nil
}
<|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 main

// #include "daemon.h"
// #include <linux/if_packet.h>
// #include <linux/filter.h>
// #include <stdlib.h>  // for C.free
import "C"

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"net"
	"os/exec"
	"strconv"
	"sync/atomic"
	"time"
	"unsafe"
)

// socket handles a single AF_PACKET socket.  There will be N Socket objects for
// each SocketConfig, where N == FanoutSize.  This Socket stores the file
// descriptor and memory region of a single underlying AF_PACKET socket.
type socket struct {
	num          int                // fanout index for this socket
	conf         SocketConfig       // configuration
	fd           int                // file descriptor for AF_PACKET socket
	newConns     chan *net.UnixConn // new client connections come in here
	oldConns     chan *conn         // old client connections come in here for cleanup
	newBlocks    chan *block        // when a new block is available, it comes in here
	blocks       []*block           // all blocks in the memory region
	currentConns map[*conn]bool     // list of current connections a new block will be sent to
	ring         uintptr            // pointer to memory region
}

// newSocket creates a new Socket object based on a config.
func newSocket(sc SocketConfig, fanoutID int, num int) (*socket, error) {
	s := &socket{
		num:          num,
		conf:         sc,
		newConns:     make(chan *net.UnixConn),
		oldConns:     make(chan *conn),
		newBlocks:    make(chan *block),
		currentConns: map[*conn]bool{},
		blocks:       make([]*block, sc.NumBlocks),
	}

	// Compile the BPF filter, if it was requested.
	var filt *C.struct_sock_fprog
	if sc.Filter != "" {
		f, err := compileFilter(sc.Interface, sc.Filter)
		if err != nil {
			return nil, fmt.Errorf("unable to compile filter %q on interface %q: %v", sc.Filter, sc.Interface, err)
		}
		filt = &f.filt
	}

	// Set up block objects, used to reference count blocks for clients.
	for i := 0; i < sc.NumBlocks; i++ {
		s.blocks[i] = &block{s: s, index: i}
	}

	// Call into our C code to actually create the socket.
	iface := C.CString(sc.Interface)
	defer C.free(unsafe.Pointer(iface))
	var fd C.int
	var ring unsafe.Pointer
	var errStr *C.char
	if _, err := C.AFPacket(iface, C.int(sc.BlockSize), C.int(sc.NumBlocks),
		C.int(sc.BlockTimeoutMillis), C.int(fanoutID), C.int(sc.FanoutType), filt,
		&fd, &ring, &errStr); err != nil {
		return nil, fmt.Errorf("C AFPacket call failed: %v: %v", C.GoString(errStr), err)
	}
	s.fd = int(fd)
	s.ring = uintptr(ring)
	log.Printf("%v set up with %+v", s, sc)
	return s, nil
}

// String returns a unique string for this socket.
func (s *socket) String() string {
	return fmt.Sprintf("[S:%v:%v]", s.conf.SocketName, s.num)
}

// getNewBlocks is a goroutine that watches for new available packet blocks,
// which the run() method passes to clients.
func (s *socket) getNewBlocks() {
	blockIndex := 0
	for {
		b := s.blocks[blockIndex]
		for !b.ready() {
			time.Sleep(time.Millisecond * 10)
		}
		b.ref()
		v(3, "%v got new block %v", s, b)
		s.newBlocks <- b
		blockIndex = (blockIndex + 1) % s.conf.NumBlocks
	}
}

// run handles new connections, old connections, new blocks... basically
// everything.
func (s *socket) run() {
	go s.getNewBlocks()
	for {
		select {
		case c := <-s.newConns:
			// register a new client connection
			s.addNewConn(c)
		case c := <-s.oldConns:
			// unregister an old client connection and close its blocks
			close(c.newBlocks)
			delete(s.currentConns, c)
		case b := <-s.newBlocks:
			// a new block is avaiable, send it out to all clients
			for c, _ := range s.currentConns {
				b.ref()
				select {
				case c.newBlocks <- b:
				default:
					v(1, "failed to send %v to %v", b, c)
					b.unref()
				}
			}
			b.unref()
		}
	}
}

// conn represents a set-up client connection (already initiated and with the
// file descriptor passed through).
type conn struct {
	s         *socket
	c         *net.UnixConn
	newBlocks chan *block
	oldBlocks chan int
}

// String returns a unique string for this connection.
func (c *conn) String() string {
	return fmt.Sprintf("[C:%v:%v]", c.s, c.c.RemoteAddr())
}

// handleReads handles client->server communication.
func (c *conn) handleReads() {
	defer close(c.oldBlocks)
	for {
		// Wait for a block index to be passed back from the client.
		var buf [4]byte
		n, err := c.c.Read(buf[:])
		if err == io.EOF {
			return
		} else if err != nil || n != len(buf) {
			v(1, "%v read error (%d bytes): %v", c, n, err)
			return
		}
		i := int(binary.BigEndian.Uint32(buf[:]))
		if i < 0 || i >= c.s.conf.NumBlocks {
			log.Printf("%v got invalid block %d", c, i)
			return
		}
		// We add one to the returned int so we can detect a closed channel (which
		// returns 0, the zero-value for ints).
		c.oldBlocks <- i + 1
	}
}

// run handles communicating with a single external client via a single
// connection.  It maintains the invariant that every block it gets via the
// newBlocks channel will be unref'd exactly once.  It's up to the block sender
// to ref the blocks for the conn.
func (c *conn) run() {
	go c.handleReads()
	outstanding := make([]time.Time, len(c.s.blocks))

	// Wait for either the reader or writer to stop.
loop:
	for {
		select {
		case b := <-c.newBlocks:
			if !outstanding[b.index].IsZero() {
				log.Fatalf("%v received already outstanding block %v", c, b)
			}
			outstanding[b.index] = time.Now()
			var buf [4]byte
			binary.BigEndian.PutUint32(buf[:], uint32(b.index))
			if _, err := c.c.Write(buf[:]); err != nil {
				v(1, "%v write error for %v: %v", c, b, err)
				break loop
			}
		case i := <-c.oldBlocks:
			if i == 0 {
				// read loop is closed
				break loop
			}
			i-- // We added 1 to index in handleReads, remove 1 to get back to correct index.
			if outstanding[i].IsZero() {
				log.Println("%v received non-outstanding block %v from client", c, i)
				break loop
			}
			b := c.s.blocks[i]
			v(3, "%v took %v to process block %v", c, time.Since(outstanding[i]), b)
			outstanding[i] = time.Time{}
			b.unref() // MOST IMPORTANT LINE EVER
		}
	}

	// Close things down.
	log.Println("Connection %v closing", c)
	c.c.Close()
	v(3, "%v marking self old", c)
	c.s.oldConns <- c
	v(3, "%v waiting for reads", c)
	for b := range c.newBlocks {
		v(3, "%v returning unsent %v", c, b)
		b.unref()
	}
	// empty out oldBlocks to allow handleReads to finish, but don't do anything
	// with them.  the next loop (over outstanding) will unref and return all
	// remaining blocks.
	for _ = range c.oldBlocks {
	}
	for i, t := range outstanding {
		if !t.IsZero() {
			b := c.s.blocks[i]
			v(3, "%v returning outstanding %v after %v", c, b, time.Since(t))
			b.unref()
		}
	}
}

// addNewConn is called by the testimonyd server when a new connection has been
// initiated.  The passed-in conn should already have done the initial
// configuration handshake, and be ready to start receiving blocks.
func (s *socket) addNewConn(c *net.UnixConn) {
	newConn := &conn{
		s:         s,
		c:         c,
		newBlocks: make(chan *block),
		oldBlocks: make(chan int),
	}
	log.Printf("%v new connection %v", s, newConn)
	s.currentConns[newConn] = true
	go newConn.run()
}

// block stores ilocal information on a single block within the memory region.
type block struct {
	s     *socket
	index int // my index within the memory block

	r int32 // reference count for this block, uses atomic
}

// ref reference the block.
func (b *block) ref() {
	refs := atomic.AddInt32(&b.r, 1)
	vup(5, 1, "%v refs = %d", b, refs)
}

// unref dereferences the block.  When the refcount reaches zero, the block is
// returned to the kernel via clear().
func (b *block) unref() {
	refs := atomic.AddInt32(&b.r, -1)
	vup(5, 1, "%v unref = %d", b, refs)
	if refs == 0 {
		b.clear()
	} else if refs < 0 {
		panic(fmt.Sprintf("invalid unref of %v to %d", b, refs))
	}
}

// String provides a unique human-readable string.
func (b *block) String() string {
	return fmt.Sprintf("[B:%v:%v]", b.s, b.index)
}

// cblock provides this block as a C tpacket pointer.
func (b *block) cblock() *C.struct_tpacket_hdr_v1 {
	blockDesc := (*C.struct_tpacket_block_desc)(unsafe.Pointer(b.s.ring + uintptr(b.s.conf.BlockSize)*uintptr(b.index)))
	hdr := (*C.struct_tpacket_hdr_v1)(unsafe.Pointer(&blockDesc.hdr[0]))
	return hdr
}

// clear clears the block's block status, returning the block to the kernel so
// it can add additional packets.
func (b *block) clear() {
	vup(3, 2, "%v clear", b)
	b.cblock().block_status = 0
}

// ready returns true when the block status has been set by the kernel, saying
// that packets are ready for processing.
func (b *block) ready() bool {
	return atomic.LoadInt32(&b.r) == 0 && b.cblock().block_status != 0
}

// filter wraps a C BPF filter.
type filter struct {
	bpfs []C.struct_sock_filter
	filt C.struct_sock_fprog
}

// compileFilter compiles a BPF filter, currently by calling tcpdump externally.
func compileFilter(iface, filt string) (*filter, error) {
	cmd := exec.Command("/usr/sbin/tcpdump", "-i", iface, "-ddd", filt)
	var out bytes.Buffer
	cmd.Stdout = &out
	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("could not run tcpdump to compile BPF: %v", err)
	}
	ints := []int{}
	scanner := bufio.NewScanner(&out)
	scanner.Split(bufio.ScanWords)
	for scanner.Scan() {
		i, err := strconv.Atoi(scanner.Text())
		if err != nil {
			return nil, fmt.Errorf("error scanning token %q: %v", scanner.Text(), err)
		}
		ints = append(ints, i)
	}
	if len(ints) == 0 || len(ints) != ints[0]*4+1 {
		return nil, fmt.Errorf("invalid length of tcpdump ints")
	}
	f := &filter{}
	ints = ints[1:]
	for i := 0; i < len(ints); i += 4 {
		f.bpfs = append(f.bpfs, C.struct_sock_filter{
			code: C.__u16(ints[i]),
			jt:   C.__u8(ints[i+1]),
			jf:   C.__u8(ints[i+2]),
			k:    C.__u32(ints[i+3]),
		})
	}
	f.filt.len = C.ushort(len(f.bpfs))
	f.filt.filter = (*C.struct_sock_filter)(unsafe.Pointer(&f.bpfs[0]))
	return f, nil
}
<commit_msg>Buffer channels on socket.<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 main

// #include "daemon.h"
// #include <linux/if_packet.h>
// #include <linux/filter.h>
// #include <stdlib.h>  // for C.free
import "C"

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"net"
	"os/exec"
	"strconv"
	"sync/atomic"
	"time"
	"unsafe"
)

// socket handles a single AF_PACKET socket.  There will be N Socket objects for
// each SocketConfig, where N == FanoutSize.  This Socket stores the file
// descriptor and memory region of a single underlying AF_PACKET socket.
type socket struct {
	num          int                // fanout index for this socket
	conf         SocketConfig       // configuration
	fd           int                // file descriptor for AF_PACKET socket
	newConns     chan *net.UnixConn // new client connections come in here
	oldConns     chan *conn         // old client connections come in here for cleanup
	newBlocks    chan *block        // when a new block is available, it comes in here
	blocks       []*block           // all blocks in the memory region
	currentConns map[*conn]bool     // list of current connections a new block will be sent to
	ring         uintptr            // pointer to memory region
}

// newSocket creates a new Socket object based on a config.
func newSocket(sc SocketConfig, fanoutID int, num int) (*socket, error) {
	s := &socket{
		num:          num,
		conf:         sc,
		newConns:     make(chan *net.UnixConn),
		oldConns:     make(chan *conn),
		newBlocks:    make(chan *block),
		currentConns: map[*conn]bool{},
		blocks:       make([]*block, sc.NumBlocks),
	}

	// Compile the BPF filter, if it was requested.
	var filt *C.struct_sock_fprog
	if sc.Filter != "" {
		f, err := compileFilter(sc.Interface, sc.Filter)
		if err != nil {
			return nil, fmt.Errorf("unable to compile filter %q on interface %q: %v", sc.Filter, sc.Interface, err)
		}
		filt = &f.filt
	}

	// Set up block objects, used to reference count blocks for clients.
	for i := 0; i < sc.NumBlocks; i++ {
		s.blocks[i] = &block{s: s, index: i}
	}

	// Call into our C code to actually create the socket.
	iface := C.CString(sc.Interface)
	defer C.free(unsafe.Pointer(iface))
	var fd C.int
	var ring unsafe.Pointer
	var errStr *C.char
	if _, err := C.AFPacket(iface, C.int(sc.BlockSize), C.int(sc.NumBlocks),
		C.int(sc.BlockTimeoutMillis), C.int(fanoutID), C.int(sc.FanoutType), filt,
		&fd, &ring, &errStr); err != nil {
		return nil, fmt.Errorf("C AFPacket call failed: %v: %v", C.GoString(errStr), err)
	}
	s.fd = int(fd)
	s.ring = uintptr(ring)
	log.Printf("%v set up with %+v", s, sc)
	return s, nil
}

// String returns a unique string for this socket.
func (s *socket) String() string {
	return fmt.Sprintf("[S:%v:%v]", s.conf.SocketName, s.num)
}

// getNewBlocks is a goroutine that watches for new available packet blocks,
// which the run() method passes to clients.
func (s *socket) getNewBlocks() {
	blockIndex := 0
	for {
		b := s.blocks[blockIndex]
		for !b.ready() {
			time.Sleep(time.Millisecond * 10)
		}
		b.ref()
		v(3, "%v got new block %v", s, b)
		s.newBlocks <- b
		blockIndex = (blockIndex + 1) % s.conf.NumBlocks
	}
}

// run handles new connections, old connections, new blocks... basically
// everything.
func (s *socket) run() {
	go s.getNewBlocks()
	for {
		select {
		case c := <-s.newConns:
			// register a new client connection
			s.addNewConn(c)
		case c := <-s.oldConns:
			// unregister an old client connection and close its blocks
			close(c.newBlocks)
			delete(s.currentConns, c)
		case b := <-s.newBlocks:
			// a new block is avaiable, send it out to all clients
			for c, _ := range s.currentConns {
				b.ref()
				select {
				case c.newBlocks <- b:
				default:
					v(1, "failed to send %v to %v", b, c)
					b.unref()
				}
			}
			b.unref()
		}
	}
}

// conn represents a set-up client connection (already initiated and with the
// file descriptor passed through).
type conn struct {
	s         *socket
	c         *net.UnixConn
	newBlocks chan *block
	oldBlocks chan int
}

// String returns a unique string for this connection.
func (c *conn) String() string {
	return fmt.Sprintf("[C:%v:%v]", c.s, c.c.RemoteAddr())
}

// handleReads handles client->server communication.
func (c *conn) handleReads() {
	defer close(c.oldBlocks)
	for {
		// Wait for a block index to be passed back from the client.
		var buf [4]byte
		n, err := c.c.Read(buf[:])
		if err == io.EOF {
			return
		} else if err != nil || n != len(buf) {
			v(1, "%v read error (%d bytes): %v", c, n, err)
			return
		}
		i := int(binary.BigEndian.Uint32(buf[:]))
		if i < 0 || i >= c.s.conf.NumBlocks {
			log.Printf("%v got invalid block %d", c, i)
			return
		}
		// We add one to the returned int so we can detect a closed channel (which
		// returns 0, the zero-value for ints).
		c.oldBlocks <- i + 1
	}
}

// run handles communicating with a single external client via a single
// connection.  It maintains the invariant that every block it gets via the
// newBlocks channel will be unref'd exactly once.  It's up to the block sender
// to ref the blocks for the conn.
func (c *conn) run() {
	go c.handleReads()
	outstanding := make([]time.Time, len(c.s.blocks))

	// Wait for either the reader or writer to stop.
loop:
	for {
		select {
		case b := <-c.newBlocks:
			if !outstanding[b.index].IsZero() {
				log.Fatalf("%v received already outstanding block %v", c, b)
			}
			outstanding[b.index] = time.Now()
			var buf [4]byte
			binary.BigEndian.PutUint32(buf[:], uint32(b.index))
			if _, err := c.c.Write(buf[:]); err != nil {
				v(1, "%v write error for %v: %v", c, b, err)
				break loop
			}
		case i := <-c.oldBlocks:
			if i == 0 {
				// read loop is closed
				break loop
			}
			i-- // We added 1 to index in handleReads, remove 1 to get back to correct index.
			if outstanding[i].IsZero() {
				log.Println("%v received non-outstanding block %v from client", c, i)
				break loop
			}
			b := c.s.blocks[i]
			v(3, "%v took %v to process block %v", c, time.Since(outstanding[i]), b)
			outstanding[i] = time.Time{}
			b.unref() // MOST IMPORTANT LINE EVER
		}
	}

	// Close things down.
	log.Println("Connection %v closing", c)
	c.c.Close()
	v(3, "%v marking self old", c)
	c.s.oldConns <- c
	v(3, "%v waiting for reads", c)
	for b := range c.newBlocks {
		v(3, "%v returning unsent %v", c, b)
		b.unref()
	}
	// empty out oldBlocks to allow handleReads to finish, but don't do anything
	// with them.  the next loop (over outstanding) will unref and return all
	// remaining blocks.
	for _ = range c.oldBlocks {
	}
	for i, t := range outstanding {
		if !t.IsZero() {
			b := c.s.blocks[i]
			v(3, "%v returning outstanding %v after %v", c, b, time.Since(t))
			b.unref()
		}
	}
}

// addNewConn is called by the testimonyd server when a new connection has been
// initiated.  The passed-in conn should already have done the initial
// configuration handshake, and be ready to start receiving blocks.
func (s *socket) addNewConn(c *net.UnixConn) {
	newConn := &conn{
		s:         s,
		c:         c,
		newBlocks: make(chan *block, len(s.blocks)),
		oldBlocks: make(chan int, len(s.blocks)),
	}
	log.Printf("%v new connection %v", s, newConn)
	s.currentConns[newConn] = true
	go newConn.run()
}

// block stores ilocal information on a single block within the memory region.
type block struct {
	s     *socket
	index int // my index within the memory block

	r int32 // reference count for this block, uses atomic
}

// ref reference the block.
func (b *block) ref() {
	refs := atomic.AddInt32(&b.r, 1)
	vup(5, 1, "%v refs = %d", b, refs)
}

// unref dereferences the block.  When the refcount reaches zero, the block is
// returned to the kernel via clear().
func (b *block) unref() {
	refs := atomic.AddInt32(&b.r, -1)
	vup(5, 1, "%v unref = %d", b, refs)
	if refs == 0 {
		b.clear()
	} else if refs < 0 {
		panic(fmt.Sprintf("invalid unref of %v to %d", b, refs))
	}
}

// String provides a unique human-readable string.
func (b *block) String() string {
	return fmt.Sprintf("[B:%v:%v]", b.s, b.index)
}

// cblock provides this block as a C tpacket pointer.
func (b *block) cblock() *C.struct_tpacket_hdr_v1 {
	blockDesc := (*C.struct_tpacket_block_desc)(unsafe.Pointer(b.s.ring + uintptr(b.s.conf.BlockSize)*uintptr(b.index)))
	hdr := (*C.struct_tpacket_hdr_v1)(unsafe.Pointer(&blockDesc.hdr[0]))
	return hdr
}

// clear clears the block's block status, returning the block to the kernel so
// it can add additional packets.
func (b *block) clear() {
	vup(3, 2, "%v clear", b)
	b.cblock().block_status = 0
}

// ready returns true when the block status has been set by the kernel, saying
// that packets are ready for processing.
func (b *block) ready() bool {
	return atomic.LoadInt32(&b.r) == 0 && b.cblock().block_status != 0
}

// filter wraps a C BPF filter.
type filter struct {
	bpfs []C.struct_sock_filter
	filt C.struct_sock_fprog
}

// compileFilter compiles a BPF filter, currently by calling tcpdump externally.
func compileFilter(iface, filt string) (*filter, error) {
	cmd := exec.Command("/usr/sbin/tcpdump", "-i", iface, "-ddd", filt)
	var out bytes.Buffer
	cmd.Stdout = &out
	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("could not run tcpdump to compile BPF: %v", err)
	}
	ints := []int{}
	scanner := bufio.NewScanner(&out)
	scanner.Split(bufio.ScanWords)
	for scanner.Scan() {
		i, err := strconv.Atoi(scanner.Text())
		if err != nil {
			return nil, fmt.Errorf("error scanning token %q: %v", scanner.Text(), err)
		}
		ints = append(ints, i)
	}
	if len(ints) == 0 || len(ints) != ints[0]*4+1 {
		return nil, fmt.Errorf("invalid length of tcpdump ints")
	}
	f := &filter{}
	ints = ints[1:]
	for i := 0; i < len(ints); i += 4 {
		f.bpfs = append(f.bpfs, C.struct_sock_filter{
			code: C.__u16(ints[i]),
			jt:   C.__u8(ints[i+1]),
			jf:   C.__u8(ints[i+2]),
			k:    C.__u32(ints[i+3]),
		})
	}
	f.filt.len = C.ushort(len(f.bpfs))
	f.filt.filter = (*C.struct_sock_filter)(unsafe.Pointer(&f.bpfs[0]))
	return f, nil
}
<|endoftext|>


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

package zkwrangler

import (
	"path"
	"reflect"
	"sort"
	"sync"

	"code.google.com/p/vitess/go/relog"
	tm "code.google.com/p/vitess/go/vt/tabletmanager"
	"code.google.com/p/vitess/go/zk"
)

// If error is not nil, the results in the dictionary are incomplete.
func GetTabletMap(zconn zk.Conn, tabletPaths []string) (map[string]*tm.TabletInfo, error) {
	wg := sync.WaitGroup{}
	mutex := sync.Mutex{}

	tabletMap := make(map[string]*tm.TabletInfo)
	var someError error

	for _, path := range tabletPaths {
		tabletPath := path
		wg.Add(1)
		go func() {
			defer wg.Done()
			tabletInfo, err := tm.ReadTablet(zconn, tabletPath)
			mutex.Lock()
			if err != nil {
				relog.Warning("%v: %v", tabletPath, err)
				someError = err
			} else {
				tabletMap[tabletPath] = tabletInfo
			}
			mutex.Unlock()
		}()
	}

	wg.Wait()

	mutex.Lock()
	defer mutex.Unlock()
	return tabletMap, someError
}

// If error is not nil, the results in the dictionary are incomplete.
func GetTabletMapForShard(zconn zk.Conn, zkShardPath string) (map[string]*tm.TabletInfo, error) {
	aliases, err := tm.FindAllTabletAliasesInShard(zconn, zkShardPath)
	if err != nil {
		return nil, err
	}

	shardTablets := make([]string, len(aliases))
	for i, alias := range aliases {
		shardTablets[i] = tm.TabletPathForAlias(alias)
	}

	return GetTabletMap(zconn, shardTablets)
}

// Return a sorted list of tablets.
func GetAllTablets(zconn zk.Conn, zkVtPath string) ([]*tm.TabletInfo, error) {
	zkTabletsPath := path.Join(zkVtPath, "tablets")
	children, _, err := zconn.Children(zkTabletsPath)
	if err != nil {
		return nil, err
	}

	sort.Strings(children)
	tabletPaths := make([]string, len(children))
	for i, child := range children {
		tabletPaths[i] = path.Join(zkTabletsPath, child)
	}

	tabletMap, _ := GetTabletMap(zconn, tabletPaths)
	tablets := make([]*tm.TabletInfo, 0, len(tabletPaths))
	for _, tabletPath := range tabletPaths {
		tabletInfo, ok := tabletMap[tabletPath]
		if !ok {
			relog.Warning("failed to load tablet %v", tabletPath)
		}
		tablets = append(tablets, tabletInfo)
	}

	return tablets, nil
}

// Copy keys from from map m into a new slice with the type specified
// by typeHint.  Reflection can't make a new slice type just based on
// the key type AFAICT.
func CopyMapKeys(m interface{}, typeHint interface{}) interface{} {
	mapVal := reflect.ValueOf(m)
	keys := reflect.MakeSlice(reflect.TypeOf(typeHint), 0, mapVal.Len())
	for _, k := range mapVal.MapKeys() {
		keys = reflect.Append(keys, k)
	}
	return keys.Interface()
}

func CopyMapValues(m interface{}, typeHint interface{}) interface{} {
	mapVal := reflect.ValueOf(m)
	vals := reflect.MakeSlice(reflect.TypeOf(typeHint), 0, mapVal.Len())
	for _, k := range mapVal.MapKeys() {
		vals = reflect.Append(vals, mapVal.MapIndex(k))
	}
	return vals.Interface()
}
<commit_msg>ignore data race on deleted nodes<commit_after>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package zkwrangler

import (
	"path"
	"reflect"
	"sort"
	"sync"

	"code.google.com/p/vitess/go/relog"
	tm "code.google.com/p/vitess/go/vt/tabletmanager"
	"code.google.com/p/vitess/go/zk"
	"launchpad.net/gozk/zookeeper"
)

// If error is not nil, the results in the dictionary are incomplete.
func GetTabletMap(zconn zk.Conn, tabletPaths []string) (map[string]*tm.TabletInfo, error) {
	wg := sync.WaitGroup{}
	mutex := sync.Mutex{}

	tabletMap := make(map[string]*tm.TabletInfo)
	var someError error

	for _, path := range tabletPaths {
		tabletPath := path
		wg.Add(1)
		go func() {
			defer wg.Done()
			tabletInfo, err := tm.ReadTablet(zconn, tabletPath)
			mutex.Lock()
			if err != nil {
				relog.Warning("%v: %v", tabletPath, err)
				// There can be data races removing nodes - ignore them for now.
				if !zookeeper.IsError(err, zookeeper.ZNONODE) {
					someError = err
				}
			} else {
				tabletMap[tabletPath] = tabletInfo
			}
			mutex.Unlock()
		}()
	}

	wg.Wait()

	mutex.Lock()
	defer mutex.Unlock()
	return tabletMap, someError
}

// If error is not nil, the results in the dictionary are incomplete.
func GetTabletMapForShard(zconn zk.Conn, zkShardPath string) (map[string]*tm.TabletInfo, error) {
	aliases, err := tm.FindAllTabletAliasesInShard(zconn, zkShardPath)
	if err != nil {
		return nil, err
	}

	shardTablets := make([]string, len(aliases))
	for i, alias := range aliases {
		shardTablets[i] = tm.TabletPathForAlias(alias)
	}

	return GetTabletMap(zconn, shardTablets)
}

// Return a sorted list of tablets.
func GetAllTablets(zconn zk.Conn, zkVtPath string) ([]*tm.TabletInfo, error) {
	zkTabletsPath := path.Join(zkVtPath, "tablets")
	children, _, err := zconn.Children(zkTabletsPath)
	if err != nil {
		return nil, err
	}

	sort.Strings(children)
	tabletPaths := make([]string, len(children))
	for i, child := range children {
		tabletPaths[i] = path.Join(zkTabletsPath, child)
	}

	tabletMap, _ := GetTabletMap(zconn, tabletPaths)
	tablets := make([]*tm.TabletInfo, 0, len(tabletPaths))
	for _, tabletPath := range tabletPaths {
		tabletInfo, ok := tabletMap[tabletPath]
		if !ok {
			relog.Warning("failed to load tablet %v", tabletPath)
		}
		tablets = append(tablets, tabletInfo)
	}

	return tablets, nil
}

// Copy keys from from map m into a new slice with the type specified
// by typeHint.  Reflection can't make a new slice type just based on
// the key type AFAICT.
func CopyMapKeys(m interface{}, typeHint interface{}) interface{} {
	mapVal := reflect.ValueOf(m)
	keys := reflect.MakeSlice(reflect.TypeOf(typeHint), 0, mapVal.Len())
	for _, k := range mapVal.MapKeys() {
		keys = reflect.Append(keys, k)
	}
	return keys.Interface()
}

func CopyMapValues(m interface{}, typeHint interface{}) interface{} {
	mapVal := reflect.ValueOf(m)
	vals := reflect.MakeSlice(reflect.TypeOf(typeHint), 0, mapVal.Len())
	for _, k := range mapVal.MapKeys() {
		vals = reflect.Append(vals, mapVal.MapIndex(k))
	}
	return vals.Interface()
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pbdi "github.com/brotherlogic/discovery/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
	"github.com/brotherlogic/goserver/utils"

	//Needed to pull in gzip encoding init
	_ "google.golang.org/grpc/encoding/gzip"
)

func findServer(ctx context.Context, name, server string) (string, int) {
	conn, _ := grpc.Dial(utils.Discover, grpc.WithInsecure())
	defer conn.Close()

	registry := pbdi.NewDiscoveryServiceClient(conn)
	rs, _ := registry.ListAllServices(ctx, &pbdi.ListRequest{})

	for _, r := range rs.GetServices().Services {
		if r.Identifier == server && r.Name == name {
			return r.Ip, int(r.Port)
		}
	}

	return "", -1
}

func findServers(ctx context.Context) []*pbdi.RegistryEntry {
	conn, _ := grpc.Dial(utils.Discover, grpc.WithInsecure())
	defer conn.Close()

	registry := pbdi.NewDiscoveryServiceClient(conn)
	rs, _ := registry.ListAllServices(ctx, &pbdi.ListRequest{})

	list := make([]*pbdi.RegistryEntry, 0)
	for _, r := range rs.GetServices().Services {
		if r.Name == "gobuildslave" {
			list = append(list, r)
		}
	}

	return list
}

func main() {
	buildFlags := flag.NewFlagSet("BuildServer", flag.ExitOnError)
	var name = buildFlags.String("name", "", "Name of the binary to build")
	var server = buildFlags.String("server", "", "Name of the server to build on")
	var hosta = buildFlags.String("hosta", "", "Name of the server to build on")

	ctx, cancel := utils.BuildContext("gobuildslavecli-"+os.Args[1], "gobuildslave")
	defer cancel()

	if len(os.Args) <= 1 {
		fmt.Printf("Commands: build run\n")
	} else {
		switch os.Args[1] {
		case "build":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewGoBuildSlaveClient(conn)
				_, err := registry.BuildJob(ctx, &pb.JobSpec{Name: *name})
				if err != nil {
					log.Fatalf("Error building job: %v", err)
				}
			}
		case "run":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewGoBuildSlaveClient(conn)
				_, err := registry.Run(ctx, &pb.JobSpec{Name: *name, Server: *server})
				if err != nil {
					log.Fatalf("Error building job: %v", err)
				}
			}
		case "kill":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewGoBuildSlaveClient(conn)
				_, err := registry.Kill(ctx, &pb.JobSpec{Name: *name, Server: *server})
				if err != nil {
					log.Fatalf("Error building job: %v", err)
				}
			}
		case "list":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewGoBuildSlaveClient(conn)
				res, err := registry.List(ctx, &pb.Empty{})
				if err != nil {
					log.Fatalf("Error building job: %v", err)
				}
				for _, r := range res.Details {
					fmt.Printf("%v (%v) - %v\n", r.Spec.Name, time.Unix(r.StartTime, 0).Format("02/01 15:04"), r)
				}
			}
		case "nlist":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				conn, err := grpc.Dial(*hosta+":53604", grpc.WithInsecure())
				if err != nil {
					log.Fatalf("Error dialling: %v", err)
				}
				defer conn.Close()

				registry := pb.NewBuildSlaveClient(conn)
				res, err := registry.ListJobs(ctx, &pb.ListRequest{})
				if err != nil {
					log.Fatalf("Error listing job with: %v", err)
				}
				for _, r := range res.Jobs {
					fmt.Printf("%v {%v}-> %v [%v] (%v)\n", r.Job.Name, r.GetPort(), r.State, r.GetSubState(), time.Unix(r.GetLastUpdateTime(), 0))
				}
			}
		case "nbuild":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewBuildSlaveClient(conn)
				_, err := registry.RunJob(ctx, &pb.RunRequest{Job: &pb.Job{Name: "recordalerting", GoPath: "github.com/brotherlogic/recordalerting"}})
				if err != nil {
					log.Fatalf("Error listing job: %v", err)
				}
			}
		case "nconfig":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				conn, err := grpc.Dial(*hosta+":53604", grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewBuildSlaveClient(conn)
				res, err := registry.SlaveConfig(ctx, &pb.ConfigRequest{})
				if err != nil {
					log.Fatalf("Error listing job: %v", err)
				}
				fmt.Printf("%v\n", res)
			}
		case "nlistall":
			servers := findServers(ctx)

			if len(servers) == 0 {
				log.Fatalf("No Servers found!")
			}

			for _, s := range servers {
				conn, _ := grpc.Dial(s.GetIp()+":"+strconv.Itoa(int(s.GetPort())), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewBuildSlaveClient(conn)
				res, err := registry.ListJobs(ctx, &pb.ListRequest{})
				if err != nil {
					fmt.Printf("Error discovering job with: %v\n", err)
				} else {
					for _, r := range res.Jobs {
						fmt.Printf("%v:%v -> %v [%v] (%v)\n", r.Job.Name, s.GetIdentifier(), r.State, r.GetSubState(), time.Unix(r.GetLastUpdateTime(), 0))
					}
				}
			}
		}

	}
}
<commit_msg>Also include running version<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pbdi "github.com/brotherlogic/discovery/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
	"github.com/brotherlogic/goserver/utils"

	//Needed to pull in gzip encoding init
	_ "google.golang.org/grpc/encoding/gzip"
)

func findServer(ctx context.Context, name, server string) (string, int) {
	conn, _ := grpc.Dial(utils.Discover, grpc.WithInsecure())
	defer conn.Close()

	registry := pbdi.NewDiscoveryServiceClient(conn)
	rs, _ := registry.ListAllServices(ctx, &pbdi.ListRequest{})

	for _, r := range rs.GetServices().Services {
		if r.Identifier == server && r.Name == name {
			return r.Ip, int(r.Port)
		}
	}

	return "", -1
}

func findServers(ctx context.Context) []*pbdi.RegistryEntry {
	conn, _ := grpc.Dial(utils.Discover, grpc.WithInsecure())
	defer conn.Close()

	registry := pbdi.NewDiscoveryServiceClient(conn)
	rs, _ := registry.ListAllServices(ctx, &pbdi.ListRequest{})

	list := make([]*pbdi.RegistryEntry, 0)
	for _, r := range rs.GetServices().Services {
		if r.Name == "gobuildslave" {
			list = append(list, r)
		}
	}

	return list
}

func main() {
	buildFlags := flag.NewFlagSet("BuildServer", flag.ExitOnError)
	var name = buildFlags.String("name", "", "Name of the binary to build")
	var server = buildFlags.String("server", "", "Name of the server to build on")
	var hosta = buildFlags.String("hosta", "", "Name of the server to build on")

	ctx, cancel := utils.BuildContext("gobuildslavecli-"+os.Args[1], "gobuildslave")
	defer cancel()

	if len(os.Args) <= 1 {
		fmt.Printf("Commands: build run\n")
	} else {
		switch os.Args[1] {
		case "build":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewGoBuildSlaveClient(conn)
				_, err := registry.BuildJob(ctx, &pb.JobSpec{Name: *name})
				if err != nil {
					log.Fatalf("Error building job: %v", err)
				}
			}
		case "run":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewGoBuildSlaveClient(conn)
				_, err := registry.Run(ctx, &pb.JobSpec{Name: *name, Server: *server})
				if err != nil {
					log.Fatalf("Error building job: %v", err)
				}
			}
		case "kill":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewGoBuildSlaveClient(conn)
				_, err := registry.Kill(ctx, &pb.JobSpec{Name: *name, Server: *server})
				if err != nil {
					log.Fatalf("Error building job: %v", err)
				}
			}
		case "list":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewGoBuildSlaveClient(conn)
				res, err := registry.List(ctx, &pb.Empty{})
				if err != nil {
					log.Fatalf("Error building job: %v", err)
				}
				for _, r := range res.Details {
					fmt.Printf("%v (%v) - %v\n", r.Spec.Name, time.Unix(r.StartTime, 0).Format("02/01 15:04"), r)
				}
			}
		case "nlist":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				conn, err := grpc.Dial(*hosta+":53604", grpc.WithInsecure())
				if err != nil {
					log.Fatalf("Error dialling: %v", err)
				}
				defer conn.Close()

				registry := pb.NewBuildSlaveClient(conn)
				res, err := registry.ListJobs(ctx, &pb.ListRequest{})
				if err != nil {
					log.Fatalf("Error listing job with: %v", err)
				}
				for _, r := range res.Jobs {
					fmt.Printf("%v {%v / %v}-> %v [%v] (%v)\n", r.Job.Name, r.GetPort(), r.GetRunningVersion(), r.State, r.GetSubState(), time.Unix(r.GetLastUpdateTime(), 0))
				}
			}
		case "nbuild":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				host, port := findServer(ctx, "gobuildslave", *server)

				conn, _ := grpc.Dial(host+":"+strconv.Itoa(port), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewBuildSlaveClient(conn)
				_, err := registry.RunJob(ctx, &pb.RunRequest{Job: &pb.Job{Name: "recordalerting", GoPath: "github.com/brotherlogic/recordalerting"}})
				if err != nil {
					log.Fatalf("Error listing job: %v", err)
				}
			}
		case "nconfig":
			if err := buildFlags.Parse(os.Args[2:]); err == nil {
				conn, err := grpc.Dial(*hosta+":53604", grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewBuildSlaveClient(conn)
				res, err := registry.SlaveConfig(ctx, &pb.ConfigRequest{})
				if err != nil {
					log.Fatalf("Error listing job: %v", err)
				}
				fmt.Printf("%v\n", res)
			}
		case "nlistall":
			servers := findServers(ctx)

			if len(servers) == 0 {
				log.Fatalf("No Servers found!")
			}

			for _, s := range servers {
				conn, _ := grpc.Dial(s.GetIp()+":"+strconv.Itoa(int(s.GetPort())), grpc.WithInsecure())
				defer conn.Close()

				registry := pb.NewBuildSlaveClient(conn)
				res, err := registry.ListJobs(ctx, &pb.ListRequest{})
				if err != nil {
					fmt.Printf("Error discovering job with: %v\n", err)
				} else {
					for _, r := range res.Jobs {
						fmt.Printf("%v:%v -> %v [%v] (%v)\n", r.Job.Name, s.GetIdentifier(), r.State, r.GetSubState(), time.Unix(r.GetLastUpdateTime(), 0))
					}
				}
			}
		}

	}
}
<|endoftext|>


<commit_before>package main

import (
	"gopkg.in/telegram-bot-api.v4"
	"log"
	"os"
	"fmt"
	"net/http"
	"path"
	"html/template"
	"time"
	"encoding/json"
	"strconv"
)

var (
	tmpID[10] int64
	flg bool
	i int = 0
)
type Config struct {
	TelegramBotToken string
	ChatIdSite int64  `json:",string"`
	ChatIdOnline int64 `json:",string"`
}

func conf(f string) (string, int64, int64) {
	file, ok := os.Open(f)
	if ok != nil {
		log.Panic(ok)
	}
	decoder := json.NewDecoder(file)
	config := Config{}
	err := decoder.Decode(&config)
	if err != nil {
		log.Panic(err)
	}

	return config.TelegramBotToken, config.ChatIdSite, config.ChatIdOnline
}

func index(w http.ResponseWriter, _ *http.Request) {
	// создаем шаблон
	fp := path.Join("", "/workspace/index.html")
	tmpl, err := template.ParseFiles(fp)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	// отвечаем по шаблону
	if err := tmpl.Execute(w, nil); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func sendForm(w http.ResponseWriter, r *http.Request) {
	// используя токен создаем новый инстанс бота
	token, ids, _ :=conf("config.json")
	bot, err := tgbotapi.NewBotAPI(token)
	if err != nil {
		log.Panic(err)
	}
	// Парсим содержимое формы
	r.ParseForm()
	// Пишем лог парсинга формы
	log.Printf("Test name: %s", r.PostForm)
	// Проверка на заполнение (Доработать!)
	if r.PostFormValue("name") == "" {
		fmt.Fprintln(w, "Не заполнено поле \"Ваше Имя\"")
	} else if len(r.PostFormValue("phone")) > 12 || r.PostFormValue("phone") == ""  {
		fmt.Fprintln(w, "Не заполнено поле \"Телефон\", либо не коретно введен номер")
	} else if r.PostFormValue("messages") == "" {
		fmt.Fprintln(w, "Не заполнено поле \"Сообщение\"")
	} else {
		fmt.Fprintln(w, "Сообщение отправлено!")
		// Формируем текс сообщения в телеграм
		text := fmt.Sprintf(
			"`%s`\n" +
				"*Имя отправителя:* _%s_\n" +
				"*Телефон:* _%s_\n" +
				"*Сообщение:* %s\n",
				"Сообщение с сайта",
			r.PostFormValue("name"),
			r.PostFormValue("phone"),
			r.PostFormValue("messages"))
		msg := tgbotapi.NewMessage(ids, text)
		// Парсем в "Markdown"
		msg.ParseMode = "markdown"
		// Отправляем
		bot.Send(msg)
	}
}

func chatbot(b *tgbotapi.BotAPI) {
	_, _, ido := conf("config.json")
	// Создаем конфиг для общения с ботом
	u := tgbotapi.NewUpdate( 0)
	u.Timeout = 60
	// используя конфиг u создаем канал в который будут прилетать новые сообщения
	updates, err := b.GetUpdatesChan(u)
	if err != nil {
		log.Panic(err)
	}
	// в канал updates прилетают структуры типа Update
	// вычитываем их и обрабатываем
	for update := range updates {
		// универсальный ответ на любое сообщение (Доработать!)
		reply := ""
		if update.Message == nil {
			continue
		}
		// логируем от кого какое сообщение пришло
		log.Printf("Заголовок чата: [%s] Тип чата: [%s]", update.Message.Chat.Title, update.Message.Chat.Type)
		log.Printf("Имя: [%s]", update.Message.From.FirstName)
		log.Printf("ID отпровителя: [%d]", update.Message.From.ID)
		log.Printf("ID чата:[%d] Сообщение: %s", update.Message.Chat.ID, update.Message.Text)
		log.Printf("№ в очереди: [%s]", strconv.Itoa(i))
		if update.Message.Document != nil {
			log.Printf("Имя документа: [%s]", update.Message.Document.FileName)
		}
		if update.Message.Sticker != nil {
			log.Printf("Имя стикера: [%s]", update.Message.Sticker.Emoji)
		}

		if update.Message.Chat.IsPrivate() {
			if tmpID[i] != update.Message.Chat.ID {
				i++
			}
			tmpID[i] = update.Message.Chat.ID
			reply = fmt.Sprintf(
				"*Переадрисовано от:* _%s_ _%s_\n"+"`в очереди: %d`\n\n"+"%s",
				update.Message.From.FirstName, update.Message.From.LastName, i, update.Message.Text)
			msg := tgbotapi.NewMessage(ido, reply)
			msg.ParseMode = "markdown"
			b.Send(msg)
			if update.Message.Document != nil {
				b.Send(tgbotapi.NewDocumentShare(ido,update.Message.Document.FileID))
			}
			if update.Message.Sticker != nil {
				b.Send(tgbotapi.NewStickerShare(ido,update.Message.Sticker.FileID))
			}

		} else {

			switch update.Message.Command() {
			case "start":
				switch update.Message.CommandArguments() {
				case strconv.Itoa(i):
					flg = true
					reply = fmt.Sprintf("*%s*\n"+"*Специалист:* _%s_\n", "Добрый день. С вами общается:",
						update.Message.From.FirstName)
					msg := tgbotapi.NewMessage(tmpID[i], reply)
					msg.ParseMode = "markdown"
					// отправляем
					b.Send(msg)
				default:
					reply = fmt.Sprintf("`Укажите номер очереди`")
					msg := tgbotapi.NewMessage(ido, reply)
					msg.ParseMode = "markdown"
					// отправляем
					b.Send(msg)
				}
			case "stop":
				if flg {
					flg = false
					reply = fmt.Sprintf("`Чат завершен`")
					msg := tgbotapi.NewMessage(tmpID[i], reply)
					msg.ParseMode = "markdown"
					// отправляем
					b.Send(msg)
				} else {
					reply = fmt.Sprintf("`Вы не начали чат с клиентом`\n"+"``` /help - для спавки```")
					msg := tgbotapi.NewMessage(ido, reply)
					msg.ParseMode = "markdown"
					// отправляем
					b.Send(msg)
				}
			case "help":
				reply = fmt.Sprintf("*Воспользуйтесь следующими командами для общения с клиентом:*\n"+
					"``` /start [номер в очереди...] - команда запустит чат с клиентом по номеру очереди```\n"+
						"``` /stop - прекратит текущий чат с клиентом```")
				msg := tgbotapi.NewMessage(ido, reply)
				msg.ParseMode = "markdown"
				// отправляем
				b.Send(msg)
			}
			if flg {
				if update.Message.Text == "/start "+strconv.Itoa(i) {
					continue
				}
				msg := tgbotapi.NewMessage(tmpID[i], update.Message.Text)
				b.Send(msg)
				if update.Message.Document != nil {
					b.Send(tgbotapi.NewDocumentShare(tmpID[i],update.Message.Document.FileID))
				}
				if update.Message.Sticker != nil {
					b.Send(tgbotapi.NewStickerShare(tmpID[i],update.Message.Sticker.FileID))
				}
			}
		}
	}
}

func main() {
	token, ids, ido := conf("config.json")
	// используя токен создаем новый инстанс бота
	bot, err := tgbotapi.NewBotAPI(token)
	if err != nil {
		log.Panic(err)
	}
	// горутина
	go chatbot(bot)
	//time log
	loc, _ := time.LoadLocation("Europe/Minsk")
	date := time.Now().In(loc).Format(time.RFC3339)
	// пишем лог авторизации бота
	log.Printf("Authorized on account %s", bot.Self.UserName)
	log.Printf("Token: %s; IdSite %d; IdOnline %d", token, ids, ido)
	// Отправляем сообщение в телеграм, что бот активен
	bot.Send(tgbotapi.NewMessage(ids, fmt.Sprintf("%s: %s", date, "Бот запущен, ожидаются сообщения")))
	// Горутина для общения с ботом
	// Включаем сервер, прописываем роуты, статику
	http.HandleFunc("/", index)
	http.HandleFunc("/process", sendForm)
	http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("/workspace/css"))))
	http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("/workspace/img"))))
	http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("/workspace/js"))))
	http.Handle("/vendor/", http.StripPrefix("/vendor/", http.FileServer(http.Dir("/workspace/vendor"))))
	log.Fatal(http.ListenAndServe(":8080", nil))
}<commit_msg>Update<commit_after>package main

import (
	"gopkg.in/telegram-bot-api.v4"
	"log"
	"os"
	"fmt"
	"net/http"
	"path"
	"html/template"
	"time"
	"encoding/json"
	"strconv"
)

var (
	tmpID[10] int64
	flg bool
	i int = 0
	a int
	usgroup int
)
type Config struct {
	TelegramBotToken string
	ChatIdSite int64  `json:",string"`
	ChatIdOnline int64 `json:",string"`
}

func conf(f string) (string, int64, int64) {
	file, ok := os.Open(f)
	if ok != nil {
		log.Panic(ok)
	}
	decoder := json.NewDecoder(file)
	config := Config{}
	err := decoder.Decode(&config)
	if err != nil {
		log.Panic(err)
	}

	return config.TelegramBotToken, config.ChatIdSite, config.ChatIdOnline
}

func index(w http.ResponseWriter, _ *http.Request) {
	// создаем шаблон
	fp := path.Join("", "/workspace/index.html")
	tmpl, err := template.ParseFiles(fp)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	// отвечаем по шаблону
	if err := tmpl.Execute(w, nil); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func sendForm(w http.ResponseWriter, r *http.Request) {
	// используя токен создаем новый инстанс бота
	token, ids, _ :=conf("config.json")
	bot, err := tgbotapi.NewBotAPI(token)
	if err != nil {
		log.Panic(err)
	}
	// Парсим содержимое формы
	r.ParseForm()
	// Пишем лог парсинга формы
	log.Printf("Test name: %s", r.PostForm)
	// Проверка на заполнение (Доработать!)
	if r.PostFormValue("name") == "" {
		fmt.Fprintln(w, "Не заполнено поле \"Ваше Имя\"")
	} else if len(r.PostFormValue("phone")) > 12 || r.PostFormValue("phone") == ""  {
		fmt.Fprintln(w, "Не заполнено поле \"Телефон\", либо не коретно введен номер")
	} else if r.PostFormValue("messages") == "" {
		fmt.Fprintln(w, "Не заполнено поле \"Сообщение\"")
	} else {
		fmt.Fprintln(w, "Сообщение отправлено!")
		// Формируем текс сообщения в телеграм
		text := fmt.Sprintf(
			"`%s`\n" +
				"*Имя отправителя:* _%s_\n" +
				"*Телефон:* _%s_\n" +
				"*Сообщение:* %s\n",
				"Сообщение с сайта",
			r.PostFormValue("name"),
			r.PostFormValue("phone"),
			r.PostFormValue("messages"))
		msg := tgbotapi.NewMessage(ids, text)
		// Парсем в "Markdown"
		msg.ParseMode = "markdown"
		// Отправляем
		bot.Send(msg)
	}
}

func chatbot(b *tgbotapi.BotAPI) {
	_, _, ido := conf("config.json")
	// Создаем конфиг для общения с ботом
	u := tgbotapi.NewUpdate( 0)
	u.Timeout = 60
	// используя конфиг u создаем канал в который будут прилетать новые сообщения
	updates, err := b.GetUpdatesChan(u)
	if err != nil {
		log.Panic(err)
	}
	// в канал updates прилетают структуры типа Update
	// вычитываем их и обрабатываем
	for update := range updates {
		// универсальный ответ на любое сообщение (Доработать!)
		reply := ""
		if update.Message == nil {
			continue
		}
		// логируем от кого какое сообщение пришло
		log.Printf("Заголовок чата: [%s] Тип чата: [%s]", update.Message.Chat.Title, update.Message.Chat.Type)
		log.Printf("Имя: [%s]", update.Message.From.FirstName)
		log.Printf("ID отпровителя: [%d]", update.Message.From.ID)
		log.Printf("ID чата:[%d] Сообщение: %s", update.Message.Chat.ID, update.Message.Text)
		if update.Message.Document != nil {
			log.Printf("Имя документа: [%s]", update.Message.Document.FileName)
		}
		if update.Message.Sticker != nil {
			log.Printf("Имя стикера: [%s]", update.Message.Sticker.Emoji)
		}

		if update.Message.Chat.IsPrivate() {
			if tmpID[i] != update.Message.Chat.ID {
				i++
			}
			log.Printf("№ в очереди: [%s]", strconv.Itoa(i))
			tmpID[i] = update.Message.Chat.ID
			reply = fmt.Sprintf(
				"*Переадрисовано от:* _%s_ _%s_\n"+"`в очереди: %d`\n\n"+"%s",
				update.Message.From.FirstName, update.Message.From.LastName, i, update.Message.Text)
			msg := tgbotapi.NewMessage(ido, reply)
			msg.ParseMode = "markdown"
			b.Send(msg)
			if update.Message.Document != nil {
				b.Send(tgbotapi.NewDocumentShare(ido,update.Message.Document.FileID))
			}
			if update.Message.Sticker != nil {
				b.Send(tgbotapi.NewStickerShare(ido,update.Message.Sticker.FileID))
			}

		} else {

			switch update.Message.Command() {
			case "start":
				usgroup = update.Message.From.ID
				for j := 0; j <= len(tmpID); j++ {
					if update.Message.CommandArguments() == strconv.Itoa(j) {
						flg = true
						reply = fmt.Sprintf("*%s*\n"+"*Специалист:* _%s_\n", "Добрый день. С вами общается:",
							update.Message.From.FirstName)
						msg := tgbotapi.NewMessage(tmpID[j], reply)
						msg.ParseMode = "markdown"
						// отправляем
						b.Send(msg)
						a = j
						break
					} else if update.Message.CommandArguments() == "" {
						reply = fmt.Sprintf("`Укажите номер очереди`")
						msg := tgbotapi.NewMessage(ido, reply)
						msg.ParseMode = "markdown"
						// отправляем
						b.Send(msg)
						break
					}
				}
			case "stop":
				if flg {
					flg = false
					reply = fmt.Sprintf("`Чат завершен`")
					msg := tgbotapi.NewMessage(tmpID[i], reply)
					msg.ParseMode = "markdown"
					// отправляем
					b.Send(msg)
				} else {
					reply = fmt.Sprintf("`Вы не начали чат с клиентом`\n"+"``` /help - для спавки```")
					msg := tgbotapi.NewMessage(ido, reply)
					msg.ParseMode = "markdown"
					// отправляем
					b.Send(msg)
				}
			case "help":
				reply = fmt.Sprintf("*Воспользуйтесь следующими командами для общения с клиентом:*\n"+
					"``` /start [номер в очереди...] - команда запустит чат с клиентом по номеру очереди```\n"+
						"``` /stop - прекратит текущий чат с клиентом```")
				msg := tgbotapi.NewMessage(ido, reply)
				msg.ParseMode = "markdown"
				// отправляем
				b.Send(msg)
			}
			if flg && usgroup == update.Message.From.ID {
				if update.Message.Text == "/start "+strconv.Itoa(a) {
					continue
				}
				msg := tgbotapi.NewMessage(tmpID[a], update.Message.Text)
				b.Send(msg)
				if update.Message.Document != nil {
					b.Send(tgbotapi.NewDocumentShare(tmpID[a],update.Message.Document.FileID))
				}
				if update.Message.Sticker != nil {
					b.Send(tgbotapi.NewStickerShare(tmpID[a],update.Message.Sticker.FileID))
				}
			}
		}
	}
}

func main() {
	token, ids, ido := conf("config.json")
	// используя токен создаем новый инстанс бота
	bot, err := tgbotapi.NewBotAPI(token)
	if err != nil {
		log.Panic(err)
	}
	// горутина
	go chatbot(bot)
	//time log
	loc, _ := time.LoadLocation("Europe/Minsk")
	date := time.Now().In(loc).Format(time.RFC3339)
	// пишем лог авторизации бота
	log.Printf("Authorized on account %s", bot.Self.UserName)
	log.Printf("Token: %s; IdSite %d; IdOnline %d", token, ids, ido)
	// Отправляем сообщение в телеграм, что бот активен
	bot.Send(tgbotapi.NewMessage(ids, fmt.Sprintf("%s: %s", date, "Бот запущен, ожидаются сообщения")))
	// Горутина для общения с ботом
	// Включаем сервер, прописываем роуты, статику
	http.HandleFunc("/", index)
	http.HandleFunc("/process", sendForm)
	http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("/workspace/css"))))
	http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("/workspace/img"))))
	http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("/workspace/js"))))
	http.Handle("/vendor/", http.StripPrefix("/vendor/", http.FileServer(http.Dir("/workspace/vendor"))))
	log.Fatal(http.ListenAndServe(":8080", nil))
}<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"net"
	"io/ioutil"
	"time"
	"log"
)

func main() {
	usage := fmt.Sprintf("Usage: $s client|server host:port\n", os.Args[0])
	if len(os.Args) != 3 {
		log.Fatal(usage)
	}
	service := os.Args[2]

	if os.Args[1] == "client" {
		client(service)
	} else if os.Args[1] == "server" {
		server(service)
	} else {
		log.Fatal(usage)
	}

}

func dieIfError(err error) {
	if err != nil {
		log.Fatal("Fatal error: %s", err.Error())
	}
}

func client(service string) {
	tcpAddr, err := net.ResolveTCPAddr("tcp", service)
	dieIfError(err)
	conn, err := net.DialTCP("tcp", nil, tcpAddr)
	dieIfError(err)
	_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
	dieIfError(err)
	result, err := ioutil.ReadAll(conn)
	dieIfError(err)
	log.Println(string(result))
	os.Exit(0)
}

func server(service string) {
	tcpAddr, err := net.ResolveTCPAddr("tcp", service)
	dieIfError(err)
	listener, err := net.ListenTCP("tcp", tcpAddr)
	dieIfError(err)
	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		daytime := time.Now().String()
		log.Println("Access come !")
		conn.Write([]byte(daytime))
		conn.Close()
	}

}
<commit_msg>いい感じの変数<commit_after>package main

import (
	"fmt"
	"os"
	"net"
	"io/ioutil"
	"time"
	"log"
)

func main() {
	usage := fmt.Sprintf("Usage: $s client|server host:port\n", os.Args[0])
	if len(os.Args) != 3 {
		log.Fatal(usage)
	}
	service := os.Args[2]

	if os.Args[1] == "client" {
		client(service)
	} else if os.Args[1] == "server" {
		server(service)
	} else {
		log.Fatal(usage)
	}

}

func dieIfError(err error) {
	if err != nil {
		log.Fatal("Fatal error: %s", err.Error())
	}
}

func client(service string) {
	tcpAddr, err := net.ResolveTCPAddr("tcp", service)
	dieIfError(err)
	conn, err := net.DialTCP("tcp", nil, tcpAddr)
	dieIfError(err)
	_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
	dieIfError(err)
	result, err := ioutil.ReadAll(conn)
	dieIfError(err)
	log.Println(string(result))
	os.Exit(0)
}

func server(service string) {
	tcpAddr, err := net.ResolveTCPAddr("tcp", service)
	dieIfError(err)
	listener, err := net.ListenTCP("tcp", tcpAddr)
	dieIfError(err)
	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		now := time.Now().String()
		log.Println("Access come !")
		conn.Write([]byte(now))
		conn.Close()
	}

}
<|endoftext|>


<commit_before>/* The accounts package defines operations performed on an object-storage account.

Reference: http://developer.openstack.org/api-ref-objectstorage-v1.html#storage_account_services
*/
package accounts
<commit_msg>Amending doc.go of package to be more helpful<commit_after>// Package accounts contains functionality for working with Object Storage
// account resources. An account is the top-level resource the object storage
// hierarchy: containers belong to accounts, objects belong to containers.
//
// Another way of thinking of an account is like a namespace for all your
// resources. It is synonymous with a project or tenant in other OpenStack
// services.
package accounts
<|endoftext|>


<commit_before>// Copyright 2018 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package app

import (
	"expvar"
	"net/http"
	"net/http/pprof"
	"strings"

	"github.com/issue9/middleware/compress"
	"github.com/issue9/middleware/header"
	"github.com/issue9/middleware/host"
	"github.com/issue9/middleware/recovery"

	"github.com/issue9/web/internal/webconfig"
)

const (
	// 此变量理论上可以更改，但实际上，更改之后，所有的子页面都会不可用。
	debugPprofPath = "/debug/pprof/"

	// 此地址可以修改。
	debugVarsPath = "/debug/vars"
)

// 目前支持的压缩方式
var compresses = map[string]compress.WriterFunc{
	"gizp":    compress.NewGzip,
	"deflate": compress.NewDeflate,
}

// 通过配置文件加载相关的中间件。
//
// 始终保持这些中间件在最后初始化。用户添加的中间件由 app.mux.UnshiftMiddlewares 添加。
func (app *App) buildMiddlewares(conf *webconfig.WebConfig) {
	// domains
	if len(conf.AllowedDomains) > 0 {
		app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
			return host.New(h, conf.AllowedDomains...)
		})
	}

	// headers
	if len(conf.Headers) > 0 {
		app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
			return header.New(h, conf.Headers, nil)
		})
	}

	// compress
	if conf.Compress != nil {
		app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
			return compress.New(h, &compress.Options{
				Funcs:    compresses,
				Types:    conf.Compress.Types,
				Size:     conf.Compress.Size,
				ErrorLog: app.ERROR(),
			})
		})
	}

	app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
		return app.errorhandlers.New(h)
	})

	// recovery
	app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
		return recovery.New(h, app.errorhandlers.Recovery(app.ERROR()))
	})

	// NOTE: 在最外层添加调试地址，保证调试内容不会被其它 handler 干扰。
	if conf.Debug {
		app.Debug("调试模式，地址启用：", debugPprofPath, debugVarsPath)
		app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
			return debug(h)
		})
	}
}

func debug(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case strings.HasPrefix(r.URL.Path, debugPprofPath):
			path := r.URL.Path[len(debugPprofPath):]
			switch path {
			case "cmdline":
				pprof.Cmdline(w, r)
			case "profile":
				pprof.Profile(w, r)
			case "symbol":
				pprof.Symbol(w, r)
			case "trace":
				pprof.Trace(w, r)
			default:
				pprof.Index(w, r)
			}
		case strings.HasPrefix(r.URL.Path, debugVarsPath):
			expvar.Handler().ServeHTTP(w, r)
		default:
			h.ServeHTTP(w, r)
		}
	}) // end return http.HandlerFunc
}
<commit_msg>去掉默认的输出提示<commit_after>// Copyright 2018 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package app

import (
	"expvar"
	"net/http"
	"net/http/pprof"
	"strings"

	"github.com/issue9/middleware/compress"
	"github.com/issue9/middleware/header"
	"github.com/issue9/middleware/host"
	"github.com/issue9/middleware/recovery"

	"github.com/issue9/web/internal/webconfig"
)

const (
	// 此变量理论上可以更改，但实际上，更改之后，所有的子页面都会不可用。
	debugPprofPath = "/debug/pprof/"

	// 此地址可以修改。
	debugVarsPath = "/debug/vars"
)

// 目前支持的压缩方式
var compresses = map[string]compress.WriterFunc{
	"gizp":    compress.NewGzip,
	"deflate": compress.NewDeflate,
}

// 通过配置文件加载相关的中间件。
//
// 始终保持这些中间件在最后初始化。用户添加的中间件由 app.mux.UnshiftMiddlewares 添加。
func (app *App) buildMiddlewares(conf *webconfig.WebConfig) {
	// domains
	if len(conf.AllowedDomains) > 0 {
		app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
			return host.New(h, conf.AllowedDomains...)
		})
	}

	// headers
	if len(conf.Headers) > 0 {
		app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
			return header.New(h, conf.Headers, nil)
		})
	}

	// compress
	if conf.Compress != nil {
		app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
			return compress.New(h, &compress.Options{
				Funcs:    compresses,
				Types:    conf.Compress.Types,
				Size:     conf.Compress.Size,
				ErrorLog: app.ERROR(),
			})
		})
	}

	app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
		return app.errorhandlers.New(h)
	})

	// recovery
	app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
		return recovery.New(h, app.errorhandlers.Recovery(app.ERROR()))
	})

	// NOTE: 在最外层添加调试地址，保证调试内容不会被其它 handler 干扰。
	if conf.Debug {
		app.Mux().AppendMiddlewares(func(h http.Handler) http.Handler {
			return debug(h)
		})
	}
}

func debug(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case strings.HasPrefix(r.URL.Path, debugPprofPath):
			path := r.URL.Path[len(debugPprofPath):]
			switch path {
			case "cmdline":
				pprof.Cmdline(w, r)
			case "profile":
				pprof.Profile(w, r)
			case "symbol":
				pprof.Symbol(w, r)
			case "trace":
				pprof.Trace(w, r)
			default:
				pprof.Index(w, r)
			}
		case strings.HasPrefix(r.URL.Path, debugVarsPath):
			expvar.Handler().ServeHTTP(w, r)
		default:
			h.ServeHTTP(w, r)
		}
	}) // end return http.HandlerFunc
}
<|endoftext|>