<commit_before>/*
Copyright 2018 The Jetstack cert-manager contributors.

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 pki

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"math/big"
	"time"

	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	"github.com/jetstack/cert-manager/pkg/util"
)

func CommonNameForCertificate(crt *v1alpha1.Certificate) string {
	if crt.Spec.CommonName != "" {
		return crt.Spec.CommonName
	}
	if len(crt.Spec.DNSNames) == 0 {
		return ""
	}
	return crt.Spec.DNSNames[0]
}

func DNSNamesForCertificate(crt *v1alpha1.Certificate) []string {
	if len(crt.Spec.DNSNames) == 0 {
		if crt.Spec.CommonName == "" {
			return []string{}
		}
		return []string{crt.Spec.CommonName}
	}
	if crt.Spec.CommonName != "" {
		return util.RemoveDuplicates(append([]string{crt.Spec.CommonName}, crt.Spec.DNSNames...))
	}
	return crt.Spec.DNSNames
}

const defaultOrganization = "cert-manager"

func OrganizationForCertificate(crt *v1alpha1.Certificate) []string {
	if len(crt.Spec.Organization) == 0 {
		return []string{defaultOrganization}
	}

	return crt.Spec.Organization
}

var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128)

// default certification duration is 1 year
const defaultNotAfter = time.Hour * 24 * 365

func GenerateCSR(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate) (*x509.CertificateRequest, error) {
	commonName := CommonNameForCertificate(crt)
	dnsNames := DNSNamesForCertificate(crt)
	organization := OrganizationForCertificate(crt)

	if len(commonName) == 0 && len(dnsNames) == 0 {
		return nil, fmt.Errorf("no domains specified on certificate")
	}

	sigAlgo, err := SignatureAlgorithm(crt)
	if err != nil {
		return nil, err
	}

	return &x509.CertificateRequest{
		Version:            3,
		SignatureAlgorithm: sigAlgo,
		Subject: pkix.Name{
			Organization: organization,
			CommonName:   commonName,
		},
		DNSNames: dnsNames,
		// TODO: work out how best to handle extensions/key usages here
		ExtraExtensions: []pkix.Extension{},
	}, nil
}

// GenerateTemplate will create a x509.Certificate for the given Certificate resource.
// This should create a Certificate template that is equivalent to the CertificateRequest
// generated by GenerateCSR.
// The PublicKey field must be populated by the caller.
func GenerateTemplate(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate, serialNo *big.Int) (*x509.Certificate, error) {
	commonName := CommonNameForCertificate(crt)
	dnsNames := DNSNamesForCertificate(crt)
	organization := OrganizationForCertificate(crt)

	if len(commonName) == 0 && len(dnsNames) == 0 {
		return nil, fmt.Errorf("no domains specified on certificate")
	}

	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, fmt.Errorf("failed to generate serial number: %s", err.Error())
	}

	sigAlgo, err := SignatureAlgorithm(crt)
	if err != nil {
		return nil, err
	}

	keyUsages := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
	if crt.Spec.IsCA {
		keyUsages |= x509.KeyUsageCertSign
	}
	return &x509.Certificate{
		Version:               3,
		BasicConstraintsValid: true,
		SerialNumber:          serialNumber,
		SignatureAlgorithm:    sigAlgo,
		IsCA:                  crt.Spec.IsCA,
		Subject: pkix.Name{
			Organization: organization,
			CommonName:   commonName,
		},
		NotBefore: time.Now(),
		NotAfter:  time.Now().Add(defaultNotAfter),
		// see http://golang.org/pkg/crypto/x509/#KeyUsage
		KeyUsage: keyUsages,
		DNSNames: dnsNames,
	}, nil
}

// SignCertificate returns a signed x509.Certificate object for the given
// *v1alpha1.Certificate crt.
// publicKey is the public key of the signee, and signerKey is the private
// key of the signer.
func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, publicKey interface{}, signerKey interface{}) ([]byte, *x509.Certificate, error) {
	derBytes, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey, signerKey)

	if err != nil {
		return nil, nil, fmt.Errorf("error creating x509 certificate: %s", err.Error())
	}

	cert, err := DecodeDERCertificateBytes(derBytes)

	if err != nil {
		return nil, nil, fmt.Errorf("error decoding DER certificate bytes: %s", err.Error())
	}

	pemBytes := bytes.NewBuffer([]byte{})
	err = pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	if err != nil {
		return nil, nil, fmt.Errorf("error encoding certificate PEM: %s", err.Error())
	}

	// don't bundle the CA for selfsigned certificates
	// TODO: better comparison method here? for now we can just compare pointers.
	if issuerCert != template {
		// bundle the CA
		err = pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: issuerCert.Raw})
		if err != nil {
			return nil, nil, fmt.Errorf("error encoding issuer cetificate PEM: %s", err.Error())
		}
	}

	return pemBytes.Bytes(), cert, err
}

func EncodeCSR(template *x509.CertificateRequest, key crypto.Signer) ([]byte, error) {
	derBytes, err := x509.CreateCertificateRequest(rand.Reader, template, key)
	if err != nil {
		return nil, fmt.Errorf("error creating x509 certificate: %s", err.Error())
	}

	return derBytes, nil
}

func EncodeX509(cert *x509.Certificate) ([]byte, error) {
	caPem := bytes.NewBuffer([]byte{})
	err := pem.Encode(caPem, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
	if err != nil {
		return nil, err
	}

	return caPem.Bytes(), nil
}

// Return the appropriate signature algorithm for the certificate
// Adapted from https://github.com/cloudflare/cfssl/blob/master/csr/csr.go#L102
func SignatureAlgorithm(crt *v1alpha1.Certificate) (x509.SignatureAlgorithm, error) {
	switch crt.Spec.KeyAlgorithm {
	case v1alpha1.KeyAlgorithm(""):
		// If keyAlgorithm is not specified, we default to rsa with keysize 2048
		return x509.SHA256WithRSA, nil
	case v1alpha1.RSAKeyAlgorithm:
		switch {
		case crt.Spec.KeySize >= 4096:
			return x509.SHA512WithRSA, nil
		case crt.Spec.KeySize >= 3072:
			return x509.SHA384WithRSA, nil
		case crt.Spec.KeySize >= 2048:
			return x509.SHA256WithRSA, nil
		default:
			return x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported rsa keysize specified: %d. min keysize %d", crt.Spec.KeySize, MinRSAKeySize)
		}
	case v1alpha1.ECDSAKeyAlgorithm:
		switch crt.Spec.KeySize {
		case 521:
			return x509.ECDSAWithSHA512, nil
		case 384:
			return x509.ECDSAWithSHA384, nil
		case 256:
			return x509.ECDSAWithSHA256, nil
		default:
			return x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported ecdsa keysize specified: %d", crt.Spec.KeySize)
		}
	default:
		return x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported algorithm specified: %s. should be either 'ecdsa' or 'rsa", crt.Spec.KeyAlgorithm)
	}
}
<commit_msg>Update SignatureAlgorithm function to also return PublicKey type<commit_after>/*
Copyright 2018 The Jetstack cert-manager contributors.

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 pki

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"math/big"
	"time"

	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	"github.com/jetstack/cert-manager/pkg/util"
)

func CommonNameForCertificate(crt *v1alpha1.Certificate) string {
	if crt.Spec.CommonName != "" {
		return crt.Spec.CommonName
	}
	if len(crt.Spec.DNSNames) == 0 {
		return ""
	}
	return crt.Spec.DNSNames[0]
}

func DNSNamesForCertificate(crt *v1alpha1.Certificate) []string {
	if len(crt.Spec.DNSNames) == 0 {
		if crt.Spec.CommonName == "" {
			return []string{}
		}
		return []string{crt.Spec.CommonName}
	}
	if crt.Spec.CommonName != "" {
		return util.RemoveDuplicates(append([]string{crt.Spec.CommonName}, crt.Spec.DNSNames...))
	}
	return crt.Spec.DNSNames
}

const defaultOrganization = "cert-manager"

func OrganizationForCertificate(crt *v1alpha1.Certificate) []string {
	if len(crt.Spec.Organization) == 0 {
		return []string{defaultOrganization}
	}

	return crt.Spec.Organization
}

var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128)

// default certification duration is 1 year
const defaultNotAfter = time.Hour * 24 * 365

func GenerateCSR(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate) (*x509.CertificateRequest, error) {
	commonName := CommonNameForCertificate(crt)
	dnsNames := DNSNamesForCertificate(crt)
	organization := OrganizationForCertificate(crt)

	if len(commonName) == 0 && len(dnsNames) == 0 {
		return nil, fmt.Errorf("no domains specified on certificate")
	}

	pubKeyAlgo, sigAlgo, err := SignatureAlgorithm(crt)
	if err != nil {
		return nil, err
	}

	return &x509.CertificateRequest{
		Version:            3,
		SignatureAlgorithm: sigAlgo,
		PublicKeyAlgorithm: pubKeyAlgo,
		Subject: pkix.Name{
			Organization: organization,
			CommonName:   commonName,
		},
		DNSNames: dnsNames,
		// TODO: work out how best to handle extensions/key usages here
		ExtraExtensions: []pkix.Extension{},
	}, nil
}

// GenerateTemplate will create a x509.Certificate for the given Certificate resource.
// This should create a Certificate template that is equivalent to the CertificateRequest
// generated by GenerateCSR.
// The PublicKey field must be populated by the caller.
func GenerateTemplate(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate, serialNo *big.Int) (*x509.Certificate, error) {
	commonName := CommonNameForCertificate(crt)
	dnsNames := DNSNamesForCertificate(crt)
	organization := OrganizationForCertificate(crt)

	if len(commonName) == 0 && len(dnsNames) == 0 {
		return nil, fmt.Errorf("no domains specified on certificate")
	}

	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, fmt.Errorf("failed to generate serial number: %s", err.Error())
	}

	pubKeyAlgo, sigAlgo, err := SignatureAlgorithm(crt)
	if err != nil {
		return nil, err
	}

	keyUsages := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
	if crt.Spec.IsCA {
		keyUsages |= x509.KeyUsageCertSign
	}

	return &x509.Certificate{
		Version:               3,
		BasicConstraintsValid: true,
		SerialNumber:          serialNumber,
		SignatureAlgorithm:    sigAlgo,
		PublicKeyAlgorithm:    pubKeyAlgo,
		IsCA:                  crt.Spec.IsCA,
		Subject: pkix.Name{
			Organization: organization,
			CommonName:   commonName,
		},
		NotBefore: time.Now(),
		NotAfter:  time.Now().Add(defaultNotAfter),
		// see http://golang.org/pkg/crypto/x509/#KeyUsage
		KeyUsage: keyUsages,
		DNSNames: dnsNames,
	}, nil
}

// SignCertificate returns a signed x509.Certificate object for the given
// *v1alpha1.Certificate crt.
// publicKey is the public key of the signee, and signerKey is the private
// key of the signer.
func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, publicKey interface{}, signerKey interface{}) ([]byte, *x509.Certificate, error) {
	derBytes, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey, signerKey)

	if err != nil {
		return nil, nil, fmt.Errorf("error creating x509 certificate: %s", err.Error())
	}

	cert, err := DecodeDERCertificateBytes(derBytes)

	if err != nil {
		return nil, nil, fmt.Errorf("error decoding DER certificate bytes: %s", err.Error())
	}

	pemBytes := bytes.NewBuffer([]byte{})
	err = pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	if err != nil {
		return nil, nil, fmt.Errorf("error encoding certificate PEM: %s", err.Error())
	}

	// don't bundle the CA for selfsigned certificates
	// TODO: better comparison method here? for now we can just compare pointers.
	if issuerCert != template {
		// bundle the CA
		err = pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: issuerCert.Raw})
		if err != nil {
			return nil, nil, fmt.Errorf("error encoding issuer cetificate PEM: %s", err.Error())
		}
	}

	return pemBytes.Bytes(), cert, err
}

func EncodeCSR(template *x509.CertificateRequest, key crypto.Signer) ([]byte, error) {
	derBytes, err := x509.CreateCertificateRequest(rand.Reader, template, key)
	if err != nil {
		return nil, fmt.Errorf("error creating x509 certificate: %s", err.Error())
	}

	return derBytes, nil
}

func EncodeX509(cert *x509.Certificate) ([]byte, error) {
	caPem := bytes.NewBuffer([]byte{})
	err := pem.Encode(caPem, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
	if err != nil {
		return nil, err
	}

	return caPem.Bytes(), nil
}

// Return the appropriate signature algorithm for the certificate
// Adapted from https://github.com/cloudflare/cfssl/blob/master/csr/csr.go#L102
func SignatureAlgorithm(crt *v1alpha1.Certificate) (x509.PublicKeyAlgorithm, x509.SignatureAlgorithm, error) {
	var sigAlgo x509.SignatureAlgorithm
	var pubKeyAlgo x509.PublicKeyAlgorithm
	switch crt.Spec.KeyAlgorithm {
	case v1alpha1.KeyAlgorithm(""):
		// If keyAlgorithm is not specified, we default to rsa with keysize 2048
		pubKeyAlgo = x509.RSA
		sigAlgo = x509.SHA256WithRSA
	case v1alpha1.RSAKeyAlgorithm:
		pubKeyAlgo = x509.RSA
		switch {
		case crt.Spec.KeySize >= 4096:
			sigAlgo = x509.SHA512WithRSA
		case crt.Spec.KeySize >= 3072:
			sigAlgo = x509.SHA384WithRSA
		case crt.Spec.KeySize >= 2048:
			sigAlgo = x509.SHA256WithRSA
		default:
			return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported rsa keysize specified: %d. min keysize %d", crt.Spec.KeySize, MinRSAKeySize)
		}
	case v1alpha1.ECDSAKeyAlgorithm:
		pubKeyAlgo = x509.ECDSA
		switch crt.Spec.KeySize {
		case 521:
			sigAlgo = x509.ECDSAWithSHA512
		case 384:
			sigAlgo = x509.ECDSAWithSHA384
		case 256:
			sigAlgo = x509.ECDSAWithSHA256
		default:
			return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported ecdsa keysize specified: %d", crt.Spec.KeySize)
		}
	default:
		return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported algorithm specified: %s. should be either 'ecdsa' or 'rsa", crt.Spec.KeyAlgorithm)
	}
	return pubKeyAlgo, sigAlgo, nil
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/anthonynsimon/parrot/errors"
	"github.com/anthonynsimon/parrot/model"
	"github.com/anthonynsimon/parrot/render"
	"github.com/pressly/chi"
)

func createLocale(w http.ResponseWriter, r *http.Request) {
	projID, err := strconv.Atoi(chi.URLParam(r, "projectID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}

	loc := model.Locale{}
	errs := decodeAndValidate(r.Body, &loc)
	if errs != nil {
		render.ErrorWithStatus(w, http.StatusBadRequest, errs)
		return
	}
	loc.ProjectID = projID

	proj, err := store.GetProject(projID)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	loc.SyncKeys(proj.Keys)

	err = store.CreateLocale(&loc)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	render.JSON(w, http.StatusCreated, loc)
}

func showLocale(w http.ResponseWriter, r *http.Request) {
	projectID, err := strconv.Atoi(chi.URLParam(r, "projectID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}
	id, err := strconv.Atoi(chi.URLParam(r, "localeID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}

	loc, err := store.GetProjectLocale(projectID, id)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	proj, err := store.GetProject(projectID)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	loc.SyncKeys(proj.Keys)

	render.JSON(w, http.StatusOK, loc)
}

func findLocales(w http.ResponseWriter, r *http.Request) {
	projectID, err := strconv.Atoi(chi.URLParam(r, "projectID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return

	}
	localeIdents := r.URL.Query()["locale"]

	locs, err := store.FindProjectLocales(projectID, localeIdents...)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	project, err := store.GetProject(projectID)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	for i := range locs {
		locs[i].SyncKeys(project.Keys)
	}

	render.JSON(w, http.StatusOK, locs)
}

func updateLocale(w http.ResponseWriter, r *http.Request) {
	// TODO: reduce number of db calls
	id, err := strconv.Atoi(chi.URLParam(r, "localeID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}
	projectID, err := strconv.Atoi(chi.URLParam(r, "projectID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}

	loc := &model.Locale{}
	if err := json.NewDecoder(r.Body).Decode(&loc); err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}
	loc.ID = id

	project, err := store.GetProject(projectID)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	loc.SyncKeys(project.Keys)

	err = store.UpdateLocale(loc)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	render.JSON(w, http.StatusOK, loc)
}

func deleteLocale(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(chi.URLParam(r, "localeID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}

	resultID, err := store.DeleteLocale(id)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	render.JSON(w, http.StatusOK, map[string]interface{}{
		"message": fmt.Sprintf("deleted locale with id %d", resultID),
	})
}
<commit_msg>Change query key<commit_after>package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/anthonynsimon/parrot/errors"
	"github.com/anthonynsimon/parrot/model"
	"github.com/anthonynsimon/parrot/render"
	"github.com/pressly/chi"
)

func createLocale(w http.ResponseWriter, r *http.Request) {
	projID, err := strconv.Atoi(chi.URLParam(r, "projectID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}

	loc := model.Locale{}
	errs := decodeAndValidate(r.Body, &loc)
	if errs != nil {
		render.ErrorWithStatus(w, http.StatusBadRequest, errs)
		return
	}
	loc.ProjectID = projID

	proj, err := store.GetProject(projID)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	loc.SyncKeys(proj.Keys)

	err = store.CreateLocale(&loc)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	render.JSON(w, http.StatusCreated, loc)
}

func showLocale(w http.ResponseWriter, r *http.Request) {
	projectID, err := strconv.Atoi(chi.URLParam(r, "projectID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}
	id, err := strconv.Atoi(chi.URLParam(r, "localeID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}

	loc, err := store.GetProjectLocale(projectID, id)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	proj, err := store.GetProject(projectID)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	loc.SyncKeys(proj.Keys)

	render.JSON(w, http.StatusOK, loc)
}

func findLocales(w http.ResponseWriter, r *http.Request) {
	projectID, err := strconv.Atoi(chi.URLParam(r, "projectID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return

	}
	localeIdents := r.URL.Query()["ident"]

	locs, err := store.FindProjectLocales(projectID, localeIdents...)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	project, err := store.GetProject(projectID)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	for i := range locs {
		locs[i].SyncKeys(project.Keys)
	}

	render.JSON(w, http.StatusOK, locs)
}

func updateLocale(w http.ResponseWriter, r *http.Request) {
	// TODO: reduce number of db calls
	id, err := strconv.Atoi(chi.URLParam(r, "localeID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}
	projectID, err := strconv.Atoi(chi.URLParam(r, "projectID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}

	loc := &model.Locale{}
	if err := json.NewDecoder(r.Body).Decode(&loc); err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}
	loc.ID = id

	project, err := store.GetProject(projectID)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	loc.SyncKeys(project.Keys)

	err = store.UpdateLocale(loc)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	render.JSON(w, http.StatusOK, loc)
}

func deleteLocale(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(chi.URLParam(r, "localeID"))
	if err != nil {
		render.Error(w, errors.ErrBadRequest)
		return
	}

	resultID, err := store.DeleteLocale(id)
	if err != nil {
		render.Error(w, errors.ErrInternal)
		return
	}

	render.JSON(w, http.StatusOK, map[string]interface{}{
		"message": fmt.Sprintf("deleted locale with id %d", resultID),
	})
}
<|endoftext|>


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

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

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

package api

import (
	"fmt"
	"net/http"
	"unicode"

	"github.com/vmware/harbor/dao"
	"github.com/vmware/harbor/models"
	"github.com/vmware/harbor/utils/log"

	"strconv"
	"time"
)

// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
type ProjectAPI struct {
	BaseAPI
	userID    int
	projectID int64
}

type projectReq struct {
	ProjectName string `json:"project_name"`
	Public      bool   `json:"public"`
}

const projectNameMaxLen int = 30
const projectNameMinLen int = 4

// Prepare validates the URL and the user
func (p *ProjectAPI) Prepare() {
	idStr := p.Ctx.Input.Param(":id")
	if len(idStr) > 0 {
		var err error
		p.projectID, err = strconv.ParseInt(idStr, 10, 64)
		if err != nil {
			log.Errorf("Error parsing project id: %s, error: %v", idStr, err)
			p.CustomAbort(http.StatusBadRequest, "invalid project id")
		}
		exist, err := dao.ProjectExists(p.projectID)
		if err != nil {
			log.Errorf("Error occurred in ProjectExists, error: %v", err)
			p.CustomAbort(http.StatusInternalServerError, "Internal error.")
		}
		if !exist {
			p.CustomAbort(http.StatusNotFound, fmt.Sprintf("project does not exist, id: %v", p.projectID))
		}
	}
}

// Post ...
func (p *ProjectAPI) Post() {
	p.userID = p.ValidateUser()

	var req projectReq
	var public int
	p.DecodeJSONReq(&req)
	if req.Public {
		public = 1
	}
	err := validateProjectReq(req)
	if err != nil {
		log.Errorf("Invalid project request, error: %v", err)
		p.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid request: %v", err))
		return
	}
	projectName := req.ProjectName
	exist, err := dao.ProjectExists(projectName)
	if err != nil {
		log.Errorf("Error happened checking project existence in db, error: %v, project name: %s", err, projectName)
	}
	if exist {
		p.RenderError(http.StatusConflict, "")
		return
	}
	project := models.Project{OwnerID: p.userID, Name: projectName, CreationTime: time.Now(), Public: public}
	projectID, err := dao.AddProject(project)
	if err != nil {
		log.Errorf("Failed to add project, error: %v", err)
		p.RenderError(http.StatusInternalServerError, "Failed to add project")
	}

	p.Redirect(http.StatusCreated, strconv.FormatInt(projectID, 10))
}

// Head ...
func (p *ProjectAPI) Head() {
	projectName := p.GetString("project_name")
	if len(projectName) == 0 {
		p.CustomAbort(http.StatusBadRequest, "project_name is needed")
	}

	project, err := dao.GetProjectByName(projectName)
	if err != nil {
		log.Errorf("error occurred in GetProjectByName: %v", err)
		p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	// only public project can be Headed by user without login
	if project != nil && project.Public == 1 {
		return
	}

	userID := p.ValidateUser()
	if project == nil {
		p.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	if !checkProjectPermission(userID, project.ProjectID) {
		p.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
	}
}

// Get ...
func (p *ProjectAPI) Get() {
	project, err := dao.GetProjectByID(p.projectID)
	if err != nil {
		log.Errorf("failed to get project %d: %v", p.projectID, err)
		p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if project.Public == 0 {
		userID := p.ValidateUser()
		if !checkProjectPermission(userID, p.projectID) {
			p.CustomAbort(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
		}
	}

	p.Data["json"] = project
	p.ServeJSON()
}

// List ...
func (p *ProjectAPI) List() {
	var projectList []models.Project
	projectName := p.GetString("project_name")
	if len(projectName) > 0 {
		projectName = "%" + projectName + "%"
	}
	var public int
	var err error
	isPublic := p.GetString("is_public")
	if len(isPublic) > 0 {
		public, err = strconv.Atoi(isPublic)
		if err != nil {
			log.Errorf("Error parsing public property: %d, error: %v", isPublic, err)
			p.CustomAbort(http.StatusBadRequest, "invalid project Id")
		}
	}
	isAdmin := false
	if public == 1 {
		projectList, err = dao.GetPublicProjects(projectName)
	} else {
		//if the request is not for public projects, user must login or provide credential
		p.userID = p.ValidateUser()
		isAdmin, err = dao.IsAdminRole(p.userID)
		if err != nil {
			log.Errorf("Error occured in check admin, error: %v", err)
			p.CustomAbort(http.StatusInternalServerError, "Internal error.")
		}
		if isAdmin {
			projectList, err = dao.GetAllProjects(projectName)
		} else {
			projectList, err = dao.GetUserRelevantProjects(p.userID, projectName)
		}
	}
	if err != nil {
		log.Errorf("Error occured in get projects info, error: %v", err)
		p.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}
	for i := 0; i < len(projectList); i++ {
		if public != 1 {
			if isAdmin {
				projectList[i].Role = models.PROJECTADMIN
			}
			if projectList[i].Role == models.PROJECTADMIN {
				projectList[i].Togglable = true
			}
		}
		projectList[i].RepoCount = getRepoCountByProject(projectList[i].Name)
	}
	p.Data["json"] = projectList
	p.ServeJSON()
}

// ToggleProjectPublic ...
func (p *ProjectAPI) ToggleProjectPublic() {
	p.userID = p.ValidateUser()
	var req projectReq
	var public int

	projectID, err := strconv.ParseInt(p.Ctx.Input.Param(":id"), 10, 64)
	if err != nil {
		log.Errorf("Error parsing project id: %d, error: %v", projectID, err)
		p.RenderError(http.StatusBadRequest, "invalid project id")
		return
	}

	p.DecodeJSONReq(&req)
	if req.Public {
		public = 1
	}
	if !isProjectAdmin(p.userID, projectID) {
		log.Warningf("Current user, id: %d does not have project admin role for project, id: %d", p.userID, projectID)
		p.RenderError(http.StatusForbidden, "")
		return
	}
	err = dao.ToggleProjectPublicity(p.projectID, public)
	if err != nil {
		log.Errorf("Error while updating project, project id: %d, error: %v", projectID, err)
		p.RenderError(http.StatusInternalServerError, "Failed to update project")
	}
}

// FilterAccessLog handles GET to /api/projects/{}/logs
func (p *ProjectAPI) FilterAccessLog() {
	p.userID = p.ValidateUser()

	var filter models.AccessLog
	p.DecodeJSONReq(&filter)

	username := filter.Username
	keywords := filter.Keywords

	beginTime := time.Unix(filter.BeginTimestamp, 0)
	endTime := time.Unix(filter.EndTimestamp, 0)

	query := models.AccessLog{ProjectID: p.projectID, Username: "%" + username + "%", Keywords: keywords, BeginTime: beginTime, BeginTimestamp: filter.BeginTimestamp, EndTime: endTime, EndTimestamp: filter.EndTimestamp}

	log.Infof("Query AccessLog: begin: %v, end: %v, keywords: %s", query.BeginTime, query.EndTime, query.Keywords)

	accessLogList, err := dao.GetAccessLogs(query)
	if err != nil {
		log.Errorf("Error occurred in GetAccessLogs, error: %v", err)
		p.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}
	p.Data["json"] = accessLogList

	p.ServeJSON()
}

func isProjectAdmin(userID int, pid int64) bool {
	isSysAdmin, err := dao.IsAdminRole(userID)
	if err != nil {
		log.Errorf("Error occurred in IsAdminRole, returning false, error: %v", err)
		return false
	}

	if isSysAdmin {
		return true
	}

	rolelist, err := dao.GetUserProjectRoles(userID, pid)
	if err != nil {
		log.Errorf("Error occurred in GetUserProjectRoles, returning false, error: %v", err)
		return false
	}

	hasProjectAdminRole := false
	for _, role := range rolelist {
		if role.RoleID == models.PROJECTADMIN {
			hasProjectAdminRole = true
			break
		}
	}

	return hasProjectAdminRole
}

func validateProjectReq(req projectReq) error {
	pn := req.ProjectName
	if len(pn) == 0 {
		return fmt.Errorf("Project name can not be empty")
	}
	if isIllegalLength(req.ProjectName, projectNameMinLen, projectNameMaxLen) {
		return fmt.Errorf("project name is illegal in length. (greater than 4 or less than 30)")
	}
	if isContainIllegalChar(req.ProjectName, []string{"~", "-", "$", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'", "/", "?", "@"}) {
		return fmt.Errorf("project name contains illegal characters")
	}
	for _, v := range pn {
		if !unicode.IsLower(v) {
			return fmt.Errorf("project name must be in lower case")
		}
	}
	return nil
}
<commit_msg>bug fix: modify the implements of checking whether project name is in lower case<commit_after>/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

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

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

package api

import (
	"fmt"
	"net/http"
	"strings"
	"unicode"

	"github.com/vmware/harbor/dao"
	"github.com/vmware/harbor/models"
	"github.com/vmware/harbor/utils/log"

	"strconv"
	"time"
)

// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
type ProjectAPI struct {
	BaseAPI
	userID    int
	projectID int64
}

type projectReq struct {
	ProjectName string `json:"project_name"`
	Public      bool   `json:"public"`
}

const projectNameMaxLen int = 30
const projectNameMinLen int = 4

// Prepare validates the URL and the user
func (p *ProjectAPI) Prepare() {
	idStr := p.Ctx.Input.Param(":id")
	if len(idStr) > 0 {
		var err error
		p.projectID, err = strconv.ParseInt(idStr, 10, 64)
		if err != nil {
			log.Errorf("Error parsing project id: %s, error: %v", idStr, err)
			p.CustomAbort(http.StatusBadRequest, "invalid project id")
		}
		exist, err := dao.ProjectExists(p.projectID)
		if err != nil {
			log.Errorf("Error occurred in ProjectExists, error: %v", err)
			p.CustomAbort(http.StatusInternalServerError, "Internal error.")
		}
		if !exist {
			p.CustomAbort(http.StatusNotFound, fmt.Sprintf("project does not exist, id: %v", p.projectID))
		}
	}
}

// Post ...
func (p *ProjectAPI) Post() {
	p.userID = p.ValidateUser()

	var req projectReq
	var public int
	p.DecodeJSONReq(&req)
	if req.Public {
		public = 1
	}
	err := validateProjectReq(req)
	if err != nil {
		log.Errorf("Invalid project request, error: %v", err)
		p.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid request: %v", err))
		return
	}
	projectName := req.ProjectName
	exist, err := dao.ProjectExists(projectName)
	if err != nil {
		log.Errorf("Error happened checking project existence in db, error: %v, project name: %s", err, projectName)
	}
	if exist {
		p.RenderError(http.StatusConflict, "")
		return
	}
	project := models.Project{OwnerID: p.userID, Name: projectName, CreationTime: time.Now(), Public: public}
	projectID, err := dao.AddProject(project)
	if err != nil {
		log.Errorf("Failed to add project, error: %v", err)
		p.RenderError(http.StatusInternalServerError, "Failed to add project")
	}

	p.Redirect(http.StatusCreated, strconv.FormatInt(projectID, 10))
}

// Head ...
func (p *ProjectAPI) Head() {
	projectName := p.GetString("project_name")
	if len(projectName) == 0 {
		p.CustomAbort(http.StatusBadRequest, "project_name is needed")
	}

	project, err := dao.GetProjectByName(projectName)
	if err != nil {
		log.Errorf("error occurred in GetProjectByName: %v", err)
		p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	// only public project can be Headed by user without login
	if project != nil && project.Public == 1 {
		return
	}

	userID := p.ValidateUser()
	if project == nil {
		p.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	if !checkProjectPermission(userID, project.ProjectID) {
		p.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
	}
}

// Get ...
func (p *ProjectAPI) Get() {
	project, err := dao.GetProjectByID(p.projectID)
	if err != nil {
		log.Errorf("failed to get project %d: %v", p.projectID, err)
		p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if project.Public == 0 {
		userID := p.ValidateUser()
		if !checkProjectPermission(userID, p.projectID) {
			p.CustomAbort(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
		}
	}

	p.Data["json"] = project
	p.ServeJSON()
}

// List ...
func (p *ProjectAPI) List() {
	var projectList []models.Project
	projectName := p.GetString("project_name")
	if len(projectName) > 0 {
		projectName = "%" + projectName + "%"
	}
	var public int
	var err error
	isPublic := p.GetString("is_public")
	if len(isPublic) > 0 {
		public, err = strconv.Atoi(isPublic)
		if err != nil {
			log.Errorf("Error parsing public property: %d, error: %v", isPublic, err)
			p.CustomAbort(http.StatusBadRequest, "invalid project Id")
		}
	}
	isAdmin := false
	if public == 1 {
		projectList, err = dao.GetPublicProjects(projectName)
	} else {
		//if the request is not for public projects, user must login or provide credential
		p.userID = p.ValidateUser()
		isAdmin, err = dao.IsAdminRole(p.userID)
		if err != nil {
			log.Errorf("Error occured in check admin, error: %v", err)
			p.CustomAbort(http.StatusInternalServerError, "Internal error.")
		}
		if isAdmin {
			projectList, err = dao.GetAllProjects(projectName)
		} else {
			projectList, err = dao.GetUserRelevantProjects(p.userID, projectName)
		}
	}
	if err != nil {
		log.Errorf("Error occured in get projects info, error: %v", err)
		p.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}
	for i := 0; i < len(projectList); i++ {
		if public != 1 {
			if isAdmin {
				projectList[i].Role = models.PROJECTADMIN
			}
			if projectList[i].Role == models.PROJECTADMIN {
				projectList[i].Togglable = true
			}
		}
		projectList[i].RepoCount = getRepoCountByProject(projectList[i].Name)
	}
	p.Data["json"] = projectList
	p.ServeJSON()
}

// ToggleProjectPublic ...
func (p *ProjectAPI) ToggleProjectPublic() {
	p.userID = p.ValidateUser()
	var req projectReq
	var public int

	projectID, err := strconv.ParseInt(p.Ctx.Input.Param(":id"), 10, 64)
	if err != nil {
		log.Errorf("Error parsing project id: %d, error: %v", projectID, err)
		p.RenderError(http.StatusBadRequest, "invalid project id")
		return
	}

	p.DecodeJSONReq(&req)
	if req.Public {
		public = 1
	}
	if !isProjectAdmin(p.userID, projectID) {
		log.Warningf("Current user, id: %d does not have project admin role for project, id: %d", p.userID, projectID)
		p.RenderError(http.StatusForbidden, "")
		return
	}
	err = dao.ToggleProjectPublicity(p.projectID, public)
	if err != nil {
		log.Errorf("Error while updating project, project id: %d, error: %v", projectID, err)
		p.RenderError(http.StatusInternalServerError, "Failed to update project")
	}
}

// FilterAccessLog handles GET to /api/projects/{}/logs
func (p *ProjectAPI) FilterAccessLog() {
	p.userID = p.ValidateUser()

	var filter models.AccessLog
	p.DecodeJSONReq(&filter)

	username := filter.Username
	keywords := filter.Keywords

	beginTime := time.Unix(filter.BeginTimestamp, 0)
	endTime := time.Unix(filter.EndTimestamp, 0)

	query := models.AccessLog{ProjectID: p.projectID, Username: "%" + username + "%", Keywords: keywords, BeginTime: beginTime, BeginTimestamp: filter.BeginTimestamp, EndTime: endTime, EndTimestamp: filter.EndTimestamp}

	log.Infof("Query AccessLog: begin: %v, end: %v, keywords: %s", query.BeginTime, query.EndTime, query.Keywords)

	accessLogList, err := dao.GetAccessLogs(query)
	if err != nil {
		log.Errorf("Error occurred in GetAccessLogs, error: %v", err)
		p.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}
	p.Data["json"] = accessLogList

	p.ServeJSON()
}

func isProjectAdmin(userID int, pid int64) bool {
	isSysAdmin, err := dao.IsAdminRole(userID)
	if err != nil {
		log.Errorf("Error occurred in IsAdminRole, returning false, error: %v", err)
		return false
	}

	if isSysAdmin {
		return true
	}

	rolelist, err := dao.GetUserProjectRoles(userID, pid)
	if err != nil {
		log.Errorf("Error occurred in GetUserProjectRoles, returning false, error: %v", err)
		return false
	}

	hasProjectAdminRole := false
	for _, role := range rolelist {
		if role.RoleID == models.PROJECTADMIN {
			hasProjectAdminRole = true
			break
		}
	}

	return hasProjectAdminRole
}

func validateProjectReq(req projectReq) error {
	pn := req.ProjectName
	if len(pn) == 0 {
		return fmt.Errorf("Project name can not be empty")
	}
	if isIllegalLength(req.ProjectName, projectNameMinLen, projectNameMaxLen) {
		return fmt.Errorf("project name is illegal in length. (greater than 4 or less than 30)")
	}
	if isContainIllegalChar(req.ProjectName, []string{"~", "-", "$", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'", "/", "?", "@"}) {
		return fmt.Errorf("project name contains illegal characters")
	}

	if pn != strings.ToLower(pn) {
		return fmt.Errorf("project name must be in lower case")
	}

	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"errors"
	"math/rand"
	"sync"
	"time"
)

var (
	ErrRenewerMissingInput  = errors.New("missing input to renewer")
	ErrRenewerMissingSecret = errors.New("missing secret to renew")
	ErrRenewerNotRenewable  = errors.New("secret is not renewable")
	ErrRenewerNoSecretData  = errors.New("returned empty secret data")

	// DefaultRenewerGrace is the default grace period
	DefaultRenewerGrace = 15 * time.Second
)

// Renewer is a process for renewing a secret.
//
// 	renewer, err := client.NewRenewer(&RenewerInput{
// 		Secret: mySecret,
// 	})
// 	go renewer.Renew()
// 	defer renewer.Stop()
//
// 	for {
// 		select {
// 		case err := <-DoneCh():
// 			if err != nil {
// 				log.Fatal(err)
// 			}
//
// 			// Renewal is now over
// 		case renewal := <-RenewCh():
// 			log.Printf("Successfully renewed: %#v", renewal)
// 		default:
// 		}
// 	}
//
//
// The `DoneCh` will return if renewal fails or if the remaining lease duration
// after a renewal is less than or equal to the grace (in number of seconds). In
// both cases, the caller should attempt a re-read of the secret. Clients should
// check the return value of the channel to see if renewal was successful.
type Renewer struct {
	l sync.Mutex

	client  *Client
	secret  *Secret
	grace   time.Duration
	random  *rand.Rand
	doneCh  chan error
	renewCh chan *RenewOutput

	stopped bool
	stopCh  chan struct{}
}

// RenewerInput is used as input to the renew function.
type RenewerInput struct {
	// Secret is the secret to renew
	Secret *Secret

	// Grace is a minimum renewal (in seconds) before returring so the upstream
	// client can do a re-read. This can be used to prevent clients from waiting
	// too long to read a new credential and incur downtime.
	Grace time.Duration

	// Rand is the randomizer to use for underlying randomization. If not
	// provided, one will be generated and seeded automatically. If provided, it
	// is assumed to have already been seeded.
	Rand *rand.Rand
}

// RenewOutput is the metadata returned to the client (if it's listening) to
// renew messages.
type RenewOutput struct {
	// RenewedAt is the timestamp when the renewal took place (UTC).
	RenewedAt time.Time

	// Secret is the underlying renewal data. It's the same struct as all data
	// that is returned from Vault, but since this is renewal data, it will not
	// usually include the secret itself.
	Secret *Secret
}

// NewRenewer creates a new renewer from the given input.
func (c *Client) NewRenewer(i *RenewerInput) (*Renewer, error) {
	if i == nil {
		return nil, ErrRenewerMissingInput
	}

	secret := i.Secret
	if secret == nil {
		return nil, ErrRenewerMissingSecret
	}

	grace := i.Grace
	if grace == 0 {
		grace = DefaultRenewerGrace
	}

	random := i.Rand
	if random == nil {
		random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
	}

	return &Renewer{
		client:  c,
		secret:  secret,
		grace:   grace,
		random:  random,
		doneCh:  make(chan error),
		renewCh: make(chan *RenewOutput, 5),

		stopped: false,
		stopCh:  make(chan struct{}),
	}, nil
}

// DoneCh returns the channel where the renewer will publish when renewal stops.
// If there is an error, this will be an error.
func (r *Renewer) DoneCh() <-chan error {
	return r.doneCh
}

// RenewCh is a channel that receives a message when a successful renewal takes
// place and includes metadata about the renewal.
func (r *Renewer) RenewCh() <-chan *RenewOutput {
	return r.renewCh
}

// Stop stops the renewer.
func (r *Renewer) Stop() {
	r.l.Lock()
	if !r.stopped {
		close(r.stopCh)
		r.stopped = true
	}
	r.l.Unlock()
}

// Renew starts a background process for renewing this secret. When the secret
// is has auth data, this attempts to renew the auth (token). When the secret
// has a lease, this attempts to renew the lease.
//
// This function will not return if nothing is reading from doneCh (it blocks)
// on a write to the channel.
func (r *Renewer) Renew() {
	if r.secret.Auth != nil {
		r.doneCh <- r.renewAuth()
	} else {
		r.doneCh <- r.renewLease()
	}
}

// renewAuth is a helper for renewing authentication.
func (r *Renewer) renewAuth() error {
	if !r.secret.Auth.Renewable || r.secret.Auth.ClientToken == "" {
		return ErrRenewerNotRenewable
	}

	client, token := r.client, r.secret.Auth.ClientToken

	for {
		// Check if we are stopped.
		select {
		case <-r.stopCh:
			return nil
		default:
		}

		// Renew the auth.
		renewal, err := client.Auth().Token().RenewTokenAsSelf(token, 0)
		if err != nil {
			return err
		}

		// Push a message that a renewal took place.
		select {
		case r.renewCh <- &RenewOutput{time.Now().UTC(), renewal}:
		default:
		}

		// Somehow, sometimes, this happens.
		if renewal == nil || renewal.Auth == nil {
			return ErrRenewerNoSecretData
		}

		// Do nothing if we are not renewable
		if !renewal.Auth.Renewable {
			return ErrRenewerNotRenewable
		}

		// Grab the lease duration and sleep duration - note that we grab the auth
		// lease duration, not the secret lease duration.
		leaseDuration := time.Duration(renewal.Auth.LeaseDuration) * time.Second
		sleepDuration := r.sleepDuration(leaseDuration)

		// If we are within grace, return now.
		if leaseDuration <= r.grace || sleepDuration <= r.grace {
			return nil
		}

		select {
		case <-r.stopCh:
			return nil
		case <-time.After(sleepDuration):
			continue
		}
	}
}

// renewLease is a helper for renewing a lease.
func (r *Renewer) renewLease() error {
	if !r.secret.Renewable || r.secret.LeaseID == "" {
		return ErrRenewerNotRenewable
	}

	client, leaseID := r.client, r.secret.LeaseID

	for {
		// Check if we are stopped.
		select {
		case <-r.stopCh:
			return nil
		default:
		}

		// Renew the lease.
		renewal, err := client.Sys().Renew(leaseID, 0)
		if err != nil {
			return err
		}

		// Push a message that a renewal took place.
		select {
		case r.renewCh <- &RenewOutput{time.Now().UTC(), renewal}:
		default:
		}

		// Somehow, sometimes, this happens.
		if renewal == nil {
			return ErrRenewerNoSecretData
		}

		// Do nothing if we are not renewable
		if !renewal.Renewable {
			return ErrRenewerNotRenewable
		}

		// Grab the lease duration and sleep duration
		leaseDuration := time.Duration(renewal.LeaseDuration) * time.Second
		sleepDuration := r.sleepDuration(leaseDuration)

		// If we are within grace, return now.
		if leaseDuration <= r.grace || sleepDuration <= r.grace {
			return nil
		}

		select {
		case <-r.stopCh:
			return nil
		case <-time.After(sleepDuration):
			continue
		}
	}
}

// sleepDuration calculates the time to sleep given the base lease duration. The
// base is the resulting lease duration. It will be reduced to 1/3 and
// multiplied by a random float between 0.0 and 1.0. This extra randomness
// prevents multiple clients from all trying to renew simultaneously.
func (r *Renewer) sleepDuration(base time.Duration) time.Duration {
	sleep := float64(base)

	// Renew at 1/3 the remaining lease. This will give us an opportunity to retry
	// at least one more time should the first renewal fail.
	sleep = sleep / 3.0

	// Use a randomness so many clients do not hit Vault simultaneously.
	sleep = sleep * r.random.Float64()

	return time.Duration(sleep) * time.Second
}
<commit_msg>Do not block writing to doneCh if stopped<commit_after>package api

import (
	"errors"
	"math/rand"
	"sync"
	"time"
)

var (
	ErrRenewerMissingInput  = errors.New("missing input to renewer")
	ErrRenewerMissingSecret = errors.New("missing secret to renew")
	ErrRenewerNotRenewable  = errors.New("secret is not renewable")
	ErrRenewerNoSecretData  = errors.New("returned empty secret data")

	// DefaultRenewerGrace is the default grace period
	DefaultRenewerGrace = 15 * time.Second
)

// Renewer is a process for renewing a secret.
//
// 	renewer, err := client.NewRenewer(&RenewerInput{
// 		Secret: mySecret,
// 	})
// 	go renewer.Renew()
// 	defer renewer.Stop()
//
// 	for {
// 		select {
// 		case err := <-DoneCh():
// 			if err != nil {
// 				log.Fatal(err)
// 			}
//
// 			// Renewal is now over
// 		case renewal := <-RenewCh():
// 			log.Printf("Successfully renewed: %#v", renewal)
// 		default:
// 		}
// 	}
//
//
// The `DoneCh` will return if renewal fails or if the remaining lease duration
// after a renewal is less than or equal to the grace (in number of seconds). In
// both cases, the caller should attempt a re-read of the secret. Clients should
// check the return value of the channel to see if renewal was successful.
type Renewer struct {
	l sync.Mutex

	client  *Client
	secret  *Secret
	grace   time.Duration
	random  *rand.Rand
	doneCh  chan error
	renewCh chan *RenewOutput

	stopped bool
	stopCh  chan struct{}
}

// RenewerInput is used as input to the renew function.
type RenewerInput struct {
	// Secret is the secret to renew
	Secret *Secret

	// Grace is a minimum renewal (in seconds) before returring so the upstream
	// client can do a re-read. This can be used to prevent clients from waiting
	// too long to read a new credential and incur downtime.
	Grace time.Duration

	// Rand is the randomizer to use for underlying randomization. If not
	// provided, one will be generated and seeded automatically. If provided, it
	// is assumed to have already been seeded.
	Rand *rand.Rand
}

// RenewOutput is the metadata returned to the client (if it's listening) to
// renew messages.
type RenewOutput struct {
	// RenewedAt is the timestamp when the renewal took place (UTC).
	RenewedAt time.Time

	// Secret is the underlying renewal data. It's the same struct as all data
	// that is returned from Vault, but since this is renewal data, it will not
	// usually include the secret itself.
	Secret *Secret
}

// NewRenewer creates a new renewer from the given input.
func (c *Client) NewRenewer(i *RenewerInput) (*Renewer, error) {
	if i == nil {
		return nil, ErrRenewerMissingInput
	}

	secret := i.Secret
	if secret == nil {
		return nil, ErrRenewerMissingSecret
	}

	grace := i.Grace
	if grace == 0 {
		grace = DefaultRenewerGrace
	}

	random := i.Rand
	if random == nil {
		random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
	}

	return &Renewer{
		client:  c,
		secret:  secret,
		grace:   grace,
		random:  random,
		doneCh:  make(chan error),
		renewCh: make(chan *RenewOutput, 5),

		stopped: false,
		stopCh:  make(chan struct{}),
	}, nil
}

// DoneCh returns the channel where the renewer will publish when renewal stops.
// If there is an error, this will be an error.
func (r *Renewer) DoneCh() <-chan error {
	return r.doneCh
}

// RenewCh is a channel that receives a message when a successful renewal takes
// place and includes metadata about the renewal.
func (r *Renewer) RenewCh() <-chan *RenewOutput {
	return r.renewCh
}

// Stop stops the renewer.
func (r *Renewer) Stop() {
	r.l.Lock()
	if !r.stopped {
		close(r.stopCh)
		r.stopped = true
	}
	r.l.Unlock()
}

// Renew starts a background process for renewing this secret. When the secret
// is has auth data, this attempts to renew the auth (token). When the secret
// has a lease, this attempts to renew the lease.
//
// This function will not return if nothing is reading from doneCh (it blocks)
// on a write to the channel.
func (r *Renewer) Renew() {
	var result error
	if r.secret.Auth != nil {
		result = r.renewAuth()
	} else {
		result = r.renewLease()
	}

	select {
	case r.doneCh <- result:
	case <-r.stopCh:
	}
}

// renewAuth is a helper for renewing authentication.
func (r *Renewer) renewAuth() error {
	if !r.secret.Auth.Renewable || r.secret.Auth.ClientToken == "" {
		return ErrRenewerNotRenewable
	}

	client, token := r.client, r.secret.Auth.ClientToken

	for {
		// Check if we are stopped.
		select {
		case <-r.stopCh:
			return nil
		default:
		}

		// Renew the auth.
		renewal, err := client.Auth().Token().RenewTokenAsSelf(token, 0)
		if err != nil {
			return err
		}

		// Push a message that a renewal took place.
		select {
		case r.renewCh <- &RenewOutput{time.Now().UTC(), renewal}:
		default:
		}

		// Somehow, sometimes, this happens.
		if renewal == nil || renewal.Auth == nil {
			return ErrRenewerNoSecretData
		}

		// Do nothing if we are not renewable
		if !renewal.Auth.Renewable {
			return ErrRenewerNotRenewable
		}

		// Grab the lease duration and sleep duration - note that we grab the auth
		// lease duration, not the secret lease duration.
		leaseDuration := time.Duration(renewal.Auth.LeaseDuration) * time.Second
		sleepDuration := r.sleepDuration(leaseDuration)

		// If we are within grace, return now.
		if leaseDuration <= r.grace || sleepDuration <= r.grace {
			return nil
		}

		select {
		case <-r.stopCh:
			return nil
		case <-time.After(sleepDuration):
			continue
		}
	}
}

// renewLease is a helper for renewing a lease.
func (r *Renewer) renewLease() error {
	if !r.secret.Renewable || r.secret.LeaseID == "" {
		return ErrRenewerNotRenewable
	}

	client, leaseID := r.client, r.secret.LeaseID

	for {
		// Check if we are stopped.
		select {
		case <-r.stopCh:
			return nil
		default:
		}

		// Renew the lease.
		renewal, err := client.Sys().Renew(leaseID, 0)
		if err != nil {
			return err
		}

		// Push a message that a renewal took place.
		select {
		case r.renewCh <- &RenewOutput{time.Now().UTC(), renewal}:
		default:
		}

		// Somehow, sometimes, this happens.
		if renewal == nil {
			return ErrRenewerNoSecretData
		}

		// Do nothing if we are not renewable
		if !renewal.Renewable {
			return ErrRenewerNotRenewable
		}

		// Grab the lease duration and sleep duration
		leaseDuration := time.Duration(renewal.LeaseDuration) * time.Second
		sleepDuration := r.sleepDuration(leaseDuration)

		// If we are within grace, return now.
		if leaseDuration <= r.grace || sleepDuration <= r.grace {
			return nil
		}

		select {
		case <-r.stopCh:
			return nil
		case <-time.After(sleepDuration):
			continue
		}
	}
}

// sleepDuration calculates the time to sleep given the base lease duration. The
// base is the resulting lease duration. It will be reduced to 1/3 and
// multiplied by a random float between 0.0 and 1.0. This extra randomness
// prevents multiple clients from all trying to renew simultaneously.
func (r *Renewer) sleepDuration(base time.Duration) time.Duration {
	sleep := float64(base)

	// Renew at 1/3 the remaining lease. This will give us an opportunity to retry
	// at least one more time should the first renewal fail.
	sleep = sleep / 3.0

	// Use a randomness so many clients do not hit Vault simultaneously.
	sleep = sleep * r.random.Float64()

	return time.Duration(sleep) * time.Second
}
<|endoftext|>


<commit_before>/*
 Copyright 2016 Padduck, LLC

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

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

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

package main

import (
	"flag"
	"github.com/braintree/manners"
	"github.com/gin-gonic/gin"
	"github.com/pufferpanel/pufferd/config"
	"github.com/pufferpanel/pufferd/data/templates"
	"github.com/pufferpanel/pufferd/logging"
	"github.com/pufferpanel/pufferd/programs"
	"github.com/pufferpanel/pufferd/routing"
	"github.com/pufferpanel/pufferd/routing/server"
	"github.com/pufferpanel/pufferd/sftp"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
)

func main() {
	var loggingLevel string
	var port int
	flag.StringVar(&loggingLevel, "logging", "INFO", "Lowest logging level to display")
	flag.IntVar(&port, "port", 5656, "Port to run service on")
	flag.Parse()

	logging.SetLevelByString(loggingLevel)
	gin.SetMode(gin.ReleaseMode)

	logging.Info("Logging set to " + loggingLevel)

	config.Load()

	if _, err := os.Stat(templates.Folder); os.IsNotExist(err) {
		logging.Info("No template directory found, creating")
		err = os.MkdirAll(templates.Folder, 755)
		if err != nil {
			logging.Error("Error creating template folder", err)
		}

	}
	if files, _ := ioutil.ReadDir(templates.Folder); len(files) == 0 {
		logging.Info("Templates being copied to " + templates.Folder)
		templates.CopyTemplates()
	}

	if _, err := os.Stat(programs.ServerFolder); os.IsNotExist(err) {
		logging.Info("No server directory found, creating")
		os.MkdirAll(programs.ServerFolder, 755)
	}

	programs.LoadFromFolder()

	for _, element := range programs.GetAll() {
		if element.IsEnabled() {
			logging.Info("Starting server " + element.Id())
			element.Start()
			err := programs.Save(element.Id())
			if err != nil {
				logging.Error("Error saving server file", err)
			}
		}
	}

	r := gin.New()
	{
		r.Use(gin.Recovery())
		routing.RegisterRoutes(r)
		legacy.RegisterRoutes(r)
		server.RegisterRoutes(r)
	}

	var useHttps bool
	useHttps = false

	if _, err := os.Stat(filepath.Join("data", "https.pem")); os.IsNotExist(err) {
		logging.Warn("No HTTPS.PEM found in data folder, will use no http")
	} else if _, err := os.Stat(filepath.Join("data", "https.key")); os.IsNotExist(err) {
		logging.Warn("No HTTPS.KEY found in data folder, will use no http")
	} else {
		useHttps = true
	}

	sftp.Run()

	logging.Info("Starting web access on 0.0.0.0:" + strconv.Itoa(port))
	if useHttps {
		manners.ListenAndServeTLS(":"+strconv.FormatInt(int64(port), 10), filepath.Join("data", "https.pem"), filepath.Join("data", "https.key"), r)
	} else {
		manners.ListenAndServe(":"+strconv.FormatInt(int64(port), 10), r)
	}
}
<commit_msg>Remove legacy route registration<commit_after>/*
 Copyright 2016 Padduck, LLC

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

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

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

package main

import (
	"flag"
	"github.com/braintree/manners"
	"github.com/gin-gonic/gin"
	"github.com/pufferpanel/pufferd/config"
	"github.com/pufferpanel/pufferd/data/templates"
	"github.com/pufferpanel/pufferd/logging"
	"github.com/pufferpanel/pufferd/programs"
	"github.com/pufferpanel/pufferd/routing"
	"github.com/pufferpanel/pufferd/routing/server"
	"github.com/pufferpanel/pufferd/sftp"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
)

func main() {
	var loggingLevel string
	var port int
	flag.StringVar(&loggingLevel, "logging", "INFO", "Lowest logging level to display")
	flag.IntVar(&port, "port", 5656, "Port to run service on")
	flag.Parse()

	logging.SetLevelByString(loggingLevel)
	gin.SetMode(gin.ReleaseMode)

	logging.Info("Logging set to " + loggingLevel)

	config.Load()

	if _, err := os.Stat(templates.Folder); os.IsNotExist(err) {
		logging.Info("No template directory found, creating")
		err = os.MkdirAll(templates.Folder, 755)
		if err != nil {
			logging.Error("Error creating template folder", err)
		}

	}
	if files, _ := ioutil.ReadDir(templates.Folder); len(files) == 0 {
		logging.Info("Templates being copied to " + templates.Folder)
		templates.CopyTemplates()
	}

	if _, err := os.Stat(programs.ServerFolder); os.IsNotExist(err) {
		logging.Info("No server directory found, creating")
		os.MkdirAll(programs.ServerFolder, 755)
	}

	programs.LoadFromFolder()

	for _, element := range programs.GetAll() {
		if element.IsEnabled() {
			logging.Info("Starting server " + element.Id())
			element.Start()
			err := programs.Save(element.Id())
			if err != nil {
				logging.Error("Error saving server file", err)
			}
		}
	}

	r := gin.New()
	{
		r.Use(gin.Recovery())
		routing.RegisterRoutes(r)
		server.RegisterRoutes(r)
	}

	var useHttps bool
	useHttps = false

	if _, err := os.Stat(filepath.Join("data", "https.pem")); os.IsNotExist(err) {
		logging.Warn("No HTTPS.PEM found in data folder, will use no http")
	} else if _, err := os.Stat(filepath.Join("data", "https.key")); os.IsNotExist(err) {
		logging.Warn("No HTTPS.KEY found in data folder, will use no http")
	} else {
		useHttps = true
	}

	sftp.Run()

	logging.Info("Starting web access on 0.0.0.0:" + strconv.Itoa(port))
	if useHttps {
		manners.ListenAndServeTLS(":"+strconv.FormatInt(int64(port), 10), filepath.Join("data", "https.pem"), filepath.Join("data", "https.key"), r)
	} else {
		manners.ListenAndServe(":"+strconv.FormatInt(int64(port), 10), r)
	}
}
<|endoftext|>


<commit_before>package q

import "github.com/leeola/fixity/q/operators"

type Constraint struct {
	// Operator
	Operator    string
	Field       string
	Value       interface{}
	Constraints []Constraint
}

type Sort struct {
	Field      string
	Descending bool
}

// Query is a generic method to construct a query in the index implementation.
//
// Eg, if mysql is the index, a Query would be constructed into a SQL string.
// Furthermore, the generic constraints allow for any type of keywords/etc
// to be used by the underlying indexer implementation. FullTextSearch for
// example is a more niche feature, and not supported by many indexers.
type Query struct {
	SortBy     []Sort
	SkipBy     int
	LimitBy    int
	Constraint Constraint
}

func New() *Query {
	return &Query{
		// set a default limit
		LimitBy: 10,
	}
}

func (q *Query) Limit(l int) *Query {
	q.LimitBy = l
	return q
}

func (q *Query) Skip(s int) *Query {
	q.SkipBy = s
	return q
}

func (q *Query) Sort(field string, descending bool) *Query {
	q.SortBy = append(q.SortBy, Sort{
		Field:      field,
		Descending: descending,
	})
	return q
}

func (q *Query) And(c ...Constraint) *Query {
	q.Const(And(c...))
	return q
}

func (q *Query) Const(c Constraint) *Query {
	q.Constraint = c
	return q
}

func (q *Query) Or(c ...Constraint) *Query {
	q.Const(Or(c...))
	return q
}

// And requires that all given constraints are succeed.
//
// Note that if a single constraint is used, no And constraint is used.
// This is mainly to reduce needless abstractions, albeit not intuitively.
func And(c ...Constraint) Constraint {
	if len(c) == 1 {
		return c[0]
	}

	return Constraint{
		Operator:    operators.And,
		Constraints: c,
	}
}

func Eq(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.Equal,
		Field:    field,
		Value:    value,
	}
}

func Fts(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.FullTextSearch,
		Field:    field,
		Value:    value,
	}
}

func Gt(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.GreaterThan,
		Field:    field,
		Value:    value,
	}
}

func Gte(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.GreaterThanOrEqual,
		Field:    field,
		Value:    value,
	}
}

// In checks if the given value is found within a list of values.
//
// Details are up to the implementor, but usually a equality check is done
// on the values within the list.
func In(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.In,
		Field:    field,
		Value:    value,
	}
}

func Lt(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.GreaterThan,
		Field:    field,
		Value:    value,
	}
}

func Lte(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.GreaterThan,
		Field:    field,
		Value:    value,
	}
}

func Not(c ...Constraint) Constraint {
	return Constraint{
		Operator:    operators.Not,
		Constraints: c,
	}
}

// Or requires that any of the given constraints succeed.
//
// Note that if a single constraint is used, no Or constraint is used.
// This is mainly to reduce needless abstractions, albeit not intuitively.
func Or(c ...Constraint) Constraint {
	if len(c) == 1 {
		return c[0]
	}

	return Constraint{
		Operator:    operators.Or,
		Constraints: c,
	}
}

func Re(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.Regex,
		Field:    field,
		Value:    value,
	}
}
<commit_msg>feat: added Constraints type to easily compose a Constraints slice<commit_after>package q

import "github.com/leeola/fixity/q/operators"

type Constraint struct {
	// Operator
	Operator    string
	Field       string
	Value       interface{}
	Constraints []Constraint
}

type Constraints []Constraint

type Sort struct {
	Field      string
	Descending bool
}

// Query is a generic method to construct a query in the index implementation.
//
// Eg, if mysql is the index, a Query would be constructed into a SQL string.
// Furthermore, the generic constraints allow for any type of keywords/etc
// to be used by the underlying indexer implementation. FullTextSearch for
// example is a more niche feature, and not supported by many indexers.
type Query struct {
	SortBy     []Sort
	SkipBy     int
	LimitBy    int
	Constraint Constraint
}

func New() *Query {
	return &Query{
		// set a default limit
		LimitBy: 10,
	}
}

func (q *Query) Limit(l int) *Query {
	q.LimitBy = l
	return q
}

func (q *Query) Skip(s int) *Query {
	q.SkipBy = s
	return q
}

func (q *Query) Sort(field string, descending bool) *Query {
	q.SortBy = append(q.SortBy, Sort{
		Field:      field,
		Descending: descending,
	})
	return q
}

func (q *Query) And(c ...Constraint) *Query {
	q.Const(And(c...))
	return q
}

func (q *Query) Const(c Constraint) *Query {
	q.Constraint = c
	return q
}

func (q *Query) Or(c ...Constraint) *Query {
	q.Const(Or(c...))
	return q
}

// And requires that all given constraints are succeed.
//
// Note that if a single constraint is used, no And constraint is used.
// This is mainly to reduce needless abstractions, albeit not intuitively.
func And(c ...Constraint) Constraint {
	if len(c) == 1 {
		return c[0]
	}

	return Constraint{
		Operator:    operators.And,
		Constraints: c,
	}
}

func Eq(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.Equal,
		Field:    field,
		Value:    value,
	}
}

func Fts(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.FullTextSearch,
		Field:    field,
		Value:    value,
	}
}

func Gt(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.GreaterThan,
		Field:    field,
		Value:    value,
	}
}

func Gte(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.GreaterThanOrEqual,
		Field:    field,
		Value:    value,
	}
}

// In checks if the given value is found within a list of values.
//
// Details are up to the implementor, but usually a equality check is done
// on the values within the list.
func In(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.In,
		Field:    field,
		Value:    value,
	}
}

func Lt(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.GreaterThan,
		Field:    field,
		Value:    value,
	}
}

func Lte(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.GreaterThan,
		Field:    field,
		Value:    value,
	}
}

func Not(c ...Constraint) Constraint {
	return Constraint{
		Operator:    operators.Not,
		Constraints: c,
	}
}

// Or requires that any of the given constraints succeed.
//
// Note that if a single constraint is used, no Or constraint is used.
// This is mainly to reduce needless abstractions, albeit not intuitively.
func Or(c ...Constraint) Constraint {
	if len(c) == 1 {
		return c[0]
	}

	return Constraint{
		Operator:    operators.Or,
		Constraints: c,
	}
}

func Re(field string, value interface{}) Constraint {
	return Constraint{
		Operator: operators.Regex,
		Field:    field,
		Value:    value,
	}
}

// Add the given constraint to this slice.
func (cs *Constraints) Add(c Constraint) {
	*cs = append(*cs, c)
}

// Eq creates and adds an Eq constraint to this constraint slice.
func (cs *Constraints) Eq(field string, value interface{}) {
	cs.Add(Eq(field, value))
}

// Fts creates and adds an Fts constraint to this constraint slice.
func (cs *Constraints) Fts(field string, value interface{}) {
	cs.Add(Fts(field, value))
}

// In creates and adds an In constraint to this constraint slice.
func (cs *Constraints) In(field string, value interface{}) {
	cs.Add(In(field, value))
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package azure

import (
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/state"
)

type azureEnvironProvider struct{}

// azureEnvironProvider implements EnvironProvider.
var _ environs.EnvironProvider = (*azureEnvironProvider)(nil)

// Open is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	panic("unimplemented")
}

// Validate is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
	panic("unimplemented")
}

// BoilerplateConfig is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) BoilerplateConfig() string {
	panic("unimplemented")
}

// SecretAttrs is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) SecretAttrs(cfg *config.Config) (map[string]interface{}, error) {
	panic("unimplemented")
}

// PublicAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PublicAddress() (string, error) {
	panic("unimplemented")
}

// PrivateAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PrivateAddress() (string, error) {
	panic("unimplemented")
}

// InstanceId is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) InstanceId() (state.InstanceId, error) {
	panic("unimplemented")
}
<commit_msg>Boilerplate config and Validate().<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package azure

import (
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/state"
)

type azureEnvironProvider struct{}

// azureEnvironProvider implements EnvironProvider.
var _ environs.EnvironProvider = (*azureEnvironProvider)(nil)

// Open is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	panic("unimplemented")
}

// PublicAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PublicAddress() (string, error) {
	panic("unimplemented")
}

// PrivateAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PrivateAddress() (string, error) {
	panic("unimplemented")
}

// InstanceId is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) InstanceId() (state.InstanceId, error) {
	panic("unimplemented")
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package azure

import (
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/log"
)

type azureEnvironProvider struct{}

// azureEnvironProvider implements EnvironProvider.
var _ environs.EnvironProvider = (*azureEnvironProvider)(nil)

// Open is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	name := cfg.Name()
	log.Debugf("environs/azure: opening environment %q.", name)
	env := azureEnviron{name: name}
	return &env, env.SetConfig(cfg)
}

// PublicAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PublicAddress() (string, error) {
	panic("unimplemented")
}

// PrivateAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PrivateAddress() (string, error) {
	panic("unimplemented")
}

// InstanceId is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) InstanceId() (instance.Id, error) {
	panic("unimplemented")
}
<commit_msg>Use loggo instead of log for the Azure provider, as requested.<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package azure

import (
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/instance"
	"launchpad.net/loggo"
)

// Logger for the Azure provider.
var logger = loggo.GetLogger("juju.environs.azure")

type azureEnvironProvider struct{}

// azureEnvironProvider implements EnvironProvider.
var _ environs.EnvironProvider = (*azureEnvironProvider)(nil)

// Open is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	name := cfg.Name()
	logger.Debugf("opening environment %q.", name)
	env := azureEnviron{name: name}
	return &env, env.SetConfig(cfg)
}

// PublicAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PublicAddress() (string, error) {
	panic("unimplemented")
}

// PrivateAddress is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) PrivateAddress() (string, error) {
	panic("unimplemented")
}

// InstanceId is specified in the EnvironProvider interface.
func (prov azureEnvironProvider) InstanceId() (instance.Id, error) {
	panic("unimplemented")
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package local

import (
	"fmt"
	"path/filepath"

	"launchpad.net/loggo"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/utils"
)

var logger = loggo.GetLogger("juju.environs.local")

var _ environs.EnvironProvider = (*environProvider)(nil)

type environProvider struct{}

var provider environProvider

func init() {
	environs.RegisterProvider("local", &environProvider{})
}

var (
	defaultRootDir = "/var/lib/juju/"
)

// Open implements environs.EnvironProvider.Open.
func (environProvider) Open(cfg *config.Config) (environs.Environ, error) {
	logger.Infof("opening environment %q", cfg.Name())
	environ := &localEnviron{name: cfg.Name()}
	err := environ.SetConfig(cfg)
	if err != nil {
		logger.Errorf("failure setting config: %v", err)
		return nil, err
	}
	return environ, nil
}

// Validate implements environs.EnvironProvider.Validate.
func (provider environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
	// Check for valid changes for the base config values.
	if err := config.Validate(cfg, old); err != nil {
		return nil, err
	}
	v, err := configChecker.Coerce(cfg.UnknownAttrs(), nil)
	if err != nil {
		return nil, err
	}
	localConfig := newEnvironConfig(cfg, v.(map[string]interface{}))
	// Before potentially creating directories, make sure that the
	// root directory has not changed.
	if old != nil {
		oldLocalConfig, err := provider.newConfig(old)
		if err != nil {
			return nil, fmt.Errorf("old config is not a valid local config: %v", old)
		}
		if localConfig.rootDir() != oldLocalConfig.rootDir() {
			return nil, fmt.Errorf("cannot change root-dir from %q to %q",
				oldLocalConfig.rootDir(),
				localConfig.rootDir())
		}
	}
	dir := utils.NormalizePath(localConfig.rootDir())
	if dir == "." {
		dir = filepath.Join(defaultRootDir, localConfig.namespace())
		localConfig.attrs["root-dir"] = dir
	}

	// Apply the coerced unknown values back into the config.
	return cfg.Apply(localConfig.attrs)
}

// BoilerplateConfig implements environs.EnvironProvider.BoilerplateConfig.
func (environProvider) BoilerplateConfig() string {
	return `
## https://juju.ubuntu.com/get-started/local/
local:
  type: local
  # Override the directory that is used for the storage files and database.
  # The default location is /var/lib/juju/<USER>-<ENV>
  # root-dir: ~/.juju/local

`[1:]
}

// SecretAttrs implements environs.EnvironProvider.SecretAttrs.
func (environProvider) SecretAttrs(cfg *config.Config) (map[string]interface{}, error) {
	// don't have any secret attrs
	return nil, nil
}

// Location specific methods that are able to be called by any instance that
// has been created by this provider type.  So a machine agent may well call
// these methods to find out its own address or instance id.

// PublicAddress implements environs.EnvironProvider.PublicAddress.
func (environProvider) PublicAddress() (string, error) {
	return "", fmt.Errorf("not implemented")
}

// PrivateAddress implements environs.EnvironProvider.PrivateAddress.
func (environProvider) PrivateAddress() (string, error) {
	return "", fmt.Errorf("not implemented")
}

// InstanceId implements environs.EnvironProvider.InstanceId.
func (environProvider) InstanceId() (instance.Id, error) {
	return "", fmt.Errorf("not implemented")
}

func (environProvider) newConfig(cfg *config.Config) (*environConfig, error) {
	valid, err := provider.Validate(cfg, nil)
	if err != nil {
		return nil, err
	}
	return newEnvironConfig(valid, valid.UnknownAttrs()), nil
}
<commit_msg>Change the default root-dir.<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package local

import (
	"fmt"
	"path/filepath"

	"launchpad.net/loggo"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/utils"
)

var logger = loggo.GetLogger("juju.environs.local")

var _ environs.EnvironProvider = (*environProvider)(nil)

type environProvider struct{}

var provider environProvider

func init() {
	environs.RegisterProvider("local", &environProvider{})
}

// Open implements environs.EnvironProvider.Open.
func (environProvider) Open(cfg *config.Config) (environs.Environ, error) {
	logger.Infof("opening environment %q", cfg.Name())
	environ := &localEnviron{name: cfg.Name()}
	err := environ.SetConfig(cfg)
	if err != nil {
		logger.Errorf("failure setting config: %v", err)
		return nil, err
	}
	return environ, nil
}

// Validate implements environs.EnvironProvider.Validate.
func (provider environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
	// Check for valid changes for the base config values.
	if err := config.Validate(cfg, old); err != nil {
		return nil, err
	}
	v, err := configChecker.Coerce(cfg.UnknownAttrs(), nil)
	if err != nil {
		return nil, err
	}
	localConfig := newEnvironConfig(cfg, v.(map[string]interface{}))
	// Before potentially creating directories, make sure that the
	// root directory has not changed.
	if old != nil {
		oldLocalConfig, err := provider.newConfig(old)
		if err != nil {
			return nil, fmt.Errorf("old config is not a valid local config: %v", old)
		}
		if localConfig.rootDir() != oldLocalConfig.rootDir() {
			return nil, fmt.Errorf("cannot change root-dir from %q to %q",
				oldLocalConfig.rootDir(),
				localConfig.rootDir())
		}
	}
	dir := utils.NormalizePath(localConfig.rootDir())
	if dir == "." {
		dir = config.JujuHomePath(cfg.Name())
		localConfig.attrs["root-dir"] = dir
	}

	// Apply the coerced unknown values back into the config.
	return cfg.Apply(localConfig.attrs)
}

// BoilerplateConfig implements environs.EnvironProvider.BoilerplateConfig.
func (environProvider) BoilerplateConfig() string {
	return `
## https://juju.ubuntu.com/get-started/local/
local:
  type: local
  # Override the directory that is used for the storage files and database.
  # The default location is $JUJU_HOME/<ENV>.
  # $JUJU_HOME defaults to ~/.juju
  # root-dir: ~/.juju/local

`[1:]
}

// SecretAttrs implements environs.EnvironProvider.SecretAttrs.
func (environProvider) SecretAttrs(cfg *config.Config) (map[string]interface{}, error) {
	// don't have any secret attrs
	return nil, nil
}

// Location specific methods that are able to be called by any instance that
// has been created by this provider type.  So a machine agent may well call
// these methods to find out its own address or instance id.

// PublicAddress implements environs.EnvironProvider.PublicAddress.
func (environProvider) PublicAddress() (string, error) {
	return "", fmt.Errorf("not implemented")
}

// PrivateAddress implements environs.EnvironProvider.PrivateAddress.
func (environProvider) PrivateAddress() (string, error) {
	return "", fmt.Errorf("not implemented")
}

// InstanceId implements environs.EnvironProvider.InstanceId.
func (environProvider) InstanceId() (instance.Id, error) {
	return "", fmt.Errorf("not implemented")
}

func (environProvider) newConfig(cfg *config.Config) (*environConfig, error) {
	valid, err := provider.Validate(cfg, nil)
	if err != nil {
		return nil, err
	}
	return newEnvironConfig(valid, valid.UnknownAttrs()), nil
}
<|endoftext|>


<commit_before>package json

import (
	"encoding/json"
	"testing"

	v2net "github.com/v2ray/v2ray-core/common/net"
	v2nettesting "github.com/v2ray/v2ray-core/common/net/testing"
	v2testing "github.com/v2ray/v2ray-core/testing"
	"github.com/v2ray/v2ray-core/testing/assert"
)

func TestStringListParsingList(t *testing.T) {
	v2testing.Current(t)

	rawJson := `["a", "b", "c", "d"]`
	var strList StringList
	err := json.Unmarshal([]byte(rawJson), &strList)
	assert.Error(err).IsNil()
	assert.Int(strList.Len()).Equals(4)
}

func TestStringListParsingString(t *testing.T) {
	v2testing.Current(t)

	rawJson := `"abcd"`
	var strList StringList
	err := json.Unmarshal([]byte(rawJson), &strList)
	assert.Error(err).IsNil()
	assert.Int(strList.Len()).Equals(1)
}

func TestDomainMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "domain": ["google.com", "regexp:v2ray.com$"],
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.DomainAddress("www.v2ray.com", 80))
	assert.Bool(rule.Apply(dest)).IsTrue()
}

func TestPortMatching(t *testing.T) {
	v2testing.Current(t)

	rule := &FieldRule{
		Port: &v2nettesting.PortRange{
			FromValue: 0,
			ToValue:   100,
		},
	}
	dest := v2net.NewTCPDestination(v2net.DomainAddress("www.v2ray.com", 80))
	assert.Bool(rule.Apply(dest)).IsTrue()
}

func TestIPMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "ip": "10.0.0.0/8",
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}, 80))
	assert.Bool(rule.Apply(dest)).IsTrue()
}

func TestIPListMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "ip": ["10.0.0.0/8", "192.168.0.0/16"],
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.IPAddress([]byte{192, 168, 1, 1}, 80))
	assert.Bool(rule.Apply(dest)).IsTrue()
}

func TestPortNotMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "port": "80-100",
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}, 79))
	assert.Bool(rule.Apply(dest)).IsFalse()
}

func TestDomainNotMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "domain": ["google.com", "v2ray.com"],
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}, 80))
	assert.Bool(rule.Apply(dest)).IsFalse()
}

func TestDomainNotMatchingDomain(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "domain": ["google.com", "v2ray.com"],
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.DomainAddress("baidu.com", 80))
	assert.Bool(rule.Apply(dest)).IsFalse()
}
<commit_msg>more test case<commit_after>package json

import (
	"encoding/json"
	"testing"

	v2net "github.com/v2ray/v2ray-core/common/net"
	v2nettesting "github.com/v2ray/v2ray-core/common/net/testing"
	v2testing "github.com/v2ray/v2ray-core/testing"
	"github.com/v2ray/v2ray-core/testing/assert"
)

func TestStringListParsingList(t *testing.T) {
	v2testing.Current(t)

	rawJson := `["a", "b", "c", "d"]`
	var strList StringList
	err := json.Unmarshal([]byte(rawJson), &strList)
	assert.Error(err).IsNil()
	assert.Int(strList.Len()).Equals(4)
}

func TestStringListParsingString(t *testing.T) {
	v2testing.Current(t)

	rawJson := `"abcd"`
	var strList StringList
	err := json.Unmarshal([]byte(rawJson), &strList)
	assert.Error(err).IsNil()
	assert.Int(strList.Len()).Equals(1)
}

func TestDomainMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "domain": ["google.com", "regexp:v2ray.com$"],
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.DomainAddress("www.v2ray.com", 80))
	assert.Bool(rule.Apply(dest)).IsTrue()
}

func TestPortMatching(t *testing.T) {
	v2testing.Current(t)

	rule := &FieldRule{
		Port: &v2nettesting.PortRange{
			FromValue: 0,
			ToValue:   100,
		},
	}
	dest := v2net.NewTCPDestination(v2net.DomainAddress("www.v2ray.com", 80))
	assert.Bool(rule.Apply(dest)).IsTrue()
}

func TestIPMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "ip": "10.0.0.0/8",
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}, 80))
	assert.Bool(rule.Apply(dest)).IsTrue()
}

func TestIPListMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "ip": ["10.0.0.0/8", "192.168.0.0/16"],
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.IPAddress([]byte{192, 168, 1, 1}, 80))
	assert.Bool(rule.Apply(dest)).IsTrue()
}

func TestPortNotMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "port": "80-100",
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}, 79))
	assert.Bool(rule.Apply(dest)).IsFalse()
}

func TestDomainNotMatching(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "domain": ["google.com", "v2ray.com"],
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}, 80))
	assert.Bool(rule.Apply(dest)).IsFalse()
}

func TestDomainNotMatchingDomain(t *testing.T) {
	v2testing.Current(t)

	rawJson := `{
    "type": "field",
    "domain": ["google.com", "v2ray.com"],
    "tag": "test"
  }`
	rule := parseRule([]byte(rawJson))
	dest := v2net.NewTCPDestination(v2net.DomainAddress("baidu.com", 80))
	assert.Bool(rule.Apply(dest)).IsFalse()

	dest = v2net.NewTCPDestination(v2net.DomainAddress("www.google.com", 80))
	assert.Bool(rule.Apply(dest)).IsTrue()
}
<|endoftext|>


<commit_before>// +build integration

package gochecks_test

import (
	"fmt"
	"os"
	"testing"

	"net/http"
	"net/http/httptest"

	"github.com/streadway/amqp"

	. "github.com/aleasoluciones/gochecks"
	"github.com/stretchr/testify/assert"
)

func TestHttpCheckerWithHttpServerUp(t *testing.T) {
	t.Parallel()

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, client")
	}))
	defer ts.Close()

	check := NewHttpChecker("host", "service", ts.URL, 200)
	checkResult := check()

	assert.Equal(t, "ok", checkResult.State)
	assert.InDelta(t, checkResult.Metric, 0, 100)
}

func TestHttpCheckerWithServerDown(t *testing.T) {
	t.Parallel()

	check := NewHttpChecker("host", "service", "https://unknownurl/", 200)
	checkResult := check()

	assert.Equal(t, "critical", checkResult.State)
	assert.InDelta(t, checkResult.Metric, 0, 100)
}

func amqpUrlFromEnv() string {
	url := os.Getenv("AMQP_URL")
	if url == "" {
		url = "amqp://"
	}
	return url
}

func publishMessage(ch *amqp.Channel, exchange, routingKey, text string) {
	ch.Publish(
		exchange,
		routingKey,
		false,
		false,
		amqp.Publishing{
			Headers:         amqp.Table{},
			ContentType:     "application/json",
			ContentEncoding: "",
			Body:            []byte(text),
			DeliveryMode:    amqp.Transient,
			Priority:        0,
		})

}

func TestRabbitMQQueueLenCheck(t *testing.T) {
	t.Parallel()
	amqpUrl := amqpUrlFromEnv()
	queue := "q"
	exchange := "e"
	routingKey := "r"

	conn, _ := amqp.Dial(amqpUrl)
	ch, _ := conn.Channel()
	defer conn.Close()
	defer ch.Close()

	ch.ExchangeDeclare(exchange, "topic", true, false, false, false, nil)
	ch.QueueDelete(queue, false, false, true)
	ch.QueueDeclare(queue, false, false, false, false, nil)
	ch.QueueBind(queue, "#", exchange, false, nil)

	check := NewRabbitMQQueueLenCheck("host", "service", amqpUrl, queue, 2)
	checkResult := check()
	assert.Equal(t, "ok", checkResult.State)
	assert.Equal(t, float32(0), checkResult.Metric)

	publishMessage(ch, exchange, routingKey, "msg1")
	publishMessage(ch, exchange, routingKey, "msg2")

	checkResult = check()
	assert.Equal(t, "ok", checkResult.State)
	assert.Equal(t, float32(2), checkResult.Metric)

	publishMessage(ch, exchange, routingKey, "msg3")
	checkResult = check()

	assert.Equal(t, "critical", checkResult.State)
	assert.Equal(t, float32(3), checkResult.Metric)
}

func TestRabbitMQQueueLenCheckReturnsCriticalWhenCantConnectToRabbitMQ(t *testing.T) {
	t.Parallel()

	check := NewRabbitMQQueueLenCheck("host", "service", amqpUrlFromEnv()+"whatever", "queue", 2)
	checkResult := check()

	assert.Equal(t, "critical", checkResult.State)
}

func TestMysqlConnectionErrorCheck(t *testing.T) {
	t.Parallel()

	check := NewMysqlConnectionCheck("host", "service", "mysql://nohost/nodb")
	checkResult := check()

	assert.Equal(t, "critical", checkResult.State)
}

func TestMysqlConnectionOkCheck(t *testing.T) {
	t.Parallel()

	check := NewMysqlConnectionCheck("host", "service", os.Getenv("MYSQL_URL"))
	checkResult := check()

	assert.Equal(t, "ok", checkResult.State)
	assert.InDelta(t, checkResult.Metric, 0, 100)
}
<commit_msg>Fixed tests<commit_after>// +build integration

package gochecks_test

import (
	"fmt"
	"os"
	"testing"

	"net/http"
	"net/http/httptest"

	"github.com/streadway/amqp"

	. "github.com/aleasoluciones/gochecks"
	"github.com/stretchr/testify/assert"
)

func TestHttpCheckerWithHttpServerUp(t *testing.T) {
	t.Parallel()

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, client")
	}))
	defer ts.Close()

	check := NewHTTPChecker("host", "service", ts.URL, 200)
	checkResult := check()

	assert.Equal(t, "ok", checkResult.State)
	assert.InDelta(t, checkResult.Metric, 0, 100)
}

func TestHttpCheckerWithServerDown(t *testing.T) {
	t.Parallel()

	check := NewHTTPChecker("host", "service", "https://unknownurl/", 200)
	checkResult := check()

	assert.Equal(t, "critical", checkResult.State)
	assert.InDelta(t, checkResult.Metric, 0, 100)
}

func amqpUrlFromEnv() string {
	url := os.Getenv("AMQP_URL")
	if url == "" {
		url = "amqp://"
	}
	return url
}

func publishMessage(ch *amqp.Channel, exchange, routingKey, text string) {
	ch.Publish(
		exchange,
		routingKey,
		false,
		false,
		amqp.Publishing{
			Headers:         amqp.Table{},
			ContentType:     "application/json",
			ContentEncoding: "",
			Body:            []byte(text),
			DeliveryMode:    amqp.Transient,
			Priority:        0,
		})

}

func TestRabbitMQQueueLenCheck(t *testing.T) {
	t.Parallel()
	amqpUrl := amqpUrlFromEnv()
	queue := "q"
	exchange := "e"
	routingKey := "r"

	conn, _ := amqp.Dial(amqpUrl)
	ch, _ := conn.Channel()
	defer conn.Close()
	defer ch.Close()

	ch.ExchangeDeclare(exchange, "topic", true, false, false, false, nil)
	ch.QueueDelete(queue, false, false, true)
	ch.QueueDeclare(queue, false, false, false, false, nil)
	ch.QueueBind(queue, "#", exchange, false, nil)

	check := NewRabbitMQQueueLenCheck("host", "service", amqpUrl, queue, 2)
	checkResult := check()
	assert.Equal(t, "ok", checkResult.State)
	assert.Equal(t, float32(0), checkResult.Metric)

	publishMessage(ch, exchange, routingKey, "msg1")
	publishMessage(ch, exchange, routingKey, "msg2")

	checkResult = check()
	assert.Equal(t, "ok", checkResult.State)
	assert.Equal(t, float32(2), checkResult.Metric)

	publishMessage(ch, exchange, routingKey, "msg3")
	checkResult = check()

	assert.Equal(t, "critical", checkResult.State)
	assert.Equal(t, float32(3), checkResult.Metric)
}

func TestRabbitMQQueueLenCheckReturnsCriticalWhenCantConnectToRabbitMQ(t *testing.T) {
	t.Parallel()

	check := NewRabbitMQQueueLenCheck("host", "service", amqpUrlFromEnv()+"whatever", "queue", 2)
	checkResult := check()

	assert.Equal(t, "critical", checkResult.State)
}

func TestMysqlConnectionErrorCheck(t *testing.T) {
	t.Parallel()

	check := NewMysqlConnectionCheck("host", "service", "mysql://nohost/nodb")
	checkResult := check()

	assert.Equal(t, "critical", checkResult.State)
}

func TestMysqlConnectionOkCheck(t *testing.T) {
	t.Parallel()

	check := NewMysqlConnectionCheck("host", "service", os.Getenv("MYSQL_URL"))
	checkResult := check()

	assert.Equal(t, "ok", checkResult.State)
	assert.InDelta(t, checkResult.Metric, 0, 100)
}
<|endoftext|>


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

// Package netqueue implements a queue based on channels and networking.
//
// It is based on concepts from old/netchan and a lot of discussion about this
// theme on the internet. The implementation present here is specific to tsuru,
// but could be more generic.
package netqueue

import (
	"encoding/gob"
	"io"
)

// The size of buffered channels created by ChannelFromWriter.
const ChanSize = 32

// Message represents the message stored in the queue.
//
// A message is specified by an action and a slice of strings, representing
// arguments to the action.
//
// For example, the action "regenerate apprc" could receive one argument: the
// name of the app for which the apprc file will be regenerate.
type Message struct {
	Action string
	Args   []string
}

// ChannelFromWriter returns a channel from a given io.Writer.
//
// Every time a Message is sent to the channel, it gets written to the writer.
// ChannelFromWriter also returns a channel for errors in writtings. You can
// use a select for error checking:
//
//     ch, errCh := ChannelFromWriter(w)
//     // use ch
//     select {
//     case err := <-errCh:
//         // threat the error
//     case time.After(5e9):
//         // no error after 5 seconds
//     }
//
// Please notice that there is no deadline for the writting. You can obviously
// ignore errors, if they are not significant for you.
//
// Whenever you close the message channel (and you should, to make it clear
// that you will not send any messages to the channel anymore), error channel
// will get automatically closed.
//
// Both channels are buffered by ChanSize.
func ChannelFromWriter(w io.Writer) (chan<- Message, <-chan error) {
	msgChan := make(chan Message, ChanSize)
	errChan := make(chan error, ChanSize)
	go write(w, msgChan, errChan)
	return msgChan, errChan
}

// write reads messages from ch and write them to w, in gob format.
//
// If clients close ch, write will close errCh.
func write(w io.Writer, ch <-chan Message, errCh chan<- error) {
	defer close(errCh)
	for msg := range ch {
		encoder := gob.NewEncoder(w)
		if err := encoder.Encode(msg); err != nil {
			errCh <- err
		}
	}
}
<commit_msg>netqueue: improve docs for ChannelFromWriter<commit_after>// Copyright 2012 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package netqueue implements a queue based on channels and networking.
//
// It is based on concepts from old/netchan and a lot of discussion about this
// theme on the internet. The implementation present here is specific to tsuru,
// but could be more generic.
package netqueue

import (
	"encoding/gob"
	"io"
)

// The size of buffered channels created by ChannelFromWriter.
const ChanSize = 32

// Message represents the message stored in the queue.
//
// A message is specified by an action and a slice of strings, representing
// arguments to the action.
//
// For example, the action "regenerate apprc" could receive one argument: the
// name of the app for which the apprc file will be regenerate.
type Message struct {
	Action string
	Args   []string
}

// ChannelFromWriter returns a channel from a given io.Writer.
//
// Every time a Message is sent to the channel, it gets written to the writer
// in gob format.  ChannelFromWriter also returns a channel for errors in
// writtings. You can use a select for error checking:
//
//     ch, errCh := ChannelFromWriter(w)
//     // use ch
//     select {
//     case err := <-errCh:
//         // threat the error
//     case time.After(5e9):
//         // no error after 5 seconds
//     }
//
// Please notice that there is no deadline for the writting. You can obviously
// ignore errors, if they are not significant for you.
//
// Whenever you close the message channel (and you should, to make it clear
// that you will not send any messages to the channel anymore), error channel
// will get automatically closed.
//
// Both channels are buffered by ChanSize.
func ChannelFromWriter(w io.Writer) (chan<- Message, <-chan error) {
	msgChan := make(chan Message, ChanSize)
	errChan := make(chan error, ChanSize)
	go write(w, msgChan, errChan)
	return msgChan, errChan
}

// write reads messages from ch and write them to w, in gob format.
//
// If clients close ch, write will close errCh.
func write(w io.Writer, ch <-chan Message, errCh chan<- error) {
	defer close(errCh)
	for msg := range ch {
		encoder := gob.NewEncoder(w)
		if err := encoder.Encode(msg); err != nil {
			errCh <- err
		}
	}
}
<|endoftext|>