<commit_before>package cloudflare

import (
	"encoding/json"
	"time"

	"github.com/pkg/errors"
)

// PerHostnameAuthenticatedOriginPullsCertificateDetails represents the metadata for a Per Hostname AuthenticatedOriginPulls certificate.
type PerHostnameAuthenticatedOriginPullsCertificateDetails struct {
	ID           string    `json:"id"`
	Certificate  string    `json:"certificate"`
	Issuer       string    `json:"issuer"`
	Signature    string    `json:"signature"`
	SerialNumber string    `json:"serial_number"`
	ExpiresOn    time.Time `json:"expires_on"`
	Status       string    `json:"status"`
	UploadedOn   time.Time `json:"uploaded_on"`
}

// PerHostnameAuthenticatedOriginPullsCertificateResponse represents the response from endpoints relating to creating and deleting a Per Hostname AuthenticatedOriginPulls certificate.
type PerHostnameAuthenticatedOriginPullsCertificateResponse struct {
	Response
	Result PerHostnameAuthenticatedOriginPullsCertificateDetails `json:"result"`
}

// PerHostnameAuthenticatedOriginPullsDetails contains metadata about the Per Hostname AuthenticatedOriginPulls configuration on a hostname.
type PerHostnameAuthenticatedOriginPullsDetails struct {
	Hostname       string    `json:"hostname"`
	CertID         string    `json:"cert_id"`
	Enabled        bool      `json:"enabled"`
	Status         string    `json:"status"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
	CertStatus     string    `json:"cert_status"`
	Issuer         string    `json:"issuer"`
	Signature      string    `json:"signature"`
	SerialNumber   string    `json:"serial_number"`
	Certificate    string    `json:"certificate"`
	CertUploadedOn time.Time `json:"cert_uploaded_on"`
	CertUpdatedAt  time.Time `json:"cert_updated_at"`
	ExpiresOn      time.Time `json:"expires_on"`
}

// PerHostnameAuthenticatedOriginPullsDetailsResponse represents Per Hostname AuthenticatedOriginPulls configuration metadata for a single hostname.
type PerHostnameAuthenticatedOriginPullsDetailsResponse struct {
	Response
	Result PerHostnameAuthenticatedOriginPullsDetails `json:"result"`
}

// PerHostnamesAuthenticatedOriginPullsDetailsResponse represents Per Hostname AuthenticatedOriginPulls configuration metadata for multiple hostnames.
type PerHostnamesAuthenticatedOriginPullsDetailsResponse struct {
	Response
	Result []PerHostnameAuthenticatedOriginPullsDetails `json:"result"`
}

// PerHostnameAuthenticatedOriginPullsCertificateParams represents the required data related to the client certificate being uploaded to be used in Per Hostname AuthenticatedOriginPulls.
type PerHostnameAuthenticatedOriginPullsCertificateParams struct {
	Certificate string `json:"certificate"`
	PrivateKey  string `json:"private_key"`
}

// PerHostnameAuthenticatedOriginPullsConfig represents the config state for Per Hostname AuthenticatedOriginPulls applied on a hostname.
type PerHostnameAuthenticatedOriginPullsConfig struct {
	Hostname string `json:"hostname"`
	CertID   string `json:"cert_id"`
	Enabled  bool   `json:"enabled"`
}

// UploadPerHostnameAuthenticatedOriginPullsCertificate will upload the provided certificate and private key to the edge under Per Hostname AuthenticatedOriginPulls.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-upload-a-hostname-client-certificate
func (api *API) UploadPerHostnameAuthenticatedOriginPullsCertificate(zoneID string, params PerHostnameAuthenticatedOriginPullsCertificateParams) (PerHostnameAuthenticatedOriginPullsCertificateDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames/certificates"
	res, err := api.makeRequest("POST", uri, params)
	if err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnameAuthenticatedOriginPullsCertificateResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// GetPerHostnameAuthenticatedOriginPullsCertificate retrieves certificate metadata about the requested Per Hostname certificate.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-get-the-hostname-client-certificate
func (api *API) GetPerHostnameAuthenticatedOriginPullsCertificate(zoneID, certificateID string) (PerHostnameAuthenticatedOriginPullsCertificateDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames/certificates/" + certificateID
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnameAuthenticatedOriginPullsCertificateResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// DeletePerHostnameAuthenticatedOriginPullsCertificate will remove the requested Per Hostname certificate from the edge.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-delete-hostname-client-certificate
func (api *API) DeletePerHostnameAuthenticatedOriginPullsCertificate(zoneID, certificateID string) (PerHostnameAuthenticatedOriginPullsCertificateDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames/certificates/" + certificateID
	res, err := api.makeRequest("DELETE", uri, nil)
	if err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnameAuthenticatedOriginPullsCertificateResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// EditPerHostnameAuthenticatedOriginPullsConfig applies the supplied Per Hostname AuthenticatedOriginPulls config onto a hostname(s) in the edge.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-enable-or-disable-a-hostname-for-client-authentication
func (api *API) EditPerHostnameAuthenticatedOriginPullsConfig(zoneID string, config []PerHostnameAuthenticatedOriginPullsConfig) ([]PerHostnameAuthenticatedOriginPullsDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames"
	res, err := api.makeRequest("PUT", uri, nil)
	if err != nil {
		return []PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnamesAuthenticatedOriginPullsDetailsResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return []PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// GetPerHostnameAuthenticatedOriginPullsConfig returns the config state of Per Hostname AuthenticatedOriginPulls of the provided hostname within a zone.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-get-the-hostname-status-for-client-authentication
func (api *API) GetPerHostnameAuthenticatedOriginPullsConfig(zoneID, hostname string) (PerHostnameAuthenticatedOriginPullsDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames/" + hostname
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnameAuthenticatedOriginPullsDetailsResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}
<commit_msg>510: Properly set Per Hostname AOP PUT params (#511)<commit_after>package cloudflare

import (
	"encoding/json"
	"time"

	"github.com/pkg/errors"
)

// PerHostnameAuthenticatedOriginPullsCertificateDetails represents the metadata for a Per Hostname AuthenticatedOriginPulls certificate.
type PerHostnameAuthenticatedOriginPullsCertificateDetails struct {
	ID           string    `json:"id"`
	Certificate  string    `json:"certificate"`
	Issuer       string    `json:"issuer"`
	Signature    string    `json:"signature"`
	SerialNumber string    `json:"serial_number"`
	ExpiresOn    time.Time `json:"expires_on"`
	Status       string    `json:"status"`
	UploadedOn   time.Time `json:"uploaded_on"`
}

// PerHostnameAuthenticatedOriginPullsCertificateResponse represents the response from endpoints relating to creating and deleting a Per Hostname AuthenticatedOriginPulls certificate.
type PerHostnameAuthenticatedOriginPullsCertificateResponse struct {
	Response
	Result PerHostnameAuthenticatedOriginPullsCertificateDetails `json:"result"`
}

// PerHostnameAuthenticatedOriginPullsDetails contains metadata about the Per Hostname AuthenticatedOriginPulls configuration on a hostname.
type PerHostnameAuthenticatedOriginPullsDetails struct {
	Hostname       string    `json:"hostname"`
	CertID         string    `json:"cert_id"`
	Enabled        bool      `json:"enabled"`
	Status         string    `json:"status"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
	CertStatus     string    `json:"cert_status"`
	Issuer         string    `json:"issuer"`
	Signature      string    `json:"signature"`
	SerialNumber   string    `json:"serial_number"`
	Certificate    string    `json:"certificate"`
	CertUploadedOn time.Time `json:"cert_uploaded_on"`
	CertUpdatedAt  time.Time `json:"cert_updated_at"`
	ExpiresOn      time.Time `json:"expires_on"`
}

// PerHostnameAuthenticatedOriginPullsDetailsResponse represents Per Hostname AuthenticatedOriginPulls configuration metadata for a single hostname.
type PerHostnameAuthenticatedOriginPullsDetailsResponse struct {
	Response
	Result PerHostnameAuthenticatedOriginPullsDetails `json:"result"`
}

// PerHostnamesAuthenticatedOriginPullsDetailsResponse represents Per Hostname AuthenticatedOriginPulls configuration metadata for multiple hostnames.
type PerHostnamesAuthenticatedOriginPullsDetailsResponse struct {
	Response
	Result []PerHostnameAuthenticatedOriginPullsDetails `json:"result"`
}

// PerHostnameAuthenticatedOriginPullsCertificateParams represents the required data related to the client certificate being uploaded to be used in Per Hostname AuthenticatedOriginPulls.
type PerHostnameAuthenticatedOriginPullsCertificateParams struct {
	Certificate string `json:"certificate"`
	PrivateKey  string `json:"private_key"`
}

// PerHostnameAuthenticatedOriginPullsConfig represents the config state for Per Hostname AuthenticatedOriginPulls applied on a hostname.
type PerHostnameAuthenticatedOriginPullsConfig struct {
	Hostname string `json:"hostname"`
	CertID   string `json:"cert_id"`
	Enabled  bool   `json:"enabled"`
}

// PerHostnameAuthenticatedOriginPullsConfigParams represents the expected config param format for Per Hostname AuthenticatedOriginPulls applied on a hostname.
type PerHostnameAuthenticatedOriginPullsConfigParams struct {
	Config []PerHostnameAuthenticatedOriginPullsConfig `json:"config"`
}

// UploadPerHostnameAuthenticatedOriginPullsCertificate will upload the provided certificate and private key to the edge under Per Hostname AuthenticatedOriginPulls.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-upload-a-hostname-client-certificate
func (api *API) UploadPerHostnameAuthenticatedOriginPullsCertificate(zoneID string, params PerHostnameAuthenticatedOriginPullsCertificateParams) (PerHostnameAuthenticatedOriginPullsCertificateDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames/certificates"
	res, err := api.makeRequest("POST", uri, params)
	if err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnameAuthenticatedOriginPullsCertificateResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// GetPerHostnameAuthenticatedOriginPullsCertificate retrieves certificate metadata about the requested Per Hostname certificate.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-get-the-hostname-client-certificate
func (api *API) GetPerHostnameAuthenticatedOriginPullsCertificate(zoneID, certificateID string) (PerHostnameAuthenticatedOriginPullsCertificateDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames/certificates/" + certificateID
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnameAuthenticatedOriginPullsCertificateResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// DeletePerHostnameAuthenticatedOriginPullsCertificate will remove the requested Per Hostname certificate from the edge.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-delete-hostname-client-certificate
func (api *API) DeletePerHostnameAuthenticatedOriginPullsCertificate(zoneID, certificateID string) (PerHostnameAuthenticatedOriginPullsCertificateDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames/certificates/" + certificateID
	res, err := api.makeRequest("DELETE", uri, nil)
	if err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnameAuthenticatedOriginPullsCertificateResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// EditPerHostnameAuthenticatedOriginPullsConfig applies the supplied Per Hostname AuthenticatedOriginPulls config onto a hostname(s) in the edge.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-enable-or-disable-a-hostname-for-client-authentication
func (api *API) EditPerHostnameAuthenticatedOriginPullsConfig(zoneID string, config []PerHostnameAuthenticatedOriginPullsConfig) ([]PerHostnameAuthenticatedOriginPullsDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames"
	conf := PerHostnameAuthenticatedOriginPullsConfigParams{
		Config: config,
	}
	res, err := api.makeRequest("PUT", uri, conf)
	if err != nil {
		return []PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnamesAuthenticatedOriginPullsDetailsResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return []PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// GetPerHostnameAuthenticatedOriginPullsConfig returns the config state of Per Hostname AuthenticatedOriginPulls of the provided hostname within a zone.
//
// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-get-the-hostname-status-for-client-authentication
func (api *API) GetPerHostnameAuthenticatedOriginPullsConfig(zoneID, hostname string) (PerHostnameAuthenticatedOriginPullsDetails, error) {
	uri := "/zones/" + zoneID + "/origin_tls_client_auth/hostnames/" + hostname
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errMakeRequestError)
	}
	var r PerHostnameAuthenticatedOriginPullsDetailsResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fs

import (
	"bytes"
	"crypto/hmac"
	"encoding/hex"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/minio/minio-xl/pkg/crypto/sha256"
	"github.com/minio/minio-xl/pkg/probe"
)

// Signature - local variables
type Signature struct {
	AccessKeyID     string
	SecretAccessKey string
	Region          string
	Presigned       bool
	PresignedPolicy string
	SignedHeaders   []string
	Signature       string
	Request         *http.Request
}

const (
	authHeaderPrefix = "AWS4-HMAC-SHA256"
	iso8601Format    = "20060102T150405Z"
	yyyymmdd         = "20060102"
)

// sumHMAC calculate hmac between two input byte array
func sumHMAC(key []byte, data []byte) []byte {
	hash := hmac.New(sha256.New, key)
	hash.Write(data)
	return hash.Sum(nil)
}

// getURLEncodedName encode the strings from UTF-8 byte representations to HTML hex escape sequences
//
// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
// non english characters cannot be parsed due to the nature in which url.Encode() is written
//
// This function on the other hand is a direct replacement for url.Encode() technique to support
// pretty much every UTF-8 character.
func getURLEncodedName(name string) string {
	// if object matches reserved string, no need to encode them
	reservedNames := regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
	if reservedNames.MatchString(name) {
		return name
	}
	var encodedName string
	for _, s := range name {
		if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
			encodedName = encodedName + string(s)
			continue
		}
		switch s {
		case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
			encodedName = encodedName + string(s)
			continue
		default:
			len := utf8.RuneLen(s)
			if len < 0 {
				return name
			}
			u := make([]byte, len)
			utf8.EncodeRune(u, s)
			for _, r := range u {
				hex := hex.EncodeToString([]byte{r})
				encodedName = encodedName + "%" + strings.ToUpper(hex)
			}
		}
	}
	return encodedName
}

// getCanonicalHeaders generate a list of request headers with their values
func (r Signature) getCanonicalHeaders(signedHeaders map[string][]string) string {
	var headers []string
	vals := make(map[string][]string)
	for k, vv := range signedHeaders {
		headers = append(headers, strings.ToLower(k))
		vals[strings.ToLower(k)] = vv
	}
	headers = append(headers, "host")
	sort.Strings(headers)

	var buf bytes.Buffer
	for _, k := range headers {
		buf.WriteString(k)
		buf.WriteByte(':')
		switch {
		case k == "host":
			buf.WriteString(r.Request.Host)
			fallthrough
		default:
			for idx, v := range vals[k] {
				if idx > 0 {
					buf.WriteByte(',')
				}
				buf.WriteString(v)
			}
			buf.WriteByte('\n')
		}
	}
	return buf.String()
}

// getSignedHeaders generate a string i.e alphabetically sorted, semicolon-separated list of lowercase request header names
func (r Signature) getSignedHeaders(signedHeaders map[string][]string) string {
	var headers []string
	for k := range signedHeaders {
		headers = append(headers, strings.ToLower(k))
	}
	headers = append(headers, "host")
	sort.Strings(headers)
	return strings.Join(headers, ";")
}

// extractSignedHeaders extract signed headers from Authorization header
func (r Signature) extractSignedHeaders() map[string][]string {
	extractedSignedHeadersMap := make(map[string][]string)
	for _, header := range r.SignedHeaders {
		val, ok := r.Request.Header[http.CanonicalHeaderKey(header)]
		if !ok {
			// Golang http server strips off 'Expect' header, if the
			// client sent this as part of signed headers we need to
			// handle otherwise we would see a signature mismatch.
			// `aws-cli` sets this as part of signed headers which is
			// a bad idea since servers trying to implement AWS
			// Signature version '4' will all encounter this issue.
			//
			// According to
			// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20
			// Expect header is always of form:
			//
			//   Expect       =  "Expect" ":" 1#expectation
			//   expectation  =  "100-continue" | expectation-extension
			//
			// So it safe to assume that '100-continue' is what would
			// be sent, for the time being keep this work around.
			// Adding a *TODO* to remove this later when Golang server
			// doesn't filter out the 'Expect' header.
			if header == "expect" {
				extractedSignedHeadersMap[header] = []string{"100-continue"}
			}
			// if not found continue, we will fail later
			continue
		}
		extractedSignedHeadersMap[header] = val
	}
	return extractedSignedHeadersMap
}

// getCanonicalRequest generate a canonical request of style
//
// canonicalRequest =
//  <HTTPMethod>\n
//  <CanonicalURI>\n
//  <CanonicalQueryString>\n
//  <CanonicalHeaders>\n
//  <SignedHeaders>\n
//  <HashedPayload>
//
func (r *Signature) getCanonicalRequest() string {
	payload := r.Request.Header.Get(http.CanonicalHeaderKey("x-amz-content-sha256"))
	r.Request.URL.RawQuery = strings.Replace(r.Request.URL.Query().Encode(), "+", "%20", -1)
	encodedPath := getURLEncodedName(r.Request.URL.Path)
	// convert any space strings back to "+"
	encodedPath = strings.Replace(encodedPath, "+", "%20", -1)
	canonicalRequest := strings.Join([]string{
		r.Request.Method,
		encodedPath,
		r.Request.URL.RawQuery,
		r.getCanonicalHeaders(r.extractSignedHeaders()),
		r.getSignedHeaders(r.extractSignedHeaders()),
		payload,
	}, "\n")
	return canonicalRequest
}

// getCanonicalRequest generate a canonical request of style
//
// canonicalRequest =
//  <HTTPMethod>\n
//  <CanonicalURI>\n
//  <CanonicalQueryString>\n
//  <CanonicalHeaders>\n
//  <SignedHeaders>\n
//  <HashedPayload>
//
func (r Signature) getPresignedCanonicalRequest(presignedQuery string) string {
	rawQuery := strings.Replace(presignedQuery, "+", "%20", -1)
	encodedPath := getURLEncodedName(r.Request.URL.Path)
	// convert any space strings back to "+"
	encodedPath = strings.Replace(encodedPath, "+", "%20", -1)
	canonicalRequest := strings.Join([]string{
		r.Request.Method,
		encodedPath,
		rawQuery,
		r.getCanonicalHeaders(r.extractSignedHeaders()),
		r.getSignedHeaders(r.extractSignedHeaders()),
		"UNSIGNED-PAYLOAD",
	}, "\n")
	return canonicalRequest
}

// getScope generate a string of a specific date, an AWS region, and a service
func (r Signature) getScope(t time.Time) string {
	scope := strings.Join([]string{
		t.Format(yyyymmdd),
		r.Region,
		"s3",
		"aws4_request",
	}, "/")
	return scope
}

// getStringToSign a string based on selected query values
func (r Signature) getStringToSign(canonicalRequest string, t time.Time) string {
	stringToSign := authHeaderPrefix + "\n" + t.Format(iso8601Format) + "\n"
	stringToSign = stringToSign + r.getScope(t) + "\n"
	stringToSign = stringToSign + hex.EncodeToString(sha256.Sum256([]byte(canonicalRequest)))
	return stringToSign
}

// getSigningKey hmac seed to calculate final signature
func (r Signature) getSigningKey(t time.Time) []byte {
	secret := r.SecretAccessKey
	date := sumHMAC([]byte("AWS4"+secret), []byte(t.Format(yyyymmdd)))
	region := sumHMAC(date, []byte(r.Region))
	service := sumHMAC(region, []byte("s3"))
	signingKey := sumHMAC(service, []byte("aws4_request"))
	return signingKey
}

// getSignature final signature in hexadecimal form
func (r Signature) getSignature(signingKey []byte, stringToSign string) string {
	return hex.EncodeToString(sumHMAC(signingKey, []byte(stringToSign)))
}

// DoesPolicySignatureMatch - Verify query headers with post policy
//     - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
// returns true if matches, false otherwise. if error is not nil then it is always false
func (r *Signature) DoesPolicySignatureMatch(date string) (bool, *probe.Error) {
	t, err := time.Parse(iso8601Format, date)
	if err != nil {
		return false, probe.NewError(err)
	}
	signingKey := r.getSigningKey(t)
	stringToSign := string(r.PresignedPolicy)
	newSignature := r.getSignature(signingKey, stringToSign)
	if newSignature != r.Signature {
		return false, nil
	}
	return true, nil
}

// DoesPresignedSignatureMatch - Verify query headers with presigned signature
//     - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
// returns true if matches, false otherwise. if error is not nil then it is always false
func (r *Signature) DoesPresignedSignatureMatch() (bool, *probe.Error) {
	query := make(url.Values)
	query.Set("X-Amz-Algorithm", authHeaderPrefix)

	var date string
	if date = r.Request.URL.Query().Get("X-Amz-Date"); date == "" {
		return false, probe.NewError(MissingDateHeader{})
	}
	t, err := time.Parse(iso8601Format, date)
	if err != nil {
		return false, probe.NewError(err)
	}
	if _, ok := r.Request.URL.Query()["X-Amz-Expires"]; !ok {
		return false, probe.NewError(MissingExpiresQuery{})
	}
	expireSeconds, err := strconv.Atoi(r.Request.URL.Query().Get("X-Amz-Expires"))
	if err != nil {
		return false, probe.NewError(err)
	}
	if time.Now().UTC().Sub(t) > time.Duration(expireSeconds)*time.Second {
		return false, probe.NewError(ExpiredPresignedRequest{})
	}
	query.Set("X-Amz-Date", t.Format(iso8601Format))
	query.Set("X-Amz-Expires", strconv.Itoa(expireSeconds))
	query.Set("X-Amz-SignedHeaders", r.getSignedHeaders(r.extractSignedHeaders()))
	query.Set("X-Amz-Credential", r.AccessKeyID+"/"+r.getScope(t))

	encodedQuery := query.Encode()
	newSignature := r.getSignature(r.getSigningKey(t), r.getStringToSign(r.getPresignedCanonicalRequest(encodedQuery), t))
	encodedQuery += "&X-Amz-Signature=" + newSignature

	if encodedQuery != r.Request.URL.RawQuery {
		return false, nil
	}
	return true, nil
}

// DoesSignatureMatch - Verify authorization header with calculated header in accordance with
//     - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
// returns true if matches, false otherwise. if error is not nil then it is always false
func (r *Signature) DoesSignatureMatch(hashedPayload string) (bool, *probe.Error) {
	// set new calulated payload
	r.Request.Header.Set("X-Amz-Content-Sha256", hashedPayload)

	// Add date if not present throw error
	var date string
	if date = r.Request.Header.Get(http.CanonicalHeaderKey("x-amz-date")); date == "" {
		if date = r.Request.Header.Get("Date"); date == "" {
			return false, probe.NewError(MissingDateHeader{})
		}
	}
	t, err := time.Parse(iso8601Format, date)
	if err != nil {
		return false, probe.NewError(err)
	}
	canonicalRequest := r.getCanonicalRequest()
	stringToSign := r.getStringToSign(canonicalRequest, t)
	signingKey := r.getSigningKey(t)
	newSignature := r.getSignature(signingKey, stringToSign)

	if newSignature != r.Signature {
		return false, nil
	}
	return true, nil
}
<commit_msg>presign: Verify query params for presign individually<commit_after>/*
 * Minio Cloud Storage, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fs

import (
	"bytes"
	"crypto/hmac"
	"encoding/hex"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/minio/minio-xl/pkg/crypto/sha256"
	"github.com/minio/minio-xl/pkg/probe"
)

// Signature - local variables
type Signature struct {
	AccessKeyID     string
	SecretAccessKey string
	Region          string
	Presigned       bool
	PresignedPolicy string
	SignedHeaders   []string
	Signature       string
	Request         *http.Request
}

const (
	authHeaderPrefix = "AWS4-HMAC-SHA256"
	iso8601Format    = "20060102T150405Z"
	yyyymmdd         = "20060102"
)

// sumHMAC calculate hmac between two input byte array
func sumHMAC(key []byte, data []byte) []byte {
	hash := hmac.New(sha256.New, key)
	hash.Write(data)
	return hash.Sum(nil)
}

// getURLEncodedName encode the strings from UTF-8 byte representations to HTML hex escape sequences
//
// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
// non english characters cannot be parsed due to the nature in which url.Encode() is written
//
// This function on the other hand is a direct replacement for url.Encode() technique to support
// pretty much every UTF-8 character.
func getURLEncodedName(name string) string {
	// if object matches reserved string, no need to encode them
	reservedNames := regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
	if reservedNames.MatchString(name) {
		return name
	}
	var encodedName string
	for _, s := range name {
		if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
			encodedName = encodedName + string(s)
			continue
		}
		switch s {
		case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
			encodedName = encodedName + string(s)
			continue
		default:
			len := utf8.RuneLen(s)
			if len < 0 {
				return name
			}
			u := make([]byte, len)
			utf8.EncodeRune(u, s)
			for _, r := range u {
				hex := hex.EncodeToString([]byte{r})
				encodedName = encodedName + "%" + strings.ToUpper(hex)
			}
		}
	}
	return encodedName
}

// getCanonicalHeaders generate a list of request headers with their values
func (r Signature) getCanonicalHeaders(signedHeaders map[string][]string) string {
	var headers []string
	vals := make(map[string][]string)
	for k, vv := range signedHeaders {
		headers = append(headers, strings.ToLower(k))
		vals[strings.ToLower(k)] = vv
	}
	headers = append(headers, "host")
	sort.Strings(headers)

	var buf bytes.Buffer
	for _, k := range headers {
		buf.WriteString(k)
		buf.WriteByte(':')
		switch {
		case k == "host":
			buf.WriteString(r.Request.Host)
			fallthrough
		default:
			for idx, v := range vals[k] {
				if idx > 0 {
					buf.WriteByte(',')
				}
				buf.WriteString(v)
			}
			buf.WriteByte('\n')
		}
	}
	return buf.String()
}

// getSignedHeaders generate a string i.e alphabetically sorted, semicolon-separated list of lowercase request header names
func (r Signature) getSignedHeaders(signedHeaders map[string][]string) string {
	var headers []string
	for k := range signedHeaders {
		headers = append(headers, strings.ToLower(k))
	}
	headers = append(headers, "host")
	sort.Strings(headers)
	return strings.Join(headers, ";")
}

// extractSignedHeaders extract signed headers from Authorization header
func (r Signature) extractSignedHeaders() map[string][]string {
	extractedSignedHeadersMap := make(map[string][]string)
	for _, header := range r.SignedHeaders {
		val, ok := r.Request.Header[http.CanonicalHeaderKey(header)]
		if !ok {
			// Golang http server strips off 'Expect' header, if the
			// client sent this as part of signed headers we need to
			// handle otherwise we would see a signature mismatch.
			// `aws-cli` sets this as part of signed headers which is
			// a bad idea since servers trying to implement AWS
			// Signature version '4' will all encounter this issue.
			//
			// According to
			// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20
			// Expect header is always of form:
			//
			//   Expect       =  "Expect" ":" 1#expectation
			//   expectation  =  "100-continue" | expectation-extension
			//
			// So it safe to assume that '100-continue' is what would
			// be sent, for the time being keep this work around.
			// Adding a *TODO* to remove this later when Golang server
			// doesn't filter out the 'Expect' header.
			if header == "expect" {
				extractedSignedHeadersMap[header] = []string{"100-continue"}
			}
			// if not found continue, we will fail later
			continue
		}
		extractedSignedHeadersMap[header] = val
	}
	return extractedSignedHeadersMap
}

// getCanonicalRequest generate a canonical request of style
//
// canonicalRequest =
//  <HTTPMethod>\n
//  <CanonicalURI>\n
//  <CanonicalQueryString>\n
//  <CanonicalHeaders>\n
//  <SignedHeaders>\n
//  <HashedPayload>
//
func (r *Signature) getCanonicalRequest() string {
	payload := r.Request.Header.Get(http.CanonicalHeaderKey("x-amz-content-sha256"))
	r.Request.URL.RawQuery = strings.Replace(r.Request.URL.Query().Encode(), "+", "%20", -1)
	encodedPath := getURLEncodedName(r.Request.URL.Path)
	// convert any space strings back to "+"
	encodedPath = strings.Replace(encodedPath, "+", "%20", -1)
	canonicalRequest := strings.Join([]string{
		r.Request.Method,
		encodedPath,
		r.Request.URL.RawQuery,
		r.getCanonicalHeaders(r.extractSignedHeaders()),
		r.getSignedHeaders(r.extractSignedHeaders()),
		payload,
	}, "\n")
	return canonicalRequest
}

// getCanonicalRequest generate a canonical request of style
//
// canonicalRequest =
//  <HTTPMethod>\n
//  <CanonicalURI>\n
//  <CanonicalQueryString>\n
//  <CanonicalHeaders>\n
//  <SignedHeaders>\n
//  <HashedPayload>
//
func (r Signature) getPresignedCanonicalRequest(presignedQuery string) string {
	rawQuery := strings.Replace(presignedQuery, "+", "%20", -1)
	encodedPath := getURLEncodedName(r.Request.URL.Path)
	// convert any space strings back to "+"
	encodedPath = strings.Replace(encodedPath, "+", "%20", -1)
	canonicalRequest := strings.Join([]string{
		r.Request.Method,
		encodedPath,
		rawQuery,
		r.getCanonicalHeaders(r.extractSignedHeaders()),
		r.getSignedHeaders(r.extractSignedHeaders()),
		"UNSIGNED-PAYLOAD",
	}, "\n")
	return canonicalRequest
}

// getScope generate a string of a specific date, an AWS region, and a service
func (r Signature) getScope(t time.Time) string {
	scope := strings.Join([]string{
		t.Format(yyyymmdd),
		r.Region,
		"s3",
		"aws4_request",
	}, "/")
	return scope
}

// getStringToSign a string based on selected query values
func (r Signature) getStringToSign(canonicalRequest string, t time.Time) string {
	stringToSign := authHeaderPrefix + "\n" + t.Format(iso8601Format) + "\n"
	stringToSign = stringToSign + r.getScope(t) + "\n"
	stringToSign = stringToSign + hex.EncodeToString(sha256.Sum256([]byte(canonicalRequest)))
	return stringToSign
}

// getSigningKey hmac seed to calculate final signature
func (r Signature) getSigningKey(t time.Time) []byte {
	secret := r.SecretAccessKey
	date := sumHMAC([]byte("AWS4"+secret), []byte(t.Format(yyyymmdd)))
	region := sumHMAC(date, []byte(r.Region))
	service := sumHMAC(region, []byte("s3"))
	signingKey := sumHMAC(service, []byte("aws4_request"))
	return signingKey
}

// getSignature final signature in hexadecimal form
func (r Signature) getSignature(signingKey []byte, stringToSign string) string {
	return hex.EncodeToString(sumHMAC(signingKey, []byte(stringToSign)))
}

// DoesPolicySignatureMatch - Verify query headers with post policy
//     - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
// returns true if matches, false otherwise. if error is not nil then it is always false
func (r *Signature) DoesPolicySignatureMatch(date string) (bool, *probe.Error) {
	t, err := time.Parse(iso8601Format, date)
	if err != nil {
		return false, probe.NewError(err)
	}
	signingKey := r.getSigningKey(t)
	stringToSign := string(r.PresignedPolicy)
	newSignature := r.getSignature(signingKey, stringToSign)
	if newSignature != r.Signature {
		return false, nil
	}
	return true, nil
}

// DoesPresignedSignatureMatch - Verify query headers with presigned signature
//     - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
// returns true if matches, false otherwise. if error is not nil then it is always false
func (r *Signature) DoesPresignedSignatureMatch() (bool, *probe.Error) {
	query := make(url.Values)
	query.Set("X-Amz-Algorithm", authHeaderPrefix)

	var date string
	if date = r.Request.URL.Query().Get("X-Amz-Date"); date == "" {
		return false, probe.NewError(MissingDateHeader{})
	}
	t, err := time.Parse(iso8601Format, date)
	if err != nil {
		return false, probe.NewError(err)
	}
	if _, ok := r.Request.URL.Query()["X-Amz-Expires"]; !ok {
		return false, probe.NewError(MissingExpiresQuery{})
	}
	expireSeconds, err := strconv.Atoi(r.Request.URL.Query().Get("X-Amz-Expires"))
	if err != nil {
		return false, probe.NewError(err)
	}
	if time.Now().UTC().Sub(t) > time.Duration(expireSeconds)*time.Second {
		return false, probe.NewError(ExpiredPresignedRequest{})
	}
	query.Set("X-Amz-Date", t.Format(iso8601Format))
	query.Set("X-Amz-Expires", strconv.Itoa(expireSeconds))
	query.Set("X-Amz-SignedHeaders", r.getSignedHeaders(r.extractSignedHeaders()))
	query.Set("X-Amz-Credential", r.AccessKeyID+"/"+r.getScope(t))
	encodedQuery := query.Encode()

	// Verify if date query is same.
	if r.Request.URL.Query().Get("X-Amz-Date") != query.Get("X-Amz-Date") {
		return false, nil
	}
	// Verify if expires query is same.
	if r.Request.URL.Query().Get("X-Amz-Expires") != query.Get("X-Amz-Expires") {
		return false, nil
	}
	// Verify if signed headers query is same.
	if r.Request.URL.Query().Get("X-Amz-SignedHeaders") != query.Get("X-Amz-SignedHeaders") {
		return false, nil
	}
	// Verify if credential query is same.
	if r.Request.URL.Query().Get("X-Amz-Credential") != query.Get("X-Amz-Credential") {
		return false, nil
	}
	// Verify finally if signature is same.
	newSignature := r.getSignature(r.getSigningKey(t), r.getStringToSign(r.getPresignedCanonicalRequest(encodedQuery), t))
	if r.Request.URL.Query().Get("X-Amz-Signature") != newSignature {
		return false, nil
	}
	return true, nil
}

// DoesSignatureMatch - Verify authorization header with calculated header in accordance with
//     - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
// returns true if matches, false otherwise. if error is not nil then it is always false
func (r *Signature) DoesSignatureMatch(hashedPayload string) (bool, *probe.Error) {
	// set new calulated payload
	r.Request.Header.Set("X-Amz-Content-Sha256", hashedPayload)

	// Add date if not present throw error
	var date string
	if date = r.Request.Header.Get(http.CanonicalHeaderKey("x-amz-date")); date == "" {
		if date = r.Request.Header.Get("Date"); date == "" {
			return false, probe.NewError(MissingDateHeader{})
		}
	}
	t, err := time.Parse(iso8601Format, date)
	if err != nil {
		return false, probe.NewError(err)
	}
	canonicalRequest := r.getCanonicalRequest()
	stringToSign := r.getStringToSign(canonicalRequest, t)
	signingKey := r.getSigningKey(t)
	newSignature := r.getSignature(signingKey, stringToSign)

	if newSignature != r.Signature {
		return false, nil
	}
	return true, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"regexp"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSEbsSnapshotCopy_basic(t *testing.T) {
	var v ec2.Snapshot
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsEbsSnapshotCopyConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckEbsSnapshotCopyExists("aws_ebs_snapshot_copy.test", &v),
					testAccCheckTags(&v.Tags, "Name", "testAccAwsEbsSnapshotCopyConfig"),
				),
			},
		},
	})
}

func TestAccAWSEbsSnapshotCopy_withDescription(t *testing.T) {
	var v ec2.Snapshot
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsEbsSnapshotCopyConfigWithDescription,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckEbsSnapshotCopyExists("aws_ebs_snapshot_copy.description_test", &v),
					resource.TestCheckResourceAttr("aws_ebs_snapshot_copy.description_test", "description", "Copy Snapshot Acceptance Test"),
				),
			},
		},
	})
}

func TestAccAWSEbsSnapshotCopy_withRegions(t *testing.T) {
	var v ec2.Snapshot

	// record the initialized providers so that we can use them to
	// check for the instances in each region
	var providers []*schema.Provider
	providerFactories := map[string]terraform.ResourceProviderFactory{
		"aws": func() (terraform.ResourceProvider, error) {
			p := Provider()
			providers = append(providers, p.(*schema.Provider))
			return p, nil
		},
	}

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:          func() { testAccPreCheck(t) },
		ProviderFactories: providerFactories,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsEbsSnapshotCopyConfigWithRegions,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckEbsSnapshotCopyExistsWithProviders("aws_ebs_snapshot_copy.region_test", &v, &providers),
				),
			},
		},
	})

}

func TestAccAWSEbsSnapshotCopy_withKms(t *testing.T) {
	var v ec2.Snapshot
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsEbsSnapshotCopyConfigWithKms,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckEbsSnapshotCopyExists("aws_ebs_snapshot_copy.kms_test", &v),
					resource.TestMatchResourceAttr("aws_ebs_snapshot_copy.kms_test", "kms_key_id",
						regexp.MustCompile(`^arn:aws:kms:[a-z]{2}-[a-z]+-\d{1}:[0-9]{12}:key/[a-z0-9-]{36}$`)),
				),
			},
		},
	})
}

func testAccCheckEbsSnapshotCopyExists(n string, v *ec2.Snapshot) resource.TestCheckFunc {
	providers := []*schema.Provider{testAccProvider}
	return testAccCheckEbsSnapshotCopyExistsWithProviders(n, v, &providers)
}

func testAccCheckEbsSnapshotCopyExistsWithProviders(n string, v *ec2.Snapshot, providers *[]*schema.Provider) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		for _, provider := range *providers {
			// Ignore if Meta is empty, this can happen for validation providers
			if provider.Meta() == nil {
				continue
			}

			conn := provider.Meta().(*AWSClient).ec2conn

			request := &ec2.DescribeSnapshotsInput{
				SnapshotIds: []*string{aws.String(rs.Primary.ID)},
			}

			response, err := conn.DescribeSnapshots(request)
			if err == nil {
				if response.Snapshots != nil && len(response.Snapshots) > 0 {
					*v = *response.Snapshots[0]
					return nil
				}
			}
		}
		return fmt.Errorf("Error finding EC2 Snapshot %s", rs.Primary.ID)
	}
}

const testAccAwsEbsSnapshotCopyConfig = `
resource "aws_ebs_volume" "test" {
  availability_zone = "us-west-2a"
  size              = 1
}

resource "aws_ebs_snapshot" "test" {
  volume_id = "${aws_ebs_volume.test.id}"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfig"
  }
}

resource "aws_ebs_snapshot_copy" "test" {
  source_snapshot_id = "${aws_ebs_snapshot.test.id}"
	source_region      = "us-west-2"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfig"
  }
}
`

const testAccAwsEbsSnapshotCopyConfigWithDescription = `
resource "aws_ebs_volume" "description_test" {
	availability_zone = "us-west-2a"
	size              = 1

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithDescription"
  }
}

resource "aws_ebs_snapshot" "description_test" {
	volume_id   = "${aws_ebs_volume.description_test.id}"
	description = "EBS Snapshot Acceptance Test"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithDescription"
  }
}

resource "aws_ebs_snapshot_copy" "description_test" {
	description        = "Copy Snapshot Acceptance Test"
  source_snapshot_id = "${aws_ebs_snapshot.description_test.id}"
	source_region      = "us-west-2"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithDescription"
  }
}
`

const testAccAwsEbsSnapshotCopyConfigWithRegions = `
provider "aws" {
  region = "us-west-2"
  alias  = "uswest2"
}

provider "aws" {
  region = "us-east-1"
  alias  = "useast1"
}

resource "aws_ebs_volume" "region_test" {
  provider          = "aws.uswest2"
  availability_zone = "us-west-2a"
  size              = 1

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithRegions"
  }
}

resource "aws_ebs_snapshot" "region_test" {
  provider  = "aws.uswest2"
  volume_id = "${aws_ebs_volume.region_test.id}"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithRegions"
  }
}

resource "aws_ebs_snapshot_copy" "region_test" {
  provider           = "aws.useast1"
  source_snapshot_id = "${aws_ebs_snapshot.region_test.id}"
  source_region      = "us-west-2"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithRegions"
  }
}
`

const testAccAwsEbsSnapshotCopyConfigWithKms = `
provider "aws" {
  region = "us-west-2"
}

resource "aws_kms_key" "kms_test" {
  description             = "testAccAwsEbsSnapshotCopyConfigWithKms"
  deletion_window_in_days = 7
}

resource "aws_ebs_volume" "kms_test" {
  availability_zone = "us-west-2a"
  size              = 1

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithKms"
  }
}

resource "aws_ebs_snapshot" "kms_test" {
  volume_id = "${aws_ebs_volume.kms_test.id}"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithKms"
  }
}

resource "aws_ebs_snapshot_copy" "kms_test" {
  source_snapshot_id = "${aws_ebs_snapshot.kms_test.id}"
  source_region      = "us-west-2"
  encrypted          = true
  kms_key_id         = "${aws_kms_key.kms_test.arn}"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithKms"
  }
}
`
<commit_msg>Add CheckDestroy for aws_ebs_snapshot_copy resource<commit_after>package aws

import (
	"fmt"
	"regexp"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSEbsSnapshotCopy_basic(t *testing.T) {
	var v ec2.Snapshot
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckEbsSnapshotCopyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsEbsSnapshotCopyConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckEbsSnapshotCopyExists("aws_ebs_snapshot_copy.test", &v),
					testAccCheckTags(&v.Tags, "Name", "testAccAwsEbsSnapshotCopyConfig"),
				),
			},
		},
	})
}

func TestAccAWSEbsSnapshotCopy_withDescription(t *testing.T) {
	var v ec2.Snapshot
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckEbsSnapshotCopyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsEbsSnapshotCopyConfigWithDescription,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckEbsSnapshotCopyExists("aws_ebs_snapshot_copy.description_test", &v),
					resource.TestCheckResourceAttr("aws_ebs_snapshot_copy.description_test", "description", "Copy Snapshot Acceptance Test"),
				),
			},
		},
	})
}

func TestAccAWSEbsSnapshotCopy_withRegions(t *testing.T) {
	var v ec2.Snapshot

	// record the initialized providers so that we can use them to
	// check for the instances in each region
	var providers []*schema.Provider
	providerFactories := map[string]terraform.ResourceProviderFactory{
		"aws": func() (terraform.ResourceProvider, error) {
			p := Provider()
			providers = append(providers, p.(*schema.Provider))
			return p, nil
		},
	}

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:          func() { testAccPreCheck(t) },
		ProviderFactories: providerFactories,
		CheckDestroy:      testAccCheckEbsSnapshotCopyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsEbsSnapshotCopyConfigWithRegions,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckEbsSnapshotCopyExistsWithProviders("aws_ebs_snapshot_copy.region_test", &v, &providers),
				),
			},
		},
	})

}

func TestAccAWSEbsSnapshotCopy_withKms(t *testing.T) {
	var v ec2.Snapshot
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckEbsSnapshotCopyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAwsEbsSnapshotCopyConfigWithKms,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckEbsSnapshotCopyExists("aws_ebs_snapshot_copy.kms_test", &v),
					resource.TestMatchResourceAttr("aws_ebs_snapshot_copy.kms_test", "kms_key_id",
						regexp.MustCompile(`^arn:aws:kms:[a-z]{2}-[a-z]+-\d{1}:[0-9]{12}:key/[a-z0-9-]{36}$`)),
				),
			},
		},
	})
}

func testAccCheckEbsSnapshotCopyDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).ec2conn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_ebs_snapshot_copy" {
			continue
		}

		resp, err := conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{
			SnapshotIds: []*string{aws.String(rs.Primary.ID)},
		})

		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSnapshot.NotFound" {
			return nil
		}

		if err == nil {
			for _, snapshot := range resp.Snapshots {
				if aws.StringValue(snapshot.SnapshotId) == rs.Primary.ID {
					return fmt.Errorf("EBS Snapshot still exists")
				}
			}
		}

		return err
	}

	return nil
}

func testAccCheckEbsSnapshotCopyExists(n string, v *ec2.Snapshot) resource.TestCheckFunc {
	providers := []*schema.Provider{testAccProvider}
	return testAccCheckEbsSnapshotCopyExistsWithProviders(n, v, &providers)
}

func testAccCheckEbsSnapshotCopyExistsWithProviders(n string, v *ec2.Snapshot, providers *[]*schema.Provider) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		for _, provider := range *providers {
			// Ignore if Meta is empty, this can happen for validation providers
			if provider.Meta() == nil {
				continue
			}

			conn := provider.Meta().(*AWSClient).ec2conn

			request := &ec2.DescribeSnapshotsInput{
				SnapshotIds: []*string{aws.String(rs.Primary.ID)},
			}

			response, err := conn.DescribeSnapshots(request)
			if err == nil {
				if response.Snapshots != nil && len(response.Snapshots) > 0 {
					*v = *response.Snapshots[0]
					return nil
				}
			}
		}
		return fmt.Errorf("Error finding EC2 Snapshot %s", rs.Primary.ID)
	}
}

const testAccAwsEbsSnapshotCopyConfig = `
resource "aws_ebs_volume" "test" {
  availability_zone = "us-west-2a"
  size              = 1
}

resource "aws_ebs_snapshot" "test" {
  volume_id = "${aws_ebs_volume.test.id}"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfig"
  }
}

resource "aws_ebs_snapshot_copy" "test" {
  source_snapshot_id = "${aws_ebs_snapshot.test.id}"
	source_region      = "us-west-2"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfig"
  }
}
`

const testAccAwsEbsSnapshotCopyConfigWithDescription = `
resource "aws_ebs_volume" "description_test" {
	availability_zone = "us-west-2a"
	size              = 1

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithDescription"
  }
}

resource "aws_ebs_snapshot" "description_test" {
	volume_id   = "${aws_ebs_volume.description_test.id}"
	description = "EBS Snapshot Acceptance Test"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithDescription"
  }
}

resource "aws_ebs_snapshot_copy" "description_test" {
	description        = "Copy Snapshot Acceptance Test"
  source_snapshot_id = "${aws_ebs_snapshot.description_test.id}"
	source_region      = "us-west-2"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithDescription"
  }
}
`

const testAccAwsEbsSnapshotCopyConfigWithRegions = `
provider "aws" {
  region = "us-west-2"
  alias  = "uswest2"
}

provider "aws" {
  region = "us-east-1"
  alias  = "useast1"
}

resource "aws_ebs_volume" "region_test" {
  provider          = "aws.uswest2"
  availability_zone = "us-west-2a"
  size              = 1

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithRegions"
  }
}

resource "aws_ebs_snapshot" "region_test" {
  provider  = "aws.uswest2"
  volume_id = "${aws_ebs_volume.region_test.id}"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithRegions"
  }
}

resource "aws_ebs_snapshot_copy" "region_test" {
  provider           = "aws.useast1"
  source_snapshot_id = "${aws_ebs_snapshot.region_test.id}"
  source_region      = "us-west-2"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithRegions"
  }
}
`

const testAccAwsEbsSnapshotCopyConfigWithKms = `
provider "aws" {
  region = "us-west-2"
}

resource "aws_kms_key" "kms_test" {
  description             = "testAccAwsEbsSnapshotCopyConfigWithKms"
  deletion_window_in_days = 7
}

resource "aws_ebs_volume" "kms_test" {
  availability_zone = "us-west-2a"
  size              = 1

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithKms"
  }
}

resource "aws_ebs_snapshot" "kms_test" {
  volume_id = "${aws_ebs_volume.kms_test.id}"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithKms"
  }
}

resource "aws_ebs_snapshot_copy" "kms_test" {
  source_snapshot_id = "${aws_ebs_snapshot.kms_test.id}"
  source_region      = "us-west-2"
  encrypted          = true
  kms_key_id         = "${aws_kms_key.kms_test.arn}"

  tags = {
    Name = "testAccAwsEbsSnapshotCopyConfigWithKms"
  }
}
`
<|endoftext|>


<commit_before>// Copyright 2016-2018 Authors of Cilium
//
// 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 k8s

import (
	"net"
	"time"

	"github.com/cilium/cilium/pkg/annotation"
	"github.com/cilium/cilium/pkg/cidr"
	clientset "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned"
	"github.com/cilium/cilium/pkg/logging/logfields"

	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/kubernetes"
)

// K8sClient is a wrapper around kubernetes.Interface.
type K8sClient struct {
	// kubernetes.Interface is the object through which interactions with
	// Kubernetes are performed.
	kubernetes.Interface
}

// K8sCiliumClient is a wrapper around clientset.Interface.
type K8sCiliumClient struct {
	clientset.Interface
}

func updateNodeAnnotation(c kubernetes.Interface, node *v1.Node, v4CIDR, v6CIDR *cidr.CIDR, v4HealthIP, v6HealthIP, v4CiliumHostIP, v6CiliumHostIP net.IP) (*v1.Node, error) {
	if node.Annotations == nil {
		node.Annotations = map[string]string{}
	}

	if v4CIDR != nil {
		node.Annotations[annotation.V4CIDRName] = v4CIDR.String()
	}
	if v6CIDR != nil {
		node.Annotations[annotation.V6CIDRName] = v6CIDR.String()
	}

	if v4HealthIP != nil {
		node.Annotations[annotation.V4HealthName] = v4HealthIP.String()
	}
	if v6HealthIP != nil {
		node.Annotations[annotation.V6HealthName] = v6HealthIP.String()
	}

	if v4CiliumHostIP != nil {
		node.Annotations[annotation.CiliumHostIP] = v4CiliumHostIP.String()
	}

	if v6CiliumHostIP != nil {
		node.Annotations[annotation.CiliumHostIPv6] = v6CiliumHostIP.String()
	}

	node, err := c.CoreV1().Nodes().Update(node)
	if err != nil {
		return nil, err
	}

	if node == nil {
		return nil, ErrNilNode
	}

	return node, nil
}

// AnnotateNode writes v4 and v6 CIDRs and health IPs in the given k8s node name.
// In case of failure while updating the node, this function while spawn a go
// routine to retry the node update indefinitely.
func (k8sCli K8sClient) AnnotateNode(nodeName string, v4CIDR, v6CIDR *cidr.CIDR, v4HealthIP, v6HealthIP, v4CiliumHostIP, v6CiliumHostIP net.IP) error {
	scopedLog := log.WithFields(logrus.Fields{
		logfields.NodeName:       nodeName,
		logfields.V4Prefix:       v4CIDR,
		logfields.V6Prefix:       v6CIDR,
		logfields.V4HealthIP:     v4HealthIP,
		logfields.V6HealthIP:     v6HealthIP,
		logfields.V4CiliumHostIP: v4CiliumHostIP,
		logfields.V6CiliumHostIP: v6CiliumHostIP,
	})
	scopedLog.Debug("Updating node annotations with node CIDRs")

	go func(c kubernetes.Interface, nodeName string, v4CIDR, v6CIDR *cidr.CIDR, v4HealthIP, v6HealthIP, v4CiliumHostIP, v6CiliumHostIP net.IP) {
		var node *v1.Node
		var err error

		for n := 1; n <= maxUpdateRetries; n++ {
			if node == nil {
				node, err = GetNode(c, nodeName)
				if errors.IsNotFound(err) {
					err = ErrNilNode
				}
			}

			if err == nil && node != nil {
				node, err = updateNodeAnnotation(c, node, v4CIDR, v6CIDR, v4HealthIP, v6HealthIP, v4CiliumHostIP, v6CiliumHostIP)
			}

			switch {
			case err == nil:
				return
			case errors.IsConflict(err):
				scopedLog.WithFields(logrus.Fields{
					fieldRetry:    n,
					fieldMaxRetry: maxUpdateRetries,
				}).WithError(err).Debugf("Unable to update node resource with annotation")
			default:
				scopedLog.WithFields(logrus.Fields{
					fieldRetry:    n,
					fieldMaxRetry: maxUpdateRetries,
				}).WithError(err).Warn("Unable to update node resource with annotation")
			}

			time.Sleep(time.Duration(n) * time.Second)
		}
	}(k8sCli, nodeName, v4CIDR, v6CIDR, v4HealthIP, v6HealthIP, v4CiliumHostIP, v6CiliumHostIP)

	return nil
}
<commit_msg>pkg/k8s: switch AnnotateNode as a controller<commit_after>// Copyright 2016-2019 Authors of Cilium
//
// 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 k8s

import (
	"context"
	"net"

	"github.com/cilium/cilium/pkg/annotation"
	"github.com/cilium/cilium/pkg/cidr"
	"github.com/cilium/cilium/pkg/controller"
	clientset "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned"
	"github.com/cilium/cilium/pkg/logging/logfields"

	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/kubernetes"
)

// K8sClient is a wrapper around kubernetes.Interface.
type K8sClient struct {
	// kubernetes.Interface is the object through which interactions with
	// Kubernetes are performed.
	kubernetes.Interface
}

// K8sCiliumClient is a wrapper around clientset.Interface.
type K8sCiliumClient struct {
	clientset.Interface
}

func updateNodeAnnotation(c kubernetes.Interface, node *v1.Node, v4CIDR, v6CIDR *cidr.CIDR, v4HealthIP, v6HealthIP, v4CiliumHostIP, v6CiliumHostIP net.IP) (*v1.Node, error) {
	if node.Annotations == nil {
		node.Annotations = map[string]string{}
	}

	if v4CIDR != nil {
		node.Annotations[annotation.V4CIDRName] = v4CIDR.String()
	}
	if v6CIDR != nil {
		node.Annotations[annotation.V6CIDRName] = v6CIDR.String()
	}

	if v4HealthIP != nil {
		node.Annotations[annotation.V4HealthName] = v4HealthIP.String()
	}
	if v6HealthIP != nil {
		node.Annotations[annotation.V6HealthName] = v6HealthIP.String()
	}

	if v4CiliumHostIP != nil {
		node.Annotations[annotation.CiliumHostIP] = v4CiliumHostIP.String()
	}

	if v6CiliumHostIP != nil {
		node.Annotations[annotation.CiliumHostIPv6] = v6CiliumHostIP.String()
	}

	node, err := c.CoreV1().Nodes().Update(node)
	if err != nil {
		return nil, err
	}

	if node == nil {
		return nil, ErrNilNode
	}

	return node, nil
}

// AnnotateNode writes v4 and v6 CIDRs and health IPs in the given k8s node name.
// In case of failure while updating the node, this function while spawn a go
// routine to retry the node update indefinitely.
func (k8sCli K8sClient) AnnotateNode(nodeName string, v4CIDR, v6CIDR *cidr.CIDR, v4HealthIP, v6HealthIP, v4CiliumHostIP, v6CiliumHostIP net.IP) error {
	scopedLog := log.WithFields(logrus.Fields{
		logfields.NodeName:       nodeName,
		logfields.V4Prefix:       v4CIDR,
		logfields.V6Prefix:       v6CIDR,
		logfields.V4HealthIP:     v4HealthIP,
		logfields.V6HealthIP:     v6HealthIP,
		logfields.V4CiliumHostIP: v4CiliumHostIP,
		logfields.V6CiliumHostIP: v6CiliumHostIP,
	})
	scopedLog.Debug("Updating node annotations with node CIDRs")

	controller.NewManager().UpdateController("update-k8s-node-annotations",
		controller.ControllerParams{
			DoFunc: func(_ context.Context) error {
				node, err := GetNode(k8sCli, nodeName)
				if errors.IsNotFound(err) {
					err = ErrNilNode
				}

				if err == nil && node != nil {
					_, err = updateNodeAnnotation(k8sCli, node, v4CIDR, v6CIDR, v4HealthIP, v6HealthIP, v4CiliumHostIP, v6CiliumHostIP)
				}

				switch {
				case err == nil:
					return nil
				case errors.IsConflict(err):
					scopedLog.WithFields(logrus.Fields{
						fieldMaxRetry: maxUpdateRetries,
					}).WithError(err).Debugf("Unable to update node resource with annotation")
					return err
				default:
					scopedLog.WithFields(logrus.Fields{}).WithError(err).Warn("Unable to update node resource with annotation")
					return err
				}
			},
		})

	return nil
}
<|endoftext|>


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

// This package provides basic interfaces to I/O primitives.
// Its primary job is to wrap existing implementations of such primitives,
// such as those in package os, into shared public interfaces that
// abstract the functionality, plus some other related primitives.
package io

import (
	"os"
	"strings"
)

// Error represents an unexpected I/O behavior.
type Error struct {
	os.ErrorString
}

// ErrShortWrite means that a write accepted fewer bytes than requested
// but failed to return an explicit error.
var ErrShortWrite os.Error = &Error{"short write"}

// ErrUnexpectedEOF means that os.EOF was encountered in the
// middle of reading a fixed-size block or data structure.
var ErrUnexpectedEOF os.Error = &Error{"unexpected EOF"}

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p.  It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered.
// Even if Read returns n < len(p),
// it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available rather than block waiting for more.
//
// At the end of the input stream, Read returns 0, os.EOF.
// Read may return a non-zero number of bytes with a non-nil err.
// In particular, a Read that exhausts the input may return n > 0, os.EOF.
type Reader interface {
	Read(p []byte) (n int, err os.Error)
}

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
type Writer interface {
	Write(p []byte) (n int, err os.Error)
}

// Closer is the interface that wraps the basic Close method.
type Closer interface {
	Close() os.Error
}

// Seeker is the interface that wraps the basic Seek method.
//
// Seek sets the offset for the next Read or Write to offset,
// interpreted according to whence: 0 means relative to the origin of
// the file, 1 means relative to the current offset, and 2 means
// relative to the end.  Seek returns the new offset and an Error, if
// any.
type Seeker interface {
	Seek(offset int64, whence int) (ret int64, err os.Error)
}

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
	Reader
	Writer
}

// ReadCloser is the interface that groups the basic Read and Close methods.
type ReadCloser interface {
	Reader
	Closer
}

// WriteCloser is the interface that groups the basic Write and Close methods.
type WriteCloser interface {
	Writer
	Closer
}

// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
type ReadWriteCloser interface {
	Reader
	Writer
	Closer
}

// ReadSeeker is the interface that groups the basic Read and Seek methods.
type ReadSeeker interface {
	Reader
	Seeker
}

// WriteSeeker is the interface that groups the basic Write and Seek methods.
type WriteSeeker interface {
	Writer
	Seeker
}

// ReadWriteSeeker is the interface that groups the basic Read, Write and Seek methods.
type ReadWriteSeeker interface {
	Reader
	Writer
	Seeker
}

// ReaderFrom is the interface that wraps the ReadFrom method.
type ReaderFrom interface {
	ReadFrom(r Reader) (n int64, err os.Error)
}

// WriterTo is the interface that wraps the WriteTo method.
type WriterTo interface {
	WriteTo(w Writer) (n int64, err os.Error)
}

// ReaderAt is the interface that wraps the basic ReadAt method.
//
// ReadAt reads len(p) bytes into p starting at offset off in the
// underlying data stream.  It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered.
//
// Even if ReadAt returns n < len(p),
// it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, ReadAt blocks
// until either all the data is available or an error occurs.
//
// At the end of the input stream, ReadAt returns 0, os.EOF.
// ReadAt may return a non-zero number of bytes with a non-nil err.
// In particular, a ReadAt that exhausts the input may return n > 0, os.EOF.
type ReaderAt interface {
	ReadAt(p []byte, off int64) (n int, err os.Error)
}

// WriterAt is the interface that wraps the basic WriteAt method.
//
// WriteAt writes len(p) bytes from p to the underlying data stream
// at offset off.  It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// WriteAt must return a non-nil error if it returns n < len(p).
type WriterAt interface {
	WriteAt(p []byte, off int64) (n int, err os.Error)
}

// WriteString writes the contents of the string s to w, which accepts an array of bytes.
func WriteString(w Writer, s string) (n int, err os.Error) {
	return w.Write(strings.Bytes(s))
}

// ReadAtLeast reads from r into buf until it has read at least min bytes.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is os.EOF only if no bytes were read.
// If an EOF happens after reading fewer than min bytes,
// ReadAtLeast returns ErrUnexpectedEOF.
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err os.Error) {
	n = 0
	for n < min {
		nn, e := r.Read(buf[n:])
		if nn > 0 {
			n += nn
		}
		if e != nil {
			if e == os.EOF && n > 0 {
				e = ErrUnexpectedEOF
			}
			return n, e
		}
	}
	return n, nil
}

// ReadFull reads exactly len(buf) bytes from r into buf.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is os.EOF only if no bytes were read.
// If an EOF happens after reading some but not all the bytes,
// ReadFull returns ErrUnexpectedEOF.
func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
	return ReadAtLeast(r, buf, len(buf))
}

// Copyn copies n bytes (or until an error) from src to dst.
// It returns the number of bytes copied and the error, if any.
//
// If dst implements the ReaderFrom interface,
// the copy is implemented by calling dst.ReadFrom(src).
func Copyn(dst Writer, src Reader, n int64) (written int64, err os.Error) {
	// If the writer has a ReadFrom method, use it to to do the copy.
	// Avoids a buffer allocation and a copy.
	if rt, ok := dst.(ReaderFrom); ok {
		return rt.ReadFrom(LimitReader(src, n))
	}
	buf := make([]byte, 32*1024)
	for written < n {
		l := len(buf)
		if d := n - written; d < int64(l) {
			l = int(d)
		}
		nr, er := src.Read(buf[0:l])
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr])
			if nw > 0 {
				written += int64(nw)
			}
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = ErrShortWrite
				break
			}
		}
		if er != nil {
			err = er
			break
		}
	}
	return written, err
}

// Copy copies from src to dst until either EOF is reached
// on src or an error occurs.  It returns the number of bytes
// copied and the error, if any.
//
// If dst implements the ReaderFrom interface,
// the copy is implemented by calling dst.ReadFrom(src).
// Otherwise, if src implements the WriterTo interface,
// the copy is implemented by calling src.WriteTo(dst).
func Copy(dst Writer, src Reader) (written int64, err os.Error) {
	// If the writer has a ReadFrom method, use it to to do the copy.
	// Avoids an allocation and a copy.
	if rt, ok := dst.(ReaderFrom); ok {
		return rt.ReadFrom(src)
	}
	// Similarly, if the reader has a WriteTo method, use it to to do the copy.
	if wt, ok := src.(WriterTo); ok {
		return wt.WriteTo(dst)
	}
	buf := make([]byte, 32*1024)
	for {
		nr, er := src.Read(buf)
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr])
			if nw > 0 {
				written += int64(nw)
			}
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = ErrShortWrite
				break
			}
		}
		if er == os.EOF {
			break
		}
		if er != nil {
			err = er
			break
		}
	}
	return written, err
}

// LimitReader returns a Reader that reads from r
// but stops with os.EOF after n bytes.
func LimitReader(r Reader, n int64) Reader { return &limitedReader{r, n} }

type limitedReader struct {
	r Reader
	n int64
}

func (l *limitedReader) Read(p []byte) (n int, err os.Error) {
	if l.n <= 0 {
		return 0, os.EOF
	}
	if int64(len(p)) > l.n {
		p = p[0:l.n]
	}
	n, err = l.r.Read(p)
	l.n -= int64(n)
	return
}

// NewSectionReader returns a SectionReader that reads from r
// starting at offset off and stops with os.EOF after n bytes.
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader {
	return &SectionReader{r, off, off, off + n}
}

// SectionReader implements Read, Seek, and ReadAt on a section
// of an underlying ReaderAt.
type SectionReader struct {
	r     ReaderAt
	base  int64
	off   int64
	limit int64
}

func (s *SectionReader) Read(p []byte) (n int, err os.Error) {
	if s.off >= s.limit {
		return 0, os.EOF
	}
	if max := s.limit - s.off; int64(len(p)) > max {
		p = p[0:max]
	}
	n, err = s.r.ReadAt(p, s.off)
	s.off += int64(n)
	return
}

func (s *SectionReader) Seek(offset int64, whence int) (ret int64, err os.Error) {
	switch whence {
	default:
		return 0, os.EINVAL
	case 0:
		offset += s.base
	case 1:
		offset += s.off
	case 2:
		offset += s.limit
	}
	if offset < s.off || offset > s.limit {
		return 0, os.EINVAL
	}
	s.off = offset
	return offset - s.base, nil
}

func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err os.Error) {
	if off < 0 || off >= s.limit-s.base {
		return 0, os.EOF
	}
	off += s.base
	if max := s.limit - off; int64(len(p)) > max {
		p = p[0:max]
	}
	return s.r.ReadAt(p, off)
}

// Size returns the size of the section in bytes.
func (s *SectionReader) Size() int64 { return s.limit - s.base }
<commit_msg>add a standard interface that wraps ReadByte, io.ReadByter<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package provides basic interfaces to I/O primitives.
// Its primary job is to wrap existing implementations of such primitives,
// such as those in package os, into shared public interfaces that
// abstract the functionality, plus some other related primitives.
package io

import (
	"os"
	"strings"
)

// Error represents an unexpected I/O behavior.
type Error struct {
	os.ErrorString
}

// ErrShortWrite means that a write accepted fewer bytes than requested
// but failed to return an explicit error.
var ErrShortWrite os.Error = &Error{"short write"}

// ErrUnexpectedEOF means that os.EOF was encountered in the
// middle of reading a fixed-size block or data structure.
var ErrUnexpectedEOF os.Error = &Error{"unexpected EOF"}

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p.  It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered.
// Even if Read returns n < len(p),
// it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available rather than block waiting for more.
//
// At the end of the input stream, Read returns 0, os.EOF.
// Read may return a non-zero number of bytes with a non-nil err.
// In particular, a Read that exhausts the input may return n > 0, os.EOF.
type Reader interface {
	Read(p []byte) (n int, err os.Error)
}

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
type Writer interface {
	Write(p []byte) (n int, err os.Error)
}

// Closer is the interface that wraps the basic Close method.
type Closer interface {
	Close() os.Error
}

// Seeker is the interface that wraps the basic Seek method.
//
// Seek sets the offset for the next Read or Write to offset,
// interpreted according to whence: 0 means relative to the origin of
// the file, 1 means relative to the current offset, and 2 means
// relative to the end.  Seek returns the new offset and an Error, if
// any.
type Seeker interface {
	Seek(offset int64, whence int) (ret int64, err os.Error)
}

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
	Reader
	Writer
}

// ReadCloser is the interface that groups the basic Read and Close methods.
type ReadCloser interface {
	Reader
	Closer
}

// WriteCloser is the interface that groups the basic Write and Close methods.
type WriteCloser interface {
	Writer
	Closer
}

// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
type ReadWriteCloser interface {
	Reader
	Writer
	Closer
}

// ReadSeeker is the interface that groups the basic Read and Seek methods.
type ReadSeeker interface {
	Reader
	Seeker
}

// WriteSeeker is the interface that groups the basic Write and Seek methods.
type WriteSeeker interface {
	Writer
	Seeker
}

// ReadWriteSeeker is the interface that groups the basic Read, Write and Seek methods.
type ReadWriteSeeker interface {
	Reader
	Writer
	Seeker
}

// ReaderFrom is the interface that wraps the ReadFrom method.
type ReaderFrom interface {
	ReadFrom(r Reader) (n int64, err os.Error)
}

// WriterTo is the interface that wraps the WriteTo method.
type WriterTo interface {
	WriteTo(w Writer) (n int64, err os.Error)
}

// ReaderAt is the interface that wraps the basic ReadAt method.
//
// ReadAt reads len(p) bytes into p starting at offset off in the
// underlying data stream.  It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered.
//
// Even if ReadAt returns n < len(p),
// it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, ReadAt blocks
// until either all the data is available or an error occurs.
//
// At the end of the input stream, ReadAt returns 0, os.EOF.
// ReadAt may return a non-zero number of bytes with a non-nil err.
// In particular, a ReadAt that exhausts the input may return n > 0, os.EOF.
type ReaderAt interface {
	ReadAt(p []byte, off int64) (n int, err os.Error)
}

// WriterAt is the interface that wraps the basic WriteAt method.
//
// WriteAt writes len(p) bytes from p to the underlying data stream
// at offset off.  It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// WriteAt must return a non-nil error if it returns n < len(p).
type WriterAt interface {
	WriteAt(p []byte, off int64) (n int, err os.Error)
}

// ReadByter is the interface that wraps the ReadByte method.
//
// ReadByte reads and returns the next byte from the input.
// If no byte is available, err will be set.
type ReadByter interface {
	ReadByte() (c byte, err os.Error)
}

// WriteString writes the contents of the string s to w, which accepts an array of bytes.
func WriteString(w Writer, s string) (n int, err os.Error) {
	return w.Write(strings.Bytes(s))
}

// ReadAtLeast reads from r into buf until it has read at least min bytes.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is os.EOF only if no bytes were read.
// If an EOF happens after reading fewer than min bytes,
// ReadAtLeast returns ErrUnexpectedEOF.
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err os.Error) {
	n = 0
	for n < min {
		nn, e := r.Read(buf[n:])
		if nn > 0 {
			n += nn
		}
		if e != nil {
			if e == os.EOF && n > 0 {
				e = ErrUnexpectedEOF
			}
			return n, e
		}
	}
	return n, nil
}

// ReadFull reads exactly len(buf) bytes from r into buf.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is os.EOF only if no bytes were read.
// If an EOF happens after reading some but not all the bytes,
// ReadFull returns ErrUnexpectedEOF.
func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
	return ReadAtLeast(r, buf, len(buf))
}

// Copyn copies n bytes (or until an error) from src to dst.
// It returns the number of bytes copied and the error, if any.
//
// If dst implements the ReaderFrom interface,
// the copy is implemented by calling dst.ReadFrom(src).
func Copyn(dst Writer, src Reader, n int64) (written int64, err os.Error) {
	// If the writer has a ReadFrom method, use it to to do the copy.
	// Avoids a buffer allocation and a copy.
	if rt, ok := dst.(ReaderFrom); ok {
		return rt.ReadFrom(LimitReader(src, n))
	}
	buf := make([]byte, 32*1024)
	for written < n {
		l := len(buf)
		if d := n - written; d < int64(l) {
			l = int(d)
		}
		nr, er := src.Read(buf[0:l])
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr])
			if nw > 0 {
				written += int64(nw)
			}
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = ErrShortWrite
				break
			}
		}
		if er != nil {
			err = er
			break
		}
	}
	return written, err
}

// Copy copies from src to dst until either EOF is reached
// on src or an error occurs.  It returns the number of bytes
// copied and the error, if any.
//
// If dst implements the ReaderFrom interface,
// the copy is implemented by calling dst.ReadFrom(src).
// Otherwise, if src implements the WriterTo interface,
// the copy is implemented by calling src.WriteTo(dst).
func Copy(dst Writer, src Reader) (written int64, err os.Error) {
	// If the writer has a ReadFrom method, use it to to do the copy.
	// Avoids an allocation and a copy.
	if rt, ok := dst.(ReaderFrom); ok {
		return rt.ReadFrom(src)
	}
	// Similarly, if the reader has a WriteTo method, use it to to do the copy.
	if wt, ok := src.(WriterTo); ok {
		return wt.WriteTo(dst)
	}
	buf := make([]byte, 32*1024)
	for {
		nr, er := src.Read(buf)
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr])
			if nw > 0 {
				written += int64(nw)
			}
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = ErrShortWrite
				break
			}
		}
		if er == os.EOF {
			break
		}
		if er != nil {
			err = er
			break
		}
	}
	return written, err
}

// LimitReader returns a Reader that reads from r
// but stops with os.EOF after n bytes.
func LimitReader(r Reader, n int64) Reader { return &limitedReader{r, n} }

type limitedReader struct {
	r Reader
	n int64
}

func (l *limitedReader) Read(p []byte) (n int, err os.Error) {
	if l.n <= 0 {
		return 0, os.EOF
	}
	if int64(len(p)) > l.n {
		p = p[0:l.n]
	}
	n, err = l.r.Read(p)
	l.n -= int64(n)
	return
}

// NewSectionReader returns a SectionReader that reads from r
// starting at offset off and stops with os.EOF after n bytes.
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader {
	return &SectionReader{r, off, off, off + n}
}

// SectionReader implements Read, Seek, and ReadAt on a section
// of an underlying ReaderAt.
type SectionReader struct {
	r     ReaderAt
	base  int64
	off   int64
	limit int64
}

func (s *SectionReader) Read(p []byte) (n int, err os.Error) {
	if s.off >= s.limit {
		return 0, os.EOF
	}
	if max := s.limit - s.off; int64(len(p)) > max {
		p = p[0:max]
	}
	n, err = s.r.ReadAt(p, s.off)
	s.off += int64(n)
	return
}

func (s *SectionReader) Seek(offset int64, whence int) (ret int64, err os.Error) {
	switch whence {
	default:
		return 0, os.EINVAL
	case 0:
		offset += s.base
	case 1:
		offset += s.off
	case 2:
		offset += s.limit
	}
	if offset < s.off || offset > s.limit {
		return 0, os.EINVAL
	}
	s.off = offset
	return offset - s.base, nil
}

func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err os.Error) {
	if off < 0 || off >= s.limit-s.base {
		return 0, os.EOF
	}
	off += s.base
	if max := s.limit - off; int64(len(p)) > max {
		p = p[0:max]
	}
	return s.r.ReadAt(p, off)
}

// Size returns the size of the section in bytes.
func (s *SectionReader) Size() int64 { return s.limit - s.base }
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors 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 version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// The "-dev" suffix in the version info indicates that fact, and it means the
// current build is from a version greater that version. For example, v0.7-dev
// means version > 0.7 and < 0.8. (There's exceptions to this rule, see
// docs/releasing.md for more details.)
//
// When releasing a new Kubernetes version, this file should be updated to
// reflect the new version, and then a git annotated tag (using format vX.Y
// where X == Major version and Y == Minor version) should be created to point
// to the commit that updates pkg/version/base.go

var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion instead.
	gitMajor     string = "0"              // major version, always numeric
	gitMinor     string = "20.1+"          // minor version, numeric possibly followed by "+"
	gitVersion   string = "v0.20.1-dev"    // version from git, output of $(git describe)
	gitCommit    string = ""               // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"
)
<commit_msg>Kubernetes version v0.20.2<commit_after>/*
Copyright 2014 The Kubernetes Authors 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 version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// The "-dev" suffix in the version info indicates that fact, and it means the
// current build is from a version greater that version. For example, v0.7-dev
// means version > 0.7 and < 0.8. (There's exceptions to this rule, see
// docs/releasing.md for more details.)
//
// When releasing a new Kubernetes version, this file should be updated to
// reflect the new version, and then a git annotated tag (using format vX.Y
// where X == Major version and Y == Minor version) should be created to point
// to the commit that updates pkg/version/base.go

var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion instead.
	gitMajor     string = "0"              // major version, always numeric
	gitMinor     string = "20.2"           // minor version, numeric possibly followed by "+"
	gitVersion   string = "v0.20.2"        // version from git, output of $(git describe)
	gitCommit    string = ""               // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"
)
<|endoftext|>


<commit_before>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	. "db"
	"model"
	"net/url"
	"strconv"
	"strings"

	"github.com/polaris1119/logger"
	"golang.org/x/net/context"
)

type ReadingLogic struct{}

var DefaultReading = ReadingLogic{}

func (ReadingLogic) FindLastList(beginTime string) ([]*model.MorningReading, error) {
	readings := make([]*model.MorningReading, 0)
	err := MasterDB.Where("ctime>? AND rtype=0", beginTime).OrderBy("id DESC").Find(&readings)

	return readings, err
}

// 获取晨读列表（分页）
func (ReadingLogic) FindBy(ctx context.Context, limit, rtype int, lastIds ...int) []*model.MorningReading {
	objLog := GetLogger(ctx)

	dbSession := MasterDB.Where("rtype=?", rtype)
	if len(lastIds) > 0 && lastIds[0] > 0 {
		dbSession.And("id<?", lastIds[0])
	}

	readingList := make([]*model.MorningReading, 0)
	err := dbSession.OrderBy("id DESC").Limit(limit).Find(&readingList)
	if err != nil {
		objLog.Errorln("ResourceLogic FindReadings Error:", err)
		return nil
	}

	return readingList
}

// 【我要晨读】
func (ReadingLogic) IReading(ctx context.Context, id int) string {
	objLog := GetLogger(ctx)

	reading := &model.MorningReading{}
	_, err := MasterDB.Id(id).Get(reading)
	if err != nil {
		objLog.Errorln("reading logic IReading error:", err)
		return "/readings"
	}

	if reading.Id == 0 {
		return "/readings"
	}

	go MasterDB.Id(id).Incr("clicknum", 1).Update(reading)

	if reading.Inner == 0 {
		return "/wr?u=" + reading.Url
	}

	return "/articles/" + strconv.Itoa(reading.Inner)
}

// FindReadingByPage 获取晨读列表（分页）
func (ReadingLogic) FindReadingByPage(ctx context.Context, conds map[string]string, curPage, limit int) ([]*model.MorningReading, int) {
	objLog := GetLogger(ctx)

	session := MasterDB.NewSession()

	for k, v := range conds {
		session.And(k+"=?", v)
	}

	totalSession := session.Clone()

	offset := (curPage - 1) * limit
	readingList := make([]*model.MorningReading, 0)
	err := session.OrderBy("id DESC").Limit(limit, offset).Find(&readingList)
	if err != nil {
		objLog.Errorln("reading find error:", err)
		return nil, 0
	}

	total, err := totalSession.Count(new(model.MorningReading))
	if err != nil {
		objLog.Errorln("reading find count error:", err)
		return nil, 0
	}

	return readingList, int(total)
}

// SaveReading 保存晨读
func (ReadingLogic) SaveReading(ctx context.Context, form url.Values, username string) (errMsg string, err error) {
	reading := &model.MorningReading{}
	err = schemaDecoder.Decode(reading, form)
	if err != nil {
		logger.Errorln("reading SaveReading error", err)
		errMsg = err.Error()
		return
	}

	if reading.Inner != 0 {
		reading.Url = ""
	}

	reading.Moreurls = strings.TrimSpace(reading.Moreurls)
	if strings.Contains(reading.Moreurls, "\n") {
		reading.Moreurls = strings.Join(strings.Split(reading.Moreurls, "\n"), ",")
	}

	reading.Username = username

	logger.Debugln(reading.Rtype, "id=", reading.Id)
	if reading.Id != 0 {
		_, err = MasterDB.Id(reading.Id).Update(reading)
	} else {
		_, err = MasterDB.Insert(reading)
	}

	if err != nil {
		errMsg = "内部服务器错误"
		logger.Errorln("reading save:", errMsg, ":", err)
		return
	}

	return
}

// FindById 获取单条晨读
func (ReadingLogic) FindById(ctx context.Context, id int) *model.MorningReading {
	reading := &model.MorningReading{}
	_, err := MasterDB.Id(id).Get(reading)
	if err != nil {
		logger.Errorln("reading logic FindReadingById Error:", err)
		return nil
	}

	if reading.Id == 0 {
		return nil
	}

	return reading
}
<commit_msg>update reading report<commit_after>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author:polaris	polaris@studygolang.com

package logic

import (
	. "db"
	"model"
	"net/url"
	"strconv"
	"strings"

	"github.com/polaris1119/logger"
	"golang.org/x/net/context"
)

type ReadingLogic struct{}

var DefaultReading = ReadingLogic{}

func (ReadingLogic) FindLastList(beginTime string) ([]*model.MorningReading, error) {
	readings := make([]*model.MorningReading, 0)
	err := MasterDB.Where("ctime>? AND rtype=0", beginTime).OrderBy("id DESC").Find(&readings)

	return readings, err
}

// 获取晨读列表（分页）
func (ReadingLogic) FindBy(ctx context.Context, limit, rtype int, lastIds ...int) []*model.MorningReading {
	objLog := GetLogger(ctx)

	dbSession := MasterDB.Where("rtype=?", rtype)
	if len(lastIds) > 0 && lastIds[0] > 0 {
		dbSession.And("id<?", lastIds[0])
	}

	readingList := make([]*model.MorningReading, 0)
	err := dbSession.OrderBy("id DESC").Limit(limit).Find(&readingList)
	if err != nil {
		objLog.Errorln("ResourceLogic FindReadings Error:", err)
		return nil
	}

	return readingList
}

// 【我要晨读】
func (ReadingLogic) IReading(ctx context.Context, id int) string {
	objLog := GetLogger(ctx)

	reading := &model.MorningReading{}
	_, err := MasterDB.Id(id).Get(reading)
	if err != nil {
		objLog.Errorln("reading logic IReading error:", err)
		return "/readings"
	}

	if reading.Id == 0 {
		return "/readings"
	}

	go MasterDB.Id(id).Incr("clicknum", 1).Update(reading)

	if reading.Inner == 0 {
		return "/wr?u=" + reading.Url
	}

	return "/articles/" + strconv.Itoa(reading.Inner)
}

// FindReadingByPage 获取晨读列表（分页）
func (ReadingLogic) FindReadingByPage(ctx context.Context, conds map[string]string, curPage, limit int) ([]*model.MorningReading, int) {
	objLog := GetLogger(ctx)

	session := MasterDB.NewSession()

	for k, v := range conds {
		session.And(k+"=?", v)
	}

	totalSession := session.Clone()

	offset := (curPage - 1) * limit
	readingList := make([]*model.MorningReading, 0)
	err := session.OrderBy("id DESC").Limit(limit, offset).Find(&readingList)
	if err != nil {
		objLog.Errorln("reading find error:", err)
		return nil, 0
	}

	total, err := totalSession.Count(new(model.MorningReading))
	if err != nil {
		objLog.Errorln("reading find count error:", err)
		return nil, 0
	}

	return readingList, int(total)
}

// SaveReading 保存晨读
func (ReadingLogic) SaveReading(ctx context.Context, form url.Values, username string) (errMsg string, err error) {
	reading := &model.MorningReading{}
	err = schemaDecoder.Decode(reading, form)
	if err != nil {
		logger.Errorln("reading SaveReading error", err)
		errMsg = err.Error()
		return
	}

	readings := make([]*model.MorningReading, 0)
	if reading.Inner != 0 {
		reading.Url = ""
		err = MasterDB.Where("inner=?", reading.Inner).OrderBy("id DESC").Find(&readings)
	} else {
		err = MasterDB.Where("url=?", reading.Url).OrderBy("id DESC").Find(&readings)
	}
	if err != nil {
		logger.Errorln("reading SaveReading MasterDB.Where() error", err)
		errMsg = err.Error()
		return
	}

	reading.Moreurls = strings.TrimSpace(reading.Moreurls)
	if strings.Contains(reading.Moreurls, "\n") {
		reading.Moreurls = strings.Join(strings.Split(reading.Moreurls, "\n"), ",")
	}

	reading.Username = username

	logger.Debugln(reading.Rtype, "id=", reading.Id)
	if reading.Id != 0 {
		_, err = MasterDB.Id(reading.Id).Update(reading)
	} else {
		if len(readings) > 0 {
			logger.Errorln("reading report:", reading)
			return
		}
		_, err = MasterDB.Insert(reading)
	}

	if err != nil {
		errMsg = "内部服务器错误"
		logger.Errorln("reading save:", errMsg, ":", err)
		return
	}

	return
}

// FindById 获取单条晨读
func (ReadingLogic) FindById(ctx context.Context, id int) *model.MorningReading {
	reading := &model.MorningReading{}
	_, err := MasterDB.Id(id).Get(reading)
	if err != nil {
		logger.Errorln("reading logic FindReadingById Error:", err)
		return nil
	}

	if reading.Id == 0 {
		return nil
	}

	return reading
}
<|endoftext|>