plugin/kubernetes: Enable protobuf, Update client api package (#1114)

* vendor

* code
This commit is contained in:
Chris O'Haver 2017-09-29 15:58:50 -04:00 committed by John Belamaric
parent 45b0252c1a
commit 4b3a430ff2
1511 changed files with 286873 additions and 253612 deletions

123
Gopkg.lock generated
View file

@ -1,12 +1,6 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "cloud.google.com/go"
packages = ["compute/metadata"]
revision = "0f0b8420cb699ac4ce059c63bac263f4301fe95b"
version = "v0.12.0"
[[projects]]
name = "github.com/PuerkitoBio/purell"
packages = ["."]
@ -31,42 +25,30 @@
revision = "b2a4d4ae21c789b689dd162deb819665567f481c"
version = "0.10.0"
[[projects]]
name = "github.com/asaskevich/govalidator"
packages = ["."]
revision = "73945b6115bfbbcc57d89b7316e28109364124e1"
version = "v7"
[[projects]]
branch = "master"
name = "github.com/beorn7/perks"
packages = ["quantile"]
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
[[projects]]
name = "github.com/blang/semver"
packages = ["."]
revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f"
version = "v3.5.1"
[[projects]]
name = "github.com/coreos/etcd"
packages = ["client","pkg/pathutil","pkg/srv","pkg/types","version"]
revision = "9d43462d174c664f5edf313dec0de31e1ef4ed47"
version = "v3.2.6"
[[projects]]
branch = "master"
name = "github.com/coreos/go-oidc"
packages = ["http","jose","key","oauth2","oidc"]
revision = "a4973d9a4225417aecf5d450a9522f00c1f7130f"
[[projects]]
name = "github.com/coreos/go-semver"
packages = ["semver"]
revision = "8ab6407b697782a06568d4b7f1db25550ec2e4c6"
version = "v0.2.0"
[[projects]]
name = "github.com/coreos/pkg"
packages = ["health","httputil","timeutil"]
revision = "3ac0863d7acf3bc44daf49afef8919af12f704ef"
version = "v3"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
@ -105,22 +87,22 @@
[[projects]]
name = "github.com/emicklei/go-restful"
packages = [".","log","swagger"]
packages = [".","log"]
revision = "777bb3f19bcafe2575ffb2a3e46af92509ae9594"
version = "v1.2"
[[projects]]
name = "github.com/emicklei/go-restful-swagger12"
packages = ["."]
revision = "dcef7f55730566d41eae5db10e7d6981829720f6"
version = "1.0.1"
[[projects]]
branch = "master"
name = "github.com/farsightsec/golang-framestream"
packages = ["."]
revision = "dec85654e8b8cf6712870afb14ee53d1c98cd5e2"
[[projects]]
name = "github.com/fsnotify/fsnotify"
packages = ["."]
revision = "629574ca2a5df945712d3079857300b5e4da0236"
version = "v1.4.2"
[[projects]]
name = "github.com/ghodss/yaml"
packages = ["."]
@ -133,6 +115,18 @@
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5"
version = "v0.3.0"
[[projects]]
branch = "master"
name = "github.com/go-openapi/analysis"
packages = ["."]
revision = "8ed83f2ea9f00f945516462951a288eaa68bf0d6"
[[projects]]
branch = "master"
name = "github.com/go-openapi/errors"
packages = ["."]
revision = "03cfca65330da08a5a440053faf994a3c682b5bf"
[[projects]]
branch = "master"
name = "github.com/go-openapi/jsonpointer"
@ -145,12 +139,24 @@
packages = ["."]
revision = "36d33bfe519efae5632669801b180bf1a245da3b"
[[projects]]
branch = "master"
name = "github.com/go-openapi/loads"
packages = ["."]
revision = "a80dea3052f00e5f032e860dd7355cd0cc67e24d"
[[projects]]
branch = "master"
name = "github.com/go-openapi/spec"
packages = ["."]
revision = "3faa0055dbbf2110abc1f3b4e3adbb22721e96e7"
[[projects]]
branch = "master"
name = "github.com/go-openapi/strfmt"
packages = ["."]
revision = "610b6cacdcde6852f4de68998bd20ce1dac85b22"
[[projects]]
branch = "master"
name = "github.com/go-openapi/swag"
@ -193,6 +199,12 @@
packages = ["go/otgrpc"]
revision = "6c130eed1e297e1aa4d415a50c90d0c81c52677e"
[[projects]]
branch = "master"
name = "github.com/hashicorp/golang-lru"
packages = [".","simplelru"]
revision = "0a025b7e63adc15a622f29b0b2c4c3848243bbf6"
[[projects]]
branch = "master"
name = "github.com/howeyc/gopass"
@ -205,12 +217,6 @@
revision = "3e95a51e0639b4cf372f2ccf74c86749d747fbdc"
version = "0.2.2"
[[projects]]
name = "github.com/jonboulle/clockwork"
packages = ["."]
revision = "2eee05ed794112d45db504eb05aa693efd2b8b09"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/juju/ratelimit"
@ -235,6 +241,12 @@
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
revision = "d0303fe809921458f417bcf828397a65db30a7e4"
[[projects]]
name = "github.com/opentracing/opentracing-go"
packages = [".","ext","log"]
@ -247,12 +259,6 @@
revision = "d88c90182f3fb514be54a1c7adc11a04d41c7da9"
version = "v0.2.4"
[[projects]]
name = "github.com/pborman/uuid"
packages = ["."]
revision = "e790cca94e6cc75c7064b1332e63811d4aae1a53"
version = "v1.1"
[[projects]]
name = "github.com/pierrec/lz4"
packages = ["."]
@ -313,12 +319,6 @@
packages = ["ssh/terminal"]
revision = "81e90905daefcd6fd217b62423c0908922eadb30"
[[projects]]
branch = "master"
name = "golang.org/x/oauth2"
packages = [".","google","internal","jws","jwt"]
revision = "9a379c6b3e95a790ffc43293c2a78dee0d7b6e20"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
@ -331,12 +331,6 @@
packages = ["internal/gen","internal/triegen","internal/ucd","transform","unicode/cldr","unicode/norm","width"]
revision = "ac87088df8ef557f1e32cd00ed0b6fbc3f7ddafb"
[[projects]]
name = "google.golang.org/appengine"
packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "google.golang.org/genproto"
@ -355,21 +349,32 @@
revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
version = "v0.9.0"
[[projects]]
branch = "v2"
name = "gopkg.in/mgo.v2"
packages = ["bson","internal/json"]
revision = "3f83fa5005286a7fe593b055f0d7771a7dce4655"
[[projects]]
branch = "v2"
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
[[projects]]
name = "k8s.io/apimachinery"
packages = ["pkg/api/equality","pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/apimachinery","pkg/apimachinery/announced","pkg/apimachinery/registered","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1alpha1","pkg/conversion","pkg/conversion/queryparams","pkg/conversion/unstructured","pkg/fields","pkg/labels","pkg/openapi","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/cache","pkg/util/clock","pkg/util/diff","pkg/util/errors","pkg/util/framer","pkg/util/intstr","pkg/util/json","pkg/util/net","pkg/util/rand","pkg/util/runtime","pkg/util/sets","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/reflect"]
revision = "1fd2e63a9a370677308a42f24fd40c86438afddf"
[[projects]]
name = "k8s.io/client-go"
packages = ["1.5/discovery","1.5/kubernetes","1.5/kubernetes/typed/apps/v1alpha1","1.5/kubernetes/typed/authentication/v1beta1","1.5/kubernetes/typed/authorization/v1beta1","1.5/kubernetes/typed/autoscaling/v1","1.5/kubernetes/typed/batch/v1","1.5/kubernetes/typed/certificates/v1alpha1","1.5/kubernetes/typed/core/v1","1.5/kubernetes/typed/extensions/v1beta1","1.5/kubernetes/typed/policy/v1alpha1","1.5/kubernetes/typed/rbac/v1alpha1","1.5/kubernetes/typed/storage/v1beta1","1.5/pkg/api","1.5/pkg/api/errors","1.5/pkg/api/install","1.5/pkg/api/meta","1.5/pkg/api/meta/metatypes","1.5/pkg/api/resource","1.5/pkg/api/unversioned","1.5/pkg/api/v1","1.5/pkg/api/validation/path","1.5/pkg/apimachinery","1.5/pkg/apimachinery/announced","1.5/pkg/apimachinery/registered","1.5/pkg/apis/apps","1.5/pkg/apis/apps/install","1.5/pkg/apis/apps/v1alpha1","1.5/pkg/apis/authentication","1.5/pkg/apis/authentication/install","1.5/pkg/apis/authentication/v1beta1","1.5/pkg/apis/authorization","1.5/pkg/apis/authorization/install","1.5/pkg/apis/authorization/v1beta1","1.5/pkg/apis/autoscaling","1.5/pkg/apis/autoscaling/install","1.5/pkg/apis/autoscaling/v1","1.5/pkg/apis/batch","1.5/pkg/apis/batch/install","1.5/pkg/apis/batch/v1","1.5/pkg/apis/batch/v2alpha1","1.5/pkg/apis/certificates","1.5/pkg/apis/certificates/install","1.5/pkg/apis/certificates/v1alpha1","1.5/pkg/apis/extensions","1.5/pkg/apis/extensions/install","1.5/pkg/apis/extensions/v1beta1","1.5/pkg/apis/policy","1.5/pkg/apis/policy/install","1.5/pkg/apis/policy/v1alpha1","1.5/pkg/apis/rbac","1.5/pkg/apis/rbac/install","1.5/pkg/apis/rbac/v1alpha1","1.5/pkg/apis/storage","1.5/pkg/apis/storage/install","1.5/pkg/apis/storage/v1beta1","1.5/pkg/auth/user","1.5/pkg/conversion","1.5/pkg/conversion/queryparams","1.5/pkg/fields","1.5/pkg/genericapiserver/openapi/common","1.5/pkg/labels","1.5/pkg/runtime","1.5/pkg/runtime/serializer","1.5/pkg/runtime/serializer/json","1.5/pkg/runtime/serializer/protobuf","1.5/pkg/runtime/serializer/recognizer","1.5/pkg/runtime/serializer/streaming","1.5/pkg/runtime/serializer/versioning","1.5/pkg/selection","1.5/pkg/third_party/forked/golang/reflect","1.5/pkg/types","1.5/pkg/util","1.5/pkg/util/cert","1.5/pkg/util/clock","1.5/pkg/util/errors","1.5/pkg/util/flowcontrol","1.5/pkg/util/framer","1.5/pkg/util/homedir","1.5/pkg/util/integer","1.5/pkg/util/intstr","1.5/pkg/util/json","1.5/pkg/util/labels","1.5/pkg/util/net","1.5/pkg/util/parsers","1.5/pkg/util/rand","1.5/pkg/util/runtime","1.5/pkg/util/sets","1.5/pkg/util/uuid","1.5/pkg/util/validation","1.5/pkg/util/validation/field","1.5/pkg/util/wait","1.5/pkg/util/yaml","1.5/pkg/version","1.5/pkg/watch","1.5/pkg/watch/versioned","1.5/plugin/pkg/client/auth","1.5/plugin/pkg/client/auth/gcp","1.5/plugin/pkg/client/auth/oidc","1.5/rest","1.5/tools/auth","1.5/tools/cache","1.5/tools/clientcmd","1.5/tools/clientcmd/api","1.5/tools/clientcmd/api/latest","1.5/tools/clientcmd/api/v1","1.5/tools/metrics","1.5/transport"]
revision = "1195e3a8ee1a529d53eed7c624527a68555ddf1f"
version = "v1.5.1"
packages = ["discovery","kubernetes","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/apps/v1beta1","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1beta1","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v2alpha1","kubernetes/typed/batch/v1","kubernetes/typed/batch/v2alpha1","kubernetes/typed/certificates/v1beta1","kubernetes/typed/core/v1","kubernetes/typed/extensions/v1beta1","kubernetes/typed/networking/v1","kubernetes/typed/policy/v1beta1","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1beta1","kubernetes/typed/settings/v1alpha1","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1beta1","pkg/api","pkg/api/v1","pkg/api/v1/ref","pkg/apis/admissionregistration","pkg/apis/admissionregistration/v1alpha1","pkg/apis/apps","pkg/apis/apps/v1beta1","pkg/apis/authentication","pkg/apis/authentication/v1","pkg/apis/authentication/v1beta1","pkg/apis/authorization","pkg/apis/authorization/v1","pkg/apis/authorization/v1beta1","pkg/apis/autoscaling","pkg/apis/autoscaling/v1","pkg/apis/autoscaling/v2alpha1","pkg/apis/batch","pkg/apis/batch/v1","pkg/apis/batch/v2alpha1","pkg/apis/certificates","pkg/apis/certificates/v1beta1","pkg/apis/extensions","pkg/apis/extensions/v1beta1","pkg/apis/networking","pkg/apis/networking/v1","pkg/apis/policy","pkg/apis/policy/v1beta1","pkg/apis/rbac","pkg/apis/rbac/v1alpha1","pkg/apis/rbac/v1beta1","pkg/apis/settings","pkg/apis/settings/v1alpha1","pkg/apis/storage","pkg/apis/storage/v1","pkg/apis/storage/v1beta1","pkg/util","pkg/util/parsers","pkg/version","rest","rest/watch","tools/auth","tools/cache","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/metrics","transport","util/cert","util/flowcontrol","util/homedir","util/integer"]
revision = "d92e8497f71b7b4e0494e5bd204b48d34bd6f254"
version = "v4.0.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "0ddaab68018c7f38bccad1d9932df961ae52544dc56e3c0060ce1c28e5da0a47"
inputs-digest = "916c5fe7d9ce3a19d3956068cae5daf94be1269ae86400dfed36308fbd88a539"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -3,7 +3,8 @@ package federation
import (
"github.com/coredns/coredns/plugin/kubernetes"
"k8s.io/client-go/1.5/pkg/api"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/client-go/pkg/api/v1"
)
type APIConnFederationTest struct{}
@ -11,23 +12,22 @@ type APIConnFederationTest struct{}
func (APIConnFederationTest) Run() { return }
func (APIConnFederationTest) Stop() error { return nil }
func (APIConnFederationTest) PodIndex(string) []interface{} {
a := make([]interface{}, 1)
a[0] = &api.Pod{
ObjectMeta: api.ObjectMeta{
func (APIConnFederationTest) PodIndex(string) []*api.Pod {
a := []*api.Pod{{
ObjectMeta: meta.ObjectMeta{
Namespace: "podns",
},
Status: api.PodStatus{
PodIP: "10.240.0.1", // Remote IP set in test.ResponseWriter
},
}
}}
return a
}
func (APIConnFederationTest) ServiceList() []*api.Service {
svcs := []*api.Service{
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "svc1",
Namespace: "testns",
},
@ -41,7 +41,7 @@ func (APIConnFederationTest) ServiceList() []*api.Service {
},
},
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "hdls1",
Namespace: "testns",
},
@ -50,7 +50,7 @@ func (APIConnFederationTest) ServiceList() []*api.Service {
},
},
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "external",
Namespace: "testns",
},
@ -65,12 +65,10 @@ func (APIConnFederationTest) ServiceList() []*api.Service {
},
}
return svcs
}
func (APIConnFederationTest) EndpointsList() api.EndpointsList {
return api.EndpointsList{
Items: []api.Endpoints{
func (APIConnFederationTest) EndpointsList() []*api.Endpoints {
eps := []*api.Endpoints{
{
Subsets: []api.EndpointSubset{
{
@ -89,18 +87,18 @@ func (APIConnFederationTest) EndpointsList() api.EndpointsList {
},
},
},
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "svc1",
Namespace: "testns",
},
},
},
}
return eps
}
func (APIConnFederationTest) GetNodeByName(name string) (api.Node, error) {
return api.Node{
ObjectMeta: api.ObjectMeta{
func (APIConnFederationTest) GetNodeByName(name string) (*api.Node, error) {
return &api.Node{
ObjectMeta: meta.ObjectMeta{
Name: "test.node.foo.bar",
Labels: map[string]string{
kubernetes.LabelRegion: "fd-r",

View file

@ -4,7 +4,7 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/request"
"k8s.io/client-go/1.5/pkg/api"
api "k8s.io/client-go/pkg/api/v1"
)
// AutoPath implements the AutoPathFunc call from the autopath plugin.
@ -40,14 +40,10 @@ func (k *Kubernetes) AutoPath(state request.Request) []string {
}
// podWithIP return the api.Pod for source IP ip. It returns nil if nothing can be found.
func (k *Kubernetes) podWithIP(ip string) (p *api.Pod) {
objList := k.APIConn.PodIndex(ip)
for _, o := range objList {
p, ok := o.(*api.Pod)
if !ok {
func (k *Kubernetes) podWithIP(ip string) *api.Pod {
ps := k.APIConn.PodIndex(ip)
if len(ps) == 0 {
return nil
}
return p
}
return nil
return ps[0]
}

View file

@ -3,45 +3,31 @@ package kubernetes
import (
"errors"
"fmt"
"log"
"sync"
"time"
"k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api"
unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned"
"k8s.io/client-go/1.5/pkg/api/v1"
"k8s.io/client-go/1.5/pkg/labels"
"k8s.io/client-go/1.5/pkg/runtime"
"k8s.io/client-go/1.5/pkg/watch"
"k8s.io/client-go/1.5/tools/cache"
"k8s.io/client-go/kubernetes"
api "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/tools/cache"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
)
var (
namespace = api.NamespaceAll
)
// storeToNamespaceLister makes a Store that lists Namespaces.
type storeToNamespaceLister struct {
cache.Store
}
const podIPIndex = "PodIP"
// List lists all Namespaces in the store.
func (s *storeToNamespaceLister) List() (ns api.NamespaceList, err error) {
for _, m := range s.Store.List() {
ns.Items = append(ns.Items, *(m.(*api.Namespace)))
}
return ns, nil
}
type dnsController interface {
ServiceList() []*api.Service
PodIndex(string) []interface{}
EndpointsList() api.EndpointsList
PodIndex(string) []*api.Pod
EndpointsList() []*api.Endpoints
GetNodeByName(string) (api.Node, error)
GetNodeByName(string) (*api.Node, error)
Run()
Stop() error
@ -52,15 +38,13 @@ type dnsControl struct {
selector *labels.Selector
svcController *cache.Controller
podController *cache.Controller
nsController *cache.Controller
epController *cache.Controller
svcController cache.Controller
podController cache.Controller
epController cache.Controller
svcLister cache.StoreToServiceLister
podLister cache.StoreToPodLister
nsLister storeToNamespaceLister
epLister cache.StoreToEndpointsLister
svcLister cache.Indexer
podLister cache.Indexer
epLister cache.Store
// stopLock is used to enforce only a single call to Stop is active.
// Needed because we allow stopping through an http endpoint and
@ -74,7 +58,7 @@ type dnsControlOpts struct {
initPodCache bool
resyncPeriod time.Duration
// Label handling.
labelSelector *unversionedapi.LabelSelector
labelSelector *meta.LabelSelector
selector *labels.Selector
}
@ -85,8 +69,7 @@ func newdnsController(kubeClient *kubernetes.Clientset, opts dnsControlOpts) *dn
selector: opts.selector,
stopCh: make(chan struct{}),
}
dns.svcLister.Indexer, dns.svcController = cache.NewIndexerInformer(
dns.svcLister, dns.svcController = cache.NewIndexerInformer(
&cache.ListWatch{
ListFunc: serviceListFunc(dns.client, namespace, dns.selector),
WatchFunc: serviceWatchFunc(dns.client, namespace, dns.selector),
@ -97,7 +80,7 @@ func newdnsController(kubeClient *kubernetes.Clientset, opts dnsControlOpts) *dn
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
if opts.initPodCache {
dns.podLister.Indexer, dns.podController = cache.NewIndexerInformer(
dns.podLister, dns.podController = cache.NewIndexerInformer(
&cache.ListWatch{
ListFunc: podListFunc(dns.client, namespace, dns.selector),
WatchFunc: podWatchFunc(dns.client, namespace, dns.selector),
@ -107,17 +90,7 @@ func newdnsController(kubeClient *kubernetes.Clientset, opts dnsControlOpts) *dn
cache.ResourceEventHandlerFuncs{},
cache.Indexers{podIPIndex: podIPIndexFunc})
}
dns.nsLister.Store, dns.nsController = cache.NewInformer(
&cache.ListWatch{
ListFunc: namespaceListFunc(dns.client, dns.selector),
WatchFunc: namespaceWatchFunc(dns.client, dns.selector),
},
&api.Namespace{},
opts.resyncPeriod,
cache.ResourceEventHandlerFuncs{})
dns.epLister.Store, dns.epController = cache.NewInformer(
dns.epLister, dns.epController = cache.NewInformer(
&cache.ListWatch{
ListFunc: endpointsListFunc(dns.client, namespace, dns.selector),
WatchFunc: endpointsWatchFunc(dns.client, namespace, dns.selector),
@ -137,182 +110,86 @@ func podIPIndexFunc(obj interface{}) ([]string, error) {
return []string{p.Status.PodIP}, nil
}
func serviceListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
func serviceListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(meta.ListOptions) (runtime.Object, error) {
return func(opts meta.ListOptions) (runtime.Object, error) {
if s != nil {
opts.LabelSelector = *s
opts.LabelSelector = (*s).String()
}
listV1, err := c.Core().Services(ns).List(opts)
listV1, err := c.Services(ns).List(opts)
if err != nil {
return nil, err
}
var listAPI api.ServiceList
err = v1.Convert_v1_ServiceList_To_api_ServiceList(listV1, &listAPI, nil)
if err != nil {
return nil, err
}
return &listAPI, err
return listV1, err
}
}
func podListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
func podListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(meta.ListOptions) (runtime.Object, error) {
return func(opts meta.ListOptions) (runtime.Object, error) {
if s != nil {
opts.LabelSelector = *s
opts.LabelSelector = (*s).String()
}
listV1, err := c.Core().Pods(ns).List(opts)
listV1, err := c.Pods(ns).List(opts)
if err != nil {
return nil, err
}
var listAPI api.PodList
err = v1.Convert_v1_PodList_To_api_PodList(listV1, &listAPI, nil)
if err != nil {
return nil, err
}
return &listAPI, err
return listV1, err
}
}
func v1ToAPIFilter(in watch.Event) (out watch.Event, keep bool) {
if in.Type == watch.Error {
return in, true
}
switch v1Obj := in.Object.(type) {
case *v1.Service:
var apiObj api.Service
err := v1.Convert_v1_Service_To_api_Service(v1Obj, &apiObj, nil)
if err != nil {
log.Printf("[ERROR] Could not convert v1.Service: %s", err)
return in, true
}
return watch.Event{Type: in.Type, Object: &apiObj}, true
case *v1.Pod:
var apiObj api.Pod
err := v1.Convert_v1_Pod_To_api_Pod(v1Obj, &apiObj, nil)
if err != nil {
log.Printf("[ERROR] Could not convert v1.Pod: %s", err)
return in, true
}
return watch.Event{Type: in.Type, Object: &apiObj}, true
case *v1.Namespace:
var apiObj api.Namespace
err := v1.Convert_v1_Namespace_To_api_Namespace(v1Obj, &apiObj, nil)
if err != nil {
log.Printf("[ERROR] Could not convert v1.Namespace: %s", err)
return in, true
}
return watch.Event{Type: in.Type, Object: &apiObj}, true
case *v1.Endpoints:
var apiObj api.Endpoints
err := v1.Convert_v1_Endpoints_To_api_Endpoints(v1Obj, &apiObj, nil)
if err != nil {
log.Printf("[ERROR] Could not convert v1.Endpoint: %s", err)
return in, true
}
return watch.Event{Type: in.Type, Object: &apiObj}, true
}
log.Printf("[WARN] Unhandled v1 type in event: %v", in)
return in, true
}
func serviceWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
func serviceWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = *s
options.LabelSelector = (*s).String()
}
w, err := c.Core().Services(ns).Watch(options)
w, err := c.Services(ns).Watch(options)
if err != nil {
return nil, err
}
return watch.Filter(w, v1ToAPIFilter), nil
return w, nil
}
}
func podWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
func podWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = *s
options.LabelSelector = (*s).String()
}
w, err := c.Core().Pods(ns).Watch(options)
w, err := c.Pods(ns).Watch(options)
if err != nil {
return nil, err
}
return watch.Filter(w, v1ToAPIFilter), nil
return w, nil
}
}
func namespaceListFunc(c *kubernetes.Clientset, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
func endpointsListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(meta.ListOptions) (runtime.Object, error) {
return func(opts meta.ListOptions) (runtime.Object, error) {
if s != nil {
opts.LabelSelector = *s
opts.LabelSelector = (*s).String()
}
listV1, err := c.Core().Namespaces().List(opts)
listV1, err := c.Endpoints(ns).List(opts)
if err != nil {
return nil, err
}
var listAPI api.NamespaceList
err = v1.Convert_v1_NamespaceList_To_api_NamespaceList(listV1, &listAPI, nil)
if err != nil {
return nil, err
}
return &listAPI, err
return listV1, err
}
}
func namespaceWatchFunc(c *kubernetes.Clientset, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
func endpointsWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = *s
options.LabelSelector = (*s).String()
}
w, err := c.Core().Namespaces().Watch(options)
w, err := c.Endpoints(ns).Watch(options)
if err != nil {
return nil, err
}
return watch.Filter(w, v1ToAPIFilter), nil
}
}
func endpointsListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
if s != nil {
opts.LabelSelector = *s
}
listV1, err := c.Core().Endpoints(ns).List(opts)
if err != nil {
return nil, err
}
var listAPI api.EndpointsList
err = v1.Convert_v1_EndpointsList_To_api_EndpointsList(listV1, &listAPI, nil)
if err != nil {
return nil, err
}
return &listAPI, err
}
}
func endpointsWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = *s
}
w, err := c.Core().Endpoints(ns).Watch(options)
if err != nil {
return nil, err
}
return watch.Filter(w, v1ToAPIFilter), nil
return w, nil
}
}
func (dns *dnsControl) controllersInSync() bool {
hs := dns.svcController.HasSynced() &&
dns.nsController.HasSynced() &&
dns.epController.HasSynced()
if dns.podController != nil {
@ -341,7 +218,6 @@ func (dns *dnsControl) Stop() error {
// Run starts the controller.
func (dns *dnsControl) Run() {
go dns.svcController.Run(dns.stopCh)
go dns.nsController.Run(dns.stopCh)
go dns.epController.Run(dns.stopCh)
if dns.podController != nil {
go dns.podController.Run(dns.stopCh)
@ -349,54 +225,52 @@ func (dns *dnsControl) Run() {
<-dns.stopCh
}
func (dns *dnsControl) NamespaceList() *api.NamespaceList {
nsList, err := dns.nsLister.List()
if err != nil {
return &api.NamespaceList{}
func (dns *dnsControl) ServiceList() (svcs []*api.Service) {
os := dns.svcLister.List()
for _, o := range os {
s, ok := o.(*api.Service)
if !ok {
continue
}
return &nsList
}
func (dns *dnsControl) ServiceList() []*api.Service {
svcs, err := dns.svcLister.List(labels.Everything())
if err != nil {
return []*api.Service{}
svcs = append(svcs, s)
}
return svcs
}
func (dns *dnsControl) PodIndex(ip string) []interface{} {
if dns.podLister.Indexer == nil {
func (dns *dnsControl) PodIndex(ip string) (pods []*api.Pod) {
if dns.podLister == nil {
return nil
}
pods, err := dns.podLister.Indexer.ByIndex(podIPIndex, ip)
os, err := dns.podLister.ByIndex(podIPIndex, ip)
if err != nil {
return nil
}
for _, o := range os {
p, ok := o.(*api.Pod)
if !ok {
continue
}
pods = append(pods, p)
}
return pods
}
func (dns *dnsControl) EndpointsList() api.EndpointsList {
epl, err := dns.epLister.List()
if err != nil {
return api.EndpointsList{}
func (dns *dnsControl) EndpointsList() (eps []*api.Endpoints) {
os := dns.epLister.List()
for _, o := range os {
ep, ok := o.(*api.Endpoints)
if !ok {
continue
}
return epl
eps = append(eps, ep)
}
return eps
}
func (dns *dnsControl) GetNodeByName(name string) (api.Node, error) {
v1node, err := dns.client.Core().Nodes().Get(name)
func (dns *dnsControl) GetNodeByName(name string) (*api.Node, error) {
v1node, err := dns.client.Nodes().Get(name, meta.GetOptions{})
if err != nil {
return api.Node{}, err
return &api.Node{}, err
}
var apinode api.Node
err = v1.Convert_v1_Node_To_api_Node(v1node, &apinode, nil)
if err != nil {
return api.Node{}, err
}
return apinode, nil
return v1node, nil
}

View file

@ -8,7 +8,8 @@ import (
"github.com/miekg/dns"
"golang.org/x/net/context"
"k8s.io/client-go/1.5/pkg/api"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/client-go/pkg/api/v1"
)
var dnsTestCases = []test.Case{
@ -193,23 +194,22 @@ type APIConnServeTest struct{}
func (APIConnServeTest) Run() { return }
func (APIConnServeTest) Stop() error { return nil }
func (APIConnServeTest) PodIndex(string) []interface{} {
a := make([]interface{}, 1)
a[0] = &api.Pod{
ObjectMeta: api.ObjectMeta{
func (APIConnServeTest) PodIndex(string) []*api.Pod {
a := []*api.Pod{{
ObjectMeta: meta.ObjectMeta{
Namespace: "podns",
},
Status: api.PodStatus{
PodIP: "10.240.0.1", // Remote IP set in test.ResponseWriter
},
}
}}
return a
}
func (APIConnServeTest) ServiceList() []*api.Service {
svcs := []*api.Service{
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "svc1",
Namespace: "testns",
},
@ -223,7 +223,7 @@ func (APIConnServeTest) ServiceList() []*api.Service {
},
},
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "hdls1",
Namespace: "testns",
},
@ -232,7 +232,7 @@ func (APIConnServeTest) ServiceList() []*api.Service {
},
},
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "external",
Namespace: "testns",
},
@ -247,14 +247,12 @@ func (APIConnServeTest) ServiceList() []*api.Service {
},
}
return svcs
}
func (APIConnServeTest) EndpointsList() api.EndpointsList {
func (APIConnServeTest) EndpointsList() []*api.Endpoints {
n := "test.node.foo.bar"
return api.EndpointsList{
Items: []api.Endpoints{
eps := []*api.Endpoints{
{
Subsets: []api.EndpointSubset{
{
@ -273,7 +271,7 @@ func (APIConnServeTest) EndpointsList() api.EndpointsList {
},
},
},
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "svc1",
Namespace: "testns",
},
@ -295,7 +293,7 @@ func (APIConnServeTest) EndpointsList() api.EndpointsList {
},
},
},
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "hdls1",
Namespace: "testns",
},
@ -317,7 +315,7 @@ func (APIConnServeTest) EndpointsList() api.EndpointsList {
},
},
},
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "hdls1",
Namespace: "testns",
},
@ -334,13 +332,13 @@ func (APIConnServeTest) EndpointsList() api.EndpointsList {
},
},
},
},
}
return eps
}
func (APIConnServeTest) GetNodeByName(name string) (api.Node, error) {
return api.Node{
ObjectMeta: api.ObjectMeta{
func (APIConnServeTest) GetNodeByName(name string) (*api.Node, error) {
return &api.Node{
ObjectMeta: meta.ObjectMeta{
Name: "test.node.foo.bar",
},
}, nil

View file

@ -17,13 +17,13 @@ import (
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api"
unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned"
"k8s.io/client-go/1.5/pkg/labels"
"k8s.io/client-go/1.5/rest"
"k8s.io/client-go/1.5/tools/clientcmd"
clientcmdapi "k8s.io/client-go/1.5/tools/clientcmd/api"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
api "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// Kubernetes implements a plugin that connects to a Kubernetes cluster.
@ -76,7 +76,6 @@ var (
errNoItems = errors.New("no items found")
errNsNotExposed = errors.New("namespace is not exposed")
errInvalidRequest = errors.New("invalid query name")
errAPIBadPodType = errors.New("expected type *api.Pod")
errPodsDisabled = errors.New("pod records disabled")
)
@ -152,14 +151,17 @@ func (k *Kubernetes) getClientConfig() (*rest.Config, error) {
clusterinfo := clientcmdapi.Cluster{}
authinfo := clientcmdapi.AuthInfo{}
// Connect to API from in cluster
if len(k.APIServerList) == 0 {
cc, err := rest.InClusterConfig()
if err != nil {
return nil, err
}
cc.ContentType = "application/vnd.kubernetes.protobuf"
return cc, err
}
// Connect to API from out of cluster
endpoint := k.APIServerList[0]
if len(k.APIServerList) > 1 {
// Use a random port for api proxy, will get the value later through listener.Addr()
@ -230,7 +232,10 @@ func (k *Kubernetes) getClientConfig() (*rest.Config, error) {
overrides.AuthInfo = authinfo
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
return clientConfig.ClientConfig()
cc, err := clientConfig.ClientConfig()
cc.ContentType = "application/vnd.kubernetes.protobuf"
return cc, err
}
// initKubeCache initializes a new Kubernetes cache.
@ -248,7 +253,7 @@ func (k *Kubernetes) initKubeCache(opts dnsControlOpts) (err error) {
if opts.labelSelector != nil {
var selector labels.Selector
selector, err = unversionedapi.LabelSelectorAsSelector(opts.labelSelector)
selector, err = meta.LabelSelectorAsSelector(opts.labelSelector)
if err != nil {
return fmt.Errorf("unable to create Selector for LabelSelector '%s': %q", opts.labelSelector, err)
}
@ -317,13 +322,7 @@ func (k *Kubernetes) findPods(r recordRequest, zone string) (pods []msg.Service,
}
// PodModeVerified
objList := k.APIConn.PodIndex(ip)
for _, o := range objList {
p, ok := o.(*api.Pod)
if !ok {
return nil, errAPIBadPodType
}
for _, p := range k.APIConn.PodIndex(ip) {
// If namespace has a wildcard, filter results against Corefile namespace list.
if wildcard(namespace) && !k.namespaceExposed(p.Namespace) {
continue
@ -341,11 +340,11 @@ func (k *Kubernetes) findPods(r recordRequest, zone string) (pods []msg.Service,
// findServices returns the services matching r from the cache.
func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.Service, err error) {
serviceList := k.APIConn.ServiceList()
zonePath := msg.Path(zone, "coredns")
err = errNoItems // Set to errNoItems to signal really nothing found, gets reset when name is matched.
for _, svc := range serviceList {
for _, svc := range k.APIConn.ServiceList() {
if !(match(r.namespace, svc.Namespace) && match(r.service, svc.Name)) {
continue
}
@ -358,8 +357,8 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
// Endpoint query or headless service
if svc.Spec.ClusterIP == api.ClusterIPNone || r.endpoint != "" {
endpointsList := k.APIConn.EndpointsList()
for _, ep := range endpointsList.Items {
for _, ep := range k.APIConn.EndpointsList() {
if ep.ObjectMeta.Name != svc.Name || ep.ObjectMeta.Namespace != svc.Namespace {
continue
}

View file

@ -10,7 +10,7 @@ import (
"golang.org/x/net/context"
)
var kubeApexCases = [](test.Case){
var kubeApexCases = []test.Case{
{
Qname: "cluster.local.", Qtype: dns.TypeSOA,
Rcode: dns.RcodeSuccess,

View file

@ -7,7 +7,8 @@ import (
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"k8s.io/client-go/1.5/pkg/api"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/client-go/pkg/api/v1"
)
func TestWildcard(t *testing.T) {
@ -52,12 +53,12 @@ type APIConnServiceTest struct{}
func (APIConnServiceTest) Run() { return }
func (APIConnServiceTest) Stop() error { return nil }
func (APIConnServiceTest) PodIndex(string) []interface{} { return nil }
func (APIConnServiceTest) PodIndex(string) []*api.Pod { return nil }
func (APIConnServiceTest) ServiceList() []*api.Service {
svcs := []*api.Service{
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "svc1",
Namespace: "testns",
},
@ -71,7 +72,7 @@ func (APIConnServiceTest) ServiceList() []*api.Service {
},
},
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "hdls1",
Namespace: "testns",
},
@ -80,7 +81,7 @@ func (APIConnServiceTest) ServiceList() []*api.Service {
},
},
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "external",
Namespace: "testns",
},
@ -97,11 +98,10 @@ func (APIConnServiceTest) ServiceList() []*api.Service {
return svcs
}
func (APIConnServiceTest) EndpointsList() api.EndpointsList {
func (APIConnServiceTest) EndpointsList() []*api.Endpoints {
n := "test.node.foo.bar"
return api.EndpointsList{
Items: []api.Endpoints{
eps := []*api.Endpoints{
{
Subsets: []api.EndpointSubset{
{
@ -120,7 +120,7 @@ func (APIConnServiceTest) EndpointsList() api.EndpointsList {
},
},
},
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "svc1",
Namespace: "testns",
},
@ -142,7 +142,7 @@ func (APIConnServiceTest) EndpointsList() api.EndpointsList {
},
},
},
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "hdls1",
Namespace: "testns",
},
@ -164,7 +164,7 @@ func (APIConnServiceTest) EndpointsList() api.EndpointsList {
},
},
},
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "hdls1",
Namespace: "testns",
},
@ -181,13 +181,13 @@ func (APIConnServiceTest) EndpointsList() api.EndpointsList {
},
},
},
},
}
return eps
}
func (APIConnServiceTest) GetNodeByName(name string) (api.Node, error) {
return api.Node{
ObjectMeta: api.ObjectMeta{
func (APIConnServiceTest) GetNodeByName(name string) (*api.Node, error) {
return &api.Node{
ObjectMeta: meta.ObjectMeta{
Name: "test.node.foo.bar",
},
}, nil

View file

@ -1,6 +1,8 @@
package kubernetes
import "net"
import (
"net"
)
func localPodIP() net.IP {
addrs, err := net.InterfaceAddrs()
@ -26,8 +28,7 @@ func (k *Kubernetes) localNodeName() string {
}
// Find endpoint matching localIP
endpointsList := k.APIConn.EndpointsList()
for _, ep := range endpointsList.Items {
for _, ep := range k.APIConn.EndpointsList() {
for _, eps := range ep.Subsets {
for _, addr := range eps.Addresses {
if localIP.Equal(net.ParseIP(addr.IP)) {

View file

@ -5,7 +5,7 @@ import (
"strings"
"github.com/miekg/dns"
"k8s.io/client-go/1.5/pkg/api"
api "k8s.io/client-go/pkg/api/v1"
)
func isDefaultNS(name, zone string) bool {
@ -20,12 +20,10 @@ func (k *Kubernetes) nsAddr() *dns.A {
rr := new(dns.A)
localIP := k.interfaceAddrsFunc()
endpointsList := k.APIConn.EndpointsList()
rr.A = localIP
FindEndpoint:
for _, ep := range endpointsList.Items {
for _, ep := range k.APIConn.EndpointsList() {
for _, eps := range ep.Subsets {
for _, addr := range eps.Addresses {
if localIP.Equal(net.ParseIP(addr.IP)) {
@ -42,11 +40,9 @@ FindEndpoint:
rr.A = localIP
return rr
}
// Find service to get ClusterIP
serviceList := k.APIConn.ServiceList()
FindService:
for _, svc := range serviceList {
for _, svc := range k.APIConn.ServiceList() {
if svcName == svc.Name && svcNamespace == svc.Namespace {
if svc.Spec.ClusterIP == api.ClusterIPNone {
rr.A = localIP

View file

@ -3,33 +3,33 @@ package kubernetes
import (
"testing"
"k8s.io/client-go/1.5/pkg/api"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/client-go/pkg/api/v1"
)
type APIConnTest struct{}
func (APIConnTest) Run() { return }
func (APIConnTest) Stop() error { return nil }
func (APIConnTest) PodIndex(string) []interface{} { return nil }
func (APIConnTest) PodIndex(string) []*api.Pod { return nil }
func (APIConnTest) ServiceList() []*api.Service {
svc := api.Service{
ObjectMeta: api.ObjectMeta{
svcs := []*api.Service{
{
ObjectMeta: meta.ObjectMeta{
Name: "dns-service",
Namespace: "kube-system",
},
Spec: api.ServiceSpec{
ClusterIP: "10.0.0.111",
},
},
}
return []*api.Service{&svc}
return svcs
}
func (APIConnTest) EndpointsList() api.EndpointsList {
return api.EndpointsList{
Items: []api.Endpoints{
func (APIConnTest) EndpointsList() []*api.Endpoints {
eps := []*api.Endpoints{
{
Subsets: []api.EndpointSubset{
{
@ -40,16 +40,16 @@ func (APIConnTest) EndpointsList() api.EndpointsList {
},
},
},
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "dns-service",
Namespace: "kube-system",
},
},
},
}
return eps
}
func (APIConnTest) GetNodeByName(name string) (api.Node, error) { return api.Node{}, nil }
func (APIConnTest) GetNodeByName(name string) (*api.Node, error) { return &api.Node{}, nil }
func TestNsAddr(t *testing.T) {

View file

@ -25,9 +25,7 @@ func (k *Kubernetes) Reverse(state request.Request, exact bool, opt plugin.Optio
// If a service cluster ip does not match, it checks all endpoints
func (k *Kubernetes) serviceRecordForIP(ip, name string) []msg.Service {
// First check services with cluster ips
svcList := k.APIConn.ServiceList()
for _, service := range svcList {
for _, service := range k.APIConn.ServiceList() {
if (len(k.Namespaces) > 0) && !k.namespaceExposed(service.Namespace) {
continue
}
@ -37,8 +35,7 @@ func (k *Kubernetes) serviceRecordForIP(ip, name string) []msg.Service {
}
}
// If no cluster ips match, search endpoints
epList := k.APIConn.EndpointsList()
for _, ep := range epList.Items {
for _, ep := range k.APIConn.EndpointsList() {
if (len(k.Namespaces) > 0) && !k.namespaceExposed(ep.ObjectMeta.Namespace) {
continue
}

View file

@ -8,19 +8,20 @@ import (
"github.com/miekg/dns"
"golang.org/x/net/context"
"k8s.io/client-go/1.5/pkg/api"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/client-go/pkg/api/v1"
)
type APIConnReverseTest struct{}
func (APIConnReverseTest) Run() { return }
func (APIConnReverseTest) Stop() error { return nil }
func (APIConnReverseTest) PodIndex(string) []interface{} { return nil }
func (APIConnReverseTest) PodIndex(string) []*api.Pod { return nil }
func (APIConnReverseTest) ServiceList() []*api.Service {
svcs := []*api.Service{
{
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "svc1",
Namespace: "testns",
},
@ -37,9 +38,8 @@ func (APIConnReverseTest) ServiceList() []*api.Service {
return svcs
}
func (APIConnReverseTest) EndpointsList() api.EndpointsList {
return api.EndpointsList{
Items: []api.Endpoints{
func (APIConnReverseTest) EndpointsList() []*api.Endpoints {
eps := []*api.Endpoints{
{
Subsets: []api.EndpointSubset{
{
@ -58,18 +58,18 @@ func (APIConnReverseTest) EndpointsList() api.EndpointsList {
},
},
},
ObjectMeta: api.ObjectMeta{
ObjectMeta: meta.ObjectMeta{
Name: "svc1",
Namespace: "testns",
},
},
},
}
return eps
}
func (APIConnReverseTest) GetNodeByName(name string) (api.Node, error) {
return api.Node{
ObjectMeta: api.ObjectMeta{
func (APIConnReverseTest) GetNodeByName(name string) (*api.Node, error) {
return &api.Node{
ObjectMeta: meta.ObjectMeta{
Name: "test.node.foo.bar",
},
}, nil

View file

@ -11,10 +11,10 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/proxy"
"github.com/miekg/dns"
"github.com/mholt/caddy"
unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned"
"github.com/miekg/dns"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func init() {
@ -150,7 +150,7 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, dnsControlOpts, error) {
args := c.RemainingArgs()
if len(args) > 0 {
labelSelectorString := strings.Join(args, " ")
ls, err := unversionedapi.ParseToLabelSelector(labelSelectorString)
ls, err := meta.ParseToLabelSelector(labelSelectorString)
if err != nil {
return nil, opts, fmt.Errorf("unable to parse label selector value: '%v': %v", labelSelectorString, err)
}

View file

@ -6,7 +6,7 @@ import (
"time"
"github.com/mholt/caddy"
"k8s.io/client-go/1.5/pkg/api/unversioned"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestKubernetesParse(t *testing.T) {
@ -431,7 +431,7 @@ func TestKubernetesParse(t *testing.T) {
// Labels
if opts.labelSelector != nil {
foundLabelSelectorString := unversioned.FormatLabelSelector(opts.labelSelector)
foundLabelSelectorString := meta.FormatLabelSelector(opts.labelSelector)
if foundLabelSelectorString != test.expectedLabelSelector {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with label selector '%s'. Instead found selector '%s' for input '%s'", i, test.expectedLabelSelector, foundLabelSelectorString, test.input)
}

View file

@ -1,16 +0,0 @@
sudo: false
language: go
go:
- 1.6
- 1.7
- 1.8
install:
- go get -v cloud.google.com/go/...
script:
- openssl aes-256-cbc -K $encrypted_a8b3f4fc85f4_key -iv $encrypted_a8b3f4fc85f4_iv -in key.json.enc -out key.json -d
- GCLOUD_TESTS_GOLANG_PROJECT_ID="dulcet-port-762" GCLOUD_TESTS_GOLANG_KEY="$(pwd)/key.json"
./run-tests.sh $TRAVIS_COMMIT
env:
matrix:
# The GCLOUD_TESTS_API_KEY environment variable.
secure: VdldogUOoubQ60LhuHJ+g/aJoBiujkSkWEWl79Zb8cvQorcQbxISS+JsOOp4QkUOU4WwaHAm8/3pIH1QMWOR6O78DaLmDKi5Q4RpkVdCpUXy+OAfQaZIcBsispMrjxLXnqFjo9ELnrArfjoeCTzaX0QTCfwQwVmigC8rR30JBKI=

15
vendor/cloud.google.com/go/AUTHORS generated vendored
View file

@ -1,15 +0,0 @@
# This is the official list of cloud authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as:
# Name or Organization <email address>
# The email address is not required for organizations.
Filippo Valsorda <hi@filippo.io>
Google Inc.
Ingo Oeser <nightlyone@googlemail.com>
Palm Stone Games, Inc.
Paweł Knap <pawelknap88@gmail.com>
Péter Szilágyi <peterke@gmail.com>
Tyler Treat <ttreat31@gmail.com>

View file

@ -1,138 +0,0 @@
# Contributing
1. Sign one of the contributor license agreements below.
1. `go get golang.org/x/review/git-codereview` to install the code reviewing tool.
1. You will need to ensure that your `GOBIN` directory (by default
`$GOPATH/bin`) is in your `PATH` so that git can find the command.
1. If you would like, you may want to set up aliases for git-codereview,
such that `git codereview change` becomes `git change`. See the
[godoc](https://godoc.org/golang.org/x/review/git-codereview) for details.
1. Should you run into issues with the git-codereview tool, please note
that all error messages will assume that you have set up these
aliases.
1. Get the cloud package by running `go get -d cloud.google.com/go`.
1. If you have already checked out the source, make sure that the remote git
origin is https://code.googlesource.com/gocloud:
git remote set-url origin https://code.googlesource.com/gocloud
1. Make sure your auth is configured correctly by visiting
https://code.googlesource.com, clicking "Generate Password", and following
the directions.
1. Make changes and create a change by running `git codereview change <name>`,
provide a commit message, and use `git codereview mail` to create a Gerrit CL.
1. Keep amending to the change with `git codereview change` and mail as your receive
feedback. Each new mailed amendment will create a new patch set for your change in Gerrit.
## Integration Tests
In addition to the unit tests, you may run the integration test suite.
To run the integrations tests, creating and configuration of a project in the
Google Developers Console is required.
After creating a project, you must [create a service account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount).
Ensure the project-level **Owner** [IAM role](console.cloud.google.com/iam-admin/iam/project)
(or **Editor** and **Logs Configuration Writer** roles) are added to the
service account.
Once you create a project, set the following environment variables to be able to
run the against the actual APIs.
- **GCLOUD_TESTS_GOLANG_PROJECT_ID**: Developers Console project's ID (e.g. bamboo-shift-455)
- **GCLOUD_TESTS_GOLANG_KEY**: The path to the JSON key file.
- **GCLOUD_TESTS_API_KEY**: Your API key.
Install the [gcloud command-line tool][gcloudcli] to your machine and use it
to create some resources used in integration tests.
From the project's root directory:
``` sh
# Set the default project in your env.
$ gcloud config set project $GCLOUD_TESTS_GOLANG_PROJECT_ID
# Authenticate the gcloud tool with your account.
$ gcloud auth login
# Create the indexes used in the datastore integration tests.
$ gcloud preview datastore create-indexes datastore/testdata/index.yaml
# Create a Google Cloud storage bucket with the same name as your test project,
# and with the Stackdriver Logging service account as owner, for the sink
# integration tests in logging.
$ gsutil mb gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
$ gsutil acl ch -g cloud-logs@google.com:O gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
# Create a Spanner instance for the spanner integration tests.
$ gcloud beta spanner instances create go-integration-test --config regional-us-central1 --nodes 1 --description 'Instance for go client test'
# NOTE: Spanner instances are priced by the node-hour, so you may want to delete
# the instance after testing with 'gcloud beta spanner instances delete'.
```
Once you've set the environment variables, you can run the integration tests by
running:
``` sh
$ go test -v cloud.google.com/go/...
```
## Contributor License Agreements
Before we can accept your pull requests you'll need to sign a Contributor
License Agreement (CLA):
- **If you are an individual writing original source code** and **you own the
- intellectual property**, then you'll need to sign an [individual CLA][indvcla].
- **If you work for a company that wants to allow you to contribute your work**,
then you'll need to sign a [corporate CLA][corpcla].
You can sign these electronically (just scroll to the bottom). After that,
we'll be able to accept your pull requests.
## Contributor Code of Conduct
As contributors and maintainers of this project,
and in the interest of fostering an open and welcoming community,
we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project
a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information,
such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct.
By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project.
Project maintainers who do not follow or enforce the Code of Conduct
may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by opening an issue
or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/
[indvcla]: https://developers.google.com/open-source/cla/individual
[corpcla]: https://developers.google.com/open-source/cla/corporate

View file

@ -1,37 +0,0 @@
# People who have agreed to one of the CLAs and can contribute patches.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# https://developers.google.com/open-source/cla/individual
# https://developers.google.com/open-source/cla/corporate
#
# Names should be added to this file as:
# Name <email address>
# Keep the list alphabetically sorted.
Alexis Hunt <lexer@google.com>
Andreas Litt <andreas.litt@gmail.com>
Andrew Gerrand <adg@golang.org>
Brad Fitzpatrick <bradfitz@golang.org>
Burcu Dogan <jbd@google.com>
Dave Day <djd@golang.org>
David Sansome <me@davidsansome.com>
David Symonds <dsymonds@golang.org>
Filippo Valsorda <hi@filippo.io>
Glenn Lewis <gmlewis@google.com>
Ingo Oeser <nightlyone@googlemail.com>
Johan Euphrosine <proppy@google.com>
Jonathan Amsterdam <jba@google.com>
Luna Duclos <luna.duclos@palmstonegames.com>
Magnus Hiie <magnus.hiie@gmail.com>
Michael McGreevy <mcgreevy@golang.org>
Omar Jarjur <ojarjur@google.com>
Paweł Knap <pawelknap88@gmail.com>
Péter Szilágyi <peterke@gmail.com>
Sarah Adams <shadams@google.com>
Thanatat Tamtan <acoshift@gmail.com>
Toby Burress <kurin@google.com>
Tuo Shan <shantuo@google.com>
Tyler Treat <ttreat31@gmail.com>

View file

@ -1,54 +0,0 @@
# Code Changes
## v0.10.0
- pubsub: Replace
```
sub.ModifyPushConfig(ctx, pubsub.PushConfig{Endpoint: "https://example.com/push"})
```
with
```
sub.Update(ctx, pubsub.SubscriptionConfigToUpdate{
PushConfig: &pubsub.PushConfig{Endpoint: "https://example.com/push"},
})
```
- trace: traceGRPCServerInterceptor will be provided from *trace.Client.
Given an initialized `*trace.Client` named `tc`, instead of
```
s := grpc.NewServer(grpc.UnaryInterceptor(trace.GRPCServerInterceptor(tc)))
```
write
```
s := grpc.NewServer(grpc.UnaryInterceptor(tc.GRPCServerInterceptor()))
```
- trace trace.GRPCClientInterceptor will also provided from *trace.Client.
Instead of
```
conn, err := grpc.Dial(srv.Addr, grpc.WithUnaryInterceptor(trace.GRPCClientInterceptor()))
```
write
```
conn, err := grpc.Dial(srv.Addr, grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor()))
```
- trace: We removed the deprecated `trace.EnableGRPCTracing`. Use the gRPC
interceptor as a dial option as shown below when initializing Cloud package
clients:
```
c, err := pubsub.NewClient(ctx, "project-id", option.WithGRPCDialOption(grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor())))
if err != nil {
...
}
```

482
vendor/cloud.google.com/go/README.md generated vendored
View file

@ -1,482 +0,0 @@
# Google Cloud Client Libraries for Go
[![GoDoc](https://godoc.org/cloud.google.com/go?status.svg)](https://godoc.org/cloud.google.com/go)
Go packages for [Google Cloud Platform](https://cloud.google.com) services.
``` go
import "cloud.google.com/go"
```
To install the packages on your system,
```
$ go get -u cloud.google.com/go/...
```
**NOTE:** Some of these packages are under development, and may occasionally
make backwards-incompatible changes.
**NOTE:** Github repo is a mirror of [https://code.googlesource.com/gocloud](https://code.googlesource.com/gocloud).
* [News](#news)
* [Supported APIs](#supported-apis)
* [Go Versions Supported](#go-versions-supported)
* [Authorization](#authorization)
* [Cloud Datastore](#cloud-datastore-)
* [Cloud Storage](#cloud-storage-)
* [Cloud Pub/Sub](#cloud-pub-sub-)
* [Cloud BigQuery](#cloud-bigquery-)
* [Stackdriver Logging](#stackdriver-logging-)
* [Cloud Spanner](#cloud-spanner-)
## News
_August 22, 2017+
*v0.12.0*
- pubsub: Subscription.Receive now uses streaming pull.
- pubsub: add Client.TopicInProject to access topics in a different project
than the client.
- errors: renamed errorreporting. The errors package will be removed shortly.
- datastore: improved retry behavior.
- bigquery: support updates to dataset metadata, with etags.
- bigquery: add etag support to Table.Update (BREAKING: etag argument added).
- bigquery: generate all job IDs on the client.
- storage: support bucket lifecycle configurations.
_July 31, 2017_
*v0.11.0*
- Clients for spanner, pubsub and video are now in beta.
- New client for DLP.
- spanner: performance and testing improvements.
- storage: requester-pays buckets are supported.
- storage, profiler, bigtable, bigquery: bug fixes and other minor improvements.
- pubsub: bug fixes and other minor improvements
_June 17, 2017_
*v0.10.0*
- pubsub: Subscription.ModifyPushConfig replaced with Subscription.Update.
- pubsub: Subscription.Receive now runs concurrently for higher throughput.
- vision: cloud.google.com/go/vision is deprecated. Use
cloud.google.com/go/vision/apiv1 instead.
- translation: now stable.
- trace: several changes to the surface. See the link below.
[Code changes required from v0.9.0.](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/MIGRATION.md)
[Older news](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/old-news.md)
## Supported APIs
Google API | Status | Package
---------------------------------|--------------|-----------------------------------------------------------
[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`][cloud-datastore-ref]
[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`][cloud-storage-ref]
[Bigtable][cloud-bigtable] | beta | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref]
[BigQuery][cloud-bigquery] | beta | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref]
[Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`][cloud-logging-ref]
[Monitoring][cloud-monitoring] | alpha | [`cloud.google.com/go/monitoring/apiv3`][cloud-monitoring-ref]
[Pub/Sub][cloud-pubsub] | beta | [`cloud.google.com/go/pubsub`][cloud-pubsub-ref]
[Vision][cloud-vision] | beta | [`cloud.google.com/go/vision/apiv1`][cloud-vision-ref]
[Language][cloud-language] | beta | [`cloud.google.com/go/language/apiv1`][cloud-language-ref]
[Speech][cloud-speech] | beta | [`cloud.google.com/go/speech/apiv1`][cloud-speech-ref]
[Spanner][cloud-spanner] | beta | [`cloud.google.com/go/spanner`][cloud-spanner-ref]
[Translation][cloud-translation] | stable | [`cloud.google.com/go/translate`][cloud-translation-ref]
[Trace][cloud-trace] | alpha | [`cloud.google.com/go/trace`][cloud-trace-ref]
[Video Intelligence][cloud-video]| beta | [`cloud.google.com/go/videointelligence/apiv1beta1`][cloud-video-ref]
[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`][cloud-errors-ref]
> **Alpha status**: the API is still being actively developed. As a
> result, it might change in backward-incompatible ways and is not recommended
> for production use.
>
> **Beta status**: the API is largely complete, but still has outstanding
> features and bugs to be addressed. There may be minor backwards-incompatible
> changes where necessary.
>
> **Stable status**: the API is mature and ready for production use. We will
> continue addressing bugs and feature requests.
Documentation and examples are available at
https://godoc.org/cloud.google.com/go
Visit or join the
[google-api-go-announce group](https://groups.google.com/forum/#!forum/google-api-go-announce)
for updates on these packages.
## Go Versions Supported
We support the two most recent major versions of Go. If Google App Engine uses
an older version, we support that as well. You can see which versions are
currently supported by looking at the lines following `go:` in
[`.travis.yml`](.travis.yml).
## Authorization
By default, each API will use [Google Application Default Credentials][default-creds]
for authorization credentials used in calling the API endpoints. This will allow your
application to run in many environments without requiring explicit configuration.
[snip]:# (auth)
```go
client, err := storage.NewClient(ctx)
```
To authorize using a
[JSON key file](https://cloud.google.com/iam/docs/managing-service-account-keys),
pass
[`option.WithServiceAccountFile`](https://godoc.org/google.golang.org/api/option#WithServiceAccountFile)
to the `NewClient` function of the desired package. For example:
[snip]:# (auth-JSON)
```go
client, err := storage.NewClient(ctx, option.WithServiceAccountFile("path/to/keyfile.json"))
```
You can exert more control over authorization by using the
[`golang.org/x/oauth2`](https://godoc.org/golang.org/x/oauth2) package to
create an `oauth2.TokenSource`. Then pass
[`option.WithTokenSource`](https://godoc.org/google.golang.org/api/option#WithTokenSource)
to the `NewClient` function:
[snip]:# (auth-ts)
```go
tokenSource := ...
client, err := storage.NewClient(ctx, option.WithTokenSource(tokenSource))
```
## Cloud Datastore [![GoDoc](https://godoc.org/cloud.google.com/go/datastore?status.svg)](https://godoc.org/cloud.google.com/go/datastore)
- [About Cloud Datastore][cloud-datastore]
- [Activating the API for your project][cloud-datastore-activation]
- [API documentation][cloud-datastore-docs]
- [Go client documentation](https://godoc.org/cloud.google.com/go/datastore)
- [Complete sample program](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/datastore/tasks)
### Example Usage
First create a `datastore.Client` to use throughout your application:
[snip]:# (datastore-1)
```go
client, err := datastore.NewClient(ctx, "my-project-id")
if err != nil {
log.Fatal(err)
}
```
Then use that client to interact with the API:
[snip]:# (datastore-2)
```go
type Post struct {
Title string
Body string `datastore:",noindex"`
PublishedAt time.Time
}
keys := []*datastore.Key{
datastore.NameKey("Post", "post1", nil),
datastore.NameKey("Post", "post2", nil),
}
posts := []*Post{
{Title: "Post 1", Body: "...", PublishedAt: time.Now()},
{Title: "Post 2", Body: "...", PublishedAt: time.Now()},
}
if _, err := client.PutMulti(ctx, keys, posts); err != nil {
log.Fatal(err)
}
```
## Cloud Storage [![GoDoc](https://godoc.org/cloud.google.com/go/storage?status.svg)](https://godoc.org/cloud.google.com/go/storage)
- [About Cloud Storage][cloud-storage]
- [API documentation][cloud-storage-docs]
- [Go client documentation](https://godoc.org/cloud.google.com/go/storage)
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/storage)
### Example Usage
First create a `storage.Client` to use throughout your application:
[snip]:# (storage-1)
```go
client, err := storage.NewClient(ctx)
if err != nil {
log.Fatal(err)
}
```
[snip]:# (storage-2)
```go
// Read the object1 from bucket.
rc, err := client.Bucket("bucket").Object("object1").NewReader(ctx)
if err != nil {
log.Fatal(err)
}
defer rc.Close()
body, err := ioutil.ReadAll(rc)
if err != nil {
log.Fatal(err)
}
```
## Cloud Pub/Sub [![GoDoc](https://godoc.org/cloud.google.com/go/pubsub?status.svg)](https://godoc.org/cloud.google.com/go/pubsub)
- [About Cloud Pubsub][cloud-pubsub]
- [API documentation][cloud-pubsub-docs]
- [Go client documentation](https://godoc.org/cloud.google.com/go/pubsub)
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/pubsub)
### Example Usage
First create a `pubsub.Client` to use throughout your application:
[snip]:# (pubsub-1)
```go
client, err := pubsub.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
```
Then use the client to publish and subscribe:
[snip]:# (pubsub-2)
```go
// Publish "hello world" on topic1.
topic := client.Topic("topic1")
res := topic.Publish(ctx, &pubsub.Message{
Data: []byte("hello world"),
})
// The publish happens asynchronously.
// Later, you can get the result from res:
...
msgID, err := res.Get(ctx)
if err != nil {
log.Fatal(err)
}
// Use a callback to receive messages via subscription1.
sub := client.Subscription("subscription1")
err = sub.Receive(ctx, func(ctx context.Context, m *pubsub.Message) {
fmt.Println(m.Data)
m.Ack() // Acknowledge that we've consumed the message.
})
if err != nil {
log.Println(err)
}
```
## Cloud BigQuery [![GoDoc](https://godoc.org/cloud.google.com/go/bigquery?status.svg)](https://godoc.org/cloud.google.com/go/bigquery)
- [About Cloud BigQuery][cloud-bigquery]
- [API documentation][cloud-bigquery-docs]
- [Go client documentation][cloud-bigquery-ref]
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/bigquery)
### Example Usage
First create a `bigquery.Client` to use throughout your application:
[snip]:# (bq-1)
```go
c, err := bigquery.NewClient(ctx, "my-project-ID")
if err != nil {
// TODO: Handle error.
}
```
Then use that client to interact with the API:
[snip]:# (bq-2)
```go
// Construct a query.
q := c.Query(`
SELECT year, SUM(number)
FROM [bigquery-public-data:usa_names.usa_1910_2013]
WHERE name = "William"
GROUP BY year
ORDER BY year
`)
// Execute the query.
it, err := q.Read(ctx)
if err != nil {
// TODO: Handle error.
}
// Iterate through the results.
for {
var values []bigquery.Value
err := it.Next(&values)
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
fmt.Println(values)
}
```
## Stackdriver Logging [![GoDoc](https://godoc.org/cloud.google.com/go/logging?status.svg)](https://godoc.org/cloud.google.com/go/logging)
- [About Stackdriver Logging][cloud-logging]
- [API documentation][cloud-logging-docs]
- [Go client documentation][cloud-logging-ref]
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/logging)
### Example Usage
First create a `logging.Client` to use throughout your application:
[snip]:# (logging-1)
```go
ctx := context.Background()
client, err := logging.NewClient(ctx, "my-project")
if err != nil {
// TODO: Handle error.
}
```
Usually, you'll want to add log entries to a buffer to be periodically flushed
(automatically and asynchronously) to the Stackdriver Logging service.
[snip]:# (logging-2)
```go
logger := client.Logger("my-log")
logger.Log(logging.Entry{Payload: "something happened!"})
```
Close your client before your program exits, to flush any buffered log entries.
[snip]:# (logging-3)
```go
err = client.Close()
if err != nil {
// TODO: Handle error.
}
```
## Cloud Spanner [![GoDoc](https://godoc.org/cloud.google.com/go/spanner?status.svg)](https://godoc.org/cloud.google.com/go/spanner)
- [About Cloud Spanner][cloud-spanner]
- [API documentation][cloud-spanner-docs]
- [Go client documentation](https://godoc.org/cloud.google.com/go/spanner)
### Example Usage
First create a `spanner.Client` to use throughout your application:
[snip]:# (spanner-1)
```go
client, err := spanner.NewClient(ctx, "projects/P/instances/I/databases/D")
if err != nil {
log.Fatal(err)
}
```
[snip]:# (spanner-2)
```go
// Simple Reads And Writes
_, err = client.Apply(ctx, []*spanner.Mutation{
spanner.Insert("Users",
[]string{"name", "email"},
[]interface{}{"alice", "a@example.com"})})
if err != nil {
log.Fatal(err)
}
row, err := client.Single().ReadRow(ctx, "Users",
spanner.Key{"alice"}, []string{"email"})
if err != nil {
log.Fatal(err)
}
```
## Contributing
Contributions are welcome. Please, see the
[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md)
document for details. We're using Gerrit for our code reviews. Please don't open pull
requests against this repo, new pull requests will be automatically closed.
Please note that this project is released with a Contributor Code of Conduct.
By participating in this project you agree to abide by its terms.
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md#contributor-code-of-conduct)
for more information.
[cloud-datastore]: https://cloud.google.com/datastore/
[cloud-datastore-ref]: https://godoc.org/cloud.google.com/go/datastore
[cloud-datastore-docs]: https://cloud.google.com/datastore/docs
[cloud-datastore-activation]: https://cloud.google.com/datastore/docs/activate
[cloud-pubsub]: https://cloud.google.com/pubsub/
[cloud-pubsub-ref]: https://godoc.org/cloud.google.com/go/pubsub
[cloud-pubsub-docs]: https://cloud.google.com/pubsub/docs
[cloud-storage]: https://cloud.google.com/storage/
[cloud-storage-ref]: https://godoc.org/cloud.google.com/go/storage
[cloud-storage-docs]: https://cloud.google.com/storage/docs
[cloud-storage-create-bucket]: https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets
[cloud-bigtable]: https://cloud.google.com/bigtable/
[cloud-bigtable-ref]: https://godoc.org/cloud.google.com/go/bigtable
[cloud-bigquery]: https://cloud.google.com/bigquery/
[cloud-bigquery-docs]: https://cloud.google.com/bigquery/docs
[cloud-bigquery-ref]: https://godoc.org/cloud.google.com/go/bigquery
[cloud-logging]: https://cloud.google.com/logging/
[cloud-logging-docs]: https://cloud.google.com/logging/docs
[cloud-logging-ref]: https://godoc.org/cloud.google.com/go/logging
[cloud-monitoring]: https://cloud.google.com/monitoring/
[cloud-monitoring-ref]: https://godoc.org/cloud.google.com/go/monitoring/apiv3
[cloud-vision]: https://cloud.google.com/vision
[cloud-vision-ref]: https://godoc.org/cloud.google.com/go/vision/apiv1
[cloud-language]: https://cloud.google.com/natural-language
[cloud-language-ref]: https://godoc.org/cloud.google.com/go/language/apiv1
[cloud-speech]: https://cloud.google.com/speech
[cloud-speech-ref]: https://godoc.org/cloud.google.com/go/speech/apiv1
[cloud-spanner]: https://cloud.google.com/spanner/
[cloud-spanner-ref]: https://godoc.org/cloud.google.com/go/spanner
[cloud-spanner-docs]: https://cloud.google.com/spanner/docs
[cloud-translation]: https://cloud.google.com/translation
[cloud-translation-ref]: https://godoc.org/cloud.google.com/go/translation
[cloud-trace]: https://cloud.google.com/trace/
[cloud-trace-ref]: https://godoc.org/cloud.google.com/go/trace
[cloud-video]: https://cloud.google.com/video-intelligence/
[cloud-video-ref]: https://godoc.org/cloud.google.com/go/videointelligence/apiv1beta1
[cloud-errors]: https://cloud.google.com/error-reporting/
[cloud-errors-ref]: https://godoc.org/cloud.google.com/go/errorreporting
[default-creds]: https://developers.google.com/identity/protocols/application-default-credentials

View file

@ -1,32 +0,0 @@
# This file configures AppVeyor (http://www.appveyor.com),
# a Windows-based CI service similar to Travis.
# Identifier for this run
version: "{build}"
# Clone the repo into this path, which conforms to the standard
# Go workspace structure.
clone_folder: c:\gopath\src\cloud.google.com\go
environment:
GOPATH: c:\gopath
GCLOUD_TESTS_GOLANG_PROJECT_ID: dulcet-port-762
GCLOUD_TESTS_GOLANG_KEY: c:\gopath\src\cloud.google.com\go\key.json
KEYFILE_CONTENTS:
secure: IvRbDAhM2PIQqzVkjzJ4FjizUvoQ+c3vG/qhJQG+HlZ/L5KEkqLu+x6WjLrExrNMyGku4znB2jmbTrUW3Ob4sGG+R5vvqeQ3YMHCVIkw5CxY+/bUDkW5RZWsVbuCnNa/vKsWmCP+/sZW6ICe29yKJ2ZOb6QaauI4s9R6j+cqBbU9pumMGYFRb0Rw3uUU7DKmVFCy+NjTENZIlDP9rmjANgAzigowJJEb2Tg9sLlQKmQeKiBSRN8lKc5Nq60a+fIzHGKvql4eIitDDDpOpyHv15/Xr1BzFw2yDoiR4X1lng0u7q0X9RgX4VIYa6gT16NXBEmQgbuX8gh7SfPMp9RhiZD9sVUaV+yogEabYpyPnmUURo0hXwkctKaBkQlEmKvjHwF5dvbg8+yqGhwtjAgFNimXG3INrwQsfQsZskkQWanutbJf9xy50GyWWFZZdi0uT4oXP/b5P7aklPXKXsvrJKBh7RjEaqBrhi86IJwOjBspvoR4l2WmcQyxb2xzQS1pjbBJFQfYJJ8+JgsstTL8PBO9d4ybJC0li1Om1qnWxkaewvPxxuoHJ9LpRKof19yRYWBmhTXb2tTASKG/zslvl4fgG4DmQBS93WC7dsiGOhAraGw2eCTgd0lYZOhk1FjWl9TS80aktXxzH/7nTvem5ohm+eDl6O0wnTL4KXjQVNSQ1PyLn4lGRJ5MNGzBTRFWIr2API2rca4Fysyfh/UdmazPGlNbY9JPGqb9+F04QzLfqm+Zz/cHy59E7lOSMBlUI4KD6d6ZNNKNRH+/g9i+fSiyiXKugTfda8KBnWGyPwprxuWGYaiQUGUYOwJY5R6x5c4mjImAB310V+Wo33UbWFJiwxEDsiCNqW1meVkBzt2er26vh4qbgCUIQ3iM3gFPfHgy+QxkmIhic7Q1HYacQElt8AAP41M7cCKWCuZidegP37MBB//mjjiNt047ZSQEvB4tqsX/OvfbByVef+cbtVw9T0yjHvmCdPW1XrhyrCCgclu6oYYdbmc5D7BBDRbjjMWGv6YvceAbfGf6ukdB5PuV+TGEN/FoQ1QTRA6Aqf+3fLMg4mS4oyTfw5xyYNbv3qoyLPrp+BnxI53WB9p0hfMg4n9FD6NntBxjDq+Q3Lk/bjC/Y4MaRWdzbMzF9a0lgGfcw9DURlK5p7uGJC9vg34feNoQprxVEZRQ01cHLeob6eGkYm4HxSRx8JY39Mh+9wzJo+k/aIvFleNC3e35NOrkXr6wb5e42n2DwBdPqdNolTLtLFRglAL1LTpp27UjvjieWJAKfoDTR5CKl01sZqt0wPdLLcvsMj6CiPFmccUIOYeZMe86kLBD61Qa5F1EwkgO3Om2qSjW96FzL4skRc+BmU5RrHlAFSldR1wpUgtkUMv9vH5Cy+UJdcvpZ8KbmhZ2PsjF7ddJ1ve9RAw3cP325AyIMwZ77Ef1mgTM0NJze6eSW1qKlEsgt1FADPyeUu1NQTA2H2dueMPGlArWTSUgyWR9AdfpqouT7eg0JWI5w+yUZZC+/rPglYbt84oLmYpwuli0z8FyEQRPIc3EtkfWIv/yYgDr2TZ0N2KvGfpi/MAUWgxI1gleC2uKgEOEtuJthd3XZjF2NoE7IBqjQOINybcJOjyeB5vRLDY1FLuxYzdg1y1etkV4XQig/vje
install:
# Info for debugging.
- echo %PATH%
- go version
- go env
- go get -v -d -t ./...
# Provide a build script, or AppVeyor will call msbuild.
build_script:
- go install -v ./...
- echo %KEYFILE_CONTENTS% > %GCLOUD_TESTS_GOLANG_KEY%
test_script:
- go test -v ./...

View file

@ -1,49 +0,0 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cloud_test
import (
"cloud.google.com/go/datastore"
"golang.org/x/net/context"
"google.golang.org/api/option"
)
func Example_applicationDefaultCredentials() {
// Google Application Default Credentials is the recommended way to authorize
// and authenticate clients.
//
// See the following link on how to create and obtain Application Default Credentials:
// https://developers.google.com/identity/protocols/application-default-credentials.
client, err := datastore.NewClient(context.Background(), "project-id")
if err != nil {
// TODO: handle error.
}
_ = client // Use the client.
}
func Example_serviceAccountFile() {
// Use a JSON key file associated with a Google service account to
// authenticate and authorize. Service Account keys can be created and
// downloaded from https://console.developers.google.com/permissions/serviceaccounts.
//
// Note: This example uses the datastore client, but the same steps apply to
// the other client libraries underneath this package.
client, err := datastore.NewClient(context.Background(),
"project-id", option.WithServiceAccountFile("/path/to/service-account-key.json"))
if err != nil {
// TODO: handle error.
}
_ = client // Use the client.
}

View file

@ -1,437 +0,0 @@
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package metadata provides access to Google Compute Engine (GCE)
// metadata and API service accounts.
//
// This package is a wrapper around the GCE metadata service,
// as documented at https://developers.google.com/compute/docs/metadata.
package metadata // import "cloud.google.com/go/compute/metadata"
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"sync"
"time"
"golang.org/x/net/context"
"golang.org/x/net/context/ctxhttp"
)
const (
// metadataIP is the documented metadata server IP address.
metadataIP = "169.254.169.254"
// metadataHostEnv is the environment variable specifying the
// GCE metadata hostname. If empty, the default value of
// metadataIP ("169.254.169.254") is used instead.
// This is variable name is not defined by any spec, as far as
// I know; it was made up for the Go package.
metadataHostEnv = "GCE_METADATA_HOST"
userAgent = "gcloud-golang/0.1"
)
type cachedValue struct {
k string
trim bool
mu sync.Mutex
v string
}
var (
projID = &cachedValue{k: "project/project-id", trim: true}
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
instID = &cachedValue{k: "instance/id", trim: true}
)
var (
metaClient = &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
ResponseHeaderTimeout: 2 * time.Second,
},
}
subscribeClient = &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
},
}
)
// NotDefinedError is returned when requested metadata is not defined.
//
// The underlying string is the suffix after "/computeMetadata/v1/".
//
// This error is not returned if the value is defined to be the empty
// string.
type NotDefinedError string
func (suffix NotDefinedError) Error() string {
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
}
// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func Get(suffix string) (string, error) {
val, _, err := getETag(metaClient, suffix)
return val, err
}
// getETag returns a value from the metadata service as well as the associated
// ETag using the provided client. This func is otherwise equivalent to Get.
func getETag(client *http.Client, suffix string) (value, etag string, err error) {
// Using a fixed IP makes it very difficult to spoof the metadata service in
// a container, which is an important use-case for local testing of cloud
// deployments. To enable spoofing of the metadata service, the environment
// variable GCE_METADATA_HOST is first inspected to decide where metadata
// requests shall go.
host := os.Getenv(metadataHostEnv)
if host == "" {
// Using 169.254.169.254 instead of "metadata" here because Go
// binaries built with the "netgo" tag and without cgo won't
// know the search suffix for "metadata" is
// ".google.internal", and this IP address is documented as
// being stable anyway.
host = metadataIP
}
url := "http://" + host + "/computeMetadata/v1/" + suffix
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Metadata-Flavor", "Google")
req.Header.Set("User-Agent", userAgent)
res, err := client.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound {
return "", "", NotDefinedError(suffix)
}
if res.StatusCode != 200 {
return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
}
all, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
return string(all), res.Header.Get("Etag"), nil
}
func getTrimmed(suffix string) (s string, err error) {
s, err = Get(suffix)
s = strings.TrimSpace(s)
return
}
func (c *cachedValue) get() (v string, err error) {
defer c.mu.Unlock()
c.mu.Lock()
if c.v != "" {
return c.v, nil
}
if c.trim {
v, err = getTrimmed(c.k)
} else {
v, err = Get(c.k)
}
if err == nil {
c.v = v
}
return
}
var (
onGCEOnce sync.Once
onGCE bool
)
// OnGCE reports whether this process is running on Google Compute Engine.
func OnGCE() bool {
onGCEOnce.Do(initOnGCE)
return onGCE
}
func initOnGCE() {
onGCE = testOnGCE()
}
func testOnGCE() bool {
// The user explicitly said they're on GCE, so trust them.
if os.Getenv(metadataHostEnv) != "" {
return true
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
resc := make(chan bool, 2)
// Try two strategies in parallel.
// See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194
go func() {
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
req.Header.Set("User-Agent", userAgent)
res, err := ctxhttp.Do(ctx, metaClient, req)
if err != nil {
resc <- false
return
}
defer res.Body.Close()
resc <- res.Header.Get("Metadata-Flavor") == "Google"
}()
go func() {
addrs, err := net.LookupHost("metadata.google.internal")
if err != nil || len(addrs) == 0 {
resc <- false
return
}
resc <- strsContains(addrs, metadataIP)
}()
tryHarder := systemInfoSuggestsGCE()
if tryHarder {
res := <-resc
if res {
// The first strategy succeeded, so let's use it.
return true
}
// Wait for either the DNS or metadata server probe to
// contradict the other one and say we are running on
// GCE. Give it a lot of time to do so, since the system
// info already suggests we're running on a GCE BIOS.
timer := time.NewTimer(5 * time.Second)
defer timer.Stop()
select {
case res = <-resc:
return res
case <-timer.C:
// Too slow. Who knows what this system is.
return false
}
}
// There's no hint from the system info that we're running on
// GCE, so use the first probe's result as truth, whether it's
// true or false. The goal here is to optimize for speed for
// users who are NOT running on GCE. We can't assume that
// either a DNS lookup or an HTTP request to a blackholed IP
// address is fast. Worst case this should return when the
// metaClient's Transport.ResponseHeaderTimeout or
// Transport.Dial.Timeout fires (in two seconds).
return <-resc
}
// systemInfoSuggestsGCE reports whether the local system (without
// doing network requests) suggests that we're running on GCE. If this
// returns true, testOnGCE tries a bit harder to reach its metadata
// server.
func systemInfoSuggestsGCE() bool {
if runtime.GOOS != "linux" {
// We don't have any non-Linux clues available, at least yet.
return false
}
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
name := strings.TrimSpace(string(slurp))
return name == "Google" || name == "Google Compute Engine"
}
// Subscribe subscribes to a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
// The suffix may contain query parameters.
//
// Subscribe calls fn with the latest metadata value indicated by the provided
// suffix. If the metadata value is deleted, fn is called with the empty string
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
// is deleted. Subscribe returns the error value returned from the last call to
// fn, which may be nil when ok == false.
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
const failedSubscribeSleep = time.Second * 5
// First check to see if the metadata value exists at all.
val, lastETag, err := getETag(subscribeClient, suffix)
if err != nil {
return err
}
if err := fn(val, true); err != nil {
return err
}
ok := true
if strings.ContainsRune(suffix, '?') {
suffix += "&wait_for_change=true&last_etag="
} else {
suffix += "?wait_for_change=true&last_etag="
}
for {
val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag))
if err != nil {
if _, deleted := err.(NotDefinedError); !deleted {
time.Sleep(failedSubscribeSleep)
continue // Retry on other errors.
}
ok = false
}
lastETag = etag
if err := fn(val, ok); err != nil || !ok {
return err
}
}
}
// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return projID.get() }
// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return projNum.get() }
// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) {
return getTrimmed("instance/network-interfaces/0/ip")
}
// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) {
return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) {
return getTrimmed("instance/hostname")
}
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) {
var s []string
j, err := Get("instance/tags")
if err != nil {
return nil, err
}
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
return nil, err
}
return s, nil
}
// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) {
return instID.get()
}
// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) {
host, err := Hostname()
if err != nil {
return "", err
}
return strings.Split(host, ".")[0], nil
}
// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) {
zone, err := getTrimmed("instance/zone")
// zone is of the form "projects/<projNum>/zones/<zoneName>".
if err != nil {
return "", err
}
return zone[strings.LastIndex(zone, "/")+1:], nil
}
// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM. The value of
// an attribute can be obtained with ProjectAttributeValue.
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
func lines(suffix string) ([]string, error) {
j, err := Get(suffix)
if err != nil {
return nil, err
}
s := strings.Split(strings.TrimSpace(j), "\n")
for i := range s {
s[i] = strings.TrimSpace(s[i])
}
return s, nil
}
// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func InstanceAttributeValue(attr string) (string, error) {
return Get("instance/attributes/" + attr)
}
// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func ProjectAttributeValue(attr string) (string, error) {
return Get("project/attributes/" + attr)
}
// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func Scopes(serviceAccount string) ([]string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return lines("instance/service-accounts/" + serviceAccount + "/scopes")
}
func strsContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}

View file

@ -1,48 +0,0 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metadata
import (
"os"
"sync"
"testing"
)
func TestOnGCE_Stress(t *testing.T) {
if testing.Short() {
t.Skip("skipping in -short mode")
}
var last bool
for i := 0; i < 100; i++ {
onGCEOnce = sync.Once{}
now := OnGCE()
if i > 0 && now != last {
t.Errorf("%d. changed from %v to %v", i, last, now)
}
last = now
}
t.Logf("OnGCE() = %v", last)
}
func TestOnGCE_Force(t *testing.T) {
onGCEOnce = sync.Once{}
old := os.Getenv(metadataHostEnv)
defer os.Setenv(metadataHostEnv, old)
os.Setenv(metadataHostEnv, "127.0.0.1")
if !OnGCE() {
t.Error("OnGCE() = false; want true")
}
}

Binary file not shown.

View file

@ -1,70 +0,0 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cloud
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
var sentinels = []string{
"Copyright",
"Google Inc",
`Licensed under the Apache License, Version 2.0 (the "License");`,
}
func TestLicense(t *testing.T) {
err := filepath.Walk(".", func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if ext := filepath.Ext(path); ext != ".go" && ext != ".proto" {
return nil
}
if strings.HasSuffix(path, ".pb.go") {
// .pb.go files are generated from the proto files.
// .proto files must have license headers.
return nil
}
if path == "bigtable/cmd/cbt/cbtdoc.go" {
// Automatically generated.
return nil
}
src, err := ioutil.ReadFile(path)
if err != nil {
return nil
}
src = src[:140] // Ensure all of the sentinel values are at the top of the file.
// Find license
for _, sentinel := range sentinels {
if !bytes.Contains(src, []byte(sentinel)) {
t.Errorf("%v: license header not present. want %q", path, sentinel)
return nil
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
}

View file

@ -1,451 +0,0 @@
_March 17, 2017_
Breaking Pubsub changes.
* Publish is now asynchronous
([announcement](https://groups.google.com/d/topic/google-api-go-announce/aaqRDIQ3rvU/discussion)).
* Subscription.Pull replaced by Subscription.Receive, which takes a callback ([announcement](https://groups.google.com/d/topic/google-api-go-announce/8pt6oetAdKc/discussion)).
* Message.Done replaced with Message.Ack and Message.Nack.
_February 14, 2017_
Release of a client library for Spanner. See
the
[blog post](https://cloudplatform.googleblog.com/2017/02/introducing-Cloud-Spanner-a-global-database-service-for-mission-critical-applications.html).
Note that although the Spanner service is beta, the Go client library is alpha.
_December 12, 2016_
Beta release of BigQuery, DataStore, Logging and Storage. See the
[blog post](https://cloudplatform.googleblog.com/2016/12/announcing-new-google-cloud-client.html).
Also, BigQuery now supports structs. Read a row directly into a struct with
`RowIterator.Next`, and upload a row directly from a struct with `Uploader.Put`.
You can also use field tags. See the [package documentation][cloud-bigquery-ref]
for details.
_December 5, 2016_
More changes to BigQuery:
* The `ValueList` type was removed. It is no longer necessary. Instead of
```go
var v ValueList
... it.Next(&v) ..
```
use
```go
var v []Value
... it.Next(&v) ...
```
* Previously, repeatedly calling `RowIterator.Next` on the same `[]Value` or
`ValueList` would append to the slice. Now each call resets the size to zero first.
* Schema inference will infer the SQL type BYTES for a struct field of
type []byte. Previously it inferred STRING.
* The types `uint`, `uint64` and `uintptr` are no longer supported in schema
inference. BigQuery's integer type is INT64, and those types may hold values
that are not correctly represented in a 64-bit signed integer.
* The SQL types DATE, TIME and DATETIME are now supported. They correspond to
the `Date`, `Time` and `DateTime` types in the new `cloud.google.com/go/civil`
package.
_November 17, 2016_
Change to BigQuery: values from INTEGER columns will now be returned as int64,
not int. This will avoid errors arising from large values on 32-bit systems.
_November 8, 2016_
New datastore feature: datastore now encodes your nested Go structs as Entity values,
instead of a flattened list of the embedded struct's fields.
This means that you may now have twice-nested slices, eg.
```go
type State struct {
Cities []struct{
Populations []int
}
}
```
See [the announcement](https://groups.google.com/forum/#!topic/google-api-go-announce/79jtrdeuJAg) for
more details.
_November 8, 2016_
Breaking changes to datastore: contexts no longer hold namespaces; instead you
must set a key's namespace explicitly. Also, key functions have been changed
and renamed.
* The WithNamespace function has been removed. To specify a namespace in a Query, use the Query.Namespace method:
```go
q := datastore.NewQuery("Kind").Namespace("ns")
```
* All the fields of Key are exported. That means you can construct any Key with a struct literal:
```go
k := &Key{Kind: "Kind", ID: 37, Namespace: "ns"}
```
* As a result of the above, the Key methods Kind, ID, d.Name, Parent, SetParent and Namespace have been removed.
* `NewIncompleteKey` has been removed, replaced by `IncompleteKey`. Replace
```go
NewIncompleteKey(ctx, kind, parent)
```
with
```go
IncompleteKey(kind, parent)
```
and if you do use namespaces, make sure you set the namespace on the returned key.
* `NewKey` has been removed, replaced by `NameKey` and `IDKey`. Replace
```go
NewKey(ctx, kind, name, 0, parent)
NewKey(ctx, kind, "", id, parent)
```
with
```go
NameKey(kind, name, parent)
IDKey(kind, id, parent)
```
and if you do use namespaces, make sure you set the namespace on the returned key.
* The `Done` variable has been removed. Replace `datastore.Done` with `iterator.Done`, from the package `google.golang.org/api/iterator`.
* The `Client.Close` method will have a return type of error. It will return the result of closing the underlying gRPC connection.
See [the announcement](https://groups.google.com/forum/#!topic/google-api-go-announce/hqXtM_4Ix-0) for
more details.
_October 27, 2016_
Breaking change to bigquery: `NewGCSReference` is now a function,
not a method on `Client`.
New bigquery feature: `Table.LoaderFrom` now accepts a `ReaderSource`, enabling
loading data into a table from a file or any `io.Reader`.
_October 21, 2016_
Breaking change to pubsub: removed `pubsub.Done`.
Use `iterator.Done` instead, where `iterator` is the package
`google.golang.org/api/iterator`.
_October 19, 2016_
Breaking changes to cloud.google.com/go/bigquery:
* Client.Table and Client.OpenTable have been removed.
Replace
```go
client.OpenTable("project", "dataset", "table")
```
with
```go
client.DatasetInProject("project", "dataset").Table("table")
```
* Client.CreateTable has been removed.
Replace
```go
client.CreateTable(ctx, "project", "dataset", "table")
```
with
```go
client.DatasetInProject("project", "dataset").Table("table").Create(ctx)
```
* Dataset.ListTables have been replaced with Dataset.Tables.
Replace
```go
tables, err := ds.ListTables(ctx)
```
with
```go
it := ds.Tables(ctx)
for {
table, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: use table.
}
```
* Client.Read has been replaced with Job.Read, Table.Read and Query.Read.
Replace
```go
it, err := client.Read(ctx, job)
```
with
```go
it, err := job.Read(ctx)
```
and similarly for reading from tables or queries.
* The iterator returned from the Read methods is now named RowIterator. Its
behavior is closer to the other iterators in these libraries. It no longer
supports the Schema method; see the next item.
Replace
```go
for it.Next(ctx) {
var vals ValueList
if err := it.Get(&vals); err != nil {
// TODO: Handle error.
}
// TODO: use vals.
}
if err := it.Err(); err != nil {
// TODO: Handle error.
}
```
with
```
for {
var vals ValueList
err := it.Next(&vals)
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
// TODO: use vals.
}
```
Instead of the `RecordsPerRequest(n)` option, write
```go
it.PageInfo().MaxSize = n
```
Instead of the `StartIndex(i)` option, write
```go
it.StartIndex = i
```
* ValueLoader.Load now takes a Schema in addition to a slice of Values.
Replace
```go
func (vl *myValueLoader) Load(v []bigquery.Value)
```
with
```go
func (vl *myValueLoader) Load(v []bigquery.Value, s bigquery.Schema)
```
* Table.Patch is replace by Table.Update.
Replace
```go
p := table.Patch()
p.Description("new description")
metadata, err := p.Apply(ctx)
```
with
```go
metadata, err := table.Update(ctx, bigquery.TableMetadataToUpdate{
Description: "new description",
})
```
* Client.Copy is replaced by separate methods for each of its four functions.
All options have been replaced by struct fields.
* To load data from Google Cloud Storage into a table, use Table.LoaderFrom.
Replace
```go
client.Copy(ctx, table, gcsRef)
```
with
```go
table.LoaderFrom(gcsRef).Run(ctx)
```
Instead of passing options to Copy, set fields on the Loader:
```go
loader := table.LoaderFrom(gcsRef)
loader.WriteDisposition = bigquery.WriteTruncate
```
* To extract data from a table into Google Cloud Storage, use
Table.ExtractorTo. Set fields on the returned Extractor instead of
passing options.
Replace
```go
client.Copy(ctx, gcsRef, table)
```
with
```go
table.ExtractorTo(gcsRef).Run(ctx)
```
* To copy data into a table from one or more other tables, use
Table.CopierFrom. Set fields on the returned Copier instead of passing options.
Replace
```go
client.Copy(ctx, dstTable, srcTable)
```
with
```go
dst.Table.CopierFrom(srcTable).Run(ctx)
```
* To start a query job, create a Query and call its Run method. Set fields
on the query instead of passing options.
Replace
```go
client.Copy(ctx, table, query)
```
with
```go
query.Run(ctx)
```
* Table.NewUploader has been renamed to Table.Uploader. Instead of options,
configure an Uploader by setting its fields.
Replace
```go
u := table.NewUploader(bigquery.UploadIgnoreUnknownValues())
```
with
```go
u := table.NewUploader(bigquery.UploadIgnoreUnknownValues())
u.IgnoreUnknownValues = true
```
_October 10, 2016_
Breaking changes to cloud.google.com/go/storage:
* AdminClient replaced by methods on Client.
Replace
```go
adminClient.CreateBucket(ctx, bucketName, attrs)
```
with
```go
client.Bucket(bucketName).Create(ctx, projectID, attrs)
```
* BucketHandle.List replaced by BucketHandle.Objects.
Replace
```go
for query != nil {
objs, err := bucket.List(d.ctx, query)
if err != nil { ... }
query = objs.Next
for _, obj := range objs.Results {
fmt.Println(obj)
}
}
```
with
```go
iter := bucket.Objects(d.ctx, query)
for {
obj, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil { ... }
fmt.Println(obj)
}
```
(The `iterator` package is at `google.golang.org/api/iterator`.)
Replace `Query.Cursor` with `ObjectIterator.PageInfo().Token`.
Replace `Query.MaxResults` with `ObjectIterator.PageInfo().MaxSize`.
* ObjectHandle.CopyTo replaced by ObjectHandle.CopierFrom.
Replace
```go
attrs, err := src.CopyTo(ctx, dst, nil)
```
with
```go
attrs, err := dst.CopierFrom(src).Run(ctx)
```
Replace
```go
attrs, err := src.CopyTo(ctx, dst, &storage.ObjectAttrs{ContextType: "text/html"})
```
with
```go
c := dst.CopierFrom(src)
c.ContextType = "text/html"
attrs, err := c.Run(ctx)
```
* ObjectHandle.ComposeFrom replaced by ObjectHandle.ComposerFrom.
Replace
```go
attrs, err := dst.ComposeFrom(ctx, []*storage.ObjectHandle{src1, src2}, nil)
```
with
```go
attrs, err := dst.ComposerFrom(src1, src2).Run(ctx)
```
* ObjectHandle.Update's ObjectAttrs argument replaced by ObjectAttrsToUpdate.
Replace
```go
attrs, err := obj.Update(ctx, &storage.ObjectAttrs{ContextType: "text/html"})
```
with
```go
attrs, err := obj.Update(ctx, storage.ObjectAttrsToUpdate{ContextType: "text/html"})
```
* ObjectHandle.WithConditions replaced by ObjectHandle.If.
Replace
```go
obj.WithConditions(storage.Generation(gen), storage.IfMetaGenerationMatch(mgen))
```
with
```go
obj.Generation(gen).If(storage.Conditions{MetagenerationMatch: mgen})
```
Replace
```go
obj.WithConditions(storage.IfGenerationMatch(0))
```
with
```go
obj.If(storage.Conditions{DoesNotExist: true})
```
* `storage.Done` replaced by `iterator.Done` (from package `google.golang.org/api/iterator`).
_October 6, 2016_
Package preview/logging deleted. Use logging instead.
_September 27, 2016_
Logging client replaced with preview version (see below).
_September 8, 2016_
* New clients for some of Google's Machine Learning APIs: Vision, Speech, and
Natural Language.
* Preview version of a new [Stackdriver Logging][cloud-logging] client in
[`cloud.google.com/go/preview/logging`](https://godoc.org/cloud.google.com/go/preview/logging).
This client uses gRPC as its transport layer, and supports log reading, sinks
and metrics. It will replace the current client at `cloud.google.com/go/logging` shortly.

View file

@ -1,86 +0,0 @@
#!/bin/bash
# Selectively run tests for this repo, based on what has changed
# in a commit. Runs short tests for the whole repo, and full tests
# for changed directories.
set -e
prefix=cloud.google.com/go
dryrun=false
if [[ $1 == "-n" ]]; then
dryrun=true
shift
fi
if [[ $1 == "" ]]; then
echo >&2 "usage: $0 [-n] COMMIT"
exit 1
fi
# Files or directories that cause all tests to run if modified.
declare -A run_all
run_all=([.travis.yml]=1 [run-tests.sh]=1)
function run {
if $dryrun; then
echo $*
else
(set -x; $*)
fi
}
# Find all the packages that have changed in this commit.
declare -A changed_packages
for f in $(git diff-tree --no-commit-id --name-only -r $1); do
if [[ ${run_all[$f]} == 1 ]]; then
# This change requires a full test. Do it and exit.
run go test -race -v $prefix/...
exit
fi
# Map, e.g., "spanner/client.go" to "$prefix/spanner".
d=$(dirname $f)
if [[ $d == "." ]]; then
pkg=$prefix
else
pkg=$prefix/$d
fi
changed_packages[$pkg]=1
done
echo "changed packages: ${!changed_packages[*]}"
# Reports whether its argument, a package name, depends (recursively)
# on a changed package.
function depends_on_changed_package {
# According to go list, a package does not depend on itself, so
# we test that separately.
if [[ ${changed_packages[$1]} == 1 ]]; then
return 0
fi
for dep in $(go list -f '{{range .Deps}}{{.}} {{end}}' $1); do
if [[ ${changed_packages[$dep]} == 1 ]]; then
return 0
fi
done
return 1
}
# Collect the packages into two separate lists. (It is faster go test a list of
# packages than to individually go test each one.)
shorts=
fulls=
for pkg in $(go list $prefix/...); do # for each package in the repo
if depends_on_changed_package $pkg; then # if it depends on a changed package
fulls="$fulls $pkg" # run the full test
else # otherwise
shorts="$shorts $pkg" # run the short test
fi
done
run go test -race -v -short $shorts
run go test -race -v $fulls

14
vendor/github.com/asaskevich/govalidator/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,14 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- tip
notifications:
email:
- bwatas@gmail.com

21
vendor/github.com/asaskevich/govalidator/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Alex Saskevich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

423
vendor/github.com/asaskevich/govalidator/README.md generated vendored Normal file
View file

@ -0,0 +1,423 @@
govalidator
===========
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/asaskevich/govalidator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![GoDoc](https://godoc.org/github.com/asaskevich/govalidator?status.png)](https://godoc.org/github.com/asaskevich/govalidator) [![Coverage Status](https://img.shields.io/coveralls/asaskevich/govalidator.svg)](https://coveralls.io/r/asaskevich/govalidator?branch=master) [![wercker status](https://app.wercker.com/status/1ec990b09ea86c910d5f08b0e02c6043/s "wercker status")](https://app.wercker.com/project/bykey/1ec990b09ea86c910d5f08b0e02c6043)
[![Build Status](https://travis-ci.org/asaskevich/govalidator.svg?branch=master)](https://travis-ci.org/asaskevich/govalidator) [![Go Report Card](https://goreportcard.com/badge/github.com/asaskevich/govalidator)](https://goreportcard.com/report/github.com/asaskevich/govalidator) [![GoSearch](http://go-search.org/badge?id=github.com%2Fasaskevich%2Fgovalidator)](http://go-search.org/view?id=github.com%2Fasaskevich%2Fgovalidator)
A package of validators and sanitizers for strings, structs and collections. Based on [validator.js](https://github.com/chriso/validator.js).
#### Installation
Make sure that Go is installed on your computer.
Type the following command in your terminal:
go get github.com/asaskevich/govalidator
or you can get specified release of the package with `gopkg.in`:
go get gopkg.in/asaskevich/govalidator.v4
After it the package is ready to use.
#### Import package in your project
Add following line in your `*.go` file:
```go
import "github.com/asaskevich/govalidator"
```
If you are unhappy to use long `govalidator`, you can do something like this:
```go
import (
valid "github.com/asaskevich/govalidator"
)
```
#### Activate behavior to require all fields have a validation tag by default
`SetFieldsRequiredByDefault` causes validation to fail when struct fields do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). A good place to activate this is a package init function or the main() function.
```go
import "github.com/asaskevich/govalidator"
func init() {
govalidator.SetFieldsRequiredByDefault(true)
}
```
Here's some code to explain it:
```go
// this struct definition will fail govalidator.ValidateStruct() (and the field values do not matter):
type exampleStruct struct {
Name string ``
Email string `valid:"email"`
}
// this, however, will only fail when Email is empty or an invalid email address:
type exampleStruct2 struct {
Name string `valid:"-"`
Email string `valid:"email"`
}
// lastly, this will only fail when Email is an invalid email address but not when it's empty:
type exampleStruct2 struct {
Name string `valid:"-"`
Email string `valid:"email,optional"`
}
```
#### Recent breaking changes (see [#123](https://github.com/asaskevich/govalidator/pull/123))
##### Custom validator function signature
A context was added as the second parameter, for structs this is the object being validated this makes dependent validation possible.
```go
import "github.com/asaskevich/govalidator"
// old signature
func(i interface{}) bool
// new signature
func(i interface{}, o interface{}) bool
```
##### Adding a custom validator
This was changed to prevent data races when accessing custom validators.
```go
import "github.com/asaskevich/govalidator"
// before
govalidator.CustomTypeTagMap["customByteArrayValidator"] = CustomTypeValidator(func(i interface{}, o interface{}) bool {
// ...
})
// after
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator(func(i interface{}, o interface{}) bool {
// ...
}))
```
#### List of functions:
```go
func Abs(value float64) float64
func BlackList(str, chars string) string
func ByteLength(str string, params ...string) bool
func CamelCaseToUnderscore(str string) string
func Contains(str, substring string) bool
func Count(array []interface{}, iterator ConditionIterator) int
func Each(array []interface{}, iterator Iterator)
func ErrorByField(e error, field string) string
func ErrorsByField(e error) map[string]string
func Filter(array []interface{}, iterator ConditionIterator) []interface{}
func Find(array []interface{}, iterator ConditionIterator) interface{}
func GetLine(s string, index int) (string, error)
func GetLines(s string) []string
func InRange(value, left, right float64) bool
func IsASCII(str string) bool
func IsAlpha(str string) bool
func IsAlphanumeric(str string) bool
func IsBase64(str string) bool
func IsByteLength(str string, min, max int) bool
func IsCIDR(str string) bool
func IsCreditCard(str string) bool
func IsDNSName(str string) bool
func IsDataURI(str string) bool
func IsDialString(str string) bool
func IsDivisibleBy(str, num string) bool
func IsEmail(str string) bool
func IsFilePath(str string) (bool, int)
func IsFloat(str string) bool
func IsFullWidth(str string) bool
func IsHalfWidth(str string) bool
func IsHexadecimal(str string) bool
func IsHexcolor(str string) bool
func IsHost(str string) bool
func IsIP(str string) bool
func IsIPv4(str string) bool
func IsIPv6(str string) bool
func IsISBN(str string, version int) bool
func IsISBN10(str string) bool
func IsISBN13(str string) bool
func IsISO3166Alpha2(str string) bool
func IsISO3166Alpha3(str string) bool
func IsISO693Alpha2(str string) bool
func IsISO693Alpha3b(str string) bool
func IsISO4217(str string) bool
func IsIn(str string, params ...string) bool
func IsInt(str string) bool
func IsJSON(str string) bool
func IsLatitude(str string) bool
func IsLongitude(str string) bool
func IsLowerCase(str string) bool
func IsMAC(str string) bool
func IsMongoID(str string) bool
func IsMultibyte(str string) bool
func IsNatural(value float64) bool
func IsNegative(value float64) bool
func IsNonNegative(value float64) bool
func IsNonPositive(value float64) bool
func IsNull(str string) bool
func IsNumeric(str string) bool
func IsPort(str string) bool
func IsPositive(value float64) bool
func IsPrintableASCII(str string) bool
func IsRFC3339(str string) bool
func IsRGBcolor(str string) bool
func IsRequestURI(rawurl string) bool
func IsRequestURL(rawurl string) bool
func IsSSN(str string) bool
func IsSemver(str string) bool
func IsTime(str string, format string) bool
func IsURL(str string) bool
func IsUTFDigit(str string) bool
func IsUTFLetter(str string) bool
func IsUTFLetterNumeric(str string) bool
func IsUTFNumeric(str string) bool
func IsUUID(str string) bool
func IsUUIDv3(str string) bool
func IsUUIDv4(str string) bool
func IsUUIDv5(str string) bool
func IsUpperCase(str string) bool
func IsVariableWidth(str string) bool
func IsWhole(value float64) bool
func LeftTrim(str, chars string) string
func Map(array []interface{}, iterator ResultIterator) []interface{}
func Matches(str, pattern string) bool
func NormalizeEmail(str string) (string, error)
func PadBoth(str string, padStr string, padLen int) string
func PadLeft(str string, padStr string, padLen int) string
func PadRight(str string, padStr string, padLen int) string
func Range(str string, params ...string) bool
func RemoveTags(s string) string
func ReplacePattern(str, pattern, replace string) string
func Reverse(s string) string
func RightTrim(str, chars string) string
func RuneLength(str string, params ...string) bool
func SafeFileName(str string) string
func SetFieldsRequiredByDefault(value bool)
func Sign(value float64) float64
func StringLength(str string, params ...string) bool
func StringMatches(s string, params ...string) bool
func StripLow(str string, keepNewLines bool) string
func ToBoolean(str string) (bool, error)
func ToFloat(str string) (float64, error)
func ToInt(str string) (int64, error)
func ToJSON(obj interface{}) (string, error)
func ToString(obj interface{}) string
func Trim(str, chars string) string
func Truncate(str string, length int, ending string) string
func UnderscoreToCamelCase(s string) string
func ValidateStruct(s interface{}) (bool, error)
func WhiteList(str, chars string) string
type ConditionIterator
type CustomTypeValidator
type Error
func (e Error) Error() string
type Errors
func (es Errors) Error() string
func (es Errors) Errors() []error
type ISO3166Entry
type Iterator
type ParamValidator
type ResultIterator
type UnsupportedTypeError
func (e *UnsupportedTypeError) Error() string
type Validator
```
#### Examples
###### IsURL
```go
println(govalidator.IsURL(`http://user@pass:domain.com/path/page`))
```
###### ToString
```go
type User struct {
FirstName string
LastName string
}
str := govalidator.ToString(&User{"John", "Juan"})
println(str)
```
###### Each, Map, Filter, Count for slices
Each iterates over the slice/array and calls Iterator for every item
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.Iterator = func(value interface{}, index int) {
println(value.(int))
}
govalidator.Each(data, fn)
```
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.ResultIterator = func(value interface{}, index int) interface{} {
return value.(int) * 3
}
_ = govalidator.Map(data, fn) // result = []interface{}{1, 6, 9, 12, 15}
```
```go
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var fn govalidator.ConditionIterator = func(value interface{}, index int) bool {
return value.(int)%2 == 0
}
_ = govalidator.Filter(data, fn) // result = []interface{}{2, 4, 6, 8, 10}
_ = govalidator.Count(data, fn) // result = 5
```
###### ValidateStruct [#2](https://github.com/asaskevich/govalidator/pull/2)
If you want to validate structs, you can use tag `valid` for any field in your structure. All validators used with this field in one tag are separated by comma. If you want to skip validation, place `-` in your tag. If you need a validator that is not on the list below, you can add it like this:
```go
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
```
For completely custom validators (interface-based), see below.
Here is a list of available validators for struct fields (validator - used function):
```go
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
```
Validators with parameters
```go
"range(min|max)": Range,
"length(min|max)": ByteLength,
"runelength(min|max)": RuneLength,
"matches(pattern)": StringMatches,
"in(string1|string2|...|stringN)": IsIn,
```
And here is small example of usage:
```go
type Post struct {
Title string `valid:"alphanum,required"`
Message string `valid:"duck,ascii"`
AuthorIP string `valid:"ipv4"`
Date string `valid:"-"`
}
post := &Post{
Title: "My Example Post",
Message: "duck",
AuthorIP: "123.234.54.3",
}
// Add your own struct validation tags
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
result, err := govalidator.ValidateStruct(post)
if err != nil {
println("error: " + err.Error())
}
println(result)
```
###### WhiteList
```go
// Remove all characters from string ignoring characters between "a" and "z"
println(govalidator.WhiteList("a3a43a5a4a3a2a23a4a5a4a3a4", "a-z") == "aaaaaaaaaaaa")
```
###### Custom validation functions
Custom validation using your own domain specific validators is also available - here's an example of how to use it:
```go
import "github.com/asaskevich/govalidator"
type CustomByteArray [6]byte // custom types are supported and can be validated
type StructWithCustomByteArray struct {
ID CustomByteArray `valid:"customByteArrayValidator,customMinLengthValidator"` // multiple custom validators are possible as well and will be evaluated in sequence
Email string `valid:"email"`
CustomMinLength int `valid:"-"`
}
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator(func(i interface{}, context interface{}) bool {
switch v := context.(type) { // you can type switch on the context interface being validated
case StructWithCustomByteArray:
// you can check and validate against some other field in the context,
// return early or not validate against the context at all your choice
case SomeOtherType:
// ...
default:
// expecting some other type? Throw/panic here or continue
}
switch v := i.(type) { // type switch on the struct field being validated
case CustomByteArray:
for _, e := range v { // this validator checks that the byte array is not empty, i.e. not all zeroes
if e != 0 {
return true
}
}
}
return false
}))
govalidator.CustomTypeTagMap.Set("customMinLengthValidator", CustomTypeValidator(func(i interface{}, context interface{}) bool {
switch v := context.(type) { // this validates a field against the value in another field, i.e. dependent validation
case StructWithCustomByteArray:
return len(v.ID) >= v.CustomMinLength
}
return false
}))
```
#### Notes
Documentation is available here: [godoc.org](https://godoc.org/github.com/asaskevich/govalidator).
Full information about code coverage is also available here: [govalidator on gocover.io](http://gocover.io/github.com/asaskevich/govalidator).
#### Support
If you do have a contribution for the package feel free to put up a Pull Request or open Issue.
#### Special thanks to [contributors](https://github.com/asaskevich/govalidator/graphs/contributors)
* [Daniel Lohse](https://github.com/annismckenzie)
* [Attila Oláh](https://github.com/attilaolah)
* [Daniel Korner](https://github.com/Dadie)
* [Steven Wilkin](https://github.com/stevenwilkin)
* [Deiwin Sarjas](https://github.com/deiwin)
* [Noah Shibley](https://github.com/slugmobile)
* [Nathan Davies](https://github.com/nathj07)
* [Matt Sanford](https://github.com/mzsanford)
* [Simon ccl1115](https://github.com/ccl1115)

58
vendor/github.com/asaskevich/govalidator/arrays.go generated vendored Normal file
View file

@ -0,0 +1,58 @@
package govalidator
// Iterator is the function that accepts element of slice/array and its index
type Iterator func(interface{}, int)
// ResultIterator is the function that accepts element of slice/array and its index and returns any result
type ResultIterator func(interface{}, int) interface{}
// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean
type ConditionIterator func(interface{}, int) bool
// Each iterates over the slice and apply Iterator to every item
func Each(array []interface{}, iterator Iterator) {
for index, data := range array {
iterator(data, index)
}
}
// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result.
func Map(array []interface{}, iterator ResultIterator) []interface{} {
var result = make([]interface{}, len(array))
for index, data := range array {
result[index] = iterator(data, index)
}
return result
}
// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise.
func Find(array []interface{}, iterator ConditionIterator) interface{} {
for index, data := range array {
if iterator(data, index) {
return data
}
}
return nil
}
// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice.
func Filter(array []interface{}, iterator ConditionIterator) []interface{} {
var result = make([]interface{}, 0)
for index, data := range array {
if iterator(data, index) {
result = append(result, data)
}
}
return result
}
// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator.
func Count(array []interface{}, iterator ConditionIterator) int {
count := 0
for index, data := range array {
if iterator(data, index) {
count = count + 1
}
}
return count
}

116
vendor/github.com/asaskevich/govalidator/arrays_test.go generated vendored Normal file
View file

@ -0,0 +1,116 @@
package govalidator
import "testing"
func TestEach(t *testing.T) {
// TODO Maybe refactor?
t.Parallel()
acc := 0
data := []interface{}{1, 2, 3, 4, 5}
var fn Iterator = func(value interface{}, index int) {
acc = acc + value.(int)
}
Each(data, fn)
if acc != 15 {
t.Errorf("Expected Each(..) to be %v, got %v", 15, acc)
}
}
func ExampleEach() {
data := []interface{}{1, 2, 3, 4, 5}
var fn Iterator = func(value interface{}, index int) {
println(value.(int))
}
Each(data, fn)
}
func TestMap(t *testing.T) {
// TODO Maybe refactor?
t.Parallel()
data := []interface{}{1, 2, 3, 4, 5}
var fn ResultIterator = func(value interface{}, index int) interface{} {
return value.(int) * 3
}
result := Map(data, fn)
for i, d := range result {
if d != fn(data[i], i) {
t.Errorf("Expected Map(..) to be %v, got %v", fn(data[i], i), d)
}
}
}
func ExampleMap() {
data := []interface{}{1, 2, 3, 4, 5}
var fn ResultIterator = func(value interface{}, index int) interface{} {
return value.(int) * 3
}
_ = Map(data, fn) // result = []interface{}{1, 6, 9, 12, 15}
}
func TestFind(t *testing.T) {
// TODO Maybe refactor?
t.Parallel()
findElement := 96
data := []interface{}{1, 2, 3, 4, findElement, 5}
var fn1 ConditionIterator = func(value interface{}, index int) bool {
return value.(int) == findElement
}
var fn2 ConditionIterator = func(value interface{}, index int) bool {
value, _ = value.(string)
return value == "govalidator"
}
val1 := Find(data, fn1)
val2 := Find(data, fn2)
if val1 != findElement {
t.Errorf("Expected Find(..) to be %v, got %v", findElement, val1)
}
if val2 != nil {
t.Errorf("Expected Find(..) to be %v, got %v", nil, val2)
}
}
func TestFilter(t *testing.T) {
// TODO Maybe refactor?
t.Parallel()
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
answer := []interface{}{2, 4, 6, 8, 10}
var fn ConditionIterator = func(value interface{}, index int) bool {
return value.(int)%2 == 0
}
result := Filter(data, fn)
for i := range result {
if result[i] != answer[i] {
t.Errorf("Expected Filter(..) to be %v, got %v", answer[i], result[i])
}
}
}
func ExampleFilter() {
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var fn ConditionIterator = func(value interface{}, index int) bool {
return value.(int)%2 == 0
}
_ = Filter(data, fn) // result = []interface{}{2, 4, 6, 8, 10}
}
func TestCount(t *testing.T) {
// TODO Maybe refactor?
t.Parallel()
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
count := 5
var fn ConditionIterator = func(value interface{}, index int) bool {
return value.(int)%2 == 0
}
result := Count(data, fn)
if result != count {
t.Errorf("Expected Count(..) to be %v, got %v", count, result)
}
}
func ExampleCount() {
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var fn ConditionIterator = func(value interface{}, index int) bool {
return value.(int)%2 == 0
}
_ = Count(data, fn) // result = 5
}

45
vendor/github.com/asaskevich/govalidator/converter.go generated vendored Normal file
View file

@ -0,0 +1,45 @@
package govalidator
import (
"encoding/json"
"fmt"
"strconv"
)
// ToString convert the input to a string.
func ToString(obj interface{}) string {
res := fmt.Sprintf("%v", obj)
return string(res)
}
// ToJSON convert the input to a valid JSON string
func ToJSON(obj interface{}) (string, error) {
res, err := json.Marshal(obj)
if err != nil {
res = []byte("")
}
return string(res), err
}
// ToFloat convert the input string to a float, or 0.0 if the input is not a float.
func ToFloat(str string) (float64, error) {
res, err := strconv.ParseFloat(str, 64)
if err != nil {
res = 0.0
}
return res, err
}
// ToInt convert the input string to an integer, or 0 if the input is not an integer.
func ToInt(str string) (int64, error) {
res, err := strconv.ParseInt(str, 0, 64)
if err != nil {
res = 0
}
return res, err
}
// ToBoolean convert the input string to a boolean.
func ToBoolean(str string) (bool, error) {
return strconv.ParseBool(str)
}

View file

@ -0,0 +1,78 @@
package govalidator
import (
"fmt"
"testing"
)
func TestToInt(t *testing.T) {
tests := []string{"1000", "-123", "abcdef", "100000000000000000000000000000000000000000000"}
expected := []int64{1000, -123, 0, 0}
for i := 0; i < len(tests); i++ {
result, _ := ToInt(tests[i])
if result != expected[i] {
t.Log("Case ", i, ": expected ", expected[i], " when result is ", result)
t.FailNow()
}
}
}
func TestToBoolean(t *testing.T) {
tests := []string{"true", "1", "True", "false", "0", "abcdef"}
expected := []bool{true, true, true, false, false, false}
for i := 0; i < len(tests); i++ {
res, _ := ToBoolean(tests[i])
if res != expected[i] {
t.Log("Case ", i, ": expected ", expected[i], " when result is ", res)
t.FailNow()
}
}
}
func toString(t *testing.T, test interface{}, expected string) {
res := ToString(test)
if res != expected {
t.Log("Case ToString: expected ", expected, " when result is ", res)
t.FailNow()
}
}
func TestToString(t *testing.T) {
toString(t, "str123", "str123")
toString(t, 123, "123")
toString(t, 12.3, "12.3")
toString(t, true, "true")
toString(t, 1.5+10i, "(1.5+10i)")
// Sprintf function not guarantee that maps with equal keys always will be equal in string representation
//toString(t, struct{ Keys map[int]int }{Keys: map[int]int{1: 2, 3: 4}}, "{map[1:2 3:4]}")
}
func TestToFloat(t *testing.T) {
tests := []string{"", "123", "-.01", "10.", "string", "1.23e3", ".23e10"}
expected := []float64{0, 123, -0.01, 10.0, 0, 1230, 0.23e10}
for i := 0; i < len(tests); i++ {
res, _ := ToFloat(tests[i])
if res != expected[i] {
t.Log("Case ", i, ": expected ", expected[i], " when result is ", res)
t.FailNow()
}
}
}
func TestToJSON(t *testing.T) {
tests := []interface{}{"test", map[string]string{"a": "b", "b": "c"}, func() error { return fmt.Errorf("Error") }}
expected := [][]string{
{"\"test\"", "<nil>"},
{"{\"a\":\"b\",\"b\":\"c\"}", "<nil>"},
{"", "json: unsupported type: func() error"},
}
for i, test := range tests {
actual, err := ToJSON(test)
if actual != expected[i][0] {
t.Errorf("Expected toJSON(%v) to return '%v', got '%v'", test, expected[i][0], actual)
}
if fmt.Sprintf("%v", err) != expected[i][1] {
t.Errorf("Expected error returned from toJSON(%v) to return '%v', got '%v'", test, expected[i][1], fmt.Sprintf("%v", err))
}
}
}

31
vendor/github.com/asaskevich/govalidator/error.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
package govalidator
// Errors is an array of multiple errors and conforms to the error interface.
type Errors []error
// Errors returns itself.
func (es Errors) Errors() []error {
return es
}
func (es Errors) Error() string {
var err string
for _, e := range es {
err += e.Error() + ";"
}
return err
}
// Error encapsulates a name, an error and whether there's a custom error message or not.
type Error struct {
Name string
Err error
CustomErrorMessageExists bool
}
func (e Error) Error() string {
if e.CustomErrorMessageExists {
return e.Err.Error()
}
return e.Name + ": " + e.Err.Error()
}

29
vendor/github.com/asaskevich/govalidator/error_test.go generated vendored Normal file
View file

@ -0,0 +1,29 @@
package govalidator
import (
"fmt"
"testing"
)
func TestErrorsToString(t *testing.T) {
t.Parallel()
customErr := &Error{Name: "Custom Error Name", Err: fmt.Errorf("stdlib error")}
customErrWithCustomErrorMessage := &Error{Name: "Custom Error Name 2", Err: fmt.Errorf("Bad stuff happened"), CustomErrorMessageExists: true}
var tests = []struct {
param1 Errors
expected string
}{
{Errors{}, ""},
{Errors{fmt.Errorf("Error 1")}, "Error 1;"},
{Errors{fmt.Errorf("Error 1"), fmt.Errorf("Error 2")}, "Error 1;Error 2;"},
{Errors{customErr, fmt.Errorf("Error 2")}, "Custom Error Name: stdlib error;Error 2;"},
{Errors{fmt.Errorf("Error 123"), customErrWithCustomErrorMessage}, "Error 123;Bad stuff happened;"},
}
for _, test := range tests {
actual := test.param1.Error()
if actual != test.expected {
t.Errorf("Expected Error() to return '%v', got '%v'", test.expected, actual)
}
}
}

57
vendor/github.com/asaskevich/govalidator/numerics.go generated vendored Normal file
View file

@ -0,0 +1,57 @@
package govalidator
import "math"
// Abs returns absolute value of number
func Abs(value float64) float64 {
return math.Abs(value)
}
// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise
func Sign(value float64) float64 {
if value > 0 {
return 1
} else if value < 0 {
return -1
} else {
return 0
}
}
// IsNegative returns true if value < 0
func IsNegative(value float64) bool {
return value < 0
}
// IsPositive returns true if value > 0
func IsPositive(value float64) bool {
return value > 0
}
// IsNonNegative returns true if value >= 0
func IsNonNegative(value float64) bool {
return value >= 0
}
// IsNonPositive returns true if value <= 0
func IsNonPositive(value float64) bool {
return value <= 0
}
// InRange returns true if value lies between left and right border
func InRange(value, left, right float64) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// IsWhole returns true if value is whole number
func IsWhole(value float64) bool {
return math.Remainder(value, 1) == 0
}
// IsNatural returns true if value is natural number (positive and whole)
func IsNatural(value float64) bool {
return IsWhole(value) && IsPositive(value)
}

View file

@ -0,0 +1,204 @@
package govalidator
import "testing"
func TestAbs(t *testing.T) {
t.Parallel()
var tests = []struct {
param float64
expected float64
}{
{0, 0},
{-1, 1},
{10, 10},
{3.14, 3.14},
{-96, 96},
{-10e-12, 10e-12},
}
for _, test := range tests {
actual := Abs(test.param)
if actual != test.expected {
t.Errorf("Expected Abs(%v) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestSign(t *testing.T) {
t.Parallel()
var tests = []struct {
param float64
expected float64
}{
{0, 0},
{-1, -1},
{10, 1},
{3.14, 1},
{-96, -1},
{-10e-12, -1},
}
for _, test := range tests {
actual := Sign(test.param)
if actual != test.expected {
t.Errorf("Expected Sign(%v) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestIsNegative(t *testing.T) {
t.Parallel()
var tests = []struct {
param float64
expected bool
}{
{0, false},
{-1, true},
{10, false},
{3.14, false},
{-96, true},
{-10e-12, true},
}
for _, test := range tests {
actual := IsNegative(test.param)
if actual != test.expected {
t.Errorf("Expected IsNegative(%v) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestIsNonNegative(t *testing.T) {
t.Parallel()
var tests = []struct {
param float64
expected bool
}{
{0, true},
{-1, false},
{10, true},
{3.14, true},
{-96, false},
{-10e-12, false},
}
for _, test := range tests {
actual := IsNonNegative(test.param)
if actual != test.expected {
t.Errorf("Expected IsNonNegative(%v) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestIsPositive(t *testing.T) {
t.Parallel()
var tests = []struct {
param float64
expected bool
}{
{0, false},
{-1, false},
{10, true},
{3.14, true},
{-96, false},
{-10e-12, false},
}
for _, test := range tests {
actual := IsPositive(test.param)
if actual != test.expected {
t.Errorf("Expected IsPositive(%v) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestIsNonPositive(t *testing.T) {
t.Parallel()
var tests = []struct {
param float64
expected bool
}{
{0, true},
{-1, true},
{10, false},
{3.14, false},
{-96, true},
{-10e-12, true},
}
for _, test := range tests {
actual := IsNonPositive(test.param)
if actual != test.expected {
t.Errorf("Expected IsNonPositive(%v) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestIsWhole(t *testing.T) {
t.Parallel()
var tests = []struct {
param float64
expected bool
}{
{0, true},
{-1, true},
{10, true},
{3.14, false},
{-96, true},
{-10e-12, false},
}
for _, test := range tests {
actual := IsWhole(test.param)
if actual != test.expected {
t.Errorf("Expected IsWhole(%v) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestIsNatural(t *testing.T) {
t.Parallel()
var tests = []struct {
param float64
expected bool
}{
{0, false},
{-1, false},
{10, true},
{3.14, false},
{96, true},
{-10e-12, false},
}
for _, test := range tests {
actual := IsNatural(test.param)
if actual != test.expected {
t.Errorf("Expected IsNatural(%v) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestInRange(t *testing.T) {
t.Parallel()
var tests = []struct {
param float64
left float64
right float64
expected bool
}{
{0, 0, 0, true},
{1, 0, 0, false},
{-1, 0, 0, false},
{0, -1, 1, true},
{0, 0, 1, true},
{0, -1, 0, true},
{0, 0, -1, true},
{0, 10, 5, false},
}
for _, test := range tests {
actual := InRange(test.param, test.left, test.right)
if actual != test.expected {
t.Errorf("Expected InRange(%v, %v, %v) to be %v, got %v", test.param, test.left, test.right, test.expected, actual)
}
}
}

91
vendor/github.com/asaskevich/govalidator/patterns.go generated vendored Normal file
View file

@ -0,0 +1,91 @@
package govalidator
import "regexp"
// Basic regular expressions for validating strings
const (
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
ISBN13 string = "^(?:[0-9]{13})$"
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[0-9]+$"
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
Hexadecimal string = "^[0-9a-fA-F]+$"
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
ASCII string = "^[\x00-\x7F]+$"
Multibyte string = "[^\x00-\x7F]"
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
PrintableASCII string = "^[\x20-\x7E]+$"
DataURI string = "^data:.+\\/(.+);base64$"
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername string = `(\S+(:\S*)?@)`
Hostname string = ``
URLPath string = `((\/|\?|#)[^\s]*)`
URLPort string = `(:(\d{1,5}))`
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))`
URLSubdomain string = `((www\.)|([a-zA-Z0-9]([-\.][-\._a-zA-Z0-9]+)*))`
URL string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^(/[^/\x00]*)+/?$`
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
tagName string = "valid"
)
// Used by IsFilePath func
const (
// Unknown is unresolved OS type
Unknown = iota
// Win is Windows type
Win
// Unix is *nix OS types
Unix
)
var (
rxEmail = regexp.MustCompile(Email)
rxCreditCard = regexp.MustCompile(CreditCard)
rxISBN10 = regexp.MustCompile(ISBN10)
rxISBN13 = regexp.MustCompile(ISBN13)
rxUUID3 = regexp.MustCompile(UUID3)
rxUUID4 = regexp.MustCompile(UUID4)
rxUUID5 = regexp.MustCompile(UUID5)
rxUUID = regexp.MustCompile(UUID)
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
rxInt = regexp.MustCompile(Int)
rxFloat = regexp.MustCompile(Float)
rxHexadecimal = regexp.MustCompile(Hexadecimal)
rxHexcolor = regexp.MustCompile(Hexcolor)
rxRGBcolor = regexp.MustCompile(RGBcolor)
rxASCII = regexp.MustCompile(ASCII)
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
rxMultibyte = regexp.MustCompile(Multibyte)
rxFullWidth = regexp.MustCompile(FullWidth)
rxHalfWidth = regexp.MustCompile(HalfWidth)
rxBase64 = regexp.MustCompile(Base64)
rxDataURI = regexp.MustCompile(DataURI)
rxLatitude = regexp.MustCompile(Latitude)
rxLongitude = regexp.MustCompile(Longitude)
rxDNSName = regexp.MustCompile(DNSName)
rxURL = regexp.MustCompile(URL)
rxSSN = regexp.MustCompile(SSN)
rxWinPath = regexp.MustCompile(WinPath)
rxUnixPath = regexp.MustCompile(UnixPath)
rxSemver = regexp.MustCompile(Semver)
)

613
vendor/github.com/asaskevich/govalidator/types.go generated vendored Normal file
View file

@ -0,0 +1,613 @@
package govalidator
import (
"reflect"
"regexp"
"sync"
)
// Validator is a wrapper for a validator function that returns bool and accepts string.
type Validator func(str string) bool
// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type.
// The second parameter should be the context (in the case of validating a struct: the whole object being validated).
type CustomTypeValidator func(i interface{}, o interface{}) bool
// ParamValidator is a wrapper for validator functions that accepts additional parameters.
type ParamValidator func(str string, params ...string) bool
type tagOptionsMap map[string]string
// UnsupportedTypeError is a wrapper for reflect.Type
type UnsupportedTypeError struct {
Type reflect.Type
}
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements the methods to sort by string.
type stringValues []reflect.Value
// ParamTagMap is a map of functions accept variants parameters
var ParamTagMap = map[string]ParamValidator{
"length": ByteLength,
"range": Range,
"runelength": RuneLength,
"stringlength": StringLength,
"matches": StringMatches,
"in": isInRaw,
}
// ParamTagRegexMap maps param tags to their respective regexes.
var ParamTagRegexMap = map[string]*regexp.Regexp{
"range": regexp.MustCompile("^range\\((\\d+)\\|(\\d+)\\)$"),
"length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"),
"runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"),
"stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"),
"in": regexp.MustCompile(`^in\((.*)\)`),
"matches": regexp.MustCompile(`^matches\((.+)\)$`),
}
type customTypeTagMap struct {
validators map[string]CustomTypeValidator
sync.RWMutex
}
func (tm *customTypeTagMap) Get(name string) (CustomTypeValidator, bool) {
tm.RLock()
defer tm.RUnlock()
v, ok := tm.validators[name]
return v, ok
}
func (tm *customTypeTagMap) Set(name string, ctv CustomTypeValidator) {
tm.Lock()
defer tm.Unlock()
tm.validators[name] = ctv
}
// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function.
// Use this to validate compound or custom types that need to be handled as a whole, e.g.
// `type UUID [16]byte` (this would be handled as an array of bytes).
var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeValidator)}
// TagMap is a map of functions, that can be used as tags for ValidateStruct function.
var TagMap = map[string]Validator{
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"ISO4217": IsISO4217,
}
// ISO3166Entry stores country codes
type ISO3166Entry struct {
EnglishShortName string
FrenchShortName string
Alpha2Code string
Alpha3Code string
Numeric string
}
//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes"
var ISO3166List = []ISO3166Entry{
{"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"},
{"Albania", "Albanie (l')", "AL", "ALB", "008"},
{"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"},
{"Algeria", "Algérie (l')", "DZ", "DZA", "012"},
{"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"},
{"Andorra", "Andorre (l')", "AD", "AND", "020"},
{"Angola", "Angola (l')", "AO", "AGO", "024"},
{"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"},
{"Azerbaijan", "Azerbaïdjan (l')", "AZ", "AZE", "031"},
{"Argentina", "Argentine (l')", "AR", "ARG", "032"},
{"Australia", "Australie (l')", "AU", "AUS", "036"},
{"Austria", "Autriche (l')", "AT", "AUT", "040"},
{"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"},
{"Bahrain", "Bahreïn", "BH", "BHR", "048"},
{"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"},
{"Armenia", "Arménie (l')", "AM", "ARM", "051"},
{"Barbados", "Barbade (la)", "BB", "BRB", "052"},
{"Belgium", "Belgique (la)", "BE", "BEL", "056"},
{"Bermuda", "Bermudes (les)", "BM", "BMU", "060"},
{"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"},
{"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"},
{"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"},
{"Botswana", "Botswana (le)", "BW", "BWA", "072"},
{"Bouvet Island", "Bouvet (l'Île)", "BV", "BVT", "074"},
{"Brazil", "Brésil (le)", "BR", "BRA", "076"},
{"Belize", "Belize (le)", "BZ", "BLZ", "084"},
{"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"},
{"Solomon Islands", "Salomon (Îles)", "SB", "SLB", "090"},
{"Virgin Islands (British)", "Vierges britanniques (les Îles)", "VG", "VGB", "092"},
{"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"},
{"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"},
{"Myanmar", "Myanmar (le)", "MM", "MMR", "104"},
{"Burundi", "Burundi (le)", "BI", "BDI", "108"},
{"Belarus", "Bélarus (le)", "BY", "BLR", "112"},
{"Cambodia", "Cambodge (le)", "KH", "KHM", "116"},
{"Cameroon", "Cameroun (le)", "CM", "CMR", "120"},
{"Canada", "Canada (le)", "CA", "CAN", "124"},
{"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"},
{"Cayman Islands (the)", "Caïmans (les Îles)", "KY", "CYM", "136"},
{"Central African Republic (the)", "République centrafricaine (la)", "CF", "CAF", "140"},
{"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"},
{"Chad", "Tchad (le)", "TD", "TCD", "148"},
{"Chile", "Chili (le)", "CL", "CHL", "152"},
{"China", "Chine (la)", "CN", "CHN", "156"},
{"Taiwan (Province of China)", "Taïwan (Province de Chine)", "TW", "TWN", "158"},
{"Christmas Island", "Christmas (l'Île)", "CX", "CXR", "162"},
{"Cocos (Keeling) Islands (the)", "Cocos (les Îles)/ Keeling (les Îles)", "CC", "CCK", "166"},
{"Colombia", "Colombie (la)", "CO", "COL", "170"},
{"Comoros (the)", "Comores (les)", "KM", "COM", "174"},
{"Mayotte", "Mayotte", "YT", "MYT", "175"},
{"Congo (the)", "Congo (le)", "CG", "COG", "178"},
{"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"},
{"Cook Islands (the)", "Cook (les Îles)", "CK", "COK", "184"},
{"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"},
{"Croatia", "Croatie (la)", "HR", "HRV", "191"},
{"Cuba", "Cuba", "CU", "CUB", "192"},
{"Cyprus", "Chypre", "CY", "CYP", "196"},
{"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"},
{"Benin", "Bénin (le)", "BJ", "BEN", "204"},
{"Denmark", "Danemark (le)", "DK", "DNK", "208"},
{"Dominica", "Dominique (la)", "DM", "DMA", "212"},
{"Dominican Republic (the)", "dominicaine (la République)", "DO", "DOM", "214"},
{"Ecuador", "Équateur (l')", "EC", "ECU", "218"},
{"El Salvador", "El Salvador", "SV", "SLV", "222"},
{"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"},
{"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"},
{"Eritrea", "Érythrée (l')", "ER", "ERI", "232"},
{"Estonia", "Estonie (l')", "EE", "EST", "233"},
{"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"},
{"Falkland Islands (the) [Malvinas]", "Falkland (les Îles)/Malouines (les Îles)", "FK", "FLK", "238"},
{"South Georgia and the South Sandwich Islands", "Géorgie du Sud-et-les Îles Sandwich du Sud (la)", "GS", "SGS", "239"},
{"Fiji", "Fidji (les)", "FJ", "FJI", "242"},
{"Finland", "Finlande (la)", "FI", "FIN", "246"},
{"Åland Islands", "Åland(les Îles)", "AX", "ALA", "248"},
{"France", "France (la)", "FR", "FRA", "250"},
{"French Guiana", "Guyane française (la )", "GF", "GUF", "254"},
{"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"},
{"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"},
{"Djibouti", "Djibouti", "DJ", "DJI", "262"},
{"Gabon", "Gabon (le)", "GA", "GAB", "266"},
{"Georgia", "Géorgie (la)", "GE", "GEO", "268"},
{"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"},
{"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"},
{"Germany", "Allemagne (l')", "DE", "DEU", "276"},
{"Ghana", "Ghana (le)", "GH", "GHA", "288"},
{"Gibraltar", "Gibraltar", "GI", "GIB", "292"},
{"Kiribati", "Kiribati", "KI", "KIR", "296"},
{"Greece", "Grèce (la)", "GR", "GRC", "300"},
{"Greenland", "Groenland (le)", "GL", "GRL", "304"},
{"Grenada", "Grenade (la)", "GD", "GRD", "308"},
{"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"},
{"Guam", "Guam", "GU", "GUM", "316"},
{"Guatemala", "Guatemala (le)", "GT", "GTM", "320"},
{"Guinea", "Guinée (la)", "GN", "GIN", "324"},
{"Guyana", "Guyana (le)", "GY", "GUY", "328"},
{"Haiti", "Haïti", "HT", "HTI", "332"},
{"Heard Island and McDonald Islands", "Heard-et-Îles MacDonald (l'Île)", "HM", "HMD", "334"},
{"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"},
{"Honduras", "Honduras (le)", "HN", "HND", "340"},
{"Hong Kong", "Hong Kong", "HK", "HKG", "344"},
{"Hungary", "Hongrie (la)", "HU", "HUN", "348"},
{"Iceland", "Islande (l')", "IS", "ISL", "352"},
{"India", "Inde (l')", "IN", "IND", "356"},
{"Indonesia", "Indonésie (l')", "ID", "IDN", "360"},
{"Iran (Islamic Republic of)", "Iran (République Islamique d')", "IR", "IRN", "364"},
{"Iraq", "Iraq (l')", "IQ", "IRQ", "368"},
{"Ireland", "Irlande (l')", "IE", "IRL", "372"},
{"Israel", "Israël", "IL", "ISR", "376"},
{"Italy", "Italie (l')", "IT", "ITA", "380"},
{"Côte d'Ivoire", "Côte d'Ivoire (la)", "CI", "CIV", "384"},
{"Jamaica", "Jamaïque (la)", "JM", "JAM", "388"},
{"Japan", "Japon (le)", "JP", "JPN", "392"},
{"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"},
{"Jordan", "Jordanie (la)", "JO", "JOR", "400"},
{"Kenya", "Kenya (le)", "KE", "KEN", "404"},
{"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"},
{"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"},
{"Kuwait", "Koweït (le)", "KW", "KWT", "414"},
{"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"},
{"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"},
{"Lebanon", "Liban (le)", "LB", "LBN", "422"},
{"Lesotho", "Lesotho (le)", "LS", "LSO", "426"},
{"Latvia", "Lettonie (la)", "LV", "LVA", "428"},
{"Liberia", "Libéria (le)", "LR", "LBR", "430"},
{"Libya", "Libye (la)", "LY", "LBY", "434"},
{"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"},
{"Lithuania", "Lituanie (la)", "LT", "LTU", "440"},
{"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"},
{"Macao", "Macao", "MO", "MAC", "446"},
{"Madagascar", "Madagascar", "MG", "MDG", "450"},
{"Malawi", "Malawi (le)", "MW", "MWI", "454"},
{"Malaysia", "Malaisie (la)", "MY", "MYS", "458"},
{"Maldives", "Maldives (les)", "MV", "MDV", "462"},
{"Mali", "Mali (le)", "ML", "MLI", "466"},
{"Malta", "Malte", "MT", "MLT", "470"},
{"Martinique", "Martinique (la)", "MQ", "MTQ", "474"},
{"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"},
{"Mauritius", "Maurice", "MU", "MUS", "480"},
{"Mexico", "Mexique (le)", "MX", "MEX", "484"},
{"Monaco", "Monaco", "MC", "MCO", "492"},
{"Mongolia", "Mongolie (la)", "MN", "MNG", "496"},
{"Moldova (the Republic of)", "Moldova , République de", "MD", "MDA", "498"},
{"Montenegro", "Monténégro (le)", "ME", "MNE", "499"},
{"Montserrat", "Montserrat", "MS", "MSR", "500"},
{"Morocco", "Maroc (le)", "MA", "MAR", "504"},
{"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"},
{"Oman", "Oman", "OM", "OMN", "512"},
{"Namibia", "Namibie (la)", "NA", "NAM", "516"},
{"Nauru", "Nauru", "NR", "NRU", "520"},
{"Nepal", "Népal (le)", "NP", "NPL", "524"},
{"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"},
{"Curaçao", "Curaçao", "CW", "CUW", "531"},
{"Aruba", "Aruba", "AW", "ABW", "533"},
{"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"},
{"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"},
{"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"},
{"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"},
{"New Zealand", "Nouvelle-Zélande (la)", "NZ", "NZL", "554"},
{"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"},
{"Niger (the)", "Niger (le)", "NE", "NER", "562"},
{"Nigeria", "Nigéria (le)", "NG", "NGA", "566"},
{"Niue", "Niue", "NU", "NIU", "570"},
{"Norfolk Island", "Norfolk (l'Île)", "NF", "NFK", "574"},
{"Norway", "Norvège (la)", "NO", "NOR", "578"},
{"Northern Mariana Islands (the)", "Mariannes du Nord (les Îles)", "MP", "MNP", "580"},
{"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"},
{"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"},
{"Marshall Islands (the)", "Marshall (Îles)", "MH", "MHL", "584"},
{"Palau", "Palaos (les)", "PW", "PLW", "585"},
{"Pakistan", "Pakistan (le)", "PK", "PAK", "586"},
{"Panama", "Panama (le)", "PA", "PAN", "591"},
{"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"},
{"Paraguay", "Paraguay (le)", "PY", "PRY", "600"},
{"Peru", "Pérou (le)", "PE", "PER", "604"},
{"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"},
{"Pitcairn", "Pitcairn", "PN", "PCN", "612"},
{"Poland", "Pologne (la)", "PL", "POL", "616"},
{"Portugal", "Portugal (le)", "PT", "PRT", "620"},
{"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"},
{"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"},
{"Puerto Rico", "Porto Rico", "PR", "PRI", "630"},
{"Qatar", "Qatar (le)", "QA", "QAT", "634"},
{"Réunion", "Réunion (La)", "RE", "REU", "638"},
{"Romania", "Roumanie (la)", "RO", "ROU", "642"},
{"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"},
{"Rwanda", "Rwanda (le)", "RW", "RWA", "646"},
{"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"},
{"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"},
{"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"},
{"Anguilla", "Anguilla", "AI", "AIA", "660"},
{"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"},
{"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"},
{"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"},
{"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"},
{"San Marino", "Saint-Marin", "SM", "SMR", "674"},
{"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"},
{"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"},
{"Senegal", "Sénégal (le)", "SN", "SEN", "686"},
{"Serbia", "Serbie (la)", "RS", "SRB", "688"},
{"Seychelles", "Seychelles (les)", "SC", "SYC", "690"},
{"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"},
{"Singapore", "Singapour", "SG", "SGP", "702"},
{"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"},
{"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"},
{"Slovenia", "Slovénie (la)", "SI", "SVN", "705"},
{"Somalia", "Somalie (la)", "SO", "SOM", "706"},
{"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"},
{"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"},
{"Spain", "Espagne (l')", "ES", "ESP", "724"},
{"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"},
{"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"},
{"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"},
{"Suriname", "Suriname (le)", "SR", "SUR", "740"},
{"Svalbard and Jan Mayen", "Svalbard et l'Île Jan Mayen (le)", "SJ", "SJM", "744"},
{"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"},
{"Sweden", "Suède (la)", "SE", "SWE", "752"},
{"Switzerland", "Suisse (la)", "CH", "CHE", "756"},
{"Syrian Arab Republic", "République arabe syrienne (la)", "SY", "SYR", "760"},
{"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"},
{"Thailand", "Thaïlande (la)", "TH", "THA", "764"},
{"Togo", "Togo (le)", "TG", "TGO", "768"},
{"Tokelau", "Tokelau (les)", "TK", "TKL", "772"},
{"Tonga", "Tonga (les)", "TO", "TON", "776"},
{"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"},
{"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"},
{"Tunisia", "Tunisie (la)", "TN", "TUN", "788"},
{"Turkey", "Turquie (la)", "TR", "TUR", "792"},
{"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"},
{"Turks and Caicos Islands (the)", "Turks-et-Caïcos (les Îles)", "TC", "TCA", "796"},
{"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"},
{"Uganda", "Ouganda (l')", "UG", "UGA", "800"},
{"Ukraine", "Ukraine (l')", "UA", "UKR", "804"},
{"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'exRépublique yougoslave de)", "MK", "MKD", "807"},
{"Egypt", "Égypte (l')", "EG", "EGY", "818"},
{"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"},
{"Guernsey", "Guernesey", "GG", "GGY", "831"},
{"Jersey", "Jersey", "JE", "JEY", "832"},
{"Isle of Man", "Île de Man", "IM", "IMN", "833"},
{"Tanzania, United Republic of", "Tanzanie, République-Unie de", "TZ", "TZA", "834"},
{"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"},
{"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"},
{"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"},
{"Uruguay", "Uruguay (l')", "UY", "URY", "858"},
{"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"},
{"Venezuela (Bolivarian Republic of)", "Venezuela (République bolivarienne du)", "VE", "VEN", "862"},
{"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"},
{"Samoa", "Samoa (le)", "WS", "WSM", "882"},
{"Yemen", "Yémen (le)", "YE", "YEM", "887"},
{"Zambia", "Zambie (la)", "ZM", "ZMB", "894"},
}
// ISO4217List is the list of ISO currency codes
var ISO4217List = []string{
"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN",
"BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD",
"CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK",
"DJF", "DKK", "DOP", "DZD",
"EGP", "ERN", "ETB", "EUR",
"FJD", "FKP",
"GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD",
"HKD", "HNL", "HRK", "HTG", "HUF",
"IDR", "ILS", "INR", "IQD", "IRR", "ISK",
"JMD", "JOD", "JPY",
"KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT",
"LAK", "LBP", "LKR", "LRD", "LSL", "LYD",
"MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN",
"NAD", "NGN", "NIO", "NOK", "NPR", "NZD",
"OMR",
"PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG",
"QAR",
"RON", "RSD", "RUB", "RWF",
"SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "SVC", "SYP", "SZL",
"THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS",
"UAH", "UGX", "USD", "USN", "UYI", "UYU", "UZS",
"VEF", "VND", "VUV",
"WST",
"XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX",
"YER",
"ZAR", "ZMW", "ZWL",
}
// ISO693Entry stores ISO language codes
type ISO693Entry struct {
Alpha3bCode string
Alpha2Code string
English string
}
//ISO693List based on http://data.okfn.org/data/core/language-codes/r/language-codes-3b2.json
var ISO693List = []ISO693Entry{
{Alpha3bCode: "aar", Alpha2Code: "aa", English: "Afar"},
{Alpha3bCode: "abk", Alpha2Code: "ab", English: "Abkhazian"},
{Alpha3bCode: "afr", Alpha2Code: "af", English: "Afrikaans"},
{Alpha3bCode: "aka", Alpha2Code: "ak", English: "Akan"},
{Alpha3bCode: "alb", Alpha2Code: "sq", English: "Albanian"},
{Alpha3bCode: "amh", Alpha2Code: "am", English: "Amharic"},
{Alpha3bCode: "ara", Alpha2Code: "ar", English: "Arabic"},
{Alpha3bCode: "arg", Alpha2Code: "an", English: "Aragonese"},
{Alpha3bCode: "arm", Alpha2Code: "hy", English: "Armenian"},
{Alpha3bCode: "asm", Alpha2Code: "as", English: "Assamese"},
{Alpha3bCode: "ava", Alpha2Code: "av", English: "Avaric"},
{Alpha3bCode: "ave", Alpha2Code: "ae", English: "Avestan"},
{Alpha3bCode: "aym", Alpha2Code: "ay", English: "Aymara"},
{Alpha3bCode: "aze", Alpha2Code: "az", English: "Azerbaijani"},
{Alpha3bCode: "bak", Alpha2Code: "ba", English: "Bashkir"},
{Alpha3bCode: "bam", Alpha2Code: "bm", English: "Bambara"},
{Alpha3bCode: "baq", Alpha2Code: "eu", English: "Basque"},
{Alpha3bCode: "bel", Alpha2Code: "be", English: "Belarusian"},
{Alpha3bCode: "ben", Alpha2Code: "bn", English: "Bengali"},
{Alpha3bCode: "bih", Alpha2Code: "bh", English: "Bihari languages"},
{Alpha3bCode: "bis", Alpha2Code: "bi", English: "Bislama"},
{Alpha3bCode: "bos", Alpha2Code: "bs", English: "Bosnian"},
{Alpha3bCode: "bre", Alpha2Code: "br", English: "Breton"},
{Alpha3bCode: "bul", Alpha2Code: "bg", English: "Bulgarian"},
{Alpha3bCode: "bur", Alpha2Code: "my", English: "Burmese"},
{Alpha3bCode: "cat", Alpha2Code: "ca", English: "Catalan; Valencian"},
{Alpha3bCode: "cha", Alpha2Code: "ch", English: "Chamorro"},
{Alpha3bCode: "che", Alpha2Code: "ce", English: "Chechen"},
{Alpha3bCode: "chi", Alpha2Code: "zh", English: "Chinese"},
{Alpha3bCode: "chu", Alpha2Code: "cu", English: "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"},
{Alpha3bCode: "chv", Alpha2Code: "cv", English: "Chuvash"},
{Alpha3bCode: "cor", Alpha2Code: "kw", English: "Cornish"},
{Alpha3bCode: "cos", Alpha2Code: "co", English: "Corsican"},
{Alpha3bCode: "cre", Alpha2Code: "cr", English: "Cree"},
{Alpha3bCode: "cze", Alpha2Code: "cs", English: "Czech"},
{Alpha3bCode: "dan", Alpha2Code: "da", English: "Danish"},
{Alpha3bCode: "div", Alpha2Code: "dv", English: "Divehi; Dhivehi; Maldivian"},
{Alpha3bCode: "dut", Alpha2Code: "nl", English: "Dutch; Flemish"},
{Alpha3bCode: "dzo", Alpha2Code: "dz", English: "Dzongkha"},
{Alpha3bCode: "eng", Alpha2Code: "en", English: "English"},
{Alpha3bCode: "epo", Alpha2Code: "eo", English: "Esperanto"},
{Alpha3bCode: "est", Alpha2Code: "et", English: "Estonian"},
{Alpha3bCode: "ewe", Alpha2Code: "ee", English: "Ewe"},
{Alpha3bCode: "fao", Alpha2Code: "fo", English: "Faroese"},
{Alpha3bCode: "fij", Alpha2Code: "fj", English: "Fijian"},
{Alpha3bCode: "fin", Alpha2Code: "fi", English: "Finnish"},
{Alpha3bCode: "fre", Alpha2Code: "fr", English: "French"},
{Alpha3bCode: "fry", Alpha2Code: "fy", English: "Western Frisian"},
{Alpha3bCode: "ful", Alpha2Code: "ff", English: "Fulah"},
{Alpha3bCode: "geo", Alpha2Code: "ka", English: "Georgian"},
{Alpha3bCode: "ger", Alpha2Code: "de", English: "German"},
{Alpha3bCode: "gla", Alpha2Code: "gd", English: "Gaelic; Scottish Gaelic"},
{Alpha3bCode: "gle", Alpha2Code: "ga", English: "Irish"},
{Alpha3bCode: "glg", Alpha2Code: "gl", English: "Galician"},
{Alpha3bCode: "glv", Alpha2Code: "gv", English: "Manx"},
{Alpha3bCode: "gre", Alpha2Code: "el", English: "Greek, Modern (1453-)"},
{Alpha3bCode: "grn", Alpha2Code: "gn", English: "Guarani"},
{Alpha3bCode: "guj", Alpha2Code: "gu", English: "Gujarati"},
{Alpha3bCode: "hat", Alpha2Code: "ht", English: "Haitian; Haitian Creole"},
{Alpha3bCode: "hau", Alpha2Code: "ha", English: "Hausa"},
{Alpha3bCode: "heb", Alpha2Code: "he", English: "Hebrew"},
{Alpha3bCode: "her", Alpha2Code: "hz", English: "Herero"},
{Alpha3bCode: "hin", Alpha2Code: "hi", English: "Hindi"},
{Alpha3bCode: "hmo", Alpha2Code: "ho", English: "Hiri Motu"},
{Alpha3bCode: "hrv", Alpha2Code: "hr", English: "Croatian"},
{Alpha3bCode: "hun", Alpha2Code: "hu", English: "Hungarian"},
{Alpha3bCode: "ibo", Alpha2Code: "ig", English: "Igbo"},
{Alpha3bCode: "ice", Alpha2Code: "is", English: "Icelandic"},
{Alpha3bCode: "ido", Alpha2Code: "io", English: "Ido"},
{Alpha3bCode: "iii", Alpha2Code: "ii", English: "Sichuan Yi; Nuosu"},
{Alpha3bCode: "iku", Alpha2Code: "iu", English: "Inuktitut"},
{Alpha3bCode: "ile", Alpha2Code: "ie", English: "Interlingue; Occidental"},
{Alpha3bCode: "ina", Alpha2Code: "ia", English: "Interlingua (International Auxiliary Language Association)"},
{Alpha3bCode: "ind", Alpha2Code: "id", English: "Indonesian"},
{Alpha3bCode: "ipk", Alpha2Code: "ik", English: "Inupiaq"},
{Alpha3bCode: "ita", Alpha2Code: "it", English: "Italian"},
{Alpha3bCode: "jav", Alpha2Code: "jv", English: "Javanese"},
{Alpha3bCode: "jpn", Alpha2Code: "ja", English: "Japanese"},
{Alpha3bCode: "kal", Alpha2Code: "kl", English: "Kalaallisut; Greenlandic"},
{Alpha3bCode: "kan", Alpha2Code: "kn", English: "Kannada"},
{Alpha3bCode: "kas", Alpha2Code: "ks", English: "Kashmiri"},
{Alpha3bCode: "kau", Alpha2Code: "kr", English: "Kanuri"},
{Alpha3bCode: "kaz", Alpha2Code: "kk", English: "Kazakh"},
{Alpha3bCode: "khm", Alpha2Code: "km", English: "Central Khmer"},
{Alpha3bCode: "kik", Alpha2Code: "ki", English: "Kikuyu; Gikuyu"},
{Alpha3bCode: "kin", Alpha2Code: "rw", English: "Kinyarwanda"},
{Alpha3bCode: "kir", Alpha2Code: "ky", English: "Kirghiz; Kyrgyz"},
{Alpha3bCode: "kom", Alpha2Code: "kv", English: "Komi"},
{Alpha3bCode: "kon", Alpha2Code: "kg", English: "Kongo"},
{Alpha3bCode: "kor", Alpha2Code: "ko", English: "Korean"},
{Alpha3bCode: "kua", Alpha2Code: "kj", English: "Kuanyama; Kwanyama"},
{Alpha3bCode: "kur", Alpha2Code: "ku", English: "Kurdish"},
{Alpha3bCode: "lao", Alpha2Code: "lo", English: "Lao"},
{Alpha3bCode: "lat", Alpha2Code: "la", English: "Latin"},
{Alpha3bCode: "lav", Alpha2Code: "lv", English: "Latvian"},
{Alpha3bCode: "lim", Alpha2Code: "li", English: "Limburgan; Limburger; Limburgish"},
{Alpha3bCode: "lin", Alpha2Code: "ln", English: "Lingala"},
{Alpha3bCode: "lit", Alpha2Code: "lt", English: "Lithuanian"},
{Alpha3bCode: "ltz", Alpha2Code: "lb", English: "Luxembourgish; Letzeburgesch"},
{Alpha3bCode: "lub", Alpha2Code: "lu", English: "Luba-Katanga"},
{Alpha3bCode: "lug", Alpha2Code: "lg", English: "Ganda"},
{Alpha3bCode: "mac", Alpha2Code: "mk", English: "Macedonian"},
{Alpha3bCode: "mah", Alpha2Code: "mh", English: "Marshallese"},
{Alpha3bCode: "mal", Alpha2Code: "ml", English: "Malayalam"},
{Alpha3bCode: "mao", Alpha2Code: "mi", English: "Maori"},
{Alpha3bCode: "mar", Alpha2Code: "mr", English: "Marathi"},
{Alpha3bCode: "may", Alpha2Code: "ms", English: "Malay"},
{Alpha3bCode: "mlg", Alpha2Code: "mg", English: "Malagasy"},
{Alpha3bCode: "mlt", Alpha2Code: "mt", English: "Maltese"},
{Alpha3bCode: "mon", Alpha2Code: "mn", English: "Mongolian"},
{Alpha3bCode: "nau", Alpha2Code: "na", English: "Nauru"},
{Alpha3bCode: "nav", Alpha2Code: "nv", English: "Navajo; Navaho"},
{Alpha3bCode: "nbl", Alpha2Code: "nr", English: "Ndebele, South; South Ndebele"},
{Alpha3bCode: "nde", Alpha2Code: "nd", English: "Ndebele, North; North Ndebele"},
{Alpha3bCode: "ndo", Alpha2Code: "ng", English: "Ndonga"},
{Alpha3bCode: "nep", Alpha2Code: "ne", English: "Nepali"},
{Alpha3bCode: "nno", Alpha2Code: "nn", English: "Norwegian Nynorsk; Nynorsk, Norwegian"},
{Alpha3bCode: "nob", Alpha2Code: "nb", English: "Bokmål, Norwegian; Norwegian Bokmål"},
{Alpha3bCode: "nor", Alpha2Code: "no", English: "Norwegian"},
{Alpha3bCode: "nya", Alpha2Code: "ny", English: "Chichewa; Chewa; Nyanja"},
{Alpha3bCode: "oci", Alpha2Code: "oc", English: "Occitan (post 1500); Provençal"},
{Alpha3bCode: "oji", Alpha2Code: "oj", English: "Ojibwa"},
{Alpha3bCode: "ori", Alpha2Code: "or", English: "Oriya"},
{Alpha3bCode: "orm", Alpha2Code: "om", English: "Oromo"},
{Alpha3bCode: "oss", Alpha2Code: "os", English: "Ossetian; Ossetic"},
{Alpha3bCode: "pan", Alpha2Code: "pa", English: "Panjabi; Punjabi"},
{Alpha3bCode: "per", Alpha2Code: "fa", English: "Persian"},
{Alpha3bCode: "pli", Alpha2Code: "pi", English: "Pali"},
{Alpha3bCode: "pol", Alpha2Code: "pl", English: "Polish"},
{Alpha3bCode: "por", Alpha2Code: "pt", English: "Portuguese"},
{Alpha3bCode: "pus", Alpha2Code: "ps", English: "Pushto; Pashto"},
{Alpha3bCode: "que", Alpha2Code: "qu", English: "Quechua"},
{Alpha3bCode: "roh", Alpha2Code: "rm", English: "Romansh"},
{Alpha3bCode: "rum", Alpha2Code: "ro", English: "Romanian; Moldavian; Moldovan"},
{Alpha3bCode: "run", Alpha2Code: "rn", English: "Rundi"},
{Alpha3bCode: "rus", Alpha2Code: "ru", English: "Russian"},
{Alpha3bCode: "sag", Alpha2Code: "sg", English: "Sango"},
{Alpha3bCode: "san", Alpha2Code: "sa", English: "Sanskrit"},
{Alpha3bCode: "sin", Alpha2Code: "si", English: "Sinhala; Sinhalese"},
{Alpha3bCode: "slo", Alpha2Code: "sk", English: "Slovak"},
{Alpha3bCode: "slv", Alpha2Code: "sl", English: "Slovenian"},
{Alpha3bCode: "sme", Alpha2Code: "se", English: "Northern Sami"},
{Alpha3bCode: "smo", Alpha2Code: "sm", English: "Samoan"},
{Alpha3bCode: "sna", Alpha2Code: "sn", English: "Shona"},
{Alpha3bCode: "snd", Alpha2Code: "sd", English: "Sindhi"},
{Alpha3bCode: "som", Alpha2Code: "so", English: "Somali"},
{Alpha3bCode: "sot", Alpha2Code: "st", English: "Sotho, Southern"},
{Alpha3bCode: "spa", Alpha2Code: "es", English: "Spanish; Castilian"},
{Alpha3bCode: "srd", Alpha2Code: "sc", English: "Sardinian"},
{Alpha3bCode: "srp", Alpha2Code: "sr", English: "Serbian"},
{Alpha3bCode: "ssw", Alpha2Code: "ss", English: "Swati"},
{Alpha3bCode: "sun", Alpha2Code: "su", English: "Sundanese"},
{Alpha3bCode: "swa", Alpha2Code: "sw", English: "Swahili"},
{Alpha3bCode: "swe", Alpha2Code: "sv", English: "Swedish"},
{Alpha3bCode: "tah", Alpha2Code: "ty", English: "Tahitian"},
{Alpha3bCode: "tam", Alpha2Code: "ta", English: "Tamil"},
{Alpha3bCode: "tat", Alpha2Code: "tt", English: "Tatar"},
{Alpha3bCode: "tel", Alpha2Code: "te", English: "Telugu"},
{Alpha3bCode: "tgk", Alpha2Code: "tg", English: "Tajik"},
{Alpha3bCode: "tgl", Alpha2Code: "tl", English: "Tagalog"},
{Alpha3bCode: "tha", Alpha2Code: "th", English: "Thai"},
{Alpha3bCode: "tib", Alpha2Code: "bo", English: "Tibetan"},
{Alpha3bCode: "tir", Alpha2Code: "ti", English: "Tigrinya"},
{Alpha3bCode: "ton", Alpha2Code: "to", English: "Tonga (Tonga Islands)"},
{Alpha3bCode: "tsn", Alpha2Code: "tn", English: "Tswana"},
{Alpha3bCode: "tso", Alpha2Code: "ts", English: "Tsonga"},
{Alpha3bCode: "tuk", Alpha2Code: "tk", English: "Turkmen"},
{Alpha3bCode: "tur", Alpha2Code: "tr", English: "Turkish"},
{Alpha3bCode: "twi", Alpha2Code: "tw", English: "Twi"},
{Alpha3bCode: "uig", Alpha2Code: "ug", English: "Uighur; Uyghur"},
{Alpha3bCode: "ukr", Alpha2Code: "uk", English: "Ukrainian"},
{Alpha3bCode: "urd", Alpha2Code: "ur", English: "Urdu"},
{Alpha3bCode: "uzb", Alpha2Code: "uz", English: "Uzbek"},
{Alpha3bCode: "ven", Alpha2Code: "ve", English: "Venda"},
{Alpha3bCode: "vie", Alpha2Code: "vi", English: "Vietnamese"},
{Alpha3bCode: "vol", Alpha2Code: "vo", English: "Volapük"},
{Alpha3bCode: "wel", Alpha2Code: "cy", English: "Welsh"},
{Alpha3bCode: "wln", Alpha2Code: "wa", English: "Walloon"},
{Alpha3bCode: "wol", Alpha2Code: "wo", English: "Wolof"},
{Alpha3bCode: "xho", Alpha2Code: "xh", English: "Xhosa"},
{Alpha3bCode: "yid", Alpha2Code: "yi", English: "Yiddish"},
{Alpha3bCode: "yor", Alpha2Code: "yo", English: "Yoruba"},
{Alpha3bCode: "zha", Alpha2Code: "za", English: "Zhuang; Chuang"},
{Alpha3bCode: "zul", Alpha2Code: "zu", English: "Zulu"},
}

262
vendor/github.com/asaskevich/govalidator/utils.go generated vendored Normal file
View file

@ -0,0 +1,262 @@
package govalidator
import (
"errors"
"fmt"
"html"
"math"
"path"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)
// Contains check if the string contains the substring.
func Contains(str, substring string) bool {
return strings.Contains(str, substring)
}
// Matches check if string matches the pattern (pattern is regular expression)
// In case of error return false
func Matches(str, pattern string) bool {
match, _ := regexp.MatchString(pattern, str)
return match
}
// LeftTrim trim characters from the left-side of the input.
// If second argument is empty, it's will be remove leading spaces.
func LeftTrim(str, chars string) string {
if chars == "" {
return strings.TrimLeftFunc(str, unicode.IsSpace)
}
r, _ := regexp.Compile("^[" + chars + "]+")
return r.ReplaceAllString(str, "")
}
// RightTrim trim characters from the right-side of the input.
// If second argument is empty, it's will be remove spaces.
func RightTrim(str, chars string) string {
if chars == "" {
return strings.TrimRightFunc(str, unicode.IsSpace)
}
r, _ := regexp.Compile("[" + chars + "]+$")
return r.ReplaceAllString(str, "")
}
// Trim trim characters from both sides of the input.
// If second argument is empty, it's will be remove spaces.
func Trim(str, chars string) string {
return LeftTrim(RightTrim(str, chars), chars)
}
// WhiteList remove characters that do not appear in the whitelist.
func WhiteList(str, chars string) string {
pattern := "[^" + chars + "]+"
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, "")
}
// BlackList remove characters that appear in the blacklist.
func BlackList(str, chars string) string {
pattern := "[" + chars + "]+"
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, "")
}
// StripLow remove characters with a numerical value < 32 and 127, mostly control characters.
// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
func StripLow(str string, keepNewLines bool) string {
chars := ""
if keepNewLines {
chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F"
} else {
chars = "\x00-\x1F\x7F"
}
return BlackList(str, chars)
}
// ReplacePattern replace regular expression pattern in string
func ReplacePattern(str, pattern, replace string) string {
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, replace)
}
// Escape replace <, >, & and " with HTML entities.
var Escape = html.EscapeString
func addSegment(inrune, segment []rune) []rune {
if len(segment) == 0 {
return inrune
}
if len(inrune) != 0 {
inrune = append(inrune, '_')
}
inrune = append(inrune, segment...)
return inrune
}
// UnderscoreToCamelCase converts from underscore separated form to camel case form.
// Ex.: my_func => MyFunc
func UnderscoreToCamelCase(s string) string {
return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1)
}
// CamelCaseToUnderscore converts from camel case form to underscore separated form.
// Ex.: MyFunc => my_func
func CamelCaseToUnderscore(str string) string {
var output []rune
var segment []rune
for _, r := range str {
if !unicode.IsLower(r) {
output = addSegment(output, segment)
segment = nil
}
segment = append(segment, unicode.ToLower(r))
}
output = addSegment(output, segment)
return string(output)
}
// Reverse return reversed string
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
// GetLines split string by "\n" and return array of lines
func GetLines(s string) []string {
return strings.Split(s, "\n")
}
// GetLine return specified line of multiline string
func GetLine(s string, index int) (string, error) {
lines := GetLines(s)
if index < 0 || index >= len(lines) {
return "", errors.New("line index out of bounds")
}
return lines[index], nil
}
// RemoveTags remove all tags from HTML string
func RemoveTags(s string) string {
return ReplacePattern(s, "<[^>]*>", "")
}
// SafeFileName return safe string that can be used in file names
func SafeFileName(str string) string {
name := strings.ToLower(str)
name = path.Clean(path.Base(name))
name = strings.Trim(name, " ")
separators, err := regexp.Compile(`[ &_=+:]`)
if err == nil {
name = separators.ReplaceAllString(name, "-")
}
legal, err := regexp.Compile(`[^[:alnum:]-.]`)
if err == nil {
name = legal.ReplaceAllString(name, "")
}
for strings.Contains(name, "--") {
name = strings.Replace(name, "--", "-", -1)
}
return name
}
// NormalizeEmail canonicalize an email address.
// The local part of the email address is lowercased for all domains; the hostname is always lowercased and
// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail).
// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and
// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are
// normalized to @gmail.com.
func NormalizeEmail(str string) (string, error) {
if !IsEmail(str) {
return "", fmt.Errorf("%s is not an email", str)
}
parts := strings.Split(str, "@")
parts[0] = strings.ToLower(parts[0])
parts[1] = strings.ToLower(parts[1])
if parts[1] == "gmail.com" || parts[1] == "googlemail.com" {
parts[1] = "gmail.com"
parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0]
}
return strings.Join(parts, "@"), nil
}
// Truncate a string to the closest length without breaking words.
func Truncate(str string, length int, ending string) string {
var aftstr, befstr string
if len(str) > length {
words := strings.Fields(str)
before, present := 0, 0
for i := range words {
befstr = aftstr
before = present
aftstr = aftstr + words[i] + " "
present = len(aftstr)
if present > length && i != 0 {
if (length - before) < (present - length) {
return Trim(befstr, " /\\.,\"'#!?&@+-") + ending
}
return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending
}
}
}
return str
}
// PadLeft pad left side of string if size of string is less then indicated pad length
func PadLeft(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, false)
}
// PadRight pad right side of string if size of string is less then indicated pad length
func PadRight(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, false, true)
}
// PadBoth pad sides of string if size of string is less then indicated pad length
func PadBoth(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, true)
}
// PadString either left, right or both sides, not the padding string can be unicode and more then one
// character
func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string {
// When padded length is less then the current string size
if padLen < utf8.RuneCountInString(str) {
return str
}
padLen -= utf8.RuneCountInString(str)
targetLen := padLen
targetLenLeft := targetLen
targetLenRight := targetLen
if padLeft && padRight {
targetLenLeft = padLen / 2
targetLenRight = padLen - targetLenLeft
}
strToRepeatLen := utf8.RuneCountInString(padStr)
repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen)))
repeatedString := strings.Repeat(padStr, repeatTimes)
leftSide := ""
if padLeft {
leftSide = repeatedString[0:targetLenLeft]
}
rightSide := ""
if padRight {
rightSide = repeatedString[0:targetLenRight]
}
return leftSide + str + rightSide
}

500
vendor/github.com/asaskevich/govalidator/utils_test.go generated vendored Normal file
View file

@ -0,0 +1,500 @@
package govalidator
import (
"reflect"
"testing"
)
func TestContains(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
expected bool
}{
{"abacada", "", true},
{"abacada", "ritir", false},
{"abacada", "a", true},
{"abacada", "aca", true},
}
for _, test := range tests {
actual := Contains(test.param1, test.param2)
if actual != test.expected {
t.Errorf("Expected Contains(%q,%q) to be %v, got %v", test.param1, test.param2, test.expected, actual)
}
}
}
func TestMatches(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
expected bool
}{
{"123456789", "[0-9]+", true},
{"abacada", "cab$", false},
{"111222333", "((111|222|333)+)+", true},
{"abacaba", "((123+]", false},
}
for _, test := range tests {
actual := Matches(test.param1, test.param2)
if actual != test.expected {
t.Errorf("Expected Matches(%q,%q) to be %v, got %v", test.param1, test.param2, test.expected, actual)
}
}
}
func TestLeftTrim(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
expected string
}{
{" \r\n\tfoo \r\n\t ", "", "foo \r\n\t "},
{"010100201000", "01", "201000"},
}
for _, test := range tests {
actual := LeftTrim(test.param1, test.param2)
if actual != test.expected {
t.Errorf("Expected LeftTrim(%q,%q) to be %v, got %v", test.param1, test.param2, test.expected, actual)
}
}
}
func TestRightTrim(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
expected string
}{
{" \r\n\tfoo \r\n\t ", "", " \r\n\tfoo"},
{"010100201000", "01", "0101002"},
}
for _, test := range tests {
actual := RightTrim(test.param1, test.param2)
if actual != test.expected {
t.Errorf("Expected RightTrim(%q,%q) to be %v, got %v", test.param1, test.param2, test.expected, actual)
}
}
}
func TestTrim(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
expected string
}{
{" \r\n\tfoo \r\n\t ", "", "foo"},
{"010100201000", "01", "2"},
{"1234567890987654321", "1-8", "909"},
}
for _, test := range tests {
actual := Trim(test.param1, test.param2)
if actual != test.expected {
t.Errorf("Expected Trim(%q,%q) to be %v, got %v", test.param1, test.param2, test.expected, actual)
}
}
}
// This small example illustrate how to work with Trim function.
func ExampleTrim() {
// Remove from left and right spaces and "\r", "\n", "\t" characters
println(Trim(" \r\r\ntext\r \t\n", "") == "text")
// Remove from left and right characters that are between "1" and "8".
// "1-8" is like full list "12345678".
println(Trim("1234567890987654321", "1-8") == "909")
}
func TestWhiteList(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
expected string
}{
{"abcdef", "abc", "abc"},
{"aaaaaaaaaabbbbbbbbbb", "abc", "aaaaaaaaaabbbbbbbbbb"},
{"a1b2c3", "abc", "abc"},
{" ", "abc", ""},
{"a3a43a5a4a3a2a23a4a5a4a3a4", "a-z", "aaaaaaaaaaaa"},
}
for _, test := range tests {
actual := WhiteList(test.param1, test.param2)
if actual != test.expected {
t.Errorf("Expected WhiteList(%q,%q) to be %v, got %v", test.param1, test.param2, test.expected, actual)
}
}
}
// This small example illustrate how to work with WhiteList function.
func ExampleWhiteList() {
// Remove all characters from string ignoring characters between "a" and "z"
println(WhiteList("a3a43a5a4a3a2a23a4a5a4a3a4", "a-z") == "aaaaaaaaaaaa")
}
func TestBlackList(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
expected string
}{
{"abcdef", "abc", "def"},
{"aaaaaaaaaabbbbbbbbbb", "abc", ""},
{"a1b2c3", "abc", "123"},
{" ", "abc", " "},
{"a3a43a5a4a3a2a23a4a5a4a3a4", "a-z", "34354322345434"},
}
for _, test := range tests {
actual := BlackList(test.param1, test.param2)
if actual != test.expected {
t.Errorf("Expected BlackList(%q,%q) to be %v, got %v", test.param1, test.param2, test.expected, actual)
}
}
}
func TestStripLow(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 bool
expected string
}{
{"foo\x00", false, "foo"},
{"\x7Ffoo\x02", false, "foo"},
{"\x01\x09", false, ""},
{"foo\x0A\x0D", false, "foo"},
{"perch\u00e9", false, "perch\u00e9"},
{"\u20ac", false, "\u20ac"},
{"\u2206\x0A", false, "\u2206"},
{"foo\x0A\x0D", true, "foo\x0A\x0D"},
{"\x03foo\x0A\x0D", true, "foo\x0A\x0D"},
}
for _, test := range tests {
actual := StripLow(test.param1, test.param2)
if actual != test.expected {
t.Errorf("Expected StripLow(%q,%t) to be %v, got %v", test.param1, test.param2, test.expected, actual)
}
}
}
func TestReplacePattern(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
param3 string
expected string
}{
{"ab123ba", "[0-9]+", "aca", "abacaba"},
{"abacaba", "[0-9]+", "aca", "abacaba"},
{"httpftp://github.comio", "(ftp|io)", "", "http://github.com"},
{"aaaaaaaaaa", "a", "", ""},
{"http123123ftp://git534543hub.comio", "(ftp|io|[0-9]+)", "", "http://github.com"},
}
for _, test := range tests {
actual := ReplacePattern(test.param1, test.param2, test.param3)
if actual != test.expected {
t.Errorf("Expected ReplacePattern(%q,%q,%q) to be %v, got %v", test.param1, test.param2, test.param3, test.expected, actual)
}
}
}
// This small example illustrate how to work with ReplacePattern function.
func ExampleReplacePattern() {
// Replace in "http123123ftp://git534543hub.comio" following (pattern "(ftp|io|[0-9]+)"):
// - Sequence "ftp".
// - Sequence "io".
// - Sequence of digits.
// with empty string.
println(ReplacePattern("http123123ftp://git534543hub.comio", "(ftp|io|[0-9]+)", "") == "http://github.com")
}
func TestEscape(t *testing.T) {
t.Parallel()
var tests = []struct {
param string
expected string
}{
{`<img alt="foo&bar">`, "&lt;img alt=&#34;foo&amp;bar&#34;&gt;"},
}
for _, test := range tests {
actual := Escape(test.param)
if actual != test.expected {
t.Errorf("Expected Escape(%q) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestUnderscoreToCamelCase(t *testing.T) {
t.Parallel()
var tests = []struct {
param string
expected string
}{
{"a_b_c", "ABC"},
{"my_func", "MyFunc"},
{"1ab_cd", "1abCd"},
}
for _, test := range tests {
actual := UnderscoreToCamelCase(test.param)
if actual != test.expected {
t.Errorf("Expected UnderscoreToCamelCase(%q) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestCamelCaseToUnderscore(t *testing.T) {
t.Parallel()
var tests = []struct {
param string
expected string
}{
{"MyFunc", "my_func"},
{"ABC", "a_b_c"},
{"1B", "1_b"},
}
for _, test := range tests {
actual := CamelCaseToUnderscore(test.param)
if actual != test.expected {
t.Errorf("Expected CamelCaseToUnderscore(%q) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestReverse(t *testing.T) {
t.Parallel()
var tests = []struct {
param string
expected string
}{
{"abc", "cba"},
{"カタカナ", "ナカタカ"},
}
for _, test := range tests {
actual := Reverse(test.param)
if actual != test.expected {
t.Errorf("Expected Reverse(%q) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestGetLines(t *testing.T) {
t.Parallel()
var tests = []struct {
param string
expected []string
}{
{"abc", []string{"abc"}},
{"a\nb\nc", []string{"a", "b", "c"}},
}
for _, test := range tests {
actual := GetLines(test.param)
if !reflect.DeepEqual(actual, test.expected) {
t.Errorf("Expected GetLines(%q) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestGetLine(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 int
expected string
}{
{"abc", 0, "abc"},
{"a\nb\nc", 0, "a"},
{"abc", -1, ""},
{"abacaba\n", 1, ""},
{"abc", 3, ""},
}
for _, test := range tests {
actual, _ := GetLine(test.param1, test.param2)
if actual != test.expected {
t.Errorf("Expected GetLine(%q, %d) to be %v, got %v", test.param1, test.param2, test.expected, actual)
}
}
}
func TestRemoveTags(t *testing.T) {
t.Parallel()
var tests = []struct {
param string
expected string
}{
{"abc", "abc"},
{"<!-- Test -->", ""},
{"<div><div><p><a>Text</a></p></div></div>", "Text"},
{`<a href="#">Link</a>`, "Link"},
}
for _, test := range tests {
actual := RemoveTags(test.param)
if actual != test.expected {
t.Errorf("Expected RemoveTags(%q) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestSafeFileName(t *testing.T) {
t.Parallel()
var tests = []struct {
param string
expected string
}{
{"abc", "abc"},
{"123456789 '_-?ASDF@£$%£%^é.html", "123456789-asdf.html"},
{"ReadMe.md", "readme.md"},
{"file:///c:/test.go", "test.go"},
{"../../../Hello World!.txt", "hello-world.txt"},
}
for _, test := range tests {
actual := SafeFileName(test.param)
if actual != test.expected {
t.Errorf("Expected SafeFileName(%q) to be %v, got %v", test.param, test.expected, actual)
}
}
}
func TestNormalizeEmail(t *testing.T) {
t.Parallel()
var tests = []struct {
param string
expected string
}{
{`test@me.com`, `test@me.com`},
{`some.name@gmail.com`, `somename@gmail.com`},
{`some.name@googlemail.com`, `somename@gmail.com`},
{`some.name+extension@gmail.com`, `somename@gmail.com`},
{`some.name+extension@googlemail.com`, `somename@gmail.com`},
{`some.name.middlename+extension@gmail.com`, `somenamemiddlename@gmail.com`},
{`some.name.middlename+extension@googlemail.com`, `somenamemiddlename@gmail.com`},
{`some.name.midd.lena.me.+extension@gmail.com`, `somenamemiddlename@gmail.com`},
{`some.name.midd.lena.me.+extension@googlemail.com`, `somenamemiddlename@gmail.com`},
{`some.name+extension@unknown.com`, `some.name+extension@unknown.com`},
{`hans@m端ller.com`, `hans@m端ller.com`},
{`hans`, ``},
}
for _, test := range tests {
actual, err := NormalizeEmail(test.param)
if actual != test.expected {
t.Errorf("Expected NormalizeEmail(%q) to be %v, got %v, err %v", test.param, test.expected, actual, err)
}
}
}
func TestTruncate(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 int
param3 string
expected string
}{
{`Lorem ipsum dolor sit amet, consectetur adipiscing elit.`, 25, `...`, `Lorem ipsum dolor sit amet...`},
{`Measuring programming progress by lines of code is like measuring aircraft building progress by weight.`, 35, ` new born babies!`, `Measuring programming progress by new born babies!`},
{`Testestestestestestestestestest testestestestestestestestest`, 7, `...`, `Testestestestestestestestestest...`},
{`Testing`, 7, `...`, `Testing`},
}
for _, test := range tests {
actual := Truncate(test.param1, test.param2, test.param3)
if actual != test.expected {
t.Errorf("Expected Truncate(%q, %d, %q) to be %v, got %v", test.param1, test.param2, test.param3, test.expected, actual)
}
}
}
func TestPadLeft(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
param3 int
expected string
}{
{"こんにちは", "xyz", 12, "xyzxyzxこんにちは"},
{"こんにちは", "xyz", 11, "xyzxyzこんにちは"},
{"abc", "x", 5, "xxabc"},
{"abc", "xyz", 5, "xyabc"},
{"abcde", "xyz", 5, "abcde"},
{"abcde", "xyz", 4, "abcde"},
}
for _, test := range tests {
actual := PadLeft(test.param1, test.param2, test.param3)
if actual != test.expected {
t.Errorf("Expected PadLeft(%q,%q,%q) to be %v, got %v", test.param1, test.param2, test.param3, test.expected, actual)
}
}
}
func TestPadRight(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
param3 int
expected string
}{
{"こんにちは", "xyz", 12, "こんにちはxyzxyzx"},
{"こんにちは", "xyz", 11, "こんにちはxyzxyz"},
{"abc", "x", 5, "abcxx"},
{"abc", "xyz", 5, "abcxy"},
{"abcde", "xyz", 5, "abcde"},
{"abcde", "xyz", 4, "abcde"},
}
for _, test := range tests {
actual := PadRight(test.param1, test.param2, test.param3)
if actual != test.expected {
t.Errorf("Expected PadRight(%q,%q,%q) to be %v, got %v", test.param1, test.param2, test.param3, test.expected, actual)
}
}
}
func TestPadBoth(t *testing.T) {
t.Parallel()
var tests = []struct {
param1 string
param2 string
param3 int
expected string
}{
{"こんにちは", "xyz", 12, "xyzこんにちはxyzx"},
{"こんにちは", "xyz", 11, "xyzこんにちはxyz"},
{"abc", "x", 5, "xabcx"},
{"abc", "xyz", 5, "xabcx"},
{"abcde", "xyz", 5, "abcde"},
{"abcde", "xyz", 4, "abcde"},
}
for _, test := range tests {
actual := PadBoth(test.param1, test.param2, test.param3)
if actual != test.expected {
t.Errorf("Expected PadBoth(%q,%q,%q) to be %v, got %v", test.param1, test.param2, test.param3, test.expected, actual)
}
}
}

1044
vendor/github.com/asaskevich/govalidator/validator.go generated vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

15
vendor/github.com/asaskevich/govalidator/wercker.yml generated vendored Normal file
View file

@ -0,0 +1,15 @@
box: golang
build:
steps:
- setup-go-workspace
- script:
name: go get
code: |
go version
go get -t ./...
- script:
name: go test
code: |
go test -race ./...

View file

@ -1,21 +0,0 @@
language: go
matrix:
include:
- go: 1.4.3
- go: 1.5.4
- go: 1.6.3
- go: 1.7
- go: tip
allow_failures:
- go: tip
install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
script:
- echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci
-repotoken $COVERALLS_TOKEN
- echo "Build examples" ; cd examples && go build
- echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .)
env:
global:
secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw=

View file

@ -1,194 +0,0 @@
semver for golang [![Build Status](https://travis-ci.org/blang/semver.svg?branch=master)](https://travis-ci.org/blang/semver) [![GoDoc](https://godoc.org/github.com/blang/semver?status.png)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master)
======
semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`.
Usage
-----
```bash
$ go get github.com/blang/semver
```
Note: Always vendor your dependencies or fix on a specific version tag.
```go
import github.com/blang/semver
v1, err := semver.Make("1.0.0-beta")
v2, err := semver.Make("2.0.0-beta")
v1.Compare(v2)
```
Also check the [GoDocs](http://godoc.org/github.com/blang/semver).
Why should I use this lib?
-----
- Fully spec compatible
- No reflection
- No regex
- Fully tested (Coverage >99%)
- Readable parsing/validation errors
- Fast (See [Benchmarks](#benchmarks))
- Only Stdlib
- Uses values instead of pointers
- Many features, see below
Features
-----
- Parsing and validation at all levels
- Comparator-like comparisons
- Compare Helper Methods
- InPlace manipulation
- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1`
- Wildcards `>=1.x`, `<=2.5.x`
- Sortable (implements sort.Interface)
- database/sql compatible (sql.Scanner/Valuer)
- encoding/json compatible (json.Marshaler/Unmarshaler)
Ranges
------
A `Range` is a set of conditions which specify which versions satisfy the range.
A condition is composed of an operator and a version. The supported operators are:
- `<1.0.0` Less than `1.0.0`
- `<=1.0.0` Less than or equal to `1.0.0`
- `>1.0.0` Greater than `1.0.0`
- `>=1.0.0` Greater than or equal to `1.0.0`
- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0`
- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`.
Note that spaces between the operator and the version will be gracefully tolerated.
A `Range` can link multiple `Ranges` separated by space:
Ranges can be linked by logical AND:
- `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0`
- `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2`
Ranges can also be linked by logical OR:
- `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x`
AND has a higher precedence than OR. It's not possible to use brackets.
Ranges can be combined by both AND and OR
- `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
Range usage:
```
v, err := semver.Parse("1.2.3")
range, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0")
if range(v) {
//valid
}
```
Example
-----
Have a look at full examples in [examples/main.go](examples/main.go)
```go
import github.com/blang/semver
v, err := semver.Make("0.0.1-alpha.preview+123.github")
fmt.Printf("Major: %d\n", v.Major)
fmt.Printf("Minor: %d\n", v.Minor)
fmt.Printf("Patch: %d\n", v.Patch)
fmt.Printf("Pre: %s\n", v.Pre)
fmt.Printf("Build: %s\n", v.Build)
// Prerelease versions array
if len(v.Pre) > 0 {
fmt.Println("Prerelease versions:")
for i, pre := range v.Pre {
fmt.Printf("%d: %q\n", i, pre)
}
}
// Build meta data array
if len(v.Build) > 0 {
fmt.Println("Build meta data:")
for i, build := range v.Build {
fmt.Printf("%d: %q\n", i, build)
}
}
v001, err := semver.Make("0.0.1")
// Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE
v001.GT(v) == true
v.LT(v001) == true
v.GTE(v) == true
v.LTE(v) == true
// Or use v.Compare(v2) for comparisons (-1, 0, 1):
v001.Compare(v) == 1
v.Compare(v001) == -1
v.Compare(v) == 0
// Manipulate Version in place:
v.Pre[0], err = semver.NewPRVersion("beta")
if err != nil {
fmt.Printf("Error parsing pre release version: %q", err)
}
fmt.Println("\nValidate versions:")
v.Build[0] = "?"
err = v.Validate()
if err != nil {
fmt.Printf("Validation failed: %s\n", err)
}
```
Benchmarks
-----
BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op
BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op
BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op
BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op
BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op
BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op
BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op
BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op
BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op
BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op
BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op
BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op
BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op
BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op
BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op
BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op
BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op
BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op
BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op
BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op
See benchmark cases at [semver_test.go](semver_test.go)
Motivation
-----
I simply couldn't find any lib supporting the full spec. Others were just wrong or used reflection and regex which i don't like.
Contribution
-----
Feel free to make a pull request. For bigger changes create a issue first to discuss about it.
License
-----
See [LICENSE](LICENSE) file.

View file

@ -1,23 +0,0 @@
package semver
import (
"encoding/json"
)
// MarshalJSON implements the encoding/json.Marshaler interface.
func (v Version) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
// UnmarshalJSON implements the encoding/json.Unmarshaler interface.
func (v *Version) UnmarshalJSON(data []byte) (err error) {
var versionString string
if err = json.Unmarshal(data, &versionString); err != nil {
return
}
*v, err = Parse(versionString)
return
}

View file

@ -1,49 +0,0 @@
package semver
import (
"encoding/json"
"strconv"
"testing"
)
func TestJSONMarshal(t *testing.T) {
versionString := "3.1.4-alpha.1.5.9+build.2.6.5"
v, err := Parse(versionString)
if err != nil {
t.Fatal(err)
}
versionJSON, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
quotedVersionString := strconv.Quote(versionString)
if string(versionJSON) != quotedVersionString {
t.Fatalf("JSON marshaled semantic version not equal: expected %q, got %q", quotedVersionString, string(versionJSON))
}
}
func TestJSONUnmarshal(t *testing.T) {
versionString := "3.1.4-alpha.1.5.9+build.2.6.5"
quotedVersionString := strconv.Quote(versionString)
var v Version
if err := json.Unmarshal([]byte(quotedVersionString), &v); err != nil {
t.Fatal(err)
}
if v.String() != versionString {
t.Fatalf("JSON unmarshaled semantic version not equal: expected %q, got %q", versionString, v.String())
}
badVersionString := strconv.Quote("3.1.4.1.5.9.2.6.5-other-digits-of-pi")
if err := json.Unmarshal([]byte(badVersionString), &v); err == nil {
t.Fatal("expected JSON unmarshal error, got nil")
}
if err := json.Unmarshal([]byte("3.1"), &v); err == nil {
t.Fatal("expected JSON unmarshal error, got nil")
}
}

View file

@ -1,17 +0,0 @@
{
"author": "blang",
"bugs": {
"URL": "https://github.com/blang/semver/issues",
"url": "https://github.com/blang/semver/issues"
},
"gx": {
"dvcsimport": "github.com/blang/semver"
},
"gxVersion": "0.10.0",
"language": "go",
"license": "MIT",
"name": "semver",
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
"version": "3.5.1"
}

View file

@ -1,416 +0,0 @@
package semver
import (
"fmt"
"strconv"
"strings"
"unicode"
)
type wildcardType int
const (
noneWildcard wildcardType = iota
majorWildcard wildcardType = 1
minorWildcard wildcardType = 2
patchWildcard wildcardType = 3
)
func wildcardTypefromInt(i int) wildcardType {
switch i {
case 1:
return majorWildcard
case 2:
return minorWildcard
case 3:
return patchWildcard
default:
return noneWildcard
}
}
type comparator func(Version, Version) bool
var (
compEQ comparator = func(v1 Version, v2 Version) bool {
return v1.Compare(v2) == 0
}
compNE = func(v1 Version, v2 Version) bool {
return v1.Compare(v2) != 0
}
compGT = func(v1 Version, v2 Version) bool {
return v1.Compare(v2) == 1
}
compGE = func(v1 Version, v2 Version) bool {
return v1.Compare(v2) >= 0
}
compLT = func(v1 Version, v2 Version) bool {
return v1.Compare(v2) == -1
}
compLE = func(v1 Version, v2 Version) bool {
return v1.Compare(v2) <= 0
}
)
type versionRange struct {
v Version
c comparator
}
// rangeFunc creates a Range from the given versionRange.
func (vr *versionRange) rangeFunc() Range {
return Range(func(v Version) bool {
return vr.c(v, vr.v)
})
}
// Range represents a range of versions.
// A Range can be used to check if a Version satisfies it:
//
// range, err := semver.ParseRange(">1.0.0 <2.0.0")
// range(semver.MustParse("1.1.1") // returns true
type Range func(Version) bool
// OR combines the existing Range with another Range using logical OR.
func (rf Range) OR(f Range) Range {
return Range(func(v Version) bool {
return rf(v) || f(v)
})
}
// AND combines the existing Range with another Range using logical AND.
func (rf Range) AND(f Range) Range {
return Range(func(v Version) bool {
return rf(v) && f(v)
})
}
// ParseRange parses a range and returns a Range.
// If the range could not be parsed an error is returned.
//
// Valid ranges are:
// - "<1.0.0"
// - "<=1.0.0"
// - ">1.0.0"
// - ">=1.0.0"
// - "1.0.0", "=1.0.0", "==1.0.0"
// - "!1.0.0", "!=1.0.0"
//
// A Range can consist of multiple ranges separated by space:
// Ranges can be linked by logical AND:
// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
//
// Ranges can also be linked by logical OR:
// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
//
// AND has a higher precedence than OR. It's not possible to use brackets.
//
// Ranges can be combined by both AND and OR
//
// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
func ParseRange(s string) (Range, error) {
parts := splitAndTrim(s)
orParts, err := splitORParts(parts)
if err != nil {
return nil, err
}
expandedParts, err := expandWildcardVersion(orParts)
if err != nil {
return nil, err
}
var orFn Range
for _, p := range expandedParts {
var andFn Range
for _, ap := range p {
opStr, vStr, err := splitComparatorVersion(ap)
if err != nil {
return nil, err
}
vr, err := buildVersionRange(opStr, vStr)
if err != nil {
return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
}
rf := vr.rangeFunc()
// Set function
if andFn == nil {
andFn = rf
} else { // Combine with existing function
andFn = andFn.AND(rf)
}
}
if orFn == nil {
orFn = andFn
} else {
orFn = orFn.OR(andFn)
}
}
return orFn, nil
}
// splitORParts splits the already cleaned parts by '||'.
// Checks for invalid positions of the operator and returns an
// error if found.
func splitORParts(parts []string) ([][]string, error) {
var ORparts [][]string
last := 0
for i, p := range parts {
if p == "||" {
if i == 0 {
return nil, fmt.Errorf("First element in range is '||'")
}
ORparts = append(ORparts, parts[last:i])
last = i + 1
}
}
if last == len(parts) {
return nil, fmt.Errorf("Last element in range is '||'")
}
ORparts = append(ORparts, parts[last:])
return ORparts, nil
}
// buildVersionRange takes a slice of 2: operator and version
// and builds a versionRange, otherwise an error.
func buildVersionRange(opStr, vStr string) (*versionRange, error) {
c := parseComparator(opStr)
if c == nil {
return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
}
v, err := Parse(vStr)
if err != nil {
return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
}
return &versionRange{
v: v,
c: c,
}, nil
}
// inArray checks if a byte is contained in an array of bytes
func inArray(s byte, list []byte) bool {
for _, el := range list {
if el == s {
return true
}
}
return false
}
// splitAndTrim splits a range string by spaces and cleans whitespaces
func splitAndTrim(s string) (result []string) {
last := 0
var lastChar byte
excludeFromSplit := []byte{'>', '<', '='}
for i := 0; i < len(s); i++ {
if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
if last < i-1 {
result = append(result, s[last:i])
}
last = i + 1
} else if s[i] != ' ' {
lastChar = s[i]
}
}
if last < len(s)-1 {
result = append(result, s[last:])
}
for i, v := range result {
result[i] = strings.Replace(v, " ", "", -1)
}
// parts := strings.Split(s, " ")
// for _, x := range parts {
// if s := strings.TrimSpace(x); len(s) != 0 {
// result = append(result, s)
// }
// }
return
}
// splitComparatorVersion splits the comparator from the version.
// Input must be free of leading or trailing spaces.
func splitComparatorVersion(s string) (string, string, error) {
i := strings.IndexFunc(s, unicode.IsDigit)
if i == -1 {
return "", "", fmt.Errorf("Could not get version from string: %q", s)
}
return strings.TrimSpace(s[0:i]), s[i:], nil
}
// getWildcardType will return the type of wildcard that the
// passed version contains
func getWildcardType(vStr string) wildcardType {
parts := strings.Split(vStr, ".")
nparts := len(parts)
wildcard := parts[nparts-1]
possibleWildcardType := wildcardTypefromInt(nparts)
if wildcard == "x" {
return possibleWildcardType
}
return noneWildcard
}
// createVersionFromWildcard will convert a wildcard version
// into a regular version, replacing 'x's with '0's, handling
// special cases like '1.x.x' and '1.x'
func createVersionFromWildcard(vStr string) string {
// handle 1.x.x
vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
parts := strings.Split(vStr2, ".")
// handle 1.x
if len(parts) == 2 {
return vStr2 + ".0"
}
return vStr2
}
// incrementMajorVersion will increment the major version
// of the passed version
func incrementMajorVersion(vStr string) (string, error) {
parts := strings.Split(vStr, ".")
i, err := strconv.Atoi(parts[0])
if err != nil {
return "", err
}
parts[0] = strconv.Itoa(i + 1)
return strings.Join(parts, "."), nil
}
// incrementMajorVersion will increment the minor version
// of the passed version
func incrementMinorVersion(vStr string) (string, error) {
parts := strings.Split(vStr, ".")
i, err := strconv.Atoi(parts[1])
if err != nil {
return "", err
}
parts[1] = strconv.Itoa(i + 1)
return strings.Join(parts, "."), nil
}
// expandWildcardVersion will expand wildcards inside versions
// following these rules:
//
// * when dealing with patch wildcards:
// >= 1.2.x will become >= 1.2.0
// <= 1.2.x will become < 1.3.0
// > 1.2.x will become >= 1.3.0
// < 1.2.x will become < 1.2.0
// != 1.2.x will become < 1.2.0 >= 1.3.0
//
// * when dealing with minor wildcards:
// >= 1.x will become >= 1.0.0
// <= 1.x will become < 2.0.0
// > 1.x will become >= 2.0.0
// < 1.0 will become < 1.0.0
// != 1.x will become < 1.0.0 >= 2.0.0
//
// * when dealing with wildcards without
// version operator:
// 1.2.x will become >= 1.2.0 < 1.3.0
// 1.x will become >= 1.0.0 < 2.0.0
func expandWildcardVersion(parts [][]string) ([][]string, error) {
var expandedParts [][]string
for _, p := range parts {
var newParts []string
for _, ap := range p {
if strings.Index(ap, "x") != -1 {
opStr, vStr, err := splitComparatorVersion(ap)
if err != nil {
return nil, err
}
versionWildcardType := getWildcardType(vStr)
flatVersion := createVersionFromWildcard(vStr)
var resultOperator string
var shouldIncrementVersion bool
switch opStr {
case ">":
resultOperator = ">="
shouldIncrementVersion = true
case ">=":
resultOperator = ">="
case "<":
resultOperator = "<"
case "<=":
resultOperator = "<"
shouldIncrementVersion = true
case "", "=", "==":
newParts = append(newParts, ">="+flatVersion)
resultOperator = "<"
shouldIncrementVersion = true
case "!=", "!":
newParts = append(newParts, "<"+flatVersion)
resultOperator = ">="
shouldIncrementVersion = true
}
var resultVersion string
if shouldIncrementVersion {
switch versionWildcardType {
case patchWildcard:
resultVersion, _ = incrementMinorVersion(flatVersion)
case minorWildcard:
resultVersion, _ = incrementMajorVersion(flatVersion)
}
} else {
resultVersion = flatVersion
}
ap = resultOperator + resultVersion
}
newParts = append(newParts, ap)
}
expandedParts = append(expandedParts, newParts)
}
return expandedParts, nil
}
func parseComparator(s string) comparator {
switch s {
case "==":
fallthrough
case "":
fallthrough
case "=":
return compEQ
case ">":
return compGT
case ">=":
return compGE
case "<":
return compLT
case "<=":
return compLE
case "!":
fallthrough
case "!=":
return compNE
}
return nil
}
// MustParseRange is like ParseRange but panics if the range cannot be parsed.
func MustParseRange(s string) Range {
r, err := ParseRange(s)
if err != nil {
panic(`semver: ParseRange(` + s + `): ` + err.Error())
}
return r
}

View file

@ -1,581 +0,0 @@
package semver
import (
"reflect"
"strings"
"testing"
)
type wildcardTypeTest struct {
input string
wildcardType wildcardType
}
type comparatorTest struct {
input string
comparator func(comparator) bool
}
func TestParseComparator(t *testing.T) {
compatorTests := []comparatorTest{
{">", testGT},
{">=", testGE},
{"<", testLT},
{"<=", testLE},
{"", testEQ},
{"=", testEQ},
{"==", testEQ},
{"!=", testNE},
{"!", testNE},
{"-", nil},
{"<==", nil},
{"<<", nil},
{">>", nil},
}
for _, tc := range compatorTests {
if c := parseComparator(tc.input); c == nil {
if tc.comparator != nil {
t.Errorf("Comparator nil for case %q\n", tc.input)
}
} else if !tc.comparator(c) {
t.Errorf("Invalid comparator for case %q\n", tc.input)
}
}
}
var (
v1 = MustParse("1.2.2")
v2 = MustParse("1.2.3")
v3 = MustParse("1.2.4")
)
func testEQ(f comparator) bool {
return f(v1, v1) && !f(v1, v2)
}
func testNE(f comparator) bool {
return !f(v1, v1) && f(v1, v2)
}
func testGT(f comparator) bool {
return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1)
}
func testGE(f comparator) bool {
return f(v2, v1) && f(v3, v2) && !f(v1, v2)
}
func testLT(f comparator) bool {
return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1)
}
func testLE(f comparator) bool {
return f(v1, v2) && f(v2, v3) && !f(v2, v1)
}
func TestSplitAndTrim(t *testing.T) {
tests := []struct {
i string
s []string
}{
{"1.2.3 1.2.3", []string{"1.2.3", "1.2.3"}},
{" 1.2.3 1.2.3 ", []string{"1.2.3", "1.2.3"}}, // Spaces
{" >= 1.2.3 <= 1.2.3 ", []string{">=1.2.3", "<=1.2.3"}}, // Spaces between operator and version
{"1.2.3 || >=1.2.3 <1.2.3", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}},
{" 1.2.3 || >=1.2.3 <1.2.3 ", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}},
}
for _, tc := range tests {
p := splitAndTrim(tc.i)
if !reflect.DeepEqual(p, tc.s) {
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
}
}
}
func TestSplitComparatorVersion(t *testing.T) {
tests := []struct {
i string
p []string
}{
{">1.2.3", []string{">", "1.2.3"}},
{">=1.2.3", []string{">=", "1.2.3"}},
{"<1.2.3", []string{"<", "1.2.3"}},
{"<=1.2.3", []string{"<=", "1.2.3"}},
{"1.2.3", []string{"", "1.2.3"}},
{"=1.2.3", []string{"=", "1.2.3"}},
{"==1.2.3", []string{"==", "1.2.3"}},
{"!=1.2.3", []string{"!=", "1.2.3"}},
{"!1.2.3", []string{"!", "1.2.3"}},
{"error", nil},
}
for _, tc := range tests {
if op, v, err := splitComparatorVersion(tc.i); err != nil {
if tc.p != nil {
t.Errorf("Invalid for case %q: Expected %q, got error %q", tc.i, tc.p, err)
}
} else if op != tc.p[0] {
t.Errorf("Invalid operator for case %q: Expected %q, got: %q", tc.i, tc.p[0], op)
} else if v != tc.p[1] {
t.Errorf("Invalid version for case %q: Expected %q, got: %q", tc.i, tc.p[1], v)
}
}
}
func TestBuildVersionRange(t *testing.T) {
tests := []struct {
opStr string
vStr string
c func(comparator) bool
v string
}{
{">", "1.2.3", testGT, "1.2.3"},
{">=", "1.2.3", testGE, "1.2.3"},
{"<", "1.2.3", testLT, "1.2.3"},
{"<=", "1.2.3", testLE, "1.2.3"},
{"", "1.2.3", testEQ, "1.2.3"},
{"=", "1.2.3", testEQ, "1.2.3"},
{"==", "1.2.3", testEQ, "1.2.3"},
{"!=", "1.2.3", testNE, "1.2.3"},
{"!", "1.2.3", testNE, "1.2.3"},
{">>", "1.2.3", nil, ""}, // Invalid comparator
{"=", "invalid", nil, ""}, // Invalid version
}
for _, tc := range tests {
if r, err := buildVersionRange(tc.opStr, tc.vStr); err != nil {
if tc.c != nil {
t.Errorf("Invalid for case %q: Expected %q, got error %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tc.v, err)
}
} else if r == nil {
t.Errorf("Invalid for case %q: got nil", strings.Join([]string{tc.opStr, tc.vStr}, ""))
} else {
// test version
if tv := MustParse(tc.v); !r.v.EQ(tv) {
t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v)
}
// test comparator
if r.c == nil {
t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, ""))
continue
}
if !tc.c(r.c) {
t.Errorf("Invalid comparator for case %q\n", strings.Join([]string{tc.opStr, tc.vStr}, ""))
}
}
}
}
func TestSplitORParts(t *testing.T) {
tests := []struct {
i []string
o [][]string
}{
{[]string{">1.2.3", "||", "<1.2.3", "||", "=1.2.3"}, [][]string{
[]string{">1.2.3"},
[]string{"<1.2.3"},
[]string{"=1.2.3"},
}},
{[]string{">1.2.3", "<1.2.3", "||", "=1.2.3"}, [][]string{
[]string{">1.2.3", "<1.2.3"},
[]string{"=1.2.3"},
}},
{[]string{">1.2.3", "||"}, nil},
{[]string{"||", ">1.2.3"}, nil},
}
for _, tc := range tests {
o, err := splitORParts(tc.i)
if err != nil && tc.o != nil {
t.Errorf("Unexpected error for case %q: %s", tc.i, err)
}
if !reflect.DeepEqual(tc.o, o) {
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o)
}
}
}
func TestGetWildcardType(t *testing.T) {
wildcardTypeTests := []wildcardTypeTest{
{"x", majorWildcard},
{"1.x", minorWildcard},
{"1.2.x", patchWildcard},
{"fo.o.b.ar", noneWildcard},
}
for _, tc := range wildcardTypeTests {
o := getWildcardType(tc.input)
if o != tc.wildcardType {
t.Errorf("Invalid for case: %q: Expected %q, got: %q", tc.input, tc.wildcardType, o)
}
}
}
func TestCreateVersionFromWildcard(t *testing.T) {
tests := []struct {
i string
s string
}{
{"1.2.x", "1.2.0"},
{"1.x", "1.0.0"},
}
for _, tc := range tests {
p := createVersionFromWildcard(tc.i)
if p != tc.s {
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
}
}
}
func TestIncrementMajorVersion(t *testing.T) {
tests := []struct {
i string
s string
}{
{"1.2.3", "2.2.3"},
{"1.2", "2.2"},
{"foo.bar", ""},
}
for _, tc := range tests {
p, _ := incrementMajorVersion(tc.i)
if p != tc.s {
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
}
}
}
func TestIncrementMinorVersion(t *testing.T) {
tests := []struct {
i string
s string
}{
{"1.2.3", "1.3.3"},
{"1.2", "1.3"},
{"foo.bar", ""},
}
for _, tc := range tests {
p, _ := incrementMinorVersion(tc.i)
if p != tc.s {
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
}
}
}
func TestExpandWildcardVersion(t *testing.T) {
tests := []struct {
i [][]string
o [][]string
}{
{[][]string{[]string{"foox"}}, nil},
{[][]string{[]string{">=1.2.x"}}, [][]string{[]string{">=1.2.0"}}},
{[][]string{[]string{"<=1.2.x"}}, [][]string{[]string{"<1.3.0"}}},
{[][]string{[]string{">1.2.x"}}, [][]string{[]string{">=1.3.0"}}},
{[][]string{[]string{"<1.2.x"}}, [][]string{[]string{"<1.2.0"}}},
{[][]string{[]string{"!=1.2.x"}}, [][]string{[]string{"<1.2.0", ">=1.3.0"}}},
{[][]string{[]string{">=1.x"}}, [][]string{[]string{">=1.0.0"}}},
{[][]string{[]string{"<=1.x"}}, [][]string{[]string{"<2.0.0"}}},
{[][]string{[]string{">1.x"}}, [][]string{[]string{">=2.0.0"}}},
{[][]string{[]string{"<1.x"}}, [][]string{[]string{"<1.0.0"}}},
{[][]string{[]string{"!=1.x"}}, [][]string{[]string{"<1.0.0", ">=2.0.0"}}},
{[][]string{[]string{"1.2.x"}}, [][]string{[]string{">=1.2.0", "<1.3.0"}}},
{[][]string{[]string{"1.x"}}, [][]string{[]string{">=1.0.0", "<2.0.0"}}},
}
for _, tc := range tests {
o, _ := expandWildcardVersion(tc.i)
if !reflect.DeepEqual(tc.o, o) {
t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o)
}
}
}
func TestVersionRangeToRange(t *testing.T) {
vr := versionRange{
v: MustParse("1.2.3"),
c: compLT,
}
rf := vr.rangeFunc()
if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) {
t.Errorf("Invalid conversion to range func")
}
}
func TestRangeAND(t *testing.T) {
v := MustParse("1.2.2")
v1 := MustParse("1.2.1")
v2 := MustParse("1.2.3")
rf1 := Range(func(v Version) bool {
return v.GT(v1)
})
rf2 := Range(func(v Version) bool {
return v.LT(v2)
})
rf := rf1.AND(rf2)
if rf(v1) {
t.Errorf("Invalid rangefunc, accepted: %s", v1)
}
if rf(v2) {
t.Errorf("Invalid rangefunc, accepted: %s", v2)
}
if !rf(v) {
t.Errorf("Invalid rangefunc, did not accept: %s", v)
}
}
func TestRangeOR(t *testing.T) {
tests := []struct {
v Version
b bool
}{
{MustParse("1.2.0"), true},
{MustParse("1.2.2"), false},
{MustParse("1.2.4"), true},
}
v1 := MustParse("1.2.1")
v2 := MustParse("1.2.3")
rf1 := Range(func(v Version) bool {
return v.LT(v1)
})
rf2 := Range(func(v Version) bool {
return v.GT(v2)
})
rf := rf1.OR(rf2)
for _, tc := range tests {
if r := rf(tc.v); r != tc.b {
t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r)
}
}
}
func TestParseRange(t *testing.T) {
type tv struct {
v string
b bool
}
tests := []struct {
i string
t []tv
}{
// Simple expressions
{">1.2.3", []tv{
{"1.2.2", false},
{"1.2.3", false},
{"1.2.4", true},
}},
{">=1.2.3", []tv{
{"1.2.3", true},
{"1.2.4", true},
{"1.2.2", false},
}},
{"<1.2.3", []tv{
{"1.2.2", true},
{"1.2.3", false},
{"1.2.4", false},
}},
{"<=1.2.3", []tv{
{"1.2.2", true},
{"1.2.3", true},
{"1.2.4", false},
}},
{"1.2.3", []tv{
{"1.2.2", false},
{"1.2.3", true},
{"1.2.4", false},
}},
{"=1.2.3", []tv{
{"1.2.2", false},
{"1.2.3", true},
{"1.2.4", false},
}},
{"==1.2.3", []tv{
{"1.2.2", false},
{"1.2.3", true},
{"1.2.4", false},
}},
{"!=1.2.3", []tv{
{"1.2.2", true},
{"1.2.3", false},
{"1.2.4", true},
}},
{"!1.2.3", []tv{
{"1.2.2", true},
{"1.2.3", false},
{"1.2.4", true},
}},
// Simple Expression errors
{">>1.2.3", nil},
{"!1.2.3", nil},
{"1.0", nil},
{"string", nil},
{"", nil},
{"fo.ob.ar.x", nil},
// AND Expressions
{">1.2.2 <1.2.4", []tv{
{"1.2.2", false},
{"1.2.3", true},
{"1.2.4", false},
}},
{"<1.2.2 <1.2.4", []tv{
{"1.2.1", true},
{"1.2.2", false},
{"1.2.3", false},
{"1.2.4", false},
}},
{">1.2.2 <1.2.5 !=1.2.4", []tv{
{"1.2.2", false},
{"1.2.3", true},
{"1.2.4", false},
{"1.2.5", false},
}},
{">1.2.2 <1.2.5 !1.2.4", []tv{
{"1.2.2", false},
{"1.2.3", true},
{"1.2.4", false},
{"1.2.5", false},
}},
// OR Expressions
{">1.2.2 || <1.2.4", []tv{
{"1.2.2", true},
{"1.2.3", true},
{"1.2.4", true},
}},
{"<1.2.2 || >1.2.4", []tv{
{"1.2.2", false},
{"1.2.3", false},
{"1.2.4", false},
}},
// Wildcard expressions
{">1.x", []tv{
{"0.1.9", false},
{"1.2.6", false},
{"1.9.0", false},
{"2.0.0", true},
}},
{">1.2.x", []tv{
{"1.1.9", false},
{"1.2.6", false},
{"1.3.0", true},
}},
// Combined Expressions
{">1.2.2 <1.2.4 || >=2.0.0", []tv{
{"1.2.2", false},
{"1.2.3", true},
{"1.2.4", false},
{"2.0.0", true},
{"2.0.1", true},
}},
{"1.x || >=2.0.x <2.2.x", []tv{
{"0.9.2", false},
{"1.2.2", true},
{"2.0.0", true},
{"2.1.8", true},
{"2.2.0", false},
}},
{">1.2.2 <1.2.4 || >=2.0.0 <3.0.0", []tv{
{"1.2.2", false},
{"1.2.3", true},
{"1.2.4", false},
{"2.0.0", true},
{"2.0.1", true},
{"2.9.9", true},
{"3.0.0", false},
}},
}
for _, tc := range tests {
r, err := ParseRange(tc.i)
if err != nil && tc.t != nil {
t.Errorf("Error parsing range %q: %s", tc.i, err)
continue
}
for _, tvc := range tc.t {
v := MustParse(tvc.v)
if res := r(v); res != tvc.b {
t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res)
}
}
}
}
func TestMustParseRange(t *testing.T) {
testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0"
r := MustParseRange(testCase)
if !r(MustParse("1.2.3")) {
t.Errorf("Unexpected range behavior on MustParseRange")
}
}
func TestMustParseRange_panic(t *testing.T) {
defer func() {
if recover() == nil {
t.Errorf("Should have panicked")
}
}()
_ = MustParseRange("invalid version")
}
func BenchmarkRangeParseSimple(b *testing.B) {
const VERSION = ">1.0.0"
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
ParseRange(VERSION)
}
}
func BenchmarkRangeParseAverage(b *testing.B) {
const VERSION = ">=1.0.0 <2.0.0"
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
ParseRange(VERSION)
}
}
func BenchmarkRangeParseComplex(b *testing.B) {
const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0"
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
ParseRange(VERSION)
}
}
func BenchmarkRangeMatchSimple(b *testing.B) {
const VERSION = ">1.0.0"
r, _ := ParseRange(VERSION)
v := MustParse("2.0.0")
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
r(v)
}
}
func BenchmarkRangeMatchAverage(b *testing.B) {
const VERSION = ">=1.0.0 <2.0.0"
r, _ := ParseRange(VERSION)
v := MustParse("1.2.3")
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
r(v)
}
}
func BenchmarkRangeMatchComplex(b *testing.B) {
const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0"
r, _ := ParseRange(VERSION)
v := MustParse("5.0.1")
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
r(v)
}
}

View file

@ -1,418 +0,0 @@
package semver
import (
"errors"
"fmt"
"strconv"
"strings"
)
const (
numbers string = "0123456789"
alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
alphanum = alphas + numbers
)
// SpecVersion is the latest fully supported spec version of semver
var SpecVersion = Version{
Major: 2,
Minor: 0,
Patch: 0,
}
// Version represents a semver compatible version
type Version struct {
Major uint64
Minor uint64
Patch uint64
Pre []PRVersion
Build []string //No Precendence
}
// Version to string
func (v Version) String() string {
b := make([]byte, 0, 5)
b = strconv.AppendUint(b, v.Major, 10)
b = append(b, '.')
b = strconv.AppendUint(b, v.Minor, 10)
b = append(b, '.')
b = strconv.AppendUint(b, v.Patch, 10)
if len(v.Pre) > 0 {
b = append(b, '-')
b = append(b, v.Pre[0].String()...)
for _, pre := range v.Pre[1:] {
b = append(b, '.')
b = append(b, pre.String()...)
}
}
if len(v.Build) > 0 {
b = append(b, '+')
b = append(b, v.Build[0]...)
for _, build := range v.Build[1:] {
b = append(b, '.')
b = append(b, build...)
}
}
return string(b)
}
// Equals checks if v is equal to o.
func (v Version) Equals(o Version) bool {
return (v.Compare(o) == 0)
}
// EQ checks if v is equal to o.
func (v Version) EQ(o Version) bool {
return (v.Compare(o) == 0)
}
// NE checks if v is not equal to o.
func (v Version) NE(o Version) bool {
return (v.Compare(o) != 0)
}
// GT checks if v is greater than o.
func (v Version) GT(o Version) bool {
return (v.Compare(o) == 1)
}
// GTE checks if v is greater than or equal to o.
func (v Version) GTE(o Version) bool {
return (v.Compare(o) >= 0)
}
// GE checks if v is greater than or equal to o.
func (v Version) GE(o Version) bool {
return (v.Compare(o) >= 0)
}
// LT checks if v is less than o.
func (v Version) LT(o Version) bool {
return (v.Compare(o) == -1)
}
// LTE checks if v is less than or equal to o.
func (v Version) LTE(o Version) bool {
return (v.Compare(o) <= 0)
}
// LE checks if v is less than or equal to o.
func (v Version) LE(o Version) bool {
return (v.Compare(o) <= 0)
}
// Compare compares Versions v to o:
// -1 == v is less than o
// 0 == v is equal to o
// 1 == v is greater than o
func (v Version) Compare(o Version) int {
if v.Major != o.Major {
if v.Major > o.Major {
return 1
}
return -1
}
if v.Minor != o.Minor {
if v.Minor > o.Minor {
return 1
}
return -1
}
if v.Patch != o.Patch {
if v.Patch > o.Patch {
return 1
}
return -1
}
// Quick comparison if a version has no prerelease versions
if len(v.Pre) == 0 && len(o.Pre) == 0 {
return 0
} else if len(v.Pre) == 0 && len(o.Pre) > 0 {
return 1
} else if len(v.Pre) > 0 && len(o.Pre) == 0 {
return -1
}
i := 0
for ; i < len(v.Pre) && i < len(o.Pre); i++ {
if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
continue
} else if comp == 1 {
return 1
} else {
return -1
}
}
// If all pr versions are the equal but one has further prversion, this one greater
if i == len(v.Pre) && i == len(o.Pre) {
return 0
} else if i == len(v.Pre) && i < len(o.Pre) {
return -1
} else {
return 1
}
}
// Validate validates v and returns error in case
func (v Version) Validate() error {
// Major, Minor, Patch already validated using uint64
for _, pre := range v.Pre {
if !pre.IsNum { //Numeric prerelease versions already uint64
if len(pre.VersionStr) == 0 {
return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
}
if !containsOnly(pre.VersionStr, alphanum) {
return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
}
}
}
for _, build := range v.Build {
if len(build) == 0 {
return fmt.Errorf("Build meta data can not be empty %q", build)
}
if !containsOnly(build, alphanum) {
return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
}
}
return nil
}
// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
func New(s string) (vp *Version, err error) {
v, err := Parse(s)
vp = &v
return
}
// Make is an alias for Parse, parses version string and returns a validated Version or error
func Make(s string) (Version, error) {
return Parse(s)
}
// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
// specs to be parsed by this library. It does so by normalizing versions before passing them to
// Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions
// with only major and minor components specified
func ParseTolerant(s string) (Version, error) {
s = strings.TrimSpace(s)
s = strings.TrimPrefix(s, "v")
// Split into major.minor.(patch+pr+meta)
parts := strings.SplitN(s, ".", 3)
if len(parts) < 3 {
if strings.ContainsAny(parts[len(parts)-1], "+-") {
return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
}
for len(parts) < 3 {
parts = append(parts, "0")
}
s = strings.Join(parts, ".")
}
return Parse(s)
}
// Parse parses version string and returns a validated Version or error
func Parse(s string) (Version, error) {
if len(s) == 0 {
return Version{}, errors.New("Version string empty")
}
// Split into major.minor.(patch+pr+meta)
parts := strings.SplitN(s, ".", 3)
if len(parts) != 3 {
return Version{}, errors.New("No Major.Minor.Patch elements found")
}
// Major
if !containsOnly(parts[0], numbers) {
return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
}
if hasLeadingZeroes(parts[0]) {
return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
}
major, err := strconv.ParseUint(parts[0], 10, 64)
if err != nil {
return Version{}, err
}
// Minor
if !containsOnly(parts[1], numbers) {
return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
}
if hasLeadingZeroes(parts[1]) {
return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
}
minor, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
return Version{}, err
}
v := Version{}
v.Major = major
v.Minor = minor
var build, prerelease []string
patchStr := parts[2]
if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
build = strings.Split(patchStr[buildIndex+1:], ".")
patchStr = patchStr[:buildIndex]
}
if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
prerelease = strings.Split(patchStr[preIndex+1:], ".")
patchStr = patchStr[:preIndex]
}
if !containsOnly(patchStr, numbers) {
return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
}
if hasLeadingZeroes(patchStr) {
return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
}
patch, err := strconv.ParseUint(patchStr, 10, 64)
if err != nil {
return Version{}, err
}
v.Patch = patch
// Prerelease
for _, prstr := range prerelease {
parsedPR, err := NewPRVersion(prstr)
if err != nil {
return Version{}, err
}
v.Pre = append(v.Pre, parsedPR)
}
// Build meta data
for _, str := range build {
if len(str) == 0 {
return Version{}, errors.New("Build meta data is empty")
}
if !containsOnly(str, alphanum) {
return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
}
v.Build = append(v.Build, str)
}
return v, nil
}
// MustParse is like Parse but panics if the version cannot be parsed.
func MustParse(s string) Version {
v, err := Parse(s)
if err != nil {
panic(`semver: Parse(` + s + `): ` + err.Error())
}
return v
}
// PRVersion represents a PreRelease Version
type PRVersion struct {
VersionStr string
VersionNum uint64
IsNum bool
}
// NewPRVersion creates a new valid prerelease version
func NewPRVersion(s string) (PRVersion, error) {
if len(s) == 0 {
return PRVersion{}, errors.New("Prerelease is empty")
}
v := PRVersion{}
if containsOnly(s, numbers) {
if hasLeadingZeroes(s) {
return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
}
num, err := strconv.ParseUint(s, 10, 64)
// Might never be hit, but just in case
if err != nil {
return PRVersion{}, err
}
v.VersionNum = num
v.IsNum = true
} else if containsOnly(s, alphanum) {
v.VersionStr = s
v.IsNum = false
} else {
return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
}
return v, nil
}
// IsNumeric checks if prerelease-version is numeric
func (v PRVersion) IsNumeric() bool {
return v.IsNum
}
// Compare compares two PreRelease Versions v and o:
// -1 == v is less than o
// 0 == v is equal to o
// 1 == v is greater than o
func (v PRVersion) Compare(o PRVersion) int {
if v.IsNum && !o.IsNum {
return -1
} else if !v.IsNum && o.IsNum {
return 1
} else if v.IsNum && o.IsNum {
if v.VersionNum == o.VersionNum {
return 0
} else if v.VersionNum > o.VersionNum {
return 1
} else {
return -1
}
} else { // both are Alphas
if v.VersionStr == o.VersionStr {
return 0
} else if v.VersionStr > o.VersionStr {
return 1
} else {
return -1
}
}
}
// PreRelease version to string
func (v PRVersion) String() string {
if v.IsNum {
return strconv.FormatUint(v.VersionNum, 10)
}
return v.VersionStr
}
func containsOnly(s string, set string) bool {
return strings.IndexFunc(s, func(r rune) bool {
return !strings.ContainsRune(set, r)
}) == -1
}
func hasLeadingZeroes(s string) bool {
return len(s) > 1 && s[0] == '0'
}
// NewBuildVersion creates a new valid build version
func NewBuildVersion(s string) (string, error) {
if len(s) == 0 {
return "", errors.New("Buildversion is empty")
}
if !containsOnly(s, alphanum) {
return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
}
return s, nil
}

View file

@ -1,458 +0,0 @@
package semver
import (
"testing"
)
func prstr(s string) PRVersion {
return PRVersion{s, 0, false}
}
func prnum(i uint64) PRVersion {
return PRVersion{"", i, true}
}
type formatTest struct {
v Version
result string
}
var formatTests = []formatTest{
{Version{1, 2, 3, nil, nil}, "1.2.3"},
{Version{0, 0, 1, nil, nil}, "0.0.1"},
{Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1-alpha.preview+123.456"},
{Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3-alpha.1+123.456"},
{Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3-alpha.1"},
{Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3+123.456"},
// Prereleases and build metadata hyphens
{Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3-alpha.b-eta+123.b-uild"},
{Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3+123.b-uild"},
{Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3-alpha.b-eta"},
}
var tolerantFormatTests = []formatTest{
{Version{1, 2, 3, nil, nil}, "v1.2.3"},
{Version{1, 2, 3, nil, nil}, " 1.2.3 "},
{Version{1, 2, 0, nil, nil}, "1.2"},
{Version{1, 0, 0, nil, nil}, "1"},
}
func TestStringer(t *testing.T) {
for _, test := range formatTests {
if res := test.v.String(); res != test.result {
t.Errorf("Stringer, expected %q but got %q", test.result, res)
}
}
}
func TestParse(t *testing.T) {
for _, test := range formatTests {
if v, err := Parse(test.result); err != nil {
t.Errorf("Error parsing %q: %q", test.result, err)
} else if comp := v.Compare(test.v); comp != 0 {
t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp)
} else if err := v.Validate(); err != nil {
t.Errorf("Error validating parsed version %q: %q", test.v, err)
}
}
}
func TestParseTolerant(t *testing.T) {
for _, test := range tolerantFormatTests {
if v, err := ParseTolerant(test.result); err != nil {
t.Errorf("Error parsing %q: %q", test.result, err)
} else if comp := v.Compare(test.v); comp != 0 {
t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp)
} else if err := v.Validate(); err != nil {
t.Errorf("Error validating parsed version %q: %q", test.v, err)
}
}
}
func TestMustParse(t *testing.T) {
_ = MustParse("32.2.1-alpha")
}
func TestMustParse_panic(t *testing.T) {
defer func() {
if recover() == nil {
t.Errorf("Should have panicked")
}
}()
_ = MustParse("invalid version")
}
func TestValidate(t *testing.T) {
for _, test := range formatTests {
if err := test.v.Validate(); err != nil {
t.Errorf("Error validating %q: %q", test.v, err)
}
}
}
type compareTest struct {
v1 Version
v2 Version
result int
}
var compareTests = []compareTest{
{Version{1, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 0},
{Version{2, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 1},
{Version{0, 1, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 0},
{Version{0, 2, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 1},
{Version{0, 0, 1, nil, nil}, Version{0, 0, 1, nil, nil}, 0},
{Version{0, 0, 2, nil, nil}, Version{0, 0, 1, nil, nil}, 1},
{Version{1, 2, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 0},
{Version{2, 2, 4, nil, nil}, Version{1, 2, 4, nil, nil}, 1},
{Version{1, 3, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 1},
{Version{1, 2, 4, nil, nil}, Version{1, 2, 3, nil, nil}, 1},
// Spec Examples #11
{Version{1, 0, 0, nil, nil}, Version{2, 0, 0, nil, nil}, -1},
{Version{2, 0, 0, nil, nil}, Version{2, 1, 0, nil, nil}, -1},
{Version{2, 1, 0, nil, nil}, Version{2, 1, 1, nil, nil}, -1},
// Spec Examples #9
{Version{1, 0, 0, nil, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, 1},
{Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, -1},
{Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, -1},
{Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, -1},
{Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, -1},
{Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, -1},
{Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, -1},
{Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, Version{1, 0, 0, nil, nil}, -1},
// Ignore Build metadata
{Version{1, 0, 0, nil, []string{"1", "2", "3"}}, Version{1, 0, 0, nil, nil}, 0},
}
func TestCompare(t *testing.T) {
for _, test := range compareTests {
if res := test.v1.Compare(test.v2); res != test.result {
t.Errorf("Comparing %q : %q, expected %d but got %d", test.v1, test.v2, test.result, res)
}
//Test counterpart
if res := test.v2.Compare(test.v1); res != -test.result {
t.Errorf("Comparing %q : %q, expected %d but got %d", test.v2, test.v1, -test.result, res)
}
}
}
type wrongformatTest struct {
v *Version
str string
}
var wrongformatTests = []wrongformatTest{
{nil, ""},
{nil, "."},
{nil, "1."},
{nil, ".1"},
{nil, "a.b.c"},
{nil, "1.a.b"},
{nil, "1.1.a"},
{nil, "1.a.1"},
{nil, "a.1.1"},
{nil, ".."},
{nil, "1.."},
{nil, "1.1."},
{nil, "1..1"},
{nil, "1.1.+123"},
{nil, "1.1.-beta"},
{nil, "-1.1.1"},
{nil, "1.-1.1"},
{nil, "1.1.-1"},
// giant numbers
{nil, "20000000000000000000.1.1"},
{nil, "1.20000000000000000000.1"},
{nil, "1.1.20000000000000000000"},
{nil, "1.1.1-20000000000000000000"},
// Leading zeroes
{nil, "01.1.1"},
{nil, "001.1.1"},
{nil, "1.01.1"},
{nil, "1.001.1"},
{nil, "1.1.01"},
{nil, "1.1.001"},
{nil, "1.1.1-01"},
{nil, "1.1.1-001"},
{nil, "1.1.1-beta.01"},
{nil, "1.1.1-beta.001"},
{&Version{0, 0, 0, []PRVersion{prstr("!")}, nil}, "0.0.0-!"},
{&Version{0, 0, 0, nil, []string{"!"}}, "0.0.0+!"},
// empty prversion
{&Version{0, 0, 0, []PRVersion{prstr(""), prstr("alpha")}, nil}, "0.0.0-.alpha"},
// empty build meta data
{&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{""}}, "0.0.0-alpha+"},
{&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{"test", ""}}, "0.0.0-alpha+test."},
}
func TestWrongFormat(t *testing.T) {
for _, test := range wrongformatTests {
if res, err := Parse(test.str); err == nil {
t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res)
}
if test.v != nil {
if err := test.v.Validate(); err == nil {
t.Errorf("Validating wrong format version %q (%q), expected error", test.v, test.str)
}
}
}
}
var wrongTolerantFormatTests = []wrongformatTest{
{nil, "1.0+abc"},
{nil, "1.0-rc.1"},
}
func TestWrongTolerantFormat(t *testing.T) {
for _, test := range wrongTolerantFormatTests {
if res, err := ParseTolerant(test.str); err == nil {
t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res)
}
}
}
func TestCompareHelper(t *testing.T) {
v := Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}
v1 := Version{1, 0, 0, nil, nil}
if !v.EQ(v) {
t.Errorf("%q should be equal to %q", v, v)
}
if !v.Equals(v) {
t.Errorf("%q should be equal to %q", v, v)
}
if !v1.NE(v) {
t.Errorf("%q should not be equal to %q", v1, v)
}
if !v.GTE(v) {
t.Errorf("%q should be greater than or equal to %q", v, v)
}
if !v.LTE(v) {
t.Errorf("%q should be less than or equal to %q", v, v)
}
if !v.LT(v1) {
t.Errorf("%q should be less than %q", v, v1)
}
if !v.LTE(v1) {
t.Errorf("%q should be less than or equal %q", v, v1)
}
if !v.LE(v1) {
t.Errorf("%q should be less than or equal %q", v, v1)
}
if !v1.GT(v) {
t.Errorf("%q should be greater than %q", v1, v)
}
if !v1.GTE(v) {
t.Errorf("%q should be greater than or equal %q", v1, v)
}
if !v1.GE(v) {
t.Errorf("%q should be greater than or equal %q", v1, v)
}
}
func TestPreReleaseVersions(t *testing.T) {
p1, err := NewPRVersion("123")
if !p1.IsNumeric() {
t.Errorf("Expected numeric prversion, got %q", p1)
}
if p1.VersionNum != 123 {
t.Error("Wrong prversion number")
}
if err != nil {
t.Errorf("Not expected error %q", err)
}
p2, err := NewPRVersion("alpha")
if p2.IsNumeric() {
t.Errorf("Expected non-numeric prversion, got %q", p2)
}
if p2.VersionStr != "alpha" {
t.Error("Wrong prversion string")
}
if err != nil {
t.Errorf("Not expected error %q", err)
}
}
func TestBuildMetaDataVersions(t *testing.T) {
_, err := NewBuildVersion("123")
if err != nil {
t.Errorf("Unexpected error %q", err)
}
_, err = NewBuildVersion("build")
if err != nil {
t.Errorf("Unexpected error %q", err)
}
_, err = NewBuildVersion("test?")
if err == nil {
t.Error("Expected error, got none")
}
_, err = NewBuildVersion("")
if err == nil {
t.Error("Expected error, got none")
}
}
func TestNewHelper(t *testing.T) {
v, err := New("1.2.3")
if err != nil {
t.Fatalf("Unexpected error %q", err)
}
// New returns pointer
if v == nil {
t.Fatal("Version is nil")
}
if v.Compare(Version{1, 2, 3, nil, nil}) != 0 {
t.Fatal("Unexpected comparison problem")
}
}
func TestMakeHelper(t *testing.T) {
v, err := Make("1.2.3")
if err != nil {
t.Fatalf("Unexpected error %q", err)
}
if v.Compare(Version{1, 2, 3, nil, nil}) != 0 {
t.Fatal("Unexpected comparison problem")
}
}
func BenchmarkParseSimple(b *testing.B) {
const VERSION = "0.0.1"
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
Parse(VERSION)
}
}
func BenchmarkParseComplex(b *testing.B) {
const VERSION = "0.0.1-alpha.preview+123.456"
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
Parse(VERSION)
}
}
func BenchmarkParseAverage(b *testing.B) {
l := len(formatTests)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
Parse(formatTests[n%l].result)
}
}
func BenchmarkParseTolerantAverage(b *testing.B) {
l := len(tolerantFormatTests)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
ParseTolerant(tolerantFormatTests[n%l].result)
}
}
func BenchmarkStringSimple(b *testing.B) {
const VERSION = "0.0.1"
v, _ := Parse(VERSION)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
v.String()
}
}
func BenchmarkStringLarger(b *testing.B) {
const VERSION = "11.15.2012"
v, _ := Parse(VERSION)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
v.String()
}
}
func BenchmarkStringComplex(b *testing.B) {
const VERSION = "0.0.1-alpha.preview+123.456"
v, _ := Parse(VERSION)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
v.String()
}
}
func BenchmarkStringAverage(b *testing.B) {
l := len(formatTests)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
formatTests[n%l].v.String()
}
}
func BenchmarkValidateSimple(b *testing.B) {
const VERSION = "0.0.1"
v, _ := Parse(VERSION)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
v.Validate()
}
}
func BenchmarkValidateComplex(b *testing.B) {
const VERSION = "0.0.1-alpha.preview+123.456"
v, _ := Parse(VERSION)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
v.Validate()
}
}
func BenchmarkValidateAverage(b *testing.B) {
l := len(formatTests)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
formatTests[n%l].v.Validate()
}
}
func BenchmarkCompareSimple(b *testing.B) {
const VERSION = "0.0.1"
v, _ := Parse(VERSION)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
v.Compare(v)
}
}
func BenchmarkCompareComplex(b *testing.B) {
const VERSION = "0.0.1-alpha.preview+123.456"
v, _ := Parse(VERSION)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
v.Compare(v)
}
}
func BenchmarkCompareAverage(b *testing.B) {
l := len(compareTests)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
compareTests[n%l].v1.Compare((compareTests[n%l].v2))
}
}

View file

@ -1,28 +0,0 @@
package semver
import (
"sort"
)
// Versions represents multiple versions.
type Versions []Version
// Len returns length of version collection
func (s Versions) Len() int {
return len(s)
}
// Swap swaps two versions inside the collection by its indices
func (s Versions) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less checks if version at index i is less than version at index j
func (s Versions) Less(i, j int) bool {
return s[i].LT(s[j])
}
// Sort sorts a slice of versions
func Sort(versions []Version) {
sort.Sort(Versions(versions))
}

View file

@ -1,30 +0,0 @@
package semver
import (
"reflect"
"testing"
)
func TestSort(t *testing.T) {
v100, _ := Parse("1.0.0")
v010, _ := Parse("0.1.0")
v001, _ := Parse("0.0.1")
versions := []Version{v010, v100, v001}
Sort(versions)
correct := []Version{v001, v010, v100}
if !reflect.DeepEqual(versions, correct) {
t.Fatalf("Sort returned wrong order: %s", versions)
}
}
func BenchmarkSort(b *testing.B) {
v100, _ := Parse("1.0.0")
v010, _ := Parse("0.1.0")
v001, _ := Parse("0.0.1")
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
Sort([]Version{v010, v100, v001})
}
}

View file

@ -1,30 +0,0 @@
package semver
import (
"database/sql/driver"
"fmt"
)
// Scan implements the database/sql.Scanner interface.
func (v *Version) Scan(src interface{}) (err error) {
var str string
switch src := src.(type) {
case string:
str = src
case []byte:
str = string(src)
default:
return fmt.Errorf("Version.Scan: cannot convert %T to string.", src)
}
if t, err := Parse(str); err == nil {
*v = t
}
return
}
// Value implements the database/sql/driver.Valuer interface.
func (v Version) Value() (driver.Value, error) {
return v.String(), nil
}

View file

@ -1,38 +0,0 @@
package semver
import (
"testing"
)
type scanTest struct {
val interface{}
shouldError bool
expected string
}
var scanTests = []scanTest{
{"1.2.3", false, "1.2.3"},
{[]byte("1.2.3"), false, "1.2.3"},
{7, true, ""},
{7e4, true, ""},
{true, true, ""},
}
func TestScanString(t *testing.T) {
for _, tc := range scanTests {
s := &Version{}
err := s.Scan(tc.val)
if tc.shouldError {
if err == nil {
t.Fatalf("Scan did not return an error on %v (%T)", tc.val, tc.val)
}
} else {
if err != nil {
t.Fatalf("Scan returned an unexpected error: %s (%T) on %v (%T)", tc.val, tc.val, tc.val, tc.val)
}
if val, _ := s.Value(); val != tc.expected {
t.Errorf("Wrong Value returned, expected %q, got %q", tc.expected, val)
}
}
}
}

View file

@ -1,2 +0,0 @@
/bin
/gopath

View file

@ -1,16 +0,0 @@
language: go
go:
- 1.7.5
- 1.8
install:
- go get -v -t github.com/coreos/go-oidc/...
- go get golang.org/x/tools/cmd/cover
- go get github.com/golang/lint/golint
script:
- ./test
notifications:
email: false

View file

@ -1,71 +0,0 @@
# How to Contribute
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
GitHub pull requests. This document outlines some of the conventions on
development workflow, commit message formatting, contact points and other
resources to make it easier to get your contribution accepted.
# Certificate of Origin
By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the
contribution. See the [DCO](DCO) file for details.
# Email and Chat
The project currently uses the general CoreOS email list and IRC channel:
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
are very busy and read the mailing lists.
## Getting Started
- Fork the repository on GitHub
- Read the [README](README.md) for build and test instructions
- Play with the project, submit bugs, submit patches!
## Contribution Flow
This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
Thanks for your contributions!
### Format of the Commit Message
We follow a rough convention for commit messages that is designed to answer two
questions: what changed and why. The subject line should feature the what and
the body of the commit should describe the why.
```
scripts: add the test-cluster command
this uses tmux to setup a test cluster that you can easily kill and
start for debugging.
Fixes #38
```
The format can be described more formally as follows:
```
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.

36
vendor/github.com/coreos/go-oidc/DCO generated vendored
View file

@ -1,36 +0,0 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

View file

@ -1,3 +0,0 @@
Bobby Rullo <bobby.rullo@coreos.com> (@bobbyrullo)
Ed Rooth <ed.rooth@coreos.com> (@sym3tri)
Eric Chiang <eric.chiang@coreos.com> (@ericchiang)

View file

@ -1,5 +0,0 @@
CoreOS Project
Copyright 2014 CoreOS, Inc
This product includes software developed at CoreOS, Inc.
(http://www.coreos.com/).

View file

@ -1,72 +0,0 @@
# go-oidc
[![GoDoc](https://godoc.org/github.com/coreos/go-oidc?status.svg)](https://godoc.org/github.com/coreos/go-oidc)
[![Build Status](https://travis-ci.org/coreos/go-oidc.png?branch=master)](https://travis-ci.org/coreos/go-oidc)
## OpenID Connect support for Go
This package enables OpenID Connect support for the [golang.org/x/oauth2](https://godoc.org/golang.org/x/oauth2) package.
```go
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
// handle error
}
// Configure an OpenID Connect aware OAuth2 client.
oauth2Config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
// Discovery returns the OAuth2 endpoints.
Endpoint: provider.Endpoint(),
// "openid" is a required scope for OpenID Connect flows.
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
```
OAuth2 redirects are unchanged.
```go
func handleRedirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
}
```
The on responses, the provider can be used to verify ID Tokens.
```go
var verifier = provider.Verifier(&oidc.Config{ClientID: clientID})
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
// Verify state and errors.
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
// handle error
}
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
// handle missing token
}
// Parse and verify ID Token payload.
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
// handle error
}
// Extract custom claims
var claims struct {
Email string `json:"email"`
Verified bool `json:"email_verified"`
}
if err := idToken.Claims(&claims); err != nil {
// handle error
}
}
```

View file

@ -1,7 +0,0 @@
package http
import "net/http"
type Client interface {
Do(*http.Request) (*http.Response, error)
}

View file

@ -1,2 +0,0 @@
// Package http is DEPRECATED. Use net/http instead.
package http

View file

@ -1,161 +0,0 @@
package http
import (
"encoding/base64"
"encoding/json"
"errors"
"log"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
)
func WriteError(w http.ResponseWriter, code int, msg string) {
e := struct {
Error string `json:"error"`
}{
Error: msg,
}
b, err := json.Marshal(e)
if err != nil {
log.Printf("go-oidc: failed to marshal %#v: %v", e, err)
code = http.StatusInternalServerError
b = []byte(`{"error":"server_error"}`)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(b)
}
// BasicAuth parses a username and password from the request's
// Authorization header. This was pulled from golang master:
// https://codereview.appspot.com/76540043
func BasicAuth(r *http.Request) (username, password string, ok bool) {
auth := r.Header.Get("Authorization")
if auth == "" {
return
}
if !strings.HasPrefix(auth, "Basic ") {
return
}
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic "))
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
func cacheControlMaxAge(hdr string) (time.Duration, bool, error) {
for _, field := range strings.Split(hdr, ",") {
parts := strings.SplitN(strings.TrimSpace(field), "=", 2)
k := strings.ToLower(strings.TrimSpace(parts[0]))
if k != "max-age" {
continue
}
if len(parts) == 1 {
return 0, false, errors.New("max-age has no value")
}
v := strings.TrimSpace(parts[1])
if v == "" {
return 0, false, errors.New("max-age has empty value")
}
age, err := strconv.Atoi(v)
if err != nil {
return 0, false, err
}
if age <= 0 {
return 0, false, nil
}
return time.Duration(age) * time.Second, true, nil
}
return 0, false, nil
}
func expires(date, expires string) (time.Duration, bool, error) {
if date == "" || expires == "" {
return 0, false, nil
}
var te time.Time
var err error
if expires == "0" {
return 0, false, nil
}
te, err = time.Parse(time.RFC1123, expires)
if err != nil {
return 0, false, err
}
td, err := time.Parse(time.RFC1123, date)
if err != nil {
return 0, false, err
}
ttl := te.Sub(td)
// headers indicate data already expired, caller should not
// have to care about this case
if ttl <= 0 {
return 0, false, nil
}
return ttl, true, nil
}
func Cacheable(hdr http.Header) (time.Duration, bool, error) {
ttl, ok, err := cacheControlMaxAge(hdr.Get("Cache-Control"))
if err != nil || ok {
return ttl, ok, err
}
return expires(hdr.Get("Date"), hdr.Get("Expires"))
}
// MergeQuery appends additional query values to an existing URL.
func MergeQuery(u url.URL, q url.Values) url.URL {
uv := u.Query()
for k, vs := range q {
for _, v := range vs {
uv.Add(k, v)
}
}
u.RawQuery = uv.Encode()
return u
}
// NewResourceLocation appends a resource id to the end of the requested URL path.
func NewResourceLocation(reqURL *url.URL, id string) string {
var u url.URL
u = *reqURL
u.Path = path.Join(u.Path, id)
u.RawQuery = ""
u.Fragment = ""
return u.String()
}
// CopyRequest returns a clone of the provided *http.Request.
// The returned object is a shallow copy of the struct and a
// deep copy of its Header field.
func CopyRequest(r *http.Request) *http.Request {
r2 := *r
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return &r2
}

View file

@ -1,374 +0,0 @@
package http
import (
"net/http"
"net/url"
"reflect"
"testing"
"time"
)
func TestCacheControlMaxAgeSuccess(t *testing.T) {
tests := []struct {
hdr string
wantAge time.Duration
wantOK bool
}{
{"max-age=12", 12 * time.Second, true},
{"max-age=-12", 0, false},
{"max-age=0", 0, false},
{"public, max-age=12", 12 * time.Second, true},
{"public, max-age=40192, must-revalidate", 40192 * time.Second, true},
{"public, not-max-age=12, must-revalidate", time.Duration(0), false},
}
for i, tt := range tests {
maxAge, ok, err := cacheControlMaxAge(tt.hdr)
if err != nil {
t.Errorf("case %d: err=%v", i, err)
}
if tt.wantAge != maxAge {
t.Errorf("case %d: want=%d got=%d", i, tt.wantAge, maxAge)
}
if tt.wantOK != ok {
t.Errorf("case %d: incorrect ok value: want=%t got=%t", i, tt.wantOK, ok)
}
}
}
func TestCacheControlMaxAgeFail(t *testing.T) {
tests := []string{
"max-age=aasdf",
"max-age=",
"max-age",
}
for i, tt := range tests {
_, ok, err := cacheControlMaxAge(tt)
if ok {
t.Errorf("case %d: want ok=false, got true", i)
}
if err == nil {
t.Errorf("case %d: want non-nil err", i)
}
}
}
func TestMergeQuery(t *testing.T) {
tests := []struct {
u string
q url.Values
w string
}{
// No values
{
u: "http://example.com",
q: nil,
w: "http://example.com",
},
// No additional values
{
u: "http://example.com?foo=bar",
q: nil,
w: "http://example.com?foo=bar",
},
// Simple addition
{
u: "http://example.com",
q: url.Values{
"foo": []string{"bar"},
},
w: "http://example.com?foo=bar",
},
// Addition with existing values
{
u: "http://example.com?dog=boo",
q: url.Values{
"foo": []string{"bar"},
},
w: "http://example.com?dog=boo&foo=bar",
},
// Merge
{
u: "http://example.com?dog=boo",
q: url.Values{
"dog": []string{"elroy"},
},
w: "http://example.com?dog=boo&dog=elroy",
},
// Add and merge
{
u: "http://example.com?dog=boo",
q: url.Values{
"dog": []string{"elroy"},
"foo": []string{"bar"},
},
w: "http://example.com?dog=boo&dog=elroy&foo=bar",
},
// Multivalue merge
{
u: "http://example.com?dog=boo",
q: url.Values{
"dog": []string{"elroy", "penny"},
},
w: "http://example.com?dog=boo&dog=elroy&dog=penny",
},
}
for i, tt := range tests {
ur, err := url.Parse(tt.u)
if err != nil {
t.Errorf("case %d: failed parsing test url: %v, error: %v", i, tt.u, err)
}
got := MergeQuery(*ur, tt.q)
want, err := url.Parse(tt.w)
if err != nil {
t.Errorf("case %d: failed parsing want url: %v, error: %v", i, tt.w, err)
}
if !reflect.DeepEqual(*want, got) {
t.Errorf("case %d: want: %v, got: %v", i, *want, got)
}
}
}
func TestExpiresPass(t *testing.T) {
tests := []struct {
date string
exp string
wantTTL time.Duration
wantOK bool
}{
// Expires and Date properly set
{
date: "Thu, 01 Dec 1983 22:00:00 GMT",
exp: "Fri, 02 Dec 1983 01:00:00 GMT",
wantTTL: 10800 * time.Second,
wantOK: true,
},
// empty headers
{
date: "",
exp: "",
wantOK: false,
},
// lack of Expirs short-ciruits Date parsing
{
date: "foo",
exp: "",
wantOK: false,
},
// lack of Date short-ciruits Expires parsing
{
date: "",
exp: "foo",
wantOK: false,
},
// no Date
{
exp: "Thu, 01 Dec 1983 22:00:00 GMT",
wantTTL: 0,
wantOK: false,
},
// no Expires
{
date: "Thu, 01 Dec 1983 22:00:00 GMT",
wantTTL: 0,
wantOK: false,
},
// Expires set to false
{
date: "Thu, 01 Dec 1983 22:00:00 GMT",
exp: "0",
wantTTL: 0,
wantOK: false,
},
// Expires < Date
{
date: "Fri, 02 Dec 1983 01:00:00 GMT",
exp: "Thu, 01 Dec 1983 22:00:00 GMT",
wantTTL: 0,
wantOK: false,
},
}
for i, tt := range tests {
ttl, ok, err := expires(tt.date, tt.exp)
if err != nil {
t.Errorf("case %d: err=%v", i, err)
}
if tt.wantTTL != ttl {
t.Errorf("case %d: want=%d got=%d", i, tt.wantTTL, ttl)
}
if tt.wantOK != ok {
t.Errorf("case %d: incorrect ok value: want=%t got=%t", i, tt.wantOK, ok)
}
}
}
func TestExpiresFail(t *testing.T) {
tests := []struct {
date string
exp string
}{
// malformed Date header
{
date: "foo",
exp: "Fri, 02 Dec 1983 01:00:00 GMT",
},
// malformed exp header
{
date: "Fri, 02 Dec 1983 01:00:00 GMT",
exp: "bar",
},
}
for i, tt := range tests {
_, _, err := expires(tt.date, tt.exp)
if err == nil {
t.Errorf("case %d: expected non-nil error", i)
}
}
}
func TestCacheablePass(t *testing.T) {
tests := []struct {
headers http.Header
wantTTL time.Duration
wantOK bool
}{
// valid Cache-Control
{
headers: http.Header{
"Cache-Control": []string{"max-age=100"},
},
wantTTL: 100 * time.Second,
wantOK: true,
},
// valid Date/Expires
{
headers: http.Header{
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
"Expires": []string{"Fri, 02 Dec 1983 01:00:00 GMT"},
},
wantTTL: 10800 * time.Second,
wantOK: true,
},
// Cache-Control supersedes Date/Expires
{
headers: http.Header{
"Cache-Control": []string{"max-age=100"},
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
"Expires": []string{"Fri, 02 Dec 1983 01:00:00 GMT"},
},
wantTTL: 100 * time.Second,
wantOK: true,
},
// no caching headers
{
headers: http.Header{},
wantOK: false,
},
}
for i, tt := range tests {
ttl, ok, err := Cacheable(tt.headers)
if err != nil {
t.Errorf("case %d: err=%v", i, err)
continue
}
if tt.wantTTL != ttl {
t.Errorf("case %d: want=%d got=%d", i, tt.wantTTL, ttl)
}
if tt.wantOK != ok {
t.Errorf("case %d: incorrect ok value: want=%t got=%t", i, tt.wantOK, ok)
}
}
}
func TestCacheableFail(t *testing.T) {
tests := []http.Header{
// invalid Cache-Control short-circuits
http.Header{
"Cache-Control": []string{"max-age"},
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
"Expires": []string{"Fri, 02 Dec 1983 01:00:00 GMT"},
},
// no Cache-Control, invalid Expires
http.Header{
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
"Expires": []string{"boo"},
},
}
for i, tt := range tests {
_, _, err := Cacheable(tt)
if err == nil {
t.Errorf("case %d: want non-nil err", i)
}
}
}
func TestNewResourceLocation(t *testing.T) {
tests := []struct {
ru *url.URL
id string
want string
}{
{
ru: &url.URL{
Scheme: "http",
Host: "example.com",
},
id: "foo",
want: "http://example.com/foo",
},
// https
{
ru: &url.URL{
Scheme: "https",
Host: "example.com",
},
id: "foo",
want: "https://example.com/foo",
},
// with path
{
ru: &url.URL{
Scheme: "http",
Host: "example.com",
Path: "one/two/three",
},
id: "foo",
want: "http://example.com/one/two/three/foo",
},
// with fragment
{
ru: &url.URL{
Scheme: "http",
Host: "example.com",
Fragment: "frag",
},
id: "foo",
want: "http://example.com/foo",
},
// with query
{
ru: &url.URL{
Scheme: "http",
Host: "example.com",
RawQuery: "dog=elroy",
},
id: "foo",
want: "http://example.com/foo",
},
}
for i, tt := range tests {
got := NewResourceLocation(tt.ru, tt.id)
if tt.want != got {
t.Errorf("case %d: want=%s, got=%s", i, tt.want, got)
}
}
}

View file

@ -1,29 +0,0 @@
package http
import (
"errors"
"net/url"
)
// ParseNonEmptyURL checks that a string is a parsable URL which is also not empty
// since `url.Parse("")` does not return an error. Must contian a scheme and a host.
func ParseNonEmptyURL(u string) (*url.URL, error) {
if u == "" {
return nil, errors.New("url is empty")
}
ur, err := url.Parse(u)
if err != nil {
return nil, err
}
if ur.Scheme == "" {
return nil, errors.New("url scheme is empty")
}
if ur.Host == "" {
return nil, errors.New("url host is empty")
}
return ur, nil
}

View file

@ -1,49 +0,0 @@
package http
import (
"net/url"
"testing"
)
func TestParseNonEmptyURL(t *testing.T) {
tests := []struct {
u string
ok bool
}{
{"", false},
{"http://", false},
{"example.com", false},
{"example", false},
{"http://example", true},
{"http://example:1234", true},
{"http://example.com", true},
{"http://example.com:1234", true},
}
for i, tt := range tests {
u, err := ParseNonEmptyURL(tt.u)
if err != nil {
t.Logf("err: %v", err)
if tt.ok {
t.Errorf("case %d: unexpected error: %v", i, err)
} else {
continue
}
}
if !tt.ok {
t.Errorf("case %d: expected error but got none", i)
continue
}
uu, err := url.Parse(tt.u)
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
if uu.String() != u.String() {
t.Errorf("case %d: incorrect url value, want: %q, got: %q", i, uu.String(), u.String())
}
}
}

View file

@ -1,20 +0,0 @@
// +build !golint
// Don't lint this file. We don't want to have to add a comment to each constant.
package oidc
const (
// JOSE asymmetric signing algorithm values as defined by RFC 7518
//
// see: https://tools.ietf.org/html/rfc7518#section-3.1
RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
ES256 = "ES256" // ECDSA using P-256 and SHA-256
ES384 = "ES384" // ECDSA using P-384 and SHA-384
ES512 = "ES512" // ECDSA using P-521 and SHA-512
PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
)

View file

@ -1,126 +0,0 @@
package jose
import (
"encoding/json"
"fmt"
"math"
"time"
)
type Claims map[string]interface{}
func (c Claims) Add(name string, value interface{}) {
c[name] = value
}
func (c Claims) StringClaim(name string) (string, bool, error) {
cl, ok := c[name]
if !ok {
return "", false, nil
}
v, ok := cl.(string)
if !ok {
return "", false, fmt.Errorf("unable to parse claim as string: %v", name)
}
return v, true, nil
}
func (c Claims) StringsClaim(name string) ([]string, bool, error) {
cl, ok := c[name]
if !ok {
return nil, false, nil
}
if v, ok := cl.([]string); ok {
return v, true, nil
}
// When unmarshaled, []string will become []interface{}.
if v, ok := cl.([]interface{}); ok {
var ret []string
for _, vv := range v {
str, ok := vv.(string)
if !ok {
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
}
ret = append(ret, str)
}
return ret, true, nil
}
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
}
func (c Claims) Int64Claim(name string) (int64, bool, error) {
cl, ok := c[name]
if !ok {
return 0, false, nil
}
v, ok := cl.(int64)
if !ok {
vf, ok := cl.(float64)
if !ok {
return 0, false, fmt.Errorf("unable to parse claim as int64: %v", name)
}
v = int64(vf)
}
return v, true, nil
}
func (c Claims) Float64Claim(name string) (float64, bool, error) {
cl, ok := c[name]
if !ok {
return 0, false, nil
}
v, ok := cl.(float64)
if !ok {
vi, ok := cl.(int64)
if !ok {
return 0, false, fmt.Errorf("unable to parse claim as float64: %v", name)
}
v = float64(vi)
}
return v, true, nil
}
func (c Claims) TimeClaim(name string) (time.Time, bool, error) {
v, ok, err := c.Float64Claim(name)
if !ok || err != nil {
return time.Time{}, ok, err
}
s := math.Trunc(v)
ns := (v - s) * math.Pow(10, 9)
return time.Unix(int64(s), int64(ns)).UTC(), true, nil
}
func decodeClaims(payload []byte) (Claims, error) {
var c Claims
if err := json.Unmarshal(payload, &c); err != nil {
return nil, fmt.Errorf("malformed JWT claims, unable to decode: %v", err)
}
return c, nil
}
func marshalClaims(c Claims) ([]byte, error) {
b, err := json.Marshal(c)
if err != nil {
return nil, err
}
return b, nil
}
func encodeClaims(c Claims) (string, error) {
b, err := marshalClaims(c)
if err != nil {
return "", err
}
return encodeSegment(b), nil
}

View file

@ -1,328 +0,0 @@
package jose
import (
"reflect"
"testing"
"time"
)
func TestString(t *testing.T) {
tests := []struct {
cl Claims
key string
ok bool
err bool
val string
}{
// ok, no err, claim exists
{
cl: Claims{
"foo": "bar",
},
key: "foo",
val: "bar",
ok: true,
err: false,
},
// no claims
{
cl: Claims{},
key: "foo",
val: "",
ok: false,
err: false,
},
// missing claim
{
cl: Claims{
"foo": "bar",
},
key: "xxx",
val: "",
ok: false,
err: false,
},
// unparsable: type
{
cl: Claims{
"foo": struct{}{},
},
key: "foo",
val: "",
ok: false,
err: true,
},
// unparsable: nil value
{
cl: Claims{
"foo": nil,
},
key: "foo",
val: "",
ok: false,
err: true,
},
}
for i, tt := range tests {
val, ok, err := tt.cl.StringClaim(tt.key)
if tt.err && err == nil {
t.Errorf("case %d: want err=non-nil, got err=nil", i)
} else if !tt.err && err != nil {
t.Errorf("case %d: want err=nil, got err=%v", i, err)
}
if tt.ok != ok {
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
}
if tt.val != val {
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
}
}
}
func TestInt64(t *testing.T) {
tests := []struct {
cl Claims
key string
ok bool
err bool
val int64
}{
// ok, no err, claim exists
{
cl: Claims{
"foo": int64(100),
},
key: "foo",
val: int64(100),
ok: true,
err: false,
},
// no claims
{
cl: Claims{},
key: "foo",
val: 0,
ok: false,
err: false,
},
// missing claim
{
cl: Claims{
"foo": "bar",
},
key: "xxx",
val: 0,
ok: false,
err: false,
},
// unparsable: type
{
cl: Claims{
"foo": struct{}{},
},
key: "foo",
val: 0,
ok: false,
err: true,
},
// unparsable: nil value
{
cl: Claims{
"foo": nil,
},
key: "foo",
val: 0,
ok: false,
err: true,
},
}
for i, tt := range tests {
val, ok, err := tt.cl.Int64Claim(tt.key)
if tt.err && err == nil {
t.Errorf("case %d: want err=non-nil, got err=nil", i)
} else if !tt.err && err != nil {
t.Errorf("case %d: want err=nil, got err=%v", i, err)
}
if tt.ok != ok {
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
}
if tt.val != val {
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
}
}
}
func TestTime(t *testing.T) {
now := time.Now().UTC()
unixNow := now.Unix()
tests := []struct {
cl Claims
key string
ok bool
err bool
val time.Time
}{
// ok, no err, claim exists
{
cl: Claims{
"foo": unixNow,
},
key: "foo",
val: time.Unix(now.Unix(), 0).UTC(),
ok: true,
err: false,
},
// no claims
{
cl: Claims{},
key: "foo",
val: time.Time{},
ok: false,
err: false,
},
// missing claim
{
cl: Claims{
"foo": "bar",
},
key: "xxx",
val: time.Time{},
ok: false,
err: false,
},
// unparsable: type
{
cl: Claims{
"foo": struct{}{},
},
key: "foo",
val: time.Time{},
ok: false,
err: true,
},
// unparsable: nil value
{
cl: Claims{
"foo": nil,
},
key: "foo",
val: time.Time{},
ok: false,
err: true,
},
}
for i, tt := range tests {
val, ok, err := tt.cl.TimeClaim(tt.key)
if tt.err && err == nil {
t.Errorf("case %d: want err=non-nil, got err=nil", i)
} else if !tt.err && err != nil {
t.Errorf("case %d: want err=nil, got err=%v", i, err)
}
if tt.ok != ok {
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
}
if tt.val != val {
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
}
}
}
func TestStringArray(t *testing.T) {
tests := []struct {
cl Claims
key string
ok bool
err bool
val []string
}{
// ok, no err, claim exists
{
cl: Claims{
"foo": []string{"bar", "faf"},
},
key: "foo",
val: []string{"bar", "faf"},
ok: true,
err: false,
},
// ok, no err, []interface{}
{
cl: Claims{
"foo": []interface{}{"bar", "faf"},
},
key: "foo",
val: []string{"bar", "faf"},
ok: true,
err: false,
},
// no claims
{
cl: Claims{},
key: "foo",
val: nil,
ok: false,
err: false,
},
// missing claim
{
cl: Claims{
"foo": "bar",
},
key: "xxx",
val: nil,
ok: false,
err: false,
},
// unparsable: type
{
cl: Claims{
"foo": struct{}{},
},
key: "foo",
val: nil,
ok: false,
err: true,
},
// unparsable: nil value
{
cl: Claims{
"foo": nil,
},
key: "foo",
val: nil,
ok: false,
err: true,
},
}
for i, tt := range tests {
val, ok, err := tt.cl.StringsClaim(tt.key)
if tt.err && err == nil {
t.Errorf("case %d: want err=non-nil, got err=nil", i)
} else if !tt.err && err != nil {
t.Errorf("case %d: want err=nil, got err=%v", i, err)
}
if tt.ok != ok {
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
}
if !reflect.DeepEqual(tt.val, val) {
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
}
}
}

View file

@ -1,2 +0,0 @@
// Package jose is DEPRECATED. Use gopkg.in/square/go-jose.v2 instead.
package jose

View file

@ -1,112 +0,0 @@
package jose
import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
)
const (
HeaderMediaType = "typ"
HeaderKeyAlgorithm = "alg"
HeaderKeyID = "kid"
)
const (
// Encryption Algorithm Header Parameter Values for JWS
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-6
AlgHS256 = "HS256"
AlgHS384 = "HS384"
AlgHS512 = "HS512"
AlgRS256 = "RS256"
AlgRS384 = "RS384"
AlgRS512 = "RS512"
AlgES256 = "ES256"
AlgES384 = "ES384"
AlgES512 = "ES512"
AlgPS256 = "PS256"
AlgPS384 = "PS384"
AlgPS512 = "PS512"
AlgNone = "none"
)
const (
// Algorithm Header Parameter Values for JWE
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-4.1
AlgRSA15 = "RSA1_5"
AlgRSAOAEP = "RSA-OAEP"
AlgRSAOAEP256 = "RSA-OAEP-256"
AlgA128KW = "A128KW"
AlgA192KW = "A192KW"
AlgA256KW = "A256KW"
AlgDir = "dir"
AlgECDHES = "ECDH-ES"
AlgECDHESA128KW = "ECDH-ES+A128KW"
AlgECDHESA192KW = "ECDH-ES+A192KW"
AlgECDHESA256KW = "ECDH-ES+A256KW"
AlgA128GCMKW = "A128GCMKW"
AlgA192GCMKW = "A192GCMKW"
AlgA256GCMKW = "A256GCMKW"
AlgPBES2HS256A128KW = "PBES2-HS256+A128KW"
AlgPBES2HS384A192KW = "PBES2-HS384+A192KW"
AlgPBES2HS512A256KW = "PBES2-HS512+A256KW"
)
const (
// Encryption Algorithm Header Parameter Values for JWE
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-22
EncA128CBCHS256 = "A128CBC-HS256"
EncA128CBCHS384 = "A128CBC-HS384"
EncA256CBCHS512 = "A256CBC-HS512"
EncA128GCM = "A128GCM"
EncA192GCM = "A192GCM"
EncA256GCM = "A256GCM"
)
type JOSEHeader map[string]string
func (j JOSEHeader) Validate() error {
if _, exists := j[HeaderKeyAlgorithm]; !exists {
return fmt.Errorf("header missing %q parameter", HeaderKeyAlgorithm)
}
return nil
}
func decodeHeader(seg string) (JOSEHeader, error) {
b, err := decodeSegment(seg)
if err != nil {
return nil, err
}
var h JOSEHeader
err = json.Unmarshal(b, &h)
if err != nil {
return nil, err
}
return h, nil
}
func encodeHeader(h JOSEHeader) (string, error) {
b, err := json.Marshal(h)
if err != nil {
return "", err
}
return encodeSegment(b), nil
}
// Decode JWT specific base64url encoding with padding stripped
func decodeSegment(seg string) ([]byte, error) {
if l := len(seg) % 4; l != 0 {
seg += strings.Repeat("=", 4-l)
}
return base64.URLEncoding.DecodeString(seg)
}
// Encode JWT specific base64url encoding with padding stripped
func encodeSegment(seg []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
}

View file

@ -1,135 +0,0 @@
package jose
import (
"bytes"
"encoding/base64"
"encoding/binary"
"encoding/json"
"math/big"
"strings"
)
// JSON Web Key
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-36#page-5
type JWK struct {
ID string
Type string
Alg string
Use string
Exponent int
Modulus *big.Int
Secret []byte
}
type jwkJSON struct {
ID string `json:"kid"`
Type string `json:"kty"`
Alg string `json:"alg"`
Use string `json:"use"`
Exponent string `json:"e"`
Modulus string `json:"n"`
}
func (j *JWK) MarshalJSON() ([]byte, error) {
t := jwkJSON{
ID: j.ID,
Type: j.Type,
Alg: j.Alg,
Use: j.Use,
Exponent: encodeExponent(j.Exponent),
Modulus: encodeModulus(j.Modulus),
}
return json.Marshal(&t)
}
func (j *JWK) UnmarshalJSON(data []byte) error {
var t jwkJSON
err := json.Unmarshal(data, &t)
if err != nil {
return err
}
e, err := decodeExponent(t.Exponent)
if err != nil {
return err
}
n, err := decodeModulus(t.Modulus)
if err != nil {
return err
}
j.ID = t.ID
j.Type = t.Type
j.Alg = t.Alg
j.Use = t.Use
j.Exponent = e
j.Modulus = n
return nil
}
type JWKSet struct {
Keys []JWK `json:"keys"`
}
func decodeExponent(e string) (int, error) {
decE, err := decodeBase64URLPaddingOptional(e)
if err != nil {
return 0, err
}
var eBytes []byte
if len(decE) < 8 {
eBytes = make([]byte, 8-len(decE), 8)
eBytes = append(eBytes, decE...)
} else {
eBytes = decE
}
eReader := bytes.NewReader(eBytes)
var E uint64
err = binary.Read(eReader, binary.BigEndian, &E)
if err != nil {
return 0, err
}
return int(E), nil
}
func encodeExponent(e int) string {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(e))
var idx int
for ; idx < 8; idx++ {
if b[idx] != 0x0 {
break
}
}
return base64.RawURLEncoding.EncodeToString(b[idx:])
}
// Turns a URL encoded modulus of a key into a big int.
func decodeModulus(n string) (*big.Int, error) {
decN, err := decodeBase64URLPaddingOptional(n)
if err != nil {
return nil, err
}
N := big.NewInt(0)
N.SetBytes(decN)
return N, nil
}
func encodeModulus(n *big.Int) string {
return base64.RawURLEncoding.EncodeToString(n.Bytes())
}
// decodeBase64URLPaddingOptional decodes Base64 whether there is padding or not.
// The stdlib version currently doesn't handle this.
// We can get rid of this is if this bug:
// https://github.com/golang/go/issues/4237
// ever closes.
func decodeBase64URLPaddingOptional(e string) ([]byte, error) {
if m := len(e) % 4; m != 0 {
e += strings.Repeat("=", 4-m)
}
return base64.URLEncoding.DecodeString(e)
}

View file

@ -1,64 +0,0 @@
package jose
import (
"testing"
)
func TestDecodeBase64URLPaddingOptional(t *testing.T) {
tests := []struct {
encoded string
decoded string
err bool
}{
{
// With padding
encoded: "VGVjdG9uaWM=",
decoded: "Tectonic",
},
{
// Without padding
encoded: "VGVjdG9uaWM",
decoded: "Tectonic",
},
{
// Even More padding
encoded: "VGVjdG9uaQ==",
decoded: "Tectoni",
},
{
// And take it away!
encoded: "VGVjdG9uaQ",
decoded: "Tectoni",
},
{
// Too much padding.
encoded: "VGVjdG9uaWNh=",
decoded: "",
err: true,
},
{
// Too much padding.
encoded: "VGVjdG9uaWNh=",
decoded: "",
err: true,
},
}
for i, tt := range tests {
got, err := decodeBase64URLPaddingOptional(tt.encoded)
if tt.err {
if err == nil {
t.Errorf("case %d: expected non-nil err", i)
}
continue
}
if err != nil {
t.Errorf("case %d: want nil err, got: %v", i, err)
}
if string(got) != tt.decoded {
t.Errorf("case %d: want=%q, got=%q", i, tt.decoded, got)
}
}
}

View file

@ -1,51 +0,0 @@
package jose
import (
"fmt"
"strings"
)
type JWS struct {
RawHeader string
Header JOSEHeader
RawPayload string
Payload []byte
Signature []byte
}
// Given a raw encoded JWS token parses it and verifies the structure.
func ParseJWS(raw string) (JWS, error) {
parts := strings.Split(raw, ".")
if len(parts) != 3 {
return JWS{}, fmt.Errorf("malformed JWS, only %d segments", len(parts))
}
rawSig := parts[2]
jws := JWS{
RawHeader: parts[0],
RawPayload: parts[1],
}
header, err := decodeHeader(jws.RawHeader)
if err != nil {
return JWS{}, fmt.Errorf("malformed JWS, unable to decode header, %s", err)
}
if err = header.Validate(); err != nil {
return JWS{}, fmt.Errorf("malformed JWS, %s", err)
}
jws.Header = header
payload, err := decodeSegment(jws.RawPayload)
if err != nil {
return JWS{}, fmt.Errorf("malformed JWS, unable to decode payload: %s", err)
}
jws.Payload = payload
sig, err := decodeSegment(rawSig)
if err != nil {
return JWS{}, fmt.Errorf("malformed JWS, unable to decode signature: %s", err)
}
jws.Signature = sig
return jws, nil
}

View file

@ -1,74 +0,0 @@
package jose
import (
"strings"
"testing"
)
type testCase struct{ t string }
var validInput []testCase
var invalidInput []testCase
func init() {
validInput = []testCase{
{
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
},
}
invalidInput = []testCase{
// empty
{
"",
},
// undecodeable
{
"aaa.bbb.ccc",
},
// missing parts
{
"aaa",
},
// missing parts
{
"aaa.bbb",
},
// too many parts
{
"aaa.bbb.ccc.ddd",
},
// invalid header
// EncodeHeader(map[string]string{"foo": "bar"})
{
"eyJmb28iOiJiYXIifQ.bbb.ccc",
},
}
}
func TestParseJWS(t *testing.T) {
for i, tt := range validInput {
jws, err := ParseJWS(tt.t)
if err != nil {
t.Errorf("test: %d. expected: valid, actual: invalid", i)
}
expectedHeader := strings.Split(tt.t, ".")[0]
if jws.RawHeader != expectedHeader {
t.Errorf("test: %d. expected: %s, actual: %s", i, expectedHeader, jws.RawHeader)
}
expectedPayload := strings.Split(tt.t, ".")[1]
if jws.RawPayload != expectedPayload {
t.Errorf("test: %d. expected: %s, actual: %s", i, expectedPayload, jws.RawPayload)
}
}
for i, tt := range invalidInput {
_, err := ParseJWS(tt.t)
if err == nil {
t.Errorf("test: %d. expected: invalid, actual: valid", i)
}
}
}

View file

@ -1,82 +0,0 @@
package jose
import "strings"
type JWT JWS
func ParseJWT(token string) (jwt JWT, err error) {
jws, err := ParseJWS(token)
if err != nil {
return
}
return JWT(jws), nil
}
func NewJWT(header JOSEHeader, claims Claims) (jwt JWT, err error) {
jwt = JWT{}
jwt.Header = header
jwt.Header[HeaderMediaType] = "JWT"
claimBytes, err := marshalClaims(claims)
if err != nil {
return
}
jwt.Payload = claimBytes
eh, err := encodeHeader(header)
if err != nil {
return
}
jwt.RawHeader = eh
ec, err := encodeClaims(claims)
if err != nil {
return
}
jwt.RawPayload = ec
return
}
func (j *JWT) KeyID() (string, bool) {
kID, ok := j.Header[HeaderKeyID]
return kID, ok
}
func (j *JWT) Claims() (Claims, error) {
return decodeClaims(j.Payload)
}
// Encoded data part of the token which may be signed.
func (j *JWT) Data() string {
return strings.Join([]string{j.RawHeader, j.RawPayload}, ".")
}
// Full encoded JWT token string in format: header.claims.signature
func (j *JWT) Encode() string {
d := j.Data()
s := encodeSegment(j.Signature)
return strings.Join([]string{d, s}, ".")
}
func NewSignedJWT(claims Claims, s Signer) (*JWT, error) {
header := JOSEHeader{
HeaderKeyAlgorithm: s.Alg(),
HeaderKeyID: s.ID(),
}
jwt, err := NewJWT(header, claims)
if err != nil {
return nil, err
}
sig, err := s.Sign([]byte(jwt.Data()))
if err != nil {
return nil, err
}
jwt.Signature = sig
return &jwt, nil
}

View file

@ -1,94 +0,0 @@
package jose
import (
"reflect"
"testing"
)
func TestParseJWT(t *testing.T) {
tests := []struct {
r string
h JOSEHeader
c Claims
}{
{
// Example from JWT spec:
// http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#ExampleJWT
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
JOSEHeader{
HeaderMediaType: "JWT",
HeaderKeyAlgorithm: "HS256",
},
Claims{
"iss": "joe",
// NOTE: test numbers must be floats for equality checks to work since values are converted form interface{} to float64 by default.
"exp": 1300819380.0,
"http://example.com/is_root": true,
},
},
}
for i, tt := range tests {
jwt, err := ParseJWT(tt.r)
if err != nil {
t.Errorf("raw token should parse. test: %d. expected: valid, actual: invalid. err=%v", i, err)
}
if !reflect.DeepEqual(tt.h, jwt.Header) {
t.Errorf("JOSE headers should match. test: %d. expected: %v, actual: %v", i, tt.h, jwt.Header)
}
claims, err := jwt.Claims()
if err != nil {
t.Errorf("test: %d. expected: valid claim parsing. err=%v", i, err)
}
if !reflect.DeepEqual(tt.c, claims) {
t.Errorf("claims should match. test: %d. expected: %v, actual: %v", i, tt.c, claims)
}
enc := jwt.Encode()
if enc != tt.r {
t.Errorf("encoded jwt should match raw jwt. test: %d. expected: %v, actual: %v", i, tt.r, enc)
}
}
}
func TestNewJWTHeaderType(t *testing.T) {
jwt, err := NewJWT(JOSEHeader{}, Claims{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
want := "JWT"
got := jwt.Header[HeaderMediaType]
if want != got {
t.Fatalf("Header %q incorrect: want=%s got=%s", HeaderMediaType, want, got)
}
}
func TestNewJWTHeaderKeyID(t *testing.T) {
jwt, err := NewJWT(JOSEHeader{HeaderKeyID: "foo"}, Claims{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
want := "foo"
got, ok := jwt.KeyID()
if !ok {
t.Fatalf("KeyID not set")
} else if want != got {
t.Fatalf("KeyID incorrect: want=%s got=%s", want, got)
}
}
func TestNewJWTHeaderKeyIDNotSet(t *testing.T) {
jwt, err := NewJWT(JOSEHeader{}, Claims{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, ok := jwt.KeyID(); ok {
t.Fatalf("KeyID set, but should not be")
}
}

View file

@ -1,24 +0,0 @@
package jose
import (
"fmt"
)
type Verifier interface {
ID() string
Alg() string
Verify(sig []byte, data []byte) error
}
type Signer interface {
Verifier
Sign(data []byte) (sig []byte, err error)
}
func NewVerifier(jwk JWK) (Verifier, error) {
if jwk.Type != "RSA" {
return nil, fmt.Errorf("unsupported key type %q", jwk.Type)
}
return NewVerifierRSA(jwk)
}

View file

@ -1,67 +0,0 @@
package jose
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"fmt"
)
type VerifierRSA struct {
KeyID string
Hash crypto.Hash
PublicKey rsa.PublicKey
}
type SignerRSA struct {
PrivateKey rsa.PrivateKey
VerifierRSA
}
func NewVerifierRSA(jwk JWK) (*VerifierRSA, error) {
if jwk.Alg != "" && jwk.Alg != "RS256" {
return nil, fmt.Errorf("unsupported key algorithm %q", jwk.Alg)
}
v := VerifierRSA{
KeyID: jwk.ID,
PublicKey: rsa.PublicKey{
N: jwk.Modulus,
E: jwk.Exponent,
},
Hash: crypto.SHA256,
}
return &v, nil
}
func NewSignerRSA(kid string, key rsa.PrivateKey) *SignerRSA {
return &SignerRSA{
PrivateKey: key,
VerifierRSA: VerifierRSA{
KeyID: kid,
PublicKey: key.PublicKey,
Hash: crypto.SHA256,
},
}
}
func (v *VerifierRSA) ID() string {
return v.KeyID
}
func (v *VerifierRSA) Alg() string {
return "RS256"
}
func (v *VerifierRSA) Verify(sig []byte, data []byte) error {
h := v.Hash.New()
h.Write(data)
return rsa.VerifyPKCS1v15(&v.PublicKey, v.Hash, h.Sum(nil), sig)
}
func (s *SignerRSA) Sign(data []byte) ([]byte, error) {
h := s.Hash.New()
h.Write(data)
return rsa.SignPKCS1v15(rand.Reader, &s.PrivateKey, s.Hash, h.Sum(nil))
}

View file

@ -1,206 +0,0 @@
package oidc
import (
"context"
"errors"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
"github.com/pquerna/cachecontrol"
jose "gopkg.in/square/go-jose.v2"
)
// keysExpiryDelta is the allowed clock skew between a client and the OpenID Connect
// server.
//
// When keys expire, they are valid for this amount of time after.
//
// If the keys have not expired, and an ID Token claims it was signed by a key not in
// the cache, if and only if the keys expire in this amount of time, the keys will be
// updated.
const keysExpiryDelta = 30 * time.Second
func newRemoteKeySet(ctx context.Context, jwksURL string, now func() time.Time) *remoteKeySet {
if now == nil {
now = time.Now
}
return &remoteKeySet{jwksURL: jwksURL, ctx: ctx, now: now}
}
type remoteKeySet struct {
jwksURL string
ctx context.Context
now func() time.Time
// guard all other fields
mu sync.Mutex
// inflight suppresses parallel execution of updateKeys and allows
// multiple goroutines to wait for its result.
inflight *inflight
// A set of cached keys and their expiry.
cachedKeys []jose.JSONWebKey
expiry time.Time
}
// inflight is used to wait on some in-flight request from multiple goroutines.
type inflight struct {
doneCh chan struct{}
keys []jose.JSONWebKey
err error
}
func newInflight() *inflight {
return &inflight{doneCh: make(chan struct{})}
}
// wait returns a channel that multiple goroutines can receive on. Once it returns
// a value, the inflight request is done and result() can be inspected.
func (i *inflight) wait() <-chan struct{} {
return i.doneCh
}
// done can only be called by a single goroutine. It records the result of the
// inflight request and signals other goroutines that the result is safe to
// inspect.
func (i *inflight) done(keys []jose.JSONWebKey, err error) {
i.keys = keys
i.err = err
close(i.doneCh)
}
// result cannot be called until the wait() channel has returned a value.
func (i *inflight) result() ([]jose.JSONWebKey, error) {
return i.keys, i.err
}
func (r *remoteKeySet) verify(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
// We don't support JWTs signed with multiple signatures.
keyID := ""
for _, sig := range jws.Signatures {
keyID = sig.Header.KeyID
break
}
keys, expiry := r.keysFromCache()
// Don't check expiry yet. This optimizes for when the provider is unavailable.
for _, key := range keys {
if keyID == "" || key.KeyID == keyID {
if payload, err := jws.Verify(&key); err == nil {
return payload, nil
}
}
}
if !r.now().Add(keysExpiryDelta).After(expiry) {
// Keys haven't expired, don't refresh.
return nil, errors.New("failed to verify id token signature")
}
keys, err := r.keysFromRemote(ctx)
if err != nil {
return nil, fmt.Errorf("fetching keys %v", err)
}
for _, key := range keys {
if keyID == "" || key.KeyID == keyID {
if payload, err := jws.Verify(&key); err == nil {
return payload, nil
}
}
}
return nil, errors.New("failed to verify id token signature")
}
func (r *remoteKeySet) keysFromCache() (keys []jose.JSONWebKey, expiry time.Time) {
r.mu.Lock()
defer r.mu.Unlock()
return r.cachedKeys, r.expiry
}
// keysFromRemote syncs the key set from the remote set, records the values in the
// cache, and returns the key set.
func (r *remoteKeySet) keysFromRemote(ctx context.Context) ([]jose.JSONWebKey, error) {
// Need to lock to inspect the inflight request field.
r.mu.Lock()
// If there's not a current inflight request, create one.
if r.inflight == nil {
r.inflight = newInflight()
// This goroutine has exclusive ownership over the current inflight
// request. It releases the resource by nil'ing the inflight field
// once the goroutine is done.
go func() {
// Sync keys and finish inflight when that's done.
keys, expiry, err := r.updateKeys()
r.inflight.done(keys, err)
// Lock to update the keys and indicate that there is no longer an
// inflight request.
r.mu.Lock()
defer r.mu.Unlock()
if err == nil {
r.cachedKeys = keys
r.expiry = expiry
}
// Free inflight so a different request can run.
r.inflight = nil
}()
}
inflight := r.inflight
r.mu.Unlock()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-inflight.wait():
return inflight.result()
}
}
func (r *remoteKeySet) updateKeys() ([]jose.JSONWebKey, time.Time, error) {
req, err := http.NewRequest("GET", r.jwksURL, nil)
if err != nil {
return nil, time.Time{}, fmt.Errorf("oidc: can't create request: %v", err)
}
resp, err := doRequest(r.ctx, req)
if err != nil {
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, time.Time{}, fmt.Errorf("unable to read response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed: %s %s", resp.Status, body)
}
var keySet jose.JSONWebKeySet
err = unmarshalResp(resp, body, &keySet)
if err != nil {
return nil, time.Time{}, fmt.Errorf("oidc: failed to decode keys: %v %s", err, body)
}
// If the server doesn't provide cache control headers, assume the
// keys expire immediately.
expiry := r.now()
_, e, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{})
if err == nil && e.After(expiry) {
expiry = e
}
return keySet.Keys, expiry, nil
}

View file

@ -1,250 +0,0 @@
package oidc
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
jose "gopkg.in/square/go-jose.v2"
)
type keyServer struct {
keys jose.JSONWebKeySet
setHeaders func(h http.Header)
}
func (k *keyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if k.setHeaders != nil {
k.setHeaders(w.Header())
}
if err := json.NewEncoder(w).Encode(k.keys); err != nil {
panic(err)
}
}
type signingKey struct {
keyID string // optional
priv interface{}
pub interface{}
alg jose.SignatureAlgorithm
}
// sign creates a JWS using the private key from the provided payload.
func (s *signingKey) sign(t *testing.T, payload []byte) string {
privKey := &jose.JSONWebKey{Key: s.priv, Algorithm: string(s.alg), KeyID: s.keyID}
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: s.alg, Key: privKey}, nil)
if err != nil {
t.Fatal(err)
}
jws, err := signer.Sign(payload)
if err != nil {
t.Fatal(err)
}
data, err := jws.CompactSerialize()
if err != nil {
t.Fatal(err)
}
return data
}
// jwk returns the public part of the signing key.
func (s *signingKey) jwk() jose.JSONWebKey {
return jose.JSONWebKey{Key: s.pub, Use: "sig", Algorithm: string(s.alg), KeyID: s.keyID}
}
func newRSAKey(t *testing.T) *signingKey {
priv, err := rsa.GenerateKey(rand.Reader, 1028)
if err != nil {
t.Fatal(err)
}
return &signingKey{"", priv, priv.Public(), jose.RS256}
}
func newECDSAKey(t *testing.T) *signingKey {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
return &signingKey{"", priv, priv.Public(), jose.ES256}
}
func TestRSAVerify(t *testing.T) {
good := newRSAKey(t)
bad := newRSAKey(t)
testKeyVerify(t, good, bad, good)
}
func TestECDSAVerify(t *testing.T) {
good := newECDSAKey(t)
bad := newECDSAKey(t)
testKeyVerify(t, good, bad, good)
}
func TestMultipleKeysVerify(t *testing.T) {
key1 := newRSAKey(t)
key2 := newRSAKey(t)
bad := newECDSAKey(t)
key1.keyID = "key1"
key2.keyID = "key2"
bad.keyID = "key3"
testKeyVerify(t, key2, bad, key1, key2)
}
func TestMismatchedKeyID(t *testing.T) {
key1 := newRSAKey(t)
key2 := newRSAKey(t)
// shallow copy
bad := new(signingKey)
*bad = *key1
// The bad key is a valid key this time, but has a different Key ID.
// It shouldn't match key1 because of the mismatched ID, even though
// it would confirm the signature just fine.
bad.keyID = "key3"
key1.keyID = "key1"
key2.keyID = "key2"
testKeyVerify(t, key2, bad, key1, key2)
}
func testKeyVerify(t *testing.T, good, bad *signingKey, verification ...*signingKey) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
keySet := jose.JSONWebKeySet{}
for _, v := range verification {
keySet.Keys = append(keySet.Keys, v.jwk())
}
payload := []byte("a secret")
jws, err := jose.ParseSigned(good.sign(t, payload))
if err != nil {
t.Fatal(err)
}
badJWS, err := jose.ParseSigned(bad.sign(t, payload))
if err != nil {
t.Fatal(err)
}
s := httptest.NewServer(&keyServer{keys: keySet})
defer s.Close()
rks := newRemoteKeySet(ctx, s.URL, nil)
// Ensure the token verifies.
gotPayload, err := rks.verify(ctx, jws)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(gotPayload, payload) {
t.Errorf("expected payload %s got %s", payload, gotPayload)
}
// Ensure the token verifies from the cache.
gotPayload, err = rks.verify(ctx, jws)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(gotPayload, payload) {
t.Errorf("expected payload %s got %s", payload, gotPayload)
}
// Ensure item signed by wrong token doesn't verify.
if _, err := rks.verify(context.Background(), badJWS); err == nil {
t.Errorf("incorrectly verified signature")
}
}
func TestCacheControl(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
key1 := newRSAKey(t)
key2 := newRSAKey(t)
key1.keyID = "key1"
key2.keyID = "key2"
payload := []byte("a secret")
jws1, err := jose.ParseSigned(key1.sign(t, payload))
if err != nil {
t.Fatal(err)
}
jws2, err := jose.ParseSigned(key2.sign(t, payload))
if err != nil {
t.Fatal(err)
}
cacheForSeconds := 1200
now := time.Now()
server := &keyServer{
keys: jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{key1.jwk()},
},
setHeaders: func(h http.Header) {
h.Set("Cache-Control", "max-age="+strconv.Itoa(cacheForSeconds))
},
}
s := httptest.NewServer(server)
defer s.Close()
rks := newRemoteKeySet(ctx, s.URL, func() time.Time { return now })
if _, err := rks.verify(ctx, jws1); err != nil {
t.Errorf("failed to verify valid signature: %v", err)
}
if _, err := rks.verify(ctx, jws2); err == nil {
t.Errorf("incorrectly verified signature")
}
// Add second key to public list.
server.keys = jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{key1.jwk(), key2.jwk()},
}
if _, err := rks.verify(ctx, jws1); err != nil {
t.Errorf("failed to verify valid signature: %v", err)
}
if _, err := rks.verify(ctx, jws2); err == nil {
t.Errorf("incorrectly verified signature, still within cache limit")
}
// Move time forward. Remote key set should not query the remote server.
now = now.Add(time.Duration(cacheForSeconds) * time.Second)
if _, err := rks.verify(ctx, jws1); err != nil {
t.Errorf("failed to verify valid signature: %v", err)
}
if _, err := rks.verify(ctx, jws2); err != nil {
t.Errorf("failed to verify valid signature: %v", err)
}
// Kill server and move time forward again. Keys should still verify.
s.Close()
now = now.Add(time.Duration(cacheForSeconds) * time.Second)
if _, err := rks.verify(ctx, jws1); err != nil {
t.Errorf("failed to verify valid signature: %v", err)
}
if _, err := rks.verify(ctx, jws2); err != nil {
t.Errorf("failed to verify valid signature: %v", err)
}
}

View file

@ -1,2 +0,0 @@
// Package key is DEPRECATED. Use github.com/coreos/go-oidc instead.
package key

View file

@ -1,153 +0,0 @@
package key
import (
"crypto/rand"
"crypto/rsa"
"encoding/hex"
"encoding/json"
"io"
"time"
"github.com/coreos/go-oidc/jose"
)
func NewPublicKey(jwk jose.JWK) *PublicKey {
return &PublicKey{jwk: jwk}
}
type PublicKey struct {
jwk jose.JWK
}
func (k *PublicKey) MarshalJSON() ([]byte, error) {
return json.Marshal(&k.jwk)
}
func (k *PublicKey) UnmarshalJSON(data []byte) error {
var jwk jose.JWK
if err := json.Unmarshal(data, &jwk); err != nil {
return err
}
k.jwk = jwk
return nil
}
func (k *PublicKey) ID() string {
return k.jwk.ID
}
func (k *PublicKey) Verifier() (jose.Verifier, error) {
return jose.NewVerifierRSA(k.jwk)
}
type PrivateKey struct {
KeyID string
PrivateKey *rsa.PrivateKey
}
func (k *PrivateKey) ID() string {
return k.KeyID
}
func (k *PrivateKey) Signer() jose.Signer {
return jose.NewSignerRSA(k.ID(), *k.PrivateKey)
}
func (k *PrivateKey) JWK() jose.JWK {
return jose.JWK{
ID: k.KeyID,
Type: "RSA",
Alg: "RS256",
Use: "sig",
Exponent: k.PrivateKey.PublicKey.E,
Modulus: k.PrivateKey.PublicKey.N,
}
}
type KeySet interface {
ExpiresAt() time.Time
}
type PublicKeySet struct {
keys []PublicKey
index map[string]*PublicKey
expiresAt time.Time
}
func NewPublicKeySet(jwks []jose.JWK, exp time.Time) *PublicKeySet {
keys := make([]PublicKey, len(jwks))
index := make(map[string]*PublicKey)
for i, jwk := range jwks {
keys[i] = *NewPublicKey(jwk)
index[keys[i].ID()] = &keys[i]
}
return &PublicKeySet{
keys: keys,
index: index,
expiresAt: exp,
}
}
func (s *PublicKeySet) ExpiresAt() time.Time {
return s.expiresAt
}
func (s *PublicKeySet) Keys() []PublicKey {
return s.keys
}
func (s *PublicKeySet) Key(id string) *PublicKey {
return s.index[id]
}
type PrivateKeySet struct {
keys []*PrivateKey
ActiveKeyID string
expiresAt time.Time
}
func NewPrivateKeySet(keys []*PrivateKey, exp time.Time) *PrivateKeySet {
return &PrivateKeySet{
keys: keys,
ActiveKeyID: keys[0].ID(),
expiresAt: exp.UTC(),
}
}
func (s *PrivateKeySet) Keys() []*PrivateKey {
return s.keys
}
func (s *PrivateKeySet) ExpiresAt() time.Time {
return s.expiresAt
}
func (s *PrivateKeySet) Active() *PrivateKey {
for i, k := range s.keys {
if k.ID() == s.ActiveKeyID {
return s.keys[i]
}
}
return nil
}
type GeneratePrivateKeyFunc func() (*PrivateKey, error)
func GeneratePrivateKey() (*PrivateKey, error) {
pk, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
keyID := make([]byte, 20)
if _, err := io.ReadFull(rand.Reader, keyID); err != nil {
return nil, err
}
k := PrivateKey{
KeyID: hex.EncodeToString(keyID),
PrivateKey: pk,
}
return &k, nil
}

View file

@ -1,103 +0,0 @@
package key
import (
"crypto/rsa"
"math/big"
"reflect"
"testing"
"time"
"github.com/coreos/go-oidc/jose"
)
func TestPrivateRSAKeyJWK(t *testing.T) {
n := big.NewInt(int64(17))
if n == nil {
panic("NewInt returned nil")
}
k := &PrivateKey{
KeyID: "foo",
PrivateKey: &rsa.PrivateKey{
PublicKey: rsa.PublicKey{N: n, E: 65537},
},
}
want := jose.JWK{
ID: "foo",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: n,
Exponent: 65537,
}
got := k.JWK()
if !reflect.DeepEqual(want, got) {
t.Fatalf("JWK mismatch: want=%#v got=%#v", want, got)
}
}
func TestPublicKeySetKey(t *testing.T) {
n := big.NewInt(int64(17))
if n == nil {
panic("NewInt returned nil")
}
k := jose.JWK{
ID: "foo",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: n,
Exponent: 65537,
}
now := time.Now().UTC()
ks := NewPublicKeySet([]jose.JWK{k}, now)
want := &PublicKey{jwk: k}
got := ks.Key("foo")
if !reflect.DeepEqual(want, got) {
t.Errorf("Unexpected response from PublicKeySet.Key: want=%#v got=%#v", want, got)
}
got = ks.Key("bar")
if got != nil {
t.Errorf("Expected nil response from PublicKeySet.Key, got %#v", got)
}
}
func TestPublicKeyMarshalJSON(t *testing.T) {
k := jose.JWK{
ID: "foo",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: big.NewInt(int64(17)),
Exponent: 65537,
}
want := `{"kid":"foo","kty":"RSA","alg":"RS256","use":"sig","e":"AQAB","n":"EQ"}`
pubKey := NewPublicKey(k)
gotBytes, err := pubKey.MarshalJSON()
if err != nil {
t.Fatalf("failed to marshal public key: %v", err)
}
got := string(gotBytes)
if got != want {
t.Errorf("got != want:\n%s\n%s", got, want)
}
}
func TestGeneratePrivateKeyIDs(t *testing.T) {
key1, err := GeneratePrivateKey()
if err != nil {
t.Fatalf("GeneratePrivateKey(): %v", err)
}
key2, err := GeneratePrivateKey()
if err != nil {
t.Fatalf("GeneratePrivateKey(): %v", err)
}
if key1.KeyID == key2.KeyID {
t.Fatalf("expected different keys to have different key IDs")
}
}

View file

@ -1,99 +0,0 @@
package key
import (
"errors"
"time"
"github.com/jonboulle/clockwork"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/pkg/health"
)
type PrivateKeyManager interface {
ExpiresAt() time.Time
Signer() (jose.Signer, error)
JWKs() ([]jose.JWK, error)
PublicKeys() ([]PublicKey, error)
WritableKeySetRepo
health.Checkable
}
func NewPrivateKeyManager() PrivateKeyManager {
return &privateKeyManager{
clock: clockwork.NewRealClock(),
}
}
type privateKeyManager struct {
keySet *PrivateKeySet
clock clockwork.Clock
}
func (m *privateKeyManager) ExpiresAt() time.Time {
if m.keySet == nil {
return m.clock.Now().UTC()
}
return m.keySet.ExpiresAt()
}
func (m *privateKeyManager) Signer() (jose.Signer, error) {
if err := m.Healthy(); err != nil {
return nil, err
}
return m.keySet.Active().Signer(), nil
}
func (m *privateKeyManager) JWKs() ([]jose.JWK, error) {
if err := m.Healthy(); err != nil {
return nil, err
}
keys := m.keySet.Keys()
jwks := make([]jose.JWK, len(keys))
for i, k := range keys {
jwks[i] = k.JWK()
}
return jwks, nil
}
func (m *privateKeyManager) PublicKeys() ([]PublicKey, error) {
jwks, err := m.JWKs()
if err != nil {
return nil, err
}
keys := make([]PublicKey, len(jwks))
for i, jwk := range jwks {
keys[i] = *NewPublicKey(jwk)
}
return keys, nil
}
func (m *privateKeyManager) Healthy() error {
if m.keySet == nil {
return errors.New("private key manager uninitialized")
}
if len(m.keySet.Keys()) == 0 {
return errors.New("private key manager zero keys")
}
if m.keySet.ExpiresAt().Before(m.clock.Now().UTC()) {
return errors.New("private key manager keys expired")
}
return nil
}
func (m *privateKeyManager) Set(keySet KeySet) error {
privKeySet, ok := keySet.(*PrivateKeySet)
if !ok {
return errors.New("unable to cast to PrivateKeySet")
}
m.keySet = privKeySet
return nil
}

View file

@ -1,225 +0,0 @@
package key
import (
"crypto/rsa"
"math/big"
"reflect"
"strconv"
"testing"
"time"
"github.com/jonboulle/clockwork"
"github.com/coreos/go-oidc/jose"
)
var (
jwk1 jose.JWK
jwk2 jose.JWK
jwk3 jose.JWK
)
func init() {
jwk1 = jose.JWK{
ID: "1",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: big.NewInt(1),
Exponent: 65537,
}
jwk2 = jose.JWK{
ID: "2",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: big.NewInt(2),
Exponent: 65537,
}
jwk3 = jose.JWK{
ID: "3",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: big.NewInt(3),
Exponent: 65537,
}
}
func generatePrivateKeyStatic(t *testing.T, idAndN int) *PrivateKey {
n := big.NewInt(int64(idAndN))
if n == nil {
t.Fatalf("Call to NewInt(%d) failed", idAndN)
}
pk := &rsa.PrivateKey{
PublicKey: rsa.PublicKey{N: n, E: 65537},
}
return &PrivateKey{
KeyID: strconv.Itoa(idAndN),
PrivateKey: pk,
}
}
func TestPrivateKeyManagerJWKsRotate(t *testing.T) {
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
km := NewPrivateKeyManager()
err := km.Set(&PrivateKeySet{
keys: []*PrivateKey{k1, k2, k3},
ActiveKeyID: k1.KeyID,
expiresAt: time.Now().Add(time.Minute),
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
want := []jose.JWK{jwk1, jwk2, jwk3}
got, err := km.JWKs()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(want, got) {
t.Fatalf("JWK mismatch: want=%#v got=%#v", want, got)
}
}
func TestPrivateKeyManagerSigner(t *testing.T) {
k := generatePrivateKeyStatic(t, 13)
km := NewPrivateKeyManager()
err := km.Set(&PrivateKeySet{
keys: []*PrivateKey{k},
ActiveKeyID: k.KeyID,
expiresAt: time.Now().Add(time.Minute),
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
signer, err := km.Signer()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
wantID := "13"
gotID := signer.ID()
if wantID != gotID {
t.Fatalf("Signer has incorrect ID: want=%s got=%s", wantID, gotID)
}
}
func TestPrivateKeyManagerHealthyFail(t *testing.T) {
keyFixture := generatePrivateKeyStatic(t, 1)
tests := []*privateKeyManager{
// keySet nil
&privateKeyManager{
keySet: nil,
clock: clockwork.NewRealClock(),
},
// zero keys
&privateKeyManager{
keySet: &PrivateKeySet{
keys: []*PrivateKey{},
expiresAt: time.Now().Add(time.Minute),
},
clock: clockwork.NewRealClock(),
},
// key set expired
&privateKeyManager{
keySet: &PrivateKeySet{
keys: []*PrivateKey{keyFixture},
expiresAt: time.Now().Add(-1 * time.Minute),
},
clock: clockwork.NewRealClock(),
},
}
for i, tt := range tests {
if err := tt.Healthy(); err == nil {
t.Errorf("case %d: nil error", i)
}
}
}
func TestPrivateKeyManagerHealthyFailsOtherMethods(t *testing.T) {
km := NewPrivateKeyManager()
if _, err := km.JWKs(); err == nil {
t.Fatalf("Expected non-nil error")
}
if _, err := km.Signer(); err == nil {
t.Fatalf("Expected non-nil error")
}
}
func TestPrivateKeyManagerExpiresAt(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
k := generatePrivateKeyStatic(t, 17)
km := &privateKeyManager{
clock: fc,
}
want := fc.Now().UTC()
got := km.ExpiresAt()
if want != got {
t.Fatalf("Incorrect expiration time: want=%v got=%v", want, got)
}
err := km.Set(&PrivateKeySet{
keys: []*PrivateKey{k},
ActiveKeyID: k.KeyID,
expiresAt: now.Add(2 * time.Minute),
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
want = fc.Now().UTC().Add(2 * time.Minute)
got = km.ExpiresAt()
if want != got {
t.Fatalf("Incorrect expiration time: want=%v got=%v", want, got)
}
}
func TestPublicKeys(t *testing.T) {
km := NewPrivateKeyManager()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
tests := [][]*PrivateKey{
[]*PrivateKey{k1},
[]*PrivateKey{k1, k2},
[]*PrivateKey{k1, k2, k3},
}
for i, tt := range tests {
ks := &PrivateKeySet{
keys: tt,
expiresAt: time.Now().Add(time.Hour),
}
km.Set(ks)
jwks, err := km.JWKs()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
pks := NewPublicKeySet(jwks, time.Now().Add(time.Hour))
want := pks.Keys()
got, err := km.PublicKeys()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("case %d: Invalid public keys: want=%v got=%v", i, want, got)
}
}
}

View file

@ -1,55 +0,0 @@
package key
import (
"errors"
"sync"
)
var ErrorNoKeys = errors.New("no keys found")
type WritableKeySetRepo interface {
Set(KeySet) error
}
type ReadableKeySetRepo interface {
Get() (KeySet, error)
}
type PrivateKeySetRepo interface {
WritableKeySetRepo
ReadableKeySetRepo
}
func NewPrivateKeySetRepo() PrivateKeySetRepo {
return &memPrivateKeySetRepo{}
}
type memPrivateKeySetRepo struct {
mu sync.RWMutex
pks PrivateKeySet
}
func (r *memPrivateKeySetRepo) Set(ks KeySet) error {
pks, ok := ks.(*PrivateKeySet)
if !ok {
return errors.New("unable to cast to PrivateKeySet")
} else if pks == nil {
return errors.New("nil KeySet")
}
r.mu.Lock()
defer r.mu.Unlock()
r.pks = *pks
return nil
}
func (r *memPrivateKeySetRepo) Get() (KeySet, error) {
r.mu.RLock()
defer r.mu.RUnlock()
if r.pks.keys == nil {
return nil, ErrorNoKeys
}
return KeySet(&r.pks), nil
}

View file

@ -1,159 +0,0 @@
package key
import (
"errors"
"log"
"time"
ptime "github.com/coreos/pkg/timeutil"
"github.com/jonboulle/clockwork"
)
var (
ErrorPrivateKeysExpired = errors.New("private keys have expired")
)
func NewPrivateKeyRotator(repo PrivateKeySetRepo, ttl time.Duration) *PrivateKeyRotator {
return &PrivateKeyRotator{
repo: repo,
ttl: ttl,
keep: 2,
generateKey: GeneratePrivateKey,
clock: clockwork.NewRealClock(),
}
}
type PrivateKeyRotator struct {
repo PrivateKeySetRepo
generateKey GeneratePrivateKeyFunc
clock clockwork.Clock
keep int
ttl time.Duration
}
func (r *PrivateKeyRotator) expiresAt() time.Time {
return r.clock.Now().UTC().Add(r.ttl)
}
func (r *PrivateKeyRotator) Healthy() error {
pks, err := r.privateKeySet()
if err != nil {
return err
}
if r.clock.Now().After(pks.ExpiresAt()) {
return ErrorPrivateKeysExpired
}
return nil
}
func (r *PrivateKeyRotator) privateKeySet() (*PrivateKeySet, error) {
ks, err := r.repo.Get()
if err != nil {
return nil, err
}
pks, ok := ks.(*PrivateKeySet)
if !ok {
return nil, errors.New("unable to cast to PrivateKeySet")
}
return pks, nil
}
func (r *PrivateKeyRotator) nextRotation() (time.Duration, error) {
pks, err := r.privateKeySet()
if err == ErrorNoKeys {
return 0, nil
}
if err != nil {
return 0, err
}
now := r.clock.Now()
// Ideally, we want to rotate after half the TTL has elapsed.
idealRotationTime := pks.ExpiresAt().Add(-r.ttl / 2)
// If we are past the ideal rotation time, rotate immediatly.
return max(0, idealRotationTime.Sub(now)), nil
}
func max(a, b time.Duration) time.Duration {
if a > b {
return a
}
return b
}
func (r *PrivateKeyRotator) Run() chan struct{} {
attempt := func() {
k, err := r.generateKey()
if err != nil {
log.Printf("go-oidc: failed generating signing key: %v", err)
return
}
exp := r.expiresAt()
if err := rotatePrivateKeys(r.repo, k, r.keep, exp); err != nil {
log.Printf("go-oidc: key rotation failed: %v", err)
return
}
}
stop := make(chan struct{})
go func() {
for {
var nextRotation time.Duration
var sleep time.Duration
var err error
for {
if nextRotation, err = r.nextRotation(); err == nil {
break
}
sleep = ptime.ExpBackoff(sleep, time.Minute)
log.Printf("go-oidc: error getting nextRotation, retrying in %v: %v", sleep, err)
time.Sleep(sleep)
}
select {
case <-r.clock.After(nextRotation):
attempt()
case <-stop:
return
}
}
}()
return stop
}
func rotatePrivateKeys(repo PrivateKeySetRepo, k *PrivateKey, keep int, exp time.Time) error {
ks, err := repo.Get()
if err != nil && err != ErrorNoKeys {
return err
}
var keys []*PrivateKey
if ks != nil {
pks, ok := ks.(*PrivateKeySet)
if !ok {
return errors.New("unable to cast to PrivateKeySet")
}
keys = pks.Keys()
}
keys = append([]*PrivateKey{k}, keys...)
if l := len(keys); l > keep {
keys = keys[0:keep]
}
nks := PrivateKeySet{
keys: keys,
ActiveKeyID: k.ID(),
expiresAt: exp,
}
return repo.Set(KeySet(&nks))
}

View file

@ -1,311 +0,0 @@
package key
import (
"reflect"
"testing"
"time"
"github.com/jonboulle/clockwork"
)
func generatePrivateKeySerialFunc(t *testing.T) GeneratePrivateKeyFunc {
var n int
return func() (*PrivateKey, error) {
n++
return generatePrivateKeyStatic(t, n), nil
}
}
func TestRotate(t *testing.T) {
now := time.Now()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
tests := []struct {
start *PrivateKeySet
key *PrivateKey
keep int
exp time.Time
want *PrivateKeySet
}{
// start with nil keys
{
start: nil,
key: k1,
keep: 2,
exp: now.Add(time.Second),
want: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(time.Second),
},
},
// start with zero keys
{
start: &PrivateKeySet{},
key: k1,
keep: 2,
exp: now.Add(time.Second),
want: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(time.Second),
},
},
// add second key
{
start: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now,
},
key: k2,
keep: 2,
exp: now.Add(time.Second),
want: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(time.Second),
},
},
// rotate in third key
{
start: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now,
},
key: k3,
keep: 2,
exp: now.Add(time.Second),
want: &PrivateKeySet{
keys: []*PrivateKey{k3, k2},
ActiveKeyID: k3.KeyID,
expiresAt: now.Add(time.Second),
},
},
}
for i, tt := range tests {
repo := NewPrivateKeySetRepo()
if tt.start != nil {
err := repo.Set(tt.start)
if err != nil {
t.Fatalf("case %d: unexpected error: %v", i, err)
}
}
rotatePrivateKeys(repo, tt.key, tt.keep, tt.exp)
got, err := repo.Get()
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
if !reflect.DeepEqual(tt.want, got) {
t.Errorf("case %d: unexpected result: want=%#v got=%#v", i, tt.want, got)
}
}
}
func TestPrivateKeyRotatorRun(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
k4 := generatePrivateKeyStatic(t, 4)
kRepo := NewPrivateKeySetRepo()
krot := NewPrivateKeyRotator(kRepo, 4*time.Second)
krot.clock = fc
krot.generateKey = generatePrivateKeySerialFunc(t)
steps := []*PrivateKeySet{
&PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(4 * time.Second),
},
&PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(6 * time.Second),
},
&PrivateKeySet{
keys: []*PrivateKey{k3, k2},
ActiveKeyID: k3.KeyID,
expiresAt: now.Add(8 * time.Second),
},
&PrivateKeySet{
keys: []*PrivateKey{k4, k3},
ActiveKeyID: k4.KeyID,
expiresAt: now.Add(10 * time.Second),
},
}
stop := krot.Run()
defer close(stop)
for i, st := range steps {
// wait for the rotater to get sleepy
fc.BlockUntil(1)
got, err := kRepo.Get()
if err != nil {
t.Fatalf("step %d: unexpected error: %v", i, err)
}
if !reflect.DeepEqual(st, got) {
t.Fatalf("step %d: unexpected state: want=%#v got=%#v", i, st, got)
}
fc.Advance(2 * time.Second)
}
}
func TestPrivateKeyRotatorExpiresAt(t *testing.T) {
fc := clockwork.NewFakeClock()
krot := &PrivateKeyRotator{
clock: fc,
ttl: time.Minute,
}
got := krot.expiresAt()
want := fc.Now().UTC().Add(time.Minute)
if !reflect.DeepEqual(want, got) {
t.Errorf("Incorrect expiration time: want=%v got=%v", want, got)
}
}
func TestNextRotation(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
tests := []struct {
expiresAt time.Time
ttl time.Duration
numKeys int
expected time.Duration
}{
{
// closest to prod
expiresAt: now.Add(time.Hour * 24),
ttl: time.Hour * 24,
numKeys: 2,
expected: time.Hour * 12,
},
{
expiresAt: now.Add(time.Hour * 2),
ttl: time.Hour * 4,
numKeys: 2,
expected: 0,
},
{
// No keys.
expiresAt: now.Add(time.Hour * 2),
ttl: time.Hour * 4,
numKeys: 0,
expected: 0,
},
{
// Nil keyset.
expiresAt: now.Add(time.Hour * 2),
ttl: time.Hour * 4,
numKeys: -1,
expected: 0,
},
{
// KeySet expired.
expiresAt: now.Add(time.Hour * -2),
ttl: time.Hour * 4,
numKeys: 2,
expected: 0,
},
{
// Expiry past now + TTL
expiresAt: now.Add(time.Hour * 5),
ttl: time.Hour * 4,
numKeys: 2,
expected: 3 * time.Hour,
},
}
for i, tt := range tests {
kRepo := NewPrivateKeySetRepo()
krot := NewPrivateKeyRotator(kRepo, tt.ttl)
krot.clock = fc
pks := &PrivateKeySet{
expiresAt: tt.expiresAt,
}
if tt.numKeys != -1 {
for n := 0; n < tt.numKeys; n++ {
pks.keys = append(pks.keys, generatePrivateKeyStatic(t, n))
}
err := kRepo.Set(pks)
if err != nil {
t.Fatalf("case %d: unexpected error: %v", i, err)
}
}
actual, err := krot.nextRotation()
if err != nil {
t.Errorf("case %d: error calling shouldRotate(): %v", i, err)
}
if actual != tt.expected {
t.Errorf("case %d: actual == %v, want %v", i, actual, tt.expected)
}
}
}
func TestHealthy(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
tests := []struct {
expiresAt time.Time
numKeys int
expected error
}{
{
expiresAt: now.Add(time.Hour),
numKeys: 2,
expected: nil,
},
{
expiresAt: now.Add(time.Hour),
numKeys: -1,
expected: ErrorNoKeys,
},
{
expiresAt: now.Add(time.Hour),
numKeys: 0,
expected: ErrorNoKeys,
},
{
expiresAt: now.Add(-time.Hour),
numKeys: 2,
expected: ErrorPrivateKeysExpired,
},
}
for i, tt := range tests {
kRepo := NewPrivateKeySetRepo()
krot := NewPrivateKeyRotator(kRepo, time.Hour)
krot.clock = fc
pks := &PrivateKeySet{
expiresAt: tt.expiresAt,
}
if tt.numKeys != -1 {
for n := 0; n < tt.numKeys; n++ {
pks.keys = append(pks.keys, generatePrivateKeyStatic(t, n))
}
err := kRepo.Set(pks)
if err != nil {
t.Fatalf("case %d: unexpected error: %v", i, err)
}
}
if err := krot.Healthy(); err != tt.expected {
t.Errorf("case %d: got==%q, want==%q", i, err, tt.expected)
}
}
}

View file

@ -1,91 +0,0 @@
package key
import (
"errors"
"log"
"time"
"github.com/jonboulle/clockwork"
"github.com/coreos/pkg/timeutil"
)
func NewKeySetSyncer(r ReadableKeySetRepo, w WritableKeySetRepo) *KeySetSyncer {
return &KeySetSyncer{
readable: r,
writable: w,
clock: clockwork.NewRealClock(),
}
}
type KeySetSyncer struct {
readable ReadableKeySetRepo
writable WritableKeySetRepo
clock clockwork.Clock
}
func (s *KeySetSyncer) Run() chan struct{} {
stop := make(chan struct{})
go func() {
var failing bool
var next time.Duration
for {
exp, err := syncKeySet(s.readable, s.writable, s.clock)
if err != nil || exp == 0 {
if !failing {
failing = true
next = time.Second
} else {
next = timeutil.ExpBackoff(next, time.Minute)
}
if exp == 0 {
log.Printf("Synced to already expired key set, retrying in %v: %v", next, err)
} else {
log.Printf("Failed syncing key set, retrying in %v: %v", next, err)
}
} else {
failing = false
next = exp / 2
}
select {
case <-s.clock.After(next):
continue
case <-stop:
return
}
}
}()
return stop
}
func Sync(r ReadableKeySetRepo, w WritableKeySetRepo) (time.Duration, error) {
return syncKeySet(r, w, clockwork.NewRealClock())
}
// syncKeySet copies the keyset from r to the KeySet at w and returns the duration in which the KeySet will expire.
// If keyset has already expired, returns a zero duration.
func syncKeySet(r ReadableKeySetRepo, w WritableKeySetRepo, clock clockwork.Clock) (exp time.Duration, err error) {
var ks KeySet
ks, err = r.Get()
if err != nil {
return
}
if ks == nil {
err = errors.New("no source KeySet")
return
}
if err = w.Set(ks); err != nil {
return
}
now := clock.Now()
if ks.ExpiresAt().After(now) {
exp = ks.ExpiresAt().Sub(now)
}
return
}

View file

@ -1,214 +0,0 @@
package key
import (
"errors"
"reflect"
"sync"
"testing"
"time"
"github.com/jonboulle/clockwork"
)
type staticReadableKeySetRepo struct {
mu sync.RWMutex
ks KeySet
err error
}
func (r *staticReadableKeySetRepo) Get() (KeySet, error) {
r.mu.RLock()
defer r.mu.RUnlock()
return r.ks, r.err
}
func (r *staticReadableKeySetRepo) set(ks KeySet, err error) {
r.mu.Lock()
defer r.mu.Unlock()
r.ks, r.err = ks, err
}
func TestKeySyncerSync(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
steps := []struct {
fromKS KeySet
fromErr error
advance time.Duration
want *PrivateKeySet
}{
// on startup, first sync should trigger within a second
{
fromKS: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(10 * time.Second),
},
advance: time.Second,
want: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(10 * time.Second),
},
},
// advance halfway into TTL, triggering sync
{
fromKS: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(15 * time.Second),
},
advance: 5 * time.Second,
want: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(15 * time.Second),
},
},
// advance halfway into TTL, triggering sync that fails
{
fromErr: errors.New("fail!"),
advance: 10 * time.Second,
want: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(15 * time.Second),
},
},
// sync retries quickly, and succeeds with fixed data
{
fromKS: &PrivateKeySet{
keys: []*PrivateKey{k3, k2, k1},
ActiveKeyID: k3.KeyID,
expiresAt: now.Add(25 * time.Second),
},
advance: 3 * time.Second,
want: &PrivateKeySet{
keys: []*PrivateKey{k3, k2, k1},
ActiveKeyID: k3.KeyID,
expiresAt: now.Add(25 * time.Second),
},
},
}
from := &staticReadableKeySetRepo{}
to := NewPrivateKeySetRepo()
syncer := NewKeySetSyncer(from, to)
syncer.clock = fc
stop := syncer.Run()
defer close(stop)
for i, st := range steps {
from.set(st.fromKS, st.fromErr)
fc.Advance(st.advance)
fc.BlockUntil(1)
ks, err := to.Get()
if err != nil {
t.Fatalf("step %d: unable to get keys: %v", i, err)
}
if !reflect.DeepEqual(st.want, ks) {
t.Fatalf("step %d: incorrect state: want=%#v got=%#v", i, st.want, ks)
}
}
}
func TestSync(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
tests := []struct {
keySet *PrivateKeySet
want time.Duration
}{
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(time.Minute),
},
want: time.Minute,
},
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(time.Minute),
},
want: time.Minute,
},
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k3, k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(time.Minute),
},
want: time.Minute,
},
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(time.Hour),
},
want: time.Hour,
},
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(-time.Hour),
},
want: 0,
},
}
for i, tt := range tests {
from := NewPrivateKeySetRepo()
to := NewPrivateKeySetRepo()
err := from.Set(tt.keySet)
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
exp, err := syncKeySet(from, to, fc)
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
if tt.want != exp {
t.Errorf("case %d: want=%v got=%v", i, tt.want, exp)
}
}
}
func TestSyncFail(t *testing.T) {
tests := []error{
nil,
errors.New("fail!"),
}
for i, tt := range tests {
from := &staticReadableKeySetRepo{ks: nil, err: tt}
to := NewPrivateKeySetRepo()
if _, err := syncKeySet(from, to, clockwork.NewFakeClock()); err == nil {
t.Errorf("case %d: expected non-nil error", i)
}
}
}

View file

@ -1,2 +0,0 @@
// Package oauth2 is DEPRECATED. Use golang.org/x/oauth instead.
package oauth2

View file

@ -1,29 +0,0 @@
package oauth2
const (
ErrorAccessDenied = "access_denied"
ErrorInvalidClient = "invalid_client"
ErrorInvalidGrant = "invalid_grant"
ErrorInvalidRequest = "invalid_request"
ErrorServerError = "server_error"
ErrorUnauthorizedClient = "unauthorized_client"
ErrorUnsupportedGrantType = "unsupported_grant_type"
ErrorUnsupportedResponseType = "unsupported_response_type"
)
type Error struct {
Type string `json:"error"`
Description string `json:"error_description,omitempty"`
State string `json:"state,omitempty"`
}
func (e *Error) Error() string {
if e.Description != "" {
return e.Type + ": " + e.Description
}
return e.Type
}
func NewError(typ string) *Error {
return &Error{Type: typ}
}

Some files were not shown because too many files have changed in this diff Show more