diff --git a/.gitignore b/.gitignore index 52aa8baa4..a76f82279 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/machine-approver \ No newline at end of file +/machine-approver +*.idea* diff --git a/Dockerfile b/Dockerfile index 5f27ada54..3c44e8564 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,12 @@ -FROM registry.svc.ci.openshift.org/openshift/release:golang-1.10 AS builder -COPY . /go/src/github.com/openshift/cluster-machine-appover -RUN cd /go/src/github.com/openshift/cluster-machine-appover && go build -o machine-approver . +# +# The standard name for this image is openshift/origin-cluster-machine-approver +# +FROM openshift/origin-release:golang-1.10 +COPY . /go/src/github.com/openshift/cluster-machine-approver +RUN cd /go/src/github.com/openshift/cluster-machine-approver && go build ./cmd/machine-approver + +FROM centos:7 +COPY --from=0 /go/src/github.com/openshift/cluster-machine-approver/machine-approver /usr/bin/machine-approver -FROM registry.svc.ci.openshift.org/openshift/origin-v4.0:base -COPY --from=builder /go/src/github.com/openshift/cluster-machine-appover/machine-approver /usr/bin/machine-approver COPY manifests /manifests LABEL io.openshift.release.operator true diff --git a/cmd/machine-approver/main.go b/cmd/machine-approver/main.go new file mode 100644 index 000000000..3ef5bcd2e --- /dev/null +++ b/cmd/machine-approver/main.go @@ -0,0 +1,48 @@ +package main + +import ( + goflag "flag" + "fmt" + "math/rand" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + utilflag "k8s.io/apiserver/pkg/util/flag" + "k8s.io/apiserver/pkg/util/logs" + + "github.com/openshift/cluster-machine-approver/pkg/cmd/operator" +) + +func main() { + rand.Seed(time.Now().UTC().UnixNano()) + + pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc) + pflag.CommandLine.AddGoFlagSet(goflag.CommandLine) + + logs.InitLogs() + defer logs.FlushLogs() + + command := NewMachineApproverOperatorCommand() + if err := command.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +func NewMachineApproverOperatorCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "machine-approver", + Short: "OpenShift osin OAuth server operator", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + os.Exit(1) + }, + } + + cmd.AddCommand(operator.NewOperator()) + + return cmd +} diff --git a/glide.lock b/glide.lock index 6aef03ee1..b7a7ce213 100644 --- a/glide.lock +++ b/glide.lock @@ -1,12 +1,125 @@ -hash: 282eb1166ddf7d7a4ab26c4394fdbf2a72486f9a6bcbfee70035b67713053a1e -updated: 2018-11-20T11:22:43.732173456-06:00 +hash: 2234530e812931feedd38cf9bfcf7c6bbca448f37f8d2c95090db5da9b6a1379 +updated: 2018-12-18T13:43:41.536022125-05:00 imports: +- name: bitbucket.org/ww/goautoneg + version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 +- name: github.com/beorn7/perks + version: 3ac7bf7a47d159a033b107610db8a1b6575507a4 + subpackages: + - quantile +- name: github.com/certifi/gocertifi + version: ee1a9a0726d2ae45f54118cac878c990d4016ded +- name: github.com/coreos/etcd + version: 95a726a27e09030f9ccbd9982a1508f5a6d25ada + subpackages: + - alarm + - auth + - auth/authpb + - client + - clientv3 + - clientv3/concurrency + - clientv3/namespace + - clientv3/naming + - compactor + - discovery + - embed + - error + - etcdserver + - etcdserver/api + - etcdserver/api/etcdhttp + - etcdserver/api/v2http + - etcdserver/api/v2http/httptypes + - etcdserver/api/v3client + - etcdserver/api/v3election + - etcdserver/api/v3election/v3electionpb + - etcdserver/api/v3election/v3electionpb/gw + - etcdserver/api/v3lock + - etcdserver/api/v3lock/v3lockpb + - etcdserver/api/v3lock/v3lockpb/gw + - etcdserver/api/v3rpc + - etcdserver/api/v3rpc/rpctypes + - etcdserver/auth + - etcdserver/etcdserverpb + - etcdserver/etcdserverpb/gw + - etcdserver/membership + - etcdserver/stats + - integration + - lease + - lease/leasehttp + - lease/leasepb + - mvcc + - mvcc/backend + - mvcc/mvccpb + - pkg/adt + - pkg/contention + - pkg/cors + - pkg/cpuutil + - pkg/crc + - pkg/debugutil + - pkg/fileutil + - pkg/httputil + - pkg/idutil + - pkg/ioutil + - pkg/logutil + - pkg/monotime + - pkg/netutil + - pkg/pathutil + - pkg/pbutil + - pkg/runtime + - pkg/schedule + - pkg/srv + - pkg/testutil + - pkg/tlsutil + - pkg/transport + - pkg/types + - pkg/wait + - proxy/grpcproxy + - proxy/grpcproxy/adapter + - proxy/grpcproxy/cache + - raft + - raft/raftpb + - rafthttp + - snap + - snap/snappb + - store + - version + - wal + - wal/walpb +- name: github.com/coreos/go-semver + version: 568e959cd89871e61434c1143528d9162da89ef2 + subpackages: + - semver +- name: github.com/coreos/go-systemd + version: 48702e0da86bd25e76cfef347e2adeb434a0d0a6 + subpackages: + - daemon + - journal - name: github.com/davecgh/go-spew version: 782f4967f2dc4564575ca782fe2d04090b5faca8 subpackages: - spew +- name: github.com/elazarl/go-bindata-assetfs + version: 3dcc96556217539f50599357fb481ac0dc7439b9 +- name: github.com/emicklei/go-restful + version: ff4f55a206334ef123e4f79bbf348980da81ca46 + subpackages: + - log +- name: github.com/emicklei/go-restful-swagger12 + version: dcef7f55730566d41eae5db10e7d6981829720f6 +- name: github.com/evanphx/json-patch + version: 94e38aa1586e8a6c8a75770bddf5ff84c48a106b +- name: github.com/getsentry/raven-go + version: 32a13797442ccb601b11761d74232773c1402d14 - name: github.com/ghodss/yaml version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee +- name: github.com/go-openapi/jsonpointer + version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 +- name: github.com/go-openapi/jsonreference + version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 +- name: github.com/go-openapi/spec + version: 1de3e0542de65ad8d75452a595886fdd0befb363 +- name: github.com/go-openapi/swag + version: f3f9494671f93fcff853e3c6e9e948b3eb71e590 - name: github.com/gogo/protobuf version: c0656edd0d9eab7c66d1eb0c568f9039345796f7 subpackages: @@ -14,10 +127,15 @@ imports: - sortkeys - name: github.com/golang/glog version: 44145f04b68cf362d9c4df2182967c2275eaefed +- name: github.com/golang/groupcache + version: 02826c3e79038b59d737d3b1c0a1d937f71a4433 + subpackages: + - lru - name: github.com/golang/protobuf version: b4deda0973fb4c70b50d226b1af49f3da59f5265 subpackages: - proto + - protoc-gen-go/descriptor - ptypes - ptypes/any - ptypes/duration @@ -42,16 +160,88 @@ imports: - simplelru - name: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc +- name: github.com/inconshreveable/mousetrap + version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/json-iterator/go version: f2b4162afba35581b6d4a50d3b8f34e33c144682 +- name: github.com/jteeuwen/go-bindata + version: a0ff2567cfb70903282db057e799fd826784d41d +- name: github.com/mailru/easyjson + version: 2f5df55504ebc322e4d52d34df6a1f5b503bf26d + subpackages: + - buffer + - jlexer + - jwriter +- name: github.com/matttproud/golang_protobuf_extensions + version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a + subpackages: + - pbutil - name: github.com/modern-go/concurrent version: bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94 - name: github.com/modern-go/reflect2 version: 05fbef0ca5da472bbf96c9322b84a53edc03c9fd +- name: github.com/NYTimes/gziphandler + version: 56545f4a5d46df9a6648819d1664c3a03a13ffdb +- name: github.com/openshift/api + version: 797a09d386e407506afc2f8562f81d335d57aaab + subpackages: + - config/v1 + - operator/v1alpha1 +- name: github.com/openshift/client-go + version: 960f72aa32a8e9b4dd769b90ff1cb5bd4c898eec +- name: github.com/openshift/library-go + version: 2dea7f3f68892fa07254c0b55b59864ce4019f0f + subpackages: + - pkg/config/client + - pkg/config/configdefaults + - pkg/config/leaderelection + - pkg/config/serving + - pkg/controller/controllercmd + - pkg/controller/fileobserver + - pkg/controller/metrics + - pkg/crypto + - pkg/operator/events + - pkg/serviceability +- name: github.com/pborman/uuid + version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 - name: github.com/peterbourgon/diskv version: 5f041e8faa004a95c88a202771f4cc3e991971e6 +- name: github.com/pkg/errors + version: 645ef00459ed84a119197bfb8d8205042c6df63d +- name: github.com/pkg/profile + version: 22592cc4bae3572d59be8852c6980023e4c9dec5 +- name: github.com/prometheus/client_golang + version: e7e903064f5e9eb5da98208bae10b475d4db0f8c + subpackages: + - prometheus +- name: github.com/prometheus/client_model + version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6 + subpackages: + - go +- name: github.com/prometheus/common + version: 13ba4ddd0caa9c28ca7b7bffe1dfa9ed8d5ef207 + subpackages: + - expfmt + - internal/bitbucket.org/ww/goautoneg + - model +- name: github.com/prometheus/procfs + version: 65c1f6f8f0fc1e2185eb9863a3bc751496404259 + subpackages: + - xfs +- name: github.com/PuerkitoBio/purell + version: 8a290539e2e8629dbc4e6bad948158f790ec31f4 +- name: github.com/PuerkitoBio/urlesc + version: 5bd2802263f21d8788851d5305584c82a5c75d7e +- name: github.com/sirupsen/logrus + version: 89742aefa4b206dcf400792f3bd35b542998eb3b +- name: github.com/spf13/cobra + version: c439c4fa093711d42e1b01acb1235b52004753c1 - name: github.com/spf13/pflag version: 583c0c0531f06d5278b7d917446061adc344b5cd +- name: github.com/ugorji/go + version: ded73eae5db7e7a0ef6f55aace87a2873c5d2b74 + subpackages: + - codec - name: golang.org/x/crypto version: 49796115aa4b964c318aad4f3084fdb41e9aa067 subpackages: @@ -63,7 +253,10 @@ imports: - http2 - http2/hpack - idna + - internal/timeseries - lex/httplex + - trace + - websocket - name: golang.org/x/sys version: 95c6576299259db960f6c5b9b69ea52422860fce subpackages: @@ -72,21 +265,56 @@ imports: - name: golang.org/x/text version: b19bf474d317b857955b12035d2c5acb57ce8b01 subpackages: + - cases + - internal + - internal/tag + - language + - runes - secure/bidirule + - secure/precis - transform - unicode/bidi - unicode/norm + - width - name: golang.org/x/time version: f51c12702a4d776e4c1fa9b0fabab841babae631 subpackages: - rate +- name: google.golang.org/genproto + version: 09f6ed296fc66555a25fe4ce95173148778dfa85 + subpackages: + - googleapis/api/annotations + - googleapis/rpc/status +- name: google.golang.org/grpc + version: 5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e + subpackages: + - balancer + - codes + - connectivity + - credentials + - grpclb/grpc_lb_v1/messages + - grpclog + - health/grpc_health_v1 + - internal + - keepalive + - metadata + - naming + - peer + - resolver + - stats + - status + - tap + - transport - name: gopkg.in/inf.v0 version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 +- name: gopkg.in/natefinch/lumberjack.v2 + version: 20b71e5b60d756d3d2f80def009790325acc2b23 - name: gopkg.in/yaml.v2 version: 670d4cfef0544295bc27a114dbac37980d83185a - name: k8s.io/api version: 2d6f90ab1293a1fb871cf149423ebb72aa7423aa subpackages: + - admission/v1beta1 - admissionregistration/v1alpha1 - admissionregistration/v1beta1 - apps/v1 @@ -116,15 +344,21 @@ imports: - storage/v1 - storage/v1alpha1 - storage/v1beta1 +- name: k8s.io/apiextensions-apiserver + version: 06dfdaae5c2bd89e1243151ff65b9bf8ee050f28 - name: k8s.io/apimachinery version: 103fd098999dc9c0c88536f5c9ad2e5da39373ae subpackages: + - pkg/api/equality - pkg/api/errors - pkg/api/meta - pkg/api/resource + - pkg/api/validation + - pkg/api/validation/path - pkg/apis/meta/internalversion - pkg/apis/meta/v1 - pkg/apis/meta/v1/unstructured + - pkg/apis/meta/v1/validation - pkg/apis/meta/v1beta1 - pkg/conversion - pkg/conversion/queryparams @@ -147,20 +381,159 @@ imports: - pkg/util/framer - pkg/util/intstr - pkg/util/json + - pkg/util/mergepatch - pkg/util/net + - pkg/util/rand - pkg/util/runtime - pkg/util/sets + - pkg/util/strategicpatch + - pkg/util/uuid - pkg/util/validation - pkg/util/validation/field - pkg/util/wait + - pkg/util/waitgroup - pkg/util/yaml - pkg/version - pkg/watch + - third_party/forked/golang/json - third_party/forked/golang/reflect +- name: k8s.io/apiserver + version: 8b122ec9e3bbab91a262d17a39325e69349dc44d + subpackages: + - pkg/admission + - pkg/admission/configuration + - pkg/admission/initializer + - pkg/admission/metrics + - pkg/admission/plugin/initialization + - pkg/admission/plugin/namespace/lifecycle + - pkg/admission/plugin/webhook/config + - pkg/admission/plugin/webhook/config/apis/webhookadmission + - pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1 + - pkg/admission/plugin/webhook/errors + - pkg/admission/plugin/webhook/generic + - pkg/admission/plugin/webhook/mutating + - pkg/admission/plugin/webhook/namespace + - pkg/admission/plugin/webhook/request + - pkg/admission/plugin/webhook/rules + - pkg/admission/plugin/webhook/validating + - pkg/apis/apiserver + - pkg/apis/apiserver/install + - pkg/apis/apiserver/v1alpha1 + - pkg/apis/audit + - pkg/apis/audit/install + - pkg/apis/audit/v1alpha1 + - pkg/apis/audit/v1beta1 + - pkg/apis/audit/validation + - pkg/audit + - pkg/audit/policy + - pkg/authentication/authenticator + - pkg/authentication/authenticatorfactory + - pkg/authentication/group + - pkg/authentication/request/anonymous + - pkg/authentication/request/bearertoken + - pkg/authentication/request/headerrequest + - pkg/authentication/request/union + - pkg/authentication/request/websocket + - pkg/authentication/request/x509 + - pkg/authentication/serviceaccount + - pkg/authentication/token/tokenfile + - pkg/authentication/user + - pkg/authorization/authorizer + - pkg/authorization/authorizerfactory + - pkg/authorization/union + - pkg/endpoints + - pkg/endpoints/discovery + - pkg/endpoints/filters + - pkg/endpoints/handlers + - pkg/endpoints/handlers/negotiation + - pkg/endpoints/handlers/responsewriters + - pkg/endpoints/metrics + - pkg/endpoints/openapi + - pkg/endpoints/request + - pkg/features + - pkg/registry/generic + - pkg/registry/generic/registry + - pkg/registry/rest + - pkg/server + - pkg/server/filters + - pkg/server/healthz + - pkg/server/httplog + - pkg/server/mux + - pkg/server/options + - pkg/server/resourceconfig + - pkg/server/routes + - pkg/server/routes/data/swagger + - pkg/server/storage + - pkg/storage + - pkg/storage/errors + - pkg/storage/etcd + - pkg/storage/etcd/metrics + - pkg/storage/etcd/util + - pkg/storage/etcd3 + - pkg/storage/etcd3/preflight + - pkg/storage/names + - pkg/storage/storagebackend + - pkg/storage/storagebackend/factory + - pkg/storage/value + - pkg/util/feature + - pkg/util/flag + - pkg/util/flushwriter + - pkg/util/logs + - pkg/util/openapi + - pkg/util/trace + - pkg/util/webhook + - pkg/util/wsstream + - plugin/pkg/audit/buffered + - plugin/pkg/audit/log + - plugin/pkg/audit/truncate + - plugin/pkg/audit/webhook + - plugin/pkg/authenticator/token/webhook + - plugin/pkg/authorizer/webhook - name: k8s.io/client-go version: 59698c7d9724b0f95f9dc9e7f7dfdcc3dfeceb82 subpackages: - discovery + - informers + - informers/admissionregistration + - informers/admissionregistration/v1alpha1 + - informers/admissionregistration/v1beta1 + - informers/apps + - informers/apps/v1 + - informers/apps/v1beta1 + - informers/apps/v1beta2 + - informers/autoscaling + - informers/autoscaling/v1 + - informers/autoscaling/v2beta1 + - informers/batch + - informers/batch/v1 + - informers/batch/v1beta1 + - informers/batch/v2alpha1 + - informers/certificates + - informers/certificates/v1beta1 + - informers/core + - informers/core/v1 + - informers/events + - informers/events/v1beta1 + - informers/extensions + - informers/extensions/v1beta1 + - informers/internalinterfaces + - informers/networking + - informers/networking/v1 + - informers/policy + - informers/policy/v1beta1 + - informers/rbac + - informers/rbac/v1 + - informers/rbac/v1alpha1 + - informers/rbac/v1beta1 + - informers/scheduling + - informers/scheduling/v1alpha1 + - informers/scheduling/v1beta1 + - informers/settings + - informers/settings/v1alpha1 + - informers/storage + - informers/storage/v1 + - informers/storage/v1alpha1 + - informers/storage/v1beta1 - kubernetes - kubernetes/scheme - kubernetes/typed/admissionregistration/v1alpha1 @@ -192,6 +565,31 @@ imports: - kubernetes/typed/storage/v1 - kubernetes/typed/storage/v1alpha1 - kubernetes/typed/storage/v1beta1 + - listers/admissionregistration/v1alpha1 + - listers/admissionregistration/v1beta1 + - listers/apps/v1 + - listers/apps/v1beta1 + - listers/apps/v1beta2 + - listers/autoscaling/v1 + - listers/autoscaling/v2beta1 + - listers/batch/v1 + - listers/batch/v1beta1 + - listers/batch/v2alpha1 + - listers/certificates/v1beta1 + - listers/core/v1 + - listers/events/v1beta1 + - listers/extensions/v1beta1 + - listers/networking/v1 + - listers/policy/v1beta1 + - listers/rbac/v1 + - listers/rbac/v1alpha1 + - listers/rbac/v1beta1 + - listers/scheduling/v1alpha1 + - listers/scheduling/v1beta1 + - listers/settings/v1alpha1 + - listers/storage/v1 + - listers/storage/v1alpha1 + - listers/storage/v1beta1 - pkg/apis/clientauthentication - pkg/apis/clientauthentication/v1alpha1 - pkg/apis/clientauthentication/v1beta1 @@ -205,8 +603,11 @@ imports: - tools/clientcmd/api - tools/clientcmd/api/latest - tools/clientcmd/api/v1 + - tools/leaderelection + - tools/leaderelection/resourcelock - tools/metrics - tools/pager + - tools/record - tools/reference - transport - util/buffer @@ -217,4 +618,14 @@ imports: - util/integer - util/retry - util/workqueue +- name: k8s.io/kube-aggregator + version: 89cd614e9090a2f1e78316ed459857c49c55d276 +- name: k8s.io/kube-openapi + version: 91cfa479c814065e420cee7ed227db0f63a5854e + subpackages: + - pkg/builder + - pkg/common + - pkg/handler + - pkg/util + - pkg/util/proto testImports: [] diff --git a/glide.yaml b/glide.yaml index 493347f35..96962b28c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,21 +1,24 @@ package: github.com/openshift/cluster-machine-approver import: +- package: k8s.io/apimachinery + version: kubernetes-1.11.1 - package: k8s.io/api version: kubernetes-1.11.1 - subpackages: - - certificates/v1beta1 - - core/v1 -- package: k8s.io/apimachinery +- package: k8s.io/apiserver version: kubernetes-1.11.1 - subpackages: - - pkg/fields - - pkg/util/runtime - - pkg/util/wait - package: k8s.io/client-go version: kubernetes-1.11.1 - subpackages: - - kubernetes - - tools/cache - - tools/clientcmd - - util/workqueue -- package: github.com/golang/glog +- package: k8s.io/kube-aggregator + version: kubernetes-1.11.1 +- package: k8s.io/apiextensions-apiserver + version: kubernetes-1.11.1 +- package: github.com/openshift/api + version: master +- package: github.com/openshift/client-go + version: master +- package: github.com/openshift/library-go + version: master + +#for bindata +- package: github.com/jteeuwen/go-bindata + version: a0ff2567cfb70903282db057e799fd826784d41d diff --git a/hack/boilerplate.txt b/hack/boilerplate.txt new file mode 100644 index 000000000..e69de29bb diff --git a/hack/build-cross.sh b/hack/build-cross.sh new file mode 100755 index 000000000..04262f85d --- /dev/null +++ b/hack/build-cross.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# Build all cross compile targets and the base binaries +STARTTIME=$(date +%s) +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +host_platform="$(os::build::host_platform)" + +# by default, build for these platforms +platforms=( + linux/amd64 + darwin/amd64 + windows/amd64 +) +image_platforms=( ) +test_platforms=( "${host_platform}" ) + +targets=( "${OS_CROSS_COMPILE_TARGETS[@]}" ) + +# Special case ppc64le +if [[ "${host_platform}" == "linux/ppc64le" ]]; then + platforms+=( "linux/ppc64le" ) +fi + +# Special case arm64 +if [[ "${host_platform}" == "linux/arm64" ]]; then + platforms+=( "linux/arm64" ) +fi + +# Special case s390x +if [[ "${host_platform}" == "linux/s390x" ]]; then + platforms+=( "linux/s390x" ) +fi + +# On linux platforms, build images +if [[ "${host_platform}" == linux/* ]]; then + image_platforms+=( "${host_platform}" ) +fi + +# filter platform list +if [[ -n "${OS_ONLY_BUILD_PLATFORMS-}" ]]; then + filtered=( ) + for platform in ${platforms[@]}; do + if [[ "${platform}" =~ "${OS_ONLY_BUILD_PLATFORMS}" ]]; then + filtered+=("${platform}") + fi + done + platforms=("${filtered[@]+"${filtered[@]}"}") + + filtered=( ) + for platform in ${image_platforms[@]}; do + if [[ "${platform}" =~ "${OS_ONLY_BUILD_PLATFORMS}" ]]; then + filtered+=("${platform}") + fi + done + image_platforms=("${filtered[@]+"${filtered[@]}"}") + + filtered=( ) + for platform in ${test_platforms[@]}; do + if [[ "${platform}" =~ "${OS_ONLY_BUILD_PLATFORMS}" ]]; then + filtered+=("${platform}") + fi + done + test_platforms=("${filtered[@]+"${filtered[@]}"}") +fi + +# Build image binaries for a subset of platforms. Image binaries are currently +# linux-only, and a subset of them are compiled with flags to make them static +# for use in Docker images "FROM scratch". +OS_BUILD_PLATFORMS=("${image_platforms[@]+"${image_platforms[@]}"}") +os::build::build_static_binaries "${OS_SCRATCH_IMAGE_COMPILE_TARGETS_LINUX[@]-}" +os::build::build_binaries "${OS_IMAGE_COMPILE_TARGETS_LINUX[@]-}" + +# Build the primary client/server for all platforms +OS_BUILD_PLATFORMS=("${platforms[@]+"${platforms[@]}"}") +os::build::build_binaries "${OS_CROSS_COMPILE_TARGETS[@]-}" + +# Build the test binaries for the host platform +OS_BUILD_PLATFORMS=("${test_platforms[@]+"${test_platforms[@]}"}") +os::build::build_binaries "${OS_TEST_TARGETS[@]-}" + +# Place binaries only +OS_BUILD_PLATFORMS=("${platforms[@]+"${platforms[@]}"}") \ + os::build::place_bins "${OS_CROSS_COMPILE_BINARIES[@]-}" +OS_BUILD_PLATFORMS=("${image_platforms[@]+"${image_platforms[@]}"}") \ + os::build::place_bins "${OS_IMAGE_COMPILE_BINARIES[@]-}" + +if [[ "${OS_GIT_TREE_STATE:-dirty}" == "clean" ]]; then + # only when we are building from a clean state can we claim to + # have created a valid set of binaries that can resemble a release + mkdir -p "${OS_OUTPUT_RELEASEPATH}" + echo "${OS_GIT_COMMIT}" > "${OS_OUTPUT_RELEASEPATH}/.commit" +fi + +ret=$?; ENDTIME=$(date +%s); echo "$0 took $(($ENDTIME - $STARTTIME)) seconds"; exit "$ret" diff --git a/hack/build-go.sh b/hack/build-go.sh new file mode 100755 index 000000000..04cd82fa8 --- /dev/null +++ b/hack/build-go.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# This script sets up a go workspace locally and builds all go components. +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +function cleanup() { + return_code=$? + os::util::describe_return_code "${return_code}" + exit "${return_code}" +} +trap "cleanup" EXIT + +build_targets=("$@") +if [[ -z "$@" ]]; then + build_targets=("${OS_CROSS_COMPILE_TARGETS[@]}") +fi + +platform="$(os::build::host_platform)" +OS_BUILD_PLATFORMS=("${OS_BUILD_PLATFORMS[@]:-${platform}}") +os::build::build_binaries "${build_targets[@]}" +os::build::place_bins "${build_targets[@]}" diff --git a/hack/build-images.sh b/hack/build-images.sh new file mode 100755 index 000000000..5c81ea9c6 --- /dev/null +++ b/hack/build-images.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# This script builds all images locally except the base and release images, +# which are handled by hack/build-base-images.sh. + +# NOTE: you only need to run this script if your code changes are part of +# any images OpenShift runs internally such as origin-sti-builder, origin-docker-builder, +# origin-deployer, etc. +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +function cleanup() { + return_code=$? + os::util::describe_return_code "${return_code}" + exit "${return_code}" +} +trap "cleanup" EXIT + +os::util::ensure::gopath_binary_exists imagebuilder + +os::build::images \ No newline at end of file diff --git a/hack/build-rpms.sh b/hack/build-rpms.sh new file mode 100755 index 000000000..32b45f4a5 --- /dev/null +++ b/hack/build-rpms.sh @@ -0,0 +1,131 @@ +#!/bin/bash + +# This script generates release zips and RPMs into _output/releases. +# tito and other build dependencies are required on the host. We will +# be running `hack/build-cross.sh` under the covers, so we transitively +# consume all of the relevant envars. +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +function cleanup() { + return_code=$? + os::util::describe_return_code "${return_code}" + exit "${return_code}" +} +trap "cleanup" EXIT + +# check whether we are in a clean output state +dirty="$( if [[ -d "${OS_OUTPUT}" ]]; then echo '1'; fi )" + +os::util::ensure::system_binary_exists rpmbuild +os::util::ensure::system_binary_exists createrepo +os::build::setup_env + +if [[ "${OS_ONLY_BUILD_PLATFORMS:-}" == 'linux/amd64' ]]; then + # when the user is asking for only Linux binaries, we will + # furthermore not build cross-platform clients in tito + make_redistributable=0 +else + make_redistributable=1 +fi +if [[ -n "${OS_BUILD_SRPM-}" ]]; then + srpm="a" +else + srpm="b" +fi + + +os::build::rpm::get_nvra_vars + +OS_RPM_SPECFILE="$( find "${OS_ROOT}" -name *.spec )" +OS_RPM_NAME="$( rpmspec -q --qf '%{name}\n' "${OS_RPM_SPECFILE}" | head -1 )" + +os::log::info "Building release RPMs for ${OS_RPM_SPECFILE} ..." + +rpm_tmp_dir="${BASETMPDIR}/rpm" + +# RPM requires the spec file be owned by the invoking user +chown "$(id -u):$(id -g)" "${OS_RPM_SPECFILE}" || true + +if [[ -n "${dirty}" && "${OS_GIT_TREE_STATE}" == "dirty" ]]; then + os::log::warning "Repository is not clean, performing fast build and reusing _output" + + # build and output from source to destination + mkdir -p "${rpm_tmp_dir}" + ln -fs "${OS_ROOT}" "${rpm_tmp_dir}/SOURCES" + ln -fs "${OS_ROOT}" "${rpm_tmp_dir}/BUILD" + + rpmbuild -bb "${OS_RPM_SPECFILE}" \ + --define "_sourcedir ${rpm_tmp_dir}/SOURCES" \ + --define "_builddir ${rpm_tmp_dir}/BUILD" \ + --define "skip_prep 1" \ + --define "skip_dist 1" \ + --define "make_redistributable ${make_redistributable}" \ + --define "version ${OS_RPM_VERSION}" --define "release ${OS_RPM_RELEASE}" \ + --define "commit ${OS_GIT_COMMIT}" \ + --define "os_git_vars ${OS_RPM_GIT_VARS}" \ + --define 'dist .el7' --define "_topdir ${rpm_tmp_dir}" + + mkdir -p "${OS_OUTPUT_RPMPATH}" + mv -f "${rpm_tmp_dir}"/RPMS/*/*.rpm "${OS_OUTPUT_RPMPATH}" + +else + mkdir -p "${rpm_tmp_dir}/SOURCES" + tar czf "${rpm_tmp_dir}/SOURCES/${OS_RPM_NAME}-${OS_RPM_VERSION}.tar.gz" \ + --owner=0 --group=0 \ + --exclude=_output --exclude=.git --transform "s|^|${OS_RPM_NAME}-${OS_RPM_VERSION}/|rSH" \ + . + + rpmbuild -b${srpm} "${OS_RPM_SPECFILE}" \ + --define "skip_dist 1" \ + --define "make_redistributable ${make_redistributable}" \ + --define "version ${OS_RPM_VERSION}" --define "release ${OS_RPM_RELEASE}" \ + --define "commit ${OS_GIT_COMMIT}" \ + --define "os_git_vars ${OS_RPM_GIT_VARS}" \ + --define 'dist .el7' --define "_topdir ${rpm_tmp_dir}" + + output_directory="$( find "${rpm_tmp_dir}" -type d -path "*/BUILD/${OS_RPM_NAME}-${OS_RPM_VERSION}/_output/local" )" + if [[ -z "${output_directory}" ]]; then + os::log::fatal 'No _output artifact directory found in rpmbuild artifacts!' + fi + + # migrate the rpm artifacts to the output directory, must be clean or move will fail + make clean + mkdir -p "${OS_OUTPUT}" + + # mv exits prematurely with status 1 in the following scenario: running as root, + # attempting to move a [directory tree containing a] symlink to a destination on + # an NFS volume exported with root_squash set. This can occur when running this + # script on a Vagrant box. The error shown is "mv: failed to preserve ownership + # for $FILE: Operation not permitted". As a workaround, if + # ${tito_output_directory} and ${OS_OUTPUT} are on different devices, use cp and + # rm instead. + if [[ $(stat -c %d "${output_directory}") == $(stat -c %d "${OS_OUTPUT}") ]]; then + mv "${output_directory}"/* "${OS_OUTPUT}" + else + cp -R "${output_directory}"/* "${OS_OUTPUT}" + rm -rf "${output_directory}"/* + fi + + mkdir -p "${OS_OUTPUT_RPMPATH}" + if [[ -n "${OS_BUILD_SRPM-}" ]]; then + mv -f "${rpm_tmp_dir}"/SRPMS/*src.rpm "${OS_OUTPUT_RPMPATH}" + fi + mv -f "${rpm_tmp_dir}"/RPMS/*/*.rpm "${OS_OUTPUT_RPMPATH}" +fi + +mkdir -p "${OS_OUTPUT_RELEASEPATH}" +echo "${OS_GIT_COMMIT}" > "${OS_OUTPUT_RELEASEPATH}/.commit" + +repo_path="$( os::util::absolute_path "${OS_OUTPUT_RPMPATH}" )" +createrepo "${repo_path}" + +echo "[${OS_RPM_NAME}-local-release] +baseurl = file://${repo_path} +gpgcheck = 0 +name = Release from Local Source for ${OS_RPM_NAME} +enabled = 1 +" > "${repo_path}/local-release.repo" + +os::log::info "Repository file for \`yum\` or \`dnf\` placed at ${repo_path}/local-release.repo +Install it with: +$ mv '${repo_path}/local-release.repo' '/etc/yum.repos.d" diff --git a/hack/cherry-pick.sh b/hack/cherry-pick.sh new file mode 100755 index 000000000..07391c9f0 --- /dev/null +++ b/hack/cherry-pick.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# See HACKING.md for usage +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +repo="${UPSTREAM_REPO:-k8s.io/kubernetes}" +package="${UPSTREAM_PACKAGE:-pkg/api}" +UPSTREAM_REPO_LOCATION="${UPSTREAM_REPO_LOCATION:-../../../${repo}}" +pr="$1" + +if [[ "$#" -ne 1 ]]; then + echo "You must supply a pull request by number or a Git range in the upstream ${repo} project" 1>&2 + exit 1 +fi +os::build::require_clean_tree # Origin tree must be clean + +patch="${TMPDIR:-/tmp}/patch" +rm -rf "${patch}" +mkdir -p "${patch}" +patch="${patch}/cherry-pick" + +if [[ ! -d "${UPSTREAM_REPO_LOCATION}" ]]; then + echo "Expected ${UPSTREAM_REPO_LOCATION} to exist" 1>&2 + exit 1 +fi + +lastrev="${NO_REBASE-}" +if [[ -z "${NO_REBASE-}" ]]; then + lastrev="$(go run ${OS_ROOT}/tools/godepversion/godepversion.go ${OS_ROOT}/Godeps/Godeps.json ${repo}/${package})" +fi + +pushd "${UPSTREAM_REPO_LOCATION}" > /dev/null +os::build::require_clean_tree + +remote="${UPSTREAM_REMOTE:-origin}" +git fetch ${remote} + +selector="$(os::build::commit_range $pr ${remote}/master)" + +if [[ -z "${NO_REBASE-}" ]]; then + echo "++ Generating patch for ${selector} onto ${lastrev} ..." 2>&1 + if git rev-parse last_upstream_branch > /dev/null 2>&1; then + git branch -d last_upstream_branch + fi + git checkout -b last_upstream_branch "${lastrev}" + git diff -p --raw --binary "${selector}" > "${patch}" + if ! git apply -3 "${patch}"; then + git rerere # record pre state + echo 2>&1 + echo "++ Merge conflicts when generating patch, please resolve conflicts and then press ENTER to continue" 1>&2 + read + fi + git rerere # record post state + # stage any new files + git add . > /dev/null + # construct a new patch + git diff --cached -p --raw --binary --{src,dst}-prefix=a/vendor/${repo}/ > "${patch}" + # cleanup the current state + git reset HEAD --hard > /dev/null + git checkout master > /dev/null + git branch -D last_upstream_branch > /dev/null +else + echo "++ Generating patch for ${selector} without rebasing ..." 2>&1 + git diff -p --raw --binary --{src,dst}-prefix=a/vendor/${repo}/ "${selector}" > "${patch}" +fi + +popd > /dev/null + +echo "++ Applying patch ..." 2>&1 +echo 2>&1 +set +e +git apply --reject "${patch}" +if [[ $? -ne 0 ]]; then + echo "++ Not all patches applied, merge *.rej into your files or rerun with REBASE=1" + exit 1 +fi + +commit_message="UPSTREAM: $pr: Cherry-picked" +if [ "$repo" != "k8s.io/kubernetes" ]; then + commit_message="UPSTREAM: $repo: $pr: Cherry-picked" +fi + +set -o errexit +git add . +git commit -m "$commit_message" > /dev/null +git commit --amend +echo 2>&1 +echo "++ Done" 2>&1 diff --git a/hack/deps b/hack/deps new file mode 100755 index 000000000..d0c549308 --- /dev/null +++ b/hack/deps @@ -0,0 +1,29 @@ +#!/bin/bash + +set -o errexit +set -o nounset +set -o pipefail + +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +if [[ $# -eq 0 || ! -f "${OS_OUTPUT}/deps" ]]; then + echo "Generating dependency graph ..." 1>&2 + mkdir -p "${OS_OUTPUT}" + os::util::list_go_deps > "${OS_OUTPUT}/deps" +fi + +if [[ $# -eq 0 ]]; then + echo "Dependencies generated to ${OS_OUTPUT}/deps" + echo + echo "Install digraph with: go get -u golang.org/x/tools/cmd/digraph" + echo + echo "To see the list of all dependencies of a package: " + echo " hack/deps.sh forward ${OS_GO_PACKAGE}/cmd/openshift" + echo + echo "To see how a package was included into a binary (one particular way): " + echo " hack/deps.sh somepath ${OS_GO_PACKAGE}/cmd/openshift FULL_PACKAGE_NAME" + exit 0 +fi + +os::util::ensure::system_binary_exists 'digraph' +cat "${OS_OUTPUT}/deps" | digraph $@ diff --git a/hack/env b/hack/env new file mode 100755 index 000000000..d47e532cc --- /dev/null +++ b/hack/env @@ -0,0 +1,42 @@ +#!/bin/bash + +# This starts a Docker container using the release image (openshift/origin-release:golang-1.6) +# and syncs the local directory into that image. The default mode performs a 'git archive' of +# the current HEAD, so you get a reproducible environment. You can also set +# OS_BUILD_ENV_REUSE_VOLUME to a docker volume name to rsync (or docker cp) the contents of +# the current directory into the image. +# +# Examples: +# # sync local dir into the volume and print the Docker create command +# $ hack/env +# +# # builds the current HEAD in the container +# $ hack/env make +# +# # builds the current HEAD and copy _output/releases back locally afterwards +# $ OS_BUILD_ENV_PRESERVE=_output/releases hack/env make release +# +# # run all update tasks and copy the api, pkg, and docs directories back out +# $ OS_BUILD_ENV_PRESERVE=api:docs:pkg hack/env make update +# +# # rsync the contents of the current directory into the 'local' docker volume +# # and iteratively build +# $ export OS_BUILD_ENV_REUSE_VOLUME=local +# $ export OS_BUILD_ENV_DOCKER_ARGS='-e OS_VERSION_FILE= ' +# $ hack/env make # slow +# $ hack/env make # fast! +# +# # force a new volume to get created from the current source +# $ OS_BUILD_ENV_VOLUME_FORCE_NEW=TRUE hack/env +# + +# NOTE: only committed code is built. +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +relative_bin_path="$( os::util::repository_relative_path "${OS_OUTPUT_BINPATH}" )" +relative_release_path="$( os::util::repository_relative_path "${OS_OUTPUT_RELEASEPATH}" )" +relative_script_path="$( os::util::repository_relative_path "${OS_OUTPUT_SCRIPTPATH}" )" +default_preserve_paths="${relative_bin_path}:${relative_release_path}:${relative_script_path}" +export OS_BUILD_ENV_PRESERVE="${OS_BUILD_ENV_PRESERVE:-"${default_preserve_paths}"}" + +os::build::environment::run "$@" \ No newline at end of file diff --git a/hack/import-restrictions.json b/hack/import-restrictions.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/hack/import-restrictions.json @@ -0,0 +1,2 @@ +[ +] diff --git a/hack/lib/build/archive.sh b/hack/lib/build/archive.sh new file mode 100644 index 000000000..deffb625b --- /dev/null +++ b/hack/lib/build/archive.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +# This library holds utility functions for archiving +# built binaries and releases. + +function os::build::archive::name() { + echo "${OS_RELEASE_ARCHIVE}-${OS_GIT_VERSION}-$1" | tr '+' '-' +} +readonly -f os::build::archive::name + +function os::build::archive::zip() { + local default_name + default_name="$( os::build::archive::name "${platform}" ).zip" + local archive_name="${archive_name:-$default_name}" + echo "++ Creating ${archive_name}" + for file in "$@"; do + pushd "${release_binpath}" &> /dev/null + sha256sum "${file}" + popd &>/dev/null + zip "${OS_OUTPUT_RELEASEPATH}/${archive_name}" -qj "${release_binpath}/${file}" + done +} +readonly -f os::build::archive::zip + +function os::build::archive::tar() { + local base_name + base_name="$( os::build::archive::name "${platform}" )" + local default_name="${base_name}.tar.gz" + local archive_name="${archive_name:-$default_name}" + echo "++ Creating ${archive_name}" + pushd "${release_binpath}" &> /dev/null + find . -type f -exec sha256sum {} \; + if [[ -n "$(which bsdtar)" ]]; then + bsdtar -czf "${OS_OUTPUT_RELEASEPATH}/${archive_name}" -s ",^\.,${base_name}," $@ + else + tar -czf --xattrs-exclude='LIBARCHIVE.xattr.security.selinux' "${OS_OUTPUT_RELEASEPATH}/${archive_name}" --transform="s,^\.,${base_name}," $@ + fi + popd &>/dev/null +} +readonly -f os::build::archive::tar + +# Checks if the filesystem on a partition that the provided path points to is +# supporting hard links. +# +# Input: +# $1 - the path where the hardlinks support test will be done. +# Returns: +# 0 - if hardlinks are supported +# non-zero - if hardlinks aren't supported +function os::build::archive::internal::is_hardlink_supported() { + local path="$1" + # Determine if FS supports hard links + local temp_file=$(TMPDIR="${path}" mktemp) + ln "${temp_file}" "${temp_file}.link" &> /dev/null && unlink "${temp_file}.link" || local supported=$? + rm -f "${temp_file}" + return ${supported:-0} +} +readonly -f os::build::archive::internal::is_hardlink_supported + +# Extract a tar.gz compressed archive in a given directory. If the +# archive contains hardlinks and the underlying filesystem is not +# supporting hardlinks then the a hard dereference will be done. +# +# Input: +# $1 - path to archive file +# $2 - directory where the archive will be extracted +function os::build::archive::extract_tar() { + local archive_file="$1" + local change_dir="$2" + + if [[ -z "${archive_file}" ]]; then + return 0 + fi + + local tar_flags="--strip-components=1" + + # Unpack archive + echo "++ Extracting $(basename ${archive_file})" + if [[ "${archive_file}" == *.zip ]]; then + unzip -o "${archive_file}" -d "${change_dir}" + return 0 + fi + if os::build::archive::internal::is_hardlink_supported "${change_dir}" ; then + # Ensure that tar won't try to set an owner when extracting to an + # nfs mount. Setting ownership on an nfs mount is likely to fail + # even for root. + local mount_type=$(df -P -T "${change_dir}" | tail -n +2 | awk '{print $2}') + if [[ "${mount_type}" = "nfs" ]]; then + tar_flags="${tar_flags} --no-same-owner" + fi + tar mxzf "${archive_file}" ${tar_flags} -C "${change_dir}" + else + local temp_dir=$(TMPDIR=/dev/shm/ mktemp -d) + tar mxzf "${archive_file}" ${tar_flags} -C "${temp_dir}" + pushd "${temp_dir}" &> /dev/null + tar cO --hard-dereference * | tar xf - -C "${change_dir}" + popd &>/dev/null + rm -rf "${temp_dir}" + fi +} +readonly -f os::build::archive::extract_tar diff --git a/hack/lib/build/binaries.sh b/hack/lib/build/binaries.sh new file mode 100644 index 000000000..9f9a6d717 --- /dev/null +++ b/hack/lib/build/binaries.sh @@ -0,0 +1,416 @@ +#!/bin/bash + +# This library holds utility functions for building +# and placing Golang binaries for multiple arches. + +# os::build::binaries_from_targets take a list of build targets and return the +# full go package to be built +function os::build::binaries_from_targets() { + local target + for target; do + if [[ -z "${target}" ]]; then + continue + fi + echo "${OS_GO_PACKAGE}/${target}" + done +} +readonly -f os::build::binaries_from_targets + +# Asks golang what it thinks the host platform is. The go tool chain does some +# slightly different things when the target platform matches the host platform. +function os::build::host_platform() { + echo "$(go env GOHOSTOS)/$(go env GOHOSTARCH)" +} +readonly -f os::build::host_platform + +# Create a user friendly version of host_platform for end users +function os::build::host_platform_friendly() { + local platform=${1:-} + if [[ -z "${platform}" ]]; then + platform=$(os::build::host_platform) + fi + if [[ $platform == "windows/amd64" ]]; then + echo "windows" + elif [[ $platform == "darwin/amd64" ]]; then + echo "mac" + elif [[ $platform == "linux/386" ]]; then + echo "linux-32bit" + elif [[ $platform == "linux/amd64" ]]; then + echo "linux-64bit" + elif [[ $platform == "linux/ppc64le" ]]; then + echo "linux-powerpc64" + elif [[ $platform == "linux/arm64" ]]; then + echo "linux-arm64" + elif [[ $platform == "linux/s390x" ]]; then + echo "linux-s390" + else + echo "$(go env GOHOSTOS)-$(go env GOHOSTARCH)" + fi +} +readonly -f os::build::host_platform_friendly + +# This converts from platform/arch to PLATFORM_ARCH, host platform will be +# considered if no parameter passed +function os::build::platform_arch() { + local platform=${1:-} + if [[ -z "${platform}" ]]; then + platform=$(os::build::host_platform) + fi + + echo $(echo ${platform} | tr '[:lower:]/' '[:upper:]_') +} +readonly -f os::build::platform_arch + +# os::build::setup_env will check that the `go` commands is available in +# ${PATH}. If not running on Travis, it will also check that the Go version is +# good enough for the Kubernetes build. +# +# Output Vars: +# export GOPATH - A modified GOPATH to our created tree along with extra +# stuff. +# export GOBIN - This is actively unset if already set as we want binaries +# placed in a predictable place. +function os::build::setup_env() { + os::util::ensure::system_binary_exists 'go' + + if [[ -z "$(which sha256sum)" ]]; then + sha256sum() { + return 0 + } + fi + + # Travis continuous build uses a head go release that doesn't report + # a version number, so we skip this check on Travis. It's unnecessary + # there anyway. + if [[ "${TRAVIS:-}" != "true" ]]; then + local go_version + go_version=($(go version)) + local expected_order=$(printf "%s\n%s\n" "${OS_REQUIRED_GO_VERSION}" "${go_version[2]}") + local actual_order=$(echo "${expected_order}" | sort --version-sort) + if [[ "${actual_order}" != "${expected_order}" ]]; then + os::log::fatal "Detected Go version: ${go_version[*]}. +Builds require Go version ${OS_REQUIRED_GO_VERSION} or greater." + fi + fi + # For any tools that expect this to be set (it is default in golang 1.6), + # force vendor experiment. + export GO15VENDOREXPERIMENT=1 + + unset GOBIN + + # default to OS_OUTPUT_GOPATH if no GOPATH set + if [[ -z "${GOPATH:-}" ]]; then + export OS_OUTPUT_GOPATH=1 + fi + + # use the regular gopath for building + if [[ -z "${OS_OUTPUT_GOPATH:-}" ]]; then + export OS_TARGET_BIN=${GOPATH}/bin + return + fi + + # create a local GOPATH in _output + GOPATH="${OS_OUTPUT}/go" + OS_TARGET_BIN=${GOPATH}/bin + local go_pkg_dir="${GOPATH}/src/${OS_GO_PACKAGE}" + local go_pkg_basedir=$(dirname "${go_pkg_dir}") + + mkdir -p "${go_pkg_basedir}" + rm -f "${go_pkg_dir}" + + # TODO: This symlink should be relative. + ln -s "${OS_ROOT}" "${go_pkg_dir}" + + # lots of tools "just don't work" unless we're in the GOPATH + cd "${go_pkg_dir}" + + # Append OS_EXTRA_GOPATH to the GOPATH if it is defined. + if [[ -n ${OS_EXTRA_GOPATH:-} ]]; then + GOPATH="${GOPATH}:${OS_EXTRA_GOPATH}" + # TODO: needs to handle multiple directories + OS_TARGET_BIN=${OS_EXTRA_GOPATH}/bin + fi + export GOPATH + export OS_TARGET_BIN +} +readonly -f os::build::setup_env + +# Build static binary targets. +# +# Input: +# $@ - targets and go flags. If no targets are set then all binaries targets +# are built. +# OS_BUILD_PLATFORMS - Incoming variable of targets to build for. If unset +# then just the host architecture is built. +function os::build::build_static_binaries() { + CGO_ENABLED=0 os::build::build_binaries -installsuffix=cgo "$@" +} +readonly -f os::build::build_static_binaries + +# Build binary targets specified +# +# Input: +# $@ - targets and go flags. If no targets are set then all binaries targets +# are built. +# OS_BUILD_PLATFORMS - Incoming variable of targets to build for. If unset +# then just the host architecture is built. +function os::build::build_binaries() { + if [[ $# -eq 0 ]]; then + return + fi + local -a binaries=( "$@" ) + # Create a sub-shell so that we don't pollute the outer environment + ( os::build::internal::build_binaries "${binaries[@]+"${binaries[@]}"}" ) +} + +# Build binary targets specified. Should always be run in a sub-shell so we don't leak GOBIN +# +# Input: +# $@ - targets and go flags. If no targets are set then all binaries targets +# are built. +# OS_BUILD_PLATFORMS - Incoming variable of targets to build for. If unset +# then just the host architecture is built. +os::build::internal::build_binaries() { + # Check for `go` binary and set ${GOPATH}. + os::build::setup_env + + # Fetch the version. + local version_ldflags + version_ldflags=$(os::build::ldflags) + + local goflags + # Use eval to preserve embedded quoted strings. + eval "goflags=(${OS_GOFLAGS:-})" + + local arg + for arg; do + if [[ "${arg}" == -* ]]; then + # Assume arguments starting with a dash are flags to pass to go. + goflags+=("${arg}") + fi + done + + os::build::export_targets "$@" + + if [[ ! "${targets[@]:+${targets[@]}}" || ! "${binaries[@]:+${binaries[@]}}" ]]; then + return 0 + fi + + local -a nonstatics=() + local -a tests=() + for binary in "${binaries[@]-}"; do + if [[ "${binary}" =~ ".test"$ ]]; then + tests+=($binary) + else + nonstatics+=($binary) + fi + done + + local pkgdir="${OS_OUTPUT_PKGDIR}" + if [[ "${CGO_ENABLED-}" == "0" ]]; then + pkgdir+="/static" + fi + + local host_platform=$(os::build::host_platform) + local platform + for platform in "${platforms[@]+"${platforms[@]}"}"; do + echo "++ Building go targets for ${platform}:" "${targets[@]}" + mkdir -p "${OS_OUTPUT_BINPATH}/${platform}" + + # output directly to the desired location + if [[ $platform == $host_platform ]]; then + export GOBIN="${OS_OUTPUT_BINPATH}/${platform}" + else + unset GOBIN + fi + + local platform_gotags_envvar=OS_GOFLAGS_TAGS_$(os::build::platform_arch ${platform}) + local platform_gotags_test_envvar=OS_GOFLAGS_TAGS_TEST_$(os::build::platform_arch ${platform}) + + # work around https://github.com/golang/go/issues/11887 + local local_ldflags="${version_ldflags}" + if [[ "${platform}" == "darwin/amd64" ]]; then + local_ldflags+=" -s" + fi + + #Add Windows File Properties/Version Info and Icon Resource for oc.exe + if [[ "$platform" == "windows/amd64" ]]; then + os::build::generate_windows_versioninfo + fi + + if [[ ${#nonstatics[@]} -gt 0 ]]; then + GOOS=${platform%/*} GOARCH=${platform##*/} go install \ + -pkgdir "${pkgdir}/${platform}" \ + -tags "${OS_GOFLAGS_TAGS-} ${!platform_gotags_envvar:-}" \ + -ldflags="${local_ldflags}" \ + "${goflags[@]:+${goflags[@]}}" \ + "${nonstatics[@]}" + + # GOBIN is not supported on cross-compile in Go 1.5+ - move to the correct target + if [[ $platform != $host_platform ]]; then + local platform_src="/${platform//\//_}" + mv "${OS_TARGET_BIN}/${platform_src}/"* "${OS_OUTPUT_BINPATH}/${platform}/" + fi + fi + + if [[ "$platform" == "windows/amd64" ]]; then + os::build::clean_windows_versioninfo + fi + + for test in "${tests[@]:+${tests[@]}}"; do + local outfile="${OS_OUTPUT_BINPATH}/${platform}/$(basename ${test})" + # disabling cgo allows use of delve + CGO_ENABLED="${OS_TEST_CGO_ENABLED:-}" GOOS=${platform%/*} GOARCH=${platform##*/} go test \ + -pkgdir "${pkgdir}/${platform}" \ + -tags "${OS_GOFLAGS_TAGS-} ${!platform_gotags_test_envvar:-}" \ + -ldflags "${local_ldflags}" \ + -i -c -o "${outfile}" \ + "${goflags[@]:+${goflags[@]}}" \ + "$(dirname ${test})" + done + done +} +readonly -f os::build::build_binaries + + # Generates the set of target packages, binaries, and platforms to build for. +# Accepts binaries via $@, and platforms via OS_BUILD_PLATFORMS, or defaults to +# the current platform. +function os::build::export_targets() { + platforms=("${OS_BUILD_PLATFORMS[@]:+${OS_BUILD_PLATFORMS[@]}}") + + targets=() + local arg + for arg; do + if [[ "${arg}" != -* ]]; then + targets+=("${arg}") + fi + done + + binaries=($(os::build::binaries_from_targets "${targets[@]-}")) +} +readonly -f os::build::export_targets + +# This will take $@ from $GOPATH/bin and copy them to the appropriate +# place in ${OS_OUTPUT_BINDIR} +# +# If OS_RELEASE_ARCHIVE is set, tar archives prefixed with OS_RELEASE_ARCHIVE for +# each of OS_BUILD_PLATFORMS are created. +# +# Ideally this wouldn't be necessary and we could just set GOBIN to +# OS_OUTPUT_BINDIR but that won't work in the face of cross compilation. 'go +# install' will place binaries that match the host platform directly in $GOBIN +# while placing cross compiled binaries into `platform_arch` subdirs. This +# complicates pretty much everything else we do around packaging and such. +function os::build::place_bins() { + ( + local host_platform + host_platform=$(os::build::host_platform) + + if [[ "${OS_RELEASE_ARCHIVE-}" != "" ]]; then + os::build::version::get_vars + mkdir -p "${OS_OUTPUT_RELEASEPATH}" + fi + + os::build::export_targets "$@" + for platform in "${platforms[@]+"${platforms[@]}"}"; do + # The substitution on platform_src below will replace all slashes with + # underscores. It'll transform darwin/amd64 -> darwin_amd64. + local platform_src="/${platform//\//_}" + + # Skip this directory if the platform has no binaries. + if [[ ! -d "${OS_OUTPUT_BINPATH}/${platform}" ]]; then + continue + fi + + # Create an array of binaries to release. Append .exe variants if the platform is windows. + local -a binaries=() + for binary in "${targets[@]}"; do + binary=$(basename $binary) + if [[ $platform == "windows/amd64" ]]; then + binaries+=("${binary}.exe") + else + binaries+=("${binary}") + fi + done + done + ) +} +readonly -f os::build::place_bins + +# os::build::ldflag() abstracts ldflag inconsistency across Go versions. +# TODO: remove +function os::build::ldflag() { + local key=${1} + local val=${2} + + echo "-X ${key}=${val}" +} +readonly -f os::build::ldflag + +# os::build::require_clean_tree exits if the current Git tree is not clean. +function os::build::require_clean_tree() { + if ! git diff-index --quiet HEAD -- || test $(git ls-files --exclude-standard --others | wc -l) != 0; then + echo "You can't have any staged or dirty files in $(pwd) for this command." + echo "Either commit them or unstage them to continue." + exit 1 + fi +} +readonly -f os::build::require_clean_tree + +# os::build::commit_range takes one or two arguments - if the first argument is an +# integer, it is assumed to be a pull request and the local origin/pr/# branch is +# used to determine the common range with the second argument. If the first argument +# is not an integer, it is assumed to be a Git commit range and output directly. +function os::build::commit_range() { + local remote + remote="${UPSTREAM_REMOTE:-origin}" + if [[ "$1" =~ ^-?[0-9]+$ ]]; then + local target + target="$(git rev-parse ${remote}/pr/$1)" + if [[ $? -ne 0 ]]; then + echo "Branch does not exist, or you have not configured ${remote}/pr/* style branches from GitHub" 1>&2 + exit 1 + fi + + local base + base="$(git merge-base ${target} $2)" + if [[ $? -ne 0 ]]; then + echo "Branch has no common commits with $2" 1>&2 + exit 1 + fi + if [[ "${base}" == "${target}" ]]; then + + # DO NOT TRUST THIS CODE + merged="$(git rev-list --reverse ${target}..$2 --ancestry-path | head -1)" + if [[ -z "${merged}" ]]; then + echo "Unable to find the commit that merged ${remote}/pr/$1" 1>&2 + exit 1 + fi + #if [[ $? -ne 0 ]]; then + # echo "Unable to find the merge commit for $1: ${merged}" 1>&2 + # exit 1 + #fi + echo "++ pr/$1 appears to have merged at ${merged}" 1>&2 + leftparent="$(git rev-list --parents -n 1 ${merged} | cut -f2 -d ' ')" + if [[ $? -ne 0 ]]; then + echo "Unable to find the left-parent for the merge of for $1" 1>&2 + exit 1 + fi + base="$(git merge-base ${target} ${leftparent})" + if [[ $? -ne 0 ]]; then + echo "Unable to find the common commit between ${leftparent} and $1" 1>&2 + exit 1 + fi + echo "${base}..${target}" + exit 0 + #echo "Branch has already been merged to upstream master, use explicit range instead" 1>&2 + #exit 1 + fi + + echo "${base}...${target}" + exit 0 + fi + + echo "$1" +} +readonly -f os::build::commit_range diff --git a/hack/lib/build/environment.sh b/hack/lib/build/environment.sh new file mode 100644 index 000000000..70e2e09a3 --- /dev/null +++ b/hack/lib/build/environment.sh @@ -0,0 +1,281 @@ +#!/bin/bash + +# This script holds library functions for setting up the Docker container build environment + +# os::build::environment::create creates a docker container with the default variables. +# arguments are passed directly to the container, OS_BUILD_ENV_GOLANG, OS_BUILD_ENV_IMAGE, +# and OS_RELEASE_DOCKER_ARGS can be used to customize the container. The docker socket +# is mounted by default and the output of the command is the container id. +function os::build::environment::create() { + set -o errexit + local release_image="${OS_BUILD_ENV_IMAGE}" + local additional_context="${OS_BUILD_ENV_DOCKER_ARGS:-}" + + local workingdir + workingdir=$( os::build::environment::release::workingdir ) + additional_context+=" -w ${workingdir}" + + if [[ "${OS_BUILD_ENV_USE_DOCKER:-y}" == "y" ]]; then + additional_context+=" --privileged -v /var/run/docker.sock:/var/run/docker.sock" + + if [[ "${OS_BUILD_ENV_LOCAL_DOCKER:-n}" == "y" ]]; then + # if OS_BUILD_ENV_LOCAL_DOCKER==y, add the local OS_ROOT as the bind mount to the working dir + # and set the running user to the current user + additional_context+=" -v ${OS_ROOT}:${workingdir} -u $(id -u)" + elif [[ -n "${OS_BUILD_ENV_VOLUME:-}" ]]; then + if docker volume inspect "${OS_BUILD_ENV_VOLUME}" >/dev/null 2>&1; then + os::log::debug "Re-using volume ${OS_BUILD_ENV_VOLUME}" + else + # if OS_BUILD_ENV_VOLUME is set and no volume already exists, create a docker volume to + # store the working output so successive iterations can reuse shared code. + os::log::debug "Creating volume ${OS_BUILD_ENV_VOLUME}" + docker volume create --name "${OS_BUILD_ENV_VOLUME}" > /dev/null + fi + + if [[ -n "${OS_BUILD_ENV_TMP_VOLUME:-}" ]]; then + if docker volume inspect "${OS_BUILD_ENV_TMP_VOLUME}" >/dev/null 2>&1; then + os::log::debug "Re-using volume ${OS_BUILD_ENV_TMP_VOLUME}" + else + # if OS_BUILD_ENV_VOLUME is set and no volume already exists, create a docker volume to + # store the working output so successive iterations can reuse shared code. + os::log::debug "Creating volume ${OS_BUILD_ENV_TMP_VOLUME}" + docker volume create --name "${OS_BUILD_ENV_TMP_VOLUME}" >/dev/null + fi + additional_context+=" -v ${OS_BUILD_ENV_TMP_VOLUME}:/tmp" + fi + additional_context+=" -v ${OS_BUILD_ENV_VOLUME}:${workingdir}" + fi + fi + + if [[ -n "${OS_BUILD_ENV_FROM_ARCHIVE-}" ]]; then + additional_context+=" -e OS_VERSION_FILE=/tmp/os-version-defs" + else + additional_context+=" -e OS_VERSION_FILE=" + fi + + declare -a cmd=( ) + declare -a env=( ) + local prefix=1 + for arg in "${@:1}"; do + if [[ "${arg}" != *"="* ]]; then + prefix=0 + fi + if [[ "${prefix}" -eq 1 ]]; then + env+=( "-e" "${arg}" ) + else + cmd+=( "${arg}" ) + fi + done + if [[ -t 0 ]]; then + if [[ "${#cmd[@]}" -eq 0 ]]; then + cmd=( "/bin/sh" ) + fi + if [[ "${cmd[0]}" == "/bin/sh" || "${cmd[0]}" == "/bin/bash" ]]; then + additional_context+=" -it" + else + # container exit races with log collection so we + # need to sleep at the end but preserve the exit + # code of whatever the user asked for us to run + cmd=( '/bin/bash' '-c' "${cmd[*]}; return_code=\$?; sleep 1; exit \${return_code}" ) + fi + fi + + # Create a new container from the release environment + os::log::debug "Creating container: \`docker create ${additional_context} ${env[@]+"${env[@]}"} ${release_image} ${cmd[@]+"${cmd[@]}"}" + docker create ${additional_context} "${env[@]+"${env[@]}"}" "${release_image}" "${cmd[@]+"${cmd[@]}"}" +} +readonly -f os::build::environment::create + +# os::build::environment::release::workingdir calculates the working directory for the current +# release image. +function os::build::environment::release::workingdir() { + if [[ -n "${OS_BUILD_ENV_WORKINGDIR-}" ]]; then + echo "${OS_BUILD_ENV_WORKINGDIR}" + return 0 + fi + set -o errexit + # get working directory + local container + container="$(docker create "${release_image}")" + local workingdir + workingdir="$(docker inspect -f '{{ index . "Config" "WorkingDir" }}' "${container}")" + docker rm "${container}" > /dev/null + echo "${workingdir}" +} +readonly -f os::build::environment::release::workingdir + +# os::build::environment::cleanup stops and removes the container named in the argument +# (unless OS_BUILD_ENV_LEAVE_CONTAINER is set, in which case it will only stop the container). +function os::build::environment::cleanup() { + local container=$1 + local volume=$2 + local tmp_volume=$3 + os::log::debug "Stopping container ${container}" + docker stop --time=0 "${container}" > /dev/null || true + if [[ -z "${OS_BUILD_ENV_LEAVE_CONTAINER:-}" ]]; then + os::log::debug "Removing container ${container}" + docker rm "${container}" > /dev/null + + if [[ -z "${OS_BUILD_ENV_REUSE_TMP_VOLUME:-}" ]]; then + os::log::debug "Removing tmp build volume" + os::build::environment::remove_volume "${tmp_volume}" + fi + if [[ -n "${OS_BUILD_ENV_CLEAN_BUILD_VOLUME:-}" ]]; then + os::log::debug "Removing build volume" + os::build::environment::remove_volume "${volume}" + fi + fi +} +readonly -f os::build::environment::cleanup + +# os::build::environment::start starts the container provided as the first argument +# using whatever content exists in the container already. +function os::build::environment::start() { + local container=$1 + + os::log::debug "Starting container ${container}" + if [[ "$( docker inspect --type container -f '{{ .Config.OpenStdin }}' "${container}" )" == "true" ]]; then + docker start -ia "${container}" + else + docker start "${container}" > /dev/null + os::log::debug "Following container logs" + docker logs -f "${container}" + fi + + local exitcode + exitcode="$( docker inspect --type container -f '{{ .State.ExitCode }}' "${container}" )" + + os::log::debug "Container exited with ${exitcode}" + + # extract content from the image + if [[ -n "${OS_BUILD_ENV_PRESERVE-}" ]]; then + local workingdir + workingdir="$(docker inspect -f '{{ index . "Config" "WorkingDir" }}' "${container}")" + local oldIFS="${IFS}" + IFS=: + for path in ${OS_BUILD_ENV_PRESERVE}; do + local parent=. + if [[ "${path}" != "." ]]; then + parent="$( dirname "${path}" )" + mkdir -p "${parent}" + fi + os::log::debug "Copying from ${container}:${workingdir}/${path} to ${parent}" + if ! output="$( docker cp "${container}:${workingdir}/${path}" "${parent}" 2>&1 )"; then + os::log::warning "Copying ${path} from the container failed!" + os::log::warning "${output}" + fi + done + IFS="${oldIFS}" + fi + return "${exitcode}" +} +readonly -f os::build::environment::start + +# os::build::environment::withsource starts the container provided as the first argument +# after copying in the contents of the current Git repository at HEAD (or, if specified, +# the ref specified in the second argument). +function os::build::environment::withsource() { + local container=$1 + local commit=${2:-HEAD} + + if [[ -n "${OS_BUILD_ENV_LOCAL_DOCKER-}" ]]; then + os::build::environment::start "${container}" + return + fi + + local workingdir + workingdir="$(docker inspect -f '{{ index . "Config" "WorkingDir" }}' "${container}")" + + if [[ -n "${OS_BUILD_ENV_FROM_ARCHIVE-}" ]]; then + # Generate version definitions. Tree state is clean because we are pulling from git directly. + OS_GIT_TREE_STATE=clean os::build::version::get_vars + os::build::version::save_vars > "/tmp/os-version-defs" + + os::log::debug "Generating source code archive" + tar -cf - -C /tmp/ os-version-defs | docker cp - "${container}:/tmp" + git archive --format=tar "${commit}" | docker cp - "${container}:${workingdir}" + os::build::environment::start "${container}" + return + fi + + local excluded=() + local oldIFS="${IFS}" + IFS=: + for exclude in ${OS_BUILD_ENV_EXCLUDE:-_output}; do + excluded+=("--exclude=${exclude}") + done + IFS="${oldIFS}" + if which rsync &>/dev/null && [[ -n "${OS_BUILD_ENV_VOLUME-}" ]]; then + os::log::debug "Syncing source using \`rsync\`" + if ! rsync -a --blocking-io "${excluded[@]}" --delete --omit-dir-times --numeric-ids -e "docker run --rm -i -v \"${OS_BUILD_ENV_VOLUME}:${workingdir}\" --entrypoint=/bin/bash \"${OS_BUILD_ENV_IMAGE}\" -c '\$@'" . remote:"${workingdir}"; then + os::log::debug "Falling back to \`tar\` and \`docker cp\` as \`rsync\` is not in container" + tar -cf - "${excluded[@]}" . | docker cp - "${container}:${workingdir}" + fi + else + os::log::debug "Syncing source using \`tar\` and \`docker cp\`" + tar -cf - "${excluded[@]}" . | docker cp - "${container}:${workingdir}" + fi + + os::build::environment::start "${container}" +} +readonly -f os::build::environment::withsource + +function os::build::environment::volume_name() { + local prefix=$1 + local commit=$2 + local volume=$3 + + if [[ -z "${volume}" ]]; then + volume="${prefix}-$( git rev-parse "${commit}" )" + fi + + echo "${volume}" | tr '[:upper:]' '[:lower:]' +} +readonly -f os::build::environment::volume_name + +function os::build::environment::remove_volume() { + local volume=$1 + + if docker volume inspect "${volume}" >/dev/null 2>&1; then + os::log::debug "Removing volume ${volume}" + docker volume rm "${volume}" >/dev/null + fi +} +readonly -f os::build::environment::remove_volume + +# os::build::environment::run launches the container with the provided arguments and +# the current commit (defaults to HEAD). The container is automatically cleaned up. +function os::build::environment::run() { + local commit="${OS_GIT_COMMIT:-HEAD}" + local volume + local tmp_volume + + volume="$( os::build::environment::volume_name "origin-build" "${commit}" "${OS_BUILD_ENV_REUSE_VOLUME:-}" )" + tmp_volume="$( os::build::environment::volume_name "origin-build-tmp" "${commit}" "${OS_BUILD_ENV_REUSE_TMP_VOLUME:-}" )" + + export OS_BUILD_ENV_VOLUME="${volume}" + export OS_BUILD_ENV_TMP_VOLUME="${tmp_volume}" + + if [[ -n "${OS_BUILD_ENV_VOLUME_FORCE_NEW:-}" ]]; then + os::build::environment::remove_volume "${volume}" + os::build::environment::remove_volume "${tmp_volume}" + fi + + if [[ -n "${OS_BUILD_ENV_PULL_IMAGE:-}" ]]; then + os::log::info "Pulling the ${OS_BUILD_ENV_IMAGE} image to update it..." + docker pull "${OS_BUILD_ENV_IMAGE}" + fi + + os::log::debug "Using commit ${commit}" + os::log::debug "Using volume ${volume}" + os::log::debug "Using tmp volume ${tmp_volume}" + + local container + container="$( os::build::environment::create "$@" )" + trap "os::build::environment::cleanup ${container} ${volume} ${tmp_volume}" EXIT + + os::log::debug "Using container ${container}" + + os::build::environment::withsource "${container}" "${commit}" +} +readonly -f os::build::environment::run diff --git a/hack/lib/build/images.sh b/hack/lib/build/images.sh new file mode 100644 index 000000000..2865527f1 --- /dev/null +++ b/hack/lib/build/images.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +# This library holds utility functions for building container images. + +# os::build::image builds an image from a directory, to a tag or tags The default +# behavior is to use the imagebuilder binary if it is available on the path with +# fallback to docker build if it is not available. +# +# Globals: +# - OS_BUILD_IMAGE_ARGS +# - OS_BUILD_IMAGE_NUM_RETRIES +# Arguments: +# - 1: the directory in which to build +# - 2: the tag to apply to the image +# Returns: +# None +function os::build::image() { + local tag=$1 + local directory=$2 + local extra_tag + + if [[ ! "${tag}" == *":"* ]]; then + # if no tag was specified in the image name, + # tag with :latest and the release commit, if + # available, falling back to the last commit + # if no release commit is recorded + local release_commit + release_commit="${OS_RELEASE_COMMIT-}" + if [[ -z "${release_commit}" && -f "${OS_OUTPUT_RELEASEPATH}/.commit" ]]; then + release_commit="$( cat "${OS_OUTPUT_RELEASEPATH}/.commit" )" + fi + if [[ -z "${release_commit}" ]]; then + release_commit="$( git log -1 --pretty=%h )" + fi + extra_tag="${tag}:${release_commit}" + + tag="${tag}:latest" + fi + + local result=1 + local image_build_log + image_build_log="$( mktemp "${BASETMPDIR}/imagelogs.XXXXX" )" + for (( i = 0; i < "${OS_BUILD_IMAGE_NUM_RETRIES:-2}"; i++ )); do + if [[ "${i}" -gt 0 ]]; then + os::log::internal::prefix_lines "[${tag%:*}]" "$( cat "${image_build_log}" )" + os::log::warning "Retrying image build for ${tag}, attempt ${i}..." + fi + + if os::build::image::internal::generic "${tag}" "${directory}" "${extra_tag:-}" >"${image_build_log}" 2>&1; then + result=0 + break + fi + done + + os::log::internal::prefix_lines "[${tag%:*}]" "$( cat "${image_build_log}" )" + return "${result}" +} +readonly -f os::build::image + +# os::build::image::internal::generic builds a container image using either imagebuilder +# or docker, defaulting to imagebuilder if present +# +# Globals: +# - OS_BUILD_IMAGE_ARGS +# Arguments: +# - 1: the directory in which to build +# - 2: the tag to apply to the image +# - 3: optionally, extra tags to add +# Returns: +# None +function os::build::image::internal::generic() { + local directory=$2 + + local result=1 + if os::util::find::system_binary 'imagebuilder' >/dev/null; then + if os::build::image::internal::imagebuilder "$@"; then + result=0 + fi + else + os::log::warning "Unable to locate 'imagebuilder' on PATH, falling back to Docker build" + if os::build::image::internal::docker "$@"; then + result=0 + fi + fi + + # ensure the temporary contents are cleaned up + git clean -fdx "${directory}" + return "${result}" +} +readonly -f os::build::image::internal::generic + +# os::build::image::internal::imagebuilder builds a container image using imagebuilder +# +# Globals: +# - OS_BUILD_IMAGE_ARGS +# Arguments: +# - 1: the directory in which to build +# - 2: the tag to apply to the image +# - 3: optionally, extra tags to add +# Returns: +# None +function os::build::image::internal::imagebuilder() { + local tag=$1 + local directory=$2 + local extra_tag="${3-}" + local options=() + + if [[ -n "${OS_BUILD_IMAGE_ARGS:-}" ]]; then + options=( ${OS_BUILD_IMAGE_ARGS} ) + fi + + if [[ -n "${extra_tag}" ]]; then + options+=( -t "${extra_tag}" ) + fi + + imagebuilder "${options[@]:-}" -t "${tag}" "${directory}" +} +readonly -f os::build::image::internal::imagebuilder + +# os::build::image::internal::docker builds a container image using docker +# +# Globals: +# - OS_BUILD_IMAGE_ARGS +# Arguments: +# - 1: the directory in which to build +# - 2: the tag to apply to the image +# - 3: optionally, extra tags to add +# Returns: +# None +function os::build::image::internal::docker() { + local tag=$1 + local directory=$2 + local extra_tag="${3-}" + local options=() + + if ! docker build ${OS_BUILD_IMAGE_ARGS:-} -t "${tag}" "${directory}"; then + return 1 + fi + + if [[ -n "${extra_tag}" ]]; then + docker tag "${tag}" "${extra_tag}" + fi +} +readonly -f os::build::image::internal::docker diff --git a/hack/lib/build/release.sh b/hack/lib/build/release.sh new file mode 100644 index 000000000..974c539b9 --- /dev/null +++ b/hack/lib/build/release.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# This library holds utility functions for building releases. + +# os::build::release::check_for_rpms checks that an RPM release has been built +function os::build::release::check_for_rpms() { + if [[ ! -d "${OS_OUTPUT_RPMPATH}" || ! -d "${OS_OUTPUT_RPMPATH}/repodata" ]]; then + relative_release_path="$( os::util::repository_relative_path "${OS_OUTPUT_RELEASEPATH}" )" + relative_bin_path="$( os::util::repository_relative_path "${OS_OUTPUT_BINPATH}" )" + os::log::fatal "No release RPMs have been built! RPMs are necessary to build container images. +Build them with: + $ OS_BUILD_ENV_PRESERVE=${relative_bin_path}:${relative_release_path} hack/env make build-rpms" + fi +} diff --git a/hack/lib/build/rpm.sh b/hack/lib/build/rpm.sh new file mode 100644 index 000000000..babd19830 --- /dev/null +++ b/hack/lib/build/rpm.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# This library holds utilities for building RPMs from Origin. + +# os::build::rpm::generate_nevra_vars determines the NEVRA of the RPMs +# that would be built from the current git state. +# +# Globals: +# - OS_GIT_VERSION +# Arguments: +# - None +# Exports: +# - OS_RPM_VERSION +# - OS_RPM_RELEASE +# - OS_RPM_ARCHITECTURE +function os::build::rpm::get_nvra_vars() { + # the package name can be overwritten but is normally 'origin' + OS_RPM_ARCHITECTURE="$(uname -i)" + + # we can extract the pacakge version from the build version + os::build::version::get_vars + if [[ "${OS_GIT_VERSION}" =~ ^v([0-9](\.[0-9]+)*)(.*) ]]; then + OS_RPM_VERSION="${BASH_REMATCH[1]}" + metadata="${BASH_REMATCH[3]}" + else + os::log::fatal "Malformed \$OS_GIT_VERSION: ${OS_GIT_VERSION}" + fi + + # we can generate the package release from the git version metadata + # OS_GIT_VERSION will always have metadata, but either contain + # pre-release information _and_ build metadata, or only the latter. + # Build metadata may or may not contain the number of commits past + # the last tag. If no commit number exists, we are on a tag and use 0. + # ex. + # -alpha.0+shasums-123-dirty + # -alpha.0+shasums-123 + # -alpha.0+shasums-dirty + # -alpha.0+shasums + # +shasums-123-dirty + # +shasums-123 + # +shasums-dirty + # +shasums + if [[ "${metadata:0:1}" == "+" ]]; then + # we only have build metadata, but need to massage it so + # we can generate a valid RPM release from it + if [[ "${metadata}" =~ ^\+([a-z0-9]{7,40})(-([0-9]+))?(-dirty)?$ ]]; then + build_sha="${BASH_REMATCH[1]}" + build_num="${BASH_REMATCH[3]:-0}" + else + os::log::fatal "Malformed git version metadata: ${metadata}" + fi + OS_RPM_RELEASE="1.${build_num}.${build_sha}" + elif [[ "${metadata:0:1}" == "-" ]]; then + # we have both build metadata and pre-release info + if [[ "${metadata}" =~ ^-([^\+]+)\+([a-z0-9]{7,40})(-([0-9]+))?(-dirty)?$ ]]; then + pre_release="${BASH_REMATCH[1]}" + build_sha="${BASH_REMATCH[2]}" + build_num="${BASH_REMATCH[4]:-0}" + else + os::log::fatal "Malformed git version metadata: ${metadata}" + fi + OS_RPM_RELEASE="0.${pre_release}.${build_num}.${build_sha}" + else + os::log::fatal "Malformed git version metadata: ${metadata}" + fi + + OS_RPM_GIT_VARS=$( os::build::version::save_vars | tr '\n' ' ' ) + + export OS_RPM_VERSION OS_RPM_RELEASE OS_RPM_ARCHITECTURE OS_RPM_GIT_VARS +} + + +# os::build::rpm::format_nvra formats the rpm NVRA vars generated by +# os::build::rpm::get_nvra_vars and will generate them if necessary +# +# Globals: +# - OS_RPM_NAME +# - OS_RPM_VERSION +# - OS_RPM_RELEASE +# - OS_RPM_ARCHITECTURE +# Arguments: +# None +# Returns: +# None +function os::build::rpm::format_nvra() { + if [[ -z "${OS_RPM_VERSION:-}" || -z "${OS_RPM_RELEASE:-}" ]]; then + os::build::rpm::get_nvra_vars + fi + if [[ -z "${OS_RPM_NAME-}" ]]; then + OS_RPM_SPECFILE="$( find "${OS_ROOT}" -name *.spec )" + OS_RPM_NAME="$( rpmspec -q --qf '%{name}\n' "${OS_RPM_SPECFILE}" | head -1 )" + fi + + echo "${OS_RPM_NAME}-${OS_RPM_VERSION}-${OS_RPM_RELEASE}.${OS_RPM_ARCHITECTURE}" +} diff --git a/hack/lib/build/version.sh b/hack/lib/build/version.sh new file mode 100644 index 000000000..c1152cd99 --- /dev/null +++ b/hack/lib/build/version.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# This library holds utility functions for determining +# product versions from Git repository state. + +# os::build::version::get_vars loads the standard version variables as +# ENV vars +function os::build::version::get_vars() { + if [[ -n "${OS_VERSION_FILE-}" ]]; then + if [[ -f "${OS_VERSION_FILE}" ]]; then + source "${OS_VERSION_FILE}" + return + fi + if [[ ! -d "${OS_ROOT}/.git" ]]; then + os::log::fatal "No version file at ${OS_VERSION_FILE}" + fi + os::log::warning "No version file at ${OS_VERSION_FILE}, falling back to git versions" + fi + os::build::version::git_vars +} +readonly -f os::build::version::get_vars + +# os::build::version::git_vars looks up the current Git vars if they have not been calculated. +function os::build::version::git_vars() { + if [[ -n "${OS_GIT_VERSION-}" ]]; then + return 0 + fi + + local git=(git --work-tree "${OS_ROOT}") + + if [[ -n ${OS_GIT_COMMIT-} ]] || OS_GIT_COMMIT=$("${git[@]}" rev-parse --short "HEAD^{commit}" 2>/dev/null); then + if [[ -z ${OS_GIT_TREE_STATE-} ]]; then + # Check if the tree is dirty. default to dirty + if git_status=$("${git[@]}" status --porcelain 2>/dev/null) && [[ -z ${git_status} ]]; then + OS_GIT_TREE_STATE="clean" + else + OS_GIT_TREE_STATE="dirty" + fi + fi + # Use git describe to find the version based on annotated tags. + if [[ -n ${OS_GIT_VERSION-} ]] || OS_GIT_VERSION=$("${git[@]}" describe --long --tags --abbrev=7 --match 'v[0-9]*' "${OS_GIT_COMMIT}^{commit}" 2>/dev/null); then + # Try to match the "git describe" output to a regex to try to extract + # the "major" and "minor" versions and whether this is the exact tagged + # version or whether the tree is between two tagged versions. + if [[ "${OS_GIT_VERSION}" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)(\.[0-9]+)*([-].*)?$ ]]; then + OS_GIT_MAJOR=${BASH_REMATCH[1]} + OS_GIT_MINOR=${BASH_REMATCH[2]} + OS_GIT_PATCH=${BASH_REMATCH[3]} + if [[ -n "${BASH_REMATCH[5]}" ]]; then + OS_GIT_MINOR+="+" + fi + fi + + # This translates the "git describe" to an actual semver.org + # compatible semantic version that looks something like this: + # v1.1.0-alpha.0.6+84c76d1-345 + OS_GIT_VERSION=$(echo "${OS_GIT_VERSION}" | sed "s/-\([0-9]\{1,\}\)-g\([0-9a-f]\{7,40\}\)$/\+\2-\1/") + # If this is an exact tag, remove the last segment. + OS_GIT_VERSION=$(echo "${OS_GIT_VERSION}" | sed "s/-0$//") + if [[ "${OS_GIT_TREE_STATE}" == "dirty" ]]; then + # git describe --dirty only considers changes to existing files, but + # that is problematic since new untracked .go files affect the build, + # so use our idea of "dirty" from git status instead. + OS_GIT_VERSION+="-dirty" + fi + fi + fi +} +readonly -f os::build::version::git_vars + +# Saves the environment flags to $1 +function os::build::version::save_vars() { + cat </dev/null 2>&1; then + os::log::warning "[CLEANUP] No \`docker\` binary found, skipping container cleanup." + return + fi + + os::log::info "[CLEANUP] Stopping docker containers" + for id in $( os::cleanup::internal::list_our_containers ); do + os::log::debug "Stopping ${id}" + docker stop "${id}" >/dev/null + done + + if [[ -n "${SKIP_IMAGE_CLEANUP:-}" ]]; then + return + fi + + os::log::info "[CLEANUP] Removing docker containers" + for id in $( os::cleanup::internal::list_our_containers ); do + os::log::debug "Removing ${id}" + docker rm --volumes "${id}" >/dev/null + done +} +readonly -f os::cleanup::containers + +# os::cleanup::dump_container_logs operates on k8s containers to dump any logs +# from the containers. +# +# Globals: +# None +# Arguments: +# None +# Returns: +# None +function os::cleanup::dump_container_logs() { + if ! os::util::find::system_binary docker >/dev/null 2>&1; then + os::log::warning "[CLEANUP] No \`docker\` binary found, skipping container log harvest." + return + fi + + local container_log_dir="${LOG_DIR}/containers" + mkdir -p "${container_log_dir}" + + os::log::info "[CLEANUP] Dumping container logs to $( os::util::repository_relative_path "${container_log_dir}" )" + for id in $( os::cleanup::internal::list_our_containers ); do + local name; name="$( docker inspect --format '{{ .Name }}' "${id}" )" + os::log::debug "Dumping logs for ${id} to ${name}.log" + docker logs "${id}" >"${container_log_dir}/${name}.log" 2>&1 + done +} +readonly -f os::cleanup::dump_container_logs + +# os::cleanup::internal::list_containers returns a space-delimited list of +# docker containers that match a name regex. +# +# Globals: +# None +# Arguments: +# 1 - regex to match on the name +# Returns: +# None +function os::cleanup::internal::list_containers() { + local regex="$1" + local ids; + for short_id in $( docker ps -aq ); do + local id; id="$( docker inspect --format '{{ .Id }}' "${short_id}" )" + local name; name="$( docker inspect --format '{{ .Name }}' "${id}" )" + if [[ "${name}" =~ ${regex} ]]; then + ids+=( "${id}" ) + fi + done + + echo "${ids[*]:+"${ids[*]}"}" +} +readonly -f os::cleanup::internal::list_containers + +# os::cleanup::tmpdir performs cleanup of temp directories as a precondition for running a test. It tries to +# clean up mounts in the temp directories. +# +# Globals: +# - BASETMPDIR +# - USE_SUDO +# Returns: +# None +function os::cleanup::tmpdir() { + os::log::info "[CLEANUP] Cleaning up temporary directories" + # ensure that the directories are clean + if os::util::find::system_binary "findmnt" &>/dev/null; then + for target in $( ${USE_SUDO:+sudo} findmnt --output TARGET --list ); do + if [[ "${target}" == "${BASETMPDIR}"* ]]; then + ${USE_SUDO:+sudo} umount "${target}" + fi + done + fi + + # delete any sub directory underneath BASETMPDIR + for directory in $( find "${BASETMPDIR}" -mindepth 2 -maxdepth 2 ); do + ${USE_SUDO:+sudo} rm -rf "${directory}" + done +} +readonly -f os::cleanup::tmpdir + +# os::cleanup::truncate_large_logs truncates very large files under +# $LOG_DIR and $ARTIFACT_DIR so we do not upload them to cloud storage +# after CI runs. +# +# Globals: +# - LOG_DIR +# - ARTIFACT_DIR +# Arguments: +# None +# Returns: +# None +function os::cleanup::truncate_large_logs() { + local max_file_size="200M" + os::log::info "[CLEANUP] Truncating log files over ${max_file_size}" + for file in $(find "${ARTIFACT_DIR}" "${LOG_DIR}" -type f -name '*.log' \( -size +${max_file_size} \)); do + mv "${file}" "${file}.tmp" + echo "LOGFILE TOO LONG ($(du -h "${file}.tmp")), PREVIOUS BYTES TRUNCATED. LAST ${max_file_size} OF LOGFILE:" > "${file}" + tail -c ${max_file_size} "${file}.tmp" >> "${file}" + rm "${file}.tmp" + done +} +readonly -f os::cleanup::truncate_large_logs + +# os::cleanup::processes kills all processes created by the test +# script. +# +# Globals: +# None +# Arguments: +# None +# Returns: +# None +function os::cleanup::processes() { + os::log::info "[CLEANUP] Killing child processes" + for job in $( jobs -pr ); do + for child in $( pgrep -P "${job}" ); do + ${USE_SUDO:+sudo} kill "${child}" &> /dev/null + done + ${USE_SUDO:+sudo} kill "${job}" &> /dev/null + done +} +readonly -f os::cleanup::processes diff --git a/hack/lib/cmd.sh b/hack/lib/cmd.sh new file mode 100644 index 000000000..a45a984a2 --- /dev/null +++ b/hack/lib/cmd.sh @@ -0,0 +1,619 @@ +#!/bin/bash +# This utility file contains functions that wrap commands to be tested. All wrapper functions run commands +# in a sub-shell and redirect all output. Tests in test-cmd *must* use these functions for testing. + +# expect_success runs the cmd and expects an exit code of 0 +function os::cmd::expect_success() { + if [[ $# -ne 1 ]]; then echo "os::cmd::expect_success expects only one argument, got $#"; return 1; fi + local cmd=$1 + + os::cmd::internal::expect_exit_code_run_grep "${cmd}" +} +readonly -f os::cmd::expect_success + +# expect_failure runs the cmd and expects a non-zero exit code +function os::cmd::expect_failure() { + if [[ $# -ne 1 ]]; then echo "os::cmd::expect_failure expects only one argument, got $#"; return 1; fi + local cmd=$1 + + os::cmd::internal::expect_exit_code_run_grep "${cmd}" "os::cmd::internal::failure_func" +} +readonly -f os::cmd::expect_failure + +# expect_success_and_text runs the cmd and expects an exit code of 0 +# as well as running a grep test to find the given string in the output +function os::cmd::expect_success_and_text() { + if [[ $# -ne 2 ]]; then echo "os::cmd::expect_success_and_text expects two arguments, got $#"; return 1; fi + local cmd=$1 + local expected_text=$2 + + os::cmd::internal::expect_exit_code_run_grep "${cmd}" "os::cmd::internal::success_func" "${expected_text}" +} +readonly -f os::cmd::expect_success_and_text + +# expect_failure_and_text runs the cmd and expects a non-zero exit code +# as well as running a grep test to find the given string in the output +function os::cmd::expect_failure_and_text() { + if [[ $# -ne 2 ]]; then echo "os::cmd::expect_failure_and_text expects two arguments, got $#"; return 1; fi + local cmd=$1 + local expected_text=$2 + + os::cmd::internal::expect_exit_code_run_grep "${cmd}" "os::cmd::internal::failure_func" "${expected_text}" +} +readonly -f os::cmd::expect_failure_and_text + +# expect_success_and_not_text runs the cmd and expects an exit code of 0 +# as well as running a grep test to ensure the given string is not in the output +function os::cmd::expect_success_and_not_text() { + if [[ $# -ne 2 ]]; then echo "os::cmd::expect_success_and_not_text expects two arguments, got $#"; return 1; fi + local cmd=$1 + local expected_text=$2 + + os::cmd::internal::expect_exit_code_run_grep "${cmd}" "os::cmd::internal::success_func" "${expected_text}" "os::cmd::internal::failure_func" +} +readonly -f os::cmd::expect_success_and_not_text + +# expect_failure_and_not_text runs the cmd and expects a non-zero exit code +# as well as running a grep test to ensure the given string is not in the output +function os::cmd::expect_failure_and_not_text() { + if [[ $# -ne 2 ]]; then echo "os::cmd::expect_failure_and_not_text expects two arguments, got $#"; return 1; fi + local cmd=$1 + local expected_text=$2 + + os::cmd::internal::expect_exit_code_run_grep "${cmd}" "os::cmd::internal::failure_func" "${expected_text}" "os::cmd::internal::failure_func" +} +readonly -f os::cmd::expect_failure_and_not_text + +# expect_code runs the cmd and expects a given exit code +function os::cmd::expect_code() { + if [[ $# -ne 2 ]]; then echo "os::cmd::expect_code expects two arguments, got $#"; return 1; fi + local cmd=$1 + local expected_cmd_code=$2 + + os::cmd::internal::expect_exit_code_run_grep "${cmd}" "os::cmd::internal::specific_code_func ${expected_cmd_code}" +} +readonly -f os::cmd::expect_code + +# expect_code_and_text runs the cmd and expects the given exit code +# as well as running a grep test to find the given string in the output +function os::cmd::expect_code_and_text() { + if [[ $# -ne 3 ]]; then echo "os::cmd::expect_code_and_text expects three arguments, got $#"; return 1; fi + local cmd=$1 + local expected_cmd_code=$2 + local expected_text=$3 + + os::cmd::internal::expect_exit_code_run_grep "${cmd}" "os::cmd::internal::specific_code_func ${expected_cmd_code}" "${expected_text}" +} +readonly -f os::cmd::expect_code_and_text + +# expect_code_and_not_text runs the cmd and expects the given exit code +# as well as running a grep test to ensure the given string is not in the output +function os::cmd::expect_code_and_not_text() { + if [[ $# -ne 3 ]]; then echo "os::cmd::expect_code_and_not_text expects three arguments, got $#"; return 1; fi + local cmd=$1 + local expected_cmd_code=$2 + local expected_text=$3 + + os::cmd::internal::expect_exit_code_run_grep "${cmd}" "os::cmd::internal::specific_code_func ${expected_cmd_code}" "${expected_text}" "os::cmd::internal::failure_func" +} +readonly -f os::cmd::expect_code_and_not_text + +millisecond=1 +second=$(( 1000 * millisecond )) +minute=$(( 60 * second )) + +# os::cmd::try_until_success runs the cmd in a small interval until either the command succeeds or times out +# the default time-out for os::cmd::try_until_success is 60 seconds. +# the default interval for os::cmd::try_until_success is 200ms +function os::cmd::try_until_success() { + if [[ $# -lt 1 ]]; then echo "os::cmd::try_until_success expects at least one arguments, got $#"; return 1; fi + local cmd=$1 + local duration=${2:-$minute} + local interval=${3:-0.2} + + os::cmd::internal::run_until_exit_code "${cmd}" "os::cmd::internal::success_func" "${duration}" "${interval}" +} +readonly -f os::cmd::try_until_success + +# os::cmd::try_until_failure runs the cmd until either the command fails or times out +# the default time-out for os::cmd::try_until_failure is 60 seconds. +function os::cmd::try_until_failure() { + if [[ $# -lt 1 ]]; then echo "os::cmd::try_until_failure expects at least one argument, got $#"; return 1; fi + local cmd=$1 + local duration=${2:-$minute} + local interval=${3:-0.2} + + os::cmd::internal::run_until_exit_code "${cmd}" "os::cmd::internal::failure_func" "${duration}" "${interval}" +} +readonly -f os::cmd::try_until_failure + +# os::cmd::try_until_text runs the cmd until either the command outputs the desired text or times out +# the default time-out for os::cmd::try_until_text is 60 seconds. +function os::cmd::try_until_text() { + if [[ $# -lt 2 ]]; then echo "os::cmd::try_until_text expects at least two arguments, got $#"; return 1; fi + local cmd=$1 + local text=$2 + local duration=${3:-$minute} + local interval=${4:-0.2} + + os::cmd::internal::run_until_text "${cmd}" "${text}" "os::cmd::internal::success_func" "${duration}" "${interval}" +} +readonly -f os::cmd::try_until_text + +# os::cmd::try_until_not_text runs the cmd until either the command doesnot output the text or times out +# the default time-out for os::cmd::try_until_not_text is 60 seconds. +function os::cmd::try_until_not_text() { + if [[ $# -lt 2 ]]; then echo "os::cmd::try_until_not_text expects at least two arguments, got $#"; return 1; fi + local cmd=$1 + local text=$2 + local duration=${3:-$minute} + local interval=${4:-0.2} + + os::cmd::internal::run_until_text "${cmd}" "${text}" "os::cmd::internal::failure_func" "${duration}" "${interval}" +} +readonly -f os::cmd::try_until_text + +# Functions in the os::cmd::internal namespace are discouraged from being used outside of os::cmd + +# In order to harvest stderr and stdout at the same time into different buckets, we need to stick them into files +# in an intermediate step +os_cmd_internal_tmpdir="${TMPDIR:-"/tmp"}/cmd" +os_cmd_internal_tmpout="${os_cmd_internal_tmpdir}/tmp_stdout.log" +os_cmd_internal_tmperr="${os_cmd_internal_tmpdir}/tmp_stderr.log" + +# os::cmd::internal::expect_exit_code_run_grep runs the provided test command and expects a specific +# exit code from that command as well as the success of a specified `grep` invocation. Output from the +# command to be tested is suppressed unless either `VERBOSE=1` or the test fails. This function bypasses +# any error exiting settings or traps set by upstream callers by masking the return code of the command +# with the return code of setting the result variable on failure. +# +# Globals: +# - JUNIT_REPORT_OUTPUT +# - VERBOSE +# Arguments: +# - 1: the command to run +# - 2: command evaluation assertion to use +# - 3: text to test for +# - 4: text assertion to use +# Returns: +# - 0: if all assertions met +# - 1: if any assertions fail +function os::cmd::internal::expect_exit_code_run_grep() { + local cmd=$1 + # default expected cmd code to 0 for success + local cmd_eval_func=${2:-os::cmd::internal::success_func} + # default to nothing + local grep_args=${3:-} + # default expected test code to 0 for success + local test_eval_func=${4:-os::cmd::internal::success_func} + + local -a junit_log + + os::cmd::internal::init_tempdir + os::test::junit::declare_test_start + + local name=$(os::cmd::internal::describe_call "${cmd}" "${cmd_eval_func}" "${grep_args}" "${test_eval_func}") + local preamble="Running ${name}..." + echo "${preamble}" + # for ease of parsing, we want the entire declaration on one line, so we replace '\n' with ';' + junit_log+=( "${name//$'\n'/;}" ) + + local start_time=$(os::cmd::internal::seconds_since_epoch) + + local cmd_result=$( os::cmd::internal::run_collecting_output "${cmd}"; echo $? ) + local cmd_succeeded=$( ${cmd_eval_func} "${cmd_result}"; echo $? ) + + local test_result=0 + if [[ -n "${grep_args}" ]]; then + test_result=$( os::cmd::internal::run_collecting_output 'grep -Eq "${grep_args}" <(os::cmd::internal::get_results)'; echo $? ) + fi + local test_succeeded=$( ${test_eval_func} "${test_result}"; echo $? ) + + local end_time=$(os::cmd::internal::seconds_since_epoch) + local time_elapsed=$(echo "scale=3; ${end_time} - ${start_time}" | bc | xargs printf '%5.3f') # in decimal seconds, we need leading zeroes for parsing later + + # clear the preamble so we can print out the success or error message + os::text::clear_string "${preamble}" + + local return_code + if (( cmd_succeeded && test_succeeded )); then + os::text::print_green "SUCCESS after ${time_elapsed}s: ${name}" + junit_log+=( "SUCCESS after ${time_elapsed}s: ${name//$'\n'/;}" ) + + if [[ -n ${VERBOSE-} ]]; then + os::cmd::internal::print_results + fi + return_code=0 + else + local cause=$(os::cmd::internal::assemble_causes "${cmd_succeeded}" "${test_succeeded}") + + os::text::print_red_bold "FAILURE after ${time_elapsed}s: ${name}: ${cause}" + junit_log+=( "FAILURE after ${time_elapsed}s: ${name//$'\n'/;}: ${cause}" ) + + os::text::print_red "$(os::cmd::internal::print_results)" + return_code=1 + fi + + junit_log+=( "$(os::cmd::internal::print_results)" ) + # append inside of a subshell so that IFS doesn't get propagated out + ( IFS=$'\n'; echo "${junit_log[*]}" >> "${JUNIT_REPORT_OUTPUT:-/dev/null}" ) + os::test::junit::declare_test_end + return "${return_code}" +} +readonly -f os::cmd::internal::expect_exit_code_run_grep + +# os::cmd::internal::init_tempdir initializes the temporary directory +function os::cmd::internal::init_tempdir() { + mkdir -p "${os_cmd_internal_tmpdir}" + rm -f "${os_cmd_internal_tmpdir}"/tmp_std{out,err}.log +} +readonly -f os::cmd::internal::init_tempdir + +# os::cmd::internal::describe_call determines the file:line of the latest function call made +# from outside of this file in the call stack, and the name of the function being called from +# that line, returning a string describing the call +function os::cmd::internal::describe_call() { + local cmd=$1 + local cmd_eval_func=$2 + local grep_args=${3:-} + local test_eval_func=${4:-} + + local caller_id=$(os::cmd::internal::determine_caller) + local full_name="${caller_id}: executing '${cmd}'" + + local cmd_expectation=$(os::cmd::internal::describe_expectation "${cmd_eval_func}") + local full_name="${full_name} expecting ${cmd_expectation}" + + if [[ -n "${grep_args}" ]]; then + local text_expecting= + case "${test_eval_func}" in + "os::cmd::internal::success_func") + text_expecting="text" ;; + "os::cmd::internal::failure_func") + text_expecting="not text" ;; + esac + full_name="${full_name} and ${text_expecting} '${grep_args}'" + fi + + echo "${full_name}" +} +readonly -f os::cmd::internal::describe_call + +# os::cmd::internal::determine_caller determines the file relative to the OpenShift Origin root directory +# and line number of the function call to the outer os::cmd wrapper function +function os::cmd::internal::determine_caller() { + local call_depth= + local len_sources="${#BASH_SOURCE[@]}" + for (( i=0; i<${len_sources}; i++ )); do + if [ ! $(echo "${BASH_SOURCE[i]}" | grep "hack/lib/cmd\.sh$") ]; then + call_depth=i + break + fi + done + + local caller_file="${BASH_SOURCE[${call_depth}]}" + caller_file="$( os::util::repository_relative_path "${caller_file}" )" + local caller_line="${BASH_LINENO[${call_depth}-1]}" + echo "${caller_file}:${caller_line}" +} +readonly -f os::cmd::internal::determine_caller + +# os::cmd::internal::describe_expectation describes a command return code evaluation function +function os::cmd::internal::describe_expectation() { + local func=$1 + case "${func}" in + "os::cmd::internal::success_func") + echo "success" ;; + "os::cmd::internal::failure_func") + echo "failure" ;; + "os::cmd::internal::specific_code_func"*[0-9]) + local code=$(echo "${func}" | grep -Eo "[0-9]+$") + echo "exit code ${code}" ;; + "") + echo "any result" + esac +} +readonly -f os::cmd::internal::describe_expectation + +# os::cmd::internal::seconds_since_epoch returns the number of seconds elapsed since the epoch +# with milli-second precision +function os::cmd::internal::seconds_since_epoch() { + local ns=$(date +%s%N) + # if `date` doesn't support nanoseconds, return second precision + if [[ "$ns" == *N ]]; then + date "+%s.000" + return + fi + echo $(bc <<< "scale=3; ${ns}/1000000000") +} +readonly -f os::cmd::internal::seconds_since_epoch + +# os::cmd::internal::run_collecting_output runs the command given, piping stdout and stderr into +# the given files, and returning the exit code of the command +function os::cmd::internal::run_collecting_output() { + local cmd=$1 + + local result= + $( eval "${cmd}" 1>>"${os_cmd_internal_tmpout}" 2>>"${os_cmd_internal_tmperr}" ) || result=$? + local result=${result:-0} # if we haven't set result yet, the command succeeded + + return "${result}" +} +readonly -f os::cmd::internal::run_collecting_output + +# os::cmd::internal::success_func determines if the input exit code denotes success +# this function returns 0 for false and 1 for true to be compatible with arithmetic tests +function os::cmd::internal::success_func() { + local exit_code=$1 + + # use a negated test to get output correct for (( )) + [[ "${exit_code}" -ne "0" ]] + return $? +} +readonly -f os::cmd::internal::success_func + +# os::cmd::internal::failure_func determines if the input exit code denotes failure +# this function returns 0 for false and 1 for true to be compatible with arithmetic tests +function os::cmd::internal::failure_func() { + local exit_code=$1 + + # use a negated test to get output correct for (( )) + [[ "${exit_code}" -eq "0" ]] + return $? +} +readonly -f os::cmd::internal::failure_func + +# os::cmd::internal::specific_code_func determines if the input exit code matches the given code +# this function returns 0 for false and 1 for true to be compatible with arithmetic tests +function os::cmd::internal::specific_code_func() { + local expected_code=$1 + local exit_code=$2 + + # use a negated test to get output correct for (( )) + [[ "${exit_code}" -ne "${expected_code}" ]] + return $? +} +readonly -f os::cmd::internal::specific_code_func + +# os::cmd::internal::get_results prints the stderr and stdout files +function os::cmd::internal::get_results() { + cat "${os_cmd_internal_tmpout}" "${os_cmd_internal_tmperr}" +} +readonly -f os::cmd::internal::get_results + +# os::cmd::internal::get_last_results prints the stderr and stdout from the last attempt +function os::cmd::internal::get_last_results() { + cat "${os_cmd_internal_tmpout}" | awk 'BEGIN { RS = "\x1e" } END { print $0 }' + cat "${os_cmd_internal_tmperr}" | awk 'BEGIN { RS = "\x1e" } END { print $0 }' +} +readonly -f os::cmd::internal::get_last_results + +# os::cmd::internal::mark_attempt marks the end of an attempt in the stdout and stderr log files +# this is used to make the try_until_* output more concise +function os::cmd::internal::mark_attempt() { + echo -e '\x1e' >> "${os_cmd_internal_tmpout}" + echo -e '\x1e' >> "${os_cmd_internal_tmperr}" +} +readonly -f os::cmd::internal::mark_attempt + +# os::cmd::internal::compress_output compresses an output file into timeline representation +function os::cmd::internal::compress_output() { + local logfile=$1 + + awk -f ${OS_ROOT}/hack/lib/compress.awk $logfile +} +readonly -f os::cmd::internal::compress_output + +# os::cmd::internal::print_results pretty-prints the stderr and stdout files. If attempt separators +# are present, this function returns a concise view of the stdout and stderr output files using a +# timeline format, where consecutive output lines that are the same are condensed into one line +# with a counter +function os::cmd::internal::print_results() { + if [[ -s "${os_cmd_internal_tmpout}" ]]; then + echo "Standard output from the command:" + if grep -q $'\x1e' "${os_cmd_internal_tmpout}"; then + os::cmd::internal::compress_output "${os_cmd_internal_tmpout}" + else + cat "${os_cmd_internal_tmpout}"; echo + fi + else + echo "There was no output from the command." + fi + + if [[ -s "${os_cmd_internal_tmperr}" ]]; then + echo "Standard error from the command:" + if grep -q $'\x1e' "${os_cmd_internal_tmperr}"; then + os::cmd::internal::compress_output "${os_cmd_internal_tmperr}" + else + cat "${os_cmd_internal_tmperr}"; echo + fi + else + echo "There was no error output from the command." + fi +} +readonly -f os::cmd::internal::print_results + +# os::cmd::internal::assemble_causes determines from the two input booleans which part of the test +# failed and generates a nice delimited list of failure causes +function os::cmd::internal::assemble_causes() { + local cmd_succeeded=$1 + local test_succeeded=$2 + + local causes=() + if (( ! cmd_succeeded )); then + causes+=("the command returned the wrong error code") + fi + if (( ! test_succeeded )); then + causes+=("the output content test failed") + fi + + local list=$(printf '; %s' "${causes[@]}") + echo "${list:2}" +} +readonly -f os::cmd::internal::assemble_causes + +# os::cmd::internal::run_until_exit_code runs the provided command until the exit code test given +# succeeds or the timeout given runs out. Output from the command to be tested is suppressed unless +# either `VERBOSE=1` or the test fails. This function bypasses any error exiting settings or traps +# set by upstream callers by masking the return code of the command with the return code of setting +# the result variable on failure. +# +# Globals: +# - JUNIT_REPORT_OUTPUT +# - VERBOSE +# Arguments: +# - 1: the command to run +# - 2: command evaluation assertion to use +# - 3: timeout duration +# - 4: interval duration +# Returns: +# - 0: if all assertions met before timeout +# - 1: if timeout occurs +function os::cmd::internal::run_until_exit_code() { + local cmd=$1 + local cmd_eval_func=$2 + local duration=$3 + local interval=$4 + + local -a junit_log + + os::cmd::internal::init_tempdir + os::test::junit::declare_test_start + + local description=$(os::cmd::internal::describe_call "${cmd}" "${cmd_eval_func}") + local duration_seconds=$(echo "scale=3; $(( duration )) / 1000" | bc | xargs printf '%5.3f') + local description="${description}; re-trying every ${interval}s until completion or ${duration_seconds}s" + local preamble="Running ${description}..." + echo "${preamble}" + # for ease of parsing, we want the entire declaration on one line, so we replace '\n' with ';' + junit_log+=( "${description//$'\n'/;}" ) + + local start_time=$(os::cmd::internal::seconds_since_epoch) + + local deadline=$(( $(date +%s000) + $duration )) + local cmd_succeeded=0 + while [ $(date +%s000) -lt $deadline ]; do + local cmd_result=$( os::cmd::internal::run_collecting_output "${cmd}"; echo $? ) + cmd_succeeded=$( ${cmd_eval_func} "${cmd_result}"; echo $? ) + if (( cmd_succeeded )); then + break + fi + sleep "${interval}" + os::cmd::internal::mark_attempt + done + + local end_time=$(os::cmd::internal::seconds_since_epoch) + local time_elapsed=$(echo "scale=9; ${end_time} - ${start_time}" | bc | xargs printf '%5.3f') # in decimal seconds, we need leading zeroes for parsing later + + # clear the preamble so we can print out the success or error message + os::text::clear_string "${preamble}" + + local return_code + if (( cmd_succeeded )); then + os::text::print_green "SUCCESS after ${time_elapsed}s: ${description}" + junit_log+=( "SUCCESS after ${time_elapsed}s: ${description//$'\n'/;}" ) + + if [[ -n ${VERBOSE-} ]]; then + os::cmd::internal::print_results + fi + return_code=0 + else + os::text::print_red_bold "FAILURE after ${time_elapsed}s: ${description}: the command timed out" + junit_log+=( "FAILURE after ${time_elapsed}s: ${description//$'\n'/;}: the command timed out" ) + + os::text::print_red "$(os::cmd::internal::print_results)" + return_code=1 + fi + + junit_log+=( "$(os::cmd::internal::print_results)" ) + ( IFS=$'\n'; echo "${junit_log[*]}" >> "${JUNIT_REPORT_OUTPUT:-/dev/null}" ) + os::test::junit::declare_test_end + return "${return_code}" +} +readonly -f os::cmd::internal::run_until_exit_code + +# os::cmd::internal::run_until_text runs the provided command until the assertion function succeeds with +# the given text on the command output or the timeout given runs out. This can be used to run until the +# output does or does not contain some text. Output from the command to be tested is suppressed unless +# either `VERBOSE=1` or the test fails. This function bypasses any error exiting settings or traps +# set by upstream callers by masking the return code of the command with the return code of setting +# the result variable on failure. +# +# Globals: +# - JUNIT_REPORT_OUTPUT +# - VERBOSE +# Arguments: +# - 1: the command to run +# - 2: text to test for +# - 3: text assertion to use +# - 4: timeout duration +# - 5: interval duration +# Returns: +# - 0: if all assertions met before timeout +# - 1: if timeout occurs +function os::cmd::internal::run_until_text() { + local cmd=$1 + local text=$2 + local test_eval_func=${3:-os::cmd::internal::success_func} + local duration=$4 + local interval=$5 + + local -a junit_log + + os::cmd::internal::init_tempdir + os::test::junit::declare_test_start + + local description=$(os::cmd::internal::describe_call "${cmd}" "" "${text}" "${test_eval_func}") + local duration_seconds=$(echo "scale=3; $(( duration )) / 1000" | bc | xargs printf '%5.3f') + local description="${description}; re-trying every ${interval}s until completion or ${duration_seconds}s" + local preamble="Running ${description}..." + echo "${preamble}" + # for ease of parsing, we want the entire declaration on one line, so we replace '\n' with ';' + junit_log+=( "${description//$'\n'/;}" ) + + local start_time=$(os::cmd::internal::seconds_since_epoch) + + local deadline=$(( $(date +%s000) + $duration )) + local test_succeeded=0 + while [ $(date +%s000) -lt $deadline ]; do + local cmd_result=$( os::cmd::internal::run_collecting_output "${cmd}"; echo $? ) + local test_result + test_result=$( os::cmd::internal::run_collecting_output 'grep -Eq "${text}" <(os::cmd::internal::get_last_results)'; echo $? ) + test_succeeded=$( ${test_eval_func} "${test_result}"; echo $? ) + + if (( test_succeeded )); then + break + fi + sleep "${interval}" + os::cmd::internal::mark_attempt + done + + local end_time=$(os::cmd::internal::seconds_since_epoch) + local time_elapsed=$(echo "scale=9; ${end_time} - ${start_time}" | bc | xargs printf '%5.3f') # in decimal seconds, we need leading zeroes for parsing later + + # clear the preamble so we can print out the success or error message + os::text::clear_string "${preamble}" + + local return_code + if (( test_succeeded )); then + os::text::print_green "SUCCESS after ${time_elapsed}s: ${description}" + junit_log+=( "SUCCESS after ${time_elapsed}s: ${description//$'\n'/;}" ) + + if [[ -n ${VERBOSE-} ]]; then + os::cmd::internal::print_results + fi + return_code=0 + else + os::text::print_red_bold "FAILURE after ${time_elapsed}s: ${description}: the command timed out" + junit_log+=( "FAILURE after ${time_elapsed}s: ${description//$'\n'/;}: the command timed out" ) + + os::text::print_red "$(os::cmd::internal::print_results)" + return_code=1 + fi + + junit_log+=( "$(os::cmd::internal::print_results)" ) + ( IFS=$'\n'; echo "${junit_log[*]}" >> "${JUNIT_REPORT_OUTPUT:-/dev/null}" ) + os::test::junit::declare_test_end + return "${return_code}" +} +readonly -f os::cmd::internal::run_until_text diff --git a/hack/lib/compress.awk b/hack/lib/compress.awk new file mode 100644 index 000000000..386d79b08 --- /dev/null +++ b/hack/lib/compress.awk @@ -0,0 +1,41 @@ +# Helper functions +function trim(s) { + gsub(/^[ \t\r\n]+|[ \t\r\n]+$/, "", s); + return s; +} + +function printRecordAndCount(record, count) { + print record; + if (count > 1) { + printf("... repeated %d times\n", count) + } +} + +BEGIN { + # Before processing, set the record separator to the ASCII record separator character \x1e + RS = "\x1e"; +} + +# This action is executed for each record +{ + # Build our current var from the trimmed record + current = trim($0); + + # Bump the count of times we have seen it + seen[current]++; + + # Print the previous record and its count (if it is not identical to the current record) + if (previous && previous != current) { + printRecordAndCount(previous, seen[previous]); + } + + # Store the current record as the previous record + previous = current; +} + +END { + # After processing, print the last record and count if it is non-empty + if (previous) { + printRecordAndCount(previous, seen[previous]); + } +} \ No newline at end of file diff --git a/hack/lib/constants.sh b/hack/lib/constants.sh new file mode 100755 index 000000000..2b1fdb5ed --- /dev/null +++ b/hack/lib/constants.sh @@ -0,0 +1,150 @@ +#!/bin/bash + +# This script provides constants for the Golang binary build process + +readonly OS_GO_PACKAGE=github.com/openshift/cluster-machine-approver + +readonly OS_BUILD_ENV_GOLANG="${OS_BUILD_ENV_GOLANG:-1.9}" +readonly OS_BUILD_ENV_IMAGE="${OS_BUILD_ENV_IMAGE:-openshift/origin-release:golang-${OS_BUILD_ENV_GOLANG}}" +readonly OS_REQUIRED_GO_VERSION="go1.9" +readonly OS_BUILD_ENV_WORKINGDIR="/go/${OS_GO_PACKAGE}" + +readonly OS_OUTPUT_BASEPATH="${OS_OUTPUT_BASEPATH:-_output}" +readonly OS_BASE_OUTPUT="${OS_ROOT}/${OS_OUTPUT_BASEPATH}" +readonly OS_OUTPUT_SCRIPTPATH="${OS_OUTPUT_SCRIPTPATH:-"${OS_BASE_OUTPUT}/scripts"}" + +readonly OS_OUTPUT_SUBPATH="${OS_OUTPUT_SUBPATH:-${OS_OUTPUT_BASEPATH}/local}" +readonly OS_OUTPUT="${OS_ROOT}/${OS_OUTPUT_SUBPATH}" +readonly OS_OUTPUT_RELEASEPATH="${OS_OUTPUT}/releases" +readonly OS_OUTPUT_RPMPATH="${OS_OUTPUT_RELEASEPATH}/rpms" +readonly OS_OUTPUT_BINPATH="${OS_OUTPUT}/bin" +readonly OS_OUTPUT_PKGDIR="${OS_OUTPUT}/pkgdir" + +readonly OS_GOFLAGS_TAGS="include_gcs include_oss containers_image_openpgp" + +readonly OS_IMAGE_COMPILE_BINARIES=( ) + +readonly OS_CROSS_COMPILE_TARGETS=( + cmd/osin-operator +) +readonly OS_CROSS_COMPILE_BINARIES=("${OS_CROSS_COMPILE_TARGETS[@]##*/}") + +readonly OS_TEST_TARGETS=( ) + +# os::build::get_product_vars exports variables that we expect to change +# depending on the distribution of Origin +function os::build::get_product_vars() { + export OS_BUILD_LDFLAGS_IMAGE_PREFIX="${OS_IMAGE_PREFIX:-"openshift/origin"}" + export OS_BUILD_LDFLAGS_DEFAULT_IMAGE_STREAMS="${OS_BUILD_LDFLAGS_DEFAULT_IMAGE_STREAMS:-"centos7"}" +} + +# os::build::ldflags calculates the -ldflags argument for building OpenShift +function os::build::ldflags() { + # Run this in a subshell to prevent settings/variables from leaking. + set -o errexit + set -o nounset + set -o pipefail + + cd "${OS_ROOT}" + + os::build::version::get_vars + os::build::get_product_vars + + local buildDate="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + + declare -a ldflags=() + + ldflags+=($(os::build::ldflag "${OS_GO_PACKAGE}/pkg/version.majorFromGit" "${OS_GIT_MAJOR}")) + ldflags+=($(os::build::ldflag "${OS_GO_PACKAGE}/pkg/version.minorFromGit" "${OS_GIT_MINOR}")) + ldflags+=($(os::build::ldflag "${OS_GO_PACKAGE}/pkg/version.versionFromGit" "${OS_GIT_VERSION}")) + ldflags+=($(os::build::ldflag "${OS_GO_PACKAGE}/pkg/version.commitFromGit" "${OS_GIT_COMMIT}")) + ldflags+=($(os::build::ldflag "${OS_GO_PACKAGE}/pkg/version.buildDate" "${buildDate}")) + + # The -ldflags parameter takes a single string, so join the output. + echo "${ldflags[*]-}" +} +readonly -f os::build::ldflags + +# No-op +function os::build::generate_windows_versioninfo() { + : +} +readonly -f os::build::generate_windows_versioninfo + +function os::build::clean_windows_versioninfo() { + : +} +readonly -f os::build::clean_windows_versioninfo + +# os::util::list_go_src_files lists files we consider part of our project +# source code, useful for tools that iterate over source to provide vet- +# ting or linting, etc. +# +# Globals: +# None +# Arguments: +# None +# Returns: +# None +function os::util::list_go_src_files() { + find . -not \( \ + \( \ + -wholename './_output' \ + -o -wholename './.*' \ + -o -wholename '*/vendor/*' \ + \) -prune \ + \) -name '*.go' | sort -u +} +readonly -f os::util::list_go_src_files + +# os::util::list_go_src_dirs lists dirs in origin/ and cmd/ dirs excluding +# doc.go useful for tools that iterate over source to provide vetting or +# linting, or for godep-save etc. +# +# Globals: +# None +# Arguments: +# None +# Returns: +# None +function os::util::list_go_src_dirs() { + os::util::list_go_src_files | cut -d '/' -f 1-2 | grep -v ".go$" | grep -v "^./cmd" | LC_ALL=C sort -u + os::util::list_go_src_files | grep "^./cmd/"| cut -d '/' -f 1-3 | grep -v ".go$" | LC_ALL=C sort -u +} +readonly -f os::util::list_go_src_dirs + +# os::util::list_test_packages_under lists all packages containing Golang test files that we +# want to run as unit tests under the given base dir in the source tree +function os::util::list_test_packages_under() { + local basedir=$* + + # we do not quote ${basedir} to allow for multiple arguments to be passed in as well as to allow for + # arguments that use expansion, e.g. paths containing brace expansion or wildcards + find ${basedir} -not \( \ + \( \ + -path 'vendor' \ + -o -path '*_output' \ + -o -path '*.git' \ + -o -path '*vendor/*' \ + -o -path '*test/*' \ + \) -prune \ + \) -name '*_test.go' | xargs -n1 dirname | sort -u | xargs -n1 printf "${OS_GO_PACKAGE}/%s\n" +} +readonly -f os::util::list_test_packages_under + +# os::util::list_go_deps outputs the list of dependencies for the project. +function os::util::list_go_deps() { + go list -f '{{.ImportPath}}{{.Imports}}' ./pkg/... ./cmd/... | tr '[]' ' ' | + sed -e 's|${OS_GO_PACKAGE}/vendor/||g' +} + +# OS_ALL_IMAGES is the list of images built by os::build::images. +readonly OS_ALL_IMAGES=( + openshift/origin-pod +) + +# os::build::images builds all images in this repo. +function os::build::images() { + tag_prefix="${OS_IMAGE_PREFIX:-"openshift/origin"}" + os::build::image "${tag_prefix}-cluster-machine-approver" . +} diff --git a/hack/lib/init.sh b/hack/lib/init.sh new file mode 100644 index 000000000..26bf38a15 --- /dev/null +++ b/hack/lib/init.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# This script is meant to be the entrypoint for OpenShift Bash scripts to import all of the support +# libraries at once in order to make Bash script preambles as minimal as possible. This script recur- +# sively `source`s *.sh files in this directory tree. As such, no files should be `source`ed outside +# of this script to ensure that we do not attempt to overwrite read-only variables. + +set -o errexit +set -o nounset +set -o pipefail + +OS_SCRIPT_START_TIME="$( date +%s )"; export OS_SCRIPT_START_TIME + +# os::util::absolute_path returns the absolute path to the directory provided +function os::util::absolute_path() { + local relative_path="$1" + local absolute_path + + pushd "${relative_path}" >/dev/null + relative_path="$( pwd )" + if [[ -h "${relative_path}" ]]; then + absolute_path="$( readlink "${relative_path}" )" + else + absolute_path="${relative_path}" + fi + popd >/dev/null + + echo "${absolute_path}" +} +readonly -f os::util::absolute_path + +# find the absolute path to the root of the Origin source tree +init_source="$( dirname "${BASH_SOURCE}" )/../.." +OS_ROOT="$( os::util::absolute_path "${init_source}" )" +export OS_ROOT +cd "${OS_ROOT}" + +for library_file in $( find "${OS_ROOT}/hack/lib" -type f -name '*.sh' -not -path '*/hack/lib/init.sh' ); do + source "${library_file}" +done + +unset library_files library_file init_source + +# all of our Bash scripts need to have the stacktrace +# handler installed to deal with errors +os::log::stacktrace::install + +# All of our Bash scripts need to have access to the +# binaries that we build so we don't have to find +# them before every invocation. +os::util::environment::update_path_var + +if [[ -z "${OS_TMP_ENV_SET-}" ]]; then + os::util::environment::setup_tmpdir_vars "$( basename "$0" ".sh" )" +fi + +# Allow setting $JUNIT_REPORT to toggle output behavior +if [[ -n "${JUNIT_REPORT:-}" ]]; then + export JUNIT_REPORT_OUTPUT="${LOG_DIR}/raw_test_output.log" +fi \ No newline at end of file diff --git a/hack/lib/log/output.sh b/hack/lib/log/output.sh new file mode 100644 index 000000000..9a6f6dd50 --- /dev/null +++ b/hack/lib/log/output.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +# This file contains functions used for writing log messages +# to stdout and stderr from scripts while they run. + +# os::log::info writes the message to stdout. +# +# Arguments: +# - all: message to write +function os::log::info() { + local message; message="$( os::log::internal::prefix_lines "[INFO]" "$*" )" + os::log::internal::to_logfile "${message}" + echo "${message}" +} +readonly -f os::log::info + +# os::log::warning writes the message to stderr. +# A warning indicates something went wrong but +# not so wrong that we cannot recover. +# +# Arguments: +# - all: message to write +function os::log::warning() { + local message; message="$( os::log::internal::prefix_lines "[WARNING]" "$*" )" + os::log::internal::to_logfile "${message}" + os::text::print_yellow "${message}" 1>&2 +} +readonly -f os::log::warning + +# os::log::error writes the message to stderr. +# An error indicates that something went wrong +# and we will most likely fail after this. +# +# Arguments: +# - all: message to write +function os::log::error() { + local message; message="$( os::log::internal::prefix_lines "[ERROR]" "$*" )" + os::log::internal::to_logfile "${message}" + os::text::print_red "${message}" 1>&2 +} +readonly -f os::log::error + +# os::log::fatal writes the message to stderr and +# returns a non-zero code to force a process exit. +# A fatal error indicates that there is no chance +# of recovery. +# +# Arguments: +# - all: message to write +function os::log::fatal() { + local message; message="$( os::log::internal::prefix_lines "[FATAL]" "$*" )" + os::log::internal::to_logfile "${message}" + os::text::print_red "${message}" 1>&2 + exit 1 +} +readonly -f os::log::fatal + +# os::log::debug writes the message to stderr if +# the ${OS_DEBUG} variable is set. +# +# Globals: +# - OS_DEBUG +# Arguments: +# - all: message to write +function os::log::debug() { + local message; message="$( os::log::internal::prefix_lines "[DEBUG]" "$*" )" + os::log::internal::to_logfile "${message}" + if [[ -n "${OS_DEBUG:-}" ]]; then + os::text::print_blue "${message}" 1>&2 + fi +} +readonly -f os::log::debug + +# os::log::internal::to_logfile makes a best-effort +# attempt to write the message to the script logfile +# +# Globals: +# - LOG_DIR +# Arguments: +# - all: message to write +function os::log::internal::to_logfile() { + if [[ -n "${LOG_DIR:-}" && -d "${LOG_DIR-}" ]]; then + echo "$*" >>"${LOG_DIR}/scripts.log" + fi +} + +# os::log::internal::prefix_lines prints out the +# original content with the given prefix at the +# start of every line. +# +# Arguments: +# - 1: prefix for lines +# - 2: content to prefix +function os::log::internal::prefix_lines() { + local prefix="$1" + local content="$2" + + local old_ifs="${IFS}" + IFS=$'\n' + for line in ${content}; do + echo "${prefix} ${line}" + done + IFS="${old_ifs}" +} \ No newline at end of file diff --git a/hack/lib/log/stacktrace.sh b/hack/lib/log/stacktrace.sh new file mode 100644 index 000000000..6f79dc338 --- /dev/null +++ b/hack/lib/log/stacktrace.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# +# This library contains an implementation of a stack trace for Bash scripts. + +# os::log::stacktrace::install installs the stacktrace as a handler for the ERR signal if one +# has not already been installed and sets `set -o errtrace` in order to propagate the handler +# If the ERR trap is not initialized, installing this plugin will initialize it. +# +# Globals: +# None +# Arguments: +# None +# Returns: +# - export OS_USE_STACKTRACE +function os::log::stacktrace::install() { + # setting 'errtrace' propagates our ERR handler to functions, expansions and subshells + set -o errtrace + + # OS_USE_STACKTRACE is read by os::util::trap at runtime to request a stacktrace + export OS_USE_STACKTRACE=true + + os::util::trap::init_err +} +readonly -f os::log::stacktrace::install + +# os::log::stacktrace::print prints the stacktrace and exits with the return code from the script that +# called for a stack trace. This function will always return 0 if it is not handling the signal, and if it +# is handling the signal, this function will always `exit`, not return, the return code it receives as +# its first argument. +# +# Globals: +# - BASH_SOURCE +# - BASH_LINENO +# - FUNCNAME +# Arguments: +# - 1: the return code of the command in the script that generated the ERR signal +# - 2: the last command that ran before handlers were invoked +# - 3: whether or not `set -o errexit` was set in the script that generated the ERR signal +# Returns: +# None +function os::log::stacktrace::print() { + local return_code=$1 + local last_command=$2 + local errexit_set=${3:-} + + if [[ "${return_code}" = "0" ]]; then + # we're not supposed to respond when no error has occurred + return 0 + fi + + if [[ -z "${errexit_set}" ]]; then + # if errexit wasn't set in the shell when the ERR signal was issued, then we can ignore the signal + # as this is not cause for failure + return 0 + fi + + # dump the entire stack for debugging purposes + os::log::debug "$( os::util::repository_relative_path "${BASH_SOURCE[0]}:${LINENO}: ${BASH_COMMAND}" )" + for (( i = 0; i < ${#BASH_LINENO[@]}; i++ )); do + os::log::debug "$( os::util::repository_relative_path "${BASH_SOURCE[$i+1]:-"$( os::util::repository_relative_path "$0" )"}" ):${BASH_LINENO[$i]}: ${FUNCNAME[$i]}" + done + + # iterate backwards through the stack until we leave library files, so we can be sure we start logging + # actual script code and not this handler's call + local stack_begin_index + for (( stack_begin_index = 0; stack_begin_index < ${#BASH_SOURCE[@]}; stack_begin_index++ )); do + if [[ ! "${BASH_SOURCE[${stack_begin_index}]}" =~ hack/lib/(log/stacktrace|util/trap)\.sh ]]; then + break + fi + done + + local preamble_finished + local stack_index=1 + local i + for (( i = stack_begin_index; i < ${#BASH_SOURCE[@]}; i++ )); do + local bash_source + bash_source="$( os::util::repository_relative_path "${BASH_SOURCE[$i]}" )" + if [[ -z "${preamble_finished:-}" ]]; then + preamble_finished=true + os::log::error "PID ${BASHPID:-$$}: ${bash_source}:${BASH_LINENO[$i-1]}: \`${last_command}\` exited with status ${return_code}." >&2 + os::log::info $'\t\t'"Stack Trace: " >&2 + os::log::info $'\t\t'" ${stack_index}: ${bash_source}:${BASH_LINENO[$i-1]}: \`${last_command}\`" >&2 + else + os::log::info $'\t\t'" ${stack_index}: ${bash_source}:${BASH_LINENO[$i-1]}: ${FUNCNAME[$i-1]}" >&2 + fi + stack_index=$(( stack_index + 1 )) + done + + # we know we're the privileged handler in this chain, so we can safely exit the shell without + # starving another handler of the privilege of reacting to this signal + os::log::info " Exiting with code ${return_code}." >&2 + exit "${return_code}" +} +readonly -f os::log::stacktrace::print diff --git a/hack/lib/log/system.sh b/hack/lib/log/system.sh new file mode 100644 index 000000000..a40556443 --- /dev/null +++ b/hack/lib/log/system.sh @@ -0,0 +1,243 @@ +#!/bin/bash +# +# This library holds all of the system logging functions for OpenShift bash scripts. + +# os::log::system::install_cleanup installs os::log::system::clean_up as a trap on exit. +# If any traps are currently set for these signals, os::log::system::clean_up is prefixed. +# +# Globals: +# None +# Arguments: +# None +# Returns: +# None +function os::log::system::install_cleanup() { + trap "os::log::system::clean_up; $(trap -p EXIT | awk -F"'" '{print $2}')" EXIT +} +readonly -f os::log::system::install_cleanup + +# os::log::system::clean_up should be trapped so that it can stop the logging utility once the script that +# installed it is finished. +# This function stops logging and generates plots of data for easy consumption. +# +# Globals: +# - LOG_DIR +# - LOGGER_PID +# - SAR_LOGFILE +# Arguments: +# None +# Returns: +# None +function os::log::system::clean_up() { + local return_code=$? + + # we don't want failures in this logger to + set +o errexit + + if jobs -pr | grep -q "${LOGGER_PID}"; then + kill -SIGTERM "${LOGGER_PID}" + # give logger ten seconds to gracefully exit before killing it + for (( i = 0; i < 10; i++ )); do + if ! jobs -pr | grep -q "${LOGGER_PID}"; then + # the logger has shutdown, we don't need to wait on it any longer + break + fi + done + + if jobs -pr | grep -q "${LOGGER_PID}"; then + # the logger has not shutdown, so kill it + kill -SIGKILL "${LOGGER_PID}" + fi + fi + + if ! which sadf >/dev/null 2>&1; then + os::log::warning "System logger data could not be unpacked and graphed, 'sadf' binary not found in this environment." + return 0 + fi + + if [[ ! -s "${SAR_LOGFILE:-}" ]]; then + os::log::warning "No system logger data could be found, log file missing." + return 0 + fi + + local log_subset_flags=( "-b" "-B" "-u ALL" "-q" "-r" ) + + local log_subset_names=( "iops" "paging" "cpu" "queue" "memory" ) + + local log_subset_file + local i + for (( i = 0; i < "${#log_subset_flags[@]}"; i++ )); do + log_subset_file="${LOG_DIR}/${log_subset_names[$i]}.txt" + # use sadf utility to extract data into easily-parseable format + sadf -d "${SAR_LOGFILE}" -- ${log_subset_flags[$i]} > "${log_subset_file}" + + local ignored_columns="hostname,interval," + + # special cases for special output from SAR, because the tool often gives us columns full of baloney + if [[ "${log_subset_names[$i]}" == "cpu" ]]; then + ignored_columns="${ignored_columns}CPU," + fi + + os::log::system::internal::prune_datafile "${log_subset_file}" "${ignored_columns}" + os::log::system::internal::plot "${log_subset_file}" + done + + # remove the `sar` log file for space constraints + rm -f "${SAR_LOGFILE}" + + return "${return_code}" +} +readonly -f os::log::system::clean_up + +# os::log::system::internal::prune_datafile removes the given columns from a datafile created by 'sadf -d' +# +# Globals: +# None +# Arguments: +# - 1: datafile +# - 2: comma-delimited columns to remove, with trailing comma +# Returns: +# None +function os::log::system::internal::prune_datafile() { + local datafile=$1 + local column_names=$2 + + if [[ "${#column_names}" -eq 0 ]]; then + return 0 + fi + + local columns_in_order + columns_in_order=( $( head -n 1 "${datafile}" | sed 's/^# //g' | tr ';' ' ' ) ) + + local columns_to_keep + local i + for (( i = 0; i < "${#columns_in_order[@]}"; i++ )); do + if ! echo "${column_names}" | grep -q "${columns_in_order[$i]},"; then + # this is a column we need to keep, adding one as 'cut' is 1-indexed + columns_to_keep+=( "$(( i + 1 ))" ) + fi + done + + # for the proper flag format for 'cut', we join the list delimiting with commas + columns_to_keep="$( IFS=','; echo "${columns_to_keep[*]}" )" + + cut --delimiter=';' -f"${columns_to_keep}" "${datafile}" > "${datafile}.tmp" + sed -i '1s/^/# /' "${datafile}.tmp" + mv "${datafile}.tmp" "${datafile}" +} +readonly -f os::log::system::internal::prune_datafile + +# os::log::system::internal::plot uses gnuplot to make a plot of some data across time points. This function is intended to be used +# on the output of a 'sar -f' read of a sar binary file. Plots will be made of all columns and stacked on each other with one x axis. +# This function needs the non-data columns of the file to be prefixed with comments. +# +# Globals: +# - LOG_DIR +# Arguments: +# - 1: data file +# Returns: +# None +function os::log::system::internal::plot() { + local datafile=$1 + local plotname + plotname="$(basename "${datafile}" .txt)" + + # we are expecting the output of a 'sadf -d' read, so the headers will be on the first line of the file + local headers + headers=( $( head -n 1 "${datafile}" | sed 's/^# //g' | tr ';' ' ' ) ) + + local records + local width + records="$(( $( wc -l < "${datafile}" ) - 1 ))" # one of these lines will be the header comment + if [[ "${records}" -gt 90 ]]; then + width="$(echo "8.5 + ${records}*0.025" | bc )" + else + width="8.5" + fi + + local gnuplot_directive=( "set terminal pdf size ${width}in,$(( 2 * (${#headers[@]} - 1) ))in" \ + "set output \"${LOG_DIR}/${plotname}.pdf\"" \ + "set datafile separator \";\"" \ + "set xdata time" \ + "set timefmt '%Y-%m-%d %H:%M:%S UTC'" \ + "set tmargin 1" \ + "set bmargin 1" \ + "set lmargin 20" \ + "set rmargin 20" \ + "set multiplot layout ${#headers[@]},1 title \"\n${plotname}\n\"" \ + "unset title" ) + + local i + for (( i = 1; i < "${#headers[@]}"; i++ )); do + local header + header="${headers[$i]}" + + if (( i == ${#headers[@]} - 1 )); then + # we need x-tick labels on the bottom plot + gnuplot_directive+=( "set xtics format '%H:%M:%S' rotate by -90" ) + else + gnuplot_directive+=( "set format x ''" ) + fi + + gnuplot_directive+=( "plot \"${datafile}\" using 1:$(( i + 1 )) title \"${header}\" with lines" ) + done + + # concatenate the array with newlines to get the final directive to send to gnuplot + gnuplot_directive="$( IFS=$'\n'; echo "${gnuplot_directive[*]}" )" + + { + printf '$ gnuplot <<< %s\n' "${gnuplot_directive}" + gnuplot <<< "${gnuplot_directive}" 2>&1 + printf '\n\n' + } >> "${LOG_DIR}/gnuplot.log" + + os::log::debug "Stacked plot for log subset \"${plotname}\" written to ${LOG_DIR}/${plotname}.pdf" +} +readonly -f os::log::system::internal::plot + +# os::log::system::start installs the system logger and begins logging +# +# Globals: +# - LOG_DIR +# Arguments: +# None +# Returns: +# - export LOGGER_PID +# - export SAR_LOGFILE +function os::log::system::start() { + if ! which sar >/dev/null 2>&1; then + os::log::debug "System logger could not be started, 'sar' binary not found in this environment." + return 0 + fi + + readonly SAR_LOGFILE="${LOG_DIR}/sar.log" + export SAR_LOGFILE + + os::log::system::internal::run "${SAR_LOGFILE}" "${LOG_DIR}/sar_stderr.log" + + os::log::system::install_cleanup +} +readonly -f os::log::system::start + +# os::log::system::internal::run runs the system logger in the background. +# 'sar' is configured to run once a second for 24 hours, so the cleanup trap should be installed to ensure that +# the process is killed once the parent script is finished. +# +# Globals: +# None +# Arguments: +# - 1: file to log binary outut to +# - 2: file to log stderr of the logger to +# Returns: +# None +function os::log::system::internal::run() { + local binary_logfile=$1 + local stderr_logfile=$2 + + sar -A -o "${binary_logfile}" 1 86400 1>/dev/null 2>"${stderr_logfile}" & + + LOGGER_PID=$! + readonly LOGGER_PID + export LOGGER_PID +} +readonly -f os::log::system::internal::run diff --git a/hack/lib/test/junit.sh b/hack/lib/test/junit.sh new file mode 100644 index 000000000..45cdf4b91 --- /dev/null +++ b/hack/lib/test/junit.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# This utility file contains functions that format test output to be parsed into jUnit XML + +# os::test::junit::declare_suite_start prints a message declaring the start of a test suite +# Any number of suites can be in flight at any time, so there is no failure condition for this +# script based on the number of suites in flight. +# +# Globals: +# - JUNIT_REPORT_OUTPUT +# - NUM_OS_JUNIT_SUITES_IN_FLIGHT +# Arguments: +# - 1: the suite name that is starting +# Returns: +# - increment NUM_OS_JUNIT_SUITES_IN_FLIGHT +function os::test::junit::declare_suite_start() { + local suite_name=$1 + local num_suites=${NUM_OS_JUNIT_SUITES_IN_FLIGHT:-0} + + echo "=== BEGIN TEST SUITE github.com/openshift/origin/test/${suite_name} ===" >> "${JUNIT_REPORT_OUTPUT:-/dev/null}" + NUM_OS_JUNIT_SUITES_IN_FLIGHT=$(( ${num_suites} + 1 )) + export NUM_OS_JUNIT_SUITES_IN_FLIGHT +} +readonly -f os::test::junit::declare_suite_start + +# os::test::junit::declare_suite_end prints a message declaring the end of a test suite +# If there aren't any suites in flight, this function will fail. +# +# Globals: +# - JUNIT_REPORT_OUTPUT +# - NUM_OS_JUNIT_SUITES_IN_FLIGHT +# Arguments: +# - 1: the suite name that is starting +# Returns: +# - export/decrement NUM_OS_JUNIT_SUITES_IN_FLIGHT +function os::test::junit::declare_suite_end() { + local num_suites=${NUM_OS_JUNIT_SUITES_IN_FLIGHT:-0} + if [[ "${num_suites}" -lt "1" ]]; then + # we can't end a suite if none have been started yet + echo "[ERROR] jUnit suite marker could not be placed, expected suites in flight, got ${num_suites}" + return 1 + fi + + echo "=== END TEST SUITE ===" >> "${JUNIT_REPORT_OUTPUT:-/dev/null}" + NUM_OS_JUNIT_SUITES_IN_FLIGHT=$(( ${num_suites} - 1 )) + export NUM_OS_JUNIT_SUITES_IN_FLIGHT +} +readonly -f os::test::junit::declare_suite_end + +# os::test::junit::declare_test_start prints a message declaring the start of a test case +# If there is already a test marked as being in flight, this function will fail. +# +# Globals: +# - JUNIT_REPORT_OUTPUT +# - NUM_OS_JUNIT_TESTS_IN_FLIGHT +# Arguments: +# None +# Returns: +# - increment NUM_OS_JUNIT_TESTS_IN_FLIGHT +function os::test::junit::declare_test_start() { + local num_tests=${NUM_OS_JUNIT_TESTS_IN_FLIGHT:-0} + if [[ "${num_tests}" -ne "0" ]]; then + # someone's declaring the starting of a test when a test is already in flight + echo "[ERROR] jUnit test marker could not be placed, expected no tests in flight, got ${num_tests}" + return 1 + fi + + local num_suites=${NUM_OS_JUNIT_SUITES_IN_FLIGHT:-0} + if [[ "${num_suites}" -lt "1" ]]; then + # we can't end a test if no suites are in flight + echo "[ERROR] jUnit test marker could not be placed, expected suites in flight, got ${num_suites}" + return 1 + fi + + echo "=== BEGIN TEST CASE ===" >> "${JUNIT_REPORT_OUTPUT:-/dev/null}" + NUM_OS_JUNIT_TESTS_IN_FLIGHT=$(( ${num_tests} + 1 )) + export NUM_OS_JUNIT_TESTS_IN_FLIGHT +} +readonly -f os::test::junit::declare_test_start + +# os::test::junit::declare_test_end prints a message declaring the end of a test case +# If there is no test marked as being in flight, this function will fail. +# +# Globals: +# - JUNIT_REPORT_OUTPUT +# - NUM_OS_JUNIT_TESTS_IN_FLIGHT +# Arguments: +# None +# Returns: +# - decrement NUM_OS_JUNIT_TESTS_IN_FLIGHT +function os::test::junit::declare_test_end() { + local num_tests=${NUM_OS_JUNIT_TESTS_IN_FLIGHT:-0} + if [[ "${num_tests}" -ne "1" ]]; then + # someone's declaring the end of a test when a test is not in flight + echo "[ERROR] jUnit test marker could not be placed, expected one test in flight, got ${num_tests}" + return 1 + fi + + echo "=== END TEST CASE ===" >> "${JUNIT_REPORT_OUTPUT:-/dev/null}" + NUM_OS_JUNIT_TESTS_IN_FLIGHT=$(( ${num_tests} - 1 )) + export NUM_OS_JUNIT_TESTS_IN_FLIGHT +} +readonly -f os::test::junit::declare_test_end + +# os::test::junit::check_test_counters checks that we do not have any test suites or test cases in flight +# This function should be called at the very end of any test script using jUnit markers to make sure no error in +# marking has occurred. +# +# Globals: +# - NUM_OS_JUNIT_SUITES_IN_FLIGHT +# - NUM_OS_JUNIT_TESTS_IN_FLIGHT +# Arguments: +# None +# Returns: +# None +function os::test::junit::check_test_counters() { + if [[ "${NUM_OS_JUNIT_SUITES_IN_FLIGHT-}" -ne "0" ]]; then + echo "[ERROR] Expected no test suites to be marked as in-flight at the end of testing, got ${NUM_OS_JUNIT_SUITES_IN_FLIGHT-}" + return 1 + elif [[ "${NUM_OS_JUNIT_TESTS_IN_FLIGHT-}" -ne "0" ]]; then + echo "[ERROR] Expected no test cases to be marked as in-flight at the end of testing, got ${NUM_OS_JUNIT_TESTS_IN_FLIGHT-}" + return 1 + fi +} +readonly -f os::test::junit::check_test_counters + +# os::test::junit::reconcile_output appends the necessary suite and test end statements to the jUnit output file +# in order to ensure that the file is in a consistent state to allow for parsing +# +# Globals: +# - NUM_OS_JUNIT_SUITES_IN_FLIGHT +# - NUM_OS_JUNIT_TESTS_IN_FLIGHT +# Arguments: +# None +# Returns: +# None +function os::test::junit::reconcile_output() { + if [[ "${NUM_OS_JUNIT_TESTS_IN_FLIGHT:-0}" = "1" ]]; then + os::test::junit::declare_test_end + fi + + for (( i = 0; i < ${NUM_OS_JUNIT_SUITES_IN_FLIGHT:-0}; i++ )); do + os::test::junit::declare_suite_end + done +} +readonly -f os::test::junit::reconcile_output diff --git a/hack/lib/util/ensure.sh b/hack/lib/util/ensure.sh new file mode 100644 index 000000000..f8c3d0473 --- /dev/null +++ b/hack/lib/util/ensure.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# This script contains helper functions for ensuring that dependencies +# exist on a host system that are required to run Origin scripts. + +# os::util::ensure::system_binary_exists ensures that the +# given binary exists on the system in the $PATH. +# +# Globals: +# None +# Arguments: +# - 1: binary to search for +# Returns: +# None +function os::util::ensure::system_binary_exists() { + local binary="$1" + +if ! os::util::find::system_binary "${binary}" >/dev/null 2>&1; then + os::log::fatal "Required \`${binary}\` binary was not found in \$PATH." + fi +} +readonly -f os::util::ensure::system_binary_exists + +# os::util::ensure::built_binary_exists ensures that the +# given binary exists on the system in the local output +# directory for the current platform. If it doesn't, we +# will attempt to build it if we can determine the correct +# hack/build-go.sh target for the binary. +# +# This function will attempt to determine the correct +# hack/build-go.sh target for the binary, but may not +# be able to do so if the target doesn't live under +# cmd/ or tools/. In that case, one should be given. +# +# Globals: +# - OS_ROOT +# Arguments: +# - 1: binary to search for +# - 2: optional build target for this binary +# Returns: +# None +function os::util::ensure::built_binary_exists() { + local binary="$1" + local target="${2:-}" + + if ! os::util::find::built_binary "${binary}" >/dev/null 2>&1; then + if [[ -z "${target}" ]]; then + if [[ -d "${OS_ROOT}/cmd/${binary}" ]]; then + target="cmd/${binary}" + elif [[ -d "${OS_ROOT}/tools/${binary}" ]]; then + target="tools/${binary}" + elif [[ -d "${OS_ROOT}/tools/rebasehelpers/${binary}" ]]; then + target="tools/rebasehelpers/${binary}" + fi + fi + + if [[ -n "${target}" ]]; then + os::log::info "No compiled \`${binary}\` binary was found. Attempting to build one using: + $ hack/build-go.sh ${target}" + "${OS_ROOT}/hack/build-go.sh" "${target}" + else + os::log::fatal "No compiled \`${binary}\` binary was found and no build target could be determined. +Provide the binary and try running $0 again." + fi + fi +} +readonly -f os::util::ensure::built_binary_exists + +# os::util::ensure::gopath_binary_exists ensures that the +# given binary exists on the system in $GOPATH. If it +# doesn't, we will attempt to build it if we can determine +# the correct install path for the binary. +# +# Globals: +# - GOPATH +# Arguments: +# - 1: binary to search for +# - 2: [optional] path to install from +# Returns: +# None +function os::util::ensure::gopath_binary_exists() { + local binary="$1" + local install_path="${2:-}" + + if ! os::util::find::gopath_binary "${binary}" >/dev/null 2>&1; then + if [[ -n "${install_path:-}" ]]; then + os::log::info "No installed \`${binary}\` was found in \$GOPATH. Attempting to install using: + $ go get ${install_path}" + go get "${install_path}" + else + os::log::fatal "Required \`${binary}\` binary was not found in \$GOPATH." + fi + fi +} +readonly -f os::util::ensure::gopath_binary_exists diff --git a/hack/lib/util/environment.sh b/hack/lib/util/environment.sh new file mode 100644 index 000000000..6e0ffbf62 --- /dev/null +++ b/hack/lib/util/environment.sh @@ -0,0 +1,289 @@ +#!/bin/bash + +# This script holds library functions for setting up the shell environment for OpenShift scripts + +# os::util::environment::use_sudo updates $USE_SUDO to be 'true', so that later scripts choosing between +# execution using 'sudo' and execution without it chose to use 'sudo' +# +# Globals: +# None +# Arguments: +# None +# Returns: +# - export USE_SUDO +function os::util::environment::use_sudo() { + USE_SUDO=true + export USE_SUDO +} +readonly -f os::util::environment::use_sudo + +# os::util::environment::setup_time_vars sets up environment variables that describe durations of time +# These variables can be used to specify times for other utility functions +# +# Globals: +# None +# Arguments: +# None +# Returns: +# - export TIME_MS +# - export TIME_SEC +# - export TIME_MIN +function os::util::environment::setup_time_vars() { + TIME_MS=1 + export TIME_MS + TIME_SEC="$(( 1000 * ${TIME_MS} ))" + export TIME_SEC + TIME_MIN="$(( 60 * ${TIME_SEC} ))" + export TIME_MIN +} +readonly -f os::util::environment::setup_time_vars + +# os::util::environment::setup_all_server_vars sets up all environment variables necessary to configure and start an OpenShift server +# +# Globals: +# - OS_ROOT +# - PATH +# - TMPDIR +# - LOG_DIR +# - ARTIFACT_DIR +# - KUBELET_SCHEME +# - KUBELET_BIND_HOST +# - KUBELET_HOST +# - KUBELET_PORT +# - BASETMPDIR +# - ETCD_PORT +# - ETCD_PEER_PORT +# - API_BIND_HOST +# - API_HOST +# - API_PORT +# - API_SCHEME +# - PUBLIC_MASTER_HOST +# - USE_IMAGES +# Arguments: +# - 1: the path under the root temporary directory for OpenShift where these subdirectories should be made +# Returns: +# - export PATH +# - export BASETMPDIR +# - export LOG_DIR +# - export VOLUME_DIR +# - export ARTIFACT_DIR +# - export FAKE_HOME_DIR +# - export KUBELET_SCHEME +# - export KUBELET_BIND_HOST +# - export KUBELET_HOST +# - export KUBELET_PORT +# - export ETCD_PORT +# - export ETCD_PEER_PORT +# - export ETCD_DATA_DIR +# - export API_BIND_HOST +# - export API_HOST +# - export API_PORT +# - export API_SCHEME +# - export SERVER_CONFIG_DIR +# - export MASTER_CONFIG_DIR +# - export NODE_CONFIG_DIR +# - export USE_IMAGES +# - export TAG +function os::util::environment::setup_all_server_vars() { + os::util::environment::setup_kubelet_vars + os::util::environment::setup_etcd_vars + os::util::environment::setup_server_vars + os::util::environment::setup_images_vars +} +readonly -f os::util::environment::setup_all_server_vars + +# os::util::environment::update_path_var updates $PATH so that OpenShift binaries are available +# +# Globals: +# - OS_ROOT +# - PATH +# Arguments: +# None +# Returns: +# - export PATH +function os::util::environment::update_path_var() { + local prefix + if os::util::find::system_binary 'go' >/dev/null 2>&1; then + prefix+="${OS_OUTPUT_BINPATH}/$(os::build::host_platform):" + fi + if [[ -n "${GOPATH:-}" ]]; then + prefix+="${GOPATH}/bin:" + fi + + PATH="${prefix:-}${PATH}" + export PATH +} +readonly -f os::util::environment::update_path_var + +# os::util::environment::setup_tmpdir_vars sets up temporary directory path variables +# +# Globals: +# - TMPDIR +# Arguments: +# - 1: the path under the root temporary directory for OpenShift where these subdirectories should be made +# Returns: +# - export BASETMPDIR +# - export BASEOUTDIR +# - export LOG_DIR +# - export VOLUME_DIR +# - export ARTIFACT_DIR +# - export FAKE_HOME_DIR +# - export OS_TMP_ENV_SET +function os::util::environment::setup_tmpdir_vars() { + local sub_dir=$1 + + BASETMPDIR="${TMPDIR:-/tmp}/openshift/${sub_dir}" + export BASETMPDIR + VOLUME_DIR="${BASETMPDIR}/volumes" + export VOLUME_DIR + + BASEOUTDIR="${OS_OUTPUT_SCRIPTPATH}/${sub_dir}" + export BASEOUTDIR + LOG_DIR="${LOG_DIR:-${BASEOUTDIR}/logs}" + export LOG_DIR + ARTIFACT_DIR="${ARTIFACT_DIR:-${BASEOUTDIR}/artifacts}" + export ARTIFACT_DIR + FAKE_HOME_DIR="${BASEOUTDIR}/openshift.local.home" + export FAKE_HOME_DIR + + mkdir -p "${LOG_DIR}" "${VOLUME_DIR}" "${ARTIFACT_DIR}" "${FAKE_HOME_DIR}" + + export OS_TMP_ENV_SET="${sub_dir}" +} +readonly -f os::util::environment::setup_tmpdir_vars + +# os::util::environment::setup_kubelet_vars sets up environment variables necessary for interacting with the kubelet +# +# Globals: +# - KUBELET_SCHEME +# - KUBELET_BIND_HOST +# - KUBELET_HOST +# - KUBELET_PORT +# Arguments: +# None +# Returns: +# - export KUBELET_SCHEME +# - export KUBELET_BIND_HOST +# - export KUBELET_HOST +# - export KUBELET_PORT +function os::util::environment::setup_kubelet_vars() { + KUBELET_SCHEME="${KUBELET_SCHEME:-https}" + export KUBELET_SCHEME + KUBELET_BIND_HOST="${KUBELET_BIND_HOST:-$(openshift start --print-ip || echo "127.0.0.1")}" + export KUBELET_BIND_HOST + KUBELET_HOST="${KUBELET_HOST:-${KUBELET_BIND_HOST}}" + export KUBELET_HOST + KUBELET_PORT="${KUBELET_PORT:-10250}" + export KUBELET_PORT +} +readonly -f os::util::environment::setup_kubelet_vars + +# os::util::environment::setup_etcd_vars sets up environment variables necessary for interacting with etcd +# +# Globals: +# - BASETMPDIR +# - ETCD_HOST +# - ETCD_PORT +# - ETCD_PEER_PORT +# Arguments: +# None +# Returns: +# - export ETCD_HOST +# - export ETCD_PORT +# - export ETCD_PEER_PORT +# - export ETCD_DATA_DIR +function os::util::environment::setup_etcd_vars() { + ETCD_HOST="${ETCD_HOST:-127.0.0.1}" + export ETCD_HOST + ETCD_PORT="${ETCD_PORT:-4001}" + export ETCD_PORT + ETCD_PEER_PORT="${ETCD_PEER_PORT:-7001}" + export ETCD_PEER_PORT + + ETCD_DATA_DIR="${BASETMPDIR}/etcd" + export ETCD_DATA_DIR + + mkdir -p "${ETCD_DATA_DIR}" +} +readonly -f os::util::environment::setup_etcd_vars + +# os::util::environment::setup_server_vars sets up environment variables necessary for interacting with the server +# +# Globals: +# - BASETMPDIR +# - KUBELET_HOST +# - API_BIND_HOST +# - API_HOST +# - API_PORT +# - API_SCHEME +# - PUBLIC_MASTER_HOST +# Arguments: +# None +# Returns: +# - export API_BIND_HOST +# - export API_HOST +# - export API_PORT +# - export API_SCHEME +# - export SERVER_CONFIG_DIR +# - export MASTER_CONFIG_DIR +# - export NODE_CONFIG_DIR +function os::util::environment::setup_server_vars() { + # turn on cache mutation detector every time we start a server + KUBE_CACHE_MUTATION_DETECTOR="${KUBE_CACHE_MUTATION_DETECTOR:-true}" + export KUBE_CACHE_MUTATION_DETECTOR + + API_BIND_HOST="${API_BIND_HOST:-$(openshift start --print-ip || echo "127.0.0.1")}" + export API_BIND_HOST + API_HOST="${API_HOST:-${API_BIND_HOST}}" + export API_HOST + API_PORT="${API_PORT:-8443}" + export API_PORT + API_SCHEME="${API_SCHEME:-https}" + export API_SCHEME + + MASTER_ADDR="${API_SCHEME}://${API_HOST}:${API_PORT}" + export MASTER_ADDR + PUBLIC_MASTER_HOST="${PUBLIC_MASTER_HOST:-${API_HOST}}" + export PUBLIC_MASTER_HOST + + SERVER_CONFIG_DIR="${BASETMPDIR}/openshift.local.config" + export SERVER_CONFIG_DIR + MASTER_CONFIG_DIR="${SERVER_CONFIG_DIR}/master" + export MASTER_CONFIG_DIR + NODE_CONFIG_DIR="${SERVER_CONFIG_DIR}/node-${KUBELET_HOST}" + export NODE_CONFIG_DIR + + mkdir -p "${SERVER_CONFIG_DIR}" "${MASTER_CONFIG_DIR}" "${NODE_CONFIG_DIR}" +} +readonly -f os::util::environment::setup_server_vars + +# os::util::environment::setup_images_vars sets up environment variables necessary for interacting with release images +# +# Globals: +# - OS_ROOT +# - USE_IMAGES +# Arguments: +# None +# Returns: +# - export USE_IMAGES +# - export TAG +# - export MAX_IMAGES_BULK_IMPORTED_PER_REPOSITORY +function os::util::environment::setup_images_vars() { + # Use either the latest release built images, or latest. + IMAGE_PREFIX="${OS_IMAGE_PREFIX:-"openshift/origin"}" + if [[ -z "${USE_IMAGES-}" ]]; then + TAG='latest' + export TAG + USE_IMAGES="${IMAGE_PREFIX}-\${component}:latest" + export USE_IMAGES + + if [[ -e "${OS_ROOT}/_output/local/releases/.commit" ]]; then + TAG="$(cat "${OS_ROOT}/_output/local/releases/.commit")" + export TAG + USE_IMAGES="${IMAGE_PREFIX}-\${component}:${TAG}" + export USE_IMAGES + fi + fi + export MAX_IMAGES_BULK_IMPORTED_PER_REPOSITORY="${MAX_IMAGES_BULK_IMPORTED_PER_REPOSITORY:-3}" +} +readonly -f os::util::environment::setup_images_vars diff --git a/hack/lib/util/find.sh b/hack/lib/util/find.sh new file mode 100644 index 000000000..19aa30981 --- /dev/null +++ b/hack/lib/util/find.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# This script contains helper functions for finding components +# in the Origin repository or on the host machine running scripts. + +# os::util::find::system_binary determines the absolute path to a +# system binary, if it exists. +# +# Globals: +# None +# Arguments: +# - 1: binary name +# Returns: +# - location of the binary +function os::util::find::system_binary() { + local binary_name="$1" + + command -v "${binary_name}" +} +readonly -f os::util::find::system_binary + +# os::util::find::built_binary determines the absolute path to a +# built binary for the current platform, if it exists. +# +# Globals: +# - OS_OUTPUT_BINPATH +# Arguments: +# - 1: binary name +# Returns: +# - location of the binary +function os::util::find::built_binary() { + local binary_name="$1" + + local binary_path; binary_path="${OS_OUTPUT_BINPATH}/$( os::build::host_platform )/${binary_name}" + # we need to check that the path leads to a file + # as directories also have the executable bit set + if [[ -f "${binary_path}" && -x "${binary_path}" ]]; then + echo "${binary_path}" + return 0 + else + return 1 + fi +} +readonly -f os::util::find::built_binary + +# os::util::find::gopath_binary determines the absolute path to a +# binary installed through the go toolchain, if it exists. +# +# Globals: +# - GOPATH +# Arguments: +# - 1: binary name +# Returns: +# - location of the binary +function os::util::find::gopath_binary() { + local binary_name="$1" + + local old_ifs="${IFS}" + IFS=":" + for part in ${GOPATH}; do + local binary_path="${part}/bin/${binary_name}" + # we need to check that the path leads to a file + # as directories also have the executable bit set + if [[ -f "${binary_path}" && -x "${binary_path}" ]]; then + echo "${binary_path}" + IFS="${old_ifs}" + return 0 + fi + done + IFS="${old_ifs}" + return 1 +} +readonly -f os::util::find::gopath_binary \ No newline at end of file diff --git a/hack/lib/util/golang.sh b/hack/lib/util/golang.sh new file mode 100644 index 000000000..2ae80ce24 --- /dev/null +++ b/hack/lib/util/golang.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# +# This library holds golang related utility functions. diff --git a/hack/lib/util/misc.sh b/hack/lib/util/misc.sh new file mode 100644 index 000000000..7a1406f42 --- /dev/null +++ b/hack/lib/util/misc.sh @@ -0,0 +1,147 @@ +#!/bin/bash +# +# This library holds miscellaneous utility functions. If there begin to be groups of functions in this +# file that share intent or are thematically similar, they should be split into their own files. + +# os::util::describe_return_code describes an exit code +# +# Globals: +# - OS_SCRIPT_START_TIME +# Arguments: +# - 1: exit code to describe +# Returns: +# None +function os::util::describe_return_code() { + local return_code=$1 + local message="$( os::util::repository_relative_path $0 ) exited with code ${return_code} " + + if [[ -n "${OS_SCRIPT_START_TIME:-}" ]]; then + local end_time + end_time="$(date +%s)" + local elapsed_time + elapsed_time="$(( end_time - OS_SCRIPT_START_TIME ))" + local formatted_time + formatted_time="$( os::util::format_seconds "${elapsed_time}" )" + message+="after ${formatted_time}" + fi + + if [[ "${return_code}" = "0" ]]; then + os::log::info "${message}" + else + os::log::error "${message}" + fi +} +readonly -f os::util::describe_return_code + +# os::util::install_describe_return_code installs the return code describer for the EXIT trap +# If the EXIT trap is not initialized, installing this plugin will initialize it. +# +# Globals: +# None +# Arguments: +# None +# Returns: +# - export OS_DESCRIBE_RETURN_CODE +# - export OS_SCRIPT_START_TIME +function os::util::install_describe_return_code() { + export OS_DESCRIBE_RETURN_CODE="true" + OS_SCRIPT_START_TIME="$( date +%s )"; export OS_SCRIPT_START_TIME + os::util::trap::init_exit +} +readonly -f os::util::install_describe_return_code + +# OS_ORIGINAL_WD is the original working directory the script sourcing this utility file was called +# from. This is an important directory as if $0 is a relative path, we cannot use the following path +# utility without knowing from where $0 is relative. +if [[ -z "${OS_ORIGINAL_WD:-}" ]]; then + # since this could be sourced in a context where the utilities are already loaded, + # we want to ensure that this is re-entrant, so we only set $OS_ORIGINAL_WD if it + # is not set already + OS_ORIGINAL_WD="$( pwd )" + readonly OS_ORIGINAL_WD + export OS_ORIGINAL_WD +fi + +# os::util::repository_relative_path returns the relative path from the $OS_ROOT directory to the +# given file, if the file is inside of the $OS_ROOT directory. If the file is outside of $OS_ROOT, +# this function will return the absolute path to the file +# +# Globals: +# - OS_ROOT +# Arguments: +# - 1: the path to relativize +# Returns: +# None +function os::util::repository_relative_path() { + local filename=$1 + local directory; directory="$( dirname "${filename}" )" + filename="$( basename "${filename}" )" + + if [[ "${directory}" != "${OS_ROOT}"* ]]; then + pushd "${OS_ORIGINAL_WD}" >/dev/null 2>&1 + directory="$( os::util::absolute_path "${directory}" )" + popd >/dev/null 2>&1 + fi + + directory="${directory##*${OS_ROOT}/}" + + echo "${directory}/${filename}" +} +readonly -f os::util::repository_relative_path + +# os::util::format_seconds formats a duration of time in seconds to print in HHh MMm SSs +# +# Globals: +# None +# Arguments: +# - 1: time in seconds to format +# Return: +# None +function os::util::format_seconds() { + local raw_seconds=$1 + + local hours minutes seconds + (( hours=raw_seconds/3600 )) + (( minutes=(raw_seconds%3600)/60 )) + (( seconds=raw_seconds%60 )) + + printf '%02dh %02dm %02ds' "${hours}" "${minutes}" "${seconds}" +} +readonly -f os::util::format_seconds + +# os::util::sed attempts to make our Bash scripts agnostic to the platform +# on which they run `sed` by glossing over a discrepancy in flag use in GNU. +# +# Globals: +# None +# Arguments: +# - all: arguments to pass to `sed -i` +# Return: +# None +function os::util::sed() { + local sudo="${USE_SUDO:+sudo}" + if LANG=C sed --help 2>&1 | grep -q "GNU sed"; then + ${sudo} sed -i'' "$@" + else + ${sudo} sed -i '' "$@" + fi +} +readonly -f os::util::sed + +# os::util::base64decode attempts to make our Bash scripts agnostic to the platform +# on which they run `base64decode` by glossing over a discrepancy in flag use in GNU. +# +# Globals: +# None +# Arguments: +# - all: arguments to pass to `base64decode` +# Return: +# None +function os::util::base64decode() { + if [[ "$(go env GOHOSTOS)" == "darwin" ]]; then + base64 -D "$@" + else + base64 -d "$@" + fi +} +readonly -f os::util::base64decode diff --git a/hack/lib/util/text.sh b/hack/lib/util/text.sh new file mode 100644 index 000000000..456aeea2b --- /dev/null +++ b/hack/lib/util/text.sh @@ -0,0 +1,164 @@ +#!/bin/bash + +# This file contains helpful aliases for manipulating the output text to the terminal as +# well as functions for one-command augmented printing. + +# os::text::reset resets the terminal output to default if it is called in a TTY +function os::text::reset() { + if os::text::internal::is_tty; then + tput sgr0 + fi +} +readonly -f os::text::reset + +# os::text::bold sets the terminal output to bold text if it is called in a TTY +function os::text::bold() { + if os::text::internal::is_tty; then + tput bold + fi +} +readonly -f os::text::bold + +# os::text::red sets the terminal output to red text if it is called in a TTY +function os::text::red() { + if os::text::internal::is_tty; then + tput setaf 1 + fi +} +readonly -f os::text::red + +# os::text::green sets the terminal output to green text if it is called in a TTY +function os::text::green() { + if os::text::internal::is_tty; then + tput setaf 2 + fi +} +readonly -f os::text::green + +# os::text::blue sets the terminal output to blue text if it is called in a TTY +function os::text::blue() { + if os::text::internal::is_tty; then + tput setaf 4 + fi +} +readonly -f os::text::blue + +# os::text::yellow sets the terminal output to yellow text if it is called in a TTY +function os::text::yellow() { + if os::text::internal::is_tty; then + tput setaf 11 + fi +} +readonly -f os::text::yellow + +# os::text::clear_last_line clears the text from the last line of output to the +# terminal and leaves the cursor on that line to allow for overwriting that text +# if it is called in a TTY +function os::text::clear_last_line() { + if os::text::internal::is_tty; then + tput cuu 1 + tput el + fi +} +readonly -f os::text::clear_last_line + +# os::text::clear_string attempts to clear the entirety of a string from the terminal. +# If the string contains literal tabs or other characters that take up more than one +# character space in output, or if the window size is changed before this function +# is called, it will not function correctly. +# No action is taken if this is called outside of a TTY +function os::text::clear_string() { + local -r string="$1" + if os::text::internal::is_tty; then + echo "${string}" | while read line; do + # num_lines is the number of terminal lines this one line of output + # would have taken up with the current terminal width in columns + local num_lines=$(( ${#line} / $( tput cols ) )) + for (( i = 0; i <= num_lines; i++ )); do + os::text::clear_last_line + done + done + fi +} + +# os::text::internal::is_tty determines if we are outputting to a TTY +function os::text::internal::is_tty() { + [[ -t 1 && -n "${TERM:-}" ]] +} +readonly -f os::text::internal::is_tty + +# os::text::print_bold prints all input in bold text +function os::text::print_bold() { + os::text::bold + echo "${*}" + os::text::reset +} +readonly -f os::text::print_bold + +# os::text::print_red prints all input in red text +function os::text::print_red() { + os::text::red + echo "${*}" + os::text::reset +} +readonly -f os::text::print_red + +# os::text::print_red_bold prints all input in bold red text +function os::text::print_red_bold() { + os::text::red + os::text::bold + echo "${*}" + os::text::reset +} +readonly -f os::text::print_red_bold + +# os::text::print_green prints all input in green text +function os::text::print_green() { + os::text::green + echo "${*}" + os::text::reset +} +readonly -f os::text::print_green + +# os::text::print_green_bold prints all input in bold green text +function os::text::print_green_bold() { + os::text::green + os::text::bold + echo "${*}" + os::text::reset +} +readonly -f os::text::print_green_bold + +# os::text::print_blue prints all input in blue text +function os::text::print_blue() { + os::text::blue + echo "${*}" + os::text::reset +} +readonly -f os::text::print_blue + +# os::text::print_blue_bold prints all input in bold blue text +function os::text::print_blue_bold() { + os::text::blue + os::text::bold + echo "${*}" + os::text::reset +} +readonly -f os::text::print_blue_bold + +# os::text::print_yellow prints all input in yellow text +function os::text::print_yellow() { + os::text::yellow + echo "${*}" + os::text::reset +} +readonly -f os::text::print_yellow + +# os::text::print_yellow_bold prints all input in bold yellow text +function os::text::print_yellow_bold() { + os::text::yellow + os::text::bold + echo "${*}" + os::text::reset +} +readonly -f os::text::print_yellow_bold diff --git a/hack/lib/util/trap.sh b/hack/lib/util/trap.sh new file mode 100644 index 000000000..30d252031 --- /dev/null +++ b/hack/lib/util/trap.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# +# This library defines the trap handlers for the ERR and EXIT signals. Any new handler for these signals +# must be added to these handlers and activated by the environment variable mechanism that the rest use. +# These functions ensure that no handler can ever alter the exit code that was emitted by a command +# in a test script. + +# os::util::trap::init_err initializes the privileged handler for the ERR signal if it hasn't +# been registered already. This will overwrite any other handlers registered on the signal. +# +# Globals: +# None +# Arguments: +# None +# Returns: +# None +function os::util::trap::init_err() { + if ! trap -p ERR | grep -q 'os::util::trap::err_handler'; then + trap 'os::util::trap::err_handler;' ERR + fi +} +readonly -f os::util::trap::init_err + +# os::util::trap::init_exit initializes the privileged handler for the EXIT signal if it hasn't +# been registered already. This will overwrite any other handlers registered on the signal. +# +# Globals: +# None +# Arguments: +# None +# Returns: +# None +function os::util::trap::init_exit() { + if ! trap -p EXIT | grep -q 'os::util::trap::exit_handler'; then + trap 'os::util::trap::exit_handler;' EXIT + fi +} +readonly -f os::util::trap::init_exit + +# os::util::trap::err_handler is the handler for the ERR signal. +# +# Globals: +# - OS_TRAP_DEBUG +# - OS_USE_STACKTRACE +# Arguments: +# None +# Returns: +# - returns original return code, allows privileged handler to exit if necessary +function os::util::trap::err_handler() { + local -r return_code=$? + local -r last_command="${BASH_COMMAND}" + + if set +o | grep -q '\-o errexit'; then + local -r errexit_set=true + fi + + if [[ "${OS_TRAP_DEBUG:-}" = "true" ]]; then + echo "[DEBUG] Error handler executing with return code \`${return_code}\`, last command \`${last_command}\`, and errexit set \`${errexit_set:-}\`" + fi + + if [[ "${OS_USE_STACKTRACE:-}" = "true" ]]; then + # the OpenShift stacktrace function is treated as a privileged handler for this signal + # and is therefore allowed to run outside of a subshell in order to allow it to `exit` + # if necessary + os::log::stacktrace::print "${return_code}" "${last_command}" "${errexit_set:-}" + fi + + return "${return_code}" +} +readonly -f os::util::trap::err_handler + +# os::util::trap::exit_handler is the handler for the EXIT signal. +# +# Globals: +# - OS_TRAP_DEBUG +# - OS_DESCRIBE_RETURN_CODE +# Arguments: +# None +# Returns: +# - original exit code of the script that exited +function os::util::trap::exit_handler() { + local -r return_code=$? + + # we do not want these traps to be able to trigger more errors, we can let them fail silently + set +o errexit + + if [[ "${OS_TRAP_DEBUG:-}" = "true" ]]; then + echo "[DEBUG] Exit handler executing with return code \`${return_code}\`" + fi + + # the following envars selectively enable optional exit traps, all of which are run inside of + # a subshell in order to sandbox them and not allow them to influence how this script will exit + if [[ "${OS_DESCRIBE_RETURN_CODE:-}" = "true" ]]; then + ( os::util::describe_return_code "${return_code}" ) + fi + + exit "${return_code}" +} +readonly -f os::util::trap::exit_handler diff --git a/hack/move-upstream.sh b/hack/move-upstream.sh new file mode 100755 index 000000000..bb2bd5c1d --- /dev/null +++ b/hack/move-upstream.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# See HACKING.md for usage +# To apply all the kube UPSTREAM patches to a kubernetes git directory, you can +# 1. Set UPSTREAM_DIR for your kube working directory +# 2. Set TARGET_BRANCH for the new branch to work in +# 3. In your kube git directory, set the current branch to the level to want to apply patches to +# 4. Run `hack/move-upstream.sh master...` +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +repo="${UPSTREAM_REPO:-k8s.io/kubernetes}" +package="${UPSTREAM_PACKAGE:-pkg/api}" + +patch="${TMPDIR:-/tmp}/patch" +rm -rf "${patch}" +mkdir -p "${patch}" +relativedir="${UPSTREAM_REPO_LOCATION:-../../../${repo}}" +if [[ ! -d "${relativedir}" ]]; then + echo "Expected ${relativedir} to exist" 1>&2 + exit 1 +fi + +if [[ -z "${NO_REBASE-}" ]]; then + if [[ "${package}" != "." ]]; then + out="${repo}/${package}" + else + out="${repo}" + fi + lastrev="$(go run ${OS_ROOT}/tools/godepversion/godepversion.go ${OS_ROOT}/Godeps/Godeps.json ${out})" +fi + +branch="${TARGET_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}" +selector="origin/master...${branch}" +if [[ -n "${1-}" ]]; then + selector="$1" +fi + +echo "++ Generating patch for ${selector} onto ${lastrev} ..." 2>&1 +index=0 +for commit in $(git log --no-merges --format="%H" --reverse "${selector}" -- "vendor/${repo}/"); do + git format-patch --raw --start-number=${index} --relative="vendor/${repo}/" "${commit}^..${commit}" -o "${patch}" + let index+=10 +done + +# remove all commits that had no entries +find "${patch}" -type f -size 0 -exec rm {} \; + +pushd "${relativedir}" > /dev/null +os::build::require_clean_tree + +# create a new branch +git checkout -b "${branch}" "${lastrev}" + +# apply the changes +if ! git am -3 --ignore-whitespace ${patch}/*.patch; then + echo 2>&1 + echo "++ Patches do not apply cleanly, continue with 'git am' in ${relativedir}" 2>&1 + exit 1 +fi + +echo 2>&1 +echo "++ All patches applied cleanly upstream" 2>&1 diff --git a/hack/push-release.sh b/hack/push-release.sh new file mode 100755 index 000000000..2a492fb22 --- /dev/null +++ b/hack/push-release.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# This script pushes all of the built images to a registry. +# +# Set OS_PUSH_BASE_IMAGES=true to push base images +# Set OS_PUSH_BASE_REGISTRY to prefix the destination images +# +STARTTIME=$(date +%s) +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +# Allow a release to be repushed with a tag +tag="${OS_PUSH_TAG:-}" +if [[ -n "${tag}" ]]; then + if [[ "${tag}" == "HEAD" ]]; then + if [[ "$( git tag --points-at HEAD | wc -l )" -ne 1 ]]; then + echo "error: There must be exactly one tag pointing to HEAD to use OS_PUSH_TAG=HEAD" + exit 1 + fi + tag=":$( git tag --points-at HEAD )" + else + tag=":${tag}" + fi +else + os::build::version::get_vars + if [[ "$( git rev-parse --abbrev-ref HEAD )" == "master" ]]; then + tag=":latest,:v${OS_GIT_MAJOR}.${OS_GIT_MINOR%+},:v${OS_GIT_MAJOR}.${OS_GIT_MINOR%+}.${OS_GIT_PATCH}" + else + tag=":v${OS_GIT_MAJOR}.${OS_GIT_MINOR%+},:v${OS_GIT_MAJOR}.${OS_GIT_MINOR%+}.${OS_GIT_PATCH}" + fi +fi + +# Source tag +source_tag="${OS_TAG:-}" +if [[ -z "${source_tag}" ]]; then + source_tag="latest" + file="${OS_ROOT}/_output/local/releases/.commit" + if [[ -e ${file} ]]; then + source_tag="$(cat $file)" + fi +fi + +images=( "${OS_ALL_IMAGES[@]}" ) + +OS_PUSH_BASE_REPO="${OS_PUSH_BASE_REPO:-openshift/}" + +PUSH_OPTS="" +if docker push --help | grep -q force; then + PUSH_OPTS="--force" +fi + +# Pull latest in preparation for tagging +if [[ "${tag}" != ":latest" ]]; then + if [[ -z "${OS_PUSH_LOCAL-}" ]]; then + for image in "${images[@]}"; do + docker pull "${OS_PUSH_BASE_REGISTRY-}openshift/${image}:${source_tag}" + done + else + os::log::warning "Pushing local :${source_tag} images to ${OS_PUSH_BASE_REGISTRY-}${OS_PUSH_BASE_REPO}*${tag}" + if [[ -z "${OS_PUSH_ALWAYS:-}" ]]; then + echo " CTRL+C to cancel, or any other key to continue" + read + fi + fi +fi + +IFS=',' read -r -a tags <<< "$tag" +if [[ "${OS_PUSH_BASE_REGISTRY-}" != "" || "${tag}" != "" ]]; then + for image in "${images[@]}"; do + for tag in "${tags[@]}"; do + docker tag "openshift/${image}:${source_tag}" "${OS_PUSH_BASE_REGISTRY-}${OS_PUSH_BASE_REPO}${image}${tag}" + done + done +fi + +for image in "${images[@]}"; do + for tag in "${tags[@]}"; do + os::log::info "Pushing ${OS_PUSH_BASE_REGISTRY-}${OS_PUSH_BASE_REPO}${image}${tag}..." + docker push ${PUSH_OPTS} "${OS_PUSH_BASE_REGISTRY-}${OS_PUSH_BASE_REPO}${image}${tag}" + done +done + +ret=$?; ENDTIME=$(date +%s); echo "$0 took $(($ENDTIME - $STARTTIME)) seconds"; exit "$ret" diff --git a/hack/test-go.sh b/hack/test-go.sh new file mode 100755 index 000000000..08d1731aa --- /dev/null +++ b/hack/test-go.sh @@ -0,0 +1,186 @@ +#!/bin/bash +# +# This script runs Go language unit tests for the repository. Arguments to this script +# are parsed as a list of packages to test until the first argument starting with '-' or '--' is +# found. That argument and all following arguments are interpreted as flags to be passed directly +# to `go test`. If no arguments are given, then "all" packages are tested. +# +# Coverage reports and jUnit XML reports can be generated by this script as well, but both cannot +# be generated at once. +# +# This script consumes the following parameters as environment variables: +# - DRY_RUN: prints all packages that would be tested with the args that would be used and exits +# - TIMEOUT: the timeout for any one unit test (default '60s') +# - DETECT_RACES: toggles the 'go test' race detector (defaults '-race') +# - COVERAGE_OUTPUT_DIR: locates the directory in which coverage output files will be placed +# - COVERAGE_SPEC: a set of flags for 'go test' that specify the coverage behavior (default '-cover -covermode=atomic') +# - GOTEST_FLAGS: any other flags to be sent to 'go test' +# - JUNIT_REPORT: toggles the creation of jUnit XML from the test output and changes this script's output behavior +# to use the 'junitreport' tool for summarizing the tests. +# - DLV_DEBUG toggles running tests using delve debugger +function cleanup() { + return_code=$? + + os::util::describe_return_code "${return_code}" + exit "${return_code}" +} +trap "cleanup" EXIT + +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" +os::build::setup_env +os::cleanup::tmpdir + +# Internalize environment variables we consume and default if they're not set +dry_run="${DRY_RUN:-}" +test_timeout="${TIMEOUT:-120s}" +detect_races="${DETECT_RACES:-true}" +coverage_output_dir="${COVERAGE_OUTPUT_DIR:-}" +coverage_spec="${COVERAGE_SPEC:--cover -covermode atomic}" +gotest_flags="${GOTEST_FLAGS:-}" +junit_report="${JUNIT_REPORT:-}" +dlv_debug="${DLV_DEBUG:-}" + +if [[ -n "${junit_report}" && -n "${coverage_output_dir}" ]]; then + echo "$0 cannot create jUnit XML reports and coverage reports at the same time." + exit 1 +fi + +# determine if user wanted verbosity +verbose= +if [[ "${gotest_flags}" =~ -v( |$) ]]; then + verbose=true +fi + +# Build arguments for 'go test' +if [[ -z "${verbose}" && -n "${junit_report}" ]]; then + # verbosity can be set explicitly by the user or set implicitly by asking for the jUnit + # XML report, so we only want to add the flag if it hasn't been added by a user already + # and is being implicitly set by jUnit report generation + gotest_flags+=" -v" +fi + +if [[ "${detect_races}" == "true" ]]; then + gotest_flags+=" -race" +fi + +# check to see if user has not disabled coverage mode +if [[ -n "${coverage_spec}" ]]; then + # if we have a coverage spec set, we add it. '-race' implies '-cover -covermode atomic' + # but specifying both at the same time does not lead to an error so we can add both specs + gotest_flags+=" ${coverage_spec}" +fi + +# check to see if user has not disabled test timeouts +if [[ -n "${test_timeout}" ]]; then + gotest_flags+=" -timeout ${test_timeout}" +fi + + +# Break up the positional arguments into packages that need to be tested and arguments that need to be passed to `go test` +package_args= +for arg in "$@"; do + if [[ "${arg}" =~ ^-.* ]]; then + # we found an arg that begins with a dash, so we stop interpreting arguments + # henceforth as packages and instead interpret them as flags to give to `go test` + break + fi + # an arg found before the first flag is a package + package_args+=" ${arg}" + shift +done +gotest_flags+=" $*" + +# Determine packages to test +godeps_package_prefix="vendor/" +test_packages= +if [[ -n "${package_args}" ]]; then + for package in ${package_args}; do + # If we're trying to recursively test a package under Godeps, strip the Godeps prefix so go test can find the packages correctly + if [[ "${package}" == "${godeps_package_prefix}"*"/..." ]]; then + test_packages="${test_packages} ${package:${#godeps_package_prefix}}" + else + test_packages="${test_packages} ${OS_GO_PACKAGE}/${package}" + fi + done +else + # If no packages are given to test, we need to generate a list of all packages with unit tests + test_packages="$(os::util::list_test_packages_under '*')" +fi + +if [[ -n "${dry_run}" ]]; then + echo "The following base flags for \`go test\` will be used by $0:" + echo "go test ${gotest_flags}" + echo "The following packages will be tested by $0:" + for package in ${test_packages}; do + echo "${package}" + done + exit 0 +fi + +# Run 'go test' with the accumulated arguments and packages: +if [[ -n "${junit_report}" ]]; then + # we need to generate jUnit xml + + test_error_file="${LOG_DIR}/test-go-err.log" + + os::log::info "Running \`go test\`..." + # we don't care if the `go test` fails in this pipe, as we want to generate the report and summarize the output anyway + set +o pipefail + + os::util::ensure::built_binary_exists 'gotest2junit' + report_file="$( mktemp "${ARTIFACT_DIR}/unit_report_XXXXX" ).xml" + + go test -json ${gotest_flags} ${test_packages} 2>"${test_error_file}" | tee "${JUNIT_REPORT_OUTPUT}" | gotest2junit > "${report_file}" + test_return_code="${PIPESTATUS[0]}" + + gzip "${test_error_file}" -c > "${ARTIFACT_DIR}/unit-error.log.gz" + gzip "${JUNIT_REPORT_OUTPUT}" -c > "${ARTIFACT_DIR}/unit.log.gz" + + set -o pipefail + + if [[ -s "${test_error_file}" ]]; then + os::log::warning "\`go test\` had the following output to stderr: +$( cat "${test_error_file}") " + fi + + if grep -q 'WARNING: DATA RACE' "${JUNIT_REPORT_OUTPUT}"; then + locations=( $( sed -n '/WARNING: DATA RACE/=' "${JUNIT_REPORT_OUTPUT}") ) + if [[ "${#locations[@]}" -gt 1 ]]; then + os::log::warning "\`go test\` detected data races." + os::log::warning "Details can be found in the full output file at lines ${locations[*]}." + else + os::log::warning "\`go test\` detected a data race." + os::log::warning "Details can be found in the full output file at line ${locations[*]}." + fi + fi + + exit "${test_return_code}" + +elif [[ -n "${coverage_output_dir}" ]]; then + # we need to generate coverage reports + for test_package in ${test_packages}; do + mkdir -p "${coverage_output_dir}/${test_package}" + local_gotest_flags="${gotest_flags} -coverprofile=${coverage_output_dir}/${test_package}/profile.out" + + go test ${local_gotest_flags} ${test_package} + done + + # assemble all profiles and generate a coverage report + echo 'mode: atomic' > "${coverage_output_dir}/profiles.out" + find "${coverage_output_dir}" -name profile.out | xargs sed '/^mode: atomic$/d' >> "${coverage_output_dir}/profiles.out" + + go tool cover "-html=${coverage_output_dir}/profiles.out" -o "${coverage_output_dir}/coverage.html" + os::log::info "Coverage profile written to ${coverage_output_dir}/coverage.html" + + # clean up all of the individual coverage reports as they have been subsumed into the report at ${coverage_output_dir}/coverage.html + # we can clean up all of the coverage reports at once as they all exist in subdirectories of ${coverage_output_dir}/${OS_GO_PACKAGE} + # and they are the only files found in those subdirectories + rm -rf "${coverage_output_dir:?}/${OS_GO_PACKAGE}" + +elif [[ -n "${dlv_debug}" ]]; then + # run tests using delve debugger + dlv test ${test_packages} +else + # we need to generate neither jUnit XML nor coverage reports + go test ${gotest_flags} ${test_packages} +fi diff --git a/hack/update-deps.sh b/hack/update-deps.sh new file mode 100755 index 000000000..f7e6057ec --- /dev/null +++ b/hack/update-deps.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e + +readonly GLIDE_MINOR_VERSION="13" +readonly REQUIRED_GLIDE_VERSION="0.$GLIDE_MINOR_VERSION" + +function verify_glide_version() { + if ! command -v glide &> /dev/null; then + echo "[FATAL] Glide was not found in \$PATH. Please install version ${REQUIRED_GLIDE_VERSION} or newer." + exit 1 + fi + + local glide_version + glide_version=($(glide --version)) + if ! echo "${glide_version[2]#v}" | awk -F. -v min=$GLIDE_MINOR_VERSION '{ exit $2 < min }'; then + echo "Detected glide version: ${glide_version[*]}." + echo "Please install Glide version ${REQUIRED_GLIDE_VERSION} or newer." + exit 1 + fi +} + +verify_glide_version + +glide update --strip-vendor diff --git a/hack/update-generated-bindata.sh b/hack/update-generated-bindata.sh new file mode 100755 index 000000000..ed2dc6c5a --- /dev/null +++ b/hack/update-generated-bindata.sh @@ -0,0 +1,32 @@ +#!/bin/bash +STARTTIME=$(date +%s) +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +os::build::setup_env + +OUTPUT_PARENT=${OUTPUT_ROOT:-$OS_ROOT} + +pushd vendor/github.com/jteeuwen/go-bindata > /dev/null + go install ./... +popd > /dev/null +os::util::ensure::gopath_binary_exists 'go-bindata' + +pushd "${OS_ROOT}" > /dev/null +"$(os::util::find::gopath_binary go-bindata)" \ + -nocompress \ + -nometadata \ + -prefix "bindata" \ + -pkg "v310_00_assets" \ + -o "${OUTPUT_PARENT}/pkg/operator/v310_00_assets/bindata.go" \ + -ignore "OWNERS" \ + bindata/v3.10.0/... + +popd > /dev/null + +# If you hit this, please reduce other tests instead of importing more +if [[ "$( cat "${OUTPUT_PARENT}/test/extended/testdata/bindata.go" | wc -c )" -gt 1500000 ]]; then + echo "error: extended bindata is $( cat "${OUTPUT_PARENT}/test/extended/testdata/bindata.go" | wc -c ) bytes, reduce the size of the import" 1>&2 + exit 1 +fi + +ret=$?; ENDTIME=$(date +%s); echo "$0 took $(($ENDTIME - $STARTTIME)) seconds"; exit "$ret" diff --git a/hack/verify-generated-bindata.sh b/hack/verify-generated-bindata.sh new file mode 100755 index 000000000..278a19995 --- /dev/null +++ b/hack/verify-generated-bindata.sh @@ -0,0 +1,18 @@ +#!/bin/bash +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +function cleanup() { + return_code=$? + rm -rf "${TMP_GENERATED_BOOTSTRAP_DIR}" + os::test::junit::generate_report + os::util::describe_return_code "${return_code}" + exit "${return_code}" +} +trap "cleanup" EXIT + +TMP_GENERATED_BOOTSTRAP_DIR="_output/verify-bindata" + +os::test::junit::declare_suite_start "verify/bindata" +os::cmd::expect_success "OUTPUT_ROOT=${TMP_GENERATED_BOOTSTRAP_DIR} ${OS_ROOT}/hack/update-generated-bindata.sh" +os::cmd::expect_success "diff -Naup ${OS_ROOT}/pkg/operator/v310_00_assets/bindata.go ${TMP_GENERATED_BOOTSTRAP_DIR}/pkg/operator/v310_00_assets/bindata.go" +os::test::junit::declare_suite_end \ No newline at end of file diff --git a/hack/verify-gofmt.sh b/hack/verify-gofmt.sh new file mode 100755 index 000000000..3ff24e84c --- /dev/null +++ b/hack/verify-gofmt.sh @@ -0,0 +1,17 @@ +#!/bin/bash +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +function cleanup() { + return_code=$? + os::util::describe_return_code "${return_code}" + exit "${return_code}" +} +trap "cleanup" EXIT + +bad_files=$(os::util::list_go_src_files | xargs gofmt -s -l) +if [[ -n "${bad_files}" ]]; then + os::log::warning "!!! gofmt needs to be run on the listed files" + echo "${bad_files}" + os::log::fatal "Try running 'gofmt -s -d [path]' +Or autocorrect with 'hack/verify-gofmt.sh | xargs -n 1 gofmt -s -w'" +fi diff --git a/hack/verify-golint.sh b/hack/verify-golint.sh new file mode 100755 index 000000000..c61e20d83 --- /dev/null +++ b/hack/verify-golint.sh @@ -0,0 +1,33 @@ +#!/bin/bash +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +os::util::ensure::system_binary_exists 'golint' + +arg="${1:-""}" +bad_files="" + +if [ "$arg" == "-m" ]; then + head=$(git rev-parse --short HEAD | xargs echo -n) + set +e + modified_files=$(git diff-tree --no-commit-id --name-only -r master..$head | \ + grep "^pkg" | grep ".go$" | grep -v "bindata.go$" | grep -v "Godeps" | \ + grep -v "third_party") + if [ -n "${modified_files}" ]; then + echo -e "Checking modified files: ${modified_files}\n" + for f in $modified_files; do golint $f; done + echo + fi + set -e +else + bad_files=$(os::util::list_go_src_files | \ + sort -u | \ + sed 's/^.{2}//' | \ + xargs -n1 printf "${GOPATH}/src/${OS_GO_PACKAGE}/%s\n" | \ + xargs -n1 golint) +fi + +if [[ -n "${bad_files}" ]]; then + echo "golint detected following problems:" + echo "${bad_files}" + exit 1 +fi diff --git a/hack/verify-govet.sh b/hack/verify-govet.sh new file mode 100755 index 000000000..f1746cdf3 --- /dev/null +++ b/hack/verify-govet.sh @@ -0,0 +1,39 @@ +#!/bin/bash +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +function cleanup() { + return_code=$? + os::util::describe_return_code "${return_code}" + exit "${return_code}" +} +trap "cleanup" EXIT + +govet_blacklist=( "${OS_GOVET_BLACKLIST[@]-}" ) + +function govet_blacklist_contains() { + local text=$1 + for blacklist_entry in "${govet_blacklist[@]-}"; do + if grep -Eqx "${blacklist_entry}" <<<"${text}"; then + # the text we got matches this blacklist entry + return 0 + fi + done + return 1 +} + +for test_dir in $(os::util::list_go_src_dirs); do + if ! result="$(go tool vet -shadow=false -printfuncs=Info,Infof,Warning,Warningf "${test_dir}" 2>&1)"; then + while read -r line; do + if ! govet_blacklist_contains "${line}"; then + echo "${line}" + FAILURE=true + fi + done <<<"${result}" + fi +done + +# We don't want to exit on the first failure of go vet, so just keep track of +# whether a failure occurred or not. +if [[ -n "${FAILURE:-}" ]]; then + os::log::fatal "FAILURE: go vet failed!" +fi diff --git a/hack/verify-imports.sh b/hack/verify-imports.sh new file mode 100755 index 000000000..a5c510499 --- /dev/null +++ b/hack/verify-imports.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# This script verifies that package trees +# conform to our import restrictions +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +function cleanup() { + return_code=$? + os::util::describe_return_code "${return_code}" + exit "${return_code}" +} +trap "cleanup" EXIT + +os::util::ensure::built_binary_exists 'import-verifier' + +os::test::junit::declare_suite_start "verify/imports" +os::cmd::expect_success "import-verifier ${OS_ROOT}/hack/import-restrictions.json" +os::test::junit::declare_suite_end \ No newline at end of file diff --git a/hack/verify-upstream-commits.sh b/hack/verify-upstream-commits.sh new file mode 100755 index 000000000..b5fc0aa4c --- /dev/null +++ b/hack/verify-upstream-commits.sh @@ -0,0 +1,19 @@ +#!/bin/bash +source "$(dirname "${BASH_SOURCE}")/lib/init.sh" + +function cleanup() { + return_code=$? + os::util::describe_return_code "${return_code}" + exit "${return_code}" +} +trap "cleanup" EXIT + +if ! git status &> /dev/null; then + os::log::fatal "Not a Git repository" +fi + +os::util::ensure::built_binary_exists 'commitchecker' + +os::test::junit::declare_suite_start "verify/upstream-commits" +os::cmd::expect_success "commitchecker" +os::test::junit::declare_suite_end \ No newline at end of file diff --git a/manifests/0000_51_machine-approver-04-role.yaml b/manifests/0000_51_machine-approver-04-role.yaml new file mode 100644 index 000000000..84b75d148 --- /dev/null +++ b/manifests/0000_51_machine-approver-04-role.yaml @@ -0,0 +1,34 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: machine-approver + namespace: openshift-cluster-machine-approver +rules: +- apiGroups: + - "" + resources: + - pods + verbs: + - list +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create +- apiGroups: + - apps + resources: + - replicasets + verbs: + - get diff --git a/manifests/0000_51_machine-approver-05-rolebinding.yaml b/manifests/0000_51_machine-approver-05-rolebinding.yaml new file mode 100644 index 000000000..bf2dadfbf --- /dev/null +++ b/manifests/0000_51_machine-approver-05-rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: machine-approver + namespace: openshift-cluster-machine-approver +roleRef: + kind: Role + name: machine-approver + namespace: openshift-cluster-machine-approver +subjects: +- kind: ServiceAccount + namespace: openshift-cluster-machine-approver + name: machine-approver-sa diff --git a/manifests/0000_51_machine-approver-06-cm.yaml b/manifests/0000_51_machine-approver-06-cm.yaml new file mode 100644 index 000000000..bf53131d6 --- /dev/null +++ b/manifests/0000_51_machine-approver-06-cm.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: openshift-cluster-machine-approver + name: origin-cluster-machine-approver-config +data: + operator-config.yaml: | + apiVersion: operator.openshift.io/v1alpha1 + kind: GenericOperatorConfig + authentication: + disabled: true + authorization: + disabled: true \ No newline at end of file diff --git a/manifests/0000_51_machine-approver-04-deployment.yaml b/manifests/0000_51_machine-approver-07-deployment.yaml similarity index 69% rename from manifests/0000_51_machine-approver-04-deployment.yaml rename to manifests/0000_51_machine-approver-07-deployment.yaml index 37098fce2..ac017015f 100644 --- a/manifests/0000_51_machine-approver-04-deployment.yaml +++ b/manifests/0000_51_machine-approver-07-deployment.yaml @@ -24,12 +24,24 @@ spec: - name: machine-approver-controller image: docker.io/openshift/origin-cluster-machine-approver:v4.0 imagePullPolicy: Always - command: ["/usr/bin/machine-approver", "--logtostderr", "-v", "3"] + command: ["machine-approver", "operator"] + args: + - "--config=/var/run/configmaps/config/operator-config.yaml" + - "-v=4" env: + # fix these - name: KUBERNETES_SERVICE_PORT value: "6443" - name: KUBERNETES_SERVICE_HOST value: "127.0.0.1" + volumeMounts: + - mountPath: /var/run/configmaps/config + name: config + volumes: + - name: config + configMap: + defaultMode: 440 + name: origin-cluster-machine-approver-config nodeSelector: node-role.kubernetes.io/master: "" tolerations: diff --git a/pkg/boilerplate/controller/controller.go b/pkg/boilerplate/controller/controller.go new file mode 100644 index 000000000..c6f9cf1b2 --- /dev/null +++ b/pkg/boilerplate/controller/controller.go @@ -0,0 +1,130 @@ +package controller + +import ( + "fmt" + "time" + + "github.com/golang/glog" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" +) + +type Runner interface { + Run(workers int, stopCh <-chan struct{}) +} + +func New(name string, sync KeySyncer, opts ...Option) Runner { + c := &controller{ + name: name, + sync: sync, + } + + WithRateLimiter(workqueue.DefaultControllerRateLimiter())(c) + + for _, opt := range opts { + opt(c) + } + + return c +} + +type controller struct { + name string + sync KeySyncer + + queue workqueue.RateLimitingInterface + maxRetries int + + run bool + runOpts []Option + + cacheSyncs []cache.InformerSynced +} + +func (c *controller) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + glog.Infof("Starting %s", c.name) + defer glog.Infof("Shutting down %s", c.name) + + c.run = true + for _, opt := range c.runOpts { + opt(c) + } + + if !cache.WaitForCacheSync(stopCh, c.cacheSyncs...) { + utilruntime.HandleError(fmt.Errorf("%s: timed out waiting for caches to sync", c.name)) + return + } + + for i := 0; i < workers; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + <-stopCh +} + +func (c *controller) add(filter ParentFilter, object v1.Object) { + namespace, name := filter.Parent(object) + qKey := queueKey{namespace: namespace, name: name} + c.queue.Add(qKey) +} + +func (c *controller) runWorker() { + for c.processNextWorkItem() { + } +} + +func (c *controller) processNextWorkItem() bool { + key, quit := c.queue.Get() + if quit { + return false + } + + qKey := key.(queueKey) + defer c.queue.Done(qKey) + + err := c.handleSync(qKey) + c.handleKey(qKey, err) + + return true +} + +func (c *controller) handleSync(key queueKey) error { + obj, err := c.sync.Key(key.namespace, key.name) + if errors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return c.sync.Sync(obj) +} + +func (c *controller) handleKey(key queueKey, err error) { + if err == nil { + c.queue.Forget(key) + return + } + + retryForever := c.maxRetries <= 0 + if retryForever || c.queue.NumRequeues(key) < c.maxRetries { + utilruntime.HandleError(fmt.Errorf("%v failed with: %v", key, err)) + c.queue.AddRateLimited(key) + return + } + + utilruntime.HandleError(fmt.Errorf("dropping key %v out of the queue: %v", key, err)) + c.queue.Forget(key) +} + +type queueKey struct { + namespace string + name string +} diff --git a/pkg/boilerplate/controller/filter.go b/pkg/boilerplate/controller/filter.go new file mode 100644 index 000000000..7a2c52fa5 --- /dev/null +++ b/pkg/boilerplate/controller/filter.go @@ -0,0 +1,69 @@ +package controller + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" +) + +type ParentFilter interface { + Parent(obj v1.Object) (namespace, name string) + Filter +} + +type Filter interface { + Add(obj v1.Object) bool + Update(oldObj, newObj v1.Object) bool + Delete(obj v1.Object) bool +} + +type ParentFunc func(obj v1.Object) (namespace, name string) + +type FilterFuncs struct { + ParentFunc ParentFunc + AddFunc func(obj v1.Object) bool + UpdateFunc func(oldObj, newObj v1.Object) bool + DeleteFunc func(obj v1.Object) bool +} + +func (f FilterFuncs) Parent(obj v1.Object) (namespace, name string) { + if f.ParentFunc == nil { + return obj.GetNamespace(), obj.GetName() + } + return f.ParentFunc(obj) +} + +func (f FilterFuncs) Add(obj v1.Object) bool { + if f.AddFunc == nil { + return false + } + return f.AddFunc(obj) +} + +func (f FilterFuncs) Update(oldObj, newObj v1.Object) bool { + if f.UpdateFunc == nil { + return false + } + return f.UpdateFunc(oldObj, newObj) +} + +func (f FilterFuncs) Delete(obj v1.Object) bool { + if f.DeleteFunc == nil { + return false + } + return f.DeleteFunc(obj) +} + +func FilterByNames(parentFunc ParentFunc, names ...string) ParentFilter { + set := sets.NewString(names...) + has := func(obj v1.Object) bool { + return set.Has(obj.GetName()) + } + return FilterFuncs{ + ParentFunc: parentFunc, + AddFunc: has, + UpdateFunc: func(oldObj, newObj v1.Object) bool { + return has(newObj) + }, + DeleteFunc: has, + } +} diff --git a/pkg/boilerplate/controller/option.go b/pkg/boilerplate/controller/option.go new file mode 100644 index 000000000..57f9d0718 --- /dev/null +++ b/pkg/boilerplate/controller/option.go @@ -0,0 +1,109 @@ +package controller + +import ( + "fmt" + "sync" + + "github.com/golang/glog" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" +) + +type Option func(*controller) + +type InformerGetter interface { + Informer() cache.SharedIndexInformer +} + +func WithMaxRetries(maxRetries int) Option { + return func(c *controller) { + c.maxRetries = maxRetries + } +} + +func WithRateLimiter(limiter workqueue.RateLimiter) Option { + return func(c *controller) { + c.queue = workqueue.NewNamedRateLimitingQueue(limiter, c.name) + } +} + +func WithInformerSynced(getter InformerGetter) Option { + informer := getter.Informer() // immediately signal that we intend to use this informer in case it is lazily initialized + return toRunOpt(func(c *controller) { + c.cacheSyncs = append(c.cacheSyncs, informer.GetController().HasSynced) + }) +} + +func WithInformer(getter InformerGetter, filter ParentFilter) Option { + informer := getter.Informer() // immediately signal that we intend to use this informer in case it is lazily initialized + return toRunOpt(func(c *controller) { + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + object := metaOrDie(obj) + if filter.Add(object) { + glog.V(4).Infof("%s: handling add %s/%s", c.name, object.GetNamespace(), object.GetName()) + c.add(filter, object) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + oldObject := metaOrDie(oldObj) + newObject := metaOrDie(newObj) + if filter.Update(oldObject, newObject) { + glog.V(4).Infof("%s: handling update %s/%s", c.name, newObject.GetNamespace(), newObject.GetName()) + c.add(filter, newObject) + } + }, + DeleteFunc: func(obj interface{}) { + accessor, err := meta.Accessor(obj) + if err != nil { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + utilruntime.HandleError(fmt.Errorf("could not get object from tombstone: %+v", obj)) + return + } + accessor, err = meta.Accessor(tombstone.Obj) + if err != nil { + utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not an accessor: %+v", obj)) + return + } + } + if filter.Delete(accessor) { + glog.V(4).Infof("%s: handling delete %s/%s", c.name, accessor.GetNamespace(), accessor.GetName()) + c.add(filter, accessor) + } + }, + }) + WithInformerSynced(getter)(c) + }) +} + +func toRunOpt(opt Option) Option { + return toOnceOpt(func(c *controller) { + if c.run { + opt(c) + return + } + c.runOpts = append(c.runOpts, opt) + }) +} + +func toOnceOpt(opt Option) Option { + var once sync.Once + return func(c *controller) { + once.Do(func() { + opt(c) + }) + } +} + +func metaOrDie(obj interface{}) v1.Object { + accessor, err := meta.Accessor(obj) + if err != nil { + panic(err) // this should never happen + } + return accessor +} diff --git a/pkg/boilerplate/controller/sync.go b/pkg/boilerplate/controller/sync.go new file mode 100644 index 000000000..aa2a8cd4d --- /dev/null +++ b/pkg/boilerplate/controller/sync.go @@ -0,0 +1,14 @@ +package controller + +import "k8s.io/apimachinery/pkg/apis/meta/v1" + +type KeySyncer interface { + Key(namespace, name string) (v1.Object, error) + Syncer +} + +type Syncer interface { + Sync(v1.Object) error +} + +type SyncFunc func(v1.Object) error diff --git a/pkg/boilerplate/operator/filter.go b/pkg/boilerplate/operator/filter.go new file mode 100644 index 000000000..cfb341c0c --- /dev/null +++ b/pkg/boilerplate/operator/filter.go @@ -0,0 +1,7 @@ +package operator + +import "github.com/openshift/cluster-machine-approver/pkg/boilerplate/controller" + +func FilterByNames(names ...string) controller.Filter { + return controller.FilterByNames(nil, names...) +} diff --git a/pkg/boilerplate/operator/operator.go b/pkg/boilerplate/operator/operator.go new file mode 100644 index 000000000..828acd4dc --- /dev/null +++ b/pkg/boilerplate/operator/operator.go @@ -0,0 +1,34 @@ +package operator + +import "github.com/openshift/cluster-machine-approver/pkg/boilerplate/controller" + +type Runner interface { + Run(stopCh <-chan struct{}) +} + +func New(name string, sync KeySyncer, opts ...Option) Runner { + o := &operator{ + name: name, + sync: &wrapper{KeySyncer: sync}, + } + + for _, opt := range opts { + opt(o) + } + + return o +} + +type operator struct { + name string + sync controller.KeySyncer + + opts []controller.Option +} + +func (o *operator) Run(stopCh <-chan struct{}) { + runner := controller.New(o.name, o.sync, o.opts...) + // only start one worker because we only have one key in our queue (see WithInformer) + // since this operator works on a singleton, it does not make sense to ever run more than one worker + runner.Run(1, stopCh) +} diff --git a/pkg/boilerplate/operator/option.go b/pkg/boilerplate/operator/option.go new file mode 100644 index 000000000..97268052f --- /dev/null +++ b/pkg/boilerplate/operator/option.go @@ -0,0 +1,28 @@ +package operator + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openshift/cluster-machine-approver/pkg/boilerplate/controller" +) + +// key is the singleton key shared by all events +// the value is irrelevant, but pandas are awesome + const key = "🐼" + +type Option func(*operator) + +func WithInformer(getter controller.InformerGetter, filter controller.Filter) Option { + return func(o *operator) { + o.opts = append(o.opts, + controller.WithInformer(getter, controller.FilterFuncs{ + ParentFunc: func(obj v1.Object) (namespace, name string) { + return key, key // return our singleton key for all events + }, + AddFunc: filter.Add, + UpdateFunc: filter.Update, + DeleteFunc: filter.Delete, + }), + ) + } +} diff --git a/pkg/boilerplate/operator/sync.go b/pkg/boilerplate/operator/sync.go new file mode 100644 index 000000000..c00bfb713 --- /dev/null +++ b/pkg/boilerplate/operator/sync.go @@ -0,0 +1,22 @@ +package operator + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openshift/cluster-machine-approver/pkg/boilerplate/controller" +) + +type KeySyncer interface { + Key() (v1.Object, error) + controller.Syncer +} + +var _ controller.KeySyncer = &wrapper{} + +type wrapper struct { + KeySyncer +} + +func (s *wrapper) Key(namespace, name string) (v1.Object, error) { + return s.KeySyncer.Key() +} diff --git a/pkg/cmd/operator/cmd.go b/pkg/cmd/operator/cmd.go new file mode 100644 index 000000000..cc8caa5dc --- /dev/null +++ b/pkg/cmd/operator/cmd.go @@ -0,0 +1,21 @@ +package operator + +import ( + "github.com/spf13/cobra" + + "github.com/openshift/cluster-machine-approver/pkg/operator" + "github.com/openshift/cluster-machine-approver/pkg/version" + "github.com/openshift/library-go/pkg/controller/controllercmd" +) + +const ( + componentName = "cluster-machine-approver" + componentNamespace = "openshift-cluster-machine-approver" +) + +func NewOperator() *cobra.Command { + cmd := controllercmd.NewControllerCommandConfig(componentName, version.Get(), operator.RunOperator).NewCommand() + cmd.Use = "operator" + cmd.Short = "Start the machine approver operator" + return cmd +} diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go new file mode 100644 index 000000000..b1da2e766 --- /dev/null +++ b/pkg/operator/operator.go @@ -0,0 +1,78 @@ +package operator + +import ( + "github.com/golang/glog" + + "github.com/openshift/cluster-machine-approver/pkg/boilerplate/controller" + + "k8s.io/apimachinery/pkg/apis/meta/v1" + + certv1beta1 "k8s.io/api/certificates/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers/certificates/v1beta1" + certificatesv1beta1 "k8s.io/client-go/kubernetes/typed/certificates/v1beta1" +) + +type approverController struct { + csrClient certificatesv1beta1.CertificateSigningRequestsGetter +} + +func NewMachineApproverController(cri v1beta1.CertificateSigningRequestInformer, csrClient certificatesv1beta1.CertificateSigningRequestsGetter) controller.Runner { + c := &approverController{ + csrClient: csrClient, + } + + add := func(obj v1.Object) bool { + return true + } + return controller.New("MachineApproverController", c, + controller.WithInformer(cri, controller.FilterFuncs{ + ParentFunc: nil, + AddFunc: add, + UpdateFunc: func(oldObj, newObj v1.Object) bool { + return true + }, + DeleteFunc: add, + }), + ) +} + +func (c approverController) Key(namespace, name string) (metav1.Object, error) { + return c.csrClient.CertificateSigningRequests().Get(name, metav1.GetOptions{}) +} + +func (c approverController) Sync(obj metav1.Object) error { + csr := obj.(*certv1beta1.CertificateSigningRequest) + // Note that you also have to check the uid if you have a local controlled resource, which + // is dependent on the actual instance, to detect that a CSR was recreated with the same name + glog.Infof("CSR %s added\n", csr.GetName()) + + var alreadyApproved bool + for _, c := range csr.Status.Conditions { + if c.Type == certv1beta1.CertificateApproved { + alreadyApproved = true + break + } + } + if alreadyApproved { + glog.Infof("CSR %s is already approved", csr.GetName()) + return nil + } + + // TODO and CSR checking logic here + + csr.Status.Conditions = append(csr.Status.Conditions, certv1beta1.CertificateSigningRequestCondition{ + Type: certv1beta1.CertificateApproved, + Reason: "NodeCSRApprove", + Message: "This CSR was approved by the machine approver operator.", + LastUpdateTime: metav1.Now(), + }) + + if _, err := c.csrClient.CertificateSigningRequests().UpdateApproval(csr); err != nil { + return err + } + + glog.Infof("CSR %s approved", csr.GetName()) + + return nil +} diff --git a/pkg/operator/starter.go b/pkg/operator/starter.go new file mode 100644 index 000000000..19dd03591 --- /dev/null +++ b/pkg/operator/starter.go @@ -0,0 +1,30 @@ +package operator + +import ( + "fmt" + "time" + + "github.com/openshift/library-go/pkg/controller/controllercmd" + + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" +) + +func RunOperator(ctx *controllercmd.ControllerContext) error { + kubeClient, err := kubernetes.NewForConfig(ctx.KubeConfig) + if err != nil { + return err + } + informer := informers.NewSharedInformerFactory(kubeClient, 10*time.Minute) + + approverController := NewMachineApproverController( + informer.Certificates().V1beta1().CertificateSigningRequests(), + kubeClient.CertificatesV1beta1(), + ) + informer.Start(ctx.StopCh) + go approverController.Run(1, ctx.StopCh) + + <-ctx.StopCh + + return fmt.Errorf("stopped") +} diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 000000000..1b30b518e --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,47 @@ +package version + +import ( + "github.com/prometheus/client_golang/prometheus" + + "k8s.io/apimachinery/pkg/version" +) + +var ( + // commitFromGit is a constant representing the source version that + // generated this build. It should be set during build via -ldflags. + commitFromGit string + // versionFromGit is a constant representing the version tag that + // generated this build. It should be set during build via -ldflags. + versionFromGit string + // major version + majorFromGit string + // minor version + minorFromGit string + // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') + buildDate string +) + +// Get returns the overall codebase version. It's for detecting +// what code a binary was built from. +func Get() version.Info { + return version.Info{ + Major: majorFromGit, + Minor: minorFromGit, + GitCommit: commitFromGit, + GitVersion: versionFromGit, + BuildDate: buildDate, + } +} + +func init() { + buildInfo := prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "openshift_cluster_osin_operator_build_info", + Help: "A metric with a constant '1' value labeled by major, minor, git commit & git version from which OpenShift Osin Operator was built.", + }, + []string{"major", "minor", "gitCommit", "gitVersion"}, + ) + buildInfo.WithLabelValues(majorFromGit, minorFromGit, commitFromGit, versionFromGit).Set(1) + + prometheus.MustRegister(buildInfo) +} diff --git a/tools/changelog/changelog.go b/tools/changelog/changelog.go new file mode 100644 index 000000000..0d03dc05a --- /dev/null +++ b/tools/changelog/changelog.go @@ -0,0 +1,294 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "regexp" + "sort" + "strings" +) + +var ( + mergeRequest = regexp.MustCompile(`Merge pull request #([\d]+)`) + webconsoleBump = regexp.MustCompile(regexp.QuoteMeta("bump(github.com/openshift/service-serving-cert-signer-web-console): ") + `([\w]+)`) + upstreamKube = regexp.MustCompile(`^UPSTREAM: (\d+)+:(.+)`) + upstreamRepo = regexp.MustCompile(`^UPSTREAM: ([\w/-]+): (\d+)+:(.+)`) + prefix = regexp.MustCompile(`^[\w-]: `) + + assignments = []prefixAssignment{ + {"cluster up", "cluster"}, + {" pv ", "storage"}, + {"haproxy", "router"}, + {"router", "router"}, + {"route", "route"}, + {"authoriz", "auth"}, + {"rbac", "auth"}, + {"authent", "auth"}, + {"reconcil", "auth"}, + {"auth", "auth"}, + {"role", "auth"}, + {" dc ", "deploy"}, + {"deployment", "deploy"}, + {"rolling", "deploy"}, + {"security context constr", "security"}, + {"scc", "security"}, + {"pipeline", "build"}, + {"build", "build"}, + {"registry", "registry"}, + {"registries", "image"}, + {"image", "image"}, + {" arp ", "network"}, + {" cni ", "network"}, + {"egress", "network"}, + {"network", "network"}, + {"oc ", "cli"}, + {"template", "template"}, + {"etcd", "server"}, + {"pod", "node"}, + {"hack/", "hack"}, + {"e2e", "test"}, + {"integration", "test"}, + {"cluster", "cluster"}, + {"master", "server"}, + {"packages", "hack"}, + {"api", "server"}, + } +) + +type prefixAssignment struct { + term string + prefix string +} + +type commit struct { + short string + parents []string + message string +} + +func contains(arr []string, value string) bool { + for _, s := range arr { + if s == value { + return true + } + } + return false +} + +func main() { + log.SetFlags(0) + if len(os.Args) != 3 { + log.Fatalf("Must specify two arguments, FROM and TO") + } + from := os.Args[1] + to := os.Args[2] + + out, err := exec.Command("git", "log", "--topo-order", "--pretty=tformat:%h %p|%s", "--reverse", fmt.Sprintf("%s..%s", from, to)).CombinedOutput() + if err != nil { + log.Fatal(err) + } + + hide := make(map[string]struct{}) + var apiChanges []string + var webconsole []string + var commits []commit + var upstreams []commit + var bumps []commit + for _, line := range strings.Split(string(out), "\n") { + if len(strings.TrimSpace(line)) == 0 { + continue + } + parts := strings.SplitN(line, "|", 2) + hashes := strings.Split(parts[0], " ") + c := commit{short: hashes[0], parents: hashes[1:], message: parts[1]} + + if strings.HasPrefix(c.message, "UPSTREAM: ") { + hide[c.short] = struct{}{} + upstreams = append(upstreams, c) + } + if strings.HasPrefix(c.message, "bump(") { + hide[c.short] = struct{}{} + bumps = append(bumps, c) + } + + if len(c.parents) == 1 { + commits = append(commits, c) + continue + } + + matches := mergeRequest.FindStringSubmatch(line) + if len(matches) == 0 { + // this may have been a human pressing the merge button, we'll just record this as a direct push + continue + } + + // split the accumulated commits into any that are force merges (assumed to be the initial set due + // to --topo-order) from the PR commits as soon as we see any of our merge parents. Then print + // any of the force merges + var first int + for i := range commits { + first = i + if contains(c.parents, commits[i].short) { + first++ + break + } + } + individual := commits[:first] + merged := commits[first:] + for _, commit := range individual { + if len(commit.parents) > 1 { + continue + } + if _, ok := hide[commit.short]; ok { + continue + } + fmt.Printf("force-merge: %s %s\n", commit.message, commit.short) + } + + // try to find either the PR title or the first commit title from the merge commit + out, err := exec.Command("git", "show", "--pretty=tformat:%b", c.short).CombinedOutput() + if err != nil { + log.Fatal(err) + } + var message string + para := strings.Split(string(out), "\n\n") + if len(para) > 0 && strings.HasPrefix(para[0], "Automatic merge from submit-queue") { + para = para[1:] + } + // this is no longer necessary with the submit queue in place + if len(para) > 0 && strings.HasPrefix(para[0], "Merged by ") { + para = para[1:] + } + // post submit-queue, the merge bot will add the PR title, which is usually pretty good + if len(para) > 0 { + message = strings.Split(para[0], "\n")[0] + } + if len(message) == 0 && len(merged) > 0 { + message = merged[0].message + } + if len(message) > 0 && len(merged) == 1 && message == merged[0].message { + merged = nil + } + + // try to calculate a prefix based on the diff + if len(message) > 0 && !prefix.MatchString(message) { + prefix, ok := findPrefixFor(message, merged) + if ok { + message = prefix + ": " + message + } + } + + // github merge + + // has api changes + display := fmt.Sprintf("%s [\\#%s](https://github.com/openshift/cluster-machine-approver/pull/%s)", message, matches[1], matches[1]) + if hasFileChanges(c.short, "api/") { + apiChanges = append(apiChanges, display) + } + + var filtered []commit + for _, commit := range merged { + if _, ok := hide[commit.short]; ok { + continue + } + filtered = append(filtered, commit) + } + if len(filtered) > 0 { + fmt.Printf("- %s\n", display) + for _, commit := range filtered { + fmt.Printf(" - %s (%s)\n", commit.message, commit.short) + } + } + + // stick the merge commit in at the beginning of the next list so we can anchor the previous parent + commits = []commit{c} + } + + // chunk the bumps + var lines []string + for _, commit := range bumps { + if m := webconsoleBump.FindStringSubmatch(commit.message); len(m) > 0 { + webconsole = append(webconsole, m[1]) + continue + } + lines = append(lines, commit.message) + } + lines = sortAndUniq(lines) + for _, line := range lines { + fmt.Printf("- %s\n", line) + } + + // chunk the upstreams + lines = nil + for _, commit := range upstreams { + lines = append(lines, commit.message) + } + lines = sortAndUniq(lines) + for _, line := range lines { + fmt.Printf("- %s\n", upstreamLinkify(line)) + } + + if len(webconsole) > 0 { + fmt.Printf("- web: from %s^..%s\n", webconsole[0], webconsole[len(webconsole)-1]) + } + + for _, apiChange := range apiChanges { + fmt.Printf(" - %s\n", apiChange) + } +} + +func findPrefixFor(message string, commits []commit) (string, bool) { + message = strings.ToLower(message) + for _, m := range assignments { + if strings.Contains(message, m.term) { + return m.prefix, true + } + } + for _, c := range commits { + if prefix, ok := findPrefixFor(c.message, nil); ok { + return prefix, ok + } + } + return "", false +} + +func hasFileChanges(commit string, prefixes ...string) bool { + out, err := exec.Command("git", "diff", "--name-only", fmt.Sprintf("%s^..%s", commit, commit)).CombinedOutput() + if err != nil { + log.Fatal(err) + } + for _, file := range strings.Split(string(out), "\n") { + for _, prefix := range prefixes { + if strings.HasPrefix(file, prefix) { + return true + } + } + } + return false +} + +func sortAndUniq(lines []string) []string { + sort.Strings(lines) + out := make([]string, 0, len(lines)) + last := "" + for _, s := range lines { + if last == s { + continue + } + last = s + out = append(out, s) + } + return out +} + +func upstreamLinkify(line string) string { + if m := upstreamKube.FindStringSubmatch(line); len(m) > 0 { + return fmt.Sprintf("UPSTREAM: [#%s](https://github.com/kubernetes/kubernetes/pull/%s):%s", m[1], m[1], m[2]) + } + if m := upstreamRepo.FindStringSubmatch(line); len(m) > 0 { + return fmt.Sprintf("UPSTREAM: [%s#%s](https://github.com/%s/pull/%s):%s", m[1], m[2], m[1], m[2], m[3]) + } + return line +} diff --git a/tools/gotest2junit/gotest2junit.go b/tools/gotest2junit/gotest2junit.go new file mode 100644 index 000000000..e289e2373 --- /dev/null +++ b/tools/gotest2junit/gotest2junit.go @@ -0,0 +1,211 @@ +package main + +import ( + "bufio" + "encoding/json" + "encoding/xml" + "flag" + "fmt" + "io" + "os" + "sort" + "strings" + "time" + + "github.com/openshift/cluster-machine-approver/tools/gotest2junit/pkg/api" +) + +type Record struct { + Package string + Test string + + Time time.Time + Action string + Output string + Elapsed float64 +} + +type testSuite struct { + suite *api.TestSuite + tests map[string]*api.TestCase +} + +func main() { + summarize := false + verbose := false + flag.BoolVar(&summarize, "summary", true, "display a summary as items are processed") + flag.BoolVar(&verbose, "v", false, "display passing results") + flag.Parse() + + if err := process(os.Stdin, summarize, verbose); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} + +func process(r io.Reader, summarize, verbose bool) error { + suites, err := stream(r, summarize, verbose) + if err != nil { + return err + } + obj := newTestSuites(suites) + out, err := xml.MarshalIndent(obj, "", " ") + if err != nil { + return err + } + fmt.Fprintf(os.Stdout, "%s\n", string(out)) + return nil +} + +func newTestSuites(suites map[string]*testSuite) *api.TestSuites { + all := &api.TestSuites{} + for _, suite := range suites { + for _, test := range suite.suite.TestCases { + suite.suite.NumTests++ + if test.SkipMessage != nil { + suite.suite.NumSkipped++ + continue + } + if test.FailureOutput != nil { + suite.suite.NumFailed++ + continue + } + } + // suites with no tests are usually empty packages, ignore them + if suite.suite.NumTests == 0 { + continue + } + // always return the test cases in consistent order + sort.Slice(suite.suite.TestCases, func(i, j int) bool { + return suite.suite.TestCases[i].Name < suite.suite.TestCases[j].Name + }) + all.Suites = append(all.Suites, suite.suite) + } + // always return the test suites in consistent order + sort.Slice(all.Suites, func(i, j int) bool { + return all.Suites[i].Name < all.Suites[j].Name + }) + return all +} + +func stream(r io.Reader, summarize, verbose bool) (map[string]*testSuite, error) { + suites := make(map[string]*testSuite) + defaultTest := &api.TestCase{ + Name: "build and execution", + } + defaultSuite := &testSuite{ + suite: &api.TestSuite{Name: "go test", TestCases: []*api.TestCase{defaultTest}}, + } + suites[""] = defaultSuite + + rdr := bufio.NewReader(r) + for { + // some output from go test -json is not valid JSON - read the line to see whether it + // starts with { - if not, just mirror it to stderr and continue. + line, err := rdr.ReadString('\n') + if err != nil { + if err != io.EOF { + return suites, err + } + break + } + if len(line) == 0 || line[0] != '{' { + defaultTest.SystemOut += line + if strings.HasPrefix(line, "FAIL") { + defaultTest.FailureOutput = &api.FailureOutput{} + } + fmt.Fprint(os.Stderr, line) + continue + } + var r Record + if err := json.Unmarshal([]byte(line), &r); err != nil { + if err == io.EOF { + return suites, nil + } + fmt.Fprintf(os.Stderr, "error: Unable to parse remainder of output %v\n", err) + return suites, nil + } + + suite, ok := suites[r.Package] + if !ok { + suite = &testSuite{ + suite: &api.TestSuite{ + Name: r.Package, + }, + tests: make(map[string]*api.TestCase), + } + suites[r.Package] = suite + } + + // if this is package level output, we only care about pass/fail duration + if len(r.Test) == 0 { + switch r.Action { + case "pass", "fail": + suite.suite.Duration = r.Elapsed + } + continue + } + + test, ok := suite.tests[r.Test] + if !ok { + test = &api.TestCase{ + Name: r.Test, + } + suite.suite.TestCases = append(suite.suite.TestCases, test) + suite.tests[r.Test] = test + } + + switch r.Action { + case "run": + case "pause": + case "cont": + case "bench": + case "skip": + if summarize { + fmt.Fprintf(os.Stderr, "SKIP: %s %s\n", r.Package, r.Test) + } + test.SkipMessage = &api.SkipMessage{ + Message: r.Output, + } + case "pass": + if summarize && verbose { + fmt.Fprintf(os.Stderr, "PASS: %s %s %s\n", r.Package, r.Test, time.Duration(r.Elapsed*float64(time.Second))) + } + test.SystemOut = "" + test.Duration = r.Elapsed + case "fail": + if summarize { + fmt.Fprintf(os.Stderr, "FAIL: %s %s %s\n", r.Package, r.Test, time.Duration(r.Elapsed*float64(time.Second))) + } + test.Duration = r.Elapsed + if len(r.Output) == 0 { + r.Output = test.SystemOut + if len(r.Output) > 50 { + r.Output = r.Output[:50] + " ..." + } + } + test.FailureOutput = &api.FailureOutput{ + Message: r.Output, + Output: r.Output, + } + case "output": + test.SystemOut += r.Output + default: + // usually a bug in go test -json + out := fmt.Sprintf("error: Unrecognized go test action %s: %#v\n", r.Action, r) + defaultTest.SystemOut += line + defaultTest.SystemOut += out + defaultTest.FailureOutput = &api.FailureOutput{} + fmt.Fprintf(os.Stderr, out) + } + } + + // if we recorded any failure output + if defaultTest.FailureOutput != nil { + defaultTest.FailureOutput.Message = "Some packages failed during test execution" + defaultTest.FailureOutput.Output = defaultTest.SystemOut + defaultTest.SystemOut = "" + } + + return suites, nil +} diff --git a/tools/gotest2junit/pkg/api/junit.xsd b/tools/gotest2junit/pkg/api/junit.xsd new file mode 100644 index 000000000..92552b33d --- /dev/null +++ b/tools/gotest2junit/pkg/api/junit.xsd @@ -0,0 +1,203 @@ + + + + JUnit test result schema for the Apache Ant JUnit and JUnitReport tasks +Copyright © 2011, Windy Road Technology Pty. Limited +The Apache Ant JUnit XML Schema is distributed under the terms of the GNU Lesser General Public License (LGPL) http://www.gnu.org/licenses/lgpl.html +Permission to waive conditions of this license may be requested from Windy Road Support (http://windyroad.org/support). + + + + + + + + + + Contains an aggregation of testsuite results + + + + + + + + + + Derived from testsuite/@name in the non-aggregated documents + + + + + Starts at '0' for the first testsuite and is incremented by 1 for each following testsuite + + + + + + + + + + + + Contains the results of exexuting a testsuite + + + + + Properties (e.g., environment settings) set during test execution + + + + + + + + + + + + + + + + + + + + + + + + Indicates that the test errored. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test. Contains as a text node relevant data for the error, e.g., a stack trace + + + + + + + The error message. e.g., if a java exception is thrown, the return value of getMessage() + + + + + The type of error that occured. e.g., if a java execption is thrown the full class name of the exception. + + + + + + + + + Indicates that the test failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals. Contains as a text node relevant data for the failure, e.g., a stack trace + + + + + + + The message specified in the assert + + + + + The type of the assert. + + + + + + + + + + Name of the test method + + + + + Full class name for the class the test method is in. + + + + + Time taken (in seconds) to execute the test + + + + + + + Data that was written to standard out while the test was executed + + + + + + + + + + Data that was written to standard error while the test was executed + + + + + + + + + + + Full class name of the test for non-aggregated testsuite documents. Class name without the package for aggregated testsuites documents + + + + + + + + + + when the test was executed. Timezone may not be specified. + + + + + Host on which the tests were executed. 'localhost' should be used if the hostname cannot be determined. + + + + + + + + + + The total number of tests in the suite + + + + + The total number of tests in the suite that failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals + + + + + The total number of tests in the suite that errorrd. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test. + + + + + Time taken (in seconds) to execute the tests in the suite + + + + + + + + + \ No newline at end of file diff --git a/tools/gotest2junit/pkg/api/string.go b/tools/gotest2junit/pkg/api/string.go new file mode 100644 index 000000000..be8e650f4 --- /dev/null +++ b/tools/gotest2junit/pkg/api/string.go @@ -0,0 +1,37 @@ +package api + +import "fmt" + +// This file implements Stringer for the API types for ease of debugging + +func (t *TestSuites) String() string { + return fmt.Sprintf("Test Suites with suites: %s.", t.Suites) +} + +func (t *TestSuite) String() string { + childDescriptions := []string{} + for _, child := range t.Children { + childDescriptions = append(childDescriptions, child.String()) + } + return fmt.Sprintf("Test Suite %q with properties: %s, %d test cases, of which %d failed and %d were skipped: %s, and children: %s.", t.Name, t.Properties, t.NumTests, t.NumFailed, t.NumSkipped, t.TestCases, childDescriptions) +} + +func (t *TestCase) String() string { + var result, message, output string + result = "passed" + if t.SkipMessage != nil { + result = "skipped" + message = t.SkipMessage.Message + } + if t.FailureOutput != nil { + result = "failed" + message = t.FailureOutput.Message + output = t.FailureOutput.Output + } + + return fmt.Sprintf("Test Case %q %s after %f seconds with message %q and output %q.", t.Name, result, t.Duration, message, output) +} + +func (p *TestSuiteProperty) String() string { + return fmt.Sprintf("%q=%q", p.Name, p.Value) +} diff --git a/tools/gotest2junit/pkg/api/test_case.go b/tools/gotest2junit/pkg/api/test_case.go new file mode 100644 index 000000000..ec83e2ddd --- /dev/null +++ b/tools/gotest2junit/pkg/api/test_case.go @@ -0,0 +1,30 @@ +package api + +import "time" + +// SetDuration sets the runtime duration of the test case +func (t *TestCase) SetDuration(duration string) error { + parsedDuration, err := time.ParseDuration(duration) + if err != nil { + return err + } + + // we round to the millisecond on duration + t.Duration = float64(int(parsedDuration.Seconds()*1000)) / 1000 + return nil +} + +// MarkSkipped marks the test as skipped with the given message +func (t *TestCase) MarkSkipped(message string) { + t.SkipMessage = &SkipMessage{ + Message: message, + } +} + +// MarkFailed marks the test as failed with the given message and output +func (t *TestCase) MarkFailed(message, output string) { + t.FailureOutput = &FailureOutput{ + Message: message, + Output: output, + } +} diff --git a/tools/gotest2junit/pkg/api/test_suite.go b/tools/gotest2junit/pkg/api/test_suite.go new file mode 100644 index 000000000..1e36d06ca --- /dev/null +++ b/tools/gotest2junit/pkg/api/test_suite.go @@ -0,0 +1,67 @@ +package api + +import "time" + +// AddProperty adds a property to the test suite, deduplicating multiple additions of the same property +// by overwriting the previous record to reflect the new values +func (t *TestSuite) AddProperty(name, value string) { + for _, property := range t.Properties { + if property.Name == name { + property.Value = value + return + } + } + + t.Properties = append(t.Properties, &TestSuiteProperty{Name: name, Value: value}) +} + +// AddTestCase adds a test case to the test suite and updates test suite metrics as necessary +func (t *TestSuite) AddTestCase(testCase *TestCase) { + t.NumTests += 1 + + switch { + case testCase.SkipMessage != nil: + t.NumSkipped += 1 + case testCase.FailureOutput != nil: + t.NumFailed += 1 + default: + // we do not preserve output on tests that are not failures or skips + testCase.SystemOut = "" + testCase.SystemErr = "" + } + + t.Duration += testCase.Duration + // we round to the millisecond on duration + t.Duration = float64(int(t.Duration*1000)) / 1000 + + t.TestCases = append(t.TestCases, testCase) +} + +// SetDuration sets the duration of the test suite if this value is not calculated by aggregating the durations +// of all of the substituent test cases. This should *not* be used if the total duration of the test suite is +// calculated as that sum, as AddTestCase will handle that case. +func (t *TestSuite) SetDuration(duration string) error { + parsedDuration, err := time.ParseDuration(duration) + if err != nil { + return err + } + + // we round to the millisecond on duration + t.Duration = float64(int(parsedDuration.Seconds()*1000)) / 1000 + return nil +} + +// ByName implements sort.Interface for []*TestSuite based on the Name field +type ByName []*TestSuite + +func (n ByName) Len() int { + return len(n) +} + +func (n ByName) Swap(i, j int) { + n[i], n[j] = n[j], n[i] +} + +func (n ByName) Less(i, j int) bool { + return n[i].Name < n[j].Name +} diff --git a/tools/gotest2junit/pkg/api/types.go b/tools/gotest2junit/pkg/api/types.go new file mode 100644 index 000000000..58339044c --- /dev/null +++ b/tools/gotest2junit/pkg/api/types.go @@ -0,0 +1,108 @@ +package api + +import "encoding/xml" + +// The below types are directly marshalled into XML. The types correspond to jUnit +// XML schema, but do not contain all valid fields. For instance, the class name +// field for test cases is omitted, as this concept does not directly apply to Go. +// For XML specifications see http://help.catchsoftware.com/display/ET/JUnit+Format +// or view the XSD included in this package as 'junit.xsd' + +// TestSuites represents a flat collection of jUnit test suites. +type TestSuites struct { + XMLName xml.Name `xml:"testsuites"` + + // Suites are the jUnit test suites held in this collection + Suites []*TestSuite `xml:"testsuite"` +} + +// TestSuite represents a single jUnit test suite, potentially holding child suites. +type TestSuite struct { + XMLName xml.Name `xml:"testsuite"` + + // Name is the name of the test suite + Name string `xml:"name,attr"` + + // NumTests records the number of tests in the TestSuite + NumTests uint `xml:"tests,attr"` + + // NumSkipped records the number of skipped tests in the suite + NumSkipped uint `xml:"skipped,attr"` + + // NumFailed records the number of failed tests in the suite + NumFailed uint `xml:"failures,attr"` + + // Duration is the time taken in seconds to run all tests in the suite + Duration float64 `xml:"time,attr"` + + // Properties holds other properties of the test suite as a mapping of name to value + Properties []*TestSuiteProperty `xml:"properties,omitempty"` + + // TestCases are the test cases contained in the test suite + TestCases []*TestCase `xml:"testcase"` + + // Children holds nested test suites + Children []*TestSuite `xml:"testsuite"` +} + +// TestSuiteProperty contains a mapping of a property name to a value +type TestSuiteProperty struct { + XMLName xml.Name `xml:"property"` + + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +// TestCase represents a jUnit test case +type TestCase struct { + XMLName xml.Name `xml:"testcase"` + + // Name is the name of the test case + Name string `xml:"name,attr"` + + // Classname is an attribute set by the package type and is required + Classname string `xml:"classname,attr,omitempty"` + + // Duration is the time taken in seconds to run the test + Duration float64 `xml:"time,attr"` + + // SkipMessage holds the reason why the test was skipped + SkipMessage *SkipMessage `xml:"skipped"` + + // FailureOutput holds the output from a failing test + FailureOutput *FailureOutput `xml:"failure"` + + // SystemOut is output written to stdout during the execution of this test case + SystemOut string `xml:"system-out,omitempty"` + + // SystemErr is output written to stderr during the execution of this test case + SystemErr string `xml:"system-err,omitempty"` +} + +// SkipMessage holds a message explaining why a test was skipped +type SkipMessage struct { + XMLName xml.Name `xml:"skipped"` + + // Message explains why the test was skipped + Message string `xml:"message,attr,omitempty"` +} + +// FailureOutput holds the output from a failing test +type FailureOutput struct { + XMLName xml.Name `xml:"failure"` + + // Message holds the failure message from the test + Message string `xml:"message,attr"` + + // Output holds verbose failure output from the test + Output string `xml:",chardata"` +} + +// TestResult is the result of a test case +type TestResult string + +const ( + TestResultPass TestResult = "pass" + TestResultSkip TestResult = "skip" + TestResultFail TestResult = "fail" +) diff --git a/tools/gotest2junit/test/test_test.go b/tools/gotest2junit/test/test_test.go new file mode 100644 index 000000000..21f4695e8 --- /dev/null +++ b/tools/gotest2junit/test/test_test.go @@ -0,0 +1,18 @@ +// +build output + +package test + +import ( + "testing" +) + +func TestFoo(t *testing.T) { + t.Run("panic", func(t *testing.T) { + panic("here") + }) + t.Run("pass", func(t *testing.T) { + }) + t.Run("skip", func(t *testing.T) { + t.Skip("skipped") + }) +} diff --git a/tools/import-verifier/import-verifier.go b/tools/import-verifier/import-verifier.go new file mode 100644 index 000000000..659f40bf5 --- /dev/null +++ b/tools/import-verifier/import-verifier.go @@ -0,0 +1,359 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" +) + +const ( + rootPackage = "github.com/openshift/service-serving-cert-signer" +) + +// Package is a subset of cmd/go.Package +type Package struct { + ImportPath string `json:",omitempty"` // import path of package in dir + Imports []string `json:",omitempty"` // import paths used by this package + TestImports []string `json:",omitempty"` // imports from TestGoFiles + XTestImports []string `json:",omitempty"` // imports from XTestGoFiles +} + +type ImportRestriction struct { + // CheckedPackageRoots are the roots of the package tree + // that are restricted by this configuration + CheckedPackageRoots []string `json:"checkedPackageRoots"` + // CheckedPackages are the specific packages + // that are restricted by this configuration + CheckedPackages []string `json:"checkedPackages"` + // IgnoredSubTrees are roots of sub-trees of the + // BaseImportPath for which we do not want to enforce + // any import restrictions whatsoever + IgnoredSubTrees []string `json:"ignoredSubTrees,omitempty"` + // AllowedImportPackages are roots of package trees that + // are allowed to be imported for this restriction + AllowedImportPackages []string `json:"allowedImportPackages"` + // AllowedImportPackageRoots are roots of package trees that + // are allowed to be imported for this restriction + AllowedImportPackageRoots []string `json:"allowedImportPackageRoots"` + // ForbiddenImportPackageRoots are roots of package trees that + // are NOT allowed to be imported for this restriction + ForbiddenImportPackageRoots []string `json:"forbiddenImportPackageRoots"` +} + +// ForbiddenImportsFor determines all of the forbidden +// imports for a package given the import restrictions +func (i *ImportRestriction) ForbiddenImportsFor(pkg Package) []string { + if !i.isRestrictedPath(pkg.ImportPath) { + return []string{} + } + return i.forbiddenImportsFor(pkg) +} + +// isRestrictedPath determines if the import path has +// any restrictions placed on it by this configuration. +// A path will be restricted if: +// - it falls under the base import path +// - it does not fall under any of the ignored sub-trees +func (i *ImportRestriction) isRestrictedPath(packageToCheck string) bool { + // if its not under our root, then its a built-in. Everything else is under + // github.com/openshift/service-serving-cert-signer or github.com/openshift/cluster-machine-approver/vendor + if !strings.HasPrefix(packageToCheck, rootPackage) { + return false + } + + // some subtrees are specifically excluded. Not sure if we still need this given + // explicit inclusion + for _, ignored := range i.IgnoredSubTrees { + if strings.HasPrefix(packageToCheck, ignored) { + return false + } + } + + return true +} + +// forbiddenImportsFor determines all of the forbidden +// imports for a package given the import restrictions +// and returns a deduplicated list of them +func (i *ImportRestriction) forbiddenImportsFor(pkg Package) []string { + forbiddenImportSet := map[string]struct{}{} + for _, packageToCheck := range append(pkg.Imports, append(pkg.TestImports, pkg.XTestImports...)...) { + if !i.isAllowed(packageToCheck) { + forbiddenImportSet[relativePackage(packageToCheck)] = struct{}{} + } + } + + var forbiddenImports []string + for imp := range forbiddenImportSet { + forbiddenImports = append(forbiddenImports, imp) + } + return forbiddenImports +} + +// isForbidden determines if an import is forbidden, +// which is true when the import is: +// - of a package under the rootPackage +// - is not of the base import path or a sub-package of it +// - is not of an allowed path or a sub-package of one +func (i *ImportRestriction) isAllowed(packageToCheck string) bool { + // if its not under our root, then its a built-in. Everything else is under + // github.com/openshift/service-serving-cert-signer or github.com/openshift/cluster-machine-approver/vendor + if !strings.HasPrefix(packageToCheck, rootPackage) { + return true + } + if i.isIncludedInRestrictedPackages(packageToCheck) { + return true + } + + for _, forbiddenPackageRoot := range i.ForbiddenImportPackageRoots { + if strings.HasPrefix(forbiddenPackageRoot, "vendor") { + forbiddenPackageRoot = rootPackage + "/" + forbiddenPackageRoot + } + if strings.HasPrefix(packageToCheck, forbiddenPackageRoot) { + return false + } + } + for _, allowedPackage := range i.AllowedImportPackages { + if strings.HasPrefix(allowedPackage, "vendor") { + allowedPackage = rootPackage + "/" + allowedPackage + } + if packageToCheck == allowedPackage { + return true + } + } + for _, allowedPackageRoot := range i.AllowedImportPackageRoots { + if strings.HasPrefix(allowedPackageRoot, "vendor") { + allowedPackageRoot = rootPackage + "/" + allowedPackageRoot + } + if strings.HasPrefix(packageToCheck, allowedPackageRoot) { + return true + } + } + + return false +} + +// isIncludedInRestrictedPackages checks to see if a package is included in the list of packages we're +// restricting. Any package being restricted is assumed to be allowed to import another package being +// restricted since they are grouped +func (i *ImportRestriction) isIncludedInRestrictedPackages(packageToCheck string) bool { + // some subtrees are specifically excluded. Not sure if we still need this given + // explicit inclusion + for _, ignored := range i.IgnoredSubTrees { + if strings.HasPrefix(packageToCheck, ignored) { + return false + } + } + + for _, currBase := range i.CheckedPackageRoots { + if strings.HasPrefix(packageToCheck, currBase) { + return true + } + } + for _, currPackageName := range i.CheckedPackages { + if currPackageName == packageToCheck { + return true + } + } + return false +} + +func relativePackage(absolutePackage string) string { + if strings.HasPrefix(absolutePackage, rootPackage+"/vendor") { + return absolutePackage[len(rootPackage)+1:] + } + return absolutePackage +} + +func main() { + if len(os.Args) != 2 { + log.Fatalf("%s requires the configuration file as it's only argument", os.Args[0]) + } + + configFile := os.Args[1] + importRestrictions, err := loadImportRestrictions(configFile) + if err != nil { + log.Fatalf("Failed to load import restrictions: %v", err) + } + + failedRestrictionCheck := false + for _, restriction := range importRestrictions { + packages := []Package{} + for _, currBase := range restriction.CheckedPackageRoots { + log.Printf("Inspecting imports under %s...\n", currBase) + currPackages, err := resolvePackage(currBase + "/...") + if err != nil { + log.Fatalf("Failed to resolve package tree %v: %v", currBase, err) + } + packages = mergePackages(packages, currPackages) + } + for _, currPackageName := range restriction.CheckedPackages { + log.Printf("Inspecting imports at %s...\n", currPackageName) + currPackages, err := resolvePackage(currPackageName) + if err != nil { + log.Fatalf("Failed to resolve package %v: %v", currPackageName, err) + } + packages = mergePackages(packages, currPackages) + } + + if len(packages) == 0 { + log.Fatalf("No packages found") + } + log.Printf("-- validating imports for %d packages in the tree", len(packages)) + for _, pkg := range packages { + if forbidden := restriction.ForbiddenImportsFor(pkg); len(forbidden) != 0 { + logForbiddenPackages(relativePackage(pkg.ImportPath), forbidden) + failedRestrictionCheck = true + } + } + + // make sure that all the allowed imports are used + if unused := unusedPackageImports(restriction.AllowedImportPackages, packages); len(unused) > 0 { + log.Printf("-- found unused package imports\n") + for _, unusedPackage := range unused { + log.Printf("\t%s\n", unusedPackage) + } + failedRestrictionCheck = true + } + if unused := unusedPackageImportRoots(restriction.AllowedImportPackageRoots, packages); len(unused) > 0 { + log.Printf("-- found unused package import roots\n") + for _, unusedPackage := range unused { + log.Printf("\t%s\n", unusedPackage) + } + failedRestrictionCheck = true + } + + log.Printf("\n") + } + + if failedRestrictionCheck { + os.Exit(1) + } +} + +func unusedPackageImports(allowedPackageImports []string, packages []Package) []string { + ret := []string{} + for _, allowedImport := range allowedPackageImports { + if strings.HasPrefix(allowedImport, "vendor") { + allowedImport = rootPackage + "/" + allowedImport + } + found := false + for _, pkg := range packages { + for _, packageToCheck := range append(pkg.Imports, append(pkg.TestImports, pkg.XTestImports...)...) { + if packageToCheck == allowedImport { + found = true + break + } + } + } + if !found { + ret = append(ret, relativePackage(allowedImport)) + } + } + + return ret +} + +func unusedPackageImportRoots(allowedPackageImportRoots []string, packages []Package) []string { + ret := []string{} + for _, allowedImportRoot := range allowedPackageImportRoots { + if strings.HasPrefix(allowedImportRoot, "vendor") { + allowedImportRoot = rootPackage + "/" + allowedImportRoot + } + found := false + for _, pkg := range packages { + for _, packageToCheck := range append(pkg.Imports, append(pkg.TestImports, pkg.XTestImports...)...) { + if strings.HasPrefix(packageToCheck, allowedImportRoot) { + found = true + break + } + } + } + if !found { + ret = append(ret, relativePackage(allowedImportRoot)) + } + } + + return ret +} + +func mergePackages(existingPackages, currPackages []Package) []Package { + for _, currPackage := range currPackages { + found := false + for _, existingPackage := range existingPackages { + if existingPackage.ImportPath == currPackage.ImportPath { + log.Printf("-- Skipping: %v", currPackage.ImportPath) + found = true + } + } + if !found { + // this was super noisy. + //log.Printf("-- Adding: %v", currPackage.ImportPath) + existingPackages = append(existingPackages, currPackage) + } + } + + return existingPackages +} + +func loadImportRestrictions(configFile string) ([]ImportRestriction, error) { + config, err := ioutil.ReadFile(configFile) + if err != nil { + return nil, fmt.Errorf("failed to load configuration from %s: %v", configFile, err) + } + + var importRestrictions []ImportRestriction + if err := json.Unmarshal(config, &importRestrictions); err != nil { + return nil, fmt.Errorf("failed to unmarshal from %s: %v", configFile, err) + } + + return importRestrictions, nil +} + +func resolvePackage(targetPackage string) ([]Package, error) { + cmd := "go" + args := []string{"list", "-json", targetPackage} + stdout, err := exec.Command(cmd, args...).Output() + if err != nil { + return nil, fmt.Errorf("Failed to run `%s %s`: %v\n", cmd, strings.Join(args, " "), err) + } + + packages, err := decodePackages(bytes.NewReader(stdout)) + if err != nil { + return nil, fmt.Errorf("Failed to decode packages: %v", err) + } + + return packages, nil +} + +func decodePackages(r io.Reader) ([]Package, error) { + // `go list -json` concatenates package definitions + // instead of emitting a single valid JSON, so we + // need to stream the output to decode it into the + // data we are looking for instead of just using a + // simple JSON decoder on stdout + var packages []Package + decoder := json.NewDecoder(r) + for decoder.More() { + var pkg Package + if err := decoder.Decode(&pkg); err != nil { + return nil, fmt.Errorf("invalid package: %v", err) + } + packages = append(packages, pkg) + } + + return packages, nil +} + +func logForbiddenPackages(base string, forbidden []string) { + log.Printf("-- found forbidden imports for %s:\n", base) + for _, forbiddenPackage := range forbidden { + log.Printf("\t%s\n", forbiddenPackage) + } +} diff --git a/tools/junitreport/.gitignore b/tools/junitreport/.gitignore new file mode 100644 index 000000000..5732e7d70 --- /dev/null +++ b/tools/junitreport/.gitignore @@ -0,0 +1 @@ +junitreport diff --git a/tools/junitreport/OWNERS b/tools/junitreport/OWNERS new file mode 100644 index 000000000..59f087213 --- /dev/null +++ b/tools/junitreport/OWNERS @@ -0,0 +1,13 @@ +reviewers: + - stevekuznetsov + - smarterclayton + - soltysh + - adelton + - liggitt + - danmcp +approvers: + - stevekuznetsov + - smarterclayton + - soltysh + - liggitt + - danmcp diff --git a/tools/junitreport/README.md b/tools/junitreport/README.md new file mode 100644 index 000000000..6b4a3b4d6 --- /dev/null +++ b/tools/junitreport/README.md @@ -0,0 +1,47 @@ +# junitreport + +`junitreport` is a tool that allows for the consumption of test output in order to create jUnit XML. + +## Installation + +In order to build and install `junitreport`, from the root of the OpenShift Origin repository, run `hack/build-go.sh tools/junitreport`. + +## Usage + +`junitreport` can read the output of different types of tests. Specify which output is being read with `--type=`. Supported test output types currently include `'gotest'`, for `go test` output, and `'oscmd'`, for `os::cmd` output. The default test type is `'gotest'`. + +`junitreport` can output flat or nested test suites. To choose which type of output to use, set `--suites=` to either `'flat'` or `'nested'`. The default suite output structure is `'flat'`. When creating nested test suites, `junitreport` will use `/` as the delimeter between suite names: `github.com/maintainer/repository/suite` will be parsed as a hierarchy of `github.com`, `github.com/maintainer`, *etc.* If you are requesting nested test suite output but do not want the root suite(s) to be as general as `github.com`, for example, set `--roots=` to be a comma-delimited list of the names of the suites you wish to use as roots. If the parser encounters a package outside of those roots, it will ignore it. This allows a user to provide a root suite and only collect data for children of that root from a larger data set. + +Ensure that the output you are feeding `junitreport` is free of extraneous text - any lines that are not test/suite declarations, metadata, or results are interpreted as test output. Text that you do not expect to see in Jenkins, for example, while looking at the output of a failed test should not be included in the input to `junitreport`. + +Currently, `junitreport` does not support the parsing of parallel test output. + +### Examples + +To parse the output of `go test` into a flat collection of test suites: + +```sh + +$ go test -v -cover ./... | junitreport > report.xml +``` + +To parse the output of `go test` into a nested collection of test suites rooted at `github.com/maintainer`: + +```sh + +$ go test -v -cover ./... | junitreport --suites=nested --roots=github.com/maintainer > report.xml +``` + +### Testing + +`junitreport` has unit tests as well as integration tests. To run the unit tests from the `junitreport` root directory: + +```sh +$ go test -v -cover ./... +``` + +To run the integration tests from the `junitreport` root directory: + +```sh +$ test/integration.sh +``` \ No newline at end of file diff --git a/tools/junitreport/junitreport.go b/tools/junitreport/junitreport.go new file mode 100644 index 000000000..7347ca882 --- /dev/null +++ b/tools/junitreport/junitreport.go @@ -0,0 +1,167 @@ +package main + +import ( + "flag" + "fmt" + "io" + "os" + "strings" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/cmd" +) + +var ( + // parserType is a flag that holds the type of parser to use + parserType string + + // builderType is a flag that holds the type of builder to use + builderType string + + // rootSuites is a flag that holds the comma-delimited list of root suite names + rootSuites string + + // testOutputFile is a flag that holds the path to the file containing test output + testOutputFile string + + // outputFile is a flag that holds the path to the jUnit XML report to be written + outputFile string + + // stream is a flag that determines if a streamed subset of the input stream should be printed as it is read + stream bool +) + +const ( + defaultParserType = "gotest" + defaultBuilderType = "flat" + defaultTestOutputFile = "/dev/stdin" + defaultOutputFile = "/dev/stdout" + defaultFilter = false +) + +func init() { + flag.StringVar(&parserType, "type", defaultParserType, "which type of test output to parse") + flag.StringVar(&builderType, "suites", defaultBuilderType, "which test suite structure to use") + flag.StringVar(&rootSuites, "roots", "", "comma-delimited list of root suite names") + flag.StringVar(&testOutputFile, "f", defaultTestOutputFile, "the path to the file containing test output to consume") + flag.StringVar(&outputFile, "output", defaultOutputFile, "the path to the jUnit XML output file to write") + flag.BoolVar(&stream, "stream", defaultFilter, "print a streamed subset of the input as it is read") +} + +const ( + junitReportUsageLong = `Consume test output to create jUnit XML files and summarize jUnit XML files. + +%[1]s consumes test output through Stdin and creates jUnit XML files. Currently, only the output of 'go test' +and the output of 'oscmd' functions with $JUNIT_REPORT_OUTPUT set are supported. jUnit XML can be build with +nested or flat test suites. Sub-trees of test suites can be selected when using the nested test-suites represen- +tation to only build XML for some subset of the test output. This parser is greedy, so all output not directly +related to a test suite is considered test case output. +` + + junitReportUsage = `Usage: + %[1]s [--type=TEST-OUTPUT-TYPE] [--suites=SUITE-TYPE] [-f=FILE] + %[1]s [-f=FILE] summarize +` + + junitReportExamples = `Examples: + # Consume 'go test' output to create a jUnit XML file + go test -v -cover ./... | %[1]s > report.xml + + # Consume 'go test' output to create a jUnit XML file, while also printing package output as it is generated + go test -v -cover ./... | %[1]s --stream > report.xml + + # Consume 'go test' output from a file to create a jUnit XML file + %[1]s -f testoutput.txt > report.xml + + # Consume 'go test' output to create a specific jUnit XML file + %[1]s --output report.xml + + # Consume 'go test' output to create a jUnit XML file with nested test suites + go test -v -cover ./... | junitreport --suites=nested > report.xml + + # Consume 'go test' output to create a jUnit XML file with nested test suites rooted at 'github.com/maintainer' + go test -v -cover ./... | junitreport --suites=nested --roots=github.com/maintainer > report.xml + + # Describe failures and skipped tests in an existing jUnit XML file + cat report.xml | %[1]s summarize + + # Consume 'os::cmd' output from to create a jUnit XML file + JUNIT_REPORT='true' hack/test-cmd.sh | junitreport --type=os::cmd > report.xml +` +) + +func main() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, junitReportUsageLong+"\n", os.Args[0]) + fmt.Fprintf(os.Stderr, junitReportUsage+"\n", os.Args[0]) + fmt.Fprintf(os.Stderr, junitReportExamples+"\n", os.Args[0]) + fmt.Fprintln(os.Stderr, "Options:") + flag.PrintDefaults() + os.Exit(2) + } + + flag.Parse() + + var rootSuiteNames []string + if len(rootSuites) > 0 { + rootSuiteNames = strings.Split(rootSuites, ",") + } + + var input io.Reader + if testOutputFile == defaultTestOutputFile { + input = os.Stdin + } else { + file, err := os.Open(testOutputFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading input file: %v\n", err) + } + defer file.Close() + input = file + } + + arguments := flag.Args() + // If we are asked to summarize an XML file, that is all we do + if len(arguments) == 1 && arguments[0] == "summarize" { + summary, err := cmd.Summarize(input) + if err != nil { + fmt.Fprintf(os.Stderr, "Error summarizing jUnit XML file: %v\n", err) + os.Exit(1) + } + fmt.Fprint(os.Stdout, summary) + os.Exit(0) + } + if len(arguments) > 1 { + fmt.Fprintf(os.Stderr, "Incorrect usage of %[1]s, see '%[1]s --help' for more details.\n", os.Args[0]) + os.Exit(1) + } + + var output io.Writer + if outputFile == defaultOutputFile { + output = os.Stdout + } else { + file, err := os.Create(outputFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err) + } + defer file.Close() + output = file + } + + // Otherwise, we get ready to parse and generate XML output. + options := cmd.JUnitReportOptions{ + Stream: stream, + Input: input, + Output: output, + } + + err := options.Complete(builderType, parserType, rootSuiteNames) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing flags: %v\n", err) + os.Exit(1) + } + + err = options.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "Error generating output: %v\n", err) + os.Exit(1) + } +} diff --git a/tools/junitreport/pkg/api/junit.xsd b/tools/junitreport/pkg/api/junit.xsd new file mode 100644 index 000000000..92552b33d --- /dev/null +++ b/tools/junitreport/pkg/api/junit.xsd @@ -0,0 +1,203 @@ + + + + JUnit test result schema for the Apache Ant JUnit and JUnitReport tasks +Copyright © 2011, Windy Road Technology Pty. Limited +The Apache Ant JUnit XML Schema is distributed under the terms of the GNU Lesser General Public License (LGPL) http://www.gnu.org/licenses/lgpl.html +Permission to waive conditions of this license may be requested from Windy Road Support (http://windyroad.org/support). + + + + + + + + + + Contains an aggregation of testsuite results + + + + + + + + + + Derived from testsuite/@name in the non-aggregated documents + + + + + Starts at '0' for the first testsuite and is incremented by 1 for each following testsuite + + + + + + + + + + + + Contains the results of exexuting a testsuite + + + + + Properties (e.g., environment settings) set during test execution + + + + + + + + + + + + + + + + + + + + + + + + Indicates that the test errored. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test. Contains as a text node relevant data for the error, e.g., a stack trace + + + + + + + The error message. e.g., if a java exception is thrown, the return value of getMessage() + + + + + The type of error that occured. e.g., if a java execption is thrown the full class name of the exception. + + + + + + + + + Indicates that the test failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals. Contains as a text node relevant data for the failure, e.g., a stack trace + + + + + + + The message specified in the assert + + + + + The type of the assert. + + + + + + + + + + Name of the test method + + + + + Full class name for the class the test method is in. + + + + + Time taken (in seconds) to execute the test + + + + + + + Data that was written to standard out while the test was executed + + + + + + + + + + Data that was written to standard error while the test was executed + + + + + + + + + + + Full class name of the test for non-aggregated testsuite documents. Class name without the package for aggregated testsuites documents + + + + + + + + + + when the test was executed. Timezone may not be specified. + + + + + Host on which the tests were executed. 'localhost' should be used if the hostname cannot be determined. + + + + + + + + + + The total number of tests in the suite + + + + + The total number of tests in the suite that failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals + + + + + The total number of tests in the suite that errorrd. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test. + + + + + Time taken (in seconds) to execute the tests in the suite + + + + + + + + + \ No newline at end of file diff --git a/tools/junitreport/pkg/api/string.go b/tools/junitreport/pkg/api/string.go new file mode 100644 index 000000000..be8e650f4 --- /dev/null +++ b/tools/junitreport/pkg/api/string.go @@ -0,0 +1,37 @@ +package api + +import "fmt" + +// This file implements Stringer for the API types for ease of debugging + +func (t *TestSuites) String() string { + return fmt.Sprintf("Test Suites with suites: %s.", t.Suites) +} + +func (t *TestSuite) String() string { + childDescriptions := []string{} + for _, child := range t.Children { + childDescriptions = append(childDescriptions, child.String()) + } + return fmt.Sprintf("Test Suite %q with properties: %s, %d test cases, of which %d failed and %d were skipped: %s, and children: %s.", t.Name, t.Properties, t.NumTests, t.NumFailed, t.NumSkipped, t.TestCases, childDescriptions) +} + +func (t *TestCase) String() string { + var result, message, output string + result = "passed" + if t.SkipMessage != nil { + result = "skipped" + message = t.SkipMessage.Message + } + if t.FailureOutput != nil { + result = "failed" + message = t.FailureOutput.Message + output = t.FailureOutput.Output + } + + return fmt.Sprintf("Test Case %q %s after %f seconds with message %q and output %q.", t.Name, result, t.Duration, message, output) +} + +func (p *TestSuiteProperty) String() string { + return fmt.Sprintf("%q=%q", p.Name, p.Value) +} diff --git a/tools/junitreport/pkg/api/test_case.go b/tools/junitreport/pkg/api/test_case.go new file mode 100644 index 000000000..ec83e2ddd --- /dev/null +++ b/tools/junitreport/pkg/api/test_case.go @@ -0,0 +1,30 @@ +package api + +import "time" + +// SetDuration sets the runtime duration of the test case +func (t *TestCase) SetDuration(duration string) error { + parsedDuration, err := time.ParseDuration(duration) + if err != nil { + return err + } + + // we round to the millisecond on duration + t.Duration = float64(int(parsedDuration.Seconds()*1000)) / 1000 + return nil +} + +// MarkSkipped marks the test as skipped with the given message +func (t *TestCase) MarkSkipped(message string) { + t.SkipMessage = &SkipMessage{ + Message: message, + } +} + +// MarkFailed marks the test as failed with the given message and output +func (t *TestCase) MarkFailed(message, output string) { + t.FailureOutput = &FailureOutput{ + Message: message, + Output: output, + } +} diff --git a/tools/junitreport/pkg/api/test_suite.go b/tools/junitreport/pkg/api/test_suite.go new file mode 100644 index 000000000..1e36d06ca --- /dev/null +++ b/tools/junitreport/pkg/api/test_suite.go @@ -0,0 +1,67 @@ +package api + +import "time" + +// AddProperty adds a property to the test suite, deduplicating multiple additions of the same property +// by overwriting the previous record to reflect the new values +func (t *TestSuite) AddProperty(name, value string) { + for _, property := range t.Properties { + if property.Name == name { + property.Value = value + return + } + } + + t.Properties = append(t.Properties, &TestSuiteProperty{Name: name, Value: value}) +} + +// AddTestCase adds a test case to the test suite and updates test suite metrics as necessary +func (t *TestSuite) AddTestCase(testCase *TestCase) { + t.NumTests += 1 + + switch { + case testCase.SkipMessage != nil: + t.NumSkipped += 1 + case testCase.FailureOutput != nil: + t.NumFailed += 1 + default: + // we do not preserve output on tests that are not failures or skips + testCase.SystemOut = "" + testCase.SystemErr = "" + } + + t.Duration += testCase.Duration + // we round to the millisecond on duration + t.Duration = float64(int(t.Duration*1000)) / 1000 + + t.TestCases = append(t.TestCases, testCase) +} + +// SetDuration sets the duration of the test suite if this value is not calculated by aggregating the durations +// of all of the substituent test cases. This should *not* be used if the total duration of the test suite is +// calculated as that sum, as AddTestCase will handle that case. +func (t *TestSuite) SetDuration(duration string) error { + parsedDuration, err := time.ParseDuration(duration) + if err != nil { + return err + } + + // we round to the millisecond on duration + t.Duration = float64(int(parsedDuration.Seconds()*1000)) / 1000 + return nil +} + +// ByName implements sort.Interface for []*TestSuite based on the Name field +type ByName []*TestSuite + +func (n ByName) Len() int { + return len(n) +} + +func (n ByName) Swap(i, j int) { + n[i], n[j] = n[j], n[i] +} + +func (n ByName) Less(i, j int) bool { + return n[i].Name < n[j].Name +} diff --git a/tools/junitreport/pkg/api/types.go b/tools/junitreport/pkg/api/types.go new file mode 100644 index 000000000..58339044c --- /dev/null +++ b/tools/junitreport/pkg/api/types.go @@ -0,0 +1,108 @@ +package api + +import "encoding/xml" + +// The below types are directly marshalled into XML. The types correspond to jUnit +// XML schema, but do not contain all valid fields. For instance, the class name +// field for test cases is omitted, as this concept does not directly apply to Go. +// For XML specifications see http://help.catchsoftware.com/display/ET/JUnit+Format +// or view the XSD included in this package as 'junit.xsd' + +// TestSuites represents a flat collection of jUnit test suites. +type TestSuites struct { + XMLName xml.Name `xml:"testsuites"` + + // Suites are the jUnit test suites held in this collection + Suites []*TestSuite `xml:"testsuite"` +} + +// TestSuite represents a single jUnit test suite, potentially holding child suites. +type TestSuite struct { + XMLName xml.Name `xml:"testsuite"` + + // Name is the name of the test suite + Name string `xml:"name,attr"` + + // NumTests records the number of tests in the TestSuite + NumTests uint `xml:"tests,attr"` + + // NumSkipped records the number of skipped tests in the suite + NumSkipped uint `xml:"skipped,attr"` + + // NumFailed records the number of failed tests in the suite + NumFailed uint `xml:"failures,attr"` + + // Duration is the time taken in seconds to run all tests in the suite + Duration float64 `xml:"time,attr"` + + // Properties holds other properties of the test suite as a mapping of name to value + Properties []*TestSuiteProperty `xml:"properties,omitempty"` + + // TestCases are the test cases contained in the test suite + TestCases []*TestCase `xml:"testcase"` + + // Children holds nested test suites + Children []*TestSuite `xml:"testsuite"` +} + +// TestSuiteProperty contains a mapping of a property name to a value +type TestSuiteProperty struct { + XMLName xml.Name `xml:"property"` + + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +// TestCase represents a jUnit test case +type TestCase struct { + XMLName xml.Name `xml:"testcase"` + + // Name is the name of the test case + Name string `xml:"name,attr"` + + // Classname is an attribute set by the package type and is required + Classname string `xml:"classname,attr,omitempty"` + + // Duration is the time taken in seconds to run the test + Duration float64 `xml:"time,attr"` + + // SkipMessage holds the reason why the test was skipped + SkipMessage *SkipMessage `xml:"skipped"` + + // FailureOutput holds the output from a failing test + FailureOutput *FailureOutput `xml:"failure"` + + // SystemOut is output written to stdout during the execution of this test case + SystemOut string `xml:"system-out,omitempty"` + + // SystemErr is output written to stderr during the execution of this test case + SystemErr string `xml:"system-err,omitempty"` +} + +// SkipMessage holds a message explaining why a test was skipped +type SkipMessage struct { + XMLName xml.Name `xml:"skipped"` + + // Message explains why the test was skipped + Message string `xml:"message,attr,omitempty"` +} + +// FailureOutput holds the output from a failing test +type FailureOutput struct { + XMLName xml.Name `xml:"failure"` + + // Message holds the failure message from the test + Message string `xml:"message,attr"` + + // Output holds verbose failure output from the test + Output string `xml:",chardata"` +} + +// TestResult is the result of a test case +type TestResult string + +const ( + TestResultPass TestResult = "pass" + TestResultSkip TestResult = "skip" + TestResultFail TestResult = "fail" +) diff --git a/tools/junitreport/pkg/builder/flat/test_suites_builder.go b/tools/junitreport/pkg/builder/flat/test_suites_builder.go new file mode 100644 index 000000000..e067389be --- /dev/null +++ b/tools/junitreport/pkg/builder/flat/test_suites_builder.go @@ -0,0 +1,29 @@ +package flat + +import ( + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder" +) + +// NewTestSuitesBuilder returns a new flat test suites builder. All test suites consumed +// by this builder will be added to a flat list of suites - no suites will be children of other suites +func NewTestSuitesBuilder() builder.TestSuitesBuilder { + return &flatTestSuitesBuilder{ + testSuites: &api.TestSuites{}, + } +} + +// flatTestSuitesBuilder is a test suites builder that does not nest suites +type flatTestSuitesBuilder struct { + testSuites *api.TestSuites +} + +// AddSuite adds a test suite to the test suites collection being built +func (b *flatTestSuitesBuilder) AddSuite(suite *api.TestSuite) { + b.testSuites.Suites = append(b.testSuites.Suites, suite) +} + +// Build releases the test suites collection being built at whatever current state it is in +func (b *flatTestSuitesBuilder) Build() *api.TestSuites { + return b.testSuites +} diff --git a/tools/junitreport/pkg/builder/flat/test_suites_builder_test.go b/tools/junitreport/pkg/builder/flat/test_suites_builder_test.go new file mode 100644 index 000000000..ee6121726 --- /dev/null +++ b/tools/junitreport/pkg/builder/flat/test_suites_builder_test.go @@ -0,0 +1,73 @@ +package flat + +import ( + "reflect" + "testing" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" +) + +func TestAddSuite(t *testing.T) { + var testCases = []struct { + name string + seedSuites *api.TestSuites + suitesToAdd []*api.TestSuite + expectedSuites *api.TestSuites + }{ + { + name: "empty", + suitesToAdd: []*api.TestSuite{ + { + Name: "testSuite", + }, + }, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "testSuite", + }, + }, + }, + }, + { + name: "populated", + seedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "testSuite", + }, + }, + }, + suitesToAdd: []*api.TestSuite{ + { + Name: "testSuite2", + }, + }, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "testSuite", + }, + { + Name: "testSuite2", + }, + }, + }, + }, + } + + for _, testCase := range testCases { + builder := NewTestSuitesBuilder() + if testCase.seedSuites != nil { + builder.(*flatTestSuitesBuilder).testSuites = testCase.seedSuites + } + + for _, suite := range testCase.suitesToAdd { + builder.AddSuite(suite) + } + + if expected, actual := testCase.expectedSuites, builder.Build(); !reflect.DeepEqual(expected, actual) { + t.Errorf("%s: did not correctly add suites:\n\texpected:\n\t%v,\n\tgot\n\t%v", testCase.name, expected, actual) + } + } +} diff --git a/tools/junitreport/pkg/builder/interfaces.go b/tools/junitreport/pkg/builder/interfaces.go new file mode 100644 index 000000000..9b2538bba --- /dev/null +++ b/tools/junitreport/pkg/builder/interfaces.go @@ -0,0 +1,12 @@ +package builder + +import "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + +// TestSuitesBuilder knows how to aggregate data to form a collection of test suites. +type TestSuitesBuilder interface { + // AddSuite adds a test suite to the collection + AddSuite(suite *api.TestSuite) + + // Build returns the built structure + Build() *api.TestSuites +} diff --git a/tools/junitreport/pkg/builder/nested/test_suites_builder.go b/tools/junitreport/pkg/builder/nested/test_suites_builder.go new file mode 100644 index 000000000..1629df858 --- /dev/null +++ b/tools/junitreport/pkg/builder/nested/test_suites_builder.go @@ -0,0 +1,185 @@ +package nested + +import ( + "sort" + "strings" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder" +) + +// NewTestSuitesBuilder returns a new nested test suites builder. All test suites consumed by +// this builder will be added to a multitree of suites rooted at the suites with the given names. +func NewTestSuitesBuilder(rootSuiteNames []string) builder.TestSuitesBuilder { + restrictedRoots := []*treeNode{} + nodes := map[string]*treeNode{} + for _, name := range rootSuiteNames { + root := &treeNode{suite: &api.TestSuite{Name: name}} + restrictedRoots = append(restrictedRoots, root) + nodes[name] = root + } + + return &nestedTestSuitesBuilder{ + restrictedRoots: restrictedRoots, + nodes: nodes, + } +} + +const ( + // TestSuiteNameDelimiter is the default delimeter for test suite names + TestSuiteNameDelimiter = "/" +) + +// nestedTestSuitesBuilder is a test suites builder that nests suites under a root suite +type nestedTestSuitesBuilder struct { + // restrictedRoots is the service-serving-cert-signeral set of roots created by the constructor, and is populated only + // if the builder is not able to add new roots to the tree, instead needing to add all nodes as + // children of these restricted roots + restrictedRoots []*treeNode + + nodes map[string]*treeNode +} + +type treeNode struct { + // suite is the test suite in this node + suite *api.TestSuite + + // children are child nodes in the tree. this field will remain empty until the tree is built + children []*treeNode + + // parent is the parent of this node. this field can be null + parent *treeNode +} + +// AddSuite adds a suite, encapsulated in a treeNode, to the list of suites that this builder cares about. +// If a suite already exists with the same name as that which is being added, the existing record is over- +// written. If roots are restricted, then test suites to be added are asssumed to be nested under one of +// the root suites created by the constructor method and the attempted addition of a suite not rooted in +// those suites will fail silently to allow for selective tree-building given a root. +func (b *nestedTestSuitesBuilder) AddSuite(suite *api.TestSuite) { + if !allowedToCreate(suite.Name, b.restrictedRoots) { + return + } + + oldVersion, exists := b.nodes[suite.Name] + if exists { + oldVersion.suite = suite + return + } + + b.nodes[suite.Name] = &treeNode{suite: suite} +} + +// allowedToCreate determines if the given name is allowed to be created in light of the restricted roots +func allowedToCreate(name string, restrictedRoots []*treeNode) bool { + if len(restrictedRoots) == 0 { + return true + } + + for _, root := range restrictedRoots { + if strings.HasPrefix(name, root.suite.Name) { + return true + } + } + + return false +} + +// Build builds an api.TestSuites from the list of nodes that is contained in the builder. +func (b *nestedTestSuitesBuilder) Build() *api.TestSuites { + // build a tree from our list of nodes + nodesToAdd := []*treeNode{} + for _, node := range b.nodes { + // make a copy of which nodes we're interested in, otherwise we'll be concurrently modifying b.nodes + nodesToAdd = append(nodesToAdd, node) + } + + for _, node := range nodesToAdd { + parentNode, exists := b.nodes[getParentName(node.suite.Name)] + if !exists { + makeParentsFor(node, b.nodes, b.restrictedRoots) + continue + } + + parentNode.children = append(parentNode.children, node) + node.parent = parentNode + } + + // find the tree's roots + roots := []*treeNode{} + for _, node := range b.nodes { + if node.parent == nil { + roots = append(roots, node) + } + } + + // update all metrics inside of test suites so they encompass those of their children + rootSuites := []*api.TestSuite{} + for _, root := range roots { + updateMetrics(root) + rootSuites = append(rootSuites, root.suite) + } + + // we need to sort our children so that we can ensure reproducible output for testing + sort.Sort(api.ByName(rootSuites)) + + return &api.TestSuites{Suites: rootSuites} +} + +// makeParentsFor recursively creates parents for the child node until a parent is created that doesn't +// contain the delimiter in its name or a restricted root is reached. +func makeParentsFor(child *treeNode, register map[string]*treeNode, restrictedRoots []*treeNode) { + parentName := getParentName(child.suite.Name) + if parentName == "" { + // if there is no parent for this child, we give up + return + } + + if parentNode, exists := register[parentName]; exists { + // if the parent we're trying to add already exists, just use it + parentNode.children = append(parentNode.children, child) + child.parent = parentNode + return + } + + if !allowedToCreate(parentName, restrictedRoots) { + // if the parent we're trying to create doesn't exist but we can't make it, give up + return + } + + parentNode := &treeNode{ + suite: &api.TestSuite{Name: parentName}, + children: []*treeNode{child}, + } + child.parent = parentNode + register[parentName] = parentNode + + makeParentsFor(parentNode, register, restrictedRoots) +} + +// getParentName returns the name of the parent package, regardless of if it exists in the multitree +func getParentName(name string) string { + if !strings.Contains(name, TestSuiteNameDelimiter) { + return "" + } + + delimeterIndex := strings.LastIndex(name, TestSuiteNameDelimiter) + return name[0:delimeterIndex] +} + +// updateMetrics recursively updates all fields in a treeNode's TestSuite +func updateMetrics(root *treeNode) { + for _, child := range root.children { + updateMetrics(child) + // we should be building a tree, so updates on children are independent and we can bring + // in the updated data for this child right away + root.suite.NumTests += child.suite.NumTests + root.suite.NumSkipped += child.suite.NumSkipped + root.suite.NumFailed += child.suite.NumFailed + root.suite.Duration += child.suite.Duration + root.suite.Children = append(root.suite.Children, child.suite) + } + + // we need to sort our children so that we can ensure reproducible output for testing + sort.Sort(api.ByName(root.suite.Children)) +} diff --git a/tools/junitreport/pkg/builder/nested/test_suites_builder_test.go b/tools/junitreport/pkg/builder/nested/test_suites_builder_test.go new file mode 100644 index 000000000..ed4bb98db --- /dev/null +++ b/tools/junitreport/pkg/builder/nested/test_suites_builder_test.go @@ -0,0 +1,180 @@ +package nested + +import ( + "reflect" + "testing" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" +) + +func TestGetParentName(t *testing.T) { + var testCases = []struct { + name string + testName string + expectedParentName string + }{ + { + name: "no parent", + testName: "root", + expectedParentName: "", + }, + { + name: "one parent", + testName: "root/package", + expectedParentName: "root", + }, + { + name: "many parents", + testName: "root/package/subpackage/etc", + expectedParentName: "root/package/subpackage", + }, + } + + for _, testCase := range testCases { + if actual, expected := getParentName(testCase.testName), testCase.expectedParentName; actual != expected { + t.Errorf("%s: did not get correct parent name for test name: expected: %q, got %q", testCase.name, expected, actual) + } + } +} + +func TestAddSuite(t *testing.T) { + var testCases = []struct { + name string + rootSuiteNames []string + seedSuites map[string]*treeNode + suiteToAdd *api.TestSuite + expectedSuites *api.TestSuites + }{ + { + name: "empty adding root", + suiteToAdd: &api.TestSuite{ + Name: "root", + }, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "root", + }, + }, + }, + }, + { + name: "empty adding child", + suiteToAdd: &api.TestSuite{ + Name: "root/child", + }, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "root", + Children: []*api.TestSuite{ + { + Name: "root/child", + }, + }, + }, + }, + }, + }, + { + name: "empty with bounds, adding out of bounds", + rootSuiteNames: []string{"someotherroot"}, + suiteToAdd: &api.TestSuite{ + Name: "root/child", + }, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "someotherroot", + }, + }, + }, + }, + { + name: "populated adding child", + seedSuites: map[string]*treeNode{ + "root": { + suite: &api.TestSuite{ + Name: "root", + }, + }, + }, + suiteToAdd: &api.TestSuite{ + Name: "root/child", + }, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "root", + Children: []*api.TestSuite{ + { + Name: "root/child", + }, + }, + }, + }, + }, + }, + { + name: "empty with bounds, adding in bounds", + rootSuiteNames: []string{"root"}, + suiteToAdd: &api.TestSuite{ + Name: "root/child/grandchild", + }, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "root", + Children: []*api.TestSuite{ + { + Name: "root/child", + Children: []*api.TestSuite{ + { + Name: "root/child/grandchild", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "populated overwriting record", + seedSuites: map[string]*treeNode{ + "root": { + suite: &api.TestSuite{ + Name: "root", + NumTests: 3, + }, + }, + }, + suiteToAdd: &api.TestSuite{ + Name: "root", + NumTests: 4, + }, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "root", + NumTests: 4, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + builder := NewTestSuitesBuilder(testCase.rootSuiteNames) + + if testCase.seedSuites != nil { + builder.(*nestedTestSuitesBuilder).nodes = testCase.seedSuites + } + + builder.AddSuite(testCase.suiteToAdd) + + if actual, expected := builder.Build(), testCase.expectedSuites; !reflect.DeepEqual(actual, expected) { + t.Errorf("%s: did not get correct test suites after addition of test suite:\n\texpected:\n\t%s,\n\tgot\n\t%s", testCase.name, expected, actual) + } + } +} diff --git a/tools/junitreport/pkg/cmd/junitreport.go b/tools/junitreport/pkg/cmd/junitreport.go new file mode 100644 index 000000000..6d8596da9 --- /dev/null +++ b/tools/junitreport/pkg/cmd/junitreport.go @@ -0,0 +1,121 @@ +package cmd + +import ( + "bufio" + "encoding/xml" + "fmt" + "io" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder/flat" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder/nested" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/parser" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/parser/gotest" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/parser/oscmd" +) + +type testSuitesBuilderType string + +const ( + flatBuilderType testSuitesBuilderType = "flat" + nestedBuilderType testSuitesBuilderType = "nested" +) + +var supportedBuilderTypes = []testSuitesBuilderType{flatBuilderType, nestedBuilderType} + +type testParserType string + +const ( + goTestParserType testParserType = "gotest" + osCmdParserType testParserType = "oscmd" +) + +var supportedTestParserTypes = []testParserType{goTestParserType, osCmdParserType} + +type JUnitReportOptions struct { + // BuilderType is the type of test suites builder to use + BuilderType testSuitesBuilderType + + // RootSuiteNames is a list of root suites to be used for nested test suite output if + // the root suite is to be more specific than the suite name without any suite delimeters + // i.e. if `github.com/owner/repo` is to be used instead of `github.com` + RootSuiteNames []string + + // ParserType is the parser type that will be used to parse test output + ParserType testParserType + + // Stream determines if package result lines should be printed to the output as they are found + Stream bool + + // Input is the reader for the test output to be parsed + Input io.Reader + + // Output is the writer for the file to which the XML is written + Output io.Writer +} + +func (o *JUnitReportOptions) Complete(builderType, parserType string, rootSuiteNames []string) error { + switch testSuitesBuilderType(builderType) { + case flatBuilderType: + o.BuilderType = flatBuilderType + case nestedBuilderType: + o.BuilderType = nestedBuilderType + default: + return fmt.Errorf("unrecognized test suites builder type: got %s, expected one of %v", builderType, supportedBuilderTypes) + } + + switch testParserType(parserType) { + case goTestParserType: + o.ParserType = goTestParserType + case osCmdParserType: + o.ParserType = osCmdParserType + default: + return fmt.Errorf("unrecognized test parser type: got %s, expected one of %v", parserType, supportedTestParserTypes) + } + + o.RootSuiteNames = rootSuiteNames + + return nil +} + +func (o *JUnitReportOptions) Run() error { + var builder builder.TestSuitesBuilder + switch o.BuilderType { + case flatBuilderType: + builder = flat.NewTestSuitesBuilder() + case nestedBuilderType: + builder = nested.NewTestSuitesBuilder(o.RootSuiteNames) + } + + var testParser parser.TestOutputParser + switch o.ParserType { + case goTestParserType: + testParser = gotest.NewParser(builder, o.Stream) + case osCmdParserType: + testParser = oscmd.NewParser(builder, o.Stream) + } + + testSuites, err := testParser.Parse(bufio.NewScanner(o.Input)) + if err != nil { + return err + } + + _, err = io.WriteString(o.Output, xml.Header) + if err != nil { + return fmt.Errorf("error writing XML header to file: %v", err) + } + + encoder := xml.NewEncoder(o.Output) + encoder.Indent("", "\t") // no prefix, indent with tabs + + if err := encoder.Encode(testSuites); err != nil { + return fmt.Errorf("error encoding test suites to XML: %v", err) + } + + _, err = io.WriteString(o.Output, "\n") + if err != nil { + return fmt.Errorf("error writing last newline to file: %v", err) + } + + return nil +} diff --git a/tools/junitreport/pkg/cmd/summarize.go b/tools/junitreport/pkg/cmd/summarize.go new file mode 100644 index 000000000..c890b6d74 --- /dev/null +++ b/tools/junitreport/pkg/cmd/summarize.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" +) + +// Summarize reads the input into a TestSuites structure and summarizes the tests contained within, +// bringing attention to tests that did not succeed. +func Summarize(input io.Reader) (string, error) { + var testSuites api.TestSuites + if err := xml.NewDecoder(input).Decode(&testSuites); err != nil { + return "", err + } + + var summary bytes.Buffer + var numTests, numFailed, numSkipped uint + var duration float64 + for _, testSuite := range testSuites.Suites { + numTests += testSuite.NumTests + numFailed += testSuite.NumFailed + numSkipped += testSuite.NumSkipped + duration += testSuite.Duration + } + + verb := "were" + if numSkipped == 1 { + verb = "was" + } + summary.WriteString(fmt.Sprintf("Of %d tests executed in %.3fs, %d succeeded, %d failed, and %d %s skipped.\n\n", numTests, duration, (numTests - numFailed - numSkipped), numFailed, numSkipped, verb)) + + for _, testSuite := range testSuites.Suites { + summarizeTests(testSuite, &summary) + } + + return summary.String(), nil +} + +func summarizeTests(testSuite *api.TestSuite, summary *bytes.Buffer) { + for _, testCase := range testSuite.TestCases { + if testCase.FailureOutput != nil { + summary.WriteString(fmt.Sprintf("In suite %q, test case %q failed:\n%s\n\n", testSuite.Name, testCase.Name, testCase.FailureOutput.Output)) + } + if testCase.SkipMessage != nil { + summary.WriteString(fmt.Sprintf("In suite %q, test case %q was skipped:\n%s\n\n", testSuite.Name, testCase.Name, testCase.SkipMessage.Message)) + } + } + + for _, childSuite := range testSuite.Children { + summarizeTests(childSuite, summary) + } +} diff --git a/tools/junitreport/pkg/errors/errors.go b/tools/junitreport/pkg/errors/errors.go new file mode 100644 index 000000000..8a8f7a69e --- /dev/null +++ b/tools/junitreport/pkg/errors/errors.go @@ -0,0 +1,31 @@ +package errors + +import "fmt" + +// NewSuiteOutOfBoundsError returns a new SuiteOutOfBounds error for the given suite name +func NewSuiteOutOfBoundsError(name string) error { + return &suiteOutOfBoundsError{ + suiteName: name, + } +} + +// suiteOutOfBoundsError describes the failure to place a test suite into a test suite tree because the suite +// in question is not a child of any suite in the tree +type suiteOutOfBoundsError struct { + suiteName string +} + +func (e *suiteOutOfBoundsError) Error() string { + return fmt.Sprintf("the test suite %q could not be placed under any existing roots in the tree", e.suiteName) +} + +// IsSuiteOutOfBoundsError determines if the given error was raised because a suite could not be placed +// in the test suite tree +func IsSuiteOutOfBoundsError(err error) bool { + if err == nil { + return false + } + + _, ok := err.(*suiteOutOfBoundsError) + return ok +} diff --git a/tools/junitreport/pkg/parser/gotest/data_parser.go b/tools/junitreport/pkg/parser/gotest/data_parser.go new file mode 100644 index 000000000..3c6071147 --- /dev/null +++ b/tools/junitreport/pkg/parser/gotest/data_parser.go @@ -0,0 +1,110 @@ +package gotest + +import ( + "regexp" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" +) + +// testStartPattern matches the line in verbose `go test` output that marks the declaration of a test. +// The first submatch of this regex is the name of the test +var testStartPattern = regexp.MustCompile(`^=== RUN\s+([^\s]+)$`) + +// ExtractRun identifies the start of a test output section. +func ExtractRun(line string) (string, bool) { + if matches := testStartPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[1]) > 0 { + return matches[1], true + } + return "", false +} + +// testResultPattern matches the line in verbose `go test` output that marks the result of a test. +// The first submatch of this regex is the result of the test (PASS, FAIL, or SKIP) +// The second submatch of this regex is the name of the test +// The third submatch of this regex is the time taken in seconds for the test to finish +var testResultPattern = regexp.MustCompile(`^(\s*)--- (PASS|FAIL|SKIP):\s+([^\s]+)\s+\((\d+\.\d+)(s| seconds)\)$`) + +// ExtractResult extracts the test result from a test output line. Depth is measured as the leading whitespace +// for the line multiplied by four, which is used to identify output from nested Go subtests. +func ExtractResult(line string) (r api.TestResult, name string, depth int, duration string, ok bool) { + if matches := testResultPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[2]) > 0 { + switch matches[2] { + case "PASS": + r = api.TestResultPass + case "SKIP": + r = api.TestResultSkip + case "FAIL": + r = api.TestResultFail + default: + return "", "", 0, "", false + } + name = matches[3] + duration = matches[4] + "s" + depth = len(matches[1]) / 4 + ok = true + return + } + return "", "", 0, "", false +} + +// testOutputPattern captures a line with leading whitespace. +var testOutputPattern = regexp.MustCompile(`^(\s*)(.*)$`) + +// ExtractOutput captures a line of output indented by whitespace and returns +// the output, the indentation depth (4 spaces is the canonical indentation used by go test), +// and whether the match was successful. +func ExtractOutput(line string) (string, int, bool) { + if matches := testOutputPattern.FindStringSubmatch(line); len(matches) > 1 { + return matches[2], len(matches[1]) / 4, true + } + return "", 0, false +} + +// coverageOutputPattern matches coverage output on a single line. +// The first submatch of this regex is the percent coverage +var coverageOutputPattern = regexp.MustCompile(`coverage:\s+(\d+\.\d+)\% of statements`) + +// packageResultPattern matches the `go test` output for the end of a package. +// The first submatch of this regex matches the result of the test (ok or FAIL) +// The second submatch of this regex matches the name of the package +// The third submatch of this regex matches the time taken in seconds for tests in the package to finish +// The sixth (optional) submatch of this regex is the percent coverage +var packageResultPattern = regexp.MustCompile(`^(ok|FAIL)\s+(.+)[\s\t]+(\d+\.\d+(s| seconds))([\s\t]+coverage:\s+(\d+\.\d+)\% of statements)?$`) + +// ExtractPackage extracts the name of the test suite from a test package line. +func ExtractPackage(line string) (name string, duration string, coverage string, ok bool) { + if matches := packageResultPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[2]) > 0 { + return matches[2], matches[3], matches[5], true + } + return "", "", "", false +} + +// ExtractDuration extracts the package duration from a test output line +func ExtractDuration(line string) (string, bool) { + if resultMatches := packageResultPattern.FindStringSubmatch(line); len(resultMatches) > 3 && len(resultMatches[3]) > 0 { + return resultMatches[3], true + } + return "", false +} + +const ( + coveragePropertyName string = "coverage.statements.pct" +) + +// ExtractProperties extracts any metadata properties of the test suite from a test output line +func ExtractProperties(line string) (map[string]string, bool) { + // the only test suite properties that Go testing can create are coverage values, which can either + // be present on their own line or in the package result line + if matches := coverageOutputPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[1]) > 0 { + return map[string]string{ + coveragePropertyName: matches[1], + }, true + } + + if resultMatches := packageResultPattern.FindStringSubmatch(line); len(resultMatches) > 6 && len(resultMatches[6]) > 0 { + return map[string]string{ + coveragePropertyName: resultMatches[6], + }, true + } + return map[string]string{}, false +} diff --git a/tools/junitreport/pkg/parser/gotest/data_parser_test.go b/tools/junitreport/pkg/parser/gotest/data_parser_test.go new file mode 100644 index 000000000..4ee24e6ef --- /dev/null +++ b/tools/junitreport/pkg/parser/gotest/data_parser_test.go @@ -0,0 +1,242 @@ +package gotest + +import ( + "reflect" + "testing" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" +) + +func TestExtractRunOk(t *testing.T) { + var testCases = []struct { + name string + testLine string + expect bool + }{ + { + name: "basic", + testLine: "=== RUN TestName", + expect: true, + }, + { + name: "numeric", + testLine: "=== RUN 1234", + expect: true, + }, + { + name: "failed print", + testLine: "some other text=== RUN TestName", + }, + } + + for _, testCase := range testCases { + if _, ok := ExtractRun(testCase.testLine); ok != testCase.expect { + t.Errorf("%s: did not correctly determine that line %q marked test beginning", testCase.name, testCase.testLine) + } + } +} + +func TestExtractRunName(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedName string + }{ + { + name: "basic start", + testLine: "=== RUN TestName", + expectedName: "TestName", + }, + { + name: "numeric", + testLine: "=== RUN 1234", + expectedName: "1234", + }, + } + + for _, testCase := range testCases { + actual, contained := ExtractRun(testCase.testLine) + if !contained { + t.Errorf("%s: failed to extract name from line %q", testCase.name, testCase.testLine) + } + if testCase.expectedName != actual { + t.Errorf("%s: did not correctly extract name from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedName, actual) + } + } +} + +func TestExtractResult(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedResult api.TestResult + fail bool + }{ + { + name: "basic", + testLine: "--- PASS: Test (0.10 seconds)", + expectedResult: api.TestResultPass, + }, + { + name: "go1.5.1 timing", + testLine: "--- PASS: TestTwo (0.03s)", + expectedResult: api.TestResultPass, + }, + { + name: "skip", + testLine: "--- SKIP: Test (0.10 seconds)", + expectedResult: api.TestResultSkip, + }, + { + name: "fail", + testLine: "--- FAIL: Test (0.10 seconds)", + expectedResult: api.TestResultFail, + }, + { + name: "failed print", + testLine: "some other text--- FAIL: Test (0.10 seconds)", + expectedResult: api.TestResultFail, + fail: true, + }, + } + + for _, testCase := range testCases { + actual, _, _, _, contained := ExtractResult(testCase.testLine) + if contained != !testCase.fail { + t.Errorf("%s: failed to extract result from line %q", testCase.name, testCase.testLine) + } + if testCase.fail { + continue + } + if testCase.expectedResult != actual { + t.Errorf("%s: did not correctly extract result from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedResult, actual) + } + } +} + +func TestExtractDuration(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedDuration string + fail bool + }{ + { + name: "basic", + testLine: "--- PASS: Test (0.10 seconds)", + expectedDuration: "0.10s", // we make the conversion to time.Duration-parseable units internally + }, + { + name: "go1.5.1 timing", + testLine: "--- PASS: TestTwo (0.03s)", + expectedDuration: "0.03s", + }, + { + name: "failed print", + testLine: "some other text--- PASS: TestTwo (0.03s)", + expectedDuration: "0.03s", + fail: true, + }, + } + + for _, testCase := range testCases { + _, _, _, actual, contained := ExtractResult(testCase.testLine) + if contained != !testCase.fail { + t.Errorf("%s: failed to extract duration from line %q", testCase.name, testCase.testLine) + } + if testCase.fail { + continue + } + if testCase.expectedDuration != actual { + t.Errorf("%s: did not correctly extract duration from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedDuration, actual) + } + } +} + +func TestExtractSuiteName(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedName string + fail bool + }{ + { + name: "basic", + testLine: "ok package/name 0.160s", + expectedName: "package/name", + }, + { + name: "go 1.5.1", + testLine: "ok package/name 0.160s", + expectedName: "package/name", + }, + { + name: "numeric", + testLine: "ok 1234 0.160s", + expectedName: "1234", + }, + { + name: "url", + testLine: "ok github.com/maintainer/repository/package/file 0.160s", + expectedName: "github.com/maintainer/repository/package/file", + }, + { + name: "with coverage", + testLine: `ok package/name 0.400s coverage: 10.0% of statements`, + expectedName: "package/name", + }, + { + name: "failed print", + testLine: `some other textok package/name 0.400s coverage: 10.0% of statements`, + expectedName: "package/name", + fail: true, + }, + } + + for _, testCase := range testCases { + actual, _, _, contained := ExtractPackage(testCase.testLine) + if contained != !testCase.fail { + t.Errorf("%s: failed to extract name from line %q", testCase.name, testCase.testLine) + } + if testCase.fail { + continue + } + if testCase.expectedName != actual { + t.Errorf("%s: did not correctly extract suite name from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedName, actual) + } + } +} + +func TestSuiteProperties(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedProperties map[string]string + }{ + { + name: "basic", + testLine: `coverage: 10.0% of statements`, + expectedProperties: map[string]string{coveragePropertyName: "10.0"}, + }, + { + name: "with package declaration", + testLine: `ok package/name 0.400s coverage: 10.0% of statements`, + expectedProperties: map[string]string{coveragePropertyName: "10.0"}, + }, + { + name: "failed print", + testLine: `some other textcoverage: 10.0% of statements`, + expectedProperties: map[string]string{coveragePropertyName: "10.0"}, + }, + } + + for _, testCase := range testCases { + actual, contained := ExtractProperties(testCase.testLine) + if !contained { + t.Errorf("%s: failed to extract properties from line %q", testCase.name, testCase.testLine) + } + if !reflect.DeepEqual(testCase.expectedProperties, actual) { + t.Errorf("%s: did not correctly extract properties from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedProperties, actual) + } + } +} diff --git a/tools/junitreport/pkg/parser/gotest/example/example_test.go b/tools/junitreport/pkg/parser/gotest/example/example_test.go new file mode 100644 index 000000000..6a19cdf11 --- /dev/null +++ b/tools/junitreport/pkg/parser/gotest/example/example_test.go @@ -0,0 +1,40 @@ +package example + +/* Used to generate output for testing + +import ( + "fmt" + "testing" +) + +func TestSubTestWithFailures(t *testing.T) { + t.Run("subtest-pass-1", func(t *testing.T) {}) + t.Run("subtest-pass-2", func(t *testing.T) {}) + t.Run("subtest-fail-1", func(t *testing.T) { fmt.Printf("text line\n"); t.Logf("log line"); t.Errorf("failed") }) +} + +func TestSubTestWithFirstFailures(t *testing.T) { + t.Run("subtest-fail-1", func(t *testing.T) { fmt.Printf("text line\n"); t.Logf("log line"); t.Errorf("failed") }) + t.Run("subtest-pass-1", func(t *testing.T) {}) + t.Run("subtest-pass-2", func(t *testing.T) {}) +} + +func TestSubTestWithSubTestFailures(t *testing.T) { + t.Run("subtest-pass-1", func(t *testing.T) {}) + t.Run("subtest-pass-2", func(t *testing.T) {}) + t.Run("subtest-fail-1", func(t *testing.T) { + fmt.Printf("text line\n") + t.Logf("log line before") + t.Run("sub-subtest-pass-1", func(t *testing.T) {}) + t.Run("sub-subtest-pass-2", func(t *testing.T) {}) + t.Run("sub-subtest-fail-1", func(t *testing.T) { fmt.Printf("text line\n"); t.Logf("log line"); t.Errorf("failed") }) + t.Logf("log line after") + }) +} + +func TestSubTestWithMiddleFailures(t *testing.T) { + t.Run("subtest-pass-1", func(t *testing.T) {}) + t.Run("subtest-fail-1", func(t *testing.T) { fmt.Printf("text line\n"); t.Logf("log line"); t.Errorf("failed") }) + t.Run("subtest-pass-2", func(t *testing.T) {}) +} +*/ diff --git a/tools/junitreport/pkg/parser/gotest/parser.go b/tools/junitreport/pkg/parser/gotest/parser.go new file mode 100644 index 000000000..d04e9589b --- /dev/null +++ b/tools/junitreport/pkg/parser/gotest/parser.go @@ -0,0 +1,270 @@ +package gotest + +import ( + "bufio" + "fmt" + "strings" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/parser" +) + +// NewParser returns a new parser that's capable of parsing Go unit test output +func NewParser(builder builder.TestSuitesBuilder, stream bool) parser.TestOutputParser { + return &testOutputParser{ + builder: builder, + stream: stream, + } +} + +type testOutputParser struct { + builder builder.TestSuitesBuilder + stream bool +} + +const ( + stateBegin = iota + stateOutput + stateResults + stateComplete +) + +func log(format string, args ...interface{}) { + //fmt.Printf(format, args...) +} + +// Parse parses `go test -v` output into test suites. Test output from `go test -v` is not bookmarked for packages, so +// the parsing strategy is to advance line-by-line, building up a slice of test cases until a package declaration is found, +// at which point all tests cases are added to that package and the process can start again. +func (p *testOutputParser) Parse(input *bufio.Scanner) (*api.TestSuites, error) { + suites := &api.TestSuites{} + + var testNameStack []string + var tests map[string]*api.TestCase + var output map[string][]string + var messages map[string][]string + var currentSuite *api.TestSuite + var state int + var count int + var orderedTests []string + + for input.Scan() { + line := input.Text() + count++ + + log("Line %03d: %d: %s\n", count, state, line) + + switch state { + + case stateBegin: + // this is the first state + name, ok := ExtractRun(line) + if !ok { + // A test that defines a test.M handler can write output prior to test execution. We will drop this because + // we have no place to put it, although the first test case *could* use it in the future. + log(" ignored output outside of suite\n") + continue + } + log(" found run command %s\n", name) + + currentSuite = &api.TestSuite{} + tests = make(map[string]*api.TestCase) + output = make(map[string][]string) + messages = make(map[string][]string) + + orderedTests = []string{name} + testNameStack = []string{name} + tests[testNameStack[0]] = &api.TestCase{ + Name: name, + } + + state = stateOutput + + case stateOutput: + // open a new test for gathering output + if name, ok := ExtractRun(line); ok { + log(" found run command %s\n", name) + test, ok := tests[name] + if !ok { + test = &api.TestCase{ + Name: name, + } + tests[name] = test + } + orderedTests = append(orderedTests, name) + testNameStack = []string{name} + continue + } + + // transition to result mode ONLY if it matches a result at the top level + if result, name, depth, duration, ok := ExtractResult(line); ok && tests[name] != nil && depth == 0 { + test := tests[name] + log(" found result %s %s %s\n", result, name, duration) + switch result { + case api.TestResultPass: + case api.TestResultFail: + test.FailureOutput = &api.FailureOutput{} + case api.TestResultSkip: + test.SkipMessage = &api.SkipMessage{} + } + if err := test.SetDuration(duration); err != nil { + return nil, fmt.Errorf("unexpected duration on line %d: %s", count, duration) + } + testNameStack = []string{name} + state = stateResults + continue + } + + // in output mode, turn output lines into output on the particular test + if _, _, ok := ExtractOutput(line); ok { + log(" found output\n") + output[testNameStack[0]] = append(output[testNameStack[0]], line) + continue + } + log(" fallthrough\n") + + case stateResults: + output, depth, ok := ExtractOutput(line) + if !ok { + return nil, fmt.Errorf("unexpected output on line %d, can't grab results", count) + } + + // we're back to the root, we expect either a new RUN, a test suite end, or this is just an + // output line that was chopped up + if depth == 0 { + if name, ok := ExtractRun(line); ok { + log(" found run %s\n", name) + // starting a new set of runs + orderedTests = append(orderedTests, name) + testNameStack = []string{name} + tests[testNameStack[0]] = &api.TestCase{ + Name: name, + } + state = stateOutput + continue + } + switch { + case line == "PASS", line == "FAIL": + log(" found end of suite\n") + // at the end of the suite + state = stateComplete + default: + // a broken output line that was not indented + log(" found message\n") + name := testNameStack[len(testNameStack)-1] + test := tests[name] + switch { + case test.FailureOutput != nil, test.SkipMessage != nil: + messages[name] = append(messages[name], output) + } + } + continue + } + + // if this is a result AND we have already declared this as a test, parse it + if result, name, _, duration, ok := ExtractResult(output); ok && tests[name] != nil { + log(" found result %s %s (%d)\n", result, name, depth) + test := tests[name] + switch result { + case api.TestResultPass: + case api.TestResultFail: + test.FailureOutput = &api.FailureOutput{} + case api.TestResultSkip: + test.SkipMessage = &api.SkipMessage{} + } + if err := test.SetDuration(duration); err != nil { + return nil, fmt.Errorf("unexpected duration on line %d: %s", count, duration) + } + switch { + case depth >= len(testNameStack): + // we found a new, more deeply nested test + testNameStack = append(testNameStack, name) + default: + if depth < len(testNameStack)-1 { + // the current result is less indented than our current test, so remove the deepest + // items from the stack + testNameStack = testNameStack[:depth] + } + testNameStack[len(testNameStack)-1] = name + } + continue + } + + // treat as regular output at the appropriate depth + log(" found message line %d %v\n", depth, testNameStack) + // BUG: in go test, double nested output is double indented for some reason + if depth >= len(testNameStack) { + depth = len(testNameStack) - 1 + } + name := testNameStack[depth] + log(" name %s\n", name) + if test, ok := tests[name]; ok { + switch { + case test.FailureOutput != nil, test.SkipMessage != nil: + messages[name] = append(messages[name], output) + } + } + + case stateComplete: + // suite exit line + if name, duration, coverage, ok := ExtractPackage(line); ok { + currentSuite.Name = name + if props, ok := ExtractProperties(coverage); ok { + for k, v := range props { + currentSuite.AddProperty(k, v) + } + } + for _, name := range orderedTests { + test := tests[name] + messageLines := messages[name] + var extraOutput []string + for i, s := range messageLines { + if s == "=== OUTPUT" { + log("test %s has OUTPUT section, %d %d\n", name, i, len(messageLines)) + if i < len(messageLines) { + log(" test %s add lines: %d\n", name, len(messageLines[i+1:])) + extraOutput = messageLines[i+1:] + } + messageLines = messageLines[:i] + break + } + } + + switch { + case test.FailureOutput != nil: + test.FailureOutput.Output = strings.Join(messageLines, "\n") + + lines := append(output[name], extraOutput...) + test.SystemOut = strings.Join(lines, "\n") + + case test.SkipMessage != nil: + test.SkipMessage.Message = strings.Join(messageLines, "\n") + + default: + lines := append(output[name], extraOutput...) + test.SystemOut = strings.Join(lines, "\n") + } + + currentSuite.AddTestCase(test) + } + if err := currentSuite.SetDuration(duration); err != nil { + return nil, fmt.Errorf("unexpected duration on line %d: %s", count, duration) + } + suites.Suites = append(suites.Suites, currentSuite) + + state = stateBegin + continue + } + + // coverage only line + if props, ok := ExtractProperties(line); ok { + for k, v := range props { + currentSuite.AddProperty(k, v) + } + } + } + } + + return suites, nil +} diff --git a/tools/junitreport/pkg/parser/gotest/parser_flat_test.go b/tools/junitreport/pkg/parser/gotest/parser_flat_test.go new file mode 100644 index 000000000..086016487 --- /dev/null +++ b/tools/junitreport/pkg/parser/gotest/parser_flat_test.go @@ -0,0 +1,378 @@ +package gotest + +import ( + "bufio" + "os" + "reflect" + "testing" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder/flat" +) + +// TestFlatParse tests that parsing the `go test` output in the test directory with a flat builder works as expected +func TestFlatParse(t *testing.T) { + var testCases = []struct { + name string + testFile string + expectedSuites *api.TestSuites + }{ + { + name: "basic", + testFile: "1.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "failure", + testFile: "2.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + NumFailed: 1, + Duration: 0.15, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + FailureOutput: &api.FailureOutput{ + Output: "file_test.go:11: Error message\nfile_test.go:11: Longer\nerror\nmessage.\n", + }, + }, + { + Name: "TestTwo", + Duration: 0.13, + }, + }, + }, + }, + }, + }, + { + name: "skip", + testFile: "3.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + NumSkipped: 1, + Duration: 0.15, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + SkipMessage: &api.SkipMessage{ + Message: "file_test.go:11: Skip message\n", + }, + }, + { + Name: "TestTwo", + Duration: 0.13, + }, + }, + }, + }, + }, + }, + { + name: "go 1.4", + testFile: "4.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "multiple suites", + testFile: "5.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name1", + NumTests: 2, + Duration: 0.16, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + { + Name: "package/name2", + NumTests: 2, + Duration: 0.15, + NumFailed: 1, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + FailureOutput: &api.FailureOutput{ + Output: "file_test.go:11: Error message\nfile_test.go:11: Longer\nerror\nmessage.\n", + }, + }, + { + Name: "TestTwo", + Duration: 0.13, + }, + }, + }, + }, + }, + }, + { + name: "coverage statement", + testFile: "6.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + Properties: []*api.TestSuiteProperty{ + { + Name: "coverage.statements.pct", + Value: "13.37", + }, + }, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "coverage statement in package result", + testFile: "7.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + Properties: []*api.TestSuiteProperty{ + { + Name: "coverage.statements.pct", + Value: "10.0", + }, + }, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "go 1.5", + testFile: "8.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.05, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + }, + { + Name: "TestTwo", + Duration: 0.03, + }, + }, + }, + }, + }, + }, + { + name: "nested", + testFile: "9.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.05, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + }, + { + Name: "TestTwo", + Duration: 0.03, + }, + }, + }, + { + Name: "package/name/nested", + NumTests: 2, + NumFailed: 1, + NumSkipped: 1, + Duration: 0.05, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + FailureOutput: &api.FailureOutput{ + Output: "file_test.go:11: Error message\nfile_test.go:11: Longer\nerror\nmessage.\n", + }, + }, + { + Name: "TestTwo", + Duration: 0.03, + SkipMessage: &api.SkipMessage{ + Message: "file_test.go:11: Skip message\n", + }, + }, + }, + }, + { + Name: "package/other/nested", + NumTests: 2, + Duration: 0.3, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.1, + }, + { + Name: "TestTwo", + Duration: 0.2, + }, + }, + }, + }, + }, + }, + { + name: "test case timing doesn't add to test suite timing", + testFile: "10.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 2.16, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "coverage statement in package result and inline", + testFile: "11.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + Properties: []*api.TestSuiteProperty{ + { + Name: "coverage.statements.pct", + Value: "10.0", + }, + }, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + parser := NewParser(flat.NewTestSuitesBuilder(), false) + + testFile := "./../../../test/gotest/testdata/" + testCase.testFile + + reader, err := os.Open(testFile) + if err != nil { + t.Fatalf("unexpected error opening file %q: %v", testFile, err) + } + testSuites, err := parser.Parse(bufio.NewScanner(reader)) + if err != nil { + t.Fatalf("unexpected error parsing file: %v", err) + } + + if !reflect.DeepEqual(testSuites, testCase.expectedSuites) { + t.Errorf("did not produce the correct test suites from file:\n%#v\n%#v", testCase.expectedSuites, testSuites) + } + }) + } +} diff --git a/tools/junitreport/pkg/parser/gotest/parser_nested_test.go b/tools/junitreport/pkg/parser/gotest/parser_nested_test.go new file mode 100644 index 000000000..8f2de58c3 --- /dev/null +++ b/tools/junitreport/pkg/parser/gotest/parser_nested_test.go @@ -0,0 +1,539 @@ +package gotest + +import ( + "bufio" + "os" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/util/diff" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder/nested" +) + +// TestNestedParse tests that parsing the `go test` output in the test directory with a nested builder works as expected +func TestNestedParse(t *testing.T) { + var testCases = []struct { + name string + testFile string + rootSuiteNames []string + expectedSuites *api.TestSuites + }{ + { + name: "basic", + testFile: "1.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "basic with restricted root", + testFile: "1.txt", + rootSuiteNames: []string{"package/name"}, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "failure", + testFile: "2.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + NumFailed: 1, + Duration: 0.15, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + FailureOutput: &api.FailureOutput{ + Output: "file_test.go:11: Error message\nfile_test.go:11: Longer\nerror\nmessage.\n", + }, + }, + { + Name: "TestTwo", + Duration: 0.13, + }, + }, + }, + }, + }, + }, + { + name: "skip", + testFile: "3.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + NumSkipped: 1, + Duration: 0.15, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + SkipMessage: &api.SkipMessage{ + Message: "file_test.go:11: Skip message\n", + }, + }, + { + Name: "TestTwo", + Duration: 0.13, + }, + }, + }, + }, + }, + }, + { + name: "go 1.4", + testFile: "4.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "multiple suites", + testFile: "5.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name1", + NumTests: 2, + Duration: 0.16, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + { + Name: "package/name2", + NumTests: 2, + Duration: 0.15, + NumFailed: 1, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + FailureOutput: &api.FailureOutput{ + Output: "file_test.go:11: Error message\nfile_test.go:11: Longer\nerror\nmessage.\n", + }, + }, + { + Name: "TestTwo", + Duration: 0.13, + }, + }, + }, + }, + }, + }, + { + name: "coverage statement", + testFile: "6.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + Properties: []*api.TestSuiteProperty{ + { + Name: "coverage.statements.pct", + Value: "13.37", + }, + }, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "coverage statement in package result", + testFile: "7.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + Properties: []*api.TestSuiteProperty{ + { + Name: "coverage.statements.pct", + Value: "10.0", + }, + }, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "go 1.5", + testFile: "8.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.05, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + }, + { + Name: "TestTwo", + Duration: 0.03, + }, + }, + }, + }, + }, + }, + { + name: "nested", + testFile: "9.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + NumFailed: 0, + NumSkipped: 0, + Duration: 0.05, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + }, + { + Name: "TestTwo", + Duration: 0.03, + }, + }, + }, + { + Name: "package/name/nested", + NumTests: 2, + NumFailed: 1, + NumSkipped: 1, + Duration: 0.05, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.02, + FailureOutput: &api.FailureOutput{ + Output: "file_test.go:11: Error message\nfile_test.go:11: Longer\nerror\nmessage.\n", + }, + }, + { + Name: "TestTwo", + Duration: 0.03, + SkipMessage: &api.SkipMessage{ + Message: "file_test.go:11: Skip message\n", + }, + }, + }, + }, + { + Name: "package/other/nested", + NumTests: 2, + Duration: 0.3, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.1, + }, + { + Name: "TestTwo", + Duration: 0.2, + }, + }, + }, + }, + }, + }, + { + name: "test case timing doesn't add to test suite timing", + testFile: "10.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 2.16, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "coverage statement in package result and inline", + testFile: "11.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 0.16, + Properties: []*api.TestSuiteProperty{ + { + Name: "coverage.statements.pct", + Value: "10.0", + }, + }, + TestCases: []*api.TestCase{ + { + Name: "TestOne", + Duration: 0.06, + }, + { + Name: "TestTwo", + Duration: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "nested tests with inline output", + testFile: "14.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "parser/gotest", + NumTests: 4, + NumFailed: 2, + Duration: 0.019, + TestCases: []*api.TestCase{ + { + Name: "TestSubTestWithFailures", + FailureOutput: &api.FailureOutput{ + Output: "", + }, + }, + { + Name: "TestSubTestWithFailures/subtest-pass-1", + }, + { + Name: "TestSubTestWithFailures/subtest-pass-2", + }, + { + Name: "TestSubTestWithFailures/subtest-fail-1", + SystemOut: "text line\n", + FailureOutput: &api.FailureOutput{ + Output: "data_parser_test.go:14: log line\ndata_parser_test.go:14: failed\n", + }, + }, + }, + }, + }, + }, + }, + { + name: "multi-suite nested output with coverage", + testFile: "15.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "parser/gotest", + NumTests: 4, + NumFailed: 2, + Duration: 0.019, + TestCases: []*api.TestCase{ + { + Name: "TestSubTestWithFailures", + FailureOutput: &api.FailureOutput{ + Output: "", + }, + }, + { + Name: "TestSubTestWithFailures/subtest-pass-1", + }, + { + Name: "TestSubTestWithFailures/subtest-pass-2", + }, + { + Name: "TestSubTestWithFailures/subtest-fail-1", + SystemOut: "text line\n", + FailureOutput: &api.FailureOutput{ + Output: "data_parser_test.go:14: log line\ndata_parser_test.go:14: failed\n", + }, + }, + }, + }, + { + Name: "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/parser/gotest/example", + NumTests: 19, + NumFailed: 9, + Duration: 0.006, + Properties: []*api.TestSuiteProperty{{Name: "coverage.statements.pct", Value: "0.0"}}, + TestCases: []*api.TestCase{ + { + Name: "TestSubTestWithFailures", + FailureOutput: &api.FailureOutput{}, + }, + {Name: "TestSubTestWithFailures/subtest-pass-1"}, + {Name: "TestSubTestWithFailures/subtest-pass-2"}, + { + Name: "TestSubTestWithFailures/subtest-fail-1", + SystemOut: "text line\n", + FailureOutput: &api.FailureOutput{ + Output: "example_test.go:11: log line\nexample_test.go:11: failed\n", + }, + }, + { + Name: "TestSubTestWithFirstFailures", + FailureOutput: &api.FailureOutput{}, + }, + { + Name: "TestSubTestWithFirstFailures/subtest-fail-1", + FailureOutput: &api.FailureOutput{Output: "example_test.go:15: log line\nexample_test.go:15: failed\n"}, + SystemOut: "text line\n", + }, + {Name: "TestSubTestWithFirstFailures/subtest-pass-1"}, + {Name: "TestSubTestWithFirstFailures/subtest-pass-2"}, + { + Name: "TestSubTestWithSubTestFailures", + FailureOutput: &api.FailureOutput{}, + }, + {Name: "TestSubTestWithSubTestFailures/subtest-pass-1"}, + {Name: "TestSubTestWithSubTestFailures/subtest-pass-2"}, + { + Name: "TestSubTestWithSubTestFailures/subtest-fail-1", + FailureOutput: &api.FailureOutput{Output: "example_test.go:25: log line before\nexample_test.go:29: log line after\n"}, + SystemOut: "text line\n", + }, + {Name: "TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-pass-1"}, + {Name: "TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-pass-2"}, + { + Name: "TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-fail-1", + FailureOutput: &api.FailureOutput{Output: "example_test.go:28: log line\nexample_test.go:28: failed\n"}, + SystemOut: "text line\n", + }, + { + Name: "TestSubTestWithMiddleFailures", + FailureOutput: &api.FailureOutput{}, + }, + {Name: "TestSubTestWithMiddleFailures/subtest-pass-1"}, + { + Name: "TestSubTestWithMiddleFailures/subtest-fail-1", + FailureOutput: &api.FailureOutput{Output: "example_test.go:35: log line\nexample_test.go:35: failed\n"}, + SystemOut: "text line\n", + }, + {Name: "TestSubTestWithMiddleFailures/subtest-pass-2"}, + }, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + parser := NewParser(nested.NewTestSuitesBuilder(testCase.rootSuiteNames), false) + + testFile := "./../../../test/gotest/testdata/" + testCase.testFile + + reader, err := os.Open(testFile) + if err != nil { + t.Fatalf("unexpected error opening file %q: %v", testFile, err) + } + testSuites, err := parser.Parse(bufio.NewScanner(reader)) + if err != nil { + t.Fatalf("unexpected error parsing file: %v", err) + } + + if !reflect.DeepEqual(testSuites, testCase.expectedSuites) { + t.Errorf("did not produce the correct test suites from file:\n %s", diff.ObjectReflectDiff(testCase.expectedSuites, testSuites)) + } + }) + } +} diff --git a/tools/junitreport/pkg/parser/interfaces.go b/tools/junitreport/pkg/parser/interfaces.go new file mode 100644 index 000000000..b00194f6c --- /dev/null +++ b/tools/junitreport/pkg/parser/interfaces.go @@ -0,0 +1,12 @@ +package parser + +import ( + "bufio" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" +) + +// TestOutputParser knows how to parse test output to create a collection of test suites +type TestOutputParser interface { + Parse(input *bufio.Scanner) (*api.TestSuites, error) +} diff --git a/tools/junitreport/pkg/parser/oscmd/data_parser.go b/tools/junitreport/pkg/parser/oscmd/data_parser.go new file mode 100644 index 000000000..8e13357a7 --- /dev/null +++ b/tools/junitreport/pkg/parser/oscmd/data_parser.go @@ -0,0 +1,138 @@ +package oscmd + +import ( + "regexp" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/parser/stack" +) + +func newTestDataParser() stack.TestDataParser { + return &testDataParser{ + // testStartPattern matches the test beginning bookend + testStartPattern: regexp.MustCompile(`=== BEGIN TEST CASE ===`), + + // testDeclarationPattern matches the test declaration line, the full match is the name of the test being run + // as we're starting the test declaration line with a Unix path, misprinting a leading newline will cause this + // pattern to match the entire misprinted line + testDeclarationPattern: regexp.MustCompile(`.+:[0-9]+: executing '.+' expecting .+`), + + // testConclusionPattern matches the test conclusion line, and contains the following submatches: + // - 1: test result + // - 2: test duration + // - 3: test name + // - 5: test result message + // In order to make this regex sane, we *require* a end-line anchor and therefore make us a little more fragile + // in the face of broken input + testConclusionPattern: regexp.MustCompile(`(SUCCESS|FAILURE) after ([0-9]+\.[0-9]+s): (.+:[0-9]+: executing '.*' expecting .*?)(: (.*))?$`), + + // testEndPattern matches the test end bookend + testEndPattern: regexp.MustCompile(`=== END TEST CASE ===`), + } +} + +type testDataParser struct { + testStartPattern *regexp.Regexp + testDeclarationPattern *regexp.Regexp + testConclusionPattern *regexp.Regexp + testEndPattern *regexp.Regexp +} + +// MarksBeginning determines if the line marks the beginning of a test case +func (p *testDataParser) MarksBeginning(line string) bool { + return p.testStartPattern.MatchString(line) +} + +// ExtractName extracts the name of the test case from test output lines +func (p *testDataParser) ExtractName(line string) (string, bool) { + // The test declaration pattern is technically a subset of the test conclusion pattern, and will therefore + // match anything the test declaration pattern matches. The match from the test conclusion pattern is more + // correct, if it exists, so we check the conclusion pattern first and return if we have a name candidate. + if matches := p.testConclusionPattern.FindStringSubmatch(line); len(matches) > 3 && len(matches[3]) > 0 { + return matches[3], true + } + + if matches := p.testDeclarationPattern.FindStringSubmatch(line); len(matches) > 0 && len(matches[0]) > 0 { + return matches[0], true + } + + return "", false +} + +// ExtractResult extracts the test result from a test output line +func (p *testDataParser) ExtractResult(line string) (api.TestResult, bool) { + if matches := p.testConclusionPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[1]) > 0 { + switch matches[1] { + case "SUCCESS": + return api.TestResultPass, true + case "FAILURE": + return api.TestResultFail, true + } + } + + return "", false +} + +// ExtractDuration extracts the test duration from a test output line +func (p *testDataParser) ExtractDuration(line string) (string, bool) { + if matches := p.testConclusionPattern.FindStringSubmatch(line); len(matches) > 2 && len(matches[2]) > 0 { + return matches[2], true + } + + return "", false +} + +// ExtractMessage extracts a message (e.g. for signalling why a failure or skip occurred) from a test output line +func (p *testDataParser) ExtractMessage(line string) (string, bool) { + if matches := p.testConclusionPattern.FindStringSubmatch(line); len(matches) > 5 && len(matches[5]) > 0 { + return matches[5], true + } + + return "", false +} + +// MarksCompletion determines if the line marks the completion of a test case +func (p *testDataParser) MarksCompletion(line string) bool { + return p.testEndPattern.MatchString(line) +} + +func newTestSuiteDataParser() stack.TestSuiteDataParser { + return &testSuiteDataParser{ + // suiteDeclarationPattern matches the suite declaration line and has the following submatches: + // - 1: suite name + suiteDeclarationPattern: regexp.MustCompile(`=== BEGIN TEST SUITE (.*) ===`), + + // suiteConclusionPattern matches the suite conclusion line + suiteConclusionPattern: regexp.MustCompile(`=== END TEST SUITE ===`), + } +} + +type testSuiteDataParser struct { + suiteDeclarationPattern *regexp.Regexp + suiteConclusionPattern *regexp.Regexp +} + +// MarksBeginning determines if the line marks the beginning of a test suite +func (p *testSuiteDataParser) MarksBeginning(line string) bool { + return p.suiteDeclarationPattern.MatchString(line) +} + +// ExtractName extracts the name of the test suite from a test output line +func (p *testSuiteDataParser) ExtractName(line string) (string, bool) { + if matches := p.suiteDeclarationPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[1]) > 0 { + return matches[1], true + } + + return "", false +} + +// ExtractProperties extracts any metadata properties of the test suite from a test output line +func (p *testSuiteDataParser) ExtractProperties(line string) (map[string]string, bool) { + // `os::cmd` suites cannot expose properties + return map[string]string{}, false +} + +// MarksCompletion determines if the line marks the completion of a test suite +func (p *testSuiteDataParser) MarksCompletion(line string) bool { + return p.suiteConclusionPattern.MatchString(line) +} diff --git a/tools/junitreport/pkg/parser/oscmd/data_parser_test.go b/tools/junitreport/pkg/parser/oscmd/data_parser_test.go new file mode 100644 index 000000000..76e9bad28 --- /dev/null +++ b/tools/junitreport/pkg/parser/oscmd/data_parser_test.go @@ -0,0 +1,300 @@ +package oscmd + +import ( + "testing" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" +) + +func TestMarksTestBeginning(t *testing.T) { + var testCases = []struct { + name string + testLine string + }{ + { + name: "default", + testLine: "=== BEGIN TEST CASE ===", + }, + { + name: "failed print before", + testLine: "some other text=== BEGIN TEST CASE ===", + }, + { + name: "failed print after", + testLine: "=== BEGIN TEST CASE ===some other text after", + }, + } + + parser := newTestDataParser() + for _, testCase := range testCases { + if !parser.MarksBeginning(testCase.testLine) { + t.Errorf("%s: did not correctly determine that line %q marked test beginning", testCase.name, testCase.testLine) + } + } +} + +func TestExtractTestName(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedName string + }{ + { + name: "test declaration", + testLine: `hack/test-cmd.sh:152: executing 'openshift ex validate master-config /tmp/openshift/test-cmd//master-config-broken.yaml' expecting failure and text 'ERROR'`, + expectedName: `hack/test-cmd.sh:152: executing 'openshift ex validate master-config /tmp/openshift/test-cmd//master-config-broken.yaml' expecting failure and text 'ERROR'`, + }, + { + name: "test conclusion success", + testLine: `SUCCESS after 0.041s: hack/../test/cmd/basicresources.sh:21: executing 'oc create -f test/testdata/resource-builder/directory -f test/testdata/resource-builder/json-no-extension -f test/testdata/resource-builder/yml-no-extension' expecting success`, + expectedName: `hack/../test/cmd/basicresources.sh:21: executing 'oc create -f test/testdata/resource-builder/directory -f test/testdata/resource-builder/json-no-extension -f test/testdata/resource-builder/yml-no-extension' expecting success`, + }, + { + name: "test conclusion failure", + testLine: `FAILURE after 30.239s: hack/../test/cmd/builds.sh:68: executing 'oc new-build -D "FROM centos:7" -o json | python -m json.tool' expecting success: the command returned the wrong error code`, + expectedName: `hack/../test/cmd/builds.sh:68: executing 'oc new-build -D "FROM centos:7" -o json | python -m json.tool' expecting success`, + }, + { + name: "failed print: test conclusion success", + testLine: `some other textSUCCESS after 0.041s: hack/../test/cmd/basicresources.sh:21: executing 'oc create -f test/testdata/resource-builder/directory -f test/testdata/resource-builder/json-no-extension -f test/testdata/resource-builder/yml-no-extension' expecting success`, + expectedName: `hack/../test/cmd/basicresources.sh:21: executing 'oc create -f test/testdata/resource-builder/directory -f test/testdata/resource-builder/json-no-extension -f test/testdata/resource-builder/yml-no-extension' expecting success`, + }, + } + + parser := newTestDataParser() + for _, testCase := range testCases { + actual, contained := parser.ExtractName(testCase.testLine) + if !contained { + t.Errorf("%s: failed to extract name from line %q", testCase.name, testCase.testLine) + } + if testCase.expectedName != actual { + t.Errorf("%s: did not correctly extract name from line:\n%q:\n\texpected\n\t%q,\n\tgot\n\t%q", testCase.name, testCase.testLine, testCase.expectedName, actual) + } + } +} + +func TestExtractResult(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedResult api.TestResult + }{ + { + name: "success", + testLine: `SUCCESS after 0.046s: hack/../test/cmd/basicresources.sh:35: executing 'oc delete pods hello-openshift' expecting success`, + expectedResult: api.TestResultPass, + }, + { + name: "failure", + testLine: `FAILURE after 30.239s: hack/../test/cmd/builds.sh:68: executing 'oc new-build -D "FROM centos:7" -o json | python -m json.tool' expecting success: the command returned the wrong error code`, + expectedResult: api.TestResultFail, + }, + { + name: "try until failure", + testLine: `SUCCESS after 0.044s: hack/../test/cmd/basicresources.sh:41: executing 'oc label pod/hello-openshift acustom=label' expecting success; re-trying every 0.2s until completion or 60.000s`, + expectedResult: api.TestResultPass, + }, + } + + parser := newTestDataParser() + for _, testCase := range testCases { + actual, contained := parser.ExtractResult(testCase.testLine) + if !contained { + t.Errorf("%s: failed to extract result from line %q", testCase.name, testCase.testLine) + } + if testCase.expectedResult != actual { + t.Errorf("%s: did not correctly extract result from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedResult, actual) + } + } +} + +func TestExtractDuration(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedDuration string + }{ + { + name: "test conclusion success", + testLine: `SUCCESS after 0.041s: hack/../test/cmd/basicresources.sh:21: executing 'oc create -f test/testdata/resource-builder/directory -f test/testdata/resource-builder/json-no-extension -f test/testdata/resource-builder/yml-no-extension' expecting success`, + expectedDuration: "0.041s", + }, + { + name: "test conclusion failure", + testLine: `FAILURE after 30.239s: hack/../test/cmd/builds.sh:68: executing 'oc new-build -D "FROM centos:7" -o json | python -m json.tool' expecting success: the command returned the wrong error code`, + expectedDuration: "30.239s", + }, + } + + parser := newTestDataParser() + for _, testCase := range testCases { + actual, contained := parser.ExtractDuration(testCase.testLine) + if !contained { + t.Errorf("%s: failed to extract duration from line %q", testCase.name, testCase.testLine) + } + if testCase.expectedDuration != actual { + t.Errorf("%s: did not correctly extract duration from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedDuration, actual) + } + } +} + +func TestExtractMessage(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedMessage string + }{ + { + name: "fail on error code", + testLine: `FAILURE after 0.041s: hack/../test/cmd/help.sh:32: executing 'oc' expecting success: the command returned the wrong error code`, + expectedMessage: "the command returned the wrong error code", + }, + { + name: "fail on text", + testLine: `FAILURE after 0.027s: hack/../test/cmd/help.sh:39: executing 'oc' expecting success and text 'Build and Deploy Commands:': the output content test failed`, + expectedMessage: "the output content test failed", + }, + { + name: "fail on both error code and text", + testLine: `FAILURE after 0.024s: hack/../test/cmd/help.sh:40: executing 'oc' expecting success and text 'Other Commands:': the command returned the wrong error code; the output content test failed`, + expectedMessage: "the command returned the wrong error code; the output content test failed", + }, + { + name: "fail on timeout", + testLine: `FAILURE after 13.514s: hack/../test/cmd/images.sh:54: executing 'oc get imagestreamtags wildfly:latest' expecting success; re-trying every 0.2s until completion or 60.000s: the command timed out`, + expectedMessage: "the command timed out", + }, + } + + parser := newTestDataParser() + for _, testCase := range testCases { + actual, contained := parser.ExtractMessage(testCase.testLine) + if !contained { + t.Errorf("%s: failed to extract duration from line %q", testCase.name, testCase.testLine) + } + if testCase.expectedMessage != actual { + t.Errorf("%s: did not correctly extract message from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedMessage, actual) + } + } +} + +func TestMarksTestCompletion(t *testing.T) { + var testCases = []struct { + name string + testLine string + }{ + { + name: "default", + testLine: "=== END TEST CASE ===", + }, + { + name: "failed print before", + testLine: "some other text=== END TEST CASE ===", + }, + { + name: "failed print after", + testLine: "=== END TEST CASE ===some other text after", + }, + } + + parser := newTestDataParser() + for _, testCase := range testCases { + if !parser.MarksCompletion(testCase.testLine) { + t.Errorf("%s: did not correctly determine that line %q marked test completion", testCase.name, testCase.testLine) + } + } +} + +func TestMarksSuiteBeginning(t *testing.T) { + var testCases = []struct { + name string + testLine string + }{ + { + name: "basic", + testLine: "=== BEGIN TEST SUITE package/name ===", + }, + { + name: "numeric", + testLine: "=== BEGIN TEST SUITE 1234 ===", + }, + { + name: "url", + testLine: "=== BEGIN TEST SUITE github.com/maintainer/repository/package/file ===", + }, + { + name: "failed print", + testLine: `some other textok=== BEGIN TEST SUITE package/name ===`, + }, + } + + parser := newTestSuiteDataParser() + for _, testCase := range testCases { + if !parser.MarksBeginning(testCase.testLine) { + t.Errorf("%s: did not correctly determine that line %q marked the start of a suite", testCase.name, testCase.testLine) + } + } +} + +func TestExtractSuiteName(t *testing.T) { + var testCases = []struct { + name string + testLine string + expectedName string + }{ + { + name: "basic", + testLine: "=== BEGIN TEST SUITE package/name ===", + expectedName: "package/name", + }, + { + name: "numeric", + testLine: "=== BEGIN TEST SUITE 1234 ===", + expectedName: "1234", + }, + { + name: "url", + testLine: "=== BEGIN TEST SUITE github.com/maintainer/repository/package/file ===", + expectedName: "github.com/maintainer/repository/package/file", + }, + { + name: "failed print", + testLine: `some other text=== BEGIN TEST SUITE package/name ===`, + expectedName: "package/name", + }, + } + + parser := newTestSuiteDataParser() + for _, testCase := range testCases { + actual, contained := parser.ExtractName(testCase.testLine) + if !contained { + t.Errorf("%s: failed to extract name from line %q", testCase.name, testCase.testLine) + } + if testCase.expectedName != actual { + t.Errorf("%s: did not correctly extract suite name from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedName, actual) + } + } +} + +func TestMarksSuiteCompletion(t *testing.T) { + var testCases = []struct { + name string + testLine string + }{ + { + name: "basic", + testLine: "=== END TEST SUITE ===", + }, + { + name: "failed print", + testLine: `some other text=== END TEST SUITE ===`, + }, + } + + parser := newTestSuiteDataParser() + for _, testCase := range testCases { + if !parser.MarksCompletion(testCase.testLine) { + t.Errorf("%s: did not correctly determine that line %q marked the end of a suite", testCase.name, testCase.testLine) + } + } +} diff --git a/tools/junitreport/pkg/parser/oscmd/parser.go b/tools/junitreport/pkg/parser/oscmd/parser.go new file mode 100644 index 000000000..f152e7376 --- /dev/null +++ b/tools/junitreport/pkg/parser/oscmd/parser.go @@ -0,0 +1,12 @@ +package oscmd + +import ( + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/parser" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/parser/stack" +) + +// NewParser returns a new parser that's capable of parsing `os::cmd` test output +func NewParser(builder builder.TestSuitesBuilder, stream bool) parser.TestOutputParser { + return stack.NewParser(builder, newTestDataParser(), newTestSuiteDataParser(), stream) +} diff --git a/tools/junitreport/pkg/parser/oscmd/parser_flat_test.go b/tools/junitreport/pkg/parser/oscmd/parser_flat_test.go new file mode 100644 index 000000000..b4d082ae4 --- /dev/null +++ b/tools/junitreport/pkg/parser/oscmd/parser_flat_test.go @@ -0,0 +1,209 @@ +package oscmd + +import ( + "bufio" + "os" + "reflect" + "testing" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder/flat" +) + +// TestFlatParse tests that parsing the `os::cmd` output in the test directory with a flat builder works as expected +func TestFlatParse(t *testing.T) { + var testCases = []struct { + name string + testFile string + expectedSuites *api.TestSuites + }{ + { + name: "basic", + testFile: "1.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + }, + { + name: "failure", + testFile: "2.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + NumFailed: 1, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + FailureOutput: &api.FailureOutput{ + Output: `=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE ===`, + Message: "the command returned the wrong error code", + }, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + }, + { + name: "multiple suites", + testFile: "3.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + { + Name: "package/name2", + NumTests: 2, + NumFailed: 1, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + FailureOutput: &api.FailureOutput{ + Output: `=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE ===`, + Message: "the command returned the wrong error code", + }, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + }, + { + name: "nested", + testFile: "4.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + { + Name: "package/name/nested", + NumTests: 2, + NumFailed: 1, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + FailureOutput: &api.FailureOutput{ + Output: `=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE ===`, + Message: "the command returned the wrong error code", + }, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + { + Name: "package/other/nested", + NumTests: 2, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + parser := NewParser(flat.NewTestSuitesBuilder(), false) + + testFile := "./../../../test/oscmd/testdata/" + testCase.testFile + + reader, err := os.Open(testFile) + if err != nil { + t.Errorf("%s: unexpected error opening file %q: %v", testCase.name, testFile, err) + continue + } + testSuites, err := parser.Parse(bufio.NewScanner(reader)) + if err != nil { + t.Errorf("%s: unexpected error parsing file: %v", testCase.name, err) + continue + } + + if !reflect.DeepEqual(testSuites, testCase.expectedSuites) { + t.Errorf("%s: did not produce the correct test suites from file:\n\texpected:\n\t%v,\n\tgot\n\t%v", testCase.name, testCase.expectedSuites, testSuites) + } + } +} diff --git a/tools/junitreport/pkg/parser/oscmd/parser_nested_test.go b/tools/junitreport/pkg/parser/oscmd/parser_nested_test.go new file mode 100644 index 000000000..d8bc5d2bb --- /dev/null +++ b/tools/junitreport/pkg/parser/oscmd/parser_nested_test.go @@ -0,0 +1,275 @@ +package oscmd + +import ( + "bufio" + "os" + "reflect" + "testing" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder/nested" +) + +// TestNestedParse tests that parsing the `go test` output in the test directory with a nested builder works as expected +func TestNestedParse(t *testing.T) { + var testCases = []struct { + name string + testFile string + rootSuiteNames []string + expectedSuites *api.TestSuites + }{ + { + name: "basic", + testFile: "1.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package", + NumTests: 2, + Duration: 11.245, + Children: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "basic with restricted root", + testFile: "1.txt", + rootSuiteNames: []string{"package/name"}, + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + }, + { + name: "failure", + testFile: "2.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package", + NumTests: 2, + NumFailed: 1, + Duration: 11.245, + Children: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + NumFailed: 1, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + FailureOutput: &api.FailureOutput{ + Output: `=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE ===`, + Message: "the command returned the wrong error code", + }, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "multiple suites", + testFile: "3.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package", + NumTests: 4, + Duration: 22.49, + NumFailed: 1, + Children: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 2, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + { + Name: "package/name2", + NumTests: 2, + NumFailed: 1, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + FailureOutput: &api.FailureOutput{ + Output: `=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE ===`, + Message: "the command returned the wrong error code", + }, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "nested", + testFile: "4.txt", + expectedSuites: &api.TestSuites{ + Suites: []*api.TestSuite{ + { + Name: "package", + NumTests: 6, + Duration: 33.735, + NumFailed: 1, + Children: []*api.TestSuite{ + { + Name: "package/name", + NumTests: 4, + Duration: 22.49, + NumFailed: 1, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + Children: []*api.TestSuite{ + { + Name: "package/name/nested", + NumTests: 2, + NumFailed: 1, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + FailureOutput: &api.FailureOutput{ + Output: `=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE ===`, + Message: "the command returned the wrong error code", + }, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + { + Name: "package/other", + NumTests: 2, + Duration: 11.245, + Children: []*api.TestSuite{ + { + Name: "package/other/nested", + NumTests: 2, + Duration: 11.245, + TestCases: []*api.TestCase{ + { + Name: `package/name/file.sh:23: executing 'some command' expecting success`, + Duration: 0.123, + }, + { + Name: `package/name/file.sh:24: executing 'some other command' expecting success`, + Duration: 11.123, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + parser := NewParser(nested.NewTestSuitesBuilder(testCase.rootSuiteNames), false) + + testFile := "./../../../test/oscmd/testdata/" + testCase.testFile + + reader, err := os.Open(testFile) + if err != nil { + t.Errorf("%s: unexpected error opening file %q: %v", testCase.name, testFile, err) + continue + } + testSuites, err := parser.Parse(bufio.NewScanner(reader)) + if err != nil { + t.Errorf("%s: unexpected error parsing file: %v", testCase.name, err) + continue + } + + if !reflect.DeepEqual(testSuites, testCase.expectedSuites) { + t.Errorf("%s: did not produce the correct test suites from file:\n\texpected:\n\t%v,\n\tgot\n\t%v", testCase.name, testCase.expectedSuites, testSuites) + } + } +} diff --git a/tools/junitreport/pkg/parser/stack/interfaces.go b/tools/junitreport/pkg/parser/stack/interfaces.go new file mode 100644 index 000000000..01825355c --- /dev/null +++ b/tools/junitreport/pkg/parser/stack/interfaces.go @@ -0,0 +1,39 @@ +package stack + +import "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + +// TestDataParser knows how to take raw test data and extract the useful information from it +type TestDataParser interface { + // MarksBeginning determines if the line marks the beginning of a test case + MarksBeginning(line string) bool + + // ExtractName extracts the name of the test case from test output lines + ExtractName(line string) (name string, succeeded bool) + + // ExtractResult extracts the test result from a test output line + ExtractResult(line string) (result api.TestResult, succeeded bool) + + // ExtractDuration extracts the test duration from a test output line + ExtractDuration(line string) (duration string, succeeded bool) + + // ExtractMessage extracts a message (e.g. for signalling why a failure or skip occurred) from a test output line + ExtractMessage(line string) (message string, succeeded bool) + + // MarksCompletion determines if the line marks the completion of a test case + MarksCompletion(line string) bool +} + +// TestSuiteDataParser knows how to take raw test suite data and extract the useful information from it +type TestSuiteDataParser interface { + // MarksBeginning determines if the line marks the beginning of a test suite + MarksBeginning(line string) bool + + // ExtractName extracts the name of the test suite from a test output line + ExtractName(line string) (name string, succeeded bool) + + // ExtractProperties extracts any metadata properties of the test suite from a test output line + ExtractProperties(line string) (properties map[string]string, succeeded bool) + + // MarksCompletion determines if the line marks the completion of a test suite + MarksCompletion(line string) bool +} diff --git a/tools/junitreport/pkg/parser/stack/parser.go b/tools/junitreport/pkg/parser/stack/parser.go new file mode 100644 index 000000000..2b31a26d4 --- /dev/null +++ b/tools/junitreport/pkg/parser/stack/parser.go @@ -0,0 +1,138 @@ +package stack + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/builder" + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/parser" +) + +// NewParser returns a new parser that's capable of parsing Go unit test output +func NewParser(builder builder.TestSuitesBuilder, testParser TestDataParser, suiteParser TestSuiteDataParser, stream bool) parser.TestOutputParser { + return &testOutputParser{ + builder: builder, + testParser: testParser, + suiteParser: suiteParser, + stream: stream, + } +} + +// testOutputParser uses a stack to parse test output. Critical assumptions that this parser makes are: +// 1 - packages may be nested but tests may not +// 2 - no package declarations will occur within the boundaries of a test +// 3 - all tests and packages are fully bounded by a start and result line +// 4 - if a package or test declaration occurs after the start of a package but before its result, +// the sub-package's or member test's result line will occur before that of the parent package +// i.e. any test or package overlap will necessarily mean that one package's lines are a superset +// of any lines of tests or other packages overlapping with it +// 5 - any text in the input file that doesn't match the parser regex is necessarily the output of the +// current test being built +type testOutputParser struct { + builder builder.TestSuitesBuilder + + testParser TestDataParser + suiteParser TestSuiteDataParser + + stream bool +} + +// Parse parses output syntax of a specific class, the assumptions of which are outlined in the struct definition. +// The specific boundary markers and metadata encodings are free to vary as long as regex can be build to extract them +// from test output. +func (p *testOutputParser) Parse(input *bufio.Scanner) (*api.TestSuites, error) { + inProgress := NewTestSuiteStack() + + var currentTest *api.TestCase + var currentResult api.TestResult + var currentOutput []string + var currentMessage string + + for input.Scan() { + line := input.Text() + isTestOutput := true + + if p.testParser.MarksBeginning(line) { + currentTest = &api.TestCase{} + currentResult = api.TestResultFail + currentOutput = []string{} + currentMessage = "" + } + + if name, contained := p.testParser.ExtractName(line); contained { + currentTest.Name = name + } + + if result, contained := p.testParser.ExtractResult(line); contained { + currentResult = result + } + + if duration, contained := p.testParser.ExtractDuration(line); contained { + if err := currentTest.SetDuration(duration); err != nil { + return nil, err + } + } + + if message, contained := p.testParser.ExtractMessage(line); contained { + currentMessage = message + } + + if p.testParser.MarksCompletion(line) { + currentOutput = append(currentOutput, line) + // if we have finished the current test case, we finalize our current test, add it to the package + // at the head of our in progress package stack, and clear our current test record. + switch currentResult { + case api.TestResultSkip: + currentTest.MarkSkipped(currentMessage) + case api.TestResultFail: + output := strings.Join(currentOutput, "\n") + currentTest.MarkFailed(currentMessage, output) + } + + if inProgress.Peek() == nil { + return nil, fmt.Errorf("found test case %q outside of a test suite", currentTest.Name) + } + + inProgress.Peek().AddTestCase(currentTest) + currentTest = &api.TestCase{} + } + + if p.suiteParser.MarksBeginning(line) { + // if we encounter the beginning of a suite, we create a new suite to be considered and + // add it to the head of our in progress package stack + inProgress.Push(&api.TestSuite{}) + isTestOutput = false + } + + if name, contained := p.suiteParser.ExtractName(line); contained { + inProgress.Peek().Name = name + isTestOutput = false + } + + if properties, contained := p.suiteParser.ExtractProperties(line); contained { + for propertyName := range properties { + inProgress.Peek().AddProperty(propertyName, properties[propertyName]) + } + isTestOutput = false + } + + if p.suiteParser.MarksCompletion(line) { + if p.stream { + fmt.Fprintln(os.Stdout, line) + } + + // if we encounter the end of a suite, we remove the suite at the head of the in progress stack + p.builder.AddSuite(inProgress.Pop()) + isTestOutput = false + } + + // we want to associate every line other than those directly involved with test suites as output of a test case + if isTestOutput { + currentOutput = append(currentOutput, line) + } + } + return p.builder.Build(), nil +} diff --git a/tools/junitreport/pkg/parser/stack/stack.go b/tools/junitreport/pkg/parser/stack/stack.go new file mode 100644 index 000000000..f2ca9c449 --- /dev/null +++ b/tools/junitreport/pkg/parser/stack/stack.go @@ -0,0 +1,73 @@ +package stack + +import ( + "fmt" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" +) + +// TestSuiteStack is a data structure that holds api.TestSuite objects in a LIFO +type TestSuiteStack interface { + // Push adds the testSuite to the top of the LIFO + Push(pkg *api.TestSuite) + // Pop removes the head of the LIFO and returns it + Pop() *api.TestSuite + // Peek returns a reference to the head of the LIFO without removing it + Peek() *api.TestSuite + // IsEmpty determines if the stack has any members + IsEmpty() bool +} + +// NewTestSuiteStack returns a new TestSuiteStack +func NewTestSuiteStack() TestSuiteStack { + return &testSuiteStack{ + head: nil, + } +} + +// testSuiteStack is an implementation of a TestSuiteStack using a linked list +type testSuiteStack struct { + head *testSuiteNode +} + +// Push adds the testSuite to the top of the LIFO +func (s *testSuiteStack) Push(data *api.TestSuite) { + newNode := &testSuiteNode{ + Member: data, + Next: s.head, + } + s.head = newNode +} + +// Pop removes the head of the LIFO and returns it +func (s *testSuiteStack) Pop() *api.TestSuite { + if s.IsEmpty() { + return nil + } + oldNode := s.head + s.head = s.head.Next + return oldNode.Member +} + +// Peek returns a reference to the head of the LIFO without removing it +func (s *testSuiteStack) Peek() *api.TestSuite { + if s.IsEmpty() { + return nil + } + return s.head.Member +} + +// IsEmpty determines if the stack has any members +func (s *testSuiteStack) IsEmpty() bool { + return s.head == nil +} + +// testSuiteNode is a node in a singly-linked list +type testSuiteNode struct { + Member *api.TestSuite + Next *testSuiteNode +} + +func (n *testSuiteNode) String() string { + return fmt.Sprintf("{Member: %s, Next: %s}", n.Member, n.Next.String()) +} diff --git a/tools/junitreport/pkg/parser/stack/stack_test.go b/tools/junitreport/pkg/parser/stack/stack_test.go new file mode 100644 index 000000000..c1117d2a7 --- /dev/null +++ b/tools/junitreport/pkg/parser/stack/stack_test.go @@ -0,0 +1,179 @@ +package stack + +import ( + "reflect" + "testing" + + "github.com/openshift/cluster-machine-approver/tools/junitreport/pkg/api" +) + +func TestPush(t *testing.T) { + var testCases = []struct { + name string + stackSeed *testSuiteNode + testSuiteToPush *api.TestSuite + expectedStack *testSuiteNode + }{ + { + name: "push on empty stack", + stackSeed: nil, + testSuiteToPush: newTestSuite("test"), + expectedStack: newTestSuiteNode(newTestSuite("test"), nil), + }, + { + name: "push on existing stack", + stackSeed: newTestSuiteNode(newTestSuite("test"), nil), + testSuiteToPush: newTestSuite("test2"), + expectedStack: newTestSuiteNode(newTestSuite("test2"), newTestSuiteNode(newTestSuite("test"), nil)), + }, + { + name: "push on deep stack", + stackSeed: newTestSuiteNode(newTestSuite("test2"), newTestSuiteNode(newTestSuite("test3"), nil)), + testSuiteToPush: newTestSuite("test1"), + expectedStack: newTestSuiteNode(newTestSuite("test1"), newTestSuiteNode(newTestSuite("test2"), newTestSuiteNode(newTestSuite("test3"), nil))), + }, + } + + for _, testCase := range testCases { + testStack := &testSuiteStack{ + head: testCase.stackSeed, + } + testStack.Push(testCase.testSuiteToPush) + + if !reflect.DeepEqual(testStack.head, testCase.expectedStack) { + t.Errorf("%s: did not get correct stack state after push:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedStack, testStack.head) + } + } +} + +func TestPop(t *testing.T) { + var testCases = []struct { + name string + stackSeed *testSuiteNode + expectedTestSuite *api.TestSuite + expectedStack *testSuiteNode + }{ + { + name: "pop on empty stack", + stackSeed: nil, + expectedTestSuite: nil, + expectedStack: nil, + }, + { + name: "pop on existing stack", + stackSeed: newTestSuiteNode(newTestSuite("test"), nil), + expectedTestSuite: newTestSuite("test"), + expectedStack: nil, + }, + { + name: "pop on deep stack", + stackSeed: newTestSuiteNode(newTestSuite("test"), newTestSuiteNode(newTestSuite("test2"), nil)), + expectedTestSuite: newTestSuite("test"), + expectedStack: newTestSuiteNode(newTestSuite("test2"), nil), + }, + } + + for _, testCase := range testCases { + testStack := &testSuiteStack{ + head: testCase.stackSeed, + } + testSuite := testStack.Pop() + if !reflect.DeepEqual(testSuite, testCase.expectedTestSuite) { + t.Errorf("%s: did not get correct package from pop:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedTestSuite, testSuite) + } + if !reflect.DeepEqual(testStack.head, testCase.expectedStack) { + t.Errorf("%s: did not get correct stack state after pop:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedStack, testStack.head) + } + } +} + +func TestPeek(t *testing.T) { + var testCases = []struct { + name string + stackSeed *testSuiteNode + expectedTestSuite *api.TestSuite + expectedStack *testSuiteNode + }{ + { + name: "peek on empty stack", + stackSeed: nil, + expectedTestSuite: nil, + expectedStack: nil, + }, + { + name: "peek on existing stack", + stackSeed: newTestSuiteNode(newTestSuite("test"), nil), + expectedTestSuite: newTestSuite("test"), + expectedStack: newTestSuiteNode(newTestSuite("test"), nil), + }, + { + name: "peek on deep stack", + stackSeed: newTestSuiteNode(newTestSuite("test"), newTestSuiteNode(newTestSuite("test2"), nil)), + expectedTestSuite: newTestSuite("test"), + expectedStack: newTestSuiteNode(newTestSuite("test"), newTestSuiteNode(newTestSuite("test2"), nil)), + }, + } + + for _, testCase := range testCases { + testStack := &testSuiteStack{ + head: testCase.stackSeed, + } + testSuite := testStack.Peek() + if !reflect.DeepEqual(testSuite, testCase.expectedTestSuite) { + t.Errorf("%s: did not get correct package from pop:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedTestSuite, testSuite) + } + if !reflect.DeepEqual(testStack.head, testCase.expectedStack) { + t.Errorf("%s: did not get correct stack state after pop:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedStack, testStack.head) + } + } +} + +func TestIsEmpty(t *testing.T) { + var testCases = []struct { + name string + stackSeed *testSuiteNode + expectedState bool + expectedStack *testSuiteNode + }{ + { + name: "isempty on empty stack", + stackSeed: nil, + expectedState: true, + expectedStack: nil, + }, + { + name: "isempty on existing stack", + stackSeed: newTestSuiteNode(newTestSuite("test"), nil), + expectedState: false, + expectedStack: newTestSuiteNode(newTestSuite("test"), nil), + }, + } + + for _, testCase := range testCases { + testStack := &testSuiteStack{ + head: testCase.stackSeed, + } + state := testStack.IsEmpty() + + if state != testCase.expectedState { + t.Errorf("%s: did not get correct stack emptiness after push: expected: %t got: %t\n", testCase.name, testCase.expectedState, state) + } + + if !reflect.DeepEqual(testStack.head, testCase.expectedStack) { + t.Errorf("%s: did not get correct stack state after push:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedStack, testStack.head) + } + } +} + +func newTestSuite(name string) *api.TestSuite { + return &api.TestSuite{ + Name: name, + } +} + +func newTestSuiteNode(testSuite *api.TestSuite, next *testSuiteNode) *testSuiteNode { + return &testSuiteNode{ + Member: testSuite, + Next: next, + } +} diff --git a/tools/junitreport/test/gotest/reports/10_flat.xml b/tools/junitreport/test/gotest/reports/10_flat.xml new file mode 100644 index 000000000..dd6eed173 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/10_flat.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/10_nested.xml b/tools/junitreport/test/gotest/reports/10_nested.xml new file mode 100644 index 000000000..b04bfc737 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/10_nested.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/11_flat.xml b/tools/junitreport/test/gotest/reports/11_flat.xml new file mode 100644 index 000000000..7bcd1ed3d --- /dev/null +++ b/tools/junitreport/test/gotest/reports/11_flat.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/11_nested.xml b/tools/junitreport/test/gotest/reports/11_nested.xml new file mode 100644 index 000000000..9f90d9374 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/11_nested.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/12_flat.xml b/tools/junitreport/test/gotest/reports/12_flat.xml new file mode 100644 index 000000000..77b9d4ae0 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/12_flat.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/junitreport/test/gotest/reports/12_nested.xml b/tools/junitreport/test/gotest/reports/12_nested.xml new file mode 100644 index 000000000..5a1e09906 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/12_nested.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/13_flat.xml b/tools/junitreport/test/gotest/reports/13_flat.xml new file mode 100644 index 000000000..0e325ffb5 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/13_flat.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + node_authorizer_test.go:125: expected true, got false + + + + + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/13_nested.xml b/tools/junitreport/test/gotest/reports/13_nested.xml new file mode 100644 index 000000000..c7d0be491 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/13_nested.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + === RUN TestAuthorizer === RUN TestAuthorizer/allowed_configmap === RUN TestAuthorizer/allowed_secret_via_pod === RUN TestAuthorizer/allowed_shared_secret_via_pod === RUN TestAuthorizer/allowed_shared_secret_via_pvc === RUN TestAuthorizer/allowed_pvc === RUN TestAuthorizer/allowed_pv === RUN TestAuthorizer/disallowed_configmap === RUN TestAuthorizer/disallowed_secret_via_pod === RUN TestAuthorizer/disallowed_shared_secret_via_pvc === RUN TestAuthorizer/disallowed_pvc === RUN TestAuthorizer/disallowed_pv --- FAIL: TestAuthorizer (0.00s) --- PASS: TestAuthorizer/allowed_configmap (0.00s) --- PASS: TestAuthorizer/allowed_secret_via_pod (0.00s) --- PASS: TestAuthorizer/allowed_shared_secret_via_pod (0.00s) --- PASS: TestAuthorizer/allowed_shared_secret_via_pvc (0.00s) --- FAIL: TestAuthorizer/allowed_pvc (0.00s) node_authorizer_test.go:125: expected true, got false --- PASS: TestAuthorizer/allowed_pv (0.00s) --- PASS: TestAuthorizer/disallowed_configmap (0.00s) --- PASS: TestAuthorizer/disallowed_secret_via_pod (0.00s) --- PASS: TestAuthorizer/disallowed_shared_secret_via_pvc (0.00s) --- PASS: TestAuthorizer/disallowed_pvc (0.00s) --- SKIP: TestAuthorizer/disallowed_pv (0.00s) node_authorizer_test.go:121: disallowed pv + + + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/14_flat.xml b/tools/junitreport/test/gotest/reports/14_flat.xml new file mode 100644 index 000000000..dd59d2817 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/14_flat.xml @@ -0,0 +1,14 @@ + + + + + + + + + + data_parser_test.go:14: log line data_parser_test.go:14: failed + text line + + + diff --git a/tools/junitreport/test/gotest/reports/15_flat.xml b/tools/junitreport/test/gotest/reports/15_flat.xml new file mode 100644 index 000000000..cf6512e64 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/15_flat.xml @@ -0,0 +1,59 @@ + + + + + + + + + + data_parser_test.go:14: log line data_parser_test.go:14: failed + text line + + + + + + + + + + + example_test.go:11: log line example_test.go:11: failed + text line + + + + + + example_test.go:15: log line example_test.go:15: failed + text line + + + + + + + + + + example_test.go:25: log line before + text line + + + + + example_test.go:28: log line example_test.go:28: failed example_test.go:29: log line after + text line + + + + + + + example_test.go:35: log line example_test.go:35: failed + text line + + + + diff --git a/tools/junitreport/test/gotest/reports/16_flat.xml b/tools/junitreport/test/gotest/reports/16_flat.xml new file mode 100644 index 000000000..e4a4aaabc --- /dev/null +++ b/tools/junitreport/test/gotest/reports/16_flat.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/17_flat.xml b/tools/junitreport/test/gotest/reports/17_flat.xml new file mode 100644 index 000000000..683d0c25b --- /dev/null +++ b/tools/junitreport/test/gotest/reports/17_flat.xml @@ -0,0 +1,12 @@ + + + + + + + runner_test.go:175: blah + more + + + + diff --git a/tools/junitreport/test/gotest/reports/1_flat.xml b/tools/junitreport/test/gotest/reports/1_flat.xml new file mode 100644 index 000000000..279a2d7d5 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/1_flat.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/1_nested.xml b/tools/junitreport/test/gotest/reports/1_nested.xml new file mode 100644 index 000000000..8030e952c --- /dev/null +++ b/tools/junitreport/test/gotest/reports/1_nested.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/1_nested_restricted.xml b/tools/junitreport/test/gotest/reports/1_nested_restricted.xml new file mode 100644 index 000000000..279a2d7d5 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/1_nested_restricted.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/2_flat.xml b/tools/junitreport/test/gotest/reports/2_flat.xml new file mode 100644 index 000000000..81270507f --- /dev/null +++ b/tools/junitreport/test/gotest/reports/2_flat.xml @@ -0,0 +1,9 @@ + + + + + file_test.go:11: Error message file_test.go:11: Longer error message. + + + + diff --git a/tools/junitreport/test/gotest/reports/2_nested.xml b/tools/junitreport/test/gotest/reports/2_nested.xml new file mode 100644 index 000000000..d3e8c1f0d --- /dev/null +++ b/tools/junitreport/test/gotest/reports/2_nested.xml @@ -0,0 +1,11 @@ + + + + + + === RUN TestOne --- FAIL: TestOne (0.02 seconds) file_test.go:11: Error message file_test.go:11: Longer error message. + + + + + diff --git a/tools/junitreport/test/gotest/reports/3_flat.xml b/tools/junitreport/test/gotest/reports/3_flat.xml new file mode 100644 index 000000000..2104b4d5d --- /dev/null +++ b/tools/junitreport/test/gotest/reports/3_flat.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/3_nested.xml b/tools/junitreport/test/gotest/reports/3_nested.xml new file mode 100644 index 000000000..6225ee15f --- /dev/null +++ b/tools/junitreport/test/gotest/reports/3_nested.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/4_flat.xml b/tools/junitreport/test/gotest/reports/4_flat.xml new file mode 100644 index 000000000..279a2d7d5 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/4_flat.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/4_nested.xml b/tools/junitreport/test/gotest/reports/4_nested.xml new file mode 100644 index 000000000..8030e952c --- /dev/null +++ b/tools/junitreport/test/gotest/reports/4_nested.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/5_flat.xml b/tools/junitreport/test/gotest/reports/5_flat.xml new file mode 100644 index 000000000..4bdaa6ad7 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/5_flat.xml @@ -0,0 +1,13 @@ + + + + + + + + + file_test.go:11: Error message file_test.go:11: Longer error message. + + + + diff --git a/tools/junitreport/test/gotest/reports/5_nested.xml b/tools/junitreport/test/gotest/reports/5_nested.xml new file mode 100644 index 000000000..32cc76206 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/5_nested.xml @@ -0,0 +1,15 @@ + + + + + + + + + + === RUN TestOne --- FAIL: TestOne (0.02 seconds) file_test.go:11: Error message file_test.go:11: Longer error message. + + + + + diff --git a/tools/junitreport/test/gotest/reports/6_flat.xml b/tools/junitreport/test/gotest/reports/6_flat.xml new file mode 100644 index 000000000..41f536c09 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/6_flat.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/6_nested.xml b/tools/junitreport/test/gotest/reports/6_nested.xml new file mode 100644 index 000000000..548923936 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/6_nested.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/7_flat.xml b/tools/junitreport/test/gotest/reports/7_flat.xml new file mode 100644 index 000000000..7bcd1ed3d --- /dev/null +++ b/tools/junitreport/test/gotest/reports/7_flat.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/7_nested.xml b/tools/junitreport/test/gotest/reports/7_nested.xml new file mode 100644 index 000000000..9f90d9374 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/7_nested.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/8_flat.xml b/tools/junitreport/test/gotest/reports/8_flat.xml new file mode 100644 index 000000000..a491e3bf0 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/8_flat.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/8_nested.xml b/tools/junitreport/test/gotest/reports/8_nested.xml new file mode 100644 index 000000000..5ba0e387a --- /dev/null +++ b/tools/junitreport/test/gotest/reports/8_nested.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/9_flat.xml b/tools/junitreport/test/gotest/reports/9_flat.xml new file mode 100644 index 000000000..1170b3972 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/9_flat.xml @@ -0,0 +1,19 @@ + + + + + + + + + file_test.go:11: Error message file_test.go:11: Longer error message. + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/9_nested.xml b/tools/junitreport/test/gotest/reports/9_nested.xml new file mode 100644 index 000000000..8128b0963 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/9_nested.xml @@ -0,0 +1,23 @@ + + + + + + + + + === RUN TestOne --- FAIL: TestOne (0.02 seconds) file_test.go:11: Error message file_test.go:11: Longer error message. + + + + + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/reports/9_nested_restricted.xml b/tools/junitreport/test/gotest/reports/9_nested_restricted.xml new file mode 100644 index 000000000..2cb9dbf66 --- /dev/null +++ b/tools/junitreport/test/gotest/reports/9_nested_restricted.xml @@ -0,0 +1,21 @@ + + + + + + + + === RUN TestOne --- FAIL: TestOne (0.02 seconds) file_test.go:11: Error message file_test.go:11: Longer error message. + + + + + + + + + + + + + diff --git a/tools/junitreport/test/gotest/summaries/10_summary.txt b/tools/junitreport/test/gotest/summaries/10_summary.txt new file mode 100644 index 000000000..69bf32539 --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/10_summary.txt @@ -0,0 +1,2 @@ +Of 2 tests executed in 2.160s, 2 succeeded, 0 failed, and 0 were skipped. + diff --git a/tools/junitreport/test/gotest/summaries/11_summary.txt b/tools/junitreport/test/gotest/summaries/11_summary.txt new file mode 100644 index 000000000..5cade455e --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/11_summary.txt @@ -0,0 +1,2 @@ +Of 2 tests executed in 0.160s, 2 succeeded, 0 failed, and 0 were skipped. + diff --git a/tools/junitreport/test/gotest/summaries/12_summary.txt b/tools/junitreport/test/gotest/summaries/12_summary.txt new file mode 100644 index 000000000..49f4f30ee --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/12_summary.txt @@ -0,0 +1,5 @@ +Of 13 tests executed in 0.077s, 12 succeeded, 0 failed, and 1 was skipped. + +In suite "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node", test case "TestPopulationMemoryUsage" was skipped: +node_authorizer_test.go:169: Skipping large population test. Run with TEST_POPULATION_MEMORY_USAGE=true to output memory profiles. + diff --git a/tools/junitreport/test/gotest/summaries/13_summary.txt b/tools/junitreport/test/gotest/summaries/13_summary.txt new file mode 100644 index 000000000..d7df63bcd --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/13_summary.txt @@ -0,0 +1,14 @@ +Of 13 tests executed in 0.078s, 9 succeeded, 2 failed, and 2 were skipped. + +In suite "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node", test case "TestAuthorizer" failed: + + +In suite "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node", test case "TestAuthorizer/allowed_pvc" failed: +node_authorizer_test.go:125: expected true, got false + +In suite "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node", test case "TestAuthorizer/disallowed_pv" was skipped: +node_authorizer_test.go:121: disallowed pv + +In suite "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node", test case "TestPopulationMemoryUsage" was skipped: +node_authorizer_test.go:172: Skipping large population test. Run with TEST_POPULATION_MEMORY_USAGE=true to output memory profiles. + diff --git a/tools/junitreport/test/gotest/summaries/14_summary.txt b/tools/junitreport/test/gotest/summaries/14_summary.txt new file mode 100644 index 000000000..ee931b25f --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/14_summary.txt @@ -0,0 +1,9 @@ +Of 4 tests executed in 0.019s, 2 succeeded, 2 failed, and 0 were skipped. + +In suite "parser/gotest", test case "TestSubTestWithFailures" failed: + + +In suite "parser/gotest", test case "TestSubTestWithFailures/subtest-fail-1" failed: +data_parser_test.go:14: log line +data_parser_test.go:14: failed + diff --git a/tools/junitreport/test/gotest/summaries/15_summary.txt b/tools/junitreport/test/gotest/summaries/15_summary.txt new file mode 100644 index 000000000..113bef3af --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/15_summary.txt @@ -0,0 +1,41 @@ +Of 23 tests executed in 0.025s, 12 succeeded, 11 failed, and 0 were skipped. + +In suite "parser/gotest", test case "TestSubTestWithFailures" failed: + + +In suite "parser/gotest", test case "TestSubTestWithFailures/subtest-fail-1" failed: +data_parser_test.go:14: log line +data_parser_test.go:14: failed + +In suite "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example", test case "TestSubTestWithFailures" failed: + + +In suite "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example", test case "TestSubTestWithFailures/subtest-fail-1" failed: +example_test.go:11: log line +example_test.go:11: failed + +In suite "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example", test case "TestSubTestWithFirstFailures" failed: + + +In suite "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example", test case "TestSubTestWithFirstFailures/subtest-fail-1" failed: +example_test.go:15: log line +example_test.go:15: failed + +In suite "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example", test case "TestSubTestWithSubTestFailures" failed: + + +In suite "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example", test case "TestSubTestWithSubTestFailures/subtest-fail-1" failed: +example_test.go:25: log line before + +In suite "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example", test case "TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-fail-1" failed: +example_test.go:28: log line +example_test.go:28: failed +example_test.go:29: log line after + +In suite "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example", test case "TestSubTestWithMiddleFailures" failed: + + +In suite "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example", test case "TestSubTestWithMiddleFailures/subtest-fail-1" failed: +example_test.go:35: log line +example_test.go:35: failed + diff --git a/tools/junitreport/test/gotest/summaries/16_summary.txt b/tools/junitreport/test/gotest/summaries/16_summary.txt new file mode 100644 index 000000000..988018c46 --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/16_summary.txt @@ -0,0 +1,2 @@ +Of 2 tests executed in 0.320s, 2 succeeded, 0 failed, and 0 were skipped. + diff --git a/tools/junitreport/test/gotest/summaries/17_summary.txt b/tools/junitreport/test/gotest/summaries/17_summary.txt new file mode 100644 index 000000000..2fba48a5f --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/17_summary.txt @@ -0,0 +1,6 @@ +Of 4 tests executed in 764.718s, 3 succeeded, 1 failed, and 0 were skipped. + +In suite "github.com/openshift/origin/test/integration/runner", test case "TestIntegration/TestRegistryClientRegistryNotFound" failed: +runner_test.go:175: blah + + diff --git a/tools/junitreport/test/gotest/summaries/1_summary.txt b/tools/junitreport/test/gotest/summaries/1_summary.txt new file mode 100644 index 000000000..5cade455e --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/1_summary.txt @@ -0,0 +1,2 @@ +Of 2 tests executed in 0.160s, 2 succeeded, 0 failed, and 0 were skipped. + diff --git a/tools/junitreport/test/gotest/summaries/2_summary.txt b/tools/junitreport/test/gotest/summaries/2_summary.txt new file mode 100644 index 000000000..d781bc678 --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/2_summary.txt @@ -0,0 +1,8 @@ +Of 2 tests executed in 0.150s, 1 succeeded, 1 failed, and 0 were skipped. + +In suite "package/name", test case "TestOne" failed: +file_test.go:11: Error message +file_test.go:11: Longer +error +message. + diff --git a/tools/junitreport/test/gotest/summaries/3_summary.txt b/tools/junitreport/test/gotest/summaries/3_summary.txt new file mode 100644 index 000000000..23744263d --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/3_summary.txt @@ -0,0 +1,5 @@ +Of 2 tests executed in 0.150s, 1 succeeded, 0 failed, and 1 was skipped. + +In suite "package/name", test case "TestOne" was skipped: +file_test.go:11: Skip message + diff --git a/tools/junitreport/test/gotest/summaries/4_summary.txt b/tools/junitreport/test/gotest/summaries/4_summary.txt new file mode 100644 index 000000000..5cade455e --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/4_summary.txt @@ -0,0 +1,2 @@ +Of 2 tests executed in 0.160s, 2 succeeded, 0 failed, and 0 were skipped. + diff --git a/tools/junitreport/test/gotest/summaries/5_summary.txt b/tools/junitreport/test/gotest/summaries/5_summary.txt new file mode 100644 index 000000000..54a80542d --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/5_summary.txt @@ -0,0 +1,8 @@ +Of 4 tests executed in 0.310s, 3 succeeded, 1 failed, and 0 were skipped. + +In suite "package/name2", test case "TestOne" failed: +file_test.go:11: Error message +file_test.go:11: Longer +error +message. + diff --git a/tools/junitreport/test/gotest/summaries/6_summary.txt b/tools/junitreport/test/gotest/summaries/6_summary.txt new file mode 100644 index 000000000..5cade455e --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/6_summary.txt @@ -0,0 +1,2 @@ +Of 2 tests executed in 0.160s, 2 succeeded, 0 failed, and 0 were skipped. + diff --git a/tools/junitreport/test/gotest/summaries/7_summary.txt b/tools/junitreport/test/gotest/summaries/7_summary.txt new file mode 100644 index 000000000..5cade455e --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/7_summary.txt @@ -0,0 +1,2 @@ +Of 2 tests executed in 0.160s, 2 succeeded, 0 failed, and 0 were skipped. + diff --git a/tools/junitreport/test/gotest/summaries/8_summary.txt b/tools/junitreport/test/gotest/summaries/8_summary.txt new file mode 100644 index 000000000..da13e69bd --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/8_summary.txt @@ -0,0 +1,2 @@ +Of 2 tests executed in 0.050s, 2 succeeded, 0 failed, and 0 were skipped. + diff --git a/tools/junitreport/test/gotest/summaries/9_summary.txt b/tools/junitreport/test/gotest/summaries/9_summary.txt new file mode 100644 index 000000000..faf5c38b4 --- /dev/null +++ b/tools/junitreport/test/gotest/summaries/9_summary.txt @@ -0,0 +1,11 @@ +Of 6 tests executed in 0.400s, 4 succeeded, 1 failed, and 1 was skipped. + +In suite "package/name/nested", test case "TestOne" failed: +file_test.go:11: Error message +file_test.go:11: Longer +error +message. + +In suite "package/name/nested", test case "TestTwo" was skipped: +file_test.go:11: Skip message + diff --git a/tools/junitreport/test/gotest/testdata/1.txt b/tools/junitreport/test/gotest/testdata/1.txt new file mode 100644 index 000000000..ce39d8746 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/1.txt @@ -0,0 +1,6 @@ +=== RUN TestOne +--- PASS: TestOne (0.06 seconds) +=== RUN TestTwo +--- PASS: TestTwo (0.10 seconds) +PASS +ok package/name 0.160s diff --git a/tools/junitreport/test/gotest/testdata/10.txt b/tools/junitreport/test/gotest/testdata/10.txt new file mode 100644 index 000000000..0cb8777ed --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/10.txt @@ -0,0 +1,6 @@ +=== RUN TestOne +--- PASS: TestOne (0.06 seconds) +=== RUN TestTwo +--- PASS: TestTwo (0.10 seconds) +PASS +ok package/name 2.160s diff --git a/tools/junitreport/test/gotest/testdata/11.txt b/tools/junitreport/test/gotest/testdata/11.txt new file mode 100644 index 000000000..59344dd52 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/11.txt @@ -0,0 +1,7 @@ +=== RUN TestOne +--- PASS: TestOne (0.06 seconds) +=== RUN TestTwo +--- PASS: TestTwo (0.10 seconds) +PASS +coverage: 10.0% of statements +ok package/name 0.160s coverage: 10.0% of statements \ No newline at end of file diff --git a/tools/junitreport/test/gotest/testdata/12.txt b/tools/junitreport/test/gotest/testdata/12.txt new file mode 100644 index 000000000..f9797c614 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/12.txt @@ -0,0 +1,29 @@ +=== RUN TestAuthorizer +=== RUN TestAuthorizer/allowed_configmap +=== RUN TestAuthorizer/allowed_secret_via_pod +=== RUN TestAuthorizer/allowed_shared_secret_via_pod +=== RUN TestAuthorizer/allowed_shared_secret_via_pvc +=== RUN TestAuthorizer/allowed_pvc +=== RUN TestAuthorizer/allowed_pv +=== RUN TestAuthorizer/disallowed_configmap +=== RUN TestAuthorizer/disallowed_secret_via_pod +=== RUN TestAuthorizer/disallowed_shared_secret_via_pvc +=== RUN TestAuthorizer/disallowed_pvc +=== RUN TestAuthorizer/disallowed_pv +--- PASS: TestAuthorizer (0.00s) + --- PASS: TestAuthorizer/allowed_configmap (0.00s) + --- PASS: TestAuthorizer/allowed_secret_via_pod (0.00s) + --- PASS: TestAuthorizer/allowed_shared_secret_via_pod (0.00s) + --- PASS: TestAuthorizer/allowed_shared_secret_via_pvc (0.00s) + --- PASS: TestAuthorizer/allowed_pvc (0.00s) + --- PASS: TestAuthorizer/allowed_pv (0.00s) + --- PASS: TestAuthorizer/disallowed_configmap (0.00s) + --- PASS: TestAuthorizer/disallowed_secret_via_pod (0.00s) + --- PASS: TestAuthorizer/disallowed_shared_secret_via_pvc (0.00s) + --- PASS: TestAuthorizer/disallowed_pvc (0.00s) + --- PASS: TestAuthorizer/disallowed_pv (0.00s) +=== RUN TestPopulationMemoryUsage +--- SKIP: TestPopulationMemoryUsage (0.00s) + node_authorizer_test.go:169: Skipping large population test. Run with TEST_POPULATION_MEMORY_USAGE=true to output memory profiles. +PASS +ok k8s.io/kubernetes/plugin/pkg/auth/authorizer/node 0.077s diff --git a/tools/junitreport/test/gotest/testdata/13.txt b/tools/junitreport/test/gotest/testdata/13.txt new file mode 100644 index 000000000..bd1eef106 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/13.txt @@ -0,0 +1,32 @@ +=== RUN TestAuthorizer +=== RUN TestAuthorizer/allowed_configmap +=== RUN TestAuthorizer/allowed_secret_via_pod +=== RUN TestAuthorizer/allowed_shared_secret_via_pod +=== RUN TestAuthorizer/allowed_shared_secret_via_pvc +=== RUN TestAuthorizer/allowed_pvc +=== RUN TestAuthorizer/allowed_pv +=== RUN TestAuthorizer/disallowed_configmap +=== RUN TestAuthorizer/disallowed_secret_via_pod +=== RUN TestAuthorizer/disallowed_shared_secret_via_pvc +=== RUN TestAuthorizer/disallowed_pvc +=== RUN TestAuthorizer/disallowed_pv +--- FAIL: TestAuthorizer (0.00s) + --- PASS: TestAuthorizer/allowed_configmap (0.00s) + --- PASS: TestAuthorizer/allowed_secret_via_pod (0.00s) + --- PASS: TestAuthorizer/allowed_shared_secret_via_pod (0.00s) + --- PASS: TestAuthorizer/allowed_shared_secret_via_pvc (0.00s) + --- FAIL: TestAuthorizer/allowed_pvc (0.00s) + node_authorizer_test.go:125: expected true, got false + --- PASS: TestAuthorizer/allowed_pv (0.00s) + --- PASS: TestAuthorizer/disallowed_configmap (0.00s) + --- PASS: TestAuthorizer/disallowed_secret_via_pod (0.00s) + --- PASS: TestAuthorizer/disallowed_shared_secret_via_pvc (0.00s) + --- PASS: TestAuthorizer/disallowed_pvc (0.00s) + --- SKIP: TestAuthorizer/disallowed_pv (0.00s) + node_authorizer_test.go:121: disallowed pv +=== RUN TestPopulationMemoryUsage +--- SKIP: TestPopulationMemoryUsage (0.00s) + node_authorizer_test.go:172: Skipping large population test. Run with TEST_POPULATION_MEMORY_USAGE=true to output memory profiles. +FAIL +exit status 1 +FAIL k8s.io/kubernetes/plugin/pkg/auth/authorizer/node 0.078s diff --git a/tools/junitreport/test/gotest/testdata/14.txt b/tools/junitreport/test/gotest/testdata/14.txt new file mode 100644 index 000000000..296f9694d --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/14.txt @@ -0,0 +1,14 @@ +=== RUN TestSubTestWithFailures +=== RUN TestSubTestWithFailures/subtest-pass-1 +=== RUN TestSubTestWithFailures/subtest-pass-2 +=== RUN TestSubTestWithFailures/subtest-fail-1 +text line +--- FAIL: TestSubTestWithFailures (0.00s) + --- PASS: TestSubTestWithFailures/subtest-pass-1 (0.00s) + --- PASS: TestSubTestWithFailures/subtest-pass-2 (0.00s) + --- FAIL: TestSubTestWithFailures/subtest-fail-1 (0.00s) + data_parser_test.go:14: log line + data_parser_test.go:14: failed +FAIL +exit status 1 +FAIL parser/gotest 0.019s \ No newline at end of file diff --git a/tools/junitreport/test/gotest/testdata/15.txt b/tools/junitreport/test/gotest/testdata/15.txt new file mode 100644 index 000000000..0558af75f --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/15.txt @@ -0,0 +1,71 @@ +=== RUN TestSubTestWithFailures +=== RUN TestSubTestWithFailures/subtest-pass-1 +=== RUN TestSubTestWithFailures/subtest-pass-2 +=== RUN TestSubTestWithFailures/subtest-fail-1 +text line +--- FAIL: TestSubTestWithFailures (0.00s) + --- PASS: TestSubTestWithFailures/subtest-pass-1 (0.00s) + --- PASS: TestSubTestWithFailures/subtest-pass-2 (0.00s) + --- FAIL: TestSubTestWithFailures/subtest-fail-1 (0.00s) + data_parser_test.go:14: log line + data_parser_test.go:14: failed +FAIL +exit status 1 +FAIL parser/gotest 0.019s +=== RUN TestSubTestWithFailures +=== RUN TestSubTestWithFailures/subtest-pass-1 +=== RUN TestSubTestWithFailures/subtest-pass-2 +=== RUN TestSubTestWithFailures/subtest-fail-1 +text line +--- FAIL: TestSubTestWithFailures (0.00s) + --- PASS: TestSubTestWithFailures/subtest-pass-1 (0.00s) + --- PASS: TestSubTestWithFailures/subtest-pass-2 (0.00s) + --- FAIL: TestSubTestWithFailures/subtest-fail-1 (0.00s) + example_test.go:11: log line + example_test.go:11: failed +=== RUN TestSubTestWithFirstFailures +=== RUN TestSubTestWithFirstFailures/subtest-fail-1 +text line +=== RUN TestSubTestWithFirstFailures/subtest-pass-1 +=== RUN TestSubTestWithFirstFailures/subtest-pass-2 +--- FAIL: TestSubTestWithFirstFailures (0.00s) + --- FAIL: TestSubTestWithFirstFailures/subtest-fail-1 (0.00s) + example_test.go:15: log line + example_test.go:15: failed + --- PASS: TestSubTestWithFirstFailures/subtest-pass-1 (0.00s) + --- PASS: TestSubTestWithFirstFailures/subtest-pass-2 (0.00s) +=== RUN TestSubTestWithSubTestFailures +=== RUN TestSubTestWithSubTestFailures/subtest-pass-1 +=== RUN TestSubTestWithSubTestFailures/subtest-pass-2 +=== RUN TestSubTestWithSubTestFailures/subtest-fail-1 +text line +=== RUN TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-pass-1 +=== RUN TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-pass-2 +=== RUN TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-fail-1 +text line +--- FAIL: TestSubTestWithSubTestFailures (0.00s) + --- PASS: TestSubTestWithSubTestFailures/subtest-pass-1 (0.00s) + --- PASS: TestSubTestWithSubTestFailures/subtest-pass-2 (0.00s) + --- FAIL: TestSubTestWithSubTestFailures/subtest-fail-1 (0.00s) + example_test.go:25: log line before + --- PASS: TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-pass-1 (0.00s) + --- PASS: TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-pass-2 (0.00s) + --- FAIL: TestSubTestWithSubTestFailures/subtest-fail-1/sub-subtest-fail-1 (0.00s) + example_test.go:28: log line + example_test.go:28: failed + example_test.go:29: log line after +=== RUN TestSubTestWithMiddleFailures +=== RUN TestSubTestWithMiddleFailures/subtest-pass-1 +=== RUN TestSubTestWithMiddleFailures/subtest-fail-1 +text line +=== RUN TestSubTestWithMiddleFailures/subtest-pass-2 +--- FAIL: TestSubTestWithMiddleFailures (0.00s) + --- PASS: TestSubTestWithMiddleFailures/subtest-pass-1 (0.00s) + --- FAIL: TestSubTestWithMiddleFailures/subtest-fail-1 (0.00s) + example_test.go:35: log line + example_test.go:35: failed + --- PASS: TestSubTestWithMiddleFailures/subtest-pass-2 (0.00s) +FAIL +coverage: 0.0% of statements +exit status 1 +FAIL github.com/openshift/origin/tools/junitreport/pkg/parser/gotest/example 0.006s \ No newline at end of file diff --git a/tools/junitreport/test/gotest/testdata/16.txt b/tools/junitreport/test/gotest/testdata/16.txt new file mode 100644 index 000000000..291c8daa3 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/16.txt @@ -0,0 +1,10 @@ +Output before first suite +=== RUN TestSubTestWithFailures +--- PASS: TestSubTestWithFailures (0.00s) +PASS +ok package/1 0.160s +Output before second suite +=== RUN TestSubTestWithFailures +--- PASS: TestSubTestWithFailures (0.00s) +PASS +ok package/2 0.160s \ No newline at end of file diff --git a/tools/junitreport/test/gotest/testdata/17.txt b/tools/junitreport/test/gotest/testdata/17.txt new file mode 100644 index 000000000..76937d585 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/17.txt @@ -0,0 +1,15 @@ +=== RUN TestIntegration +=== RUN TestIntegration/TestUserInitialization +=== RUN TestIntegration/TestRegistryClientRegistryNotFound +=== RUN TestIntegration/TestRestrictUsers +--- PASS: TestIntegration (0.05s) + --- PASS: TestIntegration/TestUserInitialization (133.72s) + --- FAIL: TestIntegration/TestRegistryClientRegistryNotFound (0.55s) + runner_test.go:175: blah + + === OUTPUT + + more + --- PASS: TestIntegration/TestRestrictUsers (13.19s) +PASS +ok github.com/openshift/origin/test/integration/runner 764.718s diff --git a/tools/junitreport/test/gotest/testdata/2.txt b/tools/junitreport/test/gotest/testdata/2.txt new file mode 100644 index 000000000..76f534d40 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/2.txt @@ -0,0 +1,11 @@ +=== RUN TestOne +--- FAIL: TestOne (0.02 seconds) + file_test.go:11: Error message + file_test.go:11: Longer + error + message. +=== RUN TestTwo +--- PASS: TestTwo (0.13 seconds) +FAIL +exit status 1 +FAIL package/name 0.150s diff --git a/tools/junitreport/test/gotest/testdata/3.txt b/tools/junitreport/test/gotest/testdata/3.txt new file mode 100644 index 000000000..155938a53 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/3.txt @@ -0,0 +1,7 @@ +=== RUN TestOne +--- SKIP: TestOne (0.02 seconds) + file_test.go:11: Skip message +=== RUN TestTwo +--- PASS: TestTwo (0.13 seconds) +PASS +ok package/name 0.150s diff --git a/tools/junitreport/test/gotest/testdata/4.txt b/tools/junitreport/test/gotest/testdata/4.txt new file mode 100644 index 000000000..f6b77625b --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/4.txt @@ -0,0 +1,6 @@ +=== RUN TestOne +--- PASS: TestOne (0.06s) +=== RUN TestTwo +--- PASS: TestTwo (0.10s) +PASS +ok package/name 0.160s diff --git a/tools/junitreport/test/gotest/testdata/5.txt b/tools/junitreport/test/gotest/testdata/5.txt new file mode 100644 index 000000000..a42720ba9 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/5.txt @@ -0,0 +1,17 @@ +=== RUN TestOne +--- PASS: TestOne (0.06 seconds) +=== RUN TestTwo +--- PASS: TestTwo (0.10 seconds) +PASS +ok package/name1 0.160s +=== RUN TestOne +--- FAIL: TestOne (0.02 seconds) + file_test.go:11: Error message + file_test.go:11: Longer + error + message. +=== RUN TestTwo +--- PASS: TestTwo (0.13 seconds) +FAIL +exit status 1 +FAIL package/name2 0.150s diff --git a/tools/junitreport/test/gotest/testdata/6.txt b/tools/junitreport/test/gotest/testdata/6.txt new file mode 100644 index 000000000..ca0af0daa --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/6.txt @@ -0,0 +1,7 @@ +=== RUN TestOne +--- PASS: TestOne (0.06 seconds) +=== RUN TestTwo +--- PASS: TestTwo (0.10 seconds) +PASS +coverage: 13.37% of statements +ok package/name 0.160s diff --git a/tools/junitreport/test/gotest/testdata/7.txt b/tools/junitreport/test/gotest/testdata/7.txt new file mode 100644 index 000000000..4295890bc --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/7.txt @@ -0,0 +1,6 @@ +=== RUN TestOne +--- PASS: TestOne (0.06 seconds) +=== RUN TestTwo +--- PASS: TestTwo (0.10 seconds) +PASS +ok package/name 0.160s coverage: 10.0% of statements diff --git a/tools/junitreport/test/gotest/testdata/8.txt b/tools/junitreport/test/gotest/testdata/8.txt new file mode 100644 index 000000000..2a40ceee4 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/8.txt @@ -0,0 +1,6 @@ +=== RUN TestOne +--- PASS: TestOne (0.02s) +=== RUN TestTwo +--- PASS: TestTwo (0.03s) +PASS +ok package/name 0.050s diff --git a/tools/junitreport/test/gotest/testdata/9.txt b/tools/junitreport/test/gotest/testdata/9.txt new file mode 100644 index 000000000..c115cf664 --- /dev/null +++ b/tools/junitreport/test/gotest/testdata/9.txt @@ -0,0 +1,23 @@ +=== RUN TestOne +--- PASS: TestOne (0.02s) +=== RUN TestTwo +--- PASS: TestTwo (0.03s) +PASS +ok package/name 0.050s +=== RUN TestOne +--- FAIL: TestOne (0.02 seconds) + file_test.go:11: Error message + file_test.go:11: Longer + error + message. +=== RUN TestTwo +--- SKIP: TestTwo (0.03 seconds) + file_test.go:11: Skip message +PASS +ok package/name/nested 0.050s +=== RUN TestOne +--- PASS: TestOne (0.1s) +=== RUN TestTwo +--- PASS: TestTwo (0.2s) +PASS +ok package/other/nested 0.300s \ No newline at end of file diff --git a/tools/junitreport/test/integration.sh b/tools/junitreport/test/integration.sh new file mode 100755 index 000000000..35036a24c --- /dev/null +++ b/tools/junitreport/test/integration.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# This file runs the integration tests for the `junitreport` binary to ensure that correct jUnit XML is produced. + +set -o errexit +set -o nounset +set -o pipefail + +JUNITREPORT_ROOT=$(dirname "${BASH_SOURCE}")/.. +pushd "${JUNITREPORT_ROOT}" > /dev/null + +diff_args='-ydb --suppress-common-lines' + +TMPDIR="/tmp/junitreport/test/integration" +mkdir -p "${TMPDIR}" + +for suite in test/*/; do + suite_name=$( basename ${suite} ) + echo "[INFO] Testing suite ${suite_name}..." + + WORKINGDIR="${TMPDIR}/${suite_name}" + mkdir -p "${WORKINGDIR}" + + # test every case with flat and nested suites + for test in ${suite}/testdata/*.txt; do + test_name=$( basename ${test} '.txt' ) + + cat "${test}" | junitreport -type "${suite_name}" -suites flat > "${WORKINGDIR}/${test_name}_flat.xml" + if ! diff ${diff_args} "${suite}/reports/${test_name}_flat.xml" "${WORKINGDIR}/${test_name}_flat.xml"; then + echo "[FAIL] Test '${test_name}' in suite '${suite_name}' failed for flat suite builder." + exit 1 + fi + + cat "${WORKINGDIR}/${test_name}_flat.xml" | junitreport summarize > "${WORKINGDIR}/${test_name}_summary.txt" + if ! diff ${diff_args} "${suite}/summaries/${test_name}_summary.txt" "${WORKINGDIR}/${test_name}_summary.txt"; then + echo "[FAIL] Test '${test_name}' in suite '${suite_name}' failed to summarize flat XML." + fi + done + + echo "[PASS] Test output type passed: ${suite_name}" +done + +echo "[PASS] junitreport testing successful" +popd > /dev/null diff --git a/tools/junitreport/test/oscmd/reports/1_flat.xml b/tools/junitreport/test/oscmd/reports/1_flat.xml new file mode 100644 index 000000000..bdda7b025 --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/1_flat.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tools/junitreport/test/oscmd/reports/1_nested.xml b/tools/junitreport/test/oscmd/reports/1_nested.xml new file mode 100644 index 000000000..f80000fc6 --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/1_nested.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tools/junitreport/test/oscmd/reports/1_nested_restricted.xml b/tools/junitreport/test/oscmd/reports/1_nested_restricted.xml new file mode 100644 index 000000000..bdda7b025 --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/1_nested_restricted.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tools/junitreport/test/oscmd/reports/2_flat.xml b/tools/junitreport/test/oscmd/reports/2_flat.xml new file mode 100644 index 000000000..10a48f5ec --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/2_flat.xml @@ -0,0 +1,9 @@ + + + + + === BEGIN TEST CASE === package/name/file.sh:23: executing 'some command' expecting success FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code There was no output from the command. There was no error output from the command. === END TEST CASE === + + + + diff --git a/tools/junitreport/test/oscmd/reports/2_nested.xml b/tools/junitreport/test/oscmd/reports/2_nested.xml new file mode 100644 index 000000000..219246f78 --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/2_nested.xml @@ -0,0 +1,11 @@ + + + + + + === BEGIN TEST CASE === package/name/file.sh:23: executing 'some command' expecting success FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code There was no output from the command. There was no error output from the command. === END TEST CASE === + + + + + diff --git a/tools/junitreport/test/oscmd/reports/3_flat.xml b/tools/junitreport/test/oscmd/reports/3_flat.xml new file mode 100644 index 000000000..060677327 --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/3_flat.xml @@ -0,0 +1,13 @@ + + + + + + + + + === BEGIN TEST CASE === package/name/file.sh:23: executing 'some command' expecting success FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code There was no output from the command. There was no error output from the command. === END TEST CASE === + + + + diff --git a/tools/junitreport/test/oscmd/reports/3_nested.xml b/tools/junitreport/test/oscmd/reports/3_nested.xml new file mode 100644 index 000000000..e2effbad5 --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/3_nested.xml @@ -0,0 +1,15 @@ + + + + + + + + + + === BEGIN TEST CASE === package/name/file.sh:23: executing 'some command' expecting success FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code There was no output from the command. There was no error output from the command. === END TEST CASE === + + + + + diff --git a/tools/junitreport/test/oscmd/reports/4_flat.xml b/tools/junitreport/test/oscmd/reports/4_flat.xml new file mode 100644 index 000000000..c14adf7cd --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/4_flat.xml @@ -0,0 +1,17 @@ + + + + + + + + + === BEGIN TEST CASE === package/name/file.sh:23: executing 'some command' expecting success FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code There was no output from the command. There was no error output from the command. === END TEST CASE === + + + + + + + + diff --git a/tools/junitreport/test/oscmd/reports/4_nested.xml b/tools/junitreport/test/oscmd/reports/4_nested.xml new file mode 100644 index 000000000..bc88dd348 --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/4_nested.xml @@ -0,0 +1,21 @@ + + + + + + + + + === BEGIN TEST CASE === package/name/file.sh:23: executing 'some command' expecting success FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code There was no output from the command. There was no error output from the command. === END TEST CASE === + + + + + + + + + + + + diff --git a/tools/junitreport/test/oscmd/reports/4_nested_restricted.xml b/tools/junitreport/test/oscmd/reports/4_nested_restricted.xml new file mode 100644 index 000000000..caf5b45b1 --- /dev/null +++ b/tools/junitreport/test/oscmd/reports/4_nested_restricted.xml @@ -0,0 +1,19 @@ + + + + + + + + === BEGIN TEST CASE === package/name/file.sh:23: executing 'some command' expecting success FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code There was no output from the command. There was no error output from the command. === END TEST CASE === + + + + + + + + + + + diff --git a/tools/junitreport/test/oscmd/summaries/1_summary.txt b/tools/junitreport/test/oscmd/summaries/1_summary.txt new file mode 100644 index 000000000..791c01d28 --- /dev/null +++ b/tools/junitreport/test/oscmd/summaries/1_summary.txt @@ -0,0 +1,2 @@ +Of 2 tests executed in 11.245s, 2 succeeded, 0 failed, and 0 were skipped. + diff --git a/tools/junitreport/test/oscmd/summaries/2_summary.txt b/tools/junitreport/test/oscmd/summaries/2_summary.txt new file mode 100644 index 000000000..3aafbfd6f --- /dev/null +++ b/tools/junitreport/test/oscmd/summaries/2_summary.txt @@ -0,0 +1,10 @@ +Of 2 tests executed in 11.245s, 1 succeeded, 1 failed, and 0 were skipped. + +In suite "package/name", test case "package/name/file.sh:23: executing 'some command' expecting success" failed: +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === + diff --git a/tools/junitreport/test/oscmd/summaries/3_summary.txt b/tools/junitreport/test/oscmd/summaries/3_summary.txt new file mode 100644 index 000000000..a5b1c80ba --- /dev/null +++ b/tools/junitreport/test/oscmd/summaries/3_summary.txt @@ -0,0 +1,10 @@ +Of 4 tests executed in 22.490s, 3 succeeded, 1 failed, and 0 were skipped. + +In suite "package/name2", test case "package/name/file.sh:23: executing 'some command' expecting success" failed: +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === + diff --git a/tools/junitreport/test/oscmd/summaries/4_summary.txt b/tools/junitreport/test/oscmd/summaries/4_summary.txt new file mode 100644 index 000000000..6eaba7d3b --- /dev/null +++ b/tools/junitreport/test/oscmd/summaries/4_summary.txt @@ -0,0 +1,10 @@ +Of 6 tests executed in 33.735s, 5 succeeded, 1 failed, and 0 were skipped. + +In suite "package/name/nested", test case "package/name/file.sh:23: executing 'some command' expecting success" failed: +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === + diff --git a/tools/junitreport/test/oscmd/testdata/1.txt b/tools/junitreport/test/oscmd/testdata/1.txt new file mode 100644 index 000000000..3ce9b9cd1 --- /dev/null +++ b/tools/junitreport/test/oscmd/testdata/1.txt @@ -0,0 +1,14 @@ +=== BEGIN TEST SUITE package/name === +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +SUCCESS after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== BEGIN TEST CASE === +package/name/file.sh:24: executing 'some other command' expecting success +SUCCESS after 11.123s: package/name/file.sh:24: executing 'some other command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== END TEST SUITE === \ No newline at end of file diff --git a/tools/junitreport/test/oscmd/testdata/2.txt b/tools/junitreport/test/oscmd/testdata/2.txt new file mode 100644 index 000000000..0e2126e8e --- /dev/null +++ b/tools/junitreport/test/oscmd/testdata/2.txt @@ -0,0 +1,14 @@ +=== BEGIN TEST SUITE package/name === +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== BEGIN TEST CASE === +package/name/file.sh:24: executing 'some other command' expecting success +SUCCESS after 11.123s: package/name/file.sh:24: executing 'some other command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== END TEST SUITE === \ No newline at end of file diff --git a/tools/junitreport/test/oscmd/testdata/3.txt b/tools/junitreport/test/oscmd/testdata/3.txt new file mode 100644 index 000000000..13052d422 --- /dev/null +++ b/tools/junitreport/test/oscmd/testdata/3.txt @@ -0,0 +1,28 @@ +=== BEGIN TEST SUITE package/name === +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +SUCCESS after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== BEGIN TEST CASE === +package/name/file.sh:24: executing 'some other command' expecting success +SUCCESS after 11.123s: package/name/file.sh:24: executing 'some other command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== END TEST SUITE === +=== BEGIN TEST SUITE package/name2 === +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== BEGIN TEST CASE === +package/name/file.sh:24: executing 'some other command' expecting success +SUCCESS after 11.123s: package/name/file.sh:24: executing 'some other command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== END TEST SUITE === \ No newline at end of file diff --git a/tools/junitreport/test/oscmd/testdata/4.txt b/tools/junitreport/test/oscmd/testdata/4.txt new file mode 100644 index 000000000..9c3f19e95 --- /dev/null +++ b/tools/junitreport/test/oscmd/testdata/4.txt @@ -0,0 +1,42 @@ +=== BEGIN TEST SUITE package/name === +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +SUCCESS after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== BEGIN TEST CASE === +package/name/file.sh:24: executing 'some other command' expecting success +SUCCESS after 11.123s: package/name/file.sh:24: executing 'some other command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== END TEST SUITE === +=== BEGIN TEST SUITE package/name/nested === +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +FAILURE after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success: the command returned the wrong error code +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== BEGIN TEST CASE === +package/name/file.sh:24: executing 'some other command' expecting success +SUCCESS after 11.123s: package/name/file.sh:24: executing 'some other command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== END TEST SUITE === +=== BEGIN TEST SUITE package/other/nested === +=== BEGIN TEST CASE === +package/name/file.sh:23: executing 'some command' expecting success +SUCCESS after 0.1234s: package/name/file.sh:23: executing 'some command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== BEGIN TEST CASE === +package/name/file.sh:24: executing 'some other command' expecting success +SUCCESS after 11.123s: package/name/file.sh:24: executing 'some other command' expecting success +There was no output from the command. +There was no error output from the command. +=== END TEST CASE === +=== END TEST SUITE === \ No newline at end of file diff --git a/vendor/bitbucket.org/ww/goautoneg/.hg_archival.txt b/vendor/bitbucket.org/ww/goautoneg/.hg_archival.txt new file mode 100644 index 000000000..b9a2ff984 --- /dev/null +++ b/vendor/bitbucket.org/ww/goautoneg/.hg_archival.txt @@ -0,0 +1,6 @@ +repo: 848b351341922ce39becda978778724d5b58dbca +node: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 +branch: default +latesttag: null +latesttagdistance: 5 +changessincelatesttag: 5 diff --git a/vendor/bitbucket.org/ww/goautoneg/Makefile b/vendor/bitbucket.org/ww/goautoneg/Makefile new file mode 100644 index 000000000..e33ee1730 --- /dev/null +++ b/vendor/bitbucket.org/ww/goautoneg/Makefile @@ -0,0 +1,13 @@ +include $(GOROOT)/src/Make.inc + +TARG=bitbucket.org/ww/goautoneg +GOFILES=autoneg.go + +include $(GOROOT)/src/Make.pkg + +format: + gofmt -w *.go + +docs: + gomake clean + godoc ${TARG} > README.txt diff --git a/vendor/bitbucket.org/ww/goautoneg/README.txt b/vendor/bitbucket.org/ww/goautoneg/README.txt new file mode 100644 index 000000000..7723656d5 --- /dev/null +++ b/vendor/bitbucket.org/ww/goautoneg/README.txt @@ -0,0 +1,67 @@ +PACKAGE + +package goautoneg +import "bitbucket.org/ww/goautoneg" + +HTTP Content-Type Autonegotiation. + +The functions in this package implement the behaviour specified in +http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + +Copyright (c) 2011, Open Knowledge Foundation Ltd. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + Neither the name of the Open Knowledge Foundation Ltd. nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +FUNCTIONS + +func Negotiate(header string, alternatives []string) (content_type string) +Negotiate the most appropriate content_type given the accept header +and a list of alternatives. + +func ParseAccept(header string) (accept []Accept) +Parse an Accept Header string returning a sorted list +of clauses + + +TYPES + +type Accept struct { + Type, SubType string + Q float32 + Params map[string]string +} +Structure to represent a clause in an HTTP Accept Header + + +SUBDIRECTORIES + + .hg diff --git a/vendor/bitbucket.org/ww/goautoneg/autoneg.go b/vendor/bitbucket.org/ww/goautoneg/autoneg.go new file mode 100644 index 000000000..648b38cb6 --- /dev/null +++ b/vendor/bitbucket.org/ww/goautoneg/autoneg.go @@ -0,0 +1,162 @@ +/* +HTTP Content-Type Autonegotiation. + +The functions in this package implement the behaviour specified in +http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + +Copyright (c) 2011, Open Knowledge Foundation Ltd. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + Neither the name of the Open Knowledge Foundation Ltd. nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +*/ +package goautoneg + +import ( + "sort" + "strconv" + "strings" +) + +// Structure to represent a clause in an HTTP Accept Header +type Accept struct { + Type, SubType string + Q float64 + Params map[string]string +} + +// For internal use, so that we can use the sort interface +type accept_slice []Accept + +func (accept accept_slice) Len() int { + slice := []Accept(accept) + return len(slice) +} + +func (accept accept_slice) Less(i, j int) bool { + slice := []Accept(accept) + ai, aj := slice[i], slice[j] + if ai.Q > aj.Q { + return true + } + if ai.Type != "*" && aj.Type == "*" { + return true + } + if ai.SubType != "*" && aj.SubType == "*" { + return true + } + return false +} + +func (accept accept_slice) Swap(i, j int) { + slice := []Accept(accept) + slice[i], slice[j] = slice[j], slice[i] +} + +// Parse an Accept Header string returning a sorted list +// of clauses +func ParseAccept(header string) (accept []Accept) { + parts := strings.Split(header, ",") + accept = make([]Accept, 0, len(parts)) + for _, part := range parts { + part := strings.Trim(part, " ") + + a := Accept{} + a.Params = make(map[string]string) + a.Q = 1.0 + + mrp := strings.Split(part, ";") + + media_range := mrp[0] + sp := strings.Split(media_range, "/") + a.Type = strings.Trim(sp[0], " ") + + switch { + case len(sp) == 1 && a.Type == "*": + a.SubType = "*" + case len(sp) == 2: + a.SubType = strings.Trim(sp[1], " ") + default: + continue + } + + if len(mrp) == 1 { + accept = append(accept, a) + continue + } + + for _, param := range mrp[1:] { + sp := strings.SplitN(param, "=", 2) + if len(sp) != 2 { + continue + } + token := strings.Trim(sp[0], " ") + if token == "q" { + a.Q, _ = strconv.ParseFloat(sp[1], 32) + } else { + a.Params[token] = strings.Trim(sp[1], " ") + } + } + + accept = append(accept, a) + } + + slice := accept_slice(accept) + sort.Sort(slice) + + return +} + +// Negotiate the most appropriate content_type given the accept header +// and a list of alternatives. +func Negotiate(header string, alternatives []string) (content_type string) { + asp := make([][]string, 0, len(alternatives)) + for _, ctype := range alternatives { + asp = append(asp, strings.SplitN(ctype, "/", 2)) + } + for _, clause := range ParseAccept(header) { + for i, ctsp := range asp { + if clause.Type == ctsp[0] && clause.SubType == ctsp[1] { + content_type = alternatives[i] + return + } + if clause.Type == ctsp[0] && clause.SubType == "*" { + content_type = alternatives[i] + return + } + if clause.Type == "*" && clause.SubType == "*" { + content_type = alternatives[i] + return + } + } + } + return +} diff --git a/vendor/bitbucket.org/ww/goautoneg/autoneg_test.go b/vendor/bitbucket.org/ww/goautoneg/autoneg_test.go new file mode 100644 index 000000000..41d328f1d --- /dev/null +++ b/vendor/bitbucket.org/ww/goautoneg/autoneg_test.go @@ -0,0 +1,33 @@ +package goautoneg + +import ( + "testing" +) + +var chrome = "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" + +func TestParseAccept(t *testing.T) { + alternatives := []string{"text/html", "image/png"} + content_type := Negotiate(chrome, alternatives) + if content_type != "image/png" { + t.Errorf("got %s expected image/png", content_type) + } + + alternatives = []string{"text/html", "text/plain", "text/n3"} + content_type = Negotiate(chrome, alternatives) + if content_type != "text/html" { + t.Errorf("got %s expected text/html", content_type) + } + + alternatives = []string{"text/n3", "text/plain"} + content_type = Negotiate(chrome, alternatives) + if content_type != "text/plain" { + t.Errorf("got %s expected text/plain", content_type) + } + + alternatives = []string{"text/n3", "application/rdf+xml"} + content_type = Negotiate(chrome, alternatives) + if content_type != "text/n3" { + t.Errorf("got %s expected text/n3", content_type) + } +} diff --git a/vendor/github.com/NYTimes/gziphandler/.gitignore b/vendor/github.com/NYTimes/gziphandler/.gitignore new file mode 100644 index 000000000..1377554eb --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/vendor/github.com/NYTimes/gziphandler/.travis.yml b/vendor/github.com/NYTimes/gziphandler/.travis.yml new file mode 100644 index 000000000..d2b67f69c --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/.travis.yml @@ -0,0 +1,6 @@ +language: go + +go: + - 1.7 + - 1.8 + - tip diff --git a/vendor/github.com/NYTimes/gziphandler/CODE_OF_CONDUCT.md b/vendor/github.com/NYTimes/gziphandler/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..cdbca194c --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +--- +layout: code-of-conduct +version: v1.0 +--- + +This code of conduct outlines our expectations for participants within the **NYTimes/gziphandler** community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community. + +Our open source community strives to: + +* **Be friendly and patient.** +* **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. +* **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. +* **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. +* **Be careful in the words that we choose**: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. +* **Try to understand why we disagree**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. + +## Definitions + +Harassment includes, but is not limited to: + +- Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, neuro(a)typicality, physical appearance, body size, race, age, regional discrimination, political or religious affiliation +- Unwelcome comments regarding a person’s lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment +- Deliberate misgendering. This includes deadnaming or persistently using a pronoun that does not correctly reflect a person's gender identity. You must address people by the name they give you when not addressing them by their username or handle +- Physical contact and simulated physical contact (eg, textual descriptions like “*hug*” or “*backrub*”) without consent or after a request to stop +- Threats of violence, both physical and psychological +- Incitement of violence towards any individual, including encouraging a person to commit suicide or to engage in self-harm +- Deliberate intimidation +- Stalking or following +- Harassing photography or recording, including logging online activity for harassment purposes +- Sustained disruption of discussion +- Unwelcome sexual attention, including gratuitous or off-topic sexual images or behaviour +- Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others +- Continued one-on-one communication after requests to cease +- Deliberate “outing” of any aspect of a person’s identity without their consent except as necessary to protect others from intentional abuse +- Publication of non-harassing private communication + +Our open source community prioritizes marginalized people’s safety over privileged people’s comfort. We will not act on complaints regarding: + +- ‘Reverse’ -isms, including ‘reverse racism,’ ‘reverse sexism,’ and ‘cisphobia’ +- Reasonable communication of boundaries, such as “leave me alone,” “go away,” or “I’m not discussing this with you” +- Refusal to explain or debate social justice concepts +- Communicating in a ‘tone’ you don’t find congenial +- Criticizing racist, sexist, cissexist, or otherwise oppressive behavior or assumptions + + +### Diversity Statement + +We encourage everyone to participate and are committed to building a community for all. Although we will fail at times, we seek to treat everyone both as fairly and equally as possible. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong. + +Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected +characteristics above, including participants with disabilities. + +### Reporting Issues + +If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via **code@nytimes.com**. All reports will be handled with discretion. In your report please include: + +- Your contact information. +- Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please +include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link. +- Any additional information that may be helpful. + +After filing a report, a representative will contact you personally, review the incident, follow up with any additional questions, and make a decision as to how to respond. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. If the complaint originates from a member of the response team, it will be handled by a different member of the response team. We will respect confidentiality requests for the purpose of protecting victims of abuse. + +### Attribution & Acknowledgements + +We all stand on the shoulders of giants across many open source communities. We'd like to thank the communities and projects that established code of conducts and diversity statements as our inspiration: + +* [Django](https://www.djangoproject.com/conduct/reporting/) +* [Python](https://www.python.org/community/diversity/) +* [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct) +* [Contributor Covenant](http://contributor-covenant.org/) +* [Geek Feminism](http://geekfeminism.org/about/code-of-conduct/) +* [Citizen Code of Conduct](http://citizencodeofconduct.org/) + +This Code of Conduct was based on https://github.com/todogroup/opencodeofconduct diff --git a/vendor/github.com/NYTimes/gziphandler/CONTRIBUTING.md b/vendor/github.com/NYTimes/gziphandler/CONTRIBUTING.md new file mode 100644 index 000000000..b89a9eb4f --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contributing to NYTimes/gziphandler + +This is an open source project started by handful of developers at The New York Times and open to the entire Go community. + +We really appreciate your help! + +## Filing issues + +When filing an issue, make sure to answer these five questions: + +1. What version of Go are you using (`go version`)? +2. What operating system and processor architecture are you using? +3. What did you do? +4. What did you expect to see? +5. What did you see instead? + +## Contributing code + +Before submitting changes, please follow these guidelines: + +1. Check the open issues and pull requests for existing discussions. +2. Open an issue to discuss a new feature. +3. Write tests. +4. Make sure code follows the ['Go Code Review Comments'](https://github.com/golang/go/wiki/CodeReviewComments). +5. Make sure your changes pass `go test`. +6. Make sure the entire test suite passes locally and on Travis CI. +7. Open a Pull Request. +8. [Squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) after receiving feedback and add a [great commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). + +Unless otherwise noted, the gziphandler source files are distributed under the Apache 2.0-style license found in the LICENSE.md file. diff --git a/vendor/github.com/NYTimes/gziphandler/LICENSE.md b/vendor/github.com/NYTimes/gziphandler/LICENSE.md new file mode 100644 index 000000000..b7e2ecb63 --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/LICENSE.md @@ -0,0 +1,13 @@ +Copyright (c) 2015 The New York Times Company + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library 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. diff --git a/vendor/github.com/NYTimes/gziphandler/README.md b/vendor/github.com/NYTimes/gziphandler/README.md new file mode 100644 index 000000000..6d7246070 --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/README.md @@ -0,0 +1,52 @@ +Gzip Handler +============ + +This is a tiny Go package which wraps HTTP handlers to transparently gzip the +response body, for clients which support it. Although it's usually simpler to +leave that to a reverse proxy (like nginx or Varnish), this package is useful +when that's undesirable. + + +## Usage + +Call `GzipHandler` with any handler (an object which implements the +`http.Handler` interface), and it'll return a new handler which gzips the +response. For example: + +```go +package main + +import ( + "io" + "net/http" + "github.com/NYTimes/gziphandler" +) + +func main() { + withoutGz := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + io.WriteString(w, "Hello, World") + }) + + withGz := gziphandler.GzipHandler(withoutGz) + + http.Handle("/", withGz) + http.ListenAndServe("0.0.0.0:8000", nil) +} +``` + + +## Documentation + +The docs can be found at [godoc.org][docs], as usual. + + +## License + +[Apache 2.0][license]. + + + + +[docs]: https://godoc.org/github.com/nytimes/gziphandler +[license]: https://github.com/nytimes/gziphandler/blob/master/LICENSE.md diff --git a/vendor/github.com/NYTimes/gziphandler/gzip.go b/vendor/github.com/NYTimes/gziphandler/gzip.go new file mode 100644 index 000000000..ea6dba1e7 --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/gzip.go @@ -0,0 +1,332 @@ +package gziphandler + +import ( + "bufio" + "compress/gzip" + "fmt" + "io" + "net" + "net/http" + "strconv" + "strings" + "sync" +) + +const ( + vary = "Vary" + acceptEncoding = "Accept-Encoding" + contentEncoding = "Content-Encoding" + contentType = "Content-Type" + contentLength = "Content-Length" +) + +type codings map[string]float64 + +const ( + // DefaultQValue is the default qvalue to assign to an encoding if no explicit qvalue is set. + // This is actually kind of ambiguous in RFC 2616, so hopefully it's correct. + // The examples seem to indicate that it is. + DefaultQValue = 1.0 + + // DefaultMinSize defines the minimum size to reach to enable compression. + // It's 512 bytes. + DefaultMinSize = 512 +) + +// gzipWriterPools stores a sync.Pool for each compression level for reuse of +// gzip.Writers. Use poolIndex to covert a compression level to an index into +// gzipWriterPools. +var gzipWriterPools [gzip.BestCompression - gzip.BestSpeed + 2]*sync.Pool + +func init() { + for i := gzip.BestSpeed; i <= gzip.BestCompression; i++ { + addLevelPool(i) + } + addLevelPool(gzip.DefaultCompression) +} + +// poolIndex maps a compression level to its index into gzipWriterPools. It +// assumes that level is a valid gzip compression level. +func poolIndex(level int) int { + // gzip.DefaultCompression == -1, so we need to treat it special. + if level == gzip.DefaultCompression { + return gzip.BestCompression - gzip.BestSpeed + 1 + } + return level - gzip.BestSpeed +} + +func addLevelPool(level int) { + gzipWriterPools[poolIndex(level)] = &sync.Pool{ + New: func() interface{} { + // NewWriterLevel only returns error on a bad level, we are guaranteeing + // that this will be a valid level so it is okay to ignore the returned + // error. + w, _ := gzip.NewWriterLevel(nil, level) + return w + }, + } +} + +// GzipResponseWriter provides an http.ResponseWriter interface, which gzips +// bytes before writing them to the underlying response. This doesn't close the +// writers, so don't forget to do that. +// It can be configured to skip response smaller than minSize. +type GzipResponseWriter struct { + http.ResponseWriter + index int // Index for gzipWriterPools. + gw *gzip.Writer + + code int // Saves the WriteHeader value. + + minSize int // Specifed the minimum response size to gzip. If the response length is bigger than this value, it is compressed. + buf []byte // Holds the first part of the write before reaching the minSize or the end of the write. +} + +// Write appends data to the gzip writer. +func (w *GzipResponseWriter) Write(b []byte) (int, error) { + // If content type is not set. + if _, ok := w.Header()[contentType]; !ok { + // It infer it from the uncompressed body. + w.Header().Set(contentType, http.DetectContentType(b)) + } + + // GZIP responseWriter is initialized. Use the GZIP responseWriter. + if w.gw != nil { + n, err := w.gw.Write(b) + return n, err + } + + // Save the write into a buffer for later use in GZIP responseWriter (if content is long enough) or at close with regular responseWriter. + // On the first write, w.buf changes from nil to a valid slice + w.buf = append(w.buf, b...) + + // If the global writes are bigger than the minSize, compression is enable. + if len(w.buf) >= w.minSize { + err := w.startGzip() + if err != nil { + return 0, err + } + } + + return len(b), nil +} + +// startGzip initialize any GZIP specific informations. +func (w *GzipResponseWriter) startGzip() error { + + // Set the GZIP header. + w.Header().Set(contentEncoding, "gzip") + + // if the Content-Length is already set, then calls to Write on gzip + // will fail to set the Content-Length header since its already set + // See: https://github.com/golang/go/issues/14975. + w.Header().Del(contentLength) + + // Write the header to gzip response. + if w.code != 0 { + w.ResponseWriter.WriteHeader(w.code) + } + + // Initialize the GZIP response. + w.init() + + // Flush the buffer into the gzip reponse. + n, err := w.gw.Write(w.buf) + + // This should never happen (per io.Writer docs), but if the write didn't + // accept the entire buffer but returned no specific error, we have no clue + // what's going on, so abort just to be safe. + if err == nil && n < len(w.buf) { + return io.ErrShortWrite + } + + w.buf = nil + return err +} + +// WriteHeader just saves the response code until close or GZIP effective writes. +func (w *GzipResponseWriter) WriteHeader(code int) { + w.code = code +} + +// init graps a new gzip writer from the gzipWriterPool and writes the correct +// content encoding header. +func (w *GzipResponseWriter) init() { + // Bytes written during ServeHTTP are redirected to this gzip writer + // before being written to the underlying response. + gzw := gzipWriterPools[w.index].Get().(*gzip.Writer) + gzw.Reset(w.ResponseWriter) + w.gw = gzw +} + +// Close will close the gzip.Writer and will put it back in the gzipWriterPool. +func (w *GzipResponseWriter) Close() error { + if w.gw == nil { + // Gzip not trigged yet, write out regular response. + if w.code != 0 { + w.ResponseWriter.WriteHeader(w.code) + } + if w.buf != nil { + _, writeErr := w.ResponseWriter.Write(w.buf) + // Returns the error if any at write. + if writeErr != nil { + return fmt.Errorf("gziphandler: write to regular responseWriter at close gets error: %q", writeErr.Error()) + } + } + return nil + } + + err := w.gw.Close() + gzipWriterPools[w.index].Put(w.gw) + w.gw = nil + return err +} + +// Flush flushes the underlying *gzip.Writer and then the underlying +// http.ResponseWriter if it is an http.Flusher. This makes GzipResponseWriter +// an http.Flusher. +func (w *GzipResponseWriter) Flush() { + if w.gw != nil { + w.gw.Flush() + } + + if fw, ok := w.ResponseWriter.(http.Flusher); ok { + fw.Flush() + } +} + +// Hijack implements http.Hijacker. If the underlying ResponseWriter is a +// Hijacker, its Hijack method is returned. Otherwise an error is returned. +func (w *GzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if hj, ok := w.ResponseWriter.(http.Hijacker); ok { + return hj.Hijack() + } + return nil, nil, fmt.Errorf("http.Hijacker interface is not supported") +} + +// verify Hijacker interface implementation +var _ http.Hijacker = &GzipResponseWriter{} + +// MustNewGzipLevelHandler behaves just like NewGzipLevelHandler except that in +// an error case it panics rather than returning an error. +func MustNewGzipLevelHandler(level int) func(http.Handler) http.Handler { + wrap, err := NewGzipLevelHandler(level) + if err != nil { + panic(err) + } + return wrap +} + +// NewGzipLevelHandler returns a wrapper function (often known as middleware) +// which can be used to wrap an HTTP handler to transparently gzip the response +// body if the client supports it (via the Accept-Encoding header). Responses will +// be encoded at the given gzip compression level. An error will be returned only +// if an invalid gzip compression level is given, so if one can ensure the level +// is valid, the returned error can be safely ignored. +func NewGzipLevelHandler(level int) (func(http.Handler) http.Handler, error) { + return NewGzipLevelAndMinSize(level, DefaultMinSize) +} + +// NewGzipLevelAndMinSize behave as NewGzipLevelHandler except it let the caller +// specify the minimum size before compression. +func NewGzipLevelAndMinSize(level, minSize int) (func(http.Handler) http.Handler, error) { + if level != gzip.DefaultCompression && (level < gzip.BestSpeed || level > gzip.BestCompression) { + return nil, fmt.Errorf("invalid compression level requested: %d", level) + } + if minSize < 0 { + return nil, fmt.Errorf("minimum size must be more than zero") + } + return func(h http.Handler) http.Handler { + index := poolIndex(level) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add(vary, acceptEncoding) + + if acceptsGzip(r) { + gw := &GzipResponseWriter{ + ResponseWriter: w, + index: index, + minSize: minSize, + } + defer gw.Close() + + h.ServeHTTP(gw, r) + } else { + h.ServeHTTP(w, r) + } + }) + }, nil +} + +// GzipHandler wraps an HTTP handler, to transparently gzip the response body if +// the client supports it (via the Accept-Encoding header). This will compress at +// the default compression level. +func GzipHandler(h http.Handler) http.Handler { + wrapper, _ := NewGzipLevelHandler(gzip.DefaultCompression) + return wrapper(h) +} + +// acceptsGzip returns true if the given HTTP request indicates that it will +// accept a gzipped response. +func acceptsGzip(r *http.Request) bool { + acceptedEncodings, _ := parseEncodings(r.Header.Get(acceptEncoding)) + return acceptedEncodings["gzip"] > 0.0 +} + +// parseEncodings attempts to parse a list of codings, per RFC 2616, as might +// appear in an Accept-Encoding header. It returns a map of content-codings to +// quality values, and an error containing the errors encountered. It's probably +// safe to ignore those, because silently ignoring errors is how the internet +// works. +// +// See: http://tools.ietf.org/html/rfc2616#section-14.3. +func parseEncodings(s string) (codings, error) { + c := make(codings) + var e []string + + for _, ss := range strings.Split(s, ",") { + coding, qvalue, err := parseCoding(ss) + + if err != nil { + e = append(e, err.Error()) + } else { + c[coding] = qvalue + } + } + + // TODO (adammck): Use a proper multi-error struct, so the individual errors + // can be extracted if anyone cares. + if len(e) > 0 { + return c, fmt.Errorf("errors while parsing encodings: %s", strings.Join(e, ", ")) + } + + return c, nil +} + +// parseCoding parses a single conding (content-coding with an optional qvalue), +// as might appear in an Accept-Encoding header. It attempts to forgive minor +// formatting errors. +func parseCoding(s string) (coding string, qvalue float64, err error) { + for n, part := range strings.Split(s, ";") { + part = strings.TrimSpace(part) + qvalue = DefaultQValue + + if n == 0 { + coding = strings.ToLower(part) + } else if strings.HasPrefix(part, "q=") { + qvalue, err = strconv.ParseFloat(strings.TrimPrefix(part, "q="), 64) + + if qvalue < 0.0 { + qvalue = 0.0 + } else if qvalue > 1.0 { + qvalue = 1.0 + } + } + } + + if coding == "" { + err = fmt.Errorf("empty content-coding") + } + + return +} diff --git a/vendor/github.com/NYTimes/gziphandler/gzip_go18.go b/vendor/github.com/NYTimes/gziphandler/gzip_go18.go new file mode 100644 index 000000000..fa9665b7e --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/gzip_go18.go @@ -0,0 +1,43 @@ +// +build go1.8 + +package gziphandler + +import "net/http" + +// Push initiates an HTTP/2 server push. +// Push returns ErrNotSupported if the client has disabled push or if push +// is not supported on the underlying connection. +func (w *GzipResponseWriter) Push(target string, opts *http.PushOptions) error { + pusher, ok := w.ResponseWriter.(http.Pusher) + if ok && pusher != nil { + return pusher.Push(target, setAcceptEncodingForPushOptions(opts)) + } + return http.ErrNotSupported +} + +// setAcceptEncodingForPushOptions sets "Accept-Encoding" : "gzip" for PushOptions without overriding existing headers. +func setAcceptEncodingForPushOptions(opts *http.PushOptions) *http.PushOptions { + + if opts == nil { + opts = &http.PushOptions{ + Header: http.Header{ + acceptEncoding: []string{"gzip"}, + }, + } + return opts + } + + if opts.Header == nil { + opts.Header = http.Header{ + acceptEncoding: []string{"gzip"}, + } + return opts + } + + if encoding := opts.Header.Get(acceptEncoding); encoding == "" { + opts.Header.Add(acceptEncoding, "gzip") + return opts + } + + return opts +} diff --git a/vendor/github.com/NYTimes/gziphandler/gzip_go18_test.go b/vendor/github.com/NYTimes/gziphandler/gzip_go18_test.go new file mode 100644 index 000000000..412b2918e --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/gzip_go18_test.go @@ -0,0 +1,70 @@ +// +build go1.8 + +package gziphandler + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSetAcceptEncodingForPushOptionsWithoutHeaders(t *testing.T) { + var opts *http.PushOptions + opts = setAcceptEncodingForPushOptions(opts) + + assert.NotNil(t, opts) + assert.NotNil(t, opts.Header) + + for k, v := range opts.Header { + assert.Equal(t, "Accept-Encoding", k) + assert.Len(t, v, 1) + assert.Equal(t, "gzip", v[0]) + } + + opts = &http.PushOptions{} + opts = setAcceptEncodingForPushOptions(opts) + + assert.NotNil(t, opts) + assert.NotNil(t, opts.Header) + + for k, v := range opts.Header { + assert.Equal(t, "Accept-Encoding", k) + assert.Len(t, v, 1) + assert.Equal(t, "gzip", v[0]) + } +} + +func TestSetAcceptEncodingForPushOptionsWithHeaders(t *testing.T) { + opts := &http.PushOptions{ + Header: http.Header{ + "User-Agent": []string{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36"}, + }, + } + opts = setAcceptEncodingForPushOptions(opts) + + assert.NotNil(t, opts) + assert.NotNil(t, opts.Header) + + assert.Equal(t, "gzip", opts.Header.Get("Accept-Encoding")) + assert.Equal(t, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36", opts.Header.Get("User-Agent")) + + opts = &http.PushOptions{ + Header: http.Header{ + "User-Agent": []string{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36"}, + acceptEncoding: []string{"deflate"}, + }, + } + opts = setAcceptEncodingForPushOptions(opts) + + assert.NotNil(t, opts) + assert.NotNil(t, opts.Header) + + e, found := opts.Header["Accept-Encoding"] + if !found { + assert.Fail(t, "Missing Accept-Encoding header value") + } + assert.Len(t, e, 1) + assert.Equal(t, "deflate", e[0]) + assert.Equal(t, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36", opts.Header.Get("User-Agent")) +} diff --git a/vendor/github.com/NYTimes/gziphandler/gzip_test.go b/vendor/github.com/NYTimes/gziphandler/gzip_test.go new file mode 100644 index 000000000..0f6488cae --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/gzip_test.go @@ -0,0 +1,380 @@ +package gziphandler + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + smallTestBody = "aaabbcaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbccc" + testBody = "aaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbccc" +) + +func TestParseEncodings(t *testing.T) { + examples := map[string]codings{ + + // Examples from RFC 2616 + "compress, gzip": {"compress": 1.0, "gzip": 1.0}, + "": {}, + "*": {"*": 1.0}, + "compress;q=0.5, gzip;q=1.0": {"compress": 0.5, "gzip": 1.0}, + "gzip;q=1.0, identity; q=0.5, *;q=0": {"gzip": 1.0, "identity": 0.5, "*": 0.0}, + + // More random stuff + "AAA;q=1": {"aaa": 1.0}, + "BBB ; q = 2": {"bbb": 1.0}, + } + + for eg, exp := range examples { + act, _ := parseEncodings(eg) + assert.Equal(t, exp, act) + } +} + +func TestGzipHandler(t *testing.T) { + // This just exists to provide something for GzipHandler to wrap. + handler := newTestHandler(testBody) + + // requests without accept-encoding are passed along as-is + + req1, _ := http.NewRequest("GET", "/whatever", nil) + resp1 := httptest.NewRecorder() + handler.ServeHTTP(resp1, req1) + res1 := resp1.Result() + + assert.Equal(t, 200, res1.StatusCode) + assert.Equal(t, "", res1.Header.Get("Content-Encoding")) + assert.Equal(t, "Accept-Encoding", res1.Header.Get("Vary")) + assert.Equal(t, testBody, resp1.Body.String()) + + // but requests with accept-encoding:gzip are compressed if possible + + req2, _ := http.NewRequest("GET", "/whatever", nil) + req2.Header.Set("Accept-Encoding", "gzip") + resp2 := httptest.NewRecorder() + handler.ServeHTTP(resp2, req2) + res2 := resp2.Result() + + assert.Equal(t, 200, res2.StatusCode) + assert.Equal(t, "gzip", res2.Header.Get("Content-Encoding")) + assert.Equal(t, "Accept-Encoding", res2.Header.Get("Vary")) + assert.Equal(t, gzipStrLevel(testBody, gzip.DefaultCompression), resp2.Body.Bytes()) + + // content-type header is correctly set based on uncompressed body + + req3, _ := http.NewRequest("GET", "/whatever", nil) + req3.Header.Set("Accept-Encoding", "gzip") + res3 := httptest.NewRecorder() + handler.ServeHTTP(res3, req3) + + assert.Equal(t, http.DetectContentType([]byte(testBody)), res3.Header().Get("Content-Type")) +} + +func TestNewGzipLevelHandler(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + io.WriteString(w, testBody) + }) + + for lvl := gzip.BestSpeed; lvl <= gzip.BestCompression; lvl++ { + wrapper, err := NewGzipLevelHandler(lvl) + if !assert.Nil(t, err, "NewGzipLevleHandler returned error for level:", lvl) { + continue + } + + req, _ := http.NewRequest("GET", "/whatever", nil) + req.Header.Set("Accept-Encoding", "gzip") + resp := httptest.NewRecorder() + wrapper(handler).ServeHTTP(resp, req) + res := resp.Result() + + assert.Equal(t, 200, res.StatusCode) + assert.Equal(t, "gzip", res.Header.Get("Content-Encoding")) + assert.Equal(t, "Accept-Encoding", res.Header.Get("Vary")) + assert.Equal(t, gzipStrLevel(testBody, lvl), resp.Body.Bytes()) + } +} + +func TestNewGzipLevelHandlerReturnsErrorForInvalidLevels(t *testing.T) { + var err error + _, err = NewGzipLevelHandler(-42) + assert.NotNil(t, err) + + _, err = NewGzipLevelHandler(42) + assert.NotNil(t, err) +} + +func TestMustNewGzipLevelHandlerWillPanic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("panic was not called") + } + }() + + _ = MustNewGzipLevelHandler(-42) +} + +func TestGzipHandlerNoBody(t *testing.T) { + tests := []struct { + statusCode int + contentEncoding string + bodyLen int + }{ + // Body must be empty. + {http.StatusNoContent, "", 0}, + {http.StatusNotModified, "", 0}, + // Body is going to get gzip'd no matter what. + {http.StatusOK, "", 0}, + } + + for num, test := range tests { + handler := GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(test.statusCode) + })) + + rec := httptest.NewRecorder() + // TODO: in Go1.7 httptest.NewRequest was introduced this should be used + // once 1.6 is not longer supported. + req := &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/"}, + Proto: "HTTP/1.1", + ProtoMinor: 1, + RemoteAddr: "192.0.2.1:1234", + Header: make(http.Header), + } + req.Header.Set("Accept-Encoding", "gzip") + handler.ServeHTTP(rec, req) + + body, err := ioutil.ReadAll(rec.Body) + if err != nil { + t.Fatalf("Unexpected error reading response body: %v", err) + } + + header := rec.Header() + assert.Equal(t, test.contentEncoding, header.Get("Content-Encoding"), fmt.Sprintf("for test iteration %d", num)) + assert.Equal(t, "Accept-Encoding", header.Get("Vary"), fmt.Sprintf("for test iteration %d", num)) + assert.Equal(t, test.bodyLen, len(body), fmt.Sprintf("for test iteration %d", num)) + } +} + +func TestGzipHandlerContentLength(t *testing.T) { + b := []byte(testBody) + handler := GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Length", strconv.Itoa(len(b))) + w.Write(b) + })) + // httptest.NewRecorder doesn't give you access to the Content-Length + // header so instead, we create a server on a random port and make + // a request to that instead + ln, err := net.Listen("tcp", "127.0.0.1:") + if err != nil { + t.Fatalf("failed creating listen socket: %v", err) + } + defer ln.Close() + srv := &http.Server{ + Handler: handler, + } + go srv.Serve(ln) + + req := &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/", Scheme: "http", Host: ln.Addr().String()}, + Header: make(http.Header), + Close: true, + } + req.Header.Set("Accept-Encoding", "gzip") + res, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Unexpected error making http request: %v", err) + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatalf("Unexpected error reading response body: %v", err) + } + + l, err := strconv.Atoi(res.Header.Get("Content-Length")) + if err != nil { + t.Fatalf("Unexpected error parsing Content-Length: %v", err) + } + assert.Len(t, body, l) + assert.Equal(t, "gzip", res.Header.Get("Content-Encoding")) + assert.NotEqual(t, b, body) +} + +func TestGzipHandlerMinSizeMustBePositive(t *testing.T) { + _, err := NewGzipLevelAndMinSize(gzip.DefaultCompression, -1) + assert.Error(t, err) +} + +func TestGzipHandlerMinSize(t *testing.T) { + responseLength := 0 + b := []byte{'x'} + + wrapper, _ := NewGzipLevelAndMinSize(gzip.DefaultCompression, 128) + handler := wrapper(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + // Write responses one byte at a time to ensure that the flush + // mechanism, if used, is working properly. + for i := 0; i < responseLength; i++ { + n, err := w.Write(b) + assert.Equal(t, 1, n) + assert.Nil(t, err) + } + }, + )) + + r, _ := http.NewRequest("GET", "/whatever", &bytes.Buffer{}) + r.Header.Add("Accept-Encoding", "gzip") + + // Short response is not compressed + responseLength = 127 + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + if w.Result().Header.Get(contentEncoding) == "gzip" { + t.Error("Expected uncompressed response, got compressed") + } + + // Long response is not compressed + responseLength = 128 + w = httptest.NewRecorder() + handler.ServeHTTP(w, r) + if w.Result().Header.Get(contentEncoding) != "gzip" { + t.Error("Expected compressed response, got uncompressed") + } +} + +func TestGzipDoubleClose(t *testing.T) { + // reset the pool for the default compression so we can make sure duplicates + // aren't added back by double close + addLevelPool(gzip.DefaultCompression) + + handler := GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // call close here and it'll get called again interally by + // NewGzipLevelHandler's handler defer + w.Write([]byte("test")) + w.(io.Closer).Close() + })) + + r := httptest.NewRequest("GET", "/", nil) + r.Header.Set("Accept-Encoding", "gzip") + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + + // the second close shouldn't have added the same writer + // so we pull out 2 writers from the pool and make sure they're different + w1 := gzipWriterPools[poolIndex(gzip.DefaultCompression)].Get() + w2 := gzipWriterPools[poolIndex(gzip.DefaultCompression)].Get() + // assert.NotEqual looks at the value and not the address, so we use regular == + assert.False(t, w1 == w2) +} + +func TestStatusCodes(t *testing.T) { + handler := GzipHandler(http.NotFoundHandler()) + r := httptest.NewRequest("GET", "/", nil) + r.Header.Set("Accept-Encoding", "gzip") + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + + result := w.Result() + if result.StatusCode != 404 { + t.Errorf("StatusCode should have been 404 but was %d", result.StatusCode) + } +} + +func TestDontWriteWhenNotWrittenTo(t *testing.T) { + // When using gzip as middleware without ANY writes in the handler, + // ensure the gzip middleware doesn't touch the actual ResponseWriter + // either. + + handler0 := GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + + handler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler0.ServeHTTP(w, r) + w.WriteHeader(404) // this only works if gzip didn't do a WriteHeader(200) + }) + + r := httptest.NewRequest("GET", "/", nil) + r.Header.Set("Accept-Encoding", "gzip") + w := httptest.NewRecorder() + handler1.ServeHTTP(w, r) + + result := w.Result() + if result.StatusCode != 404 { + t.Errorf("StatusCode should have been 404 but was %d", result.StatusCode) + } +} + +// -------------------------------------------------------------------- + +func BenchmarkGzipHandler_S2k(b *testing.B) { benchmark(b, false, 2048) } +func BenchmarkGzipHandler_S20k(b *testing.B) { benchmark(b, false, 20480) } +func BenchmarkGzipHandler_S100k(b *testing.B) { benchmark(b, false, 102400) } +func BenchmarkGzipHandler_P2k(b *testing.B) { benchmark(b, true, 2048) } +func BenchmarkGzipHandler_P20k(b *testing.B) { benchmark(b, true, 20480) } +func BenchmarkGzipHandler_P100k(b *testing.B) { benchmark(b, true, 102400) } + +// -------------------------------------------------------------------- + +func gzipStrLevel(s string, lvl int) []byte { + var b bytes.Buffer + w, _ := gzip.NewWriterLevel(&b, lvl) + io.WriteString(w, s) + w.Close() + return b.Bytes() +} + +func benchmark(b *testing.B, parallel bool, size int) { + bin, err := ioutil.ReadFile("testdata/benchmark.json") + if err != nil { + b.Fatal(err) + } + + req, _ := http.NewRequest("GET", "/whatever", nil) + req.Header.Set("Accept-Encoding", "gzip") + handler := newTestHandler(string(bin[:size])) + + if parallel { + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + runBenchmark(b, req, handler) + } + }) + } else { + b.ResetTimer() + for i := 0; i < b.N; i++ { + runBenchmark(b, req, handler) + } + } +} + +func runBenchmark(b *testing.B, req *http.Request, handler http.Handler) { + res := httptest.NewRecorder() + handler.ServeHTTP(res, req) + if code := res.Code; code != 200 { + b.Fatalf("Expected 200 but got %d", code) + } else if blen := res.Body.Len(); blen < 500 { + b.Fatalf("Expected complete response body, but got %d bytes", blen) + } +} + +func newTestHandler(body string) http.Handler { + return GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, body) + })) +} diff --git a/vendor/github.com/NYTimes/gziphandler/testdata/benchmark.json b/vendor/github.com/NYTimes/gziphandler/testdata/benchmark.json new file mode 100644 index 000000000..d9180d6ac --- /dev/null +++ b/vendor/github.com/NYTimes/gziphandler/testdata/benchmark.json @@ -0,0 +1,5456 @@ +[ + { + "_id": "55d2fc86da7c3f96f90aa005", + "age": 20, + "name": "Luann Grant", + "gender": "female", + "company": "ZOGAK", + "email": "luanngrant@zogak.com", + "phone": "+1 (915) 479-2908" + }, + { + "_id": "55d2fc8653953bc9a0958a92", + "age": 34, + "name": "Sanders Gonzalez", + "gender": "male", + "company": "PIVITOL", + "email": "sandersgonzalez@pivitol.com", + "phone": "+1 (914) 563-2007" + }, + { + "_id": "55d2fc86a38634b0954fe3c0", + "age": 26, + "name": "Compton Terry", + "gender": "male", + "company": "KOZGENE", + "email": "comptonterry@kozgene.com", + "phone": "+1 (812) 558-2536" + }, + { + "_id": "55d2fc86edf1be88253e4e2e", + "age": 29, + "name": "Erma Armstrong", + "gender": "female", + "company": "DYNO", + "email": "ermaarmstrong@dyno.com", + "phone": "+1 (811) 556-3980" + }, + { + "_id": "55d2fc86bee1bf1f233f8170", + "age": 20, + "name": "Carson Garcia", + "gender": "male", + "company": "JUNIPOOR", + "email": "carsongarcia@junipoor.com", + "phone": "+1 (820) 410-3221" + }, + { + "_id": "55d2fc864810db4a3738dea8", + "age": 38, + "name": "Henson Townsend", + "gender": "male", + "company": "OVIUM", + "email": "hensontownsend@ovium.com", + "phone": "+1 (982) 412-3108" + }, + { + "_id": "55d2fc86d714d77af8ed3fe4", + "age": 34, + "name": "Yesenia Garner", + "gender": "female", + "company": "TERRAGO", + "email": "yeseniagarner@terrago.com", + "phone": "+1 (878) 561-2314" + }, + { + "_id": "55d2fc867651311b2e11925a", + "age": 31, + "name": "Rachelle Stanton", + "gender": "female", + "company": "UNISURE", + "email": "rachellestanton@unisure.com", + "phone": "+1 (961) 504-3072" + }, + { + "_id": "55d2fc866415f6c03d5228eb", + "age": 30, + "name": "Franklin Rasmussen", + "gender": "male", + "company": "CAPSCREEN", + "email": "franklinrasmussen@capscreen.com", + "phone": "+1 (886) 525-2217" + }, + { + "_id": "55d2fc86e2e0fa5f81fe5279", + "age": 32, + "name": "Fischer Humphrey", + "gender": "male", + "company": "ATGEN", + "email": "fischerhumphrey@atgen.com", + "phone": "+1 (855) 424-3693" + }, + { + "_id": "55d2fc862c2a416777837c76", + "age": 29, + "name": "Olsen Moran", + "gender": "male", + "company": "SULTRAX", + "email": "olsenmoran@sultrax.com", + "phone": "+1 (860) 583-3380" + }, + { + "_id": "55d2fc868c3c9e44d59ec2a2", + "age": 29, + "name": "Mattie Myers", + "gender": "female", + "company": "VALPREAL", + "email": "mattiemyers@valpreal.com", + "phone": "+1 (834) 587-2707" + }, + { + "_id": "55d2fc860a6afc8beebe477d", + "age": 39, + "name": "Cleveland Jordan", + "gender": "male", + "company": "XLEEN", + "email": "clevelandjordan@xleen.com", + "phone": "+1 (848) 449-2037" + }, + { + "_id": "55d2fc8605766af1b531b5ea", + "age": 35, + "name": "Gordon Rios", + "gender": "male", + "company": "BULLZONE", + "email": "gordonrios@bullzone.com", + "phone": "+1 (882) 436-2216" + }, + { + "_id": "55d2fc86e052bfb7cf9b5a38", + "age": 33, + "name": "Todd Burch", + "gender": "male", + "company": "MOTOVATE", + "email": "toddburch@motovate.com", + "phone": "+1 (911) 470-2129" + }, + { + "_id": "55d2fc86212924928b4112d6", + "age": 31, + "name": "Autumn Strong", + "gender": "female", + "company": "NURPLEX", + "email": "autumnstrong@nurplex.com", + "phone": "+1 (827) 483-2571" + }, + { + "_id": "55d2fc86286c9bc87359e326", + "age": 27, + "name": "Rochelle Fitzgerald", + "gender": "female", + "company": "RAMEON", + "email": "rochellefitzgerald@rameon.com", + "phone": "+1 (820) 402-3411" + }, + { + "_id": "55d2fc86511439244d21c569", + "age": 24, + "name": "Perry Hopkins", + "gender": "male", + "company": "KRAGGLE", + "email": "perryhopkins@kraggle.com", + "phone": "+1 (826) 469-3928" + }, + { + "_id": "55d2fc868b8652b54c66051c", + "age": 37, + "name": "Guzman Williamson", + "gender": "male", + "company": "OCTOCORE", + "email": "guzmanwilliamson@octocore.com", + "phone": "+1 (826) 529-3380" + }, + { + "_id": "55d2fc86f16a5a4c310483df", + "age": 28, + "name": "Sheri Thompson", + "gender": "female", + "company": "AUTOGRATE", + "email": "sherithompson@autograte.com", + "phone": "+1 (942) 463-2727" + }, + { + "_id": "55d2fc86675d3040fc35daa8", + "age": 24, + "name": "Walton Macdonald", + "gender": "male", + "company": "ZIZZLE", + "email": "waltonmacdonald@zizzle.com", + "phone": "+1 (990) 510-2656" + }, + { + "_id": "55d2fc864e90e236f9e5a174", + "age": 39, + "name": "Gwendolyn Ross", + "gender": "female", + "company": "XIIX", + "email": "gwendolynross@xiix.com", + "phone": "+1 (869) 565-2774" + }, + { + "_id": "55d2fc866f306c575a36cb97", + "age": 23, + "name": "Sexton Herring", + "gender": "male", + "company": "MEDIOT", + "email": "sextonherring@mediot.com", + "phone": "+1 (935) 510-2049" + }, + { + "_id": "55d2fc867526db78550711a3", + "age": 26, + "name": "Twila Vang", + "gender": "female", + "company": "ARCTIQ", + "email": "twilavang@arctiq.com", + "phone": "+1 (979) 429-2135" + }, + { + "_id": "55d2fc86fe5e24c1b4b0dc96", + "age": 26, + "name": "Marjorie Snider", + "gender": "female", + "company": "QUARX", + "email": "marjoriesnider@quarx.com", + "phone": "+1 (913) 469-2916" + }, + { + "_id": "55d2fc861e85e0104fa7d33e", + "age": 36, + "name": "Malone Diaz", + "gender": "male", + "company": "COMVEYOR", + "email": "malonediaz@comveyor.com", + "phone": "+1 (882) 541-3306" + }, + { + "_id": "55d2fc86bc04a4fa0a338403", + "age": 39, + "name": "Amelia Sanford", + "gender": "female", + "company": "IPLAX", + "email": "ameliasanford@iplax.com", + "phone": "+1 (872) 589-3509" + }, + { + "_id": "55d2fc86f7854f672e80c1dd", + "age": 27, + "name": "Kristie Fernandez", + "gender": "female", + "company": "RONBERT", + "email": "kristiefernandez@ronbert.com", + "phone": "+1 (983) 419-3564" + }, + { + "_id": "55d2fc867faa140f152b7229", + "age": 23, + "name": "Bray Wyatt", + "gender": "male", + "company": "DEMINIMUM", + "email": "braywyatt@deminimum.com", + "phone": "+1 (943) 485-3961" + }, + { + "_id": "55d2fc863a42779d68c0dd53", + "age": 36, + "name": "Meyer Pickett", + "gender": "male", + "company": "GENEKOM", + "email": "meyerpickett@genekom.com", + "phone": "+1 (999) 534-3038" + }, + { + "_id": "55d2fc86095f5ceb3c57efeb", + "age": 22, + "name": "Carlson Ramirez", + "gender": "male", + "company": "KANGLE", + "email": "carlsonramirez@kangle.com", + "phone": "+1 (972) 590-3152" + }, + { + "_id": "55d2fc868036a76e42f30954", + "age": 24, + "name": "Roth Murray", + "gender": "male", + "company": "DEEPENDS", + "email": "rothmurray@deepends.com", + "phone": "+1 (944) 408-2208" + }, + { + "_id": "55d2fc8688ac278604ea592b", + "age": 24, + "name": "Cecelia Lambert", + "gender": "female", + "company": "SLOFAST", + "email": "cecelialambert@slofast.com", + "phone": "+1 (907) 485-2284" + }, + { + "_id": "55d2fc86b05fc3906ce838a2", + "age": 31, + "name": "Leah Ferguson", + "gender": "female", + "company": "NEBULEAN", + "email": "leahferguson@nebulean.com", + "phone": "+1 (883) 574-2101" + }, + { + "_id": "55d2fc86d21f9d73f11076b9", + "age": 28, + "name": "Therese Mueller", + "gender": "female", + "company": "STEELFAB", + "email": "theresemueller@steelfab.com", + "phone": "+1 (920) 485-2265" + }, + { + "_id": "55d2fc866254f87d0389dc98", + "age": 32, + "name": "Wanda Byrd", + "gender": "female", + "company": "HOTCAKES", + "email": "wandabyrd@hotcakes.com", + "phone": "+1 (893) 579-3658" + }, + { + "_id": "55d2fc868cf04450063aba0e", + "age": 25, + "name": "Felecia Mccall", + "gender": "female", + "company": "GINK", + "email": "feleciamccall@gink.com", + "phone": "+1 (848) 486-3047" + }, + { + "_id": "55d2fc86e0ee0341850d35ab", + "age": 37, + "name": "Goldie Stafford", + "gender": "female", + "company": "FIREWAX", + "email": "goldiestafford@firewax.com", + "phone": "+1 (831) 507-3578" + }, + { + "_id": "55d2fc868acfa20489a61720", + "age": 20, + "name": "Amy Patterson", + "gender": "female", + "company": "KNEEDLES", + "email": "amypatterson@kneedles.com", + "phone": "+1 (950) 478-3558" + }, + { + "_id": "55d2fc8680c104681b074336", + "age": 29, + "name": "Robles Alford", + "gender": "male", + "company": "TALENDULA", + "email": "roblesalford@talendula.com", + "phone": "+1 (813) 424-3650" + }, + { + "_id": "55d2fc86faeb57024907ea70", + "age": 28, + "name": "Adkins Hampton", + "gender": "male", + "company": "SYNKGEN", + "email": "adkinshampton@synkgen.com", + "phone": "+1 (974) 522-2517" + }, + { + "_id": "55d2fc866ba878e18ee65c0f", + "age": 32, + "name": "Bernadine Trevino", + "gender": "female", + "company": "EVENTEX", + "email": "bernadinetrevino@eventex.com", + "phone": "+1 (914) 577-2655" + }, + { + "_id": "55d2fc86524364ec8cb2d0c7", + "age": 38, + "name": "Robin Le", + "gender": "female", + "company": "GALLAXIA", + "email": "robinle@gallaxia.com", + "phone": "+1 (821) 472-2416" + }, + { + "_id": "55d2fc86c53dc98dacabc399", + "age": 23, + "name": "Leanna Hicks", + "gender": "female", + "company": "ZEROLOGY", + "email": "leannahicks@zerology.com", + "phone": "+1 (946) 531-3368" + }, + { + "_id": "55d2fc86e293b8cfb2d1a5bd", + "age": 31, + "name": "Herman Bridges", + "gender": "male", + "company": "DADABASE", + "email": "hermanbridges@dadabase.com", + "phone": "+1 (836) 408-3169" + }, + { + "_id": "55d2fc865dfc0cc61ec50b41", + "age": 30, + "name": "Olive Terrell", + "gender": "female", + "company": "ACCRUEX", + "email": "oliveterrell@accruex.com", + "phone": "+1 (837) 573-2059" + }, + { + "_id": "55d2fc8669dc78cdeb374ff5", + "age": 26, + "name": "Miranda Banks", + "gender": "female", + "company": "GEOFARM", + "email": "mirandabanks@geofarm.com", + "phone": "+1 (984) 574-2877" + }, + { + "_id": "55d2fc86f471f565df37a756", + "age": 31, + "name": "Noelle Wolf", + "gender": "female", + "company": "ENDIPINE", + "email": "noellewolf@endipine.com", + "phone": "+1 (880) 548-2427" + }, + { + "_id": "55d2fc8603e133136e5ca325", + "age": 38, + "name": "Amber Klein", + "gender": "female", + "company": "ISOPLEX", + "email": "amberklein@isoplex.com", + "phone": "+1 (853) 548-3856" + }, + { + "_id": "55d2fc86962b00cfd86e9d8d", + "age": 32, + "name": "Jodie Mcclain", + "gender": "female", + "company": "BESTO", + "email": "jodiemcclain@besto.com", + "phone": "+1 (857) 417-2691" + }, + { + "_id": "55d2fc8622eaf8052986a5bb", + "age": 35, + "name": "Margo Melendez", + "gender": "female", + "company": "KLUGGER", + "email": "margomelendez@klugger.com", + "phone": "+1 (823) 445-3570" + }, + { + "_id": "55d2fc861e37cd298741e801", + "age": 37, + "name": "Castaneda Dudley", + "gender": "male", + "company": "URBANSHEE", + "email": "castanedadudley@urbanshee.com", + "phone": "+1 (865) 449-2445" + }, + { + "_id": "55d2fc86e4c07ff933f7b531", + "age": 40, + "name": "Sherman Combs", + "gender": "male", + "company": "GRACKER", + "email": "shermancombs@gracker.com", + "phone": "+1 (817) 472-2316" + }, + { + "_id": "55d2fc86c4613e0a622727e6", + "age": 31, + "name": "Louise Nichols", + "gender": "female", + "company": "SPORTAN", + "email": "louisenichols@sportan.com", + "phone": "+1 (823) 543-2230" + }, + { + "_id": "55d2fc86d40dd218587cc632", + "age": 29, + "name": "Daniels Jacobs", + "gender": "male", + "company": "CALCULA", + "email": "danielsjacobs@calcula.com", + "phone": "+1 (979) 448-2244" + }, + { + "_id": "55d2fc86b44f3ec927f3b2e1", + "age": 22, + "name": "Cristina Crosby", + "gender": "female", + "company": "PHUEL", + "email": "cristinacrosby@phuel.com", + "phone": "+1 (816) 504-3557" + }, + { + "_id": "55d2fc86ba635f5436325487", + "age": 23, + "name": "Tisha Sawyer", + "gender": "female", + "company": "IMAGEFLOW", + "email": "tishasawyer@imageflow.com", + "phone": "+1 (894) 551-2933" + }, + { + "_id": "55d2fc8640e8af981a47216a", + "age": 32, + "name": "Janice Graham", + "gender": "female", + "company": "PYRAMIS", + "email": "janicegraham@pyramis.com", + "phone": "+1 (986) 409-2529" + }, + { + "_id": "55d2fc86cdbe80c5cb184731", + "age": 22, + "name": "Holman Joyce", + "gender": "male", + "company": "VERTON", + "email": "holmanjoyce@verton.com", + "phone": "+1 (955) 445-2054" + }, + { + "_id": "55d2fc860f70e0d0890e8e47", + "age": 21, + "name": "Webb Sears", + "gender": "male", + "company": "ENQUILITY", + "email": "webbsears@enquility.com", + "phone": "+1 (961) 406-3600" + }, + { + "_id": "55d2fc86150c8385661fde6f", + "age": 29, + "name": "Bush Farrell", + "gender": "male", + "company": "ENDICIL", + "email": "bushfarrell@endicil.com", + "phone": "+1 (894) 550-2963" + }, + { + "_id": "55d2fc862e2f1ca9662b03e3", + "age": 36, + "name": "Gay Walters", + "gender": "male", + "company": "ZENSURE", + "email": "gaywalters@zensure.com", + "phone": "+1 (859) 467-3816" + }, + { + "_id": "55d2fc86da1249ca6d3a068f", + "age": 33, + "name": "Marquez Palmer", + "gender": "male", + "company": "TURNLING", + "email": "marquezpalmer@turnling.com", + "phone": "+1 (878) 522-3859" + }, + { + "_id": "55d2fc86a61a66a69db5c8a9", + "age": 29, + "name": "Huber Parrish", + "gender": "male", + "company": "DELPHIDE", + "email": "huberparrish@delphide.com", + "phone": "+1 (947) 406-3267" + }, + { + "_id": "55d2fc86fff10fda8f106c2a", + "age": 22, + "name": "Foreman Cohen", + "gender": "male", + "company": "TROPOLIS", + "email": "foremancohen@tropolis.com", + "phone": "+1 (802) 409-2459" + }, + { + "_id": "55d2fc8632b185024494802d", + "age": 31, + "name": "Kathryn Peterson", + "gender": "female", + "company": "PROSURE", + "email": "kathrynpeterson@prosure.com", + "phone": "+1 (811) 531-3085" + }, + { + "_id": "55d2fc86f8e11410bb5b8e88", + "age": 34, + "name": "Aisha Duke", + "gender": "female", + "company": "APPLICA", + "email": "aishaduke@applica.com", + "phone": "+1 (977) 438-2754" + }, + { + "_id": "55d2fc86190a0097728af887", + "age": 37, + "name": "Maynard Henderson", + "gender": "male", + "company": "ILLUMITY", + "email": "maynardhenderson@illumity.com", + "phone": "+1 (832) 472-2261" + }, + { + "_id": "55d2fc86d298e6e1cf9332df", + "age": 33, + "name": "Villarreal Santiago", + "gender": "male", + "company": "QUAILCOM", + "email": "villarrealsantiago@quailcom.com", + "phone": "+1 (981) 422-2572" + }, + { + "_id": "55d2fc86b7eb3b9ed01fc186", + "age": 30, + "name": "Diaz Allison", + "gender": "male", + "company": "ISOSTREAM", + "email": "diazallison@isostream.com", + "phone": "+1 (883) 530-2186" + }, + { + "_id": "55d2fc86da92ac006d69c236", + "age": 20, + "name": "Kirkland Mccullough", + "gender": "male", + "company": "ADORNICA", + "email": "kirklandmccullough@adornica.com", + "phone": "+1 (950) 556-3562" + }, + { + "_id": "55d2fc86195f59929397f2fc", + "age": 32, + "name": "Dolores Howard", + "gender": "female", + "company": "ZINCA", + "email": "doloreshoward@zinca.com", + "phone": "+1 (904) 450-2101" + }, + { + "_id": "55d2fc860c9f707c22eaca7c", + "age": 26, + "name": "Mills Gamble", + "gender": "male", + "company": "BLEEKO", + "email": "millsgamble@bleeko.com", + "phone": "+1 (942) 402-2630" + }, + { + "_id": "55d2fc86b539183079fcab65", + "age": 35, + "name": "Walls Dotson", + "gender": "male", + "company": "GRUPOLI", + "email": "wallsdotson@grupoli.com", + "phone": "+1 (979) 477-3065" + }, + { + "_id": "55d2fc866377b14a737261c3", + "age": 29, + "name": "Hannah Coleman", + "gender": "female", + "company": "COMCUR", + "email": "hannahcoleman@comcur.com", + "phone": "+1 (944) 595-2415" + }, + { + "_id": "55d2fc867b77f0e8d5f08276", + "age": 25, + "name": "Kinney Oneill", + "gender": "male", + "company": "ELPRO", + "email": "kinneyoneill@elpro.com", + "phone": "+1 (851) 570-2363" + }, + { + "_id": "55d2fc868745f5f71578624c", + "age": 32, + "name": "Bennett Henson", + "gender": "male", + "company": "ZISIS", + "email": "bennetthenson@zisis.com", + "phone": "+1 (958) 584-2257" + }, + { + "_id": "55d2fc8604d1e7576b1a83fb", + "age": 32, + "name": "Ross Chavez", + "gender": "male", + "company": "DUOFLEX", + "email": "rosschavez@duoflex.com", + "phone": "+1 (890) 556-2052" + }, + { + "_id": "55d2fc86bd556093136802dd", + "age": 26, + "name": "Raymond Rutledge", + "gender": "male", + "company": "BUNGA", + "email": "raymondrutledge@bunga.com", + "phone": "+1 (994) 504-2118" + }, + { + "_id": "55d2fc86a122c7d89560c130", + "age": 31, + "name": "Rosemary Larsen", + "gender": "female", + "company": "EXOPLODE", + "email": "rosemarylarsen@exoplode.com", + "phone": "+1 (864) 558-3569" + }, + { + "_id": "55d2fc86dd3fe9ea62fdbea1", + "age": 27, + "name": "Tara Roth", + "gender": "female", + "company": "GAPTEC", + "email": "tararoth@gaptec.com", + "phone": "+1 (893) 531-2962" + }, + { + "_id": "55d2fc866f372ca411cd425d", + "age": 30, + "name": "Ronda Sheppard", + "gender": "female", + "company": "GREEKER", + "email": "rondasheppard@greeker.com", + "phone": "+1 (963) 569-2851" + }, + { + "_id": "55d2fc860a0dc369ac8bed9f", + "age": 37, + "name": "Nita Washington", + "gender": "female", + "company": "AQUASURE", + "email": "nitawashington@aquasure.com", + "phone": "+1 (858) 579-3734" + }, + { + "_id": "55d2fc863d59ecccd8b482b7", + "age": 34, + "name": "Shannon Sanchez", + "gender": "male", + "company": "QUORDATE", + "email": "shannonsanchez@quordate.com", + "phone": "+1 (880) 542-2259" + }, + { + "_id": "55d2fc861c73e366d8983f98", + "age": 23, + "name": "Amalia George", + "gender": "female", + "company": "QUINEX", + "email": "amaliageorge@quinex.com", + "phone": "+1 (918) 412-3805" + }, + { + "_id": "55d2fc866910c4329a167746", + "age": 30, + "name": "West Parsons", + "gender": "male", + "company": "RODEMCO", + "email": "westparsons@rodemco.com", + "phone": "+1 (869) 480-3404" + }, + { + "_id": "55d2fc861d97ff31a8eb7f06", + "age": 30, + "name": "Day Mercado", + "gender": "male", + "company": "ENJOLA", + "email": "daymercado@enjola.com", + "phone": "+1 (861) 546-2601" + }, + { + "_id": "55d2fc861e100baa15994161", + "age": 20, + "name": "Hubbard Randolph", + "gender": "male", + "company": "INSOURCE", + "email": "hubbardrandolph@insource.com", + "phone": "+1 (939) 422-2753" + }, + { + "_id": "55d2fc86dc33a0101292ea57", + "age": 32, + "name": "Ward Patrick", + "gender": "male", + "company": "ZUVY", + "email": "wardpatrick@zuvy.com", + "phone": "+1 (852) 461-3079" + }, + { + "_id": "55d2fc863074ff55ec22d7ac", + "age": 26, + "name": "Barker Small", + "gender": "male", + "company": "ZIDANT", + "email": "barkersmall@zidant.com", + "phone": "+1 (901) 514-3653" + }, + { + "_id": "55d2fc86008067de21a65447", + "age": 26, + "name": "Margret Porter", + "gender": "female", + "company": "DYMI", + "email": "margretporter@dymi.com", + "phone": "+1 (820) 404-2199" + }, + { + "_id": "55d2fc8694b7f8670d10781e", + "age": 40, + "name": "Roslyn Richmond", + "gender": "female", + "company": "SONGBIRD", + "email": "roslynrichmond@songbird.com", + "phone": "+1 (913) 406-3720" + }, + { + "_id": "55d2fc86ffff28aef88e1d69", + "age": 29, + "name": "Jimenez Yates", + "gender": "male", + "company": "IDETICA", + "email": "jimenezyates@idetica.com", + "phone": "+1 (803) 421-2358" + }, + { + "_id": "55d2fc8604bfeab3a975b2d8", + "age": 21, + "name": "Mcneil Jensen", + "gender": "male", + "company": "EXOBLUE", + "email": "mcneiljensen@exoblue.com", + "phone": "+1 (856) 581-3756" + }, + { + "_id": "55d2fc86bee368958f567c45", + "age": 40, + "name": "Millicent Trujillo", + "gender": "female", + "company": "ARTWORLDS", + "email": "millicenttrujillo@artworlds.com", + "phone": "+1 (926) 456-2237" + }, + { + "_id": "55d2fc86de2a5a6babe51d48", + "age": 26, + "name": "Dorothea Duffy", + "gender": "female", + "company": "EBIDCO", + "email": "dorotheaduffy@ebidco.com", + "phone": "+1 (873) 458-2694" + }, + { + "_id": "55d2fc86f8793320727d2020", + "age": 34, + "name": "Gloria Ward", + "gender": "female", + "company": "EXOVENT", + "email": "gloriaward@exovent.com", + "phone": "+1 (948) 552-3275" + }, + { + "_id": "55d2fc86967e5599adde68e8", + "age": 33, + "name": "Denise Hogan", + "gender": "female", + "company": "GLEAMINK", + "email": "denisehogan@gleamink.com", + "phone": "+1 (902) 544-2943" + }, + { + "_id": "55d2fc862833baf2918f7860", + "age": 23, + "name": "Clemons Berger", + "gender": "male", + "company": "ARTIQ", + "email": "clemonsberger@artiq.com", + "phone": "+1 (873) 516-2440" + }, + { + "_id": "55d2fc8610af4c20d426854a", + "age": 40, + "name": "Colette Scott", + "gender": "female", + "company": "INVENTURE", + "email": "colettescott@inventure.com", + "phone": "+1 (814) 556-3466" + }, + { + "_id": "55d2fc8697af4d0d06a2c4a6", + "age": 34, + "name": "Chavez Aguilar", + "gender": "male", + "company": "ISBOL", + "email": "chavezaguilar@isbol.com", + "phone": "+1 (952) 543-3992" + }, + { + "_id": "55d2fc861da12d86036c3e48", + "age": 29, + "name": "Arnold Collier", + "gender": "male", + "company": "MELBACOR", + "email": "arnoldcollier@melbacor.com", + "phone": "+1 (907) 404-2090" + }, + { + "_id": "55d2fc86deb921c78b56fec1", + "age": 24, + "name": "Nixon Ingram", + "gender": "male", + "company": "ELENTRIX", + "email": "nixoningram@elentrix.com", + "phone": "+1 (884) 596-3023" + }, + { + "_id": "55d2fc86a8b288ba6b8aa48f", + "age": 28, + "name": "Hurst Hull", + "gender": "male", + "company": "INCUBUS", + "email": "hursthull@incubus.com", + "phone": "+1 (970) 445-2279" + }, + { + "_id": "55d2fc864dc593d113336b1b", + "age": 32, + "name": "Giles Jacobson", + "gender": "male", + "company": "MIXERS", + "email": "gilesjacobson@mixers.com", + "phone": "+1 (868) 402-3161" + }, + { + "_id": "55d2fc86fd0246fcd0c8f864", + "age": 25, + "name": "Millie Giles", + "gender": "female", + "company": "ZAJ", + "email": "milliegiles@zaj.com", + "phone": "+1 (831) 535-3535" + }, + { + "_id": "55d2fc86dceeca0deb9f6d67", + "age": 21, + "name": "Sargent Morse", + "gender": "male", + "company": "ANARCO", + "email": "sargentmorse@anarco.com", + "phone": "+1 (994) 536-2563" + }, + { + "_id": "55d2fc86abbd1add64df6aa2", + "age": 26, + "name": "Reed Camacho", + "gender": "male", + "company": "ICOLOGY", + "email": "reedcamacho@icology.com", + "phone": "+1 (999) 408-3042" + }, + { + "_id": "55d2fc86a2593db6d584f022", + "age": 21, + "name": "Bryant Colon", + "gender": "male", + "company": "ZANILLA", + "email": "bryantcolon@zanilla.com", + "phone": "+1 (964) 570-3418" + }, + { + "_id": "55d2fc86f980f74ba093fde6", + "age": 38, + "name": "Rosella Pierce", + "gender": "female", + "company": "PRIMORDIA", + "email": "rosellapierce@primordia.com", + "phone": "+1 (936) 579-3014" + }, + { + "_id": "55d2fc86797a0001329071fb", + "age": 20, + "name": "Patrice Mckee", + "gender": "female", + "company": "GEEKY", + "email": "patricemckee@geeky.com", + "phone": "+1 (866) 565-3764" + }, + { + "_id": "55d2fc86bcc6181d0b8cae17", + "age": 29, + "name": "Wilkinson Levy", + "gender": "male", + "company": "FISHLAND", + "email": "wilkinsonlevy@fishland.com", + "phone": "+1 (998) 509-3800" + }, + { + "_id": "55d2fc86784b1e22004d637d", + "age": 28, + "name": "Isabella Snyder", + "gender": "female", + "company": "TETRATREX", + "email": "isabellasnyder@tetratrex.com", + "phone": "+1 (998) 451-2871" + }, + { + "_id": "55d2fc8603abab523ff1f02d", + "age": 35, + "name": "Claudine Berg", + "gender": "female", + "company": "ZYTREK", + "email": "claudineberg@zytrek.com", + "phone": "+1 (836) 563-3376" + }, + { + "_id": "55d2fc862796e94846a82a8b", + "age": 27, + "name": "Hyde Simon", + "gender": "male", + "company": "FUELWORKS", + "email": "hydesimon@fuelworks.com", + "phone": "+1 (872) 594-2291" + }, + { + "_id": "55d2fc86f2fa84a4f7ca4548", + "age": 29, + "name": "Wendy Roberts", + "gender": "female", + "company": "OPTICALL", + "email": "wendyroberts@opticall.com", + "phone": "+1 (895) 534-3852" + }, + { + "_id": "55d2fc8651dbb76674b6d2df", + "age": 40, + "name": "Tasha Erickson", + "gender": "female", + "company": "PURIA", + "email": "tashaerickson@puria.com", + "phone": "+1 (905) 526-2175" + }, + { + "_id": "55d2fc86b7f867e76e709d68", + "age": 32, + "name": "Marisa Dunlap", + "gender": "female", + "company": "GLUKGLUK", + "email": "marisadunlap@glukgluk.com", + "phone": "+1 (855) 400-2200" + }, + { + "_id": "55d2fc8695b6ccc0707570af", + "age": 38, + "name": "Kaitlin Baldwin", + "gender": "female", + "company": "EGYPTO", + "email": "kaitlinbaldwin@egypto.com", + "phone": "+1 (980) 482-3256" + }, + { + "_id": "55d2fc86f36b539d4494395d", + "age": 38, + "name": "Abigail Kirkland", + "gender": "female", + "company": "INRT", + "email": "abigailkirkland@inrt.com", + "phone": "+1 (916) 440-3469" + }, + { + "_id": "55d2fc86e401343dbf1de380", + "age": 34, + "name": "Marci Maynard", + "gender": "female", + "company": "INSURITY", + "email": "marcimaynard@insurity.com", + "phone": "+1 (917) 482-3601" + }, + { + "_id": "55d2fc86ba3f7fdb738899f4", + "age": 31, + "name": "Tanya Michael", + "gender": "female", + "company": "ENVIRE", + "email": "tanyamichael@envire.com", + "phone": "+1 (826) 436-3177" + }, + { + "_id": "55d2fc861cb8a1d3151ae2c1", + "age": 34, + "name": "Farrell Irwin", + "gender": "male", + "company": "EXTRAGEN", + "email": "farrellirwin@extragen.com", + "phone": "+1 (948) 442-3796" + }, + { + "_id": "55d2fc86b90f948949af3d8e", + "age": 27, + "name": "Dickson Shepherd", + "gender": "male", + "company": "COMTOURS", + "email": "dicksonshepherd@comtours.com", + "phone": "+1 (871) 458-2972" + }, + { + "_id": "55d2fc862065aeaf17581fa6", + "age": 40, + "name": "Fowler Adams", + "gender": "male", + "company": "NEPTIDE", + "email": "fowleradams@neptide.com", + "phone": "+1 (836) 431-3585" + }, + { + "_id": "55d2fc866aab8b242a30acfd", + "age": 22, + "name": "Durham Frost", + "gender": "male", + "company": "SNOWPOKE", + "email": "durhamfrost@snowpoke.com", + "phone": "+1 (801) 510-2084" + }, + { + "_id": "55d2fc86d3a4aafedc274ffa", + "age": 27, + "name": "Delia Barnes", + "gender": "female", + "company": "CONFERIA", + "email": "deliabarnes@conferia.com", + "phone": "+1 (999) 471-2182" + }, + { + "_id": "55d2fc8601ed1a317e289785", + "age": 27, + "name": "Gill Tillman", + "gender": "male", + "company": "MATRIXITY", + "email": "gilltillman@matrixity.com", + "phone": "+1 (880) 419-3960" + }, + { + "_id": "55d2fc86098dda4fa3812793", + "age": 27, + "name": "Loraine Saunders", + "gender": "female", + "company": "DOGTOWN", + "email": "lorainesaunders@dogtown.com", + "phone": "+1 (868) 500-3061" + }, + { + "_id": "55d2fc865f9615b558837892", + "age": 34, + "name": "Lou Mcdonald", + "gender": "female", + "company": "COMCUBINE", + "email": "loumcdonald@comcubine.com", + "phone": "+1 (857) 568-3427" + }, + { + "_id": "55d2fc865a5a752bb88dbcd8", + "age": 28, + "name": "Beth Lester", + "gender": "female", + "company": "EARTHWAX", + "email": "bethlester@earthwax.com", + "phone": "+1 (805) 565-2363" + }, + { + "_id": "55d2fc862e7d03a2f76c9e58", + "age": 37, + "name": "Karin Mason", + "gender": "female", + "company": "GEEKNET", + "email": "karinmason@geeknet.com", + "phone": "+1 (923) 438-3684" + }, + { + "_id": "55d2fc8617970ba4987ed4b3", + "age": 39, + "name": "Jeanine Watson", + "gender": "female", + "company": "LINGOAGE", + "email": "jeaninewatson@lingoage.com", + "phone": "+1 (919) 444-3722" + }, + { + "_id": "55d2fc86445181571721c6c9", + "age": 20, + "name": "Soto Wilkins", + "gender": "male", + "company": "JUMPSTACK", + "email": "sotowilkins@jumpstack.com", + "phone": "+1 (818) 518-3028" + }, + { + "_id": "55d2fc86b8b146361e17fa83", + "age": 20, + "name": "Mabel Fields", + "gender": "female", + "company": "MAGNINA", + "email": "mabelfields@magnina.com", + "phone": "+1 (861) 583-2161" + }, + { + "_id": "55d2fc8667abfe5721a74176", + "age": 26, + "name": "Ruthie Bailey", + "gender": "female", + "company": "ROCKLOGIC", + "email": "ruthiebailey@rocklogic.com", + "phone": "+1 (872) 402-3619" + }, + { + "_id": "55d2fc865cfa092adb20dc0f", + "age": 22, + "name": "Vaughan Vincent", + "gender": "male", + "company": "ACIUM", + "email": "vaughanvincent@acium.com", + "phone": "+1 (919) 548-3948" + }, + { + "_id": "55d2fc86fe25d347c15e1749", + "age": 38, + "name": "Myrtle Burris", + "gender": "female", + "company": "IRACK", + "email": "myrtleburris@irack.com", + "phone": "+1 (840) 526-2646" + }, + { + "_id": "55d2fc8689cd98b47e44118d", + "age": 37, + "name": "Peggy Mercer", + "gender": "female", + "company": "ESCENTA", + "email": "peggymercer@escenta.com", + "phone": "+1 (817) 574-3310" + }, + { + "_id": "55d2fc86ae662f567ae09404", + "age": 24, + "name": "Mollie Simmons", + "gender": "female", + "company": "STOCKPOST", + "email": "molliesimmons@stockpost.com", + "phone": "+1 (854) 461-2851" + }, + { + "_id": "55d2fc86f738f2bd6ebebd1c", + "age": 37, + "name": "Case Cox", + "gender": "male", + "company": "ACCEL", + "email": "casecox@accel.com", + "phone": "+1 (853) 473-2780" + }, + { + "_id": "55d2fc86c80907be4ff386d5", + "age": 26, + "name": "Snyder Mcclure", + "gender": "male", + "company": "PLASMOX", + "email": "snydermcclure@plasmox.com", + "phone": "+1 (980) 583-3213" + }, + { + "_id": "55d2fc86ed956580fa2cff25", + "age": 31, + "name": "Kelly Malone", + "gender": "male", + "company": "XANIDE", + "email": "kellymalone@xanide.com", + "phone": "+1 (871) 436-2431" + }, + { + "_id": "55d2fc86fcd2b8d749bd1feb", + "age": 20, + "name": "Jackie Carr", + "gender": "female", + "company": "VOIPA", + "email": "jackiecarr@voipa.com", + "phone": "+1 (807) 431-3436" + }, + { + "_id": "55d2fc86d9aac03a007489ef", + "age": 34, + "name": "Cochran Walter", + "gender": "male", + "company": "NIKUDA", + "email": "cochranwalter@nikuda.com", + "phone": "+1 (977) 410-3770" + }, + { + "_id": "55d2fc86140fefcd667c533f", + "age": 22, + "name": "Ellen Ortiz", + "gender": "female", + "company": "NEWCUBE", + "email": "ellenortiz@newcube.com", + "phone": "+1 (980) 541-3099" + }, + { + "_id": "55d2fc86d374c6a649e5877e", + "age": 35, + "name": "Concetta Beard", + "gender": "female", + "company": "PLASMOS", + "email": "concettabeard@plasmos.com", + "phone": "+1 (839) 539-2423" + }, + { + "_id": "55d2fc861ee809861b7c2b38", + "age": 33, + "name": "Josephine Alexander", + "gender": "female", + "company": "LOVEPAD", + "email": "josephinealexander@lovepad.com", + "phone": "+1 (880) 452-2208" + }, + { + "_id": "55d2fc8677db4f8446d43041", + "age": 39, + "name": "Melisa Dean", + "gender": "female", + "company": "GEOFORM", + "email": "melisadean@geoform.com", + "phone": "+1 (934) 452-2532" + }, + { + "_id": "55d2fc861dc0856e2ec744ab", + "age": 26, + "name": "Morgan Galloway", + "gender": "male", + "company": "ELEMANTRA", + "email": "morgangalloway@elemantra.com", + "phone": "+1 (888) 461-2261" + }, + { + "_id": "55d2fc869bc97691cec7d569", + "age": 26, + "name": "Curtis Griffith", + "gender": "male", + "company": "TINGLES", + "email": "curtisgriffith@tingles.com", + "phone": "+1 (881) 517-2174" + }, + { + "_id": "55d2fc8624b80b5986e1de83", + "age": 40, + "name": "Gentry Mccarthy", + "gender": "male", + "company": "GEEKOLA", + "email": "gentrymccarthy@geekola.com", + "phone": "+1 (908) 559-3049" + }, + { + "_id": "55d2fc869c2a158da79f9a7f", + "age": 37, + "name": "Lancaster Justice", + "gender": "male", + "company": "NAXDIS", + "email": "lancasterjustice@naxdis.com", + "phone": "+1 (980) 456-3515" + }, + { + "_id": "55d2fc867c8e30a12fddcb79", + "age": 28, + "name": "Jenifer Barr", + "gender": "female", + "company": "ZORROMOP", + "email": "jeniferbarr@zorromop.com", + "phone": "+1 (901) 440-3979" + }, + { + "_id": "55d2fc8696d7c8b59507f10c", + "age": 35, + "name": "Benjamin Nolan", + "gender": "male", + "company": "DIGITALUS", + "email": "benjaminnolan@digitalus.com", + "phone": "+1 (828) 582-3041" + }, + { + "_id": "55d2fc86b7e719a5c07b9f9e", + "age": 27, + "name": "Beach Valentine", + "gender": "male", + "company": "KAGGLE", + "email": "beachvalentine@kaggle.com", + "phone": "+1 (961) 563-3631" + }, + { + "_id": "55d2fc860bb827b4eab70038", + "age": 27, + "name": "Brady Moore", + "gender": "male", + "company": "CORPULSE", + "email": "bradymoore@corpulse.com", + "phone": "+1 (859) 434-2962" + }, + { + "_id": "55d2fc862fc00a2ce6ab7f32", + "age": 27, + "name": "Page Ray", + "gender": "male", + "company": "BEZAL", + "email": "pageray@bezal.com", + "phone": "+1 (845) 492-2182" + }, + { + "_id": "55d2fc861b57330b56d5b3f8", + "age": 40, + "name": "Lupe Gould", + "gender": "female", + "company": "DOGNOSIS", + "email": "lupegould@dognosis.com", + "phone": "+1 (955) 579-2141" + }, + { + "_id": "55d2fc864a8fabbf89d106cd", + "age": 23, + "name": "Melva Abbott", + "gender": "female", + "company": "CODACT", + "email": "melvaabbott@codact.com", + "phone": "+1 (801) 478-2678" + }, + { + "_id": "55d2fc86107ad0ad66c9d3ea", + "age": 22, + "name": "Mendez Middleton", + "gender": "male", + "company": "CUJO", + "email": "mendezmiddleton@cujo.com", + "phone": "+1 (908) 430-2032" + }, + { + "_id": "55d2fc86d2f5edd1222e87cc", + "age": 31, + "name": "Meredith Ayers", + "gender": "female", + "company": "OPTYK", + "email": "meredithayers@optyk.com", + "phone": "+1 (875) 472-2514" + }, + { + "_id": "55d2fc86551f2a796de0f3ad", + "age": 21, + "name": "Burns Serrano", + "gender": "male", + "company": "ZOUNDS", + "email": "burnsserrano@zounds.com", + "phone": "+1 (939) 558-2221" + }, + { + "_id": "55d2fc86540849ad56aeac98", + "age": 30, + "name": "Barr Sykes", + "gender": "male", + "company": "CORECOM", + "email": "barrsykes@corecom.com", + "phone": "+1 (982) 523-3577" + }, + { + "_id": "55d2fc86e7d43d06e4d135f2", + "age": 28, + "name": "Julie Johnson", + "gender": "female", + "company": "BIOTICA", + "email": "juliejohnson@biotica.com", + "phone": "+1 (918) 487-3230" + }, + { + "_id": "55d2fc86450521acf4a465d9", + "age": 23, + "name": "Dawn Vinson", + "gender": "female", + "company": "MENBRAIN", + "email": "dawnvinson@menbrain.com", + "phone": "+1 (936) 525-3273" + }, + { + "_id": "55d2fc86df3e093a104f498b", + "age": 30, + "name": "Ginger Ryan", + "gender": "female", + "company": "ENTHAZE", + "email": "gingerryan@enthaze.com", + "phone": "+1 (865) 530-2726" + }, + { + "_id": "55d2fc86b6a6fed233908500", + "age": 40, + "name": "Mcconnell Prince", + "gender": "male", + "company": "TRIPSCH", + "email": "mcconnellprince@tripsch.com", + "phone": "+1 (923) 586-2117" + }, + { + "_id": "55d2fc8619de2514561b7ac1", + "age": 23, + "name": "Peck Blackwell", + "gender": "male", + "company": "ASSISTIA", + "email": "peckblackwell@assistia.com", + "phone": "+1 (988) 549-3418" + }, + { + "_id": "55d2fc8619b2c263aec32e51", + "age": 32, + "name": "Simmons Benton", + "gender": "male", + "company": "TROLLERY", + "email": "simmonsbenton@trollery.com", + "phone": "+1 (924) 439-2962" + }, + { + "_id": "55d2fc8612c77e18fa3c743d", + "age": 36, + "name": "Mari Silva", + "gender": "female", + "company": "MAGMINA", + "email": "marisilva@magmina.com", + "phone": "+1 (863) 509-3186" + }, + { + "_id": "55d2fc86226a40ce862e518e", + "age": 40, + "name": "Erin Jefferson", + "gender": "female", + "company": "VIAGREAT", + "email": "erinjefferson@viagreat.com", + "phone": "+1 (921) 408-2295" + }, + { + "_id": "55d2fc8601013536fc55a097", + "age": 37, + "name": "Nellie Ballard", + "gender": "female", + "company": "GEOFORMA", + "email": "nellieballard@geoforma.com", + "phone": "+1 (918) 406-2600" + }, + { + "_id": "55d2fc864402965704b3453a", + "age": 27, + "name": "Austin Brewer", + "gender": "male", + "company": "METROZ", + "email": "austinbrewer@metroz.com", + "phone": "+1 (968) 584-2959" + }, + { + "_id": "55d2fc8690a7d6957a09f6c8", + "age": 34, + "name": "Pickett Buckley", + "gender": "male", + "company": "SUREPLEX", + "email": "pickettbuckley@sureplex.com", + "phone": "+1 (975) 520-3259" + }, + { + "_id": "55d2fc8695f3020c96b14f95", + "age": 39, + "name": "Coleen Herman", + "gender": "female", + "company": "NORALI", + "email": "coleenherman@norali.com", + "phone": "+1 (916) 506-2704" + }, + { + "_id": "55d2fc869b58b96d1d2cdfcc", + "age": 37, + "name": "Roy Guerrero", + "gender": "male", + "company": "PLUTORQUE", + "email": "royguerrero@plutorque.com", + "phone": "+1 (922) 541-3741" + }, + { + "_id": "55d2fc86ba1ed1189e9020ee", + "age": 29, + "name": "Oneal Curtis", + "gender": "male", + "company": "DATAGEN", + "email": "onealcurtis@datagen.com", + "phone": "+1 (847) 421-3483" + }, + { + "_id": "55d2fc86b5c5e89eb9fe1e79", + "age": 29, + "name": "Chaney Christian", + "gender": "male", + "company": "XPLOR", + "email": "chaneychristian@xplor.com", + "phone": "+1 (847) 517-3918" + }, + { + "_id": "55d2fc86f6b5a3d91952d941", + "age": 29, + "name": "Cantu Richard", + "gender": "male", + "company": "ZANYMAX", + "email": "canturichard@zanymax.com", + "phone": "+1 (972) 487-2616" + }, + { + "_id": "55d2fc8609b0f98cbc2e101d", + "age": 37, + "name": "Newton Barrera", + "gender": "male", + "company": "PORTALINE", + "email": "newtonbarrera@portaline.com", + "phone": "+1 (964) 527-3130" + }, + { + "_id": "55d2fc86d0e063ca7629c11b", + "age": 28, + "name": "Jodi Pollard", + "gender": "female", + "company": "GOKO", + "email": "jodipollard@goko.com", + "phone": "+1 (957) 468-2658" + }, + { + "_id": "55d2fc8688d95c5c579bd328", + "age": 35, + "name": "Effie Nunez", + "gender": "female", + "company": "SNACKTION", + "email": "effienunez@snacktion.com", + "phone": "+1 (805) 576-3749" + }, + { + "_id": "55d2fc86f36c0eab69222b17", + "age": 38, + "name": "Haley Battle", + "gender": "male", + "company": "TERRAGEN", + "email": "haleybattle@terragen.com", + "phone": "+1 (955) 581-3931" + }, + { + "_id": "55d2fc86ee6900b197860a35", + "age": 29, + "name": "Cathy Vaughn", + "gender": "female", + "company": "CANOPOLY", + "email": "cathyvaughn@canopoly.com", + "phone": "+1 (875) 539-3578" + }, + { + "_id": "55d2fc86613f698b355e87bc", + "age": 26, + "name": "Mendoza Maxwell", + "gender": "male", + "company": "TERAPRENE", + "email": "mendozamaxwell@teraprene.com", + "phone": "+1 (820) 471-3500" + }, + { + "_id": "55d2fc86f86a80f78c45936a", + "age": 28, + "name": "Rosetta Hughes", + "gender": "female", + "company": "DEVILTOE", + "email": "rosettahughes@deviltoe.com", + "phone": "+1 (911) 418-2439" + }, + { + "_id": "55d2fc8683d3c4a377a984a5", + "age": 28, + "name": "Frazier Larson", + "gender": "male", + "company": "DAIDO", + "email": "frazierlarson@daido.com", + "phone": "+1 (995) 459-3756" + }, + { + "_id": "55d2fc8669baef03c4d6a4a5", + "age": 27, + "name": "Brittney Ratliff", + "gender": "female", + "company": "TALKALOT", + "email": "brittneyratliff@talkalot.com", + "phone": "+1 (865) 568-2986" + }, + { + "_id": "55d2fc86404583ccc6b6a4f4", + "age": 21, + "name": "Hancock Gilliam", + "gender": "male", + "company": "EXPOSA", + "email": "hancockgilliam@exposa.com", + "phone": "+1 (894) 519-3139" + }, + { + "_id": "55d2fc8675478e0b84bb5ab3", + "age": 20, + "name": "Lilia Mccormick", + "gender": "female", + "company": "DENTREX", + "email": "liliamccormick@dentrex.com", + "phone": "+1 (997) 552-3944" + }, + { + "_id": "55d2fc8658bf3954cdf52ebb", + "age": 20, + "name": "Chrystal Mcneil", + "gender": "female", + "company": "LIMOZEN", + "email": "chrystalmcneil@limozen.com", + "phone": "+1 (943) 519-2952" + }, + { + "_id": "55d2fc865ebec9e8c7adc383", + "age": 31, + "name": "Hebert Alston", + "gender": "male", + "company": "IZZBY", + "email": "hebertalston@izzby.com", + "phone": "+1 (953) 482-2029" + }, + { + "_id": "55d2fc86bf1085e6fd2e9ea1", + "age": 23, + "name": "Mcfarland Carrillo", + "gender": "male", + "company": "EQUITOX", + "email": "mcfarlandcarrillo@equitox.com", + "phone": "+1 (999) 478-3822" + }, + { + "_id": "55d2fc8641d4f2d09f539bd6", + "age": 40, + "name": "Porter Weaver", + "gender": "male", + "company": "QUILM", + "email": "porterweaver@quilm.com", + "phone": "+1 (831) 501-2739" + }, + { + "_id": "55d2fc869ee5331a50039e30", + "age": 23, + "name": "Dale Sims", + "gender": "male", + "company": "SCENTRIC", + "email": "dalesims@scentric.com", + "phone": "+1 (845) 597-2120" + }, + { + "_id": "55d2fc86cb96d31fe71e4a18", + "age": 22, + "name": "William Dixon", + "gender": "male", + "company": "NAMEBOX", + "email": "williamdixon@namebox.com", + "phone": "+1 (970) 448-2651" + }, + { + "_id": "55d2fc86e88daeb1e198671d", + "age": 33, + "name": "Patrica Reed", + "gender": "female", + "company": "CORPORANA", + "email": "patricareed@corporana.com", + "phone": "+1 (817) 457-2413" + }, + { + "_id": "55d2fc86c33d9f422bf8bfcc", + "age": 20, + "name": "Marylou Mcmillan", + "gender": "female", + "company": "RETRACK", + "email": "maryloumcmillan@retrack.com", + "phone": "+1 (908) 568-2328" + }, + { + "_id": "55d2fc86c4b1b139887e76f1", + "age": 20, + "name": "Maritza David", + "gender": "female", + "company": "ORBEAN", + "email": "maritzadavid@orbean.com", + "phone": "+1 (851) 503-3737" + }, + { + "_id": "55d2fc86ac0f360d91c740e6", + "age": 23, + "name": "Lorie Moses", + "gender": "female", + "company": "ROCKABYE", + "email": "loriemoses@rockabye.com", + "phone": "+1 (823) 431-2387" + }, + { + "_id": "55d2fc86d7d406569b386b17", + "age": 35, + "name": "Shields Weiss", + "gender": "male", + "company": "RODEOCEAN", + "email": "shieldsweiss@rodeocean.com", + "phone": "+1 (957) 490-3725" + }, + { + "_id": "55d2fc86e4f66ce770ae2c93", + "age": 25, + "name": "Eugenia Berry", + "gender": "female", + "company": "BISBA", + "email": "eugeniaberry@bisba.com", + "phone": "+1 (948) 403-3403" + }, + { + "_id": "55d2fc8618f2f4f428223522", + "age": 33, + "name": "Howard Compton", + "gender": "male", + "company": "NAMEGEN", + "email": "howardcompton@namegen.com", + "phone": "+1 (827) 439-3667" + }, + { + "_id": "55d2fc86f5adc0f6dd535ea6", + "age": 28, + "name": "Key Davis", + "gender": "male", + "company": "PORTICO", + "email": "keydavis@portico.com", + "phone": "+1 (873) 533-2980" + }, + { + "_id": "55d2fc86876669f4e9431417", + "age": 33, + "name": "Phillips Solis", + "gender": "male", + "company": "DANCITY", + "email": "phillipssolis@dancity.com", + "phone": "+1 (883) 481-3114" + }, + { + "_id": "55d2fc86f2bb610d7ad9ea36", + "age": 40, + "name": "Cash Pugh", + "gender": "male", + "company": "STUCCO", + "email": "cashpugh@stucco.com", + "phone": "+1 (873) 512-2106" + }, + { + "_id": "55d2fc863be1649d5bd3be39", + "age": 21, + "name": "Elinor Warner", + "gender": "female", + "company": "FOSSIEL", + "email": "elinorwarner@fossiel.com", + "phone": "+1 (950) 431-3679" + }, + { + "_id": "55d2fc86fdad2af5536237e2", + "age": 35, + "name": "Jacquelyn Doyle", + "gender": "female", + "company": "CYTREX", + "email": "jacquelyndoyle@cytrex.com", + "phone": "+1 (924) 569-2919" + }, + { + "_id": "55d2fc86f3affa20ab27edff", + "age": 33, + "name": "Jeannine Mosley", + "gender": "female", + "company": "ACUSAGE", + "email": "jeanninemosley@acusage.com", + "phone": "+1 (954) 517-2805" + }, + { + "_id": "55d2fc8670dd0dbdd6e4d195", + "age": 37, + "name": "Logan Brady", + "gender": "male", + "company": "TELLIFLY", + "email": "loganbrady@tellifly.com", + "phone": "+1 (861) 576-2313" + }, + { + "_id": "55d2fc86b9a15e4721982a39", + "age": 26, + "name": "Houston Joseph", + "gender": "male", + "company": "BOILICON", + "email": "houstonjoseph@boilicon.com", + "phone": "+1 (822) 519-3430" + }, + { + "_id": "55d2fc86f225999b0b8742d2", + "age": 38, + "name": "Rita Lindsey", + "gender": "female", + "company": "FIBEROX", + "email": "ritalindsey@fiberox.com", + "phone": "+1 (805) 551-3755" + }, + { + "_id": "55d2fc86e9dad38b6873b807", + "age": 22, + "name": "Strong Poole", + "gender": "male", + "company": "KINDALOO", + "email": "strongpoole@kindaloo.com", + "phone": "+1 (918) 426-2076" + }, + { + "_id": "55d2fc861608b965b2283827", + "age": 38, + "name": "Hines Mathews", + "gender": "male", + "company": "INTRADISK", + "email": "hinesmathews@intradisk.com", + "phone": "+1 (932) 420-2236" + }, + { + "_id": "55d2fc863079075f91241a16", + "age": 28, + "name": "Trina Wiley", + "gender": "female", + "company": "HATOLOGY", + "email": "trinawiley@hatology.com", + "phone": "+1 (855) 466-3287" + }, + { + "_id": "55d2fc86f2fae1a79253fb61", + "age": 23, + "name": "Kirby Tucker", + "gender": "male", + "company": "AQUAMATE", + "email": "kirbytucker@aquamate.com", + "phone": "+1 (935) 456-3272" + }, + { + "_id": "55d2fc86c42bf49f8202b2fa", + "age": 28, + "name": "Ballard Stein", + "gender": "male", + "company": "KOOGLE", + "email": "ballardstein@koogle.com", + "phone": "+1 (943) 586-2225" + }, + { + "_id": "55d2fc865db815da198c0776", + "age": 36, + "name": "Wagner Mcfarland", + "gender": "male", + "company": "ACCIDENCY", + "email": "wagnermcfarland@accidency.com", + "phone": "+1 (920) 533-2157" + }, + { + "_id": "55d2fc866aeb268fe48fd6be", + "age": 22, + "name": "Wiley Wilder", + "gender": "male", + "company": "KIDSTOCK", + "email": "wileywilder@kidstock.com", + "phone": "+1 (957) 459-3416" + }, + { + "_id": "55d2fc8606f67a423d303437", + "age": 37, + "name": "Rosario Slater", + "gender": "female", + "company": "SPRINGBEE", + "email": "rosarioslater@springbee.com", + "phone": "+1 (950) 506-3454" + }, + { + "_id": "55d2fc86510fd16a269a0201", + "age": 37, + "name": "Walker Mcdowell", + "gender": "male", + "company": "ONTAGENE", + "email": "walkermcdowell@ontagene.com", + "phone": "+1 (953) 579-3429" + }, + { + "_id": "55d2fc867d419d30f9394f56", + "age": 32, + "name": "Booth Pratt", + "gender": "male", + "company": "ZIGGLES", + "email": "boothpratt@ziggles.com", + "phone": "+1 (835) 453-3707" + }, + { + "_id": "55d2fc86e631b8f71bbe7b35", + "age": 33, + "name": "Georgia Carpenter", + "gender": "female", + "company": "STRALUM", + "email": "georgiacarpenter@stralum.com", + "phone": "+1 (923) 536-3557" + }, + { + "_id": "55d2fc866df6437afa4cfaf6", + "age": 26, + "name": "Harding Powers", + "gender": "male", + "company": "BEADZZA", + "email": "hardingpowers@beadzza.com", + "phone": "+1 (855) 467-2993" + }, + { + "_id": "55d2fc867ade492afcfc24a6", + "age": 37, + "name": "Kaye Brown", + "gender": "female", + "company": "AMTAS", + "email": "kayebrown@amtas.com", + "phone": "+1 (926) 444-3936" + }, + { + "_id": "55d2fc862bf33dd3169710ff", + "age": 24, + "name": "Mccray Padilla", + "gender": "male", + "company": "FUTURIS", + "email": "mccraypadilla@futuris.com", + "phone": "+1 (969) 561-3819" + }, + { + "_id": "55d2fc8666f74690303abf65", + "age": 35, + "name": "Moon Moss", + "gender": "male", + "company": "EURON", + "email": "moonmoss@euron.com", + "phone": "+1 (885) 514-2872" + }, + { + "_id": "55d2fc860a8b5abfdf57bd37", + "age": 26, + "name": "Lane Gregory", + "gender": "male", + "company": "SKINSERVE", + "email": "lanegregory@skinserve.com", + "phone": "+1 (818) 455-3048" + }, + { + "_id": "55d2fc86f17541fe3b770b26", + "age": 30, + "name": "Cummings Good", + "gender": "male", + "company": "GEEKOLOGY", + "email": "cummingsgood@geekology.com", + "phone": "+1 (821) 426-3476" + }, + { + "_id": "55d2fc865b6232d788278e1f", + "age": 26, + "name": "Lottie Soto", + "gender": "female", + "company": "INTERGEEK", + "email": "lottiesoto@intergeek.com", + "phone": "+1 (905) 516-2928" + }, + { + "_id": "55d2fc868388a50b97dda5c2", + "age": 38, + "name": "Bridges Bell", + "gender": "male", + "company": "MIRACULA", + "email": "bridgesbell@miracula.com", + "phone": "+1 (917) 438-3079" + }, + { + "_id": "55d2fc86cc2120d10b75c41b", + "age": 23, + "name": "Marcella Lancaster", + "gender": "female", + "company": "NAVIR", + "email": "marcellalancaster@navir.com", + "phone": "+1 (851) 478-2535" + }, + { + "_id": "55d2fc86f52bd008c87c6993", + "age": 32, + "name": "Foley Yang", + "gender": "male", + "company": "APEXTRI", + "email": "foleyyang@apextri.com", + "phone": "+1 (978) 504-2003" + }, + { + "_id": "55d2fc86088b65117b293eef", + "age": 21, + "name": "Debora Levine", + "gender": "female", + "company": "VANTAGE", + "email": "deboralevine@vantage.com", + "phone": "+1 (820) 472-2507" + }, + { + "_id": "55d2fc86765d079d8584c281", + "age": 30, + "name": "Jill Durham", + "gender": "female", + "company": "FUTURITY", + "email": "jilldurham@futurity.com", + "phone": "+1 (996) 499-2910" + }, + { + "_id": "55d2fc860ed183243d043f79", + "age": 28, + "name": "Della Sherman", + "gender": "female", + "company": "EXTRO", + "email": "dellasherman@extro.com", + "phone": "+1 (893) 541-2867" + }, + { + "_id": "55d2fc8646733a05fa448c6e", + "age": 30, + "name": "Tamara Albert", + "gender": "female", + "company": "ECOLIGHT", + "email": "tamaraalbert@ecolight.com", + "phone": "+1 (870) 514-2615" + }, + { + "_id": "55d2fc86b8bf0a0f7ffb702e", + "age": 39, + "name": "Lynn Green", + "gender": "male", + "company": "SNIPS", + "email": "lynngreen@snips.com", + "phone": "+1 (938) 464-2073" + }, + { + "_id": "55d2fc863e577905fc3ea8e7", + "age": 29, + "name": "Barbra Tate", + "gender": "female", + "company": "ACRUEX", + "email": "barbratate@acruex.com", + "phone": "+1 (809) 418-2604" + }, + { + "_id": "55d2fc86335b53151fc242b5", + "age": 33, + "name": "Potts Dickerson", + "gender": "male", + "company": "SHADEASE", + "email": "pottsdickerson@shadease.com", + "phone": "+1 (967) 539-3330" + }, + { + "_id": "55d2fc86716df5cb28925d59", + "age": 36, + "name": "Nancy Woodard", + "gender": "female", + "company": "ZOSIS", + "email": "nancywoodard@zosis.com", + "phone": "+1 (811) 434-3223" + }, + { + "_id": "55d2fc86058113d9a4909796", + "age": 29, + "name": "Park Evans", + "gender": "male", + "company": "XUMONK", + "email": "parkevans@xumonk.com", + "phone": "+1 (836) 443-2361" + }, + { + "_id": "55d2fc8659b6d92fb2880f83", + "age": 25, + "name": "Nicole Sullivan", + "gender": "female", + "company": "QUALITEX", + "email": "nicolesullivan@qualitex.com", + "phone": "+1 (823) 584-2994" + }, + { + "_id": "55d2fc86510772cfe78617a9", + "age": 33, + "name": "Bowers Barnett", + "gender": "male", + "company": "HOUSEDOWN", + "email": "bowersbarnett@housedown.com", + "phone": "+1 (872) 466-3548" + }, + { + "_id": "55d2fc868dec3ac619cfc262", + "age": 21, + "name": "Jeri Nielsen", + "gender": "female", + "company": "MOBILDATA", + "email": "jerinielsen@mobildata.com", + "phone": "+1 (886) 581-2045" + }, + { + "_id": "55d2fc863e89d05123e90aaf", + "age": 26, + "name": "Delores Farmer", + "gender": "female", + "company": "XERONK", + "email": "deloresfarmer@xeronk.com", + "phone": "+1 (872) 556-2716" + }, + { + "_id": "55d2fc8618c10cef39c48f97", + "age": 38, + "name": "Mathis Walsh", + "gender": "male", + "company": "VURBO", + "email": "mathiswalsh@vurbo.com", + "phone": "+1 (837) 459-2909" + }, + { + "_id": "55d2fc868b3487cb71c8bac4", + "age": 20, + "name": "Ingrid Shelton", + "gender": "female", + "company": "ORBIN", + "email": "ingridshelton@orbin.com", + "phone": "+1 (914) 592-2364" + }, + { + "_id": "55d2fc86bcf5c885edacef50", + "age": 36, + "name": "Socorro Burns", + "gender": "female", + "company": "NETAGY", + "email": "socorroburns@netagy.com", + "phone": "+1 (931) 523-3116" + }, + { + "_id": "55d2fc8693f704f5c95c48a7", + "age": 39, + "name": "Jo Ware", + "gender": "female", + "company": "FLYBOYZ", + "email": "joware@flyboyz.com", + "phone": "+1 (844) 467-2192" + }, + { + "_id": "55d2fc86e87485075df33029", + "age": 38, + "name": "Emilia Flores", + "gender": "female", + "company": "ZAGGLES", + "email": "emiliaflores@zaggles.com", + "phone": "+1 (992) 408-2629" + }, + { + "_id": "55d2fc861ab925ca0b9b15c6", + "age": 31, + "name": "Burks Haney", + "gender": "male", + "company": "ZYPLE", + "email": "burkshaney@zyple.com", + "phone": "+1 (882) 401-2811" + }, + { + "_id": "55d2fc86e9c252588455b811", + "age": 31, + "name": "Holly Snow", + "gender": "female", + "company": "FURNAFIX", + "email": "hollysnow@furnafix.com", + "phone": "+1 (802) 592-2798" + }, + { + "_id": "55d2fc86e07c753096021423", + "age": 25, + "name": "Frances Hayden", + "gender": "female", + "company": "SNORUS", + "email": "franceshayden@snorus.com", + "phone": "+1 (818) 481-2431" + }, + { + "_id": "55d2fc86496884876065c346", + "age": 28, + "name": "Lila Lewis", + "gender": "female", + "company": "ZOMBOID", + "email": "lilalewis@zomboid.com", + "phone": "+1 (893) 593-2423" + }, + { + "_id": "55d2fc865c6ea080a4d0a5b5", + "age": 34, + "name": "Torres Finley", + "gender": "male", + "company": "MAXIMIND", + "email": "torresfinley@maximind.com", + "phone": "+1 (888) 504-3674" + }, + { + "_id": "55d2fc86391c44c545fe989f", + "age": 23, + "name": "Horne James", + "gender": "male", + "company": "PETICULAR", + "email": "hornejames@peticular.com", + "phone": "+1 (865) 558-2517" + }, + { + "_id": "55d2fc86c8c2529939eada03", + "age": 28, + "name": "Jennings Wallace", + "gender": "male", + "company": "CAXT", + "email": "jenningswallace@caxt.com", + "phone": "+1 (833) 599-3895" + }, + { + "_id": "55d2fc86ee656fe865fbde29", + "age": 33, + "name": "Lily Gilmore", + "gender": "female", + "company": "RADIANTIX", + "email": "lilygilmore@radiantix.com", + "phone": "+1 (854) 561-3148" + }, + { + "_id": "55d2fc868455126d596ab537", + "age": 35, + "name": "Kristy Delacruz", + "gender": "female", + "company": "EQUICOM", + "email": "kristydelacruz@equicom.com", + "phone": "+1 (871) 502-3732" + }, + { + "_id": "55d2fc863096983ef370ca49", + "age": 31, + "name": "England Vance", + "gender": "male", + "company": "VOLAX", + "email": "englandvance@volax.com", + "phone": "+1 (840) 593-3417" + }, + { + "_id": "55d2fc8671f3c1e30bee852f", + "age": 23, + "name": "Rodriguez Foreman", + "gender": "male", + "company": "EXTRAWEAR", + "email": "rodriguezforeman@extrawear.com", + "phone": "+1 (804) 497-2101" + }, + { + "_id": "55d2fc86bea38e2b0cd970cb", + "age": 35, + "name": "Richard Garrett", + "gender": "male", + "company": "SUPREMIA", + "email": "richardgarrett@supremia.com", + "phone": "+1 (925) 461-3414" + }, + { + "_id": "55d2fc862c5193ab7b4668b7", + "age": 40, + "name": "Connie Ortega", + "gender": "female", + "company": "ZILODYNE", + "email": "connieortega@zilodyne.com", + "phone": "+1 (838) 582-3241" + }, + { + "_id": "55d2fc865a6b25d6bc09180e", + "age": 24, + "name": "Solomon Bates", + "gender": "male", + "company": "EXOSPACE", + "email": "solomonbates@exospace.com", + "phone": "+1 (897) 496-2243" + }, + { + "_id": "55d2fc86a16a4e077136e4b5", + "age": 32, + "name": "Valencia Andrews", + "gender": "male", + "company": "NORSUP", + "email": "valenciaandrews@norsup.com", + "phone": "+1 (891) 503-3593" + }, + { + "_id": "55d2fc86bc5f1bf697a90465", + "age": 24, + "name": "Briggs Vasquez", + "gender": "male", + "company": "ZENTIA", + "email": "briggsvasquez@zentia.com", + "phone": "+1 (802) 415-3377" + }, + { + "_id": "55d2fc86c088d6466d83f8fc", + "age": 26, + "name": "Hester Rice", + "gender": "female", + "company": "CYTREK", + "email": "hesterrice@cytrek.com", + "phone": "+1 (855) 544-3905" + }, + { + "_id": "55d2fc8622fc3e78977288af", + "age": 25, + "name": "Shelly Hendrix", + "gender": "female", + "company": "POWERNET", + "email": "shellyhendrix@powernet.com", + "phone": "+1 (912) 431-2318" + }, + { + "_id": "55d2fc862cdec6cd321df6e0", + "age": 22, + "name": "Alison Newman", + "gender": "female", + "company": "COMTOUR", + "email": "alisonnewman@comtour.com", + "phone": "+1 (836) 582-3513" + }, + { + "_id": "55d2fc86e0070d54d4712ca4", + "age": 30, + "name": "French Rivera", + "gender": "male", + "company": "ACUMENTOR", + "email": "frenchrivera@acumentor.com", + "phone": "+1 (902) 579-2193" + }, + { + "_id": "55d2fc860f64b4423a9d6ffc", + "age": 38, + "name": "Terrell Mendez", + "gender": "male", + "company": "IMAGINART", + "email": "terrellmendez@imaginart.com", + "phone": "+1 (967) 494-2713" + }, + { + "_id": "55d2fc86548793116b225b3b", + "age": 30, + "name": "Parsons Robertson", + "gender": "male", + "company": "OBONES", + "email": "parsonsrobertson@obones.com", + "phone": "+1 (984) 434-2810" + }, + { + "_id": "55d2fc865b8b0d6a59db876c", + "age": 33, + "name": "Livingston Barry", + "gender": "male", + "company": "TYPHONICA", + "email": "livingstonbarry@typhonica.com", + "phone": "+1 (976) 560-3878" + }, + { + "_id": "55d2fc865ce23fb8b34cae53", + "age": 40, + "name": "Valeria Stout", + "gender": "female", + "company": "AVENETRO", + "email": "valeriastout@avenetro.com", + "phone": "+1 (885) 557-3624" + }, + { + "_id": "55d2fc86c03ea1d6e81563ac", + "age": 39, + "name": "Grimes Dyer", + "gender": "male", + "company": "GEOLOGIX", + "email": "grimesdyer@geologix.com", + "phone": "+1 (896) 533-2919" + }, + { + "_id": "55d2fc8655c0acb356a06c8f", + "age": 29, + "name": "Higgins Short", + "gender": "male", + "company": "BICOL", + "email": "higginsshort@bicol.com", + "phone": "+1 (976) 444-3073" + }, + { + "_id": "55d2fc865b6db005487c52bb", + "age": 34, + "name": "Gilmore Campos", + "gender": "male", + "company": "PASTURIA", + "email": "gilmorecampos@pasturia.com", + "phone": "+1 (862) 442-2147" + }, + { + "_id": "55d2fc863df4791bcb269217", + "age": 29, + "name": "Sloan Kane", + "gender": "male", + "company": "XELEGYL", + "email": "sloankane@xelegyl.com", + "phone": "+1 (946) 526-2275" + }, + { + "_id": "55d2fc86b2eb7dedbd5a9e8d", + "age": 26, + "name": "Mcpherson Thornton", + "gender": "male", + "company": "KAGE", + "email": "mcphersonthornton@kage.com", + "phone": "+1 (803) 478-2690" + }, + { + "_id": "55d2fc8603f6a8c17148c8dd", + "age": 31, + "name": "Christi Welch", + "gender": "female", + "company": "WARETEL", + "email": "christiwelch@waretel.com", + "phone": "+1 (999) 552-3114" + }, + { + "_id": "55d2fc86118d83cb9d06aa2e", + "age": 29, + "name": "Padilla Travis", + "gender": "male", + "company": "ENERVATE", + "email": "padillatravis@enervate.com", + "phone": "+1 (897) 577-3387" + }, + { + "_id": "55d2fc86aba06801708bed65", + "age": 22, + "name": "Stanton Casey", + "gender": "male", + "company": "BUZZMAKER", + "email": "stantoncasey@buzzmaker.com", + "phone": "+1 (858) 571-2667" + }, + { + "_id": "55d2fc86184810b00043a4b7", + "age": 29, + "name": "Krista Hernandez", + "gender": "female", + "company": "BIOHAB", + "email": "kristahernandez@biohab.com", + "phone": "+1 (832) 510-3654" + }, + { + "_id": "55d2fc86e9c951b5bcea3938", + "age": 36, + "name": "Deleon Oliver", + "gender": "male", + "company": "NETBOOK", + "email": "deleonoliver@netbook.com", + "phone": "+1 (934) 504-2964" + }, + { + "_id": "55d2fc86923000f3ea91ae38", + "age": 36, + "name": "Vasquez Fowler", + "gender": "male", + "company": "ORGANICA", + "email": "vasquezfowler@organica.com", + "phone": "+1 (949) 546-2722" + }, + { + "_id": "55d2fc861e12cd0fa6207a9e", + "age": 33, + "name": "Rutledge Keith", + "gender": "male", + "company": "COLAIRE", + "email": "rutledgekeith@colaire.com", + "phone": "+1 (936) 472-3739" + }, + { + "_id": "55d2fc86927eca39ef0c7ae9", + "age": 26, + "name": "Kirsten Valenzuela", + "gender": "female", + "company": "SEQUITUR", + "email": "kirstenvalenzuela@sequitur.com", + "phone": "+1 (958) 564-3259" + }, + { + "_id": "55d2fc869e922239ce293d2c", + "age": 40, + "name": "Garza Gutierrez", + "gender": "male", + "company": "SPLINX", + "email": "garzagutierrez@splinx.com", + "phone": "+1 (850) 525-3114" + }, + { + "_id": "55d2fc869dd88389d4785283", + "age": 27, + "name": "Shawna Peck", + "gender": "female", + "company": "UNQ", + "email": "shawnapeck@unq.com", + "phone": "+1 (961) 579-3704" + }, + { + "_id": "55d2fc86722d8e2a714bf7f2", + "age": 23, + "name": "Aurelia Mcpherson", + "gender": "female", + "company": "BOINK", + "email": "aureliamcpherson@boink.com", + "phone": "+1 (946) 479-2080" + }, + { + "_id": "55d2fc862324d173c26dfc68", + "age": 39, + "name": "Maryellen Daugherty", + "gender": "female", + "company": "ZILCH", + "email": "maryellendaugherty@zilch.com", + "phone": "+1 (817) 577-3290" + }, + { + "_id": "55d2fc86d311107f869da748", + "age": 35, + "name": "Zelma Hancock", + "gender": "female", + "company": "EVENTAGE", + "email": "zelmahancock@eventage.com", + "phone": "+1 (845) 578-3887" + }, + { + "_id": "55d2fc86afb4ede1f20a6d15", + "age": 34, + "name": "Tessa Adkins", + "gender": "female", + "company": "CONJURICA", + "email": "tessaadkins@conjurica.com", + "phone": "+1 (807) 497-2845" + }, + { + "_id": "55d2fc8648a2e36d6f97fad1", + "age": 22, + "name": "Wong Shaffer", + "gender": "male", + "company": "ACCUFARM", + "email": "wongshaffer@accufarm.com", + "phone": "+1 (865) 589-3833" + }, + { + "_id": "55d2fc86c4c10d58f1fe357f", + "age": 32, + "name": "Ivy Suarez", + "gender": "female", + "company": "UNCORP", + "email": "ivysuarez@uncorp.com", + "phone": "+1 (851) 582-2829" + }, + { + "_id": "55d2fc867c11d1885ca4d8f9", + "age": 25, + "name": "Sosa Barber", + "gender": "male", + "company": "FUELTON", + "email": "sosabarber@fuelton.com", + "phone": "+1 (831) 404-2343" + }, + { + "_id": "55d2fc866405b373cff4d477", + "age": 23, + "name": "Rosalind Craft", + "gender": "female", + "company": "OTHERSIDE", + "email": "rosalindcraft@otherside.com", + "phone": "+1 (847) 455-2079" + }, + { + "_id": "55d2fc863a038b06c712b4b9", + "age": 30, + "name": "Bean Mathis", + "gender": "male", + "company": "QUILK", + "email": "beanmathis@quilk.com", + "phone": "+1 (974) 596-2868" + }, + { + "_id": "55d2fc86922e4a50dfef56ac", + "age": 21, + "name": "Kelley Ruiz", + "gender": "female", + "company": "APEX", + "email": "kelleyruiz@apex.com", + "phone": "+1 (889) 522-2938" + }, + { + "_id": "55d2fc868c7bf8c05a228366", + "age": 22, + "name": "Georgette Chaney", + "gender": "female", + "company": "EXOTECHNO", + "email": "georgettechaney@exotechno.com", + "phone": "+1 (920) 591-3934" + }, + { + "_id": "55d2fc86988c5be0c8ba5412", + "age": 38, + "name": "Tami Bullock", + "gender": "female", + "company": "ISOTRONIC", + "email": "tamibullock@isotronic.com", + "phone": "+1 (828) 567-2857" + }, + { + "_id": "55d2fc862264a1d2de2e8a6b", + "age": 39, + "name": "Castillo Rosario", + "gender": "male", + "company": "KONNECT", + "email": "castillorosario@konnect.com", + "phone": "+1 (938) 402-3484" + }, + { + "_id": "55d2fc8622ee680ff3c522e1", + "age": 31, + "name": "George Weber", + "gender": "male", + "company": "FARMAGE", + "email": "georgeweber@farmage.com", + "phone": "+1 (895) 502-2654" + }, + { + "_id": "55d2fc86a0d45d2916aacb5a", + "age": 28, + "name": "Wheeler Villarreal", + "gender": "male", + "company": "IMPERIUM", + "email": "wheelervillarreal@imperium.com", + "phone": "+1 (889) 507-3796" + }, + { + "_id": "55d2fc866c6e8e85c17c61ca", + "age": 36, + "name": "Arlene Bean", + "gender": "female", + "company": "UNIA", + "email": "arlenebean@unia.com", + "phone": "+1 (970) 463-2147" + }, + { + "_id": "55d2fc86c3c95fd98562a429", + "age": 30, + "name": "Oneil Madden", + "gender": "male", + "company": "COMBOGENE", + "email": "oneilmadden@combogene.com", + "phone": "+1 (849) 507-3555" + }, + { + "_id": "55d2fc86b0f7cc31af45078a", + "age": 35, + "name": "Vaughn Merritt", + "gender": "male", + "company": "ACCUPHARM", + "email": "vaughnmerritt@accupharm.com", + "phone": "+1 (886) 428-2966" + }, + { + "_id": "55d2fc86ad825cb66f2a2feb", + "age": 21, + "name": "Duran Bradford", + "gender": "male", + "company": "SQUISH", + "email": "duranbradford@squish.com", + "phone": "+1 (930) 434-2976" + }, + { + "_id": "55d2fc86efa80c332066194d", + "age": 30, + "name": "Tanisha Knox", + "gender": "female", + "company": "FARMEX", + "email": "tanishaknox@farmex.com", + "phone": "+1 (924) 540-2066" + }, + { + "_id": "55d2fc861dbd55c1fd4bdf23", + "age": 38, + "name": "Esther Foster", + "gender": "female", + "company": "SENSATE", + "email": "estherfoster@sensate.com", + "phone": "+1 (812) 417-2687" + }, + { + "_id": "55d2fc86115b9a01067db6ad", + "age": 35, + "name": "Marion Gray", + "gender": "female", + "company": "HINWAY", + "email": "mariongray@hinway.com", + "phone": "+1 (850) 526-2167" + }, + { + "_id": "55d2fc864b40086c2e4963e2", + "age": 38, + "name": "Ava Flowers", + "gender": "female", + "company": "REVERSUS", + "email": "avaflowers@reversus.com", + "phone": "+1 (989) 415-2504" + }, + { + "_id": "55d2fc8686c59f7395222b11", + "age": 35, + "name": "Katina Burnett", + "gender": "female", + "company": "DUFLEX", + "email": "katinaburnett@duflex.com", + "phone": "+1 (843) 464-3718" + }, + { + "_id": "55d2fc86e012c977bc5b57d6", + "age": 37, + "name": "Ester Cooley", + "gender": "female", + "company": "RUBADUB", + "email": "estercooley@rubadub.com", + "phone": "+1 (856) 407-3009" + }, + { + "_id": "55d2fc865688875c75a158b5", + "age": 36, + "name": "Dennis Mccray", + "gender": "male", + "company": "PETIGEMS", + "email": "dennismccray@petigems.com", + "phone": "+1 (989) 525-3768" + }, + { + "_id": "55d2fc86ca333a1c715a35c7", + "age": 25, + "name": "Mitzi Carson", + "gender": "female", + "company": "KENEGY", + "email": "mitzicarson@kenegy.com", + "phone": "+1 (819) 450-2923" + }, + { + "_id": "55d2fc86a8eb68257312e735", + "age": 33, + "name": "Guthrie Tyson", + "gender": "male", + "company": "GLUID", + "email": "guthrietyson@gluid.com", + "phone": "+1 (878) 496-3831" + }, + { + "_id": "55d2fc865d4f3b3777fc1573", + "age": 38, + "name": "Sellers Hodges", + "gender": "male", + "company": "BALOOBA", + "email": "sellershodges@balooba.com", + "phone": "+1 (895) 557-2331" + }, + { + "_id": "55d2fc860a91cf55298e2a24", + "age": 32, + "name": "Hawkins Hardin", + "gender": "male", + "company": "ZILLANET", + "email": "hawkinshardin@zillanet.com", + "phone": "+1 (852) 511-2796" + }, + { + "_id": "55d2fc867b1c618fcb9cb2c3", + "age": 26, + "name": "Bowman Buck", + "gender": "male", + "company": "APPLIDEC", + "email": "bowmanbuck@applidec.com", + "phone": "+1 (995) 500-2863" + }, + { + "_id": "55d2fc8666610d156551484b", + "age": 23, + "name": "Mcgee Delgado", + "gender": "male", + "company": "MANTRO", + "email": "mcgeedelgado@mantro.com", + "phone": "+1 (917) 490-2295" + }, + { + "_id": "55d2fc8685385c63f5a509b3", + "age": 24, + "name": "Petty Pena", + "gender": "male", + "company": "EXOSPEED", + "email": "pettypena@exospeed.com", + "phone": "+1 (929) 470-2022" + }, + { + "_id": "55d2fc864296df53bb778e52", + "age": 38, + "name": "Ray Mclaughlin", + "gender": "male", + "company": "PYRAMAX", + "email": "raymclaughlin@pyramax.com", + "phone": "+1 (935) 453-3720" + }, + { + "_id": "55d2fc86b157acc34692412b", + "age": 22, + "name": "Hopkins Wells", + "gender": "male", + "company": "NORALEX", + "email": "hopkinswells@noralex.com", + "phone": "+1 (986) 421-2293" + }, + { + "_id": "55d2fc861febd65bb3c91219", + "age": 38, + "name": "Patsy Strickland", + "gender": "female", + "company": "POLARIA", + "email": "patsystrickland@polaria.com", + "phone": "+1 (885) 408-2213" + }, + { + "_id": "55d2fc8693fbc24aa2bcc5a8", + "age": 31, + "name": "Wolf Delaney", + "gender": "male", + "company": "EXERTA", + "email": "wolfdelaney@exerta.com", + "phone": "+1 (969) 537-3201" + }, + { + "_id": "55d2fc86b923e0543d39fed4", + "age": 30, + "name": "Fulton Hewitt", + "gender": "male", + "company": "TWIGGERY", + "email": "fultonhewitt@twiggery.com", + "phone": "+1 (894) 483-2549" + }, + { + "_id": "55d2fc8672aff3d2369b2749", + "age": 40, + "name": "Nona Meadows", + "gender": "female", + "company": "ULTRIMAX", + "email": "nonameadows@ultrimax.com", + "phone": "+1 (997) 459-2012" + }, + { + "_id": "55d2fc86a3b6922e61cdcd72", + "age": 24, + "name": "Irwin Russo", + "gender": "male", + "company": "QUINTITY", + "email": "irwinrusso@quintity.com", + "phone": "+1 (985) 597-3841" + }, + { + "_id": "55d2fc86c28f4a90a41581b7", + "age": 34, + "name": "Mara Bowman", + "gender": "female", + "company": "ATOMICA", + "email": "marabowman@atomica.com", + "phone": "+1 (927) 578-2958" + }, + { + "_id": "55d2fc86ef827e1bbb5b3ceb", + "age": 40, + "name": "Leigh Schroeder", + "gender": "female", + "company": "ZIORE", + "email": "leighschroeder@ziore.com", + "phone": "+1 (963) 484-2519" + }, + { + "_id": "55d2fc86921329e8e044472a", + "age": 27, + "name": "Sweeney Riddle", + "gender": "male", + "company": "ELITA", + "email": "sweeneyriddle@elita.com", + "phone": "+1 (974) 536-2132" + }, + { + "_id": "55d2fc864b6067f7d828f1ba", + "age": 23, + "name": "Bell Kline", + "gender": "male", + "company": "ORBOID", + "email": "bellkline@orboid.com", + "phone": "+1 (827) 461-3466" + }, + { + "_id": "55d2fc86a541995fa67027ae", + "age": 20, + "name": "Morgan Aguirre", + "gender": "female", + "company": "AEORA", + "email": "morganaguirre@aeora.com", + "phone": "+1 (987) 494-2357" + }, + { + "_id": "55d2fc86af8d98e486bda0f5", + "age": 24, + "name": "Morrison Mcbride", + "gender": "male", + "company": "TECHMANIA", + "email": "morrisonmcbride@techmania.com", + "phone": "+1 (994) 470-2394" + }, + { + "_id": "55d2fc86371a2691da434cdd", + "age": 22, + "name": "Miles Salinas", + "gender": "male", + "company": "RODEOLOGY", + "email": "milessalinas@rodeology.com", + "phone": "+1 (898) 461-3008" + }, + { + "_id": "55d2fc865196aa926884d957", + "age": 36, + "name": "Lang Riggs", + "gender": "male", + "company": "PHOTOBIN", + "email": "langriggs@photobin.com", + "phone": "+1 (849) 503-2335" + }, + { + "_id": "55d2fc86b4e30cd686840e28", + "age": 30, + "name": "Kathy Phelps", + "gender": "female", + "company": "INTRAWEAR", + "email": "kathyphelps@intrawear.com", + "phone": "+1 (992) 499-2474" + }, + { + "_id": "55d2fc86f09accb089f91415", + "age": 24, + "name": "Moss Jimenez", + "gender": "male", + "company": "PROFLEX", + "email": "mossjimenez@proflex.com", + "phone": "+1 (842) 546-3491" + }, + { + "_id": "55d2fc86e3ba881a25584928", + "age": 20, + "name": "Moody Sexton", + "gender": "male", + "company": "CENTREGY", + "email": "moodysexton@centregy.com", + "phone": "+1 (856) 581-3293" + }, + { + "_id": "55d2fc861c85cf26c6d21a64", + "age": 31, + "name": "Nannie Price", + "gender": "female", + "company": "GEOSTELE", + "email": "nannieprice@geostele.com", + "phone": "+1 (936) 447-3486" + }, + { + "_id": "55d2fc86f1e23452254fda91", + "age": 32, + "name": "Summer Johnston", + "gender": "female", + "company": "EXTRAGENE", + "email": "summerjohnston@extragene.com", + "phone": "+1 (808) 508-2748" + }, + { + "_id": "55d2fc86940fcc0f17d5f213", + "age": 24, + "name": "Genevieve Lynch", + "gender": "female", + "company": "COREPAN", + "email": "genevievelynch@corepan.com", + "phone": "+1 (921) 532-2893" + }, + { + "_id": "55d2fc86cdf6056cd058466c", + "age": 23, + "name": "Stuart Oconnor", + "gender": "male", + "company": "PUSHCART", + "email": "stuartoconnor@pushcart.com", + "phone": "+1 (925) 515-3434" + }, + { + "_id": "55d2fc863bca896e3e2ac1a7", + "age": 21, + "name": "Adela Nieves", + "gender": "female", + "company": "MEDICROIX", + "email": "adelanieves@medicroix.com", + "phone": "+1 (910) 568-3916" + }, + { + "_id": "55d2fc86826d7c7fcfc2562e", + "age": 28, + "name": "Eaton Mcintosh", + "gender": "male", + "company": "MEGALL", + "email": "eatonmcintosh@megall.com", + "phone": "+1 (806) 440-2196" + }, + { + "_id": "55d2fc863d76c0458de8afb2", + "age": 35, + "name": "Sharron Hood", + "gender": "female", + "company": "UTARIAN", + "email": "sharronhood@utarian.com", + "phone": "+1 (824) 477-3364" + }, + { + "_id": "55d2fc86c0f317fe0cdd6f66", + "age": 24, + "name": "Allison Osborn", + "gender": "female", + "company": "TUBALUM", + "email": "allisonosborn@tubalum.com", + "phone": "+1 (913) 546-3966" + }, + { + "_id": "55d2fc86e1521a5a9896e0fa", + "age": 38, + "name": "Chelsea Jarvis", + "gender": "female", + "company": "SOPRANO", + "email": "chelseajarvis@soprano.com", + "phone": "+1 (834) 417-2904" + }, + { + "_id": "55d2fc86a5f4df72a31c9201", + "age": 22, + "name": "Finley Freeman", + "gender": "male", + "company": "COMBOT", + "email": "finleyfreeman@combot.com", + "phone": "+1 (886) 448-2820" + }, + { + "_id": "55d2fc86c4c2c783570c50eb", + "age": 31, + "name": "Lynn Miles", + "gender": "female", + "company": "ZENTIX", + "email": "lynnmiles@zentix.com", + "phone": "+1 (891) 450-2505" + }, + { + "_id": "55d2fc863e649b4a99367f40", + "age": 39, + "name": "Montoya Greene", + "gender": "male", + "company": "MEMORA", + "email": "montoyagreene@memora.com", + "phone": "+1 (809) 402-3541" + }, + { + "_id": "55d2fc86b82e96b2275df9f3", + "age": 28, + "name": "Castro Huff", + "gender": "male", + "company": "BLUPLANET", + "email": "castrohuff@bluplanet.com", + "phone": "+1 (966) 554-2469" + }, + { + "_id": "55d2fc86f4a8a7700a99e31c", + "age": 23, + "name": "Guadalupe Harmon", + "gender": "female", + "company": "ISOLOGIA", + "email": "guadalupeharmon@isologia.com", + "phone": "+1 (937) 497-2022" + }, + { + "_id": "55d2fc86551e7044f31f2520", + "age": 25, + "name": "Heidi Navarro", + "gender": "female", + "company": "POLARIUM", + "email": "heidinavarro@polarium.com", + "phone": "+1 (830) 493-3328" + }, + { + "_id": "55d2fc861ee43e4303351a4b", + "age": 25, + "name": "Fry Webster", + "gender": "male", + "company": "RENOVIZE", + "email": "frywebster@renovize.com", + "phone": "+1 (960) 600-3488" + }, + { + "_id": "55d2fc86e0d6778a1c6d7195", + "age": 35, + "name": "Candice Sharpe", + "gender": "female", + "company": "UNDERTAP", + "email": "candicesharpe@undertap.com", + "phone": "+1 (989) 436-2856" + }, + { + "_id": "55d2fc86267005541d225276", + "age": 25, + "name": "Vonda Hansen", + "gender": "female", + "company": "PROVIDCO", + "email": "vondahansen@providco.com", + "phone": "+1 (883) 484-2047" + }, + { + "_id": "55d2fc8605000bc9329d28e0", + "age": 21, + "name": "Lara Dominguez", + "gender": "male", + "company": "VIXO", + "email": "laradominguez@vixo.com", + "phone": "+1 (998) 440-3632" + }, + { + "_id": "55d2fc868b364833d935b192", + "age": 36, + "name": "Jillian Gibbs", + "gender": "female", + "company": "CUIZINE", + "email": "jilliangibbs@cuizine.com", + "phone": "+1 (996) 447-3083" + }, + { + "_id": "55d2fc863690a0784d2e8bc1", + "age": 35, + "name": "Frankie Cervantes", + "gender": "female", + "company": "ISODRIVE", + "email": "frankiecervantes@isodrive.com", + "phone": "+1 (895) 533-2371" + }, + { + "_id": "55d2fc86efb91d645101592f", + "age": 36, + "name": "Nieves Walton", + "gender": "male", + "company": "ZILPHUR", + "email": "nieveswalton@zilphur.com", + "phone": "+1 (866) 412-2377" + }, + { + "_id": "55d2fc86cba2fb0dfd9eb8c3", + "age": 38, + "name": "Celina Orr", + "gender": "female", + "company": "COMVEX", + "email": "celinaorr@comvex.com", + "phone": "+1 (960) 433-2380" + }, + { + "_id": "55d2fc8650eaf118ae048bce", + "age": 33, + "name": "Moreno Conway", + "gender": "male", + "company": "VIOCULAR", + "email": "morenoconway@viocular.com", + "phone": "+1 (808) 535-2624" + }, + { + "_id": "55d2fc860e41c77df0ea1151", + "age": 33, + "name": "Wilkerson Dodson", + "gender": "male", + "company": "COMTRAK", + "email": "wilkersondodson@comtrak.com", + "phone": "+1 (932) 427-2400" + }, + { + "_id": "55d2fc86175167613833c577", + "age": 21, + "name": "Crane Lloyd", + "gender": "male", + "company": "ARCHITAX", + "email": "cranelloyd@architax.com", + "phone": "+1 (984) 467-3498" + }, + { + "_id": "55d2fc863aa593013e09d04b", + "age": 25, + "name": "Marguerite Dorsey", + "gender": "female", + "company": "UNI", + "email": "margueritedorsey@uni.com", + "phone": "+1 (989) 558-2105" + }, + { + "_id": "55d2fc86248902726f0d1c53", + "age": 35, + "name": "Dillon William", + "gender": "male", + "company": "ROOFORIA", + "email": "dillonwilliam@rooforia.com", + "phone": "+1 (879) 600-3589" + }, + { + "_id": "55d2fc86caa1b7374cd83a72", + "age": 32, + "name": "Small Floyd", + "gender": "male", + "company": "ANACHO", + "email": "smallfloyd@anacho.com", + "phone": "+1 (906) 463-2357" + }, + { + "_id": "55d2fc86c086267c74616688", + "age": 32, + "name": "Dominique Horn", + "gender": "female", + "company": "ENTOGROK", + "email": "dominiquehorn@entogrok.com", + "phone": "+1 (871) 556-2943" + }, + { + "_id": "55d2fc86d3f7731c49d48fa7", + "age": 35, + "name": "Haley Shannon", + "gender": "female", + "company": "UNEEQ", + "email": "haleyshannon@uneeq.com", + "phone": "+1 (984) 471-3688" + }, + { + "_id": "55d2fc8678e81807871b2b13", + "age": 24, + "name": "Flowers Richards", + "gender": "male", + "company": "QUILITY", + "email": "flowersrichards@quility.com", + "phone": "+1 (956) 548-3667" + }, + { + "_id": "55d2fc86b4b462a2743c10d3", + "age": 33, + "name": "Hopper Rush", + "gender": "male", + "company": "VERBUS", + "email": "hopperrush@verbus.com", + "phone": "+1 (866) 578-2948" + }, + { + "_id": "55d2fc86d660e6f5bf9c1ce1", + "age": 31, + "name": "Randall Kelley", + "gender": "male", + "company": "INSURON", + "email": "randallkelley@insuron.com", + "phone": "+1 (922) 520-3464" + }, + { + "_id": "55d2fc86e70ad03e58c3e01f", + "age": 36, + "name": "Owens Drake", + "gender": "male", + "company": "FROLIX", + "email": "owensdrake@frolix.com", + "phone": "+1 (982) 516-3575" + }, + { + "_id": "55d2fc86ef78c8ad566afe4f", + "age": 28, + "name": "Irma Garrison", + "gender": "female", + "company": "EXOZENT", + "email": "irmagarrison@exozent.com", + "phone": "+1 (882) 536-2614" + }, + { + "_id": "55d2fc8675d6b4b33c5e482f", + "age": 34, + "name": "Baldwin Carver", + "gender": "male", + "company": "PHEAST", + "email": "baldwincarver@pheast.com", + "phone": "+1 (824) 521-2892" + }, + { + "_id": "55d2fc86d382b214a715305f", + "age": 23, + "name": "Short Harding", + "gender": "male", + "company": "LIQUICOM", + "email": "shortharding@liquicom.com", + "phone": "+1 (836) 578-2063" + }, + { + "_id": "55d2fc860420ee3ca95e2166", + "age": 35, + "name": "Christy Roberson", + "gender": "female", + "company": "BITREX", + "email": "christyroberson@bitrex.com", + "phone": "+1 (840) 426-2954" + }, + { + "_id": "55d2fc86dd67ce4e4f7b6d0c", + "age": 21, + "name": "Fern Knight", + "gender": "female", + "company": "ENTROPIX", + "email": "fernknight@entropix.com", + "phone": "+1 (944) 546-2456" + }, + { + "_id": "55d2fc8651510fb366709f12", + "age": 40, + "name": "Elva Taylor", + "gender": "female", + "company": "COMFIRM", + "email": "elvataylor@comfirm.com", + "phone": "+1 (834) 468-2999" + }, + { + "_id": "55d2fc869603515857919a7c", + "age": 26, + "name": "Carmen Bentley", + "gender": "female", + "company": "LIMAGE", + "email": "carmenbentley@limage.com", + "phone": "+1 (882) 425-3588" + }, + { + "_id": "55d2fc860991d6904808f8b3", + "age": 28, + "name": "Blair Dunn", + "gender": "male", + "company": "ESSENSIA", + "email": "blairdunn@essensia.com", + "phone": "+1 (896) 477-2617" + }, + { + "_id": "55d2fc862c99e4fefd17ab8d", + "age": 23, + "name": "Minerva Swanson", + "gender": "female", + "company": "EMTRAC", + "email": "minervaswanson@emtrac.com", + "phone": "+1 (905) 427-3942" + }, + { + "_id": "55d2fc86a2b4277d171e6ed6", + "age": 40, + "name": "Alicia Lawrence", + "gender": "female", + "company": "CONCILITY", + "email": "alicialawrence@concility.com", + "phone": "+1 (903) 452-2010" + }, + { + "_id": "55d2fc86f98eb74fe02a49f5", + "age": 33, + "name": "Bernice Fitzpatrick", + "gender": "female", + "company": "JASPER", + "email": "bernicefitzpatrick@jasper.com", + "phone": "+1 (987) 518-2248" + }, + { + "_id": "55d2fc86070af0b0109b6a4c", + "age": 20, + "name": "Wilder Blackburn", + "gender": "male", + "company": "ZENTILITY", + "email": "wilderblackburn@zentility.com", + "phone": "+1 (921) 548-2995" + }, + { + "_id": "55d2fc86249796ecd0b25829", + "age": 25, + "name": "Lambert Wilson", + "gender": "male", + "company": "ANIVET", + "email": "lambertwilson@anivet.com", + "phone": "+1 (920) 592-2261" + }, + { + "_id": "55d2fc863c6bdb691f62bdf4", + "age": 24, + "name": "Cook Lee", + "gender": "male", + "company": "ZILLADYNE", + "email": "cooklee@zilladyne.com", + "phone": "+1 (812) 566-2988" + }, + { + "_id": "55d2fc86cdeae66cf4fe6582", + "age": 29, + "name": "Karyn Horne", + "gender": "female", + "company": "OBLIQ", + "email": "karynhorne@obliq.com", + "phone": "+1 (909) 452-3599" + }, + { + "_id": "55d2fc86bcbabc9eb159e1e4", + "age": 23, + "name": "Rowe Stokes", + "gender": "male", + "company": "ANDERSHUN", + "email": "rowestokes@andershun.com", + "phone": "+1 (948) 531-2335" + }, + { + "_id": "55d2fc86a1b344b621d41baa", + "age": 27, + "name": "Gonzalez Merrill", + "gender": "male", + "company": "PLAYCE", + "email": "gonzalezmerrill@playce.com", + "phone": "+1 (989) 418-3057" + }, + { + "_id": "55d2fc86300074c184103b47", + "age": 33, + "name": "Kathie Preston", + "gender": "female", + "company": "LUMBREX", + "email": "kathiepreston@lumbrex.com", + "phone": "+1 (911) 586-3177" + }, + { + "_id": "55d2fc8646059e576c41ed1f", + "age": 26, + "name": "Antoinette Stevens", + "gender": "female", + "company": "APEXIA", + "email": "antoinettestevens@apexia.com", + "phone": "+1 (828) 597-3083" + }, + { + "_id": "55d2fc86b5ec5743baacc091", + "age": 22, + "name": "Schmidt Morton", + "gender": "male", + "company": "NORSUL", + "email": "schmidtmorton@norsul.com", + "phone": "+1 (821) 530-2454" + }, + { + "_id": "55d2fc86a521743243b01d2b", + "age": 37, + "name": "Joyner Wise", + "gender": "male", + "company": "MANGELICA", + "email": "joynerwise@mangelica.com", + "phone": "+1 (884) 448-3942" + }, + { + "_id": "55d2fc86d7c827b5ded4027f", + "age": 22, + "name": "Carpenter Carey", + "gender": "male", + "company": "CENTICE", + "email": "carpentercarey@centice.com", + "phone": "+1 (823) 585-2581" + }, + { + "_id": "55d2fc866866bf441f74abdf", + "age": 34, + "name": "Luz Hays", + "gender": "female", + "company": "KONGENE", + "email": "luzhays@kongene.com", + "phone": "+1 (999) 558-2282" + }, + { + "_id": "55d2fc86af95d619737eccb7", + "age": 27, + "name": "Lesley Frye", + "gender": "female", + "company": "SUREMAX", + "email": "lesleyfrye@suremax.com", + "phone": "+1 (982) 485-2811" + }, + { + "_id": "55d2fc86b41a0594552e4839", + "age": 31, + "name": "Jacqueline Ramsey", + "gender": "female", + "company": "LYRIA", + "email": "jacquelineramsey@lyria.com", + "phone": "+1 (961) 581-2500" + }, + { + "_id": "55d2fc8682aa54548863d4b1", + "age": 31, + "name": "Ina Ford", + "gender": "female", + "company": "ENERSOL", + "email": "inaford@enersol.com", + "phone": "+1 (895) 514-3441" + }, + { + "_id": "55d2fc866bc796d86ebd697b", + "age": 22, + "name": "Francisca Ashley", + "gender": "female", + "company": "SIGNIDYNE", + "email": "franciscaashley@signidyne.com", + "phone": "+1 (911) 566-2135" + }, + { + "_id": "55d2fc868ccc122c6a517033", + "age": 34, + "name": "Morton Owen", + "gender": "male", + "company": "SHOPABOUT", + "email": "mortonowen@shopabout.com", + "phone": "+1 (932) 576-3821" + }, + { + "_id": "55d2fc86751e661b56e6e3cd", + "age": 36, + "name": "Weber Manning", + "gender": "male", + "company": "CENTREE", + "email": "webermanning@centree.com", + "phone": "+1 (865) 582-3809" + }, + { + "_id": "55d2fc8620b9766f76797a13", + "age": 37, + "name": "Edwards Steele", + "gender": "male", + "company": "KATAKANA", + "email": "edwardssteele@katakana.com", + "phone": "+1 (959) 402-3657" + }, + { + "_id": "55d2fc860a13152e82b74839", + "age": 38, + "name": "Gabriela Boone", + "gender": "female", + "company": "OVOLO", + "email": "gabrielaboone@ovolo.com", + "phone": "+1 (910) 587-2744" + }, + { + "_id": "55d2fc864e9ca8a988600768", + "age": 32, + "name": "Tricia Guy", + "gender": "female", + "company": "TALKOLA", + "email": "triciaguy@talkola.com", + "phone": "+1 (960) 474-3508" + }, + { + "_id": "55d2fc86ce61b77671fd1f33", + "age": 32, + "name": "James Romero", + "gender": "female", + "company": "CANDECOR", + "email": "jamesromero@candecor.com", + "phone": "+1 (928) 478-3272" + }, + { + "_id": "55d2fc86f3cc105e20a44aa9", + "age": 25, + "name": "Casey Hammond", + "gender": "male", + "company": "PYRAMIA", + "email": "caseyhammond@pyramia.com", + "phone": "+1 (965) 539-2923" + }, + { + "_id": "55d2fc86429d859d9e54585f", + "age": 33, + "name": "Moran Wade", + "gender": "male", + "company": "BUGSALL", + "email": "moranwade@bugsall.com", + "phone": "+1 (996) 559-2965" + }, + { + "_id": "55d2fc86820b562eff651ebc", + "age": 34, + "name": "Earline Goff", + "gender": "female", + "company": "DATAGENE", + "email": "earlinegoff@datagene.com", + "phone": "+1 (976) 565-3513" + }, + { + "_id": "55d2fc865f6176e8301edcf3", + "age": 38, + "name": "Vickie Cherry", + "gender": "female", + "company": "SILODYNE", + "email": "vickiecherry@silodyne.com", + "phone": "+1 (943) 522-2438" + }, + { + "_id": "55d2fc8633cb19840995984c", + "age": 30, + "name": "Yates Avery", + "gender": "male", + "company": "DIGINETIC", + "email": "yatesavery@diginetic.com", + "phone": "+1 (813) 587-3611" + }, + { + "_id": "55d2fc8677d1a1c749d498eb", + "age": 24, + "name": "Hodges Langley", + "gender": "male", + "company": "QUOTEZART", + "email": "hodgeslangley@quotezart.com", + "phone": "+1 (857) 496-3905" + }, + { + "_id": "55d2fc86f49e5ceca6ff241d", + "age": 35, + "name": "Serrano Bartlett", + "gender": "male", + "company": "EXOSWITCH", + "email": "serranobartlett@exoswitch.com", + "phone": "+1 (809) 421-3677" + }, + { + "_id": "55d2fc86cd4921dcf0316691", + "age": 28, + "name": "Faye Wood", + "gender": "female", + "company": "EXOTERIC", + "email": "fayewood@exoteric.com", + "phone": "+1 (840) 417-2329" + }, + { + "_id": "55d2fc8697911f7ca9adfe37", + "age": 38, + "name": "Jamie Jennings", + "gender": "female", + "company": "SURETECH", + "email": "jamiejennings@suretech.com", + "phone": "+1 (945) 599-3768" + }, + { + "_id": "55d2fc8614e0225c06b40348", + "age": 34, + "name": "Levy Sellers", + "gender": "male", + "company": "STELAECOR", + "email": "levysellers@stelaecor.com", + "phone": "+1 (805) 519-2578" + }, + { + "_id": "55d2fc8635986d3994af8adc", + "age": 36, + "name": "Kelsey Montgomery", + "gender": "female", + "company": "IMANT", + "email": "kelseymontgomery@imant.com", + "phone": "+1 (894) 555-3420" + }, + { + "_id": "55d2fc8624dfa620d7b8a991", + "age": 33, + "name": "Rush Gates", + "gender": "male", + "company": "DIGIAL", + "email": "rushgates@digial.com", + "phone": "+1 (891) 477-2651" + }, + { + "_id": "55d2fc862d52fe806312e6dd", + "age": 40, + "name": "Holloway Gay", + "gender": "male", + "company": "EARTHMARK", + "email": "hollowaygay@earthmark.com", + "phone": "+1 (811) 540-3123" + }, + { + "_id": "55d2fc86a168313cea778ac0", + "age": 20, + "name": "Cotton Jackson", + "gender": "male", + "company": "ZOLARITY", + "email": "cottonjackson@zolarity.com", + "phone": "+1 (980) 445-3468" + }, + { + "_id": "55d2fc86577d2794043213ea", + "age": 38, + "name": "Lakeisha Blanchard", + "gender": "female", + "company": "SPEEDBOLT", + "email": "lakeishablanchard@speedbolt.com", + "phone": "+1 (839) 406-2400" + }, + { + "_id": "55d2fc8609e2760d5cc298f1", + "age": 39, + "name": "Alford Church", + "gender": "male", + "company": "GADTRON", + "email": "alfordchurch@gadtron.com", + "phone": "+1 (869) 400-2097" + }, + { + "_id": "55d2fc867e4a904c96668e08", + "age": 30, + "name": "Collins Carlson", + "gender": "male", + "company": "EVEREST", + "email": "collinscarlson@everest.com", + "phone": "+1 (944) 580-3905" + }, + { + "_id": "55d2fc86ddb3ea916627267a", + "age": 33, + "name": "Cain Hester", + "gender": "male", + "company": "IMMUNICS", + "email": "cainhester@immunics.com", + "phone": "+1 (899) 586-2934" + }, + { + "_id": "55d2fc86ffaa0a8952d1a400", + "age": 23, + "name": "Mia Baird", + "gender": "female", + "company": "NIPAZ", + "email": "miabaird@nipaz.com", + "phone": "+1 (940) 569-2850" + }, + { + "_id": "55d2fc865f259364ad5b4fff", + "age": 25, + "name": "Ramona Ewing", + "gender": "female", + "company": "COMTEST", + "email": "ramonaewing@comtest.com", + "phone": "+1 (879) 546-2754" + }, + { + "_id": "55d2fc862d6af75d6794bef5", + "age": 34, + "name": "Delacruz Goodwin", + "gender": "male", + "company": "SLOGANAUT", + "email": "delacruzgoodwin@sloganaut.com", + "phone": "+1 (928) 477-2688" + }, + { + "_id": "55d2fc86e34b19d6fd2ae261", + "age": 38, + "name": "Mcleod Moody", + "gender": "male", + "company": "ECRAZE", + "email": "mcleodmoody@ecraze.com", + "phone": "+1 (989) 598-3350" + }, + { + "_id": "55d2fc8608e305ebd55e0bac", + "age": 34, + "name": "Maddox Calhoun", + "gender": "male", + "company": "TELEPARK", + "email": "maddoxcalhoun@telepark.com", + "phone": "+1 (815) 593-3540" + }, + { + "_id": "55d2fc869e175ca83d7d6597", + "age": 36, + "name": "Cora Dale", + "gender": "female", + "company": "ZILLACTIC", + "email": "coradale@zillactic.com", + "phone": "+1 (866) 545-3632" + }, + { + "_id": "55d2fc861968b039322cb743", + "age": 27, + "name": "Knapp Miranda", + "gender": "male", + "company": "TOYLETRY", + "email": "knappmiranda@toyletry.com", + "phone": "+1 (835) 591-3111" + }, + { + "_id": "55d2fc86423f7b5a304d2175", + "age": 32, + "name": "Ida Petersen", + "gender": "female", + "company": "BILLMED", + "email": "idapetersen@billmed.com", + "phone": "+1 (898) 492-2148" + }, + { + "_id": "55d2fc862cfd92eb67375bba", + "age": 37, + "name": "Concepcion Wilcox", + "gender": "female", + "company": "MAXEMIA", + "email": "concepcionwilcox@maxemia.com", + "phone": "+1 (812) 516-2631" + }, + { + "_id": "55d2fc8695ffe246079f8f0c", + "age": 40, + "name": "Corine Daniel", + "gender": "female", + "company": "MEDCOM", + "email": "corinedaniel@medcom.com", + "phone": "+1 (991) 483-2257" + }, + { + "_id": "55d2fc861f1ff641b3aa7ee5", + "age": 31, + "name": "Latasha Byers", + "gender": "female", + "company": "RUGSTARS", + "email": "latashabyers@rugstars.com", + "phone": "+1 (817) 542-3231" + }, + { + "_id": "55d2fc86724fbfd025371582", + "age": 31, + "name": "Gayle Barrett", + "gender": "female", + "company": "PARAGONIA", + "email": "gaylebarrett@paragonia.com", + "phone": "+1 (870) 547-2454" + }, + { + "_id": "55d2fc869add6e70699650fa", + "age": 40, + "name": "Angelina Tyler", + "gender": "female", + "company": "VIRVA", + "email": "angelinatyler@virva.com", + "phone": "+1 (960) 425-3784" + }, + { + "_id": "55d2fc864c12d18424ac35be", + "age": 27, + "name": "Ratliff Franks", + "gender": "male", + "company": "CEMENTION", + "email": "ratlifffranks@cemention.com", + "phone": "+1 (958) 424-2396" + }, + { + "_id": "55d2fc86bad0be82f6e2f83b", + "age": 40, + "name": "Landry Zimmerman", + "gender": "male", + "company": "JETSILK", + "email": "landryzimmerman@jetsilk.com", + "phone": "+1 (947) 573-2755" + }, + { + "_id": "55d2fc86b56ff40ff0ed70e3", + "age": 23, + "name": "Greta West", + "gender": "female", + "company": "UBERLUX", + "email": "gretawest@uberlux.com", + "phone": "+1 (995) 542-3886" + }, + { + "_id": "55d2fc8667dfd03049bf08eb", + "age": 30, + "name": "Camacho Nelson", + "gender": "male", + "company": "KYAGORO", + "email": "camachonelson@kyagoro.com", + "phone": "+1 (881) 500-3970" + }, + { + "_id": "55d2fc865458ecf3d2995a63", + "age": 36, + "name": "June Turner", + "gender": "female", + "company": "RECOGNIA", + "email": "juneturner@recognia.com", + "phone": "+1 (976) 466-2777" + }, + { + "_id": "55d2fc8682f4304f0889c829", + "age": 31, + "name": "Mckinney Stark", + "gender": "male", + "company": "OPTICOM", + "email": "mckinneystark@opticom.com", + "phone": "+1 (951) 500-3946" + }, + { + "_id": "55d2fc86ff8211995849d831", + "age": 20, + "name": "Hammond Fletcher", + "gender": "male", + "company": "ERSUM", + "email": "hammondfletcher@ersum.com", + "phone": "+1 (974) 541-3273" + }, + { + "_id": "55d2fc86a9792a08912bdb8e", + "age": 23, + "name": "White Fischer", + "gender": "male", + "company": "REALMO", + "email": "whitefischer@realmo.com", + "phone": "+1 (963) 533-2428" + }, + { + "_id": "55d2fc86c5ba8287455030be", + "age": 36, + "name": "Kimberly Mcguire", + "gender": "female", + "company": "TETAK", + "email": "kimberlymcguire@tetak.com", + "phone": "+1 (846) 410-3414" + }, + { + "_id": "55d2fc862a665bca942115f3", + "age": 35, + "name": "Sara Hurst", + "gender": "female", + "company": "FORTEAN", + "email": "sarahurst@fortean.com", + "phone": "+1 (857) 530-3627" + }, + { + "_id": "55d2fc86b72bee240f10055e", + "age": 29, + "name": "Chandler English", + "gender": "male", + "company": "PAPRICUT", + "email": "chandlerenglish@papricut.com", + "phone": "+1 (978) 582-2348" + }, + { + "_id": "55d2fc861c2bccedeac29892", + "age": 29, + "name": "Waters Riley", + "gender": "male", + "company": "ECSTASIA", + "email": "watersriley@ecstasia.com", + "phone": "+1 (842) 579-3426" + }, + { + "_id": "55d2fc86526f36994de2038a", + "age": 34, + "name": "Wood Gomez", + "gender": "male", + "company": "ASSITIA", + "email": "woodgomez@assitia.com", + "phone": "+1 (954) 565-2413" + }, + { + "_id": "55d2fc86bec8fe1e60977c9c", + "age": 31, + "name": "Fields Decker", + "gender": "male", + "company": "EMERGENT", + "email": "fieldsdecker@emergent.com", + "phone": "+1 (992) 489-3712" + }, + { + "_id": "55d2fc868b903951ddd71703", + "age": 28, + "name": "Barry Woods", + "gender": "male", + "company": "OZEAN", + "email": "barrywoods@ozean.com", + "phone": "+1 (885) 433-3285" + }, + { + "_id": "55d2fc86ca3984177237f17e", + "age": 29, + "name": "Whitfield Higgins", + "gender": "male", + "company": "PEARLESEX", + "email": "whitfieldhiggins@pearlesex.com", + "phone": "+1 (869) 468-3186" + }, + { + "_id": "55d2fc8678dd83bf6cc16648", + "age": 27, + "name": "Haynes Mills", + "gender": "male", + "company": "ZOARERE", + "email": "haynesmills@zoarere.com", + "phone": "+1 (886) 576-3206" + }, + { + "_id": "55d2fc86b6ba36d7927b8765", + "age": 27, + "name": "Kellie Hurley", + "gender": "female", + "company": "GYNKO", + "email": "kelliehurley@gynko.com", + "phone": "+1 (844) 548-2894" + }, + { + "_id": "55d2fc869adb64b23212bdfc", + "age": 30, + "name": "Brandi Shields", + "gender": "female", + "company": "KENGEN", + "email": "brandishields@kengen.com", + "phone": "+1 (947) 447-3081" + }, + { + "_id": "55d2fc8617e038558bbd0e5f", + "age": 22, + "name": "Malinda Gordon", + "gender": "female", + "company": "QUILCH", + "email": "malindagordon@quilch.com", + "phone": "+1 (945) 466-2414" + }, + { + "_id": "55d2fc8614756992c50aabfd", + "age": 30, + "name": "Wooten Mcknight", + "gender": "male", + "company": "APPLIDECK", + "email": "wootenmcknight@applideck.com", + "phone": "+1 (994) 416-2156" + }, + { + "_id": "55d2fc86b44fbdb8c8ea3a67", + "age": 36, + "name": "Mona Thomas", + "gender": "female", + "company": "KROG", + "email": "monathomas@krog.com", + "phone": "+1 (924) 423-3381" + }, + { + "_id": "55d2fc86ded545b6b4f5e536", + "age": 22, + "name": "Bates Cole", + "gender": "male", + "company": "DIGIRANG", + "email": "batescole@digirang.com", + "phone": "+1 (956) 409-2471" + }, + { + "_id": "55d2fc864abd3a5951c8e07c", + "age": 33, + "name": "Shirley Potts", + "gender": "female", + "company": "OMATOM", + "email": "shirleypotts@omatom.com", + "phone": "+1 (804) 496-2921" + }, + { + "_id": "55d2fc864e3992c902987b9c", + "age": 22, + "name": "Adrian Branch", + "gender": "female", + "company": "MULTIFLEX", + "email": "adrianbranch@multiflex.com", + "phone": "+1 (817) 499-3955" + }, + { + "_id": "55d2fc8679037ccc9d0d84d0", + "age": 25, + "name": "Deanne Rosa", + "gender": "female", + "company": "QUONATA", + "email": "deannerosa@quonata.com", + "phone": "+1 (896) 463-2190" + }, + { + "_id": "55d2fc869ec47f3f9745bcbc", + "age": 28, + "name": "Sherrie Bowers", + "gender": "female", + "company": "GOLISTIC", + "email": "sherriebowers@golistic.com", + "phone": "+1 (854) 539-3836" + }, + { + "_id": "55d2fc86c953130389da55f9", + "age": 21, + "name": "Sharp Douglas", + "gender": "male", + "company": "CHILLIUM", + "email": "sharpdouglas@chillium.com", + "phone": "+1 (999) 513-3550" + }, + { + "_id": "55d2fc86d42d710fef2a7781", + "age": 22, + "name": "Sandy Dillard", + "gender": "female", + "company": "PHARMACON", + "email": "sandydillard@pharmacon.com", + "phone": "+1 (844) 433-2832" + }, + { + "_id": "55d2fc86aa4130e6998a333b", + "age": 20, + "name": "Naomi Willis", + "gender": "female", + "company": "SAVVY", + "email": "naomiwillis@savvy.com", + "phone": "+1 (826) 499-3221" + }, + { + "_id": "55d2fc869c2c97145040281b", + "age": 38, + "name": "Rivera Stone", + "gender": "male", + "company": "ORBIXTAR", + "email": "riverastone@orbixtar.com", + "phone": "+1 (994) 439-3810" + }, + { + "_id": "55d2fc86e7165c13cd5905c1", + "age": 22, + "name": "Oliver Day", + "gender": "male", + "company": "PORTALIS", + "email": "oliverday@portalis.com", + "phone": "+1 (844) 464-2363" + }, + { + "_id": "55d2fc86b6b619b4d04c7640", + "age": 39, + "name": "Rachael Owens", + "gender": "female", + "company": "NURALI", + "email": "rachaelowens@nurali.com", + "phone": "+1 (856) 418-3617" + }, + { + "_id": "55d2fc865f2612144e6f27e6", + "age": 30, + "name": "Winifred Molina", + "gender": "female", + "company": "NITRACYR", + "email": "winifredmolina@nitracyr.com", + "phone": "+1 (881) 417-3559" + }, + { + "_id": "55d2fc86c38ab2f341eb9717", + "age": 33, + "name": "Helen Callahan", + "gender": "female", + "company": "BOLAX", + "email": "helencallahan@bolax.com", + "phone": "+1 (929) 407-3095" + }, + { + "_id": "55d2fc86e6aa094f47df5373", + "age": 32, + "name": "Leblanc Christensen", + "gender": "male", + "company": "LIQUIDOC", + "email": "leblancchristensen@liquidoc.com", + "phone": "+1 (878) 568-2054" + }, + { + "_id": "55d2fc86b297912d153c4e8f", + "age": 31, + "name": "Hill Robbins", + "gender": "male", + "company": "QUANTALIA", + "email": "hillrobbins@quantalia.com", + "phone": "+1 (826) 430-2750" + }, + { + "_id": "55d2fc86e776e4075d7df74e", + "age": 36, + "name": "Tabitha Whitley", + "gender": "female", + "company": "ZILLIDIUM", + "email": "tabithawhitley@zillidium.com", + "phone": "+1 (838) 516-3637" + }, + { + "_id": "55d2fc86197a382bbf34e81f", + "age": 36, + "name": "May Pearson", + "gender": "male", + "company": "RODEOMAD", + "email": "maypearson@rodeomad.com", + "phone": "+1 (854) 429-3462" + }, + { + "_id": "55d2fc863ade7d3517aed2c6", + "age": 28, + "name": "Alvarez Austin", + "gender": "male", + "company": "CUBIX", + "email": "alvarezaustin@cubix.com", + "phone": "+1 (847) 594-3735" + }, + { + "_id": "55d2fc86b158d5d260362ac4", + "age": 31, + "name": "Misty Shepard", + "gender": "female", + "company": "COMVEYER", + "email": "mistyshepard@comveyer.com", + "phone": "+1 (901) 567-3881" + }, + { + "_id": "55d2fc8646bce5646a0b5258", + "age": 22, + "name": "Yvette Hensley", + "gender": "female", + "company": "MYOPIUM", + "email": "yvettehensley@myopium.com", + "phone": "+1 (890) 456-2157" + }, + { + "_id": "55d2fc86c362f848f08340c2", + "age": 36, + "name": "Hernandez Rowe", + "gender": "male", + "company": "EARTHPLEX", + "email": "hernandezrowe@earthplex.com", + "phone": "+1 (807) 502-2308" + }, + { + "_id": "55d2fc8640a621a6f035ce8d", + "age": 39, + "name": "Maura Harper", + "gender": "female", + "company": "ROBOID", + "email": "mauraharper@roboid.com", + "phone": "+1 (927) 506-2290" + }, + { + "_id": "55d2fc86965968be6314d56f", + "age": 20, + "name": "Luisa Gardner", + "gender": "female", + "company": "WAAB", + "email": "luisagardner@waab.com", + "phone": "+1 (964) 514-2189" + }, + { + "_id": "55d2fc86e12b5b70fffd430a", + "age": 36, + "name": "Christa Bradley", + "gender": "female", + "company": "FURNIGEER", + "email": "christabradley@furnigeer.com", + "phone": "+1 (871) 587-3404" + }, + { + "_id": "55d2fc86153d11b40a9bf8bc", + "age": 26, + "name": "Murphy Fleming", + "gender": "male", + "company": "COLLAIRE", + "email": "murphyfleming@collaire.com", + "phone": "+1 (909) 598-3130" + }, + { + "_id": "55d2fc863ec1e4fc29e5ce50", + "age": 28, + "name": "Bobbi Harrington", + "gender": "female", + "company": "MEDIFAX", + "email": "bobbiharrington@medifax.com", + "phone": "+1 (825) 598-2607" + }, + { + "_id": "55d2fc86f4b17262ea0129c6", + "age": 40, + "name": "Paige Flynn", + "gender": "female", + "company": "PULZE", + "email": "paigeflynn@pulze.com", + "phone": "+1 (956) 529-3295" + }, + { + "_id": "55d2fc861c8dc17cb598e70d", + "age": 21, + "name": "Nina Moon", + "gender": "female", + "company": "ZOLAVO", + "email": "ninamoon@zolavo.com", + "phone": "+1 (863) 540-3993" + }, + { + "_id": "55d2fc869d8ee9d95ab9fee4", + "age": 36, + "name": "Shauna Mckay", + "gender": "female", + "company": "LUNCHPOD", + "email": "shaunamckay@lunchpod.com", + "phone": "+1 (879) 435-3179" + }, + { + "_id": "55d2fc86a03f430c5e56194b", + "age": 36, + "name": "Amie Nicholson", + "gender": "female", + "company": "LETPRO", + "email": "amienicholson@letpro.com", + "phone": "+1 (839) 600-3014" + }, + { + "_id": "55d2fc86062b4615153832f6", + "age": 31, + "name": "Pennington Whitney", + "gender": "male", + "company": "TRI@TRIBALOG", + "email": "penningtonwhitney@tri@tribalog.com", + "phone": "+1 (950) 487-3727" + }, + { + "_id": "55d2fc868849f1c7f4f80541", + "age": 23, + "name": "Gena Barton", + "gender": "female", + "company": "VORTEXACO", + "email": "genabarton@vortexaco.com", + "phone": "+1 (889) 515-2172" + }, + { + "_id": "55d2fc860c13e786f86024fd", + "age": 27, + "name": "Ashley Stephens", + "gender": "female", + "company": "ZENSUS", + "email": "ashleystephens@zensus.com", + "phone": "+1 (949) 525-3726" + }, + { + "_id": "55d2fc86cca3638bdc9c942c", + "age": 38, + "name": "Cherie Morgan", + "gender": "female", + "company": "HELIXO", + "email": "cheriemorgan@helixo.com", + "phone": "+1 (815) 514-2167" + }, + { + "_id": "55d2fc8630d769398c9a0788", + "age": 31, + "name": "Ann Wiggins", + "gender": "female", + "company": "NIXELT", + "email": "annwiggins@nixelt.com", + "phone": "+1 (878) 567-2808" + }, + { + "_id": "55d2fc86f3d0744abd99ee4a", + "age": 36, + "name": "Hinton Keller", + "gender": "male", + "company": "VENOFLEX", + "email": "hintonkeller@venoflex.com", + "phone": "+1 (978) 499-2652" + }, + { + "_id": "55d2fc86714b2f2f7f59ff69", + "age": 32, + "name": "Marsh Mullins", + "gender": "male", + "company": "ZIALACTIC", + "email": "marshmullins@zialactic.com", + "phone": "+1 (908) 537-2112" + }, + { + "_id": "55d2fc8644c73b5870be171c", + "age": 28, + "name": "Holland Underwood", + "gender": "male", + "company": "ZILLACON", + "email": "hollandunderwood@zillacon.com", + "phone": "+1 (968) 454-2162" + }, + { + "_id": "55d2fc8648b8691a6a646f9e", + "age": 31, + "name": "Beverly Oneal", + "gender": "female", + "company": "BYTREX", + "email": "beverlyoneal@bytrex.com", + "phone": "+1 (969) 522-2598" + }, + { + "_id": "55d2fc86b3e9627aa4f5f88a", + "age": 24, + "name": "Leanne Frazier", + "gender": "female", + "company": "HOPELI", + "email": "leannefrazier@hopeli.com", + "phone": "+1 (923) 532-3379" + }, + { + "_id": "55d2fc867bdc2935055e4595", + "age": 31, + "name": "Rhodes Cash", + "gender": "male", + "company": "PAPRIKUT", + "email": "rhodescash@paprikut.com", + "phone": "+1 (830) 507-2776" + }, + { + "_id": "55d2fc86543e21b2bc15201d", + "age": 30, + "name": "Cherry Bush", + "gender": "male", + "company": "PROGENEX", + "email": "cherrybush@progenex.com", + "phone": "+1 (935) 577-2984" + }, + { + "_id": "55d2fc8660c1a32dfdf4fe67", + "age": 32, + "name": "Jacobs Clark", + "gender": "male", + "company": "COMDOM", + "email": "jacobsclark@comdom.com", + "phone": "+1 (947) 434-2665" + }, + { + "_id": "55d2fc861641831257904d9c", + "age": 37, + "name": "Nell Mcmahon", + "gender": "female", + "company": "SLAMBDA", + "email": "nellmcmahon@slambda.com", + "phone": "+1 (831) 462-2693" + }, + { + "_id": "55d2fc86835340478ec889e2", + "age": 37, + "name": "Palmer Livingston", + "gender": "male", + "company": "DIGIGEN", + "email": "palmerlivingston@digigen.com", + "phone": "+1 (817) 443-2049" + }, + { + "_id": "55d2fc8697f9fd666529fa34", + "age": 40, + "name": "Ayala Schmidt", + "gender": "male", + "company": "EWAVES", + "email": "ayalaschmidt@ewaves.com", + "phone": "+1 (899) 576-2845" + }, + { + "_id": "55d2fc8682936b03c7c044de", + "age": 26, + "name": "Lynch Beck", + "gender": "male", + "company": "INDEXIA", + "email": "lynchbeck@indexia.com", + "phone": "+1 (942) 411-3724" + }, + { + "_id": "55d2fc86bb74729fe35b2bcc", + "age": 20, + "name": "Yang Hickman", + "gender": "male", + "company": "UXMOX", + "email": "yanghickman@uxmox.com", + "phone": "+1 (944) 554-2948" + }, + { + "_id": "55d2fc86bb04a1e5e39143b1", + "age": 30, + "name": "Andrews Lucas", + "gender": "male", + "company": "CIPROMOX", + "email": "andrewslucas@cipromox.com", + "phone": "+1 (942) 401-2756" + }, + { + "_id": "55d2fc86b862c1492a4c5bc1", + "age": 34, + "name": "Rosa Valdez", + "gender": "female", + "company": "ONTALITY", + "email": "rosavaldez@ontality.com", + "phone": "+1 (963) 414-3056" + }, + { + "_id": "55d2fc868ab9f2f25a46a850", + "age": 30, + "name": "Maria Caldwell", + "gender": "female", + "company": "ACRODANCE", + "email": "mariacaldwell@acrodance.com", + "phone": "+1 (963) 433-2398" + }, + { + "_id": "55d2fc868d206b4d99f1f0b2", + "age": 36, + "name": "Gilda Chase", + "gender": "female", + "company": "KOFFEE", + "email": "gildachase@koffee.com", + "phone": "+1 (980) 591-3955" + }, + { + "_id": "55d2fc86e0c5a2f031b4a0d9", + "age": 24, + "name": "Dejesus Pittman", + "gender": "male", + "company": "SLAX", + "email": "dejesuspittman@slax.com", + "phone": "+1 (819) 574-2826" + }, + { + "_id": "55d2fc868190178a9af16ec5", + "age": 23, + "name": "Valdez Gibson", + "gender": "male", + "company": "ELECTONIC", + "email": "valdezgibson@electonic.com", + "phone": "+1 (809) 520-3985" + }, + { + "_id": "55d2fc86d3bc3cf86e16bc5b", + "age": 21, + "name": "Aguilar Bird", + "gender": "male", + "company": "ULTRASURE", + "email": "aguilarbird@ultrasure.com", + "phone": "+1 (813) 455-3814" + }, + { + "_id": "55d2fc86701c49cc235f0b49", + "age": 24, + "name": "Bentley Mooney", + "gender": "male", + "company": "BEDLAM", + "email": "bentleymooney@bedlam.com", + "phone": "+1 (870) 530-2188" + }, + { + "_id": "55d2fc863fe6d9fc492a1ca5", + "age": 28, + "name": "Ruby Wooten", + "gender": "female", + "company": "MARKETOID", + "email": "rubywooten@marketoid.com", + "phone": "+1 (813) 470-3521" + }, + { + "_id": "55d2fc8622f489f721743001", + "age": 28, + "name": "Garrison Blevins", + "gender": "male", + "company": "KEENGEN", + "email": "garrisonblevins@keengen.com", + "phone": "+1 (974) 538-2989" + }, + { + "_id": "55d2fc863dc60d226c55ece7", + "age": 33, + "name": "Harper Tanner", + "gender": "male", + "company": "QABOOS", + "email": "harpertanner@qaboos.com", + "phone": "+1 (953) 406-3082" + }, + { + "_id": "55d2fc86fecd601439c2702e", + "age": 32, + "name": "Best Robles", + "gender": "male", + "company": "OCEANICA", + "email": "bestrobles@oceanica.com", + "phone": "+1 (815) 539-3097" + }, + { + "_id": "55d2fc86ba25530a2149beab", + "age": 36, + "name": "Marian Bradshaw", + "gender": "female", + "company": "XYQAG", + "email": "marianbradshaw@xyqag.com", + "phone": "+1 (928) 410-3218" + }, + { + "_id": "55d2fc867352b6b799d365e4", + "age": 23, + "name": "Whitley Oneil", + "gender": "male", + "company": "XURBAN", + "email": "whitleyoneil@xurban.com", + "phone": "+1 (802) 578-3671" + }, + { + "_id": "55d2fc865ee137dbdee5cde2", + "age": 34, + "name": "Ella Fox", + "gender": "female", + "company": "TUBESYS", + "email": "ellafox@tubesys.com", + "phone": "+1 (920) 524-3066" + }, + { + "_id": "55d2fc860cc159486a822879", + "age": 30, + "name": "Farmer Castro", + "gender": "male", + "company": "QNEKT", + "email": "farmercastro@qnekt.com", + "phone": "+1 (866) 578-2968" + }, + { + "_id": "55d2fc863b57eefc3015d373", + "age": 28, + "name": "Guy Cochran", + "gender": "male", + "company": "VICON", + "email": "guycochran@vicon.com", + "phone": "+1 (840) 567-2191" + }, + { + "_id": "55d2fc863df7dfda22e99029", + "age": 32, + "name": "Leach Rocha", + "gender": "male", + "company": "DANJA", + "email": "leachrocha@danja.com", + "phone": "+1 (971) 589-3164" + }, + { + "_id": "55d2fc86aba3b9d7ce3f877c", + "age": 36, + "name": "Tanner Hayes", + "gender": "male", + "company": "TELEQUIET", + "email": "tannerhayes@telequiet.com", + "phone": "+1 (813) 526-2989" + }, + { + "_id": "55d2fc862cd6fb84f734fa0e", + "age": 30, + "name": "Keith Maldonado", + "gender": "male", + "company": "MAGNEATO", + "email": "keithmaldonado@magneato.com", + "phone": "+1 (997) 419-3200" + }, + { + "_id": "55d2fc8663d4dc1e43943f62", + "age": 29, + "name": "Winnie Harrell", + "gender": "female", + "company": "FRENEX", + "email": "winnieharrell@frenex.com", + "phone": "+1 (966) 565-2447" + }, + { + "_id": "55d2fc86b57f9312b0d28a1d", + "age": 28, + "name": "Sandoval Garza", + "gender": "male", + "company": "INTERLOO", + "email": "sandovalgarza@interloo.com", + "phone": "+1 (972) 597-3431" + }, + { + "_id": "55d2fc86a356d194d285d160", + "age": 23, + "name": "Lina Dejesus", + "gender": "female", + "company": "ORONOKO", + "email": "linadejesus@oronoko.com", + "phone": "+1 (910) 560-2515" + }, + { + "_id": "55d2fc862f4cd754495f93c7", + "age": 30, + "name": "Jana Spence", + "gender": "female", + "company": "ZILLACOM", + "email": "janaspence@zillacom.com", + "phone": "+1 (994) 436-2023" + }, + { + "_id": "55d2fc869b25329fae4936d0", + "age": 32, + "name": "Mcdowell Fisher", + "gender": "male", + "company": "GYNK", + "email": "mcdowellfisher@gynk.com", + "phone": "+1 (941) 587-3569" + }, + { + "_id": "55d2fc866b52b90a3758bdd3", + "age": 28, + "name": "Farley Bernard", + "gender": "male", + "company": "NETROPIC", + "email": "farleybernard@netropic.com", + "phone": "+1 (856) 540-2658" + }, + { + "_id": "55d2fc864086901eaeb80443", + "age": 25, + "name": "Lorna Howe", + "gender": "female", + "company": "ISOSWITCH", + "email": "lornahowe@isoswitch.com", + "phone": "+1 (851) 432-3160" + }, + { + "_id": "55d2fc86b4ac38891f11340b", + "age": 25, + "name": "English Watts", + "gender": "male", + "company": "INFOTRIPS", + "email": "englishwatts@infotrips.com", + "phone": "+1 (942) 481-2578" + }, + { + "_id": "55d2fc86e0d047d4eb3c224f", + "age": 35, + "name": "Burch Howell", + "gender": "male", + "company": "FANFARE", + "email": "burchhowell@fanfare.com", + "phone": "+1 (986) 507-2725" + }, + { + "_id": "55d2fc86330a8dab2ddbc0c4", + "age": 39, + "name": "Hudson Bender", + "gender": "male", + "company": "ENORMO", + "email": "hudsonbender@enormo.com", + "phone": "+1 (982) 553-3993" + }, + { + "_id": "55d2fc8600bb27f4ba215be8", + "age": 30, + "name": "Mcdonald Whitehead", + "gender": "male", + "company": "SENMAO", + "email": "mcdonaldwhitehead@senmao.com", + "phone": "+1 (837) 449-3264" + }, + { + "_id": "55d2fc8683787d8b7b400408", + "age": 31, + "name": "Hope Holden", + "gender": "female", + "company": "EVENTIX", + "email": "hopeholden@eventix.com", + "phone": "+1 (888) 436-2921" + }, + { + "_id": "55d2fc86cd7d2a5c962d2c05", + "age": 36, + "name": "Suarez Mejia", + "gender": "male", + "company": "BUZZWORKS", + "email": "suarezmejia@buzzworks.com", + "phone": "+1 (919) 526-3966" + }, + { + "_id": "55d2fc8612bf58c9b2d953cf", + "age": 27, + "name": "Michele Little", + "gender": "female", + "company": "VINCH", + "email": "michelelittle@vinch.com", + "phone": "+1 (817) 414-2165" + }, + { + "_id": "55d2fc864a8103126f905972", + "age": 25, + "name": "Patrick Cooke", + "gender": "male", + "company": "BEDDER", + "email": "patrickcooke@bedder.com", + "phone": "+1 (993) 587-2086" + }, + { + "_id": "55d2fc865ddf784cac1f023c", + "age": 31, + "name": "Holcomb Beasley", + "gender": "male", + "company": "TECHTRIX", + "email": "holcombbeasley@techtrix.com", + "phone": "+1 (879) 458-3507" + }, + { + "_id": "55d2fc86e27a28e9e9b0d232", + "age": 34, + "name": "Catalina Donovan", + "gender": "female", + "company": "FILODYNE", + "email": "catalinadonovan@filodyne.com", + "phone": "+1 (818) 542-2296" + }, + { + "_id": "55d2fc861801cbac57fa3186", + "age": 21, + "name": "Leslie Bryan", + "gender": "female", + "company": "LUXURIA", + "email": "lesliebryan@luxuria.com", + "phone": "+1 (917) 590-3272" + }, + { + "_id": "55d2fc86d3aa760444ec40cc", + "age": 37, + "name": "Hobbs Noel", + "gender": "male", + "company": "ZILLA", + "email": "hobbsnoel@zilla.com", + "phone": "+1 (917) 430-3792" + }, + { + "_id": "55d2fc86f53d0267e33ddb67", + "age": 38, + "name": "Nunez Meyers", + "gender": "male", + "company": "ENTROFLEX", + "email": "nunezmeyers@entroflex.com", + "phone": "+1 (940) 419-3943" + }, + { + "_id": "55d2fc867805cf4262e648a9", + "age": 37, + "name": "Sonya Sloan", + "gender": "female", + "company": "SURELOGIC", + "email": "sonyasloan@surelogic.com", + "phone": "+1 (924) 561-3268" + }, + { + "_id": "55d2fc86a3b756eaaef9f9fa", + "age": 31, + "name": "Angeline Sargent", + "gender": "female", + "company": "QUIZMO", + "email": "angelinesargent@quizmo.com", + "phone": "+1 (952) 539-3859" + }, + { + "_id": "55d2fc860f0e4be242c866a3", + "age": 40, + "name": "Norris Webb", + "gender": "male", + "company": "ZENCO", + "email": "norriswebb@zenco.com", + "phone": "+1 (834) 527-2399" + }, + { + "_id": "55d2fc86b2da030fb755d74c", + "age": 38, + "name": "Wise Bonner", + "gender": "male", + "company": "KINETICA", + "email": "wisebonner@kinetica.com", + "phone": "+1 (938) 416-3537" + }, + { + "_id": "55d2fc8699036f35ee214843", + "age": 25, + "name": "Imogene Blankenship", + "gender": "female", + "company": "POLARAX", + "email": "imogeneblankenship@polarax.com", + "phone": "+1 (877) 476-3735" + }, + { + "_id": "55d2fc86009b5a1658986a92", + "age": 27, + "name": "Silva Schneider", + "gender": "male", + "company": "MINGA", + "email": "silvaschneider@minga.com", + "phone": "+1 (884) 420-2111" + }, + { + "_id": "55d2fc86e3dcb6d4996e9813", + "age": 40, + "name": "Lawanda Cortez", + "gender": "female", + "company": "HOMETOWN", + "email": "lawandacortez@hometown.com", + "phone": "+1 (946) 525-3826" + }, + { + "_id": "55d2fc8641977ef422e73176", + "age": 40, + "name": "Clements Waters", + "gender": "male", + "company": "FLEETMIX", + "email": "clementswaters@fleetmix.com", + "phone": "+1 (973) 523-2395" + }, + { + "_id": "55d2fc869d0f5363ad055935", + "age": 20, + "name": "Ofelia Gilbert", + "gender": "female", + "company": "ECRATER", + "email": "ofeliagilbert@ecrater.com", + "phone": "+1 (828) 404-2646" + }, + { + "_id": "55d2fc86c39b876162269895", + "age": 23, + "name": "Valenzuela Carney", + "gender": "male", + "company": "HYDROCOM", + "email": "valenzuelacarney@hydrocom.com", + "phone": "+1 (842) 566-3650" + }, + { + "_id": "55d2fc86546c31933b02dd85", + "age": 28, + "name": "Wells Santana", + "gender": "male", + "company": "ZIDOX", + "email": "wellssantana@zidox.com", + "phone": "+1 (886) 527-2963" + }, + { + "_id": "55d2fc86a981c3741ad50f8c", + "age": 29, + "name": "Karla Carroll", + "gender": "female", + "company": "MAGNEMO", + "email": "karlacarroll@magnemo.com", + "phone": "+1 (922) 418-3361" + }, + { + "_id": "55d2fc861054327ef76378e3", + "age": 29, + "name": "Juliet Butler", + "gender": "female", + "company": "ORBAXTER", + "email": "julietbutler@orbaxter.com", + "phone": "+1 (838) 554-2269" + }, + { + "_id": "55d2fc868120c8a8e4eb9149", + "age": 26, + "name": "Lisa Copeland", + "gender": "female", + "company": "SOFTMICRO", + "email": "lisacopeland@softmicro.com", + "phone": "+1 (915) 577-2302" + }, + { + "_id": "55d2fc86b0eb908d67787f43", + "age": 37, + "name": "Mcmahon Spencer", + "gender": "male", + "company": "BOILCAT", + "email": "mcmahonspencer@boilcat.com", + "phone": "+1 (871) 501-2558" + }, + { + "_id": "55d2fc860c0b6f5fb520ad2a", + "age": 38, + "name": "Campbell Baxter", + "gender": "male", + "company": "DREAMIA", + "email": "campbellbaxter@dreamia.com", + "phone": "+1 (858) 524-3012" + }, + { + "_id": "55d2fc86460508fbed08e924", + "age": 23, + "name": "Lindsay Sharp", + "gender": "male", + "company": "SYBIXTEX", + "email": "lindsaysharp@sybixtex.com", + "phone": "+1 (974) 573-3073" + }, + { + "_id": "55d2fc860bf5295ce5679523", + "age": 35, + "name": "Kathrine Browning", + "gender": "female", + "company": "LUNCHPAD", + "email": "kathrinebrowning@lunchpad.com", + "phone": "+1 (922) 458-2466" + }, + { + "_id": "55d2fc86d2311e0208cd17a2", + "age": 35, + "name": "Alice Faulkner", + "gender": "female", + "company": "PLEXIA", + "email": "alicefaulkner@plexia.com", + "phone": "+1 (939) 419-3621" + }, + { + "_id": "55d2fc86e85116cc8d6c0d70", + "age": 32, + "name": "Ford Mclean", + "gender": "male", + "company": "QUADEEBO", + "email": "fordmclean@quadeebo.com", + "phone": "+1 (974) 521-2540" + }, + { + "_id": "55d2fc86f60393147a3a8a07", + "age": 29, + "name": "Ollie Cannon", + "gender": "female", + "company": "RAMJOB", + "email": "olliecannon@ramjob.com", + "phone": "+1 (961) 497-3201" + }, + { + "_id": "55d2fc8661d02152a5ba425a", + "age": 36, + "name": "Calderon Vaughan", + "gender": "male", + "company": "DIGIQUE", + "email": "calderonvaughan@digique.com", + "phone": "+1 (912) 553-3902" + }, + { + "_id": "55d2fc86a55890c6ce345bf0", + "age": 28, + "name": "Warren Henry", + "gender": "male", + "company": "ZERBINA", + "email": "warrenhenry@zerbina.com", + "phone": "+1 (850) 464-3656" + }, + { + "_id": "55d2fc86ccead2741ea21e0e", + "age": 21, + "name": "Liliana York", + "gender": "female", + "company": "ANIXANG", + "email": "lilianayork@anixang.com", + "phone": "+1 (956) 403-2096" + }, + { + "_id": "55d2fc86d72dd184f6884371", + "age": 33, + "name": "Lora Alvarez", + "gender": "female", + "company": "BLURRYBUS", + "email": "loraalvarez@blurrybus.com", + "phone": "+1 (917) 457-2866" + }, + { + "_id": "55d2fc86fda0c180ccc9598a", + "age": 22, + "name": "Luna Ellis", + "gender": "male", + "company": "SLUMBERIA", + "email": "lunaellis@slumberia.com", + "phone": "+1 (878) 589-3511" + }, + { + "_id": "55d2fc860dd81b364fc1c2a9", + "age": 30, + "name": "Phoebe Chang", + "gender": "female", + "company": "OPTICON", + "email": "phoebechang@opticon.com", + "phone": "+1 (962) 559-3475" + }, + { + "_id": "55d2fc8657954cc73c166579", + "age": 40, + "name": "Anna Crane", + "gender": "female", + "company": "AQUAFIRE", + "email": "annacrane@aquafire.com", + "phone": "+1 (989) 567-3649" + }, + { + "_id": "55d2fc86d996f0f466a006c8", + "age": 39, + "name": "Matthews French", + "gender": "male", + "company": "CINESANCT", + "email": "matthewsfrench@cinesanct.com", + "phone": "+1 (896) 518-2965" + }, + { + "_id": "55d2fc8601aad1428aa65531", + "age": 34, + "name": "Hutchinson Ellison", + "gender": "male", + "company": "MOREGANIC", + "email": "hutchinsonellison@moreganic.com", + "phone": "+1 (860) 563-2707" + }, + { + "_id": "55d2fc86ec14a6c798e22d72", + "age": 21, + "name": "Gwen Russell", + "gender": "female", + "company": "COMVOY", + "email": "gwenrussell@comvoy.com", + "phone": "+1 (873) 468-2314" + }, + { + "_id": "55d2fc865123af00125fd9bd", + "age": 22, + "name": "Natalie Stuart", + "gender": "female", + "company": "MOMENTIA", + "email": "nataliestuart@momentia.com", + "phone": "+1 (908) 573-2177" + }, + { + "_id": "55d2fc8688af31f1e9ff0d20", + "age": 28, + "name": "Brianna Meyer", + "gender": "female", + "company": "VIASIA", + "email": "briannameyer@viasia.com", + "phone": "+1 (860) 475-3139" + }, + { + "_id": "55d2fc8687f0ff5daa16cbbd", + "age": 28, + "name": "Trisha Castillo", + "gender": "female", + "company": "CYCLONICA", + "email": "trishacastillo@cyclonica.com", + "phone": "+1 (869) 564-2957" + }, + { + "_id": "55d2fc863b1c51f11ce4e921", + "age": 36, + "name": "Powers Weeks", + "gender": "male", + "company": "BIZMATIC", + "email": "powersweeks@bizmatic.com", + "phone": "+1 (981) 464-3668" + }, + { + "_id": "55d2fc86d8f5d2bef6f84bba", + "age": 23, + "name": "Young Cabrera", + "gender": "female", + "company": "CINASTER", + "email": "youngcabrera@cinaster.com", + "phone": "+1 (897) 528-3924" + }, + { + "_id": "55d2fc86861238b57e2932fd", + "age": 27, + "name": "Maxine Rodgers", + "gender": "female", + "company": "CHORIZON", + "email": "maxinerodgers@chorizon.com", + "phone": "+1 (996) 449-2805" + }, + { + "_id": "55d2fc86fe5b2f6823cc295f", + "age": 26, + "name": "Davis Norris", + "gender": "male", + "company": "CORIANDER", + "email": "davisnorris@coriander.com", + "phone": "+1 (947) 512-2093" + }, + { + "_id": "55d2fc86862e7d0bba1ab524", + "age": 25, + "name": "Ericka Conner", + "gender": "female", + "company": "STRALOY", + "email": "erickaconner@straloy.com", + "phone": "+1 (922) 565-2956" + }, + { + "_id": "55d2fc8625bf91e39382ab4c", + "age": 21, + "name": "Payne Joyner", + "gender": "male", + "company": "OMNIGOG", + "email": "paynejoyner@omnigog.com", + "phone": "+1 (998) 521-3917" + }, + { + "_id": "55d2fc866835ee51ccea79bb", + "age": 24, + "name": "Fletcher Payne", + "gender": "male", + "company": "AMTAP", + "email": "fletcherpayne@amtap.com", + "phone": "+1 (991) 517-3798" + }, + { + "_id": "55d2fc863df3424f70db684c", + "age": 38, + "name": "Mosley Cobb", + "gender": "male", + "company": "HONOTRON", + "email": "mosleycobb@honotron.com", + "phone": "+1 (873) 593-2248" + }, + { + "_id": "55d2fc867c4ad9b233d15983", + "age": 24, + "name": "Webster Sandoval", + "gender": "male", + "company": "HOMELUX", + "email": "webstersandoval@homelux.com", + "phone": "+1 (967) 431-2940" + }, + { + "_id": "55d2fc8613bf2fe49b1a1f8c", + "age": 36, + "name": "Colon Mcgee", + "gender": "male", + "company": "ZAPPIX", + "email": "colonmcgee@zappix.com", + "phone": "+1 (806) 444-2451" + }, + { + "_id": "55d2fc8681a8ccebe8aacd93", + "age": 32, + "name": "Monique Logan", + "gender": "female", + "company": "CALLFLEX", + "email": "moniquelogan@callflex.com", + "phone": "+1 (957) 577-3780" + }, + { + "_id": "55d2fc868756d302f29fddb2", + "age": 38, + "name": "Stewart Ball", + "gender": "male", + "company": "NETPLODE", + "email": "stewartball@netplode.com", + "phone": "+1 (966) 435-2206" + }, + { + "_id": "55d2fc86457f4d474388d2c8", + "age": 23, + "name": "Montgomery Carter", + "gender": "male", + "company": "OLUCORE", + "email": "montgomerycarter@olucore.com", + "phone": "+1 (894) 556-2662" + }, + { + "_id": "55d2fc866da04e0d15b12b36", + "age": 28, + "name": "Brenda Mccoy", + "gender": "female", + "company": "AQUAZURE", + "email": "brendamccoy@aquazure.com", + "phone": "+1 (837) 483-3741" + }, + { + "_id": "55d2fc86e2a61a730a8d5c8f", + "age": 34, + "name": "Eddie Buchanan", + "gender": "female", + "company": "COGNICODE", + "email": "eddiebuchanan@cognicode.com", + "phone": "+1 (924) 479-3753" + }, + { + "_id": "55d2fc8616e7989042f61488", + "age": 23, + "name": "Eva Mendoza", + "gender": "female", + "company": "SOLGAN", + "email": "evamendoza@solgan.com", + "phone": "+1 (899) 522-3051" + }, + { + "_id": "55d2fc86152fd9e4c5471fd7", + "age": 21, + "name": "Dawson Medina", + "gender": "male", + "company": "AQUASSEUR", + "email": "dawsonmedina@aquasseur.com", + "phone": "+1 (877) 580-2295" + }, + { + "_id": "55d2fc86a3633b3a799a7811", + "age": 34, + "name": "Terrie Hobbs", + "gender": "female", + "company": "VORATAK", + "email": "terriehobbs@voratak.com", + "phone": "+1 (938) 511-2077" + }, + { + "_id": "55d2fc8665a05f05f07bb790", + "age": 37, + "name": "Iris Bishop", + "gender": "female", + "company": "INSURESYS", + "email": "irisbishop@insuresys.com", + "phone": "+1 (819) 415-3840" + }, + { + "_id": "55d2fc8799e7556a033b93f6", + "age": 29, + "name": "Estelle Grant", + "gender": "female", + "company": "ZOGAK", + "email": "estellegrant@zogak.com", + "phone": "+1 (854) 437-2898" + }, + { + "_id": "55d2fc87745c675e697af04c", + "age": 30, + "name": "Dianna Gonzalez", + "gender": "female", + "company": "PIVITOL", + "email": "diannagonzalez@pivitol.com", + "phone": "+1 (816) 545-3520" + } +] diff --git a/vendor/github.com/PuerkitoBio/purell/.gitignore b/vendor/github.com/PuerkitoBio/purell/.gitignore new file mode 100644 index 000000000..748e4c807 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/.gitignore @@ -0,0 +1,5 @@ +*.sublime-* +.DS_Store +*.swp +*.swo +tags diff --git a/vendor/github.com/PuerkitoBio/purell/.travis.yml b/vendor/github.com/PuerkitoBio/purell/.travis.yml new file mode 100644 index 000000000..facfc91c6 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - 1.4 + - 1.5 + - 1.6 + - tip diff --git a/vendor/github.com/PuerkitoBio/purell/LICENSE b/vendor/github.com/PuerkitoBio/purell/LICENSE new file mode 100644 index 000000000..4b9986dea --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2012, Martin Angers +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/PuerkitoBio/purell/README.md b/vendor/github.com/PuerkitoBio/purell/README.md new file mode 100644 index 000000000..a78a3df65 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/README.md @@ -0,0 +1,185 @@ +# Purell + +Purell is a tiny Go library to normalize URLs. It returns a pure URL. Pure-ell. Sanitizer and all. Yeah, I know... + +Based on the [wikipedia paper][wiki] and the [RFC 3986 document][rfc]. + +[![build status](https://secure.travis-ci.org/PuerkitoBio/purell.png)](http://travis-ci.org/PuerkitoBio/purell) + +## Install + +`go get github.com/PuerkitoBio/purell` + +## Changelog + +* **2016-07-27 (v1.0.0)** : Normalize IDN to ASCII (thanks to @zenovich). +* **2015-02-08** : Add fix for relative paths issue ([PR #5][pr5]) and add fix for unnecessary encoding of reserved characters ([see issue #7][iss7]). +* **v0.2.0** : Add benchmarks, Attempt IDN support. +* **v0.1.0** : Initial release. + +## Examples + +From `example_test.go` (note that in your code, you would import "github.com/PuerkitoBio/purell", and would prefix references to its methods and constants with "purell."): + +```go +package purell + +import ( + "fmt" + "net/url" +) + +func ExampleNormalizeURLString() { + if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/", + FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil { + panic(err) + } else { + fmt.Print(normalized) + } + // Output: http://somewebsite.com:80/Amazing%3F/url/ +} + +func ExampleMustNormalizeURLString() { + normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/", + FlagsUnsafeGreedy) + fmt.Print(normalized) + + // Output: http://somewebsite.com/Amazing%FA/url +} + +func ExampleNormalizeURL() { + if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil { + panic(err) + } else { + normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment) + fmt.Print(normalized) + } + + // Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0 +} +``` + +## API + +As seen in the examples above, purell offers three methods, `NormalizeURLString(string, NormalizationFlags) (string, error)`, `MustNormalizeURLString(string, NormalizationFlags) (string)` and `NormalizeURL(*url.URL, NormalizationFlags) (string)`. They all normalize the provided URL based on the specified flags. Here are the available flags: + +```go +const ( + // Safe normalizations + FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1 + FlagLowercaseHost // http://HOST -> http://host + FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF + FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA + FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$ + FlagRemoveDefaultPort // http://host:80 -> http://host + FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path + + // Usually safe normalizations + FlagRemoveTrailingSlash // http://host/path/ -> http://host/path + FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags) + FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c + + // Unsafe normalizations + FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/ + FlagRemoveFragment // http://host/path#fragment -> http://host/path + FlagForceHTTP // https://host -> http://host + FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b + FlagRemoveWWW // http://www.host/ -> http://host/ + FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags) + FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3 + + // Normalizations not in the wikipedia article, required to cover tests cases + // submitted by jehiah + FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147 + FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147 + FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147 + FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path + FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path + + // Convenience set of safe normalizations + FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator + + // For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags, + // while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix". + + // Convenience set of usually safe normalizations (includes FlagsSafe) + FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments + FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments + + // Convenience set of unsafe normalizations (includes FlagsUsuallySafe) + FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery + FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery + + // Convenience set of all available flags + FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator + FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator +) +``` + +For convenience, the set of flags `FlagsSafe`, `FlagsUsuallySafe[Greedy|NonGreedy]`, `FlagsUnsafe[Greedy|NonGreedy]` and `FlagsAll[Greedy|NonGreedy]` are provided for the similarly grouped normalizations on [wikipedia's URL normalization page][wiki]. You can add (using the bitwise OR `|` operator) or remove (using the bitwise AND NOT `&^` operator) individual flags from the sets if required, to build your own custom set. + +The [full godoc reference is available on gopkgdoc][godoc]. + +Some things to note: + +* `FlagDecodeUnnecessaryEscapes`, `FlagEncodeNecessaryEscapes`, `FlagUppercaseEscapes` and `FlagRemoveEmptyQuerySeparator` are always implicitly set, because internally, the URL string is parsed as an URL object, which automatically decodes unnecessary escapes, uppercases and encodes necessary ones, and removes empty query separators (an unnecessary `?` at the end of the url). So this operation cannot **not** be done. For this reason, `FlagRemoveEmptyQuerySeparator` (as well as the other three) has been included in the `FlagsSafe` convenience set, instead of `FlagsUnsafe`, where Wikipedia puts it. + +* The `FlagDecodeUnnecessaryEscapes` decodes the following escapes (*from -> to*): + - %24 -> $ + - %26 -> & + - %2B-%3B -> +,-./0123456789:; + - %3D -> = + - %40-%5A -> @ABCDEFGHIJKLMNOPQRSTUVWXYZ + - %5F -> _ + - %61-%7A -> abcdefghijklmnopqrstuvwxyz + - %7E -> ~ + + +* When the `NormalizeURL` function is used (passing an URL object), this source URL object is modified (that is, after the call, the URL object will be modified to reflect the normalization). + +* The *replace IP with domain name* normalization (`http://208.77.188.166/ → http://www.example.com/`) is obviously not possible for a library without making some network requests. This is not implemented in purell. + +* The *remove unused query string parameters* and *remove default query parameters* are also not implemented, since this is a very case-specific normalization, and it is quite trivial to do with an URL object. + +### Safe vs Usually Safe vs Unsafe + +Purell allows you to control the level of risk you take while normalizing an URL. You can aggressively normalize, play it totally safe, or anything in between. + +Consider the following URL: + +`HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid` + +Normalizing with the `FlagsSafe` gives: + +`https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid` + +With the `FlagsUsuallySafeGreedy`: + +`https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid` + +And with `FlagsUnsafeGreedy`: + +`http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3` + +## TODOs + +* Add a class/default instance to allow specifying custom directory index names? At the moment, removing directory index removes `(^|/)((?:default|index)\.\w{1,4})$`. + +## Thanks / Contributions + +@rogpeppe +@jehiah +@opennota +@pchristopher1275 +@zenovich + +## License + +The [BSD 3-Clause license][bsd]. + +[bsd]: http://opensource.org/licenses/BSD-3-Clause +[wiki]: http://en.wikipedia.org/wiki/URL_normalization +[rfc]: http://tools.ietf.org/html/rfc3986#section-6 +[godoc]: http://go.pkgdoc.org/github.com/PuerkitoBio/purell +[pr5]: https://github.com/PuerkitoBio/purell/pull/5 +[iss7]: https://github.com/PuerkitoBio/purell/issues/7 diff --git a/vendor/github.com/PuerkitoBio/purell/bench_test.go b/vendor/github.com/PuerkitoBio/purell/bench_test.go new file mode 100644 index 000000000..7549731fc --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/bench_test.go @@ -0,0 +1,57 @@ +package purell + +import ( + "testing" +) + +var ( + safeUrl = "HttPS://..iaMHost..Test:443/paTh^A%ef//./%41PaTH/..//?" + usuallySafeUrl = "HttPS://..iaMHost..Test:443/paTh^A%ef//./%41PaTH/../final/" + unsafeUrl = "HttPS://..www.iaMHost..Test:443/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment" + allDWORDUrl = "HttPS://1113982867:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment" + allOctalUrl = "HttPS://0102.0146.07.0223:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment" + allHexUrl = "HttPS://0x42660793:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment" + allCombinedUrl = "HttPS://..0x42660793.:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment" +) + +func BenchmarkSafe(b *testing.B) { + for i := 0; i < b.N; i++ { + NormalizeURLString(safeUrl, FlagsSafe) + } +} + +func BenchmarkUsuallySafe(b *testing.B) { + for i := 0; i < b.N; i++ { + NormalizeURLString(usuallySafeUrl, FlagsUsuallySafeGreedy) + } +} + +func BenchmarkUnsafe(b *testing.B) { + for i := 0; i < b.N; i++ { + NormalizeURLString(unsafeUrl, FlagsUnsafeGreedy) + } +} + +func BenchmarkAllDWORD(b *testing.B) { + for i := 0; i < b.N; i++ { + NormalizeURLString(allDWORDUrl, FlagsAllGreedy) + } +} + +func BenchmarkAllOctal(b *testing.B) { + for i := 0; i < b.N; i++ { + NormalizeURLString(allOctalUrl, FlagsAllGreedy) + } +} + +func BenchmarkAllHex(b *testing.B) { + for i := 0; i < b.N; i++ { + NormalizeURLString(allHexUrl, FlagsAllGreedy) + } +} + +func BenchmarkAllCombined(b *testing.B) { + for i := 0; i < b.N; i++ { + NormalizeURLString(allCombinedUrl, FlagsAllGreedy) + } +} diff --git a/vendor/github.com/PuerkitoBio/purell/benchmarks/v0.1.0 b/vendor/github.com/PuerkitoBio/purell/benchmarks/v0.1.0 new file mode 100644 index 000000000..3bbe7113c --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/benchmarks/v0.1.0 @@ -0,0 +1,9 @@ +PASS +BenchmarkSafe 500000 6131 ns/op +BenchmarkUsuallySafe 200000 7864 ns/op +BenchmarkUnsafe 100000 28560 ns/op +BenchmarkAllDWORD 50000 38722 ns/op +BenchmarkAllOctal 50000 40941 ns/op +BenchmarkAllHex 50000 44063 ns/op +BenchmarkAllCombined 50000 33613 ns/op +ok github.com/PuerkitoBio/purell 17.404s diff --git a/vendor/github.com/PuerkitoBio/purell/example_test.go b/vendor/github.com/PuerkitoBio/purell/example_test.go new file mode 100644 index 000000000..997b95369 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/example_test.go @@ -0,0 +1,35 @@ +package purell + +import ( + "fmt" + "net/url" +) + +func ExampleNormalizeURLString() { + if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/", + FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil { + panic(err) + } else { + fmt.Print(normalized) + } + // Output: http://somewebsite.com:80/Amazing%3F/url/ +} + +func ExampleMustNormalizeURLString() { + normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/", + FlagsUnsafeGreedy) + fmt.Print(normalized) + + // Output: http://somewebsite.com/Amazing%FA/url +} + +func ExampleNormalizeURL() { + if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil { + panic(err) + } else { + normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment) + fmt.Print(normalized) + } + + // Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0 +} diff --git a/vendor/github.com/PuerkitoBio/purell/purell.go b/vendor/github.com/PuerkitoBio/purell/purell.go new file mode 100644 index 000000000..b79da64b3 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/purell.go @@ -0,0 +1,375 @@ +/* +Package purell offers URL normalization as described on the wikipedia page: +http://en.wikipedia.org/wiki/URL_normalization +*/ +package purell + +import ( + "bytes" + "fmt" + "net/url" + "regexp" + "sort" + "strconv" + "strings" + + "github.com/PuerkitoBio/urlesc" + "golang.org/x/net/idna" + "golang.org/x/text/secure/precis" + "golang.org/x/text/unicode/norm" +) + +// A set of normalization flags determines how a URL will +// be normalized. +type NormalizationFlags uint + +const ( + // Safe normalizations + FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1 + FlagLowercaseHost // http://HOST -> http://host + FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF + FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA + FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$ + FlagRemoveDefaultPort // http://host:80 -> http://host + FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path + + // Usually safe normalizations + FlagRemoveTrailingSlash // http://host/path/ -> http://host/path + FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags) + FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c + + // Unsafe normalizations + FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/ + FlagRemoveFragment // http://host/path#fragment -> http://host/path + FlagForceHTTP // https://host -> http://host + FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b + FlagRemoveWWW // http://www.host/ -> http://host/ + FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags) + FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3 + + // Normalizations not in the wikipedia article, required to cover tests cases + // submitted by jehiah + FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147 + FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147 + FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147 + FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path + FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path + + // Convenience set of safe normalizations + FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator + + // For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags, + // while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix". + + // Convenience set of usually safe normalizations (includes FlagsSafe) + FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments + FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments + + // Convenience set of unsafe normalizations (includes FlagsUsuallySafe) + FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery + FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery + + // Convenience set of all available flags + FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator + FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator +) + +const ( + defaultHttpPort = ":80" + defaultHttpsPort = ":443" +) + +// Regular expressions used by the normalizations +var rxPort = regexp.MustCompile(`(:\d+)/?$`) +var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`) +var rxDupSlashes = regexp.MustCompile(`/{2,}`) +var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`) +var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`) +var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`) +var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`) +var rxEmptyPort = regexp.MustCompile(`:+$`) + +// Map of flags to implementation function. +// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically +// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator. + +// Since maps have undefined traversing order, make a slice of ordered keys +var flagsOrder = []NormalizationFlags{ + FlagLowercaseScheme, + FlagLowercaseHost, + FlagRemoveDefaultPort, + FlagRemoveDirectoryIndex, + FlagRemoveDotSegments, + FlagRemoveFragment, + FlagForceHTTP, // Must be after remove default port (because https=443/http=80) + FlagRemoveDuplicateSlashes, + FlagRemoveWWW, + FlagAddWWW, + FlagSortQuery, + FlagDecodeDWORDHost, + FlagDecodeOctalHost, + FlagDecodeHexHost, + FlagRemoveUnnecessaryHostDots, + FlagRemoveEmptyPortSeparator, + FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last + FlagAddTrailingSlash, +} + +// ... and then the map, where order is unimportant +var flags = map[NormalizationFlags]func(*url.URL){ + FlagLowercaseScheme: lowercaseScheme, + FlagLowercaseHost: lowercaseHost, + FlagRemoveDefaultPort: removeDefaultPort, + FlagRemoveDirectoryIndex: removeDirectoryIndex, + FlagRemoveDotSegments: removeDotSegments, + FlagRemoveFragment: removeFragment, + FlagForceHTTP: forceHTTP, + FlagRemoveDuplicateSlashes: removeDuplicateSlashes, + FlagRemoveWWW: removeWWW, + FlagAddWWW: addWWW, + FlagSortQuery: sortQuery, + FlagDecodeDWORDHost: decodeDWORDHost, + FlagDecodeOctalHost: decodeOctalHost, + FlagDecodeHexHost: decodeHexHost, + FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots, + FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator, + FlagRemoveTrailingSlash: removeTrailingSlash, + FlagAddTrailingSlash: addTrailingSlash, +} + +// MustNormalizeURLString returns the normalized string, and panics if an error occurs. +// It takes an URL string as input, as well as the normalization flags. +func MustNormalizeURLString(u string, f NormalizationFlags) string { + result, e := NormalizeURLString(u, f) + if e != nil { + panic(e) + } + return result +} + +// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object. +// It takes an URL string as input, as well as the normalization flags. +func NormalizeURLString(u string, f NormalizationFlags) (string, error) { + if parsed, e := url.Parse(u); e != nil { + return "", e + } else { + options := make([]precis.Option, 1, 3) + options[0] = precis.IgnoreCase + if f&FlagLowercaseHost == FlagLowercaseHost { + options = append(options, precis.FoldCase()) + } + options = append(options, precis.Norm(norm.NFC)) + profile := precis.NewFreeform(options...) + if parsed.Host, e = idna.ToASCII(profile.NewTransformer().String(parsed.Host)); e != nil { + return "", e + } + return NormalizeURL(parsed, f), nil + } + panic("Unreachable code.") +} + +// NormalizeURL returns the normalized string. +// It takes a parsed URL object as input, as well as the normalization flags. +func NormalizeURL(u *url.URL, f NormalizationFlags) string { + for _, k := range flagsOrder { + if f&k == k { + flags[k](u) + } + } + return urlesc.Escape(u) +} + +func lowercaseScheme(u *url.URL) { + if len(u.Scheme) > 0 { + u.Scheme = strings.ToLower(u.Scheme) + } +} + +func lowercaseHost(u *url.URL) { + if len(u.Host) > 0 { + u.Host = strings.ToLower(u.Host) + } +} + +func removeDefaultPort(u *url.URL) { + if len(u.Host) > 0 { + scheme := strings.ToLower(u.Scheme) + u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string { + if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) { + return "" + } + return val + }) + } +} + +func removeTrailingSlash(u *url.URL) { + if l := len(u.Path); l > 0 { + if strings.HasSuffix(u.Path, "/") { + u.Path = u.Path[:l-1] + } + } else if l = len(u.Host); l > 0 { + if strings.HasSuffix(u.Host, "/") { + u.Host = u.Host[:l-1] + } + } +} + +func addTrailingSlash(u *url.URL) { + if l := len(u.Path); l > 0 { + if !strings.HasSuffix(u.Path, "/") { + u.Path += "/" + } + } else if l = len(u.Host); l > 0 { + if !strings.HasSuffix(u.Host, "/") { + u.Host += "/" + } + } +} + +func removeDotSegments(u *url.URL) { + if len(u.Path) > 0 { + var dotFree []string + var lastIsDot bool + + sections := strings.Split(u.Path, "/") + for _, s := range sections { + if s == ".." { + if len(dotFree) > 0 { + dotFree = dotFree[:len(dotFree)-1] + } + } else if s != "." { + dotFree = append(dotFree, s) + } + lastIsDot = (s == "." || s == "..") + } + // Special case if host does not end with / and new path does not begin with / + u.Path = strings.Join(dotFree, "/") + if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") { + u.Path = "/" + u.Path + } + // Special case if the last segment was a dot, make sure the path ends with a slash + if lastIsDot && !strings.HasSuffix(u.Path, "/") { + u.Path += "/" + } + } +} + +func removeDirectoryIndex(u *url.URL) { + if len(u.Path) > 0 { + u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1") + } +} + +func removeFragment(u *url.URL) { + u.Fragment = "" +} + +func forceHTTP(u *url.URL) { + if strings.ToLower(u.Scheme) == "https" { + u.Scheme = "http" + } +} + +func removeDuplicateSlashes(u *url.URL) { + if len(u.Path) > 0 { + u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/") + } +} + +func removeWWW(u *url.URL) { + if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") { + u.Host = u.Host[4:] + } +} + +func addWWW(u *url.URL) { + if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") { + u.Host = "www." + u.Host + } +} + +func sortQuery(u *url.URL) { + q := u.Query() + + if len(q) > 0 { + arKeys := make([]string, len(q)) + i := 0 + for k, _ := range q { + arKeys[i] = k + i++ + } + sort.Strings(arKeys) + buf := new(bytes.Buffer) + for _, k := range arKeys { + sort.Strings(q[k]) + for _, v := range q[k] { + if buf.Len() > 0 { + buf.WriteRune('&') + } + buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v))) + } + } + + // Rebuild the raw query string + u.RawQuery = buf.String() + } +} + +func decodeDWORDHost(u *url.URL) { + if len(u.Host) > 0 { + if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 { + var parts [4]int64 + + dword, _ := strconv.ParseInt(matches[1], 10, 0) + for i, shift := range []uint{24, 16, 8, 0} { + parts[i] = dword >> shift & 0xFF + } + u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2]) + } + } +} + +func decodeOctalHost(u *url.URL) { + if len(u.Host) > 0 { + if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 { + var parts [4]int64 + + for i := 1; i <= 4; i++ { + parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0) + } + u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5]) + } + } +} + +func decodeHexHost(u *url.URL) { + if len(u.Host) > 0 { + if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 { + // Conversion is safe because of regex validation + parsed, _ := strconv.ParseInt(matches[1], 16, 0) + // Set host as DWORD (base 10) encoded host + u.Host = fmt.Sprintf("%d%s", parsed, matches[2]) + // The rest is the same as decoding a DWORD host + decodeDWORDHost(u) + } + } +} + +func removeUnncessaryHostDots(u *url.URL) { + if len(u.Host) > 0 { + if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 { + // Trim the leading and trailing dots + u.Host = strings.Trim(matches[1], ".") + if len(matches) > 2 { + u.Host += matches[2] + } + } + } +} + +func removeEmptyPortSeparator(u *url.URL) { + if len(u.Host) > 0 { + u.Host = rxEmptyPort.ReplaceAllString(u.Host, "") + } +} diff --git a/vendor/github.com/PuerkitoBio/purell/purell_test.go b/vendor/github.com/PuerkitoBio/purell/purell_test.go new file mode 100644 index 000000000..a3732e5a3 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/purell_test.go @@ -0,0 +1,768 @@ +package purell + +import ( + "fmt" + "net/url" + "testing" +) + +type testCase struct { + nm string + src string + flgs NormalizationFlags + res string + parsed bool +} + +var ( + cases = [...]*testCase{ + &testCase{ + "LowerScheme", + "HTTP://www.SRC.ca", + FlagLowercaseScheme, + "http://www.SRC.ca", + false, + }, + &testCase{ + "LowerScheme2", + "http://www.SRC.ca", + FlagLowercaseScheme, + "http://www.SRC.ca", + false, + }, + &testCase{ + "LowerHost", + "HTTP://www.SRC.ca/", + FlagLowercaseHost, + "http://www.src.ca/", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "UpperEscapes", + `http://www.whatever.com/Some%aa%20Special%8Ecases/`, + FlagUppercaseEscapes, + "http://www.whatever.com/Some%AA%20Special%8Ecases/", + false, + }, + &testCase{ + "UnnecessaryEscapes", + `http://www.toto.com/%41%42%2E%44/%32%33%52%2D/%5f%7E`, + FlagDecodeUnnecessaryEscapes, + "http://www.toto.com/AB.D/23R-/_~", + false, + }, + &testCase{ + "RemoveDefaultPort", + "HTTP://www.SRC.ca:80/", + FlagRemoveDefaultPort, + "http://www.SRC.ca/", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "RemoveDefaultPort2", + "HTTP://www.SRC.ca:80", + FlagRemoveDefaultPort, + "http://www.SRC.ca", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "RemoveDefaultPort3", + "HTTP://www.SRC.ca:8080", + FlagRemoveDefaultPort, + "http://www.SRC.ca:8080", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "Safe", + "HTTP://www.SRC.ca:80/to%1ato%8b%ee/OKnow%41%42%43%7e", + FlagsSafe, + "http://www.src.ca/to%1Ato%8B%EE/OKnowABC~", + false, + }, + &testCase{ + "BothLower", + "HTTP://www.SRC.ca:80/to%1ato%8b%ee/OKnow%41%42%43%7e", + FlagLowercaseHost | FlagLowercaseScheme, + "http://www.src.ca:80/to%1Ato%8B%EE/OKnowABC~", + false, + }, + &testCase{ + "RemoveTrailingSlash", + "HTTP://www.SRC.ca:80/", + FlagRemoveTrailingSlash, + "http://www.SRC.ca:80", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "RemoveTrailingSlash2", + "HTTP://www.SRC.ca:80/toto/titi/", + FlagRemoveTrailingSlash, + "http://www.SRC.ca:80/toto/titi", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "RemoveTrailingSlash3", + "HTTP://www.SRC.ca:80/toto/titi/fin/?a=1", + FlagRemoveTrailingSlash, + "http://www.SRC.ca:80/toto/titi/fin?a=1", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "AddTrailingSlash", + "HTTP://www.SRC.ca:80", + FlagAddTrailingSlash, + "http://www.SRC.ca:80/", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "AddTrailingSlash2", + "HTTP://www.SRC.ca:80/toto/titi.html", + FlagAddTrailingSlash, + "http://www.SRC.ca:80/toto/titi.html/", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "AddTrailingSlash3", + "HTTP://www.SRC.ca:80/toto/titi/fin?a=1", + FlagAddTrailingSlash, + "http://www.SRC.ca:80/toto/titi/fin/?a=1", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "RemoveDotSegments", + "HTTP://root/a/b/./../../c/", + FlagRemoveDotSegments, + "http://root/c/", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "RemoveDotSegments2", + "HTTP://root/../a/b/./../c/../d", + FlagRemoveDotSegments, + "http://root/a/d", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "UsuallySafe", + "HTTP://www.SRC.ca:80/to%1ato%8b%ee/./c/d/../OKnow%41%42%43%7e/?a=b#test", + FlagsUsuallySafeGreedy, + "http://www.src.ca/to%1Ato%8B%EE/c/OKnowABC~?a=b#test", + false, + }, + &testCase{ + "RemoveDirectoryIndex", + "HTTP://root/a/b/c/default.aspx", + FlagRemoveDirectoryIndex, + "http://root/a/b/c/", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "RemoveDirectoryIndex2", + "HTTP://root/a/b/c/default#a=b", + FlagRemoveDirectoryIndex, + "http://root/a/b/c/default#a=b", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "RemoveFragment", + "HTTP://root/a/b/c/default#toto=tata", + FlagRemoveFragment, + "http://root/a/b/c/default", // Since Go1.1, scheme is automatically lowercased + false, + }, + &testCase{ + "ForceHTTP", + "https://root/a/b/c/default#toto=tata", + FlagForceHTTP, + "http://root/a/b/c/default#toto=tata", + false, + }, + &testCase{ + "RemoveDuplicateSlashes", + "https://root/a//b///c////default#toto=tata", + FlagRemoveDuplicateSlashes, + "https://root/a/b/c/default#toto=tata", + false, + }, + &testCase{ + "RemoveDuplicateSlashes2", + "https://root//a//b///c////default#toto=tata", + FlagRemoveDuplicateSlashes, + "https://root/a/b/c/default#toto=tata", + false, + }, + &testCase{ + "RemoveWWW", + "https://www.root/a/b/c/", + FlagRemoveWWW, + "https://root/a/b/c/", + false, + }, + &testCase{ + "RemoveWWW2", + "https://WwW.Root/a/b/c/", + FlagRemoveWWW, + "https://Root/a/b/c/", + false, + }, + &testCase{ + "AddWWW", + "https://Root/a/b/c/", + FlagAddWWW, + "https://www.Root/a/b/c/", + false, + }, + &testCase{ + "SortQuery", + "http://root/toto/?b=4&a=1&c=3&b=2&a=5", + FlagSortQuery, + "http://root/toto/?a=1&a=5&b=2&b=4&c=3", + false, + }, + &testCase{ + "RemoveEmptyQuerySeparator", + "http://root/toto/?", + FlagRemoveEmptyQuerySeparator, + "http://root/toto/", + false, + }, + &testCase{ + "Unsafe", + "HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid", + FlagsUnsafeGreedy, + "http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3", + false, + }, + &testCase{ + "Safe2", + "HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid", + FlagsSafe, + "https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid", + false, + }, + &testCase{ + "UsuallySafe2", + "HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid", + FlagsUsuallySafeGreedy, + "https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid", + false, + }, + &testCase{ + "AddTrailingSlashBug", + "http://src.ca/", + FlagsAllNonGreedy, + "http://www.src.ca/", + false, + }, + &testCase{ + "SourceModified", + "HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid", + FlagsUnsafeGreedy, + "http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3", + true, + }, + &testCase{ + "IPv6-1", + "http://[2001:db8:1f70::999:de8:7648:6e8]/test", + FlagsSafe | FlagRemoveDotSegments, + "http://[2001:db8:1f70::999:de8:7648:6e8]/test", + false, + }, + &testCase{ + "IPv6-2", + "http://[::ffff:192.168.1.1]/test", + FlagsSafe | FlagRemoveDotSegments, + "http://[::ffff:192.168.1.1]/test", + false, + }, + &testCase{ + "IPv6-3", + "http://[::ffff:192.168.1.1]:80/test", + FlagsSafe | FlagRemoveDotSegments, + "http://[::ffff:192.168.1.1]/test", + false, + }, + &testCase{ + "IPv6-4", + "htTps://[::fFff:192.168.1.1]:443/test", + FlagsSafe | FlagRemoveDotSegments, + "https://[::ffff:192.168.1.1]/test", + false, + }, + &testCase{ + "FTP", + "ftp://user:pass@ftp.foo.net/foo/bar", + FlagsSafe | FlagRemoveDotSegments, + "ftp://user:pass@ftp.foo.net/foo/bar", + false, + }, + &testCase{ + "Standard-1", + "http://www.foo.com:80/foo", + FlagsSafe | FlagRemoveDotSegments, + "http://www.foo.com/foo", + false, + }, + &testCase{ + "Standard-2", + "http://www.foo.com:8000/foo", + FlagsSafe | FlagRemoveDotSegments, + "http://www.foo.com:8000/foo", + false, + }, + &testCase{ + "Standard-3", + "http://www.foo.com/%7ebar", + FlagsSafe | FlagRemoveDotSegments, + "http://www.foo.com/~bar", + false, + }, + &testCase{ + "Standard-4", + "http://www.foo.com/%7Ebar", + FlagsSafe | FlagRemoveDotSegments, + "http://www.foo.com/~bar", + false, + }, + &testCase{ + "Standard-5", + "http://USER:pass@www.Example.COM/foo/bar", + FlagsSafe | FlagRemoveDotSegments, + "http://USER:pass@www.example.com/foo/bar", + false, + }, + &testCase{ + "Standard-6", + "http://test.example/?a=%26&b=1", + FlagsSafe | FlagRemoveDotSegments, + "http://test.example/?a=%26&b=1", + false, + }, + &testCase{ + "Standard-7", + "http://test.example/%25/?p=%20val%20%25", + FlagsSafe | FlagRemoveDotSegments, + "http://test.example/%25/?p=%20val%20%25", + false, + }, + &testCase{ + "Standard-8", + "http://test.example/path/with a%20space+/", + FlagsSafe | FlagRemoveDotSegments, + "http://test.example/path/with%20a%20space+/", + false, + }, + &testCase{ + "Standard-9", + "http://test.example/?", + FlagsSafe | FlagRemoveDotSegments, + "http://test.example/", + false, + }, + &testCase{ + "Standard-10", + "http://a.COM/path/?b&a", + FlagsSafe | FlagRemoveDotSegments, + "http://a.com/path/?b&a", + false, + }, + &testCase{ + "StandardCasesAddTrailingSlash", + "http://test.example?", + FlagsSafe | FlagAddTrailingSlash, + "http://test.example/", + false, + }, + &testCase{ + "OctalIP-1", + "http://0123.011.0.4/", + FlagsSafe | FlagDecodeOctalHost, + "http://0123.011.0.4/", + false, + }, + &testCase{ + "OctalIP-2", + "http://0102.0146.07.0223/", + FlagsSafe | FlagDecodeOctalHost, + "http://66.102.7.147/", + false, + }, + &testCase{ + "OctalIP-3", + "http://0102.0146.07.0223.:23/", + FlagsSafe | FlagDecodeOctalHost, + "http://66.102.7.147.:23/", + false, + }, + &testCase{ + "OctalIP-4", + "http://USER:pass@0102.0146.07.0223../", + FlagsSafe | FlagDecodeOctalHost, + "http://USER:pass@66.102.7.147../", + false, + }, + &testCase{ + "DWORDIP-1", + "http://123.1113982867/", + FlagsSafe | FlagDecodeDWORDHost, + "http://123.1113982867/", + false, + }, + &testCase{ + "DWORDIP-2", + "http://1113982867/", + FlagsSafe | FlagDecodeDWORDHost, + "http://66.102.7.147/", + false, + }, + &testCase{ + "DWORDIP-3", + "http://1113982867.:23/", + FlagsSafe | FlagDecodeDWORDHost, + "http://66.102.7.147.:23/", + false, + }, + &testCase{ + "DWORDIP-4", + "http://USER:pass@1113982867../", + FlagsSafe | FlagDecodeDWORDHost, + "http://USER:pass@66.102.7.147../", + false, + }, + &testCase{ + "HexIP-1", + "http://0x123.1113982867/", + FlagsSafe | FlagDecodeHexHost, + "http://0x123.1113982867/", + false, + }, + &testCase{ + "HexIP-2", + "http://0x42660793/", + FlagsSafe | FlagDecodeHexHost, + "http://66.102.7.147/", + false, + }, + &testCase{ + "HexIP-3", + "http://0x42660793.:23/", + FlagsSafe | FlagDecodeHexHost, + "http://66.102.7.147.:23/", + false, + }, + &testCase{ + "HexIP-4", + "http://USER:pass@0x42660793../", + FlagsSafe | FlagDecodeHexHost, + "http://USER:pass@66.102.7.147../", + false, + }, + &testCase{ + "UnnecessaryHostDots-1", + "http://.www.foo.com../foo/bar.html", + FlagsSafe | FlagRemoveUnnecessaryHostDots, + "http://www.foo.com/foo/bar.html", + false, + }, + &testCase{ + "UnnecessaryHostDots-2", + "http://www.foo.com./foo/bar.html", + FlagsSafe | FlagRemoveUnnecessaryHostDots, + "http://www.foo.com/foo/bar.html", + false, + }, + &testCase{ + "UnnecessaryHostDots-3", + "http://www.foo.com.:81/foo", + FlagsSafe | FlagRemoveUnnecessaryHostDots, + "http://www.foo.com:81/foo", + false, + }, + &testCase{ + "UnnecessaryHostDots-4", + "http://www.example.com./", + FlagsSafe | FlagRemoveUnnecessaryHostDots, + "http://www.example.com/", + false, + }, + &testCase{ + "EmptyPort-1", + "http://www.thedraymin.co.uk:/main/?p=308", + FlagsSafe | FlagRemoveEmptyPortSeparator, + "http://www.thedraymin.co.uk/main/?p=308", + false, + }, + &testCase{ + "EmptyPort-2", + "http://www.src.ca:", + FlagsSafe | FlagRemoveEmptyPortSeparator, + "http://www.src.ca", + false, + }, + &testCase{ + "Slashes-1", + "http://test.example/foo/bar/.", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo/bar/", + false, + }, + &testCase{ + "Slashes-2", + "http://test.example/foo/bar/./", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo/bar/", + false, + }, + &testCase{ + "Slashes-3", + "http://test.example/foo/bar/..", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo/", + false, + }, + &testCase{ + "Slashes-4", + "http://test.example/foo/bar/../", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo/", + false, + }, + &testCase{ + "Slashes-5", + "http://test.example/foo/bar/../baz", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo/baz", + false, + }, + &testCase{ + "Slashes-6", + "http://test.example/foo/bar/../..", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/", + false, + }, + &testCase{ + "Slashes-7", + "http://test.example/foo/bar/../../", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/", + false, + }, + &testCase{ + "Slashes-8", + "http://test.example/foo/bar/../../baz", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/baz", + false, + }, + &testCase{ + "Slashes-9", + "http://test.example/foo/bar/../../../baz", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/baz", + false, + }, + &testCase{ + "Slashes-10", + "http://test.example/foo/bar/../../../../baz", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/baz", + false, + }, + &testCase{ + "Slashes-11", + "http://test.example/./foo", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo", + false, + }, + &testCase{ + "Slashes-12", + "http://test.example/../foo", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo", + false, + }, + &testCase{ + "Slashes-13", + "http://test.example/foo.", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo.", + false, + }, + &testCase{ + "Slashes-14", + "http://test.example/.foo", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/.foo", + false, + }, + &testCase{ + "Slashes-15", + "http://test.example/foo..", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo..", + false, + }, + &testCase{ + "Slashes-16", + "http://test.example/..foo", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/..foo", + false, + }, + &testCase{ + "Slashes-17", + "http://test.example/./../foo", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo", + false, + }, + &testCase{ + "Slashes-18", + "http://test.example/./foo/.", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo/", + false, + }, + &testCase{ + "Slashes-19", + "http://test.example/foo/./bar", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo/bar", + false, + }, + &testCase{ + "Slashes-20", + "http://test.example/foo/../bar", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/bar", + false, + }, + &testCase{ + "Slashes-21", + "http://test.example/foo//", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo/", + false, + }, + &testCase{ + "Slashes-22", + "http://test.example/foo///bar//", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "http://test.example/foo/bar/", + false, + }, + &testCase{ + "Relative", + "foo/bar", + FlagsAllGreedy, + "foo/bar", + false, + }, + &testCase{ + "Relative-1", + "./../foo", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "foo", + false, + }, + &testCase{ + "Relative-2", + "./foo/bar/../baz/../bang/..", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "foo/", + false, + }, + &testCase{ + "Relative-3", + "foo///bar//", + FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes, + "foo/bar/", + false, + }, + &testCase{ + "Relative-4", + "www.youtube.com", + FlagsUsuallySafeGreedy, + "www.youtube.com", + false, + }, + /*&testCase{ + "UrlNorm-5", + "http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3", + FlagsSafe | FlagRemoveDotSegments, + "http://ja.wikipedia.org/wiki/\xe3\x82\xad\xe3\x83\xa3\xe3\x82\xbf\xe3\x83\x94\xe3\x83\xa9\xe3\x83\xbc\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\x91\xe3\x83\xb3", + false, + }, + &testCase{ + "UrlNorm-1", + "http://test.example/?a=%e3%82%82%26", + FlagsAllGreedy, + "http://test.example/?a=\xe3\x82\x82%26", + false, + },*/ + } +) + +func TestRunner(t *testing.T) { + for _, tc := range cases { + runCase(tc, t) + } +} + +func runCase(tc *testCase, t *testing.T) { + t.Logf("running %s...", tc.nm) + if tc.parsed { + u, e := url.Parse(tc.src) + if e != nil { + t.Errorf("%s - FAIL : %s", tc.nm, e) + return + } else { + NormalizeURL(u, tc.flgs) + if s := u.String(); s != tc.res { + t.Errorf("%s - FAIL expected '%s', got '%s'", tc.nm, tc.res, s) + } + } + } else { + if s, e := NormalizeURLString(tc.src, tc.flgs); e != nil { + t.Errorf("%s - FAIL : %s", tc.nm, e) + } else if s != tc.res { + t.Errorf("%s - FAIL expected '%s', got '%s'", tc.nm, tc.res, s) + } + } +} + +func TestDecodeUnnecessaryEscapesAll(t *testing.T) { + var url = "http://host/" + + for i := 0; i < 256; i++ { + url += fmt.Sprintf("%%%02x", i) + } + if s, e := NormalizeURLString(url, FlagDecodeUnnecessaryEscapes); e != nil { + t.Fatalf("Got error %s", e.Error()) + } else { + const want = "http://host/%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22%23$%25&'()*+,-./0123456789:;%3C=%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ[%5C]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF" + if s != want { + t.Errorf("DecodeUnnecessaryEscapesAll:\nwant\n%s\ngot\n%s", want, s) + } + } +} + +func TestEncodeNecessaryEscapesAll(t *testing.T) { + var url = "http://host/" + + for i := 0; i < 256; i++ { + if i != 0x25 { + url += string(i) + } + } + if s, e := NormalizeURLString(url, FlagEncodeNecessaryEscapes); e != nil { + t.Fatalf("Got error %s", e.Error()) + } else { + const want = "http://host/%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22#$&'()*+,-./0123456789:;%3C=%3E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[%5C]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%C2%80%C2%81%C2%82%C2%83%C2%84%C2%85%C2%86%C2%87%C2%88%C2%89%C2%8A%C2%8B%C2%8C%C2%8D%C2%8E%C2%8F%C2%90%C2%91%C2%92%C2%93%C2%94%C2%95%C2%96%C2%97%C2%98%C2%99%C2%9A%C2%9B%C2%9C%C2%9D%C2%9E%C2%9F%C2%A0%C2%A1%C2%A2%C2%A3%C2%A4%C2%A5%C2%A6%C2%A7%C2%A8%C2%A9%C2%AA%C2%AB%C2%AC%C2%AD%C2%AE%C2%AF%C2%B0%C2%B1%C2%B2%C2%B3%C2%B4%C2%B5%C2%B6%C2%B7%C2%B8%C2%B9%C2%BA%C2%BB%C2%BC%C2%BD%C2%BE%C2%BF%C3%80%C3%81%C3%82%C3%83%C3%84%C3%85%C3%86%C3%87%C3%88%C3%89%C3%8A%C3%8B%C3%8C%C3%8D%C3%8E%C3%8F%C3%90%C3%91%C3%92%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%9B%C3%9C%C3%9D%C3%9E%C3%9F%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%A8%C3%A9%C3%AA%C3%AB%C3%AC%C3%AD%C3%AE%C3%AF%C3%B0%C3%B1%C3%B2%C3%B3%C3%B4%C3%B5%C3%B6%C3%B7%C3%B8%C3%B9%C3%BA%C3%BB%C3%BC%C3%BD%C3%BE%C3%BF" + if s != want { + t.Errorf("EncodeNecessaryEscapesAll:\nwant\n%s\ngot\n%s", want, s) + } + } +} diff --git a/vendor/github.com/PuerkitoBio/purell/urlnorm_test.go b/vendor/github.com/PuerkitoBio/purell/urlnorm_test.go new file mode 100644 index 000000000..a598fe928 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/urlnorm_test.go @@ -0,0 +1,52 @@ +package purell + +import ( + "testing" +) + +// Test cases merged from PR #1 +// Originally from https://github.com/jehiah/urlnorm/blob/master/test_urlnorm.py + +func assertMap(t *testing.T, cases map[string]string, f NormalizationFlags) { + for bad, good := range cases { + s, e := NormalizeURLString(bad, f) + if e != nil { + t.Errorf("%s normalizing %v to %v", e.Error(), bad, good) + } else { + if s != good { + t.Errorf("source: %v expected: %v got: %v", bad, good, s) + } + } + } +} + +// This tests normalization to a unicode representation +// precent escapes for unreserved values are unescaped to their unicode value +// tests normalization to idna domains +// test ip word handling, ipv6 address handling, and trailing domain periods +// in general, this matches google chromes unescaping for things in the address bar. +// spaces are converted to '+' (perhaphs controversial) +// http://code.google.com/p/google-url/ probably is another good reference for this approach +func TestUrlnorm(t *testing.T) { + testcases := map[string]string{ + "http://test.example/?a=%e3%82%82%26": "http://test.example/?a=%e3%82%82%26", + //"http://test.example/?a=%e3%82%82%26": "http://test.example/?a=\xe3\x82\x82%26", //should return a unicode character + "http://s.xn--q-bga.DE/": "http://s.xn--q-bga.de/", //should be in idna format + "http://XBLA\u306eXbox.com": "http://xn--xblaxbox-jf4g.com", //test utf8 and unicode + "http://президент.рф": "http://xn--d1abbgf6aiiy.xn--p1ai", + "http://ПРЕЗИДЕНТ.РФ": "http://xn--d1abbgf6aiiy.xn--p1ai", + "http://\u00e9.com": "http://xn--9ca.com", + "http://e\u0301.com": "http://xn--9ca.com", + "http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3": "http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3", + //"http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3": "http://ja.wikipedia.org/wiki/\xe3\x82\xad\xe3\x83\xa3\xe3\x82\xbf\xe3\x83\x94\xe3\x83\xa9\xe3\x83\xbc\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\x91\xe3\x83\xb3", + + "http://test.example/\xe3\x82\xad": "http://test.example/%E3%82%AD", + //"http://test.example/\xe3\x82\xad": "http://test.example/\xe3\x82\xad", + "http://test.example/?p=%23val#test-%23-val%25": "http://test.example/?p=%23val#test-%23-val%25", //check that %23 (#) is not escaped where it shouldn't be + + "http://test.domain/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%EF%BF%BDliz%C3%A6ti%C3%B8n": "http://test.domain/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%EF%BF%BDliz%C3%A6ti%C3%B8n", + //"http://test.domain/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%EF%BF%BDliz%C3%A6ti%C3%B8n": "http://test.domain/I\xc3\xb1t\xc3\xabrn\xc3\xa2ti\xc3\xb4n\xef\xbf\xbdliz\xc3\xa6ti\xc3\xb8n", + } + + assertMap(t, testcases, FlagsSafe|FlagRemoveDotSegments) +} diff --git a/vendor/github.com/PuerkitoBio/urlesc/.travis.yml b/vendor/github.com/PuerkitoBio/urlesc/.travis.yml new file mode 100644 index 000000000..478630e50 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/urlesc/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - 1.4 + - tip + +install: + - go build . + +script: + - go test -v diff --git a/vendor/github.com/PuerkitoBio/urlesc/LICENSE b/vendor/github.com/PuerkitoBio/urlesc/LICENSE new file mode 100644 index 000000000..744875676 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/urlesc/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/PuerkitoBio/urlesc/README.md b/vendor/github.com/PuerkitoBio/urlesc/README.md new file mode 100644 index 000000000..bebe305e0 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/urlesc/README.md @@ -0,0 +1,16 @@ +urlesc [![Build Status](https://travis-ci.org/PuerkitoBio/urlesc.png?branch=master)](https://travis-ci.org/PuerkitoBio/urlesc) [![GoDoc](http://godoc.org/github.com/PuerkitoBio/urlesc?status.svg)](http://godoc.org/github.com/PuerkitoBio/urlesc) +====== + +Package urlesc implements query escaping as per RFC 3986. + +It contains some parts of the net/url package, modified so as to allow +some reserved characters incorrectly escaped by net/url (see [issue 5684](https://github.com/golang/go/issues/5684)). + +## Install + + go get github.com/PuerkitoBio/urlesc + +## License + +Go license (BSD-3-Clause) + diff --git a/vendor/github.com/PuerkitoBio/urlesc/urlesc.go b/vendor/github.com/PuerkitoBio/urlesc/urlesc.go new file mode 100644 index 000000000..1b8462459 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/urlesc/urlesc.go @@ -0,0 +1,180 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package urlesc implements query escaping as per RFC 3986. +// It contains some parts of the net/url package, modified so as to allow +// some reserved characters incorrectly escaped by net/url. +// See https://github.com/golang/go/issues/5684 +package urlesc + +import ( + "bytes" + "net/url" + "strings" +) + +type encoding int + +const ( + encodePath encoding = 1 + iota + encodeUserPassword + encodeQueryComponent + encodeFragment +) + +// Return true if the specified character should be escaped when +// appearing in a URL string, according to RFC 3986. +func shouldEscape(c byte, mode encoding) bool { + // §2.3 Unreserved characters (alphanum) + if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' { + return false + } + + switch c { + case '-', '.', '_', '~': // §2.3 Unreserved characters (mark) + return false + + // §2.2 Reserved characters (reserved) + case ':', '/', '?', '#', '[', ']', '@', // gen-delims + '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims + // Different sections of the URL allow a few of + // the reserved characters to appear unescaped. + switch mode { + case encodePath: // §3.3 + // The RFC allows sub-delims and : @. + // '/', '[' and ']' can be used to assign meaning to individual path + // segments. This package only manipulates the path as a whole, + // so we allow those as well. That leaves only ? and # to escape. + return c == '?' || c == '#' + + case encodeUserPassword: // §3.2.1 + // The RFC allows : and sub-delims in + // userinfo. The parsing of userinfo treats ':' as special so we must escape + // all the gen-delims. + return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@' + + case encodeQueryComponent: // §3.4 + // The RFC allows / and ?. + return c != '/' && c != '?' + + case encodeFragment: // §4.1 + // The RFC text is silent but the grammar allows + // everything, so escape nothing but # + return c == '#' + } + } + + // Everything else must be escaped. + return true +} + +// QueryEscape escapes the string so it can be safely placed +// inside a URL query. +func QueryEscape(s string) string { + return escape(s, encodeQueryComponent) +} + +func escape(s string, mode encoding) string { + spaceCount, hexCount := 0, 0 + for i := 0; i < len(s); i++ { + c := s[i] + if shouldEscape(c, mode) { + if c == ' ' && mode == encodeQueryComponent { + spaceCount++ + } else { + hexCount++ + } + } + } + + if spaceCount == 0 && hexCount == 0 { + return s + } + + t := make([]byte, len(s)+2*hexCount) + j := 0 + for i := 0; i < len(s); i++ { + switch c := s[i]; { + case c == ' ' && mode == encodeQueryComponent: + t[j] = '+' + j++ + case shouldEscape(c, mode): + t[j] = '%' + t[j+1] = "0123456789ABCDEF"[c>>4] + t[j+2] = "0123456789ABCDEF"[c&15] + j += 3 + default: + t[j] = s[i] + j++ + } + } + return string(t) +} + +var uiReplacer = strings.NewReplacer( + "%21", "!", + "%27", "'", + "%28", "(", + "%29", ")", + "%2A", "*", +) + +// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986. +func unescapeUserinfo(s string) string { + return uiReplacer.Replace(s) +} + +// Escape reassembles the URL into a valid URL string. +// The general form of the result is one of: +// +// scheme:opaque +// scheme://userinfo@host/path?query#fragment +// +// If u.Opaque is non-empty, String uses the first form; +// otherwise it uses the second form. +// +// In the second form, the following rules apply: +// - if u.Scheme is empty, scheme: is omitted. +// - if u.User is nil, userinfo@ is omitted. +// - if u.Host is empty, host/ is omitted. +// - if u.Scheme and u.Host are empty and u.User is nil, +// the entire scheme://userinfo@host/ is omitted. +// - if u.Host is non-empty and u.Path begins with a /, +// the form host/path does not add its own /. +// - if u.RawQuery is empty, ?query is omitted. +// - if u.Fragment is empty, #fragment is omitted. +func Escape(u *url.URL) string { + var buf bytes.Buffer + if u.Scheme != "" { + buf.WriteString(u.Scheme) + buf.WriteByte(':') + } + if u.Opaque != "" { + buf.WriteString(u.Opaque) + } else { + if u.Scheme != "" || u.Host != "" || u.User != nil { + buf.WriteString("//") + if ui := u.User; ui != nil { + buf.WriteString(unescapeUserinfo(ui.String())) + buf.WriteByte('@') + } + if h := u.Host; h != "" { + buf.WriteString(h) + } + } + if u.Path != "" && u.Path[0] != '/' && u.Host != "" { + buf.WriteByte('/') + } + buf.WriteString(escape(u.Path, encodePath)) + } + if u.RawQuery != "" { + buf.WriteByte('?') + buf.WriteString(u.RawQuery) + } + if u.Fragment != "" { + buf.WriteByte('#') + buf.WriteString(escape(u.Fragment, encodeFragment)) + } + return buf.String() +} diff --git a/vendor/github.com/PuerkitoBio/urlesc/urlesc_test.go b/vendor/github.com/PuerkitoBio/urlesc/urlesc_test.go new file mode 100644 index 000000000..45202e1dd --- /dev/null +++ b/vendor/github.com/PuerkitoBio/urlesc/urlesc_test.go @@ -0,0 +1,641 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package urlesc + +import ( + "net/url" + "testing" +) + +type URLTest struct { + in string + out *url.URL + roundtrip string // expected result of reserializing the URL; empty means same as "in". +} + +var urltests = []URLTest{ + // no path + { + "http://www.google.com", + &url.URL{ + Scheme: "http", + Host: "www.google.com", + }, + "", + }, + // path + { + "http://www.google.com/", + &url.URL{ + Scheme: "http", + Host: "www.google.com", + Path: "/", + }, + "", + }, + // path with hex escaping + { + "http://www.google.com/file%20one%26two", + &url.URL{ + Scheme: "http", + Host: "www.google.com", + Path: "/file one&two", + }, + "http://www.google.com/file%20one&two", + }, + // user + { + "ftp://webmaster@www.google.com/", + &url.URL{ + Scheme: "ftp", + User: url.User("webmaster"), + Host: "www.google.com", + Path: "/", + }, + "", + }, + // escape sequence in username + { + "ftp://john%20doe@www.google.com/", + &url.URL{ + Scheme: "ftp", + User: url.User("john doe"), + Host: "www.google.com", + Path: "/", + }, + "ftp://john%20doe@www.google.com/", + }, + // query + { + "http://www.google.com/?q=go+language", + &url.URL{ + Scheme: "http", + Host: "www.google.com", + Path: "/", + RawQuery: "q=go+language", + }, + "", + }, + // query with hex escaping: NOT parsed + { + "http://www.google.com/?q=go%20language", + &url.URL{ + Scheme: "http", + Host: "www.google.com", + Path: "/", + RawQuery: "q=go%20language", + }, + "", + }, + // %20 outside query + { + "http://www.google.com/a%20b?q=c+d", + &url.URL{ + Scheme: "http", + Host: "www.google.com", + Path: "/a b", + RawQuery: "q=c+d", + }, + "", + }, + // path without leading /, so no parsing + { + "http:www.google.com/?q=go+language", + &url.URL{ + Scheme: "http", + Opaque: "www.google.com/", + RawQuery: "q=go+language", + }, + "http:www.google.com/?q=go+language", + }, + // path without leading /, so no parsing + { + "http:%2f%2fwww.google.com/?q=go+language", + &url.URL{ + Scheme: "http", + Opaque: "%2f%2fwww.google.com/", + RawQuery: "q=go+language", + }, + "http:%2f%2fwww.google.com/?q=go+language", + }, + // non-authority with path + { + "mailto:/webmaster@golang.org", + &url.URL{ + Scheme: "mailto", + Path: "/webmaster@golang.org", + }, + "mailto:///webmaster@golang.org", // unfortunate compromise + }, + // non-authority + { + "mailto:webmaster@golang.org", + &url.URL{ + Scheme: "mailto", + Opaque: "webmaster@golang.org", + }, + "", + }, + // unescaped :// in query should not create a scheme + { + "/foo?query=http://bad", + &url.URL{ + Path: "/foo", + RawQuery: "query=http://bad", + }, + "", + }, + // leading // without scheme should create an authority + { + "//foo", + &url.URL{ + Host: "foo", + }, + "", + }, + // leading // without scheme, with userinfo, path, and query + { + "//user@foo/path?a=b", + &url.URL{ + User: url.User("user"), + Host: "foo", + Path: "/path", + RawQuery: "a=b", + }, + "", + }, + // Three leading slashes isn't an authority, but doesn't return an error. + // (We can't return an error, as this code is also used via + // ServeHTTP -> ReadRequest -> Parse, which is arguably a + // different URL parsing context, but currently shares the + // same codepath) + { + "///threeslashes", + &url.URL{ + Path: "///threeslashes", + }, + "", + }, + { + "http://user:password@google.com", + &url.URL{ + Scheme: "http", + User: url.UserPassword("user", "password"), + Host: "google.com", + }, + "http://user:password@google.com", + }, + // unescaped @ in username should not confuse host + { + "http://j@ne:password@google.com", + &url.URL{ + Scheme: "http", + User: url.UserPassword("j@ne", "password"), + Host: "google.com", + }, + "http://j%40ne:password@google.com", + }, + // unescaped @ in password should not confuse host + { + "http://jane:p@ssword@google.com", + &url.URL{ + Scheme: "http", + User: url.UserPassword("jane", "p@ssword"), + Host: "google.com", + }, + "http://jane:p%40ssword@google.com", + }, + { + "http://j@ne:password@google.com/p@th?q=@go", + &url.URL{ + Scheme: "http", + User: url.UserPassword("j@ne", "password"), + Host: "google.com", + Path: "/p@th", + RawQuery: "q=@go", + }, + "http://j%40ne:password@google.com/p@th?q=@go", + }, + { + "http://www.google.com/?q=go+language#foo", + &url.URL{ + Scheme: "http", + Host: "www.google.com", + Path: "/", + RawQuery: "q=go+language", + Fragment: "foo", + }, + "", + }, + { + "http://www.google.com/?q=go+language#foo%26bar", + &url.URL{ + Scheme: "http", + Host: "www.google.com", + Path: "/", + RawQuery: "q=go+language", + Fragment: "foo&bar", + }, + "http://www.google.com/?q=go+language#foo&bar", + }, + { + "file:///home/adg/rabbits", + &url.URL{ + Scheme: "file", + Host: "", + Path: "/home/adg/rabbits", + }, + "file:///home/adg/rabbits", + }, + // "Windows" paths are no exception to the rule. + // See golang.org/issue/6027, especially comment #9. + { + "file:///C:/FooBar/Baz.txt", + &url.URL{ + Scheme: "file", + Host: "", + Path: "/C:/FooBar/Baz.txt", + }, + "file:///C:/FooBar/Baz.txt", + }, + // case-insensitive scheme + { + "MaIlTo:webmaster@golang.org", + &url.URL{ + Scheme: "mailto", + Opaque: "webmaster@golang.org", + }, + "mailto:webmaster@golang.org", + }, + // Relative path + { + "a/b/c", + &url.URL{ + Path: "a/b/c", + }, + "a/b/c", + }, + // escaped '?' in username and password + { + "http://%3Fam:pa%3Fsword@google.com", + &url.URL{ + Scheme: "http", + User: url.UserPassword("?am", "pa?sword"), + Host: "google.com", + }, + "", + }, + // escaped '?' and '#' in path + { + "http://example.com/%3F%23", + &url.URL{ + Scheme: "http", + Host: "example.com", + Path: "?#", + }, + "", + }, + // unescaped [ ] ! ' ( ) * in path + { + "http://example.com/[]!'()*", + &url.URL{ + Scheme: "http", + Host: "example.com", + Path: "[]!'()*", + }, + "http://example.com/[]!'()*", + }, + // escaped : / ? # [ ] @ in username and password + { + "http://%3A%2F%3F:%23%5B%5D%40@example.com", + &url.URL{ + Scheme: "http", + User: url.UserPassword(":/?", "#[]@"), + Host: "example.com", + }, + "", + }, + // unescaped ! $ & ' ( ) * + , ; = in username and password + { + "http://!$&'():*+,;=@example.com", + &url.URL{ + Scheme: "http", + User: url.UserPassword("!$&'()", "*+,;="), + Host: "example.com", + }, + "", + }, + // unescaped = : / . ? = in query component + { + "http://example.com/?q=http://google.com/?q=", + &url.URL{ + Scheme: "http", + Host: "example.com", + Path: "/", + RawQuery: "q=http://google.com/?q=", + }, + "", + }, + // unescaped : / ? [ ] @ ! $ & ' ( ) * + , ; = in fragment + { + "http://example.com/#:/?%23[]@!$&'()*+,;=", + &url.URL{ + Scheme: "http", + Host: "example.com", + Path: "/", + Fragment: ":/?#[]@!$&'()*+,;=", + }, + "", + }, +} + +func DoTestString(t *testing.T, parse func(string) (*url.URL, error), name string, tests []URLTest) { + for _, tt := range tests { + u, err := parse(tt.in) + if err != nil { + t.Errorf("%s(%q) returned error %s", name, tt.in, err) + continue + } + expected := tt.in + if len(tt.roundtrip) > 0 { + expected = tt.roundtrip + } + s := Escape(u) + if s != expected { + t.Errorf("Escape(%s(%q)) == %q (expected %q)", name, tt.in, s, expected) + } + } +} + +func TestURLString(t *testing.T) { + DoTestString(t, url.Parse, "Parse", urltests) + + // no leading slash on path should prepend + // slash on String() call + noslash := URLTest{ + "http://www.google.com/search", + &url.URL{ + Scheme: "http", + Host: "www.google.com", + Path: "search", + }, + "", + } + s := Escape(noslash.out) + if s != noslash.in { + t.Errorf("Expected %s; go %s", noslash.in, s) + } +} + +type EscapeTest struct { + in string + out string + err error +} + +var escapeTests = []EscapeTest{ + { + "", + "", + nil, + }, + { + "abc", + "abc", + nil, + }, + { + "one two", + "one+two", + nil, + }, + { + "10%", + "10%25", + nil, + }, + { + " ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;", + "+?%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A/%40%24%27%28%29%2A%2C%3B", + nil, + }, +} + +func TestEscape(t *testing.T) { + for _, tt := range escapeTests { + actual := QueryEscape(tt.in) + if tt.out != actual { + t.Errorf("QueryEscape(%q) = %q, want %q", tt.in, actual, tt.out) + } + + // for bonus points, verify that escape:unescape is an identity. + roundtrip, err := url.QueryUnescape(actual) + if roundtrip != tt.in || err != nil { + t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]") + } + } +} + +var resolveReferenceTests = []struct { + base, rel, expected string +}{ + // Absolute URL references + {"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"}, + {"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"}, + {"http://foo.com/bar", "mailto:foo@example.com", "mailto:foo@example.com"}, + + // Path-absolute references + {"http://foo.com/bar", "/baz", "http://foo.com/baz"}, + {"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"}, + {"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"}, + + // Scheme-relative + {"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"}, + + // Path-relative references: + + // ... current directory + {"http://foo.com", ".", "http://foo.com/"}, + {"http://foo.com/bar", ".", "http://foo.com/"}, + {"http://foo.com/bar/", ".", "http://foo.com/bar/"}, + + // ... going down + {"http://foo.com", "bar", "http://foo.com/bar"}, + {"http://foo.com/", "bar", "http://foo.com/bar"}, + {"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"}, + + // ... going up + {"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"}, + {"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"}, + {"http://foo.com/bar", "..", "http://foo.com/"}, + {"http://foo.com/bar/baz", "./..", "http://foo.com/"}, + // ".." in the middle (issue 3560) + {"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"}, + {"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"}, + {"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"}, + {"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"}, + {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"}, + {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"}, + {"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"}, + {"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"}, + + // Remove any dot-segments prior to forming the target URI. + // http://tools.ietf.org/html/rfc3986#section-5.2.4 + {"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"}, + + // Triple dot isn't special + {"http://foo.com/bar", "...", "http://foo.com/..."}, + + // Fragment + {"http://foo.com/bar", ".#frag", "http://foo.com/#frag"}, + + // RFC 3986: Normal Examples + // http://tools.ietf.org/html/rfc3986#section-5.4.1 + {"http://a/b/c/d;p?q", "g:h", "g:h"}, + {"http://a/b/c/d;p?q", "g", "http://a/b/c/g"}, + {"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"}, + {"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"}, + {"http://a/b/c/d;p?q", "/g", "http://a/g"}, + {"http://a/b/c/d;p?q", "//g", "http://g"}, + {"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"}, + {"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"}, + {"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"}, + {"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"}, + {"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"}, + {"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"}, + {"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"}, + {"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"}, + {"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"}, + {"http://a/b/c/d;p?q", ".", "http://a/b/c/"}, + {"http://a/b/c/d;p?q", "./", "http://a/b/c/"}, + {"http://a/b/c/d;p?q", "..", "http://a/b/"}, + {"http://a/b/c/d;p?q", "../", "http://a/b/"}, + {"http://a/b/c/d;p?q", "../g", "http://a/b/g"}, + {"http://a/b/c/d;p?q", "../..", "http://a/"}, + {"http://a/b/c/d;p?q", "../../", "http://a/"}, + {"http://a/b/c/d;p?q", "../../g", "http://a/g"}, + + // RFC 3986: Abnormal Examples + // http://tools.ietf.org/html/rfc3986#section-5.4.2 + {"http://a/b/c/d;p?q", "../../../g", "http://a/g"}, + {"http://a/b/c/d;p?q", "../../../../g", "http://a/g"}, + {"http://a/b/c/d;p?q", "/./g", "http://a/g"}, + {"http://a/b/c/d;p?q", "/../g", "http://a/g"}, + {"http://a/b/c/d;p?q", "g.", "http://a/b/c/g."}, + {"http://a/b/c/d;p?q", ".g", "http://a/b/c/.g"}, + {"http://a/b/c/d;p?q", "g..", "http://a/b/c/g.."}, + {"http://a/b/c/d;p?q", "..g", "http://a/b/c/..g"}, + {"http://a/b/c/d;p?q", "./../g", "http://a/b/g"}, + {"http://a/b/c/d;p?q", "./g/.", "http://a/b/c/g/"}, + {"http://a/b/c/d;p?q", "g/./h", "http://a/b/c/g/h"}, + {"http://a/b/c/d;p?q", "g/../h", "http://a/b/c/h"}, + {"http://a/b/c/d;p?q", "g;x=1/./y", "http://a/b/c/g;x=1/y"}, + {"http://a/b/c/d;p?q", "g;x=1/../y", "http://a/b/c/y"}, + {"http://a/b/c/d;p?q", "g?y/./x", "http://a/b/c/g?y/./x"}, + {"http://a/b/c/d;p?q", "g?y/../x", "http://a/b/c/g?y/../x"}, + {"http://a/b/c/d;p?q", "g#s/./x", "http://a/b/c/g#s/./x"}, + {"http://a/b/c/d;p?q", "g#s/../x", "http://a/b/c/g#s/../x"}, + + // Extras. + {"https://a/b/c/d;p?q", "//g?q", "https://g?q"}, + {"https://a/b/c/d;p?q", "//g#s", "https://g#s"}, + {"https://a/b/c/d;p?q", "//g/d/e/f?y#s", "https://g/d/e/f?y#s"}, + {"https://a/b/c/d;p#s", "?y", "https://a/b/c/d;p?y"}, + {"https://a/b/c/d;p?q#s", "?y", "https://a/b/c/d;p?y"}, +} + +func TestResolveReference(t *testing.T) { + mustParse := func(url_ string) *url.URL { + u, err := url.Parse(url_) + if err != nil { + t.Fatalf("Expected URL to parse: %q, got error: %v", url_, err) + } + return u + } + opaque := &url.URL{Scheme: "scheme", Opaque: "opaque"} + for _, test := range resolveReferenceTests { + base := mustParse(test.base) + rel := mustParse(test.rel) + url := base.ResolveReference(rel) + if Escape(url) != test.expected { + t.Errorf("URL(%q).ResolveReference(%q) == %q, got %q", test.base, test.rel, test.expected, Escape(url)) + } + // Ensure that new instances are returned. + if base == url { + t.Errorf("Expected URL.ResolveReference to return new URL instance.") + } + // Test the convenience wrapper too. + url, err := base.Parse(test.rel) + if err != nil { + t.Errorf("URL(%q).Parse(%q) failed: %v", test.base, test.rel, err) + } else if Escape(url) != test.expected { + t.Errorf("URL(%q).Parse(%q) == %q, got %q", test.base, test.rel, test.expected, Escape(url)) + } else if base == url { + // Ensure that new instances are returned for the wrapper too. + t.Errorf("Expected URL.Parse to return new URL instance.") + } + // Ensure Opaque resets the URL. + url = base.ResolveReference(opaque) + if *url != *opaque { + t.Errorf("ResolveReference failed to resolve opaque URL: want %#v, got %#v", url, opaque) + } + // Test the convenience wrapper with an opaque URL too. + url, err = base.Parse("scheme:opaque") + if err != nil { + t.Errorf(`URL(%q).Parse("scheme:opaque") failed: %v`, test.base, err) + } else if *url != *opaque { + t.Errorf("Parse failed to resolve opaque URL: want %#v, got %#v", url, opaque) + } else if base == url { + // Ensure that new instances are returned, again. + t.Errorf("Expected URL.Parse to return new URL instance.") + } + } +} + +type shouldEscapeTest struct { + in byte + mode encoding + escape bool +} + +var shouldEscapeTests = []shouldEscapeTest{ + // Unreserved characters (§2.3) + {'a', encodePath, false}, + {'a', encodeUserPassword, false}, + {'a', encodeQueryComponent, false}, + {'a', encodeFragment, false}, + {'z', encodePath, false}, + {'A', encodePath, false}, + {'Z', encodePath, false}, + {'0', encodePath, false}, + {'9', encodePath, false}, + {'-', encodePath, false}, + {'-', encodeUserPassword, false}, + {'-', encodeQueryComponent, false}, + {'-', encodeFragment, false}, + {'.', encodePath, false}, + {'_', encodePath, false}, + {'~', encodePath, false}, + + // User information (§3.2.1) + {':', encodeUserPassword, true}, + {'/', encodeUserPassword, true}, + {'?', encodeUserPassword, true}, + {'@', encodeUserPassword, true}, + {'$', encodeUserPassword, false}, + {'&', encodeUserPassword, false}, + {'+', encodeUserPassword, false}, + {',', encodeUserPassword, false}, + {';', encodeUserPassword, false}, + {'=', encodeUserPassword, false}, +} + +func TestShouldEscape(t *testing.T) { + for _, tt := range shouldEscapeTests { + if shouldEscape(tt.in, tt.mode) != tt.escape { + t.Errorf("shouldEscape(%q, %v) returned %v; expected %v", tt.in, tt.mode, !tt.escape, tt.escape) + } + } +} diff --git a/vendor/github.com/beorn7/perks/.gitignore b/vendor/github.com/beorn7/perks/.gitignore new file mode 100644 index 000000000..1bd9209aa --- /dev/null +++ b/vendor/github.com/beorn7/perks/.gitignore @@ -0,0 +1,2 @@ +*.test +*.prof diff --git a/vendor/github.com/beorn7/perks/LICENSE b/vendor/github.com/beorn7/perks/LICENSE new file mode 100644 index 000000000..339177be6 --- /dev/null +++ b/vendor/github.com/beorn7/perks/LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2013 Blake Mizerany + +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. diff --git a/vendor/github.com/beorn7/perks/README.md b/vendor/github.com/beorn7/perks/README.md new file mode 100644 index 000000000..fc0577770 --- /dev/null +++ b/vendor/github.com/beorn7/perks/README.md @@ -0,0 +1,31 @@ +# Perks for Go (golang.org) + +Perks contains the Go package quantile that computes approximate quantiles over +an unbounded data stream within low memory and CPU bounds. + +For more information and examples, see: +http://godoc.org/github.com/bmizerany/perks + +A very special thank you and shout out to Graham Cormode (Rutgers University), +Flip Korn (AT&T Labs–Research), S. Muthukrishnan (Rutgers University), and +Divesh Srivastava (AT&T Labs–Research) for their research and publication of +[Effective Computation of Biased Quantiles over Data Streams](http://www.cs.rutgers.edu/~muthu/bquant.pdf) + +Thank you, also: +* Armon Dadgar (@armon) +* Andrew Gerrand (@nf) +* Brad Fitzpatrick (@bradfitz) +* Keith Rarick (@kr) + +FAQ: + +Q: Why not move the quantile package into the project root? +A: I want to add more packages to perks later. + +Copyright (C) 2013 Blake Mizerany + +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. diff --git a/vendor/github.com/beorn7/perks/histogram/bench_test.go b/vendor/github.com/beorn7/perks/histogram/bench_test.go new file mode 100644 index 000000000..56c7e5516 --- /dev/null +++ b/vendor/github.com/beorn7/perks/histogram/bench_test.go @@ -0,0 +1,26 @@ +package histogram + +import ( + "math/rand" + "testing" +) + +func BenchmarkInsert10Bins(b *testing.B) { + b.StopTimer() + h := New(10) + b.StartTimer() + for i := 0; i < b.N; i++ { + f := rand.ExpFloat64() + h.Insert(f) + } +} + +func BenchmarkInsert100Bins(b *testing.B) { + b.StopTimer() + h := New(100) + b.StartTimer() + for i := 0; i < b.N; i++ { + f := rand.ExpFloat64() + h.Insert(f) + } +} diff --git a/vendor/github.com/beorn7/perks/histogram/histogram.go b/vendor/github.com/beorn7/perks/histogram/histogram.go new file mode 100644 index 000000000..bef05c70c --- /dev/null +++ b/vendor/github.com/beorn7/perks/histogram/histogram.go @@ -0,0 +1,108 @@ +// Package histogram provides a Go implementation of BigML's histogram package +// for Clojure/Java. It is currently experimental. +package histogram + +import ( + "container/heap" + "math" + "sort" +) + +type Bin struct { + Count int + Sum float64 +} + +func (b *Bin) Update(x *Bin) { + b.Count += x.Count + b.Sum += x.Sum +} + +func (b *Bin) Mean() float64 { + return b.Sum / float64(b.Count) +} + +type Bins []*Bin + +func (bs Bins) Len() int { return len(bs) } +func (bs Bins) Less(i, j int) bool { return bs[i].Mean() < bs[j].Mean() } +func (bs Bins) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] } + +func (bs *Bins) Push(x interface{}) { + *bs = append(*bs, x.(*Bin)) +} + +func (bs *Bins) Pop() interface{} { + return bs.remove(len(*bs) - 1) +} + +func (bs *Bins) remove(n int) *Bin { + if n < 0 || len(*bs) < n { + return nil + } + x := (*bs)[n] + *bs = append((*bs)[:n], (*bs)[n+1:]...) + return x +} + +type Histogram struct { + res *reservoir +} + +func New(maxBins int) *Histogram { + return &Histogram{res: newReservoir(maxBins)} +} + +func (h *Histogram) Insert(f float64) { + h.res.insert(&Bin{1, f}) + h.res.compress() +} + +func (h *Histogram) Bins() Bins { + return h.res.bins +} + +type reservoir struct { + n int + maxBins int + bins Bins +} + +func newReservoir(maxBins int) *reservoir { + return &reservoir{maxBins: maxBins} +} + +func (r *reservoir) insert(bin *Bin) { + r.n += bin.Count + i := sort.Search(len(r.bins), func(i int) bool { + return r.bins[i].Mean() >= bin.Mean() + }) + if i < 0 || i == r.bins.Len() { + // TODO(blake): Maybe use an .insert(i, bin) instead of + // performing the extra work of a heap.Push. + heap.Push(&r.bins, bin) + return + } + r.bins[i].Update(bin) +} + +func (r *reservoir) compress() { + for r.bins.Len() > r.maxBins { + minGapIndex := -1 + minGap := math.MaxFloat64 + for i := 0; i < r.bins.Len()-1; i++ { + gap := gapWeight(r.bins[i], r.bins[i+1]) + if minGap > gap { + minGap = gap + minGapIndex = i + } + } + prev := r.bins[minGapIndex] + next := r.bins.remove(minGapIndex + 1) + prev.Update(next) + } +} + +func gapWeight(prev, next *Bin) float64 { + return next.Mean() - prev.Mean() +} diff --git a/vendor/github.com/beorn7/perks/histogram/histogram_test.go b/vendor/github.com/beorn7/perks/histogram/histogram_test.go new file mode 100644 index 000000000..0575ebeee --- /dev/null +++ b/vendor/github.com/beorn7/perks/histogram/histogram_test.go @@ -0,0 +1,38 @@ +package histogram + +import ( + "math/rand" + "testing" +) + +func TestHistogram(t *testing.T) { + const numPoints = 1e6 + const maxBins = 3 + + h := New(maxBins) + for i := 0; i < numPoints; i++ { + f := rand.ExpFloat64() + h.Insert(f) + } + + bins := h.Bins() + if g := len(bins); g > maxBins { + t.Fatalf("got %d bins, wanted <= %d", g, maxBins) + } + + for _, b := range bins { + t.Logf("%+v", b) + } + + if g := count(h.Bins()); g != numPoints { + t.Fatalf("binned %d points, wanted %d", g, numPoints) + } +} + +func count(bins Bins) int { + binCounts := 0 + for _, b := range bins { + binCounts += b.Count + } + return binCounts +} diff --git a/vendor/github.com/beorn7/perks/quantile/bench_test.go b/vendor/github.com/beorn7/perks/quantile/bench_test.go new file mode 100644 index 000000000..0bd0e4e77 --- /dev/null +++ b/vendor/github.com/beorn7/perks/quantile/bench_test.go @@ -0,0 +1,63 @@ +package quantile + +import ( + "testing" +) + +func BenchmarkInsertTargeted(b *testing.B) { + b.ReportAllocs() + + s := NewTargeted(Targets) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) { + s := NewTargeted(TargetsSmallEpsilon) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkInsertBiased(b *testing.B) { + s := NewLowBiased(0.01) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) { + s := NewLowBiased(0.0001) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkQuery(b *testing.B) { + s := NewTargeted(Targets) + for i := float64(0); i < 1e6; i++ { + s.Insert(i) + } + b.ResetTimer() + n := float64(b.N) + for i := float64(0); i < n; i++ { + s.Query(i / n) + } +} + +func BenchmarkQuerySmallEpsilon(b *testing.B) { + s := NewTargeted(TargetsSmallEpsilon) + for i := float64(0); i < 1e6; i++ { + s.Insert(i) + } + b.ResetTimer() + n := float64(b.N) + for i := float64(0); i < n; i++ { + s.Query(i / n) + } +} diff --git a/vendor/github.com/beorn7/perks/quantile/example_test.go b/vendor/github.com/beorn7/perks/quantile/example_test.go new file mode 100644 index 000000000..ab3293aaf --- /dev/null +++ b/vendor/github.com/beorn7/perks/quantile/example_test.go @@ -0,0 +1,121 @@ +// +build go1.1 + +package quantile_test + +import ( + "bufio" + "fmt" + "log" + "os" + "strconv" + "time" + + "github.com/beorn7/perks/quantile" +) + +func Example_simple() { + ch := make(chan float64) + go sendFloats(ch) + + // Compute the 50th, 90th, and 99th percentile. + q := quantile.NewTargeted(map[float64]float64{ + 0.50: 0.005, + 0.90: 0.001, + 0.99: 0.0001, + }) + for v := range ch { + q.Insert(v) + } + + fmt.Println("perc50:", q.Query(0.50)) + fmt.Println("perc90:", q.Query(0.90)) + fmt.Println("perc99:", q.Query(0.99)) + fmt.Println("count:", q.Count()) + // Output: + // perc50: 5 + // perc90: 16 + // perc99: 223 + // count: 2388 +} + +func Example_mergeMultipleStreams() { + // Scenario: + // We have multiple database shards. On each shard, there is a process + // collecting query response times from the database logs and inserting + // them into a Stream (created via NewTargeted(0.90)), much like the + // Simple example. These processes expose a network interface for us to + // ask them to serialize and send us the results of their + // Stream.Samples so we may Merge and Query them. + // + // NOTES: + // * These sample sets are small, allowing us to get them + // across the network much faster than sending the entire list of data + // points. + // + // * For this to work correctly, we must supply the same quantiles + // a priori the process collecting the samples supplied to NewTargeted, + // even if we do not plan to query them all here. + ch := make(chan quantile.Samples) + getDBQuerySamples(ch) + q := quantile.NewTargeted(map[float64]float64{0.90: 0.001}) + for samples := range ch { + q.Merge(samples) + } + fmt.Println("perc90:", q.Query(0.90)) +} + +func Example_window() { + // Scenario: We want the 90th, 95th, and 99th percentiles for each + // minute. + + ch := make(chan float64) + go sendStreamValues(ch) + + tick := time.NewTicker(1 * time.Minute) + q := quantile.NewTargeted(map[float64]float64{ + 0.90: 0.001, + 0.95: 0.0005, + 0.99: 0.0001, + }) + for { + select { + case t := <-tick.C: + flushToDB(t, q.Samples()) + q.Reset() + case v := <-ch: + q.Insert(v) + } + } +} + +func sendStreamValues(ch chan float64) { + // Use your imagination +} + +func flushToDB(t time.Time, samples quantile.Samples) { + // Use your imagination +} + +// This is a stub for the above example. In reality this would hit the remote +// servers via http or something like it. +func getDBQuerySamples(ch chan quantile.Samples) {} + +func sendFloats(ch chan<- float64) { + f, err := os.Open("exampledata.txt") + if err != nil { + log.Fatal(err) + } + sc := bufio.NewScanner(f) + for sc.Scan() { + b := sc.Bytes() + v, err := strconv.ParseFloat(string(b), 64) + if err != nil { + log.Fatal(err) + } + ch <- v + } + if sc.Err() != nil { + log.Fatal(sc.Err()) + } + close(ch) +} diff --git a/vendor/github.com/beorn7/perks/quantile/exampledata.txt b/vendor/github.com/beorn7/perks/quantile/exampledata.txt new file mode 100644 index 000000000..1602287d7 --- /dev/null +++ b/vendor/github.com/beorn7/perks/quantile/exampledata.txt @@ -0,0 +1,2388 @@ +8 +5 +26 +12 +5 +235 +13 +6 +28 +30 +3 +3 +3 +3 +5 +2 +33 +7 +2 +4 +7 +12 +14 +5 +8 +3 +10 +4 +5 +3 +6 +6 +209 +20 +3 +10 +14 +3 +4 +6 +8 +5 +11 +7 +3 +2 +3 +3 +212 +5 +222 +4 +10 +10 +5 +6 +3 +8 +3 +10 +254 +220 +2 +3 +5 +24 +5 +4 +222 +7 +3 +3 +223 +8 +15 +12 +14 +14 +3 +2 +2 +3 +13 +3 +11 +4 +4 +6 +5 +7 +13 +5 +3 +5 +2 +5 +3 +5 +2 +7 +15 +17 +14 +3 +6 +6 +3 +17 +5 +4 +7 +6 +4 +4 +8 +6 +8 +3 +9 +3 +6 +3 +4 +5 +3 +3 +660 +4 +6 +10 +3 +6 +3 +2 +5 +13 +2 +4 +4 +10 +4 +8 +4 +3 +7 +9 +9 +3 +10 +37 +3 +13 +4 +12 +3 +6 +10 +8 +5 +21 +2 +3 +8 +3 +2 +3 +3 +4 +12 +2 +4 +8 +8 +4 +3 +2 +20 +1 +6 +32 +2 +11 +6 +18 +3 +8 +11 +3 +212 +3 +4 +2 +6 +7 +12 +11 +3 +2 +16 +10 +6 +4 +6 +3 +2 +7 +3 +2 +2 +2 +2 +5 +6 +4 +3 +10 +3 +4 +6 +5 +3 +4 +4 +5 +6 +4 +3 +4 +4 +5 +7 +5 +5 +3 +2 +7 +2 +4 +12 +4 +5 +6 +2 +4 +4 +8 +4 +15 +13 +7 +16 +5 +3 +23 +5 +5 +7 +3 +2 +9 +8 +7 +5 +8 +11 +4 +10 +76 +4 +47 +4 +3 +2 +7 +4 +2 +3 +37 +10 +4 +2 +20 +5 +4 +4 +10 +10 +4 +3 +7 +23 +240 +7 +13 +5 +5 +3 +3 +2 +5 +4 +2 +8 +7 +19 +2 +23 +8 +7 +2 +5 +3 +8 +3 +8 +13 +5 +5 +5 +2 +3 +23 +4 +9 +8 +4 +3 +3 +5 +220 +2 +3 +4 +6 +14 +3 +53 +6 +2 +5 +18 +6 +3 +219 +6 +5 +2 +5 +3 +6 +5 +15 +4 +3 +17 +3 +2 +4 +7 +2 +3 +3 +4 +4 +3 +2 +664 +6 +3 +23 +5 +5 +16 +5 +8 +2 +4 +2 +24 +12 +3 +2 +3 +5 +8 +3 +5 +4 +3 +14 +3 +5 +8 +2 +3 +7 +9 +4 +2 +3 +6 +8 +4 +3 +4 +6 +5 +3 +3 +6 +3 +19 +4 +4 +6 +3 +6 +3 +5 +22 +5 +4 +4 +3 +8 +11 +4 +9 +7 +6 +13 +4 +4 +4 +6 +17 +9 +3 +3 +3 +4 +3 +221 +5 +11 +3 +4 +2 +12 +6 +3 +5 +7 +5 +7 +4 +9 +7 +14 +37 +19 +217 +16 +3 +5 +2 +2 +7 +19 +7 +6 +7 +4 +24 +5 +11 +4 +7 +7 +9 +13 +3 +4 +3 +6 +28 +4 +4 +5 +5 +2 +5 +6 +4 +4 +6 +10 +5 +4 +3 +2 +3 +3 +6 +5 +5 +4 +3 +2 +3 +7 +4 +6 +18 +16 +8 +16 +4 +5 +8 +6 +9 +13 +1545 +6 +215 +6 +5 +6 +3 +45 +31 +5 +2 +2 +4 +3 +3 +2 +5 +4 +3 +5 +7 +7 +4 +5 +8 +5 +4 +749 +2 +31 +9 +11 +2 +11 +5 +4 +4 +7 +9 +11 +4 +5 +4 +7 +3 +4 +6 +2 +15 +3 +4 +3 +4 +3 +5 +2 +13 +5 +5 +3 +3 +23 +4 +4 +5 +7 +4 +13 +2 +4 +3 +4 +2 +6 +2 +7 +3 +5 +5 +3 +29 +5 +4 +4 +3 +10 +2 +3 +79 +16 +6 +6 +7 +7 +3 +5 +5 +7 +4 +3 +7 +9 +5 +6 +5 +9 +6 +3 +6 +4 +17 +2 +10 +9 +3 +6 +2 +3 +21 +22 +5 +11 +4 +2 +17 +2 +224 +2 +14 +3 +4 +4 +2 +4 +4 +4 +4 +5 +3 +4 +4 +10 +2 +6 +3 +3 +5 +7 +2 +7 +5 +6 +3 +218 +2 +2 +5 +2 +6 +3 +5 +222 +14 +6 +33 +3 +2 +5 +3 +3 +3 +9 +5 +3 +3 +2 +7 +4 +3 +4 +3 +5 +6 +5 +26 +4 +13 +9 +7 +3 +221 +3 +3 +4 +4 +4 +4 +2 +18 +5 +3 +7 +9 +6 +8 +3 +10 +3 +11 +9 +5 +4 +17 +5 +5 +6 +6 +3 +2 +4 +12 +17 +6 +7 +218 +4 +2 +4 +10 +3 +5 +15 +3 +9 +4 +3 +3 +6 +29 +3 +3 +4 +5 +5 +3 +8 +5 +6 +6 +7 +5 +3 +5 +3 +29 +2 +31 +5 +15 +24 +16 +5 +207 +4 +3 +3 +2 +15 +4 +4 +13 +5 +5 +4 +6 +10 +2 +7 +8 +4 +6 +20 +5 +3 +4 +3 +12 +12 +5 +17 +7 +3 +3 +3 +6 +10 +3 +5 +25 +80 +4 +9 +3 +2 +11 +3 +3 +2 +3 +8 +7 +5 +5 +19 +5 +3 +3 +12 +11 +2 +6 +5 +5 +5 +3 +3 +3 +4 +209 +14 +3 +2 +5 +19 +4 +4 +3 +4 +14 +5 +6 +4 +13 +9 +7 +4 +7 +10 +2 +9 +5 +7 +2 +8 +4 +6 +5 +5 +222 +8 +7 +12 +5 +216 +3 +4 +4 +6 +3 +14 +8 +7 +13 +4 +3 +3 +3 +3 +17 +5 +4 +3 +33 +6 +6 +33 +7 +5 +3 +8 +7 +5 +2 +9 +4 +2 +233 +24 +7 +4 +8 +10 +3 +4 +15 +2 +16 +3 +3 +13 +12 +7 +5 +4 +207 +4 +2 +4 +27 +15 +2 +5 +2 +25 +6 +5 +5 +6 +13 +6 +18 +6 +4 +12 +225 +10 +7 +5 +2 +2 +11 +4 +14 +21 +8 +10 +3 +5 +4 +232 +2 +5 +5 +3 +7 +17 +11 +6 +6 +23 +4 +6 +3 +5 +4 +2 +17 +3 +6 +5 +8 +3 +2 +2 +14 +9 +4 +4 +2 +5 +5 +3 +7 +6 +12 +6 +10 +3 +6 +2 +2 +19 +5 +4 +4 +9 +2 +4 +13 +3 +5 +6 +3 +6 +5 +4 +9 +6 +3 +5 +7 +3 +6 +6 +4 +3 +10 +6 +3 +221 +3 +5 +3 +6 +4 +8 +5 +3 +6 +4 +4 +2 +54 +5 +6 +11 +3 +3 +4 +4 +4 +3 +7 +3 +11 +11 +7 +10 +6 +13 +223 +213 +15 +231 +7 +3 +7 +228 +2 +3 +4 +4 +5 +6 +7 +4 +13 +3 +4 +5 +3 +6 +4 +6 +7 +2 +4 +3 +4 +3 +3 +6 +3 +7 +3 +5 +18 +5 +6 +8 +10 +3 +3 +3 +2 +4 +2 +4 +4 +5 +6 +6 +4 +10 +13 +3 +12 +5 +12 +16 +8 +4 +19 +11 +2 +4 +5 +6 +8 +5 +6 +4 +18 +10 +4 +2 +216 +6 +6 +6 +2 +4 +12 +8 +3 +11 +5 +6 +14 +5 +3 +13 +4 +5 +4 +5 +3 +28 +6 +3 +7 +219 +3 +9 +7 +3 +10 +6 +3 +4 +19 +5 +7 +11 +6 +15 +19 +4 +13 +11 +3 +7 +5 +10 +2 +8 +11 +2 +6 +4 +6 +24 +6 +3 +3 +3 +3 +6 +18 +4 +11 +4 +2 +5 +10 +8 +3 +9 +5 +3 +4 +5 +6 +2 +5 +7 +4 +4 +14 +6 +4 +4 +5 +5 +7 +2 +4 +3 +7 +3 +3 +6 +4 +5 +4 +4 +4 +3 +3 +3 +3 +8 +14 +2 +3 +5 +3 +2 +4 +5 +3 +7 +3 +3 +18 +3 +4 +4 +5 +7 +3 +3 +3 +13 +5 +4 +8 +211 +5 +5 +3 +5 +2 +5 +4 +2 +655 +6 +3 +5 +11 +2 +5 +3 +12 +9 +15 +11 +5 +12 +217 +2 +6 +17 +3 +3 +207 +5 +5 +4 +5 +9 +3 +2 +8 +5 +4 +3 +2 +5 +12 +4 +14 +5 +4 +2 +13 +5 +8 +4 +225 +4 +3 +4 +5 +4 +3 +3 +6 +23 +9 +2 +6 +7 +233 +4 +4 +6 +18 +3 +4 +6 +3 +4 +4 +2 +3 +7 +4 +13 +227 +4 +3 +5 +4 +2 +12 +9 +17 +3 +7 +14 +6 +4 +5 +21 +4 +8 +9 +2 +9 +25 +16 +3 +6 +4 +7 +8 +5 +2 +3 +5 +4 +3 +3 +5 +3 +3 +3 +2 +3 +19 +2 +4 +3 +4 +2 +3 +4 +4 +2 +4 +3 +3 +3 +2 +6 +3 +17 +5 +6 +4 +3 +13 +5 +3 +3 +3 +4 +9 +4 +2 +14 +12 +4 +5 +24 +4 +3 +37 +12 +11 +21 +3 +4 +3 +13 +4 +2 +3 +15 +4 +11 +4 +4 +3 +8 +3 +4 +4 +12 +8 +5 +3 +3 +4 +2 +220 +3 +5 +223 +3 +3 +3 +10 +3 +15 +4 +241 +9 +7 +3 +6 +6 +23 +4 +13 +7 +3 +4 +7 +4 +9 +3 +3 +4 +10 +5 +5 +1 +5 +24 +2 +4 +5 +5 +6 +14 +3 +8 +2 +3 +5 +13 +13 +3 +5 +2 +3 +15 +3 +4 +2 +10 +4 +4 +4 +5 +5 +3 +5 +3 +4 +7 +4 +27 +3 +6 +4 +15 +3 +5 +6 +6 +5 +4 +8 +3 +9 +2 +6 +3 +4 +3 +7 +4 +18 +3 +11 +3 +3 +8 +9 +7 +24 +3 +219 +7 +10 +4 +5 +9 +12 +2 +5 +4 +4 +4 +3 +3 +19 +5 +8 +16 +8 +6 +22 +3 +23 +3 +242 +9 +4 +3 +3 +5 +7 +3 +3 +5 +8 +3 +7 +5 +14 +8 +10 +3 +4 +3 +7 +4 +6 +7 +4 +10 +4 +3 +11 +3 +7 +10 +3 +13 +6 +8 +12 +10 +5 +7 +9 +3 +4 +7 +7 +10 +8 +30 +9 +19 +4 +3 +19 +15 +4 +13 +3 +215 +223 +4 +7 +4 +8 +17 +16 +3 +7 +6 +5 +5 +4 +12 +3 +7 +4 +4 +13 +4 +5 +2 +5 +6 +5 +6 +6 +7 +10 +18 +23 +9 +3 +3 +6 +5 +2 +4 +2 +7 +3 +3 +2 +5 +5 +14 +10 +224 +6 +3 +4 +3 +7 +5 +9 +3 +6 +4 +2 +5 +11 +4 +3 +3 +2 +8 +4 +7 +4 +10 +7 +3 +3 +18 +18 +17 +3 +3 +3 +4 +5 +3 +3 +4 +12 +7 +3 +11 +13 +5 +4 +7 +13 +5 +4 +11 +3 +12 +3 +6 +4 +4 +21 +4 +6 +9 +5 +3 +10 +8 +4 +6 +4 +4 +6 +5 +4 +8 +6 +4 +6 +4 +4 +5 +9 +6 +3 +4 +2 +9 +3 +18 +2 +4 +3 +13 +3 +6 +6 +8 +7 +9 +3 +2 +16 +3 +4 +6 +3 +2 +33 +22 +14 +4 +9 +12 +4 +5 +6 +3 +23 +9 +4 +3 +5 +5 +3 +4 +5 +3 +5 +3 +10 +4 +5 +5 +8 +4 +4 +6 +8 +5 +4 +3 +4 +6 +3 +3 +3 +5 +9 +12 +6 +5 +9 +3 +5 +3 +2 +2 +2 +18 +3 +2 +21 +2 +5 +4 +6 +4 +5 +10 +3 +9 +3 +2 +10 +7 +3 +6 +6 +4 +4 +8 +12 +7 +3 +7 +3 +3 +9 +3 +4 +5 +4 +4 +5 +5 +10 +15 +4 +4 +14 +6 +227 +3 +14 +5 +216 +22 +5 +4 +2 +2 +6 +3 +4 +2 +9 +9 +4 +3 +28 +13 +11 +4 +5 +3 +3 +2 +3 +3 +5 +3 +4 +3 +5 +23 +26 +3 +4 +5 +6 +4 +6 +3 +5 +5 +3 +4 +3 +2 +2 +2 +7 +14 +3 +6 +7 +17 +2 +2 +15 +14 +16 +4 +6 +7 +13 +6 +4 +5 +6 +16 +3 +3 +28 +3 +6 +15 +3 +9 +2 +4 +6 +3 +3 +22 +4 +12 +6 +7 +2 +5 +4 +10 +3 +16 +6 +9 +2 +5 +12 +7 +5 +5 +5 +5 +2 +11 +9 +17 +4 +3 +11 +7 +3 +5 +15 +4 +3 +4 +211 +8 +7 +5 +4 +7 +6 +7 +6 +3 +6 +5 +6 +5 +3 +4 +4 +26 +4 +6 +10 +4 +4 +3 +2 +3 +3 +4 +5 +9 +3 +9 +4 +4 +5 +5 +8 +2 +4 +2 +3 +8 +4 +11 +19 +5 +8 +6 +3 +5 +6 +12 +3 +2 +4 +16 +12 +3 +4 +4 +8 +6 +5 +6 +6 +219 +8 +222 +6 +16 +3 +13 +19 +5 +4 +3 +11 +6 +10 +4 +7 +7 +12 +5 +3 +3 +5 +6 +10 +3 +8 +2 +5 +4 +7 +2 +4 +4 +2 +12 +9 +6 +4 +2 +40 +2 +4 +10 +4 +223 +4 +2 +20 +6 +7 +24 +5 +4 +5 +2 +20 +16 +6 +5 +13 +2 +3 +3 +19 +3 +2 +4 +5 +6 +7 +11 +12 +5 +6 +7 +7 +3 +5 +3 +5 +3 +14 +3 +4 +4 +2 +11 +1 +7 +3 +9 +6 +11 +12 +5 +8 +6 +221 +4 +2 +12 +4 +3 +15 +4 +5 +226 +7 +218 +7 +5 +4 +5 +18 +4 +5 +9 +4 +4 +2 +9 +18 +18 +9 +5 +6 +6 +3 +3 +7 +3 +5 +4 +4 +4 +12 +3 +6 +31 +5 +4 +7 +3 +6 +5 +6 +5 +11 +2 +2 +11 +11 +6 +7 +5 +8 +7 +10 +5 +23 +7 +4 +3 +5 +34 +2 +5 +23 +7 +3 +6 +8 +4 +4 +4 +2 +5 +3 +8 +5 +4 +8 +25 +2 +3 +17 +8 +3 +4 +8 +7 +3 +15 +6 +5 +7 +21 +9 +5 +6 +6 +5 +3 +2 +3 +10 +3 +6 +3 +14 +7 +4 +4 +8 +7 +8 +2 +6 +12 +4 +213 +6 +5 +21 +8 +2 +5 +23 +3 +11 +2 +3 +6 +25 +2 +3 +6 +7 +6 +6 +4 +4 +6 +3 +17 +9 +7 +6 +4 +3 +10 +7 +2 +3 +3 +3 +11 +8 +3 +7 +6 +4 +14 +36 +3 +4 +3 +3 +22 +13 +21 +4 +2 +7 +4 +4 +17 +15 +3 +7 +11 +2 +4 +7 +6 +209 +6 +3 +2 +2 +24 +4 +9 +4 +3 +3 +3 +29 +2 +2 +4 +3 +3 +5 +4 +6 +3 +3 +2 +4 diff --git a/vendor/github.com/beorn7/perks/quantile/stream.go b/vendor/github.com/beorn7/perks/quantile/stream.go new file mode 100644 index 000000000..587b1fc5b --- /dev/null +++ b/vendor/github.com/beorn7/perks/quantile/stream.go @@ -0,0 +1,292 @@ +// Package quantile computes approximate quantiles over an unbounded data +// stream within low memory and CPU bounds. +// +// A small amount of accuracy is traded to achieve the above properties. +// +// Multiple streams can be merged before calling Query to generate a single set +// of results. This is meaningful when the streams represent the same type of +// data. See Merge and Samples. +// +// For more detailed information about the algorithm used, see: +// +// Effective Computation of Biased Quantiles over Data Streams +// +// http://www.cs.rutgers.edu/~muthu/bquant.pdf +package quantile + +import ( + "math" + "sort" +) + +// Sample holds an observed value and meta information for compression. JSON +// tags have been added for convenience. +type Sample struct { + Value float64 `json:",string"` + Width float64 `json:",string"` + Delta float64 `json:",string"` +} + +// Samples represents a slice of samples. It implements sort.Interface. +type Samples []Sample + +func (a Samples) Len() int { return len(a) } +func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value } +func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type invariant func(s *stream, r float64) float64 + +// NewLowBiased returns an initialized Stream for low-biased quantiles +// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but +// error guarantees can still be given even for the lower ranks of the data +// distribution. +// +// The provided epsilon is a relative error, i.e. the true quantile of a value +// returned by a query is guaranteed to be within (1±Epsilon)*Quantile. +// +// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error +// properties. +func NewLowBiased(epsilon float64) *Stream { + ƒ := func(s *stream, r float64) float64 { + return 2 * epsilon * r + } + return newStream(ƒ) +} + +// NewHighBiased returns an initialized Stream for high-biased quantiles +// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but +// error guarantees can still be given even for the higher ranks of the data +// distribution. +// +// The provided epsilon is a relative error, i.e. the true quantile of a value +// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile). +// +// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error +// properties. +func NewHighBiased(epsilon float64) *Stream { + ƒ := func(s *stream, r float64) float64 { + return 2 * epsilon * (s.n - r) + } + return newStream(ƒ) +} + +// NewTargeted returns an initialized Stream concerned with a particular set of +// quantile values that are supplied a priori. Knowing these a priori reduces +// space and computation time. The targets map maps the desired quantiles to +// their absolute errors, i.e. the true quantile of a value returned by a query +// is guaranteed to be within (Quantile±Epsilon). +// +// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties. +func NewTargeted(targets map[float64]float64) *Stream { + ƒ := func(s *stream, r float64) float64 { + var m = math.MaxFloat64 + var f float64 + for quantile, epsilon := range targets { + if quantile*s.n <= r { + f = (2 * epsilon * r) / quantile + } else { + f = (2 * epsilon * (s.n - r)) / (1 - quantile) + } + if f < m { + m = f + } + } + return m + } + return newStream(ƒ) +} + +// Stream computes quantiles for a stream of float64s. It is not thread-safe by +// design. Take care when using across multiple goroutines. +type Stream struct { + *stream + b Samples + sorted bool +} + +func newStream(ƒ invariant) *Stream { + x := &stream{ƒ: ƒ} + return &Stream{x, make(Samples, 0, 500), true} +} + +// Insert inserts v into the stream. +func (s *Stream) Insert(v float64) { + s.insert(Sample{Value: v, Width: 1}) +} + +func (s *Stream) insert(sample Sample) { + s.b = append(s.b, sample) + s.sorted = false + if len(s.b) == cap(s.b) { + s.flush() + } +} + +// Query returns the computed qth percentiles value. If s was created with +// NewTargeted, and q is not in the set of quantiles provided a priori, Query +// will return an unspecified result. +func (s *Stream) Query(q float64) float64 { + if !s.flushed() { + // Fast path when there hasn't been enough data for a flush; + // this also yields better accuracy for small sets of data. + l := len(s.b) + if l == 0 { + return 0 + } + i := int(float64(l) * q) + if i > 0 { + i -= 1 + } + s.maybeSort() + return s.b[i].Value + } + s.flush() + return s.stream.query(q) +} + +// Merge merges samples into the underlying streams samples. This is handy when +// merging multiple streams from separate threads, database shards, etc. +// +// ATTENTION: This method is broken and does not yield correct results. The +// underlying algorithm is not capable of merging streams correctly. +func (s *Stream) Merge(samples Samples) { + sort.Sort(samples) + s.stream.merge(samples) +} + +// Reset reinitializes and clears the list reusing the samples buffer memory. +func (s *Stream) Reset() { + s.stream.reset() + s.b = s.b[:0] +} + +// Samples returns stream samples held by s. +func (s *Stream) Samples() Samples { + if !s.flushed() { + return s.b + } + s.flush() + return s.stream.samples() +} + +// Count returns the total number of samples observed in the stream +// since initialization. +func (s *Stream) Count() int { + return len(s.b) + s.stream.count() +} + +func (s *Stream) flush() { + s.maybeSort() + s.stream.merge(s.b) + s.b = s.b[:0] +} + +func (s *Stream) maybeSort() { + if !s.sorted { + s.sorted = true + sort.Sort(s.b) + } +} + +func (s *Stream) flushed() bool { + return len(s.stream.l) > 0 +} + +type stream struct { + n float64 + l []Sample + ƒ invariant +} + +func (s *stream) reset() { + s.l = s.l[:0] + s.n = 0 +} + +func (s *stream) insert(v float64) { + s.merge(Samples{{v, 1, 0}}) +} + +func (s *stream) merge(samples Samples) { + // TODO(beorn7): This tries to merge not only individual samples, but + // whole summaries. The paper doesn't mention merging summaries at + // all. Unittests show that the merging is inaccurate. Find out how to + // do merges properly. + var r float64 + i := 0 + for _, sample := range samples { + for ; i < len(s.l); i++ { + c := s.l[i] + if c.Value > sample.Value { + // Insert at position i. + s.l = append(s.l, Sample{}) + copy(s.l[i+1:], s.l[i:]) + s.l[i] = Sample{ + sample.Value, + sample.Width, + math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1), + // TODO(beorn7): How to calculate delta correctly? + } + i++ + goto inserted + } + r += c.Width + } + s.l = append(s.l, Sample{sample.Value, sample.Width, 0}) + i++ + inserted: + s.n += sample.Width + r += sample.Width + } + s.compress() +} + +func (s *stream) count() int { + return int(s.n) +} + +func (s *stream) query(q float64) float64 { + t := math.Ceil(q * s.n) + t += math.Ceil(s.ƒ(s, t) / 2) + p := s.l[0] + var r float64 + for _, c := range s.l[1:] { + r += p.Width + if r+c.Width+c.Delta > t { + return p.Value + } + p = c + } + return p.Value +} + +func (s *stream) compress() { + if len(s.l) < 2 { + return + } + x := s.l[len(s.l)-1] + xi := len(s.l) - 1 + r := s.n - 1 - x.Width + + for i := len(s.l) - 2; i >= 0; i-- { + c := s.l[i] + if c.Width+x.Width+x.Delta <= s.ƒ(s, r) { + x.Width += c.Width + s.l[xi] = x + // Remove element at i. + copy(s.l[i:], s.l[i+1:]) + s.l = s.l[:len(s.l)-1] + xi -= 1 + } else { + x = c + xi = i + } + r -= c.Width + } +} + +func (s *stream) samples() Samples { + samples := make(Samples, len(s.l)) + copy(samples, s.l) + return samples +} diff --git a/vendor/github.com/beorn7/perks/quantile/stream_test.go b/vendor/github.com/beorn7/perks/quantile/stream_test.go new file mode 100644 index 000000000..4dba05449 --- /dev/null +++ b/vendor/github.com/beorn7/perks/quantile/stream_test.go @@ -0,0 +1,188 @@ +package quantile + +import ( + "math" + "math/rand" + "sort" + "testing" +) + +var ( + Targets = map[float64]float64{ + 0.01: 0.001, + 0.10: 0.01, + 0.50: 0.05, + 0.90: 0.01, + 0.99: 0.001, + } + TargetsSmallEpsilon = map[float64]float64{ + 0.01: 0.0001, + 0.10: 0.001, + 0.50: 0.005, + 0.90: 0.001, + 0.99: 0.0001, + } + LowQuantiles = []float64{0.01, 0.1, 0.5} + HighQuantiles = []float64{0.99, 0.9, 0.5} +) + +const RelativeEpsilon = 0.01 + +func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) { + sort.Float64s(a) + for quantile, epsilon := range Targets { + n := float64(len(a)) + k := int(quantile * n) + lower := int((quantile - epsilon) * n) + if lower < 1 { + lower = 1 + } + upper := int(math.Ceil((quantile + epsilon) * n)) + if upper > len(a) { + upper = len(a) + } + w, min, max := a[k-1], a[lower-1], a[upper-1] + if g := s.Query(quantile); g < min || g > max { + t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g) + } + } +} + +func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) { + sort.Float64s(a) + for _, qu := range LowQuantiles { + n := float64(len(a)) + k := int(qu * n) + + lowerRank := int((1 - RelativeEpsilon) * qu * n) + upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n)) + w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1] + if g := s.Query(qu); g < min || g > max { + t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g) + } + } +} + +func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) { + sort.Float64s(a) + for _, qu := range HighQuantiles { + n := float64(len(a)) + k := int(qu * n) + + lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n) + upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n)) + w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1] + if g := s.Query(qu); g < min || g > max { + t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g) + } + } +} + +func populateStream(s *Stream) []float64 { + a := make([]float64, 0, 1e5+100) + for i := 0; i < cap(a); i++ { + v := rand.NormFloat64() + // Add 5% asymmetric outliers. + if i%20 == 0 { + v = v*v + 1 + } + s.Insert(v) + a = append(a, v) + } + return a +} + +func TestTargetedQuery(t *testing.T) { + rand.Seed(42) + s := NewTargeted(Targets) + a := populateStream(s) + verifyPercsWithAbsoluteEpsilon(t, a, s) +} + +func TestLowBiasedQuery(t *testing.T) { + rand.Seed(42) + s := NewLowBiased(RelativeEpsilon) + a := populateStream(s) + verifyLowPercsWithRelativeEpsilon(t, a, s) +} + +func TestHighBiasedQuery(t *testing.T) { + rand.Seed(42) + s := NewHighBiased(RelativeEpsilon) + a := populateStream(s) + verifyHighPercsWithRelativeEpsilon(t, a, s) +} + +// BrokenTestTargetedMerge is broken, see Merge doc comment. +func BrokenTestTargetedMerge(t *testing.T) { + rand.Seed(42) + s1 := NewTargeted(Targets) + s2 := NewTargeted(Targets) + a := populateStream(s1) + a = append(a, populateStream(s2)...) + s1.Merge(s2.Samples()) + verifyPercsWithAbsoluteEpsilon(t, a, s1) +} + +// BrokenTestLowBiasedMerge is broken, see Merge doc comment. +func BrokenTestLowBiasedMerge(t *testing.T) { + rand.Seed(42) + s1 := NewLowBiased(RelativeEpsilon) + s2 := NewLowBiased(RelativeEpsilon) + a := populateStream(s1) + a = append(a, populateStream(s2)...) + s1.Merge(s2.Samples()) + verifyLowPercsWithRelativeEpsilon(t, a, s2) +} + +// BrokenTestHighBiasedMerge is broken, see Merge doc comment. +func BrokenTestHighBiasedMerge(t *testing.T) { + rand.Seed(42) + s1 := NewHighBiased(RelativeEpsilon) + s2 := NewHighBiased(RelativeEpsilon) + a := populateStream(s1) + a = append(a, populateStream(s2)...) + s1.Merge(s2.Samples()) + verifyHighPercsWithRelativeEpsilon(t, a, s2) +} + +func TestUncompressed(t *testing.T) { + q := NewTargeted(Targets) + for i := 100; i > 0; i-- { + q.Insert(float64(i)) + } + if g := q.Count(); g != 100 { + t.Errorf("want count 100, got %d", g) + } + // Before compression, Query should have 100% accuracy. + for quantile := range Targets { + w := quantile * 100 + if g := q.Query(quantile); g != w { + t.Errorf("want %f, got %f", w, g) + } + } +} + +func TestUncompressedSamples(t *testing.T) { + q := NewTargeted(map[float64]float64{0.99: 0.001}) + for i := 1; i <= 100; i++ { + q.Insert(float64(i)) + } + if g := q.Samples().Len(); g != 100 { + t.Errorf("want count 100, got %d", g) + } +} + +func TestUncompressedOne(t *testing.T) { + q := NewTargeted(map[float64]float64{0.99: 0.01}) + q.Insert(3.14) + if g := q.Query(0.90); g != 3.14 { + t.Error("want PI, got", g) + } +} + +func TestDefaults(t *testing.T) { + if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 { + t.Errorf("want 0, got %f", g) + } +} diff --git a/vendor/github.com/beorn7/perks/topk/topk.go b/vendor/github.com/beorn7/perks/topk/topk.go new file mode 100644 index 000000000..5ac3d9904 --- /dev/null +++ b/vendor/github.com/beorn7/perks/topk/topk.go @@ -0,0 +1,90 @@ +package topk + +import ( + "sort" +) + +// http://www.cs.ucsb.edu/research/tech_reports/reports/2005-23.pdf + +type Element struct { + Value string + Count int +} + +type Samples []*Element + +func (sm Samples) Len() int { + return len(sm) +} + +func (sm Samples) Less(i, j int) bool { + return sm[i].Count < sm[j].Count +} + +func (sm Samples) Swap(i, j int) { + sm[i], sm[j] = sm[j], sm[i] +} + +type Stream struct { + k int + mon map[string]*Element + + // the minimum Element + min *Element +} + +func New(k int) *Stream { + s := new(Stream) + s.k = k + s.mon = make(map[string]*Element) + s.min = &Element{} + + // Track k+1 so that less frequenet items contended for that spot, + // resulting in k being more accurate. + return s +} + +func (s *Stream) Insert(x string) { + s.insert(&Element{x, 1}) +} + +func (s *Stream) Merge(sm Samples) { + for _, e := range sm { + s.insert(e) + } +} + +func (s *Stream) insert(in *Element) { + e := s.mon[in.Value] + if e != nil { + e.Count++ + } else { + if len(s.mon) < s.k+1 { + e = &Element{in.Value, in.Count} + s.mon[in.Value] = e + } else { + e = s.min + delete(s.mon, e.Value) + e.Value = in.Value + e.Count += in.Count + s.min = e + } + } + if e.Count < s.min.Count { + s.min = e + } +} + +func (s *Stream) Query() Samples { + var sm Samples + for _, e := range s.mon { + sm = append(sm, e) + } + sort.Sort(sort.Reverse(sm)) + + if len(sm) < s.k { + return sm + } + + return sm[:s.k] +} diff --git a/vendor/github.com/beorn7/perks/topk/topk_test.go b/vendor/github.com/beorn7/perks/topk/topk_test.go new file mode 100644 index 000000000..c24f0f727 --- /dev/null +++ b/vendor/github.com/beorn7/perks/topk/topk_test.go @@ -0,0 +1,57 @@ +package topk + +import ( + "fmt" + "math/rand" + "sort" + "testing" +) + +func TestTopK(t *testing.T) { + stream := New(10) + ss := []*Stream{New(10), New(10), New(10)} + m := make(map[string]int) + for _, s := range ss { + for i := 0; i < 1e6; i++ { + v := fmt.Sprintf("%x", int8(rand.ExpFloat64())) + s.Insert(v) + m[v]++ + } + stream.Merge(s.Query()) + } + + var sm Samples + for x, s := range m { + sm = append(sm, &Element{x, s}) + } + sort.Sort(sort.Reverse(sm)) + + g := stream.Query() + if len(g) != 10 { + t.Fatalf("got %d, want 10", len(g)) + } + for i, e := range g { + if sm[i].Value != e.Value { + t.Errorf("at %d: want %q, got %q", i, sm[i].Value, e.Value) + } + } +} + +func TestQuery(t *testing.T) { + queryTests := []struct { + value string + expected int + }{ + {"a", 1}, + {"b", 2}, + {"c", 2}, + } + + stream := New(2) + for _, tt := range queryTests { + stream.Insert(tt.value) + if n := len(stream.Query()); n != tt.expected { + t.Errorf("want %d, got %d", tt.expected, n) + } + } +} diff --git a/vendor/github.com/certifi/gocertifi/LICENSE b/vendor/github.com/certifi/gocertifi/LICENSE new file mode 100644 index 000000000..cfd5dcbbb --- /dev/null +++ b/vendor/github.com/certifi/gocertifi/LICENSE @@ -0,0 +1,3 @@ +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. diff --git a/vendor/github.com/certifi/gocertifi/README.md b/vendor/github.com/certifi/gocertifi/README.md new file mode 100644 index 000000000..1c01b1102 --- /dev/null +++ b/vendor/github.com/certifi/gocertifi/README.md @@ -0,0 +1,60 @@ +# GoCertifi: SSL Certificates for Golang + +This Go package contains a CA bundle that you can reference in your Go code. +This is useful for systems that do not have CA bundles that Golang can find +itself, or where a uniform set of CAs is valuable. + +This is the same CA bundle that ships with the +[Python Requests](https://github.com/kennethreitz/requests) library, and is a +Golang specific port of [certifi](https://github.com/kennethreitz/certifi). The +CA bundle is derived from Mozilla's canonical set. + +## Usage + +You can use the `gocertifi` package as follows: + +```go +import "github.com/certifi/gocertifi" + +cert_pool, err := gocertifi.CACerts() +``` + +You can use the returned `*x509.CertPool` as part of an HTTP transport, for example: + +```go +import ( + "net/http" + "crypto/tls" +) + +// Setup an HTTP client with a custom transport +transport := &http.Transport{ + TLSClientConfig: &tls.Config{RootCAs: cert_pool}, +} +client := &http.Client{Transport: transport} + +// Make an HTTP request using our custom transport +resp, err := client.Get("https://example.com") +``` + +## Detailed Documentation + +Import as follows: + +```go +import "github.com/certifi/gocertifi" +``` + +### Errors + +```go +var ErrParseFailed = errors.New("gocertifi: error when parsing certificates") +``` + +### Functions + +```go +func CACerts() (*x509.CertPool, error) +``` +CACerts builds an X.509 certificate pool containing the Mozilla CA Certificate +bundle. Returns nil on error along with an appropriate error code. diff --git a/vendor/github.com/certifi/gocertifi/certifi.go b/vendor/github.com/certifi/gocertifi/certifi.go new file mode 100644 index 000000000..edf32b45f --- /dev/null +++ b/vendor/github.com/certifi/gocertifi/certifi.go @@ -0,0 +1,4330 @@ +// Code generated by go generate; DO NOT EDIT +// 2018-08-28 09:03:06.460975 -0700 PDT m=+0.705532595 +// https://mkcert.org/generate/ + +package gocertifi + +//go:generate go run gen.go + +import ( + "crypto/x509" + "errors" +) + +const pemcerts string = ` + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Label: "GlobalSign Root CA - R2" +# Serial: 4835703278459682885658125 +# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 +# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe +# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 +MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL +v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 +eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq +tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd +C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa +zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB +mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH +V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n +bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG +3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs +J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO +291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS +ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd +AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 3 Public Primary Certification Authority - G3" +# Serial: 206684696279472310254277870180966723415 +# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 +# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 +# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Premium 2048 Secure Server CA" +# Serial: 946069240 +# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 +# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 +# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Label: "AddTrust External Root" +# Serial: 1 +# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f +# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 +# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Label: "Entrust Root Certification Authority" +# Serial: 1164660820 +# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 +# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 +# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. +# Label: "GeoTrust Global CA" +# Serial: 144470 +# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 +# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 +# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Label: "GeoTrust Universal CA" +# Serial: 1 +# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 +# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 +# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy +c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 +IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV +VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 +cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT +QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh +F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v +c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w +mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd +VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX +teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ +f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe +Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ +nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY +MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX +IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn +ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z +uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN +Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja +QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW +koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 +ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt +DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm +bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Universal CA 2" +# Serial: 1 +# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 +# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 +# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy +c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD +VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 +c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 +WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG +FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq +XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL +se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb +KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd +IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 +y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt +hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc +QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 +Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV +HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ +KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ +L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr +Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo +ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY +T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz +GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m +1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV +OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH +6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX +QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +# Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association +# Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association +# Label: "Visa eCommerce Root" +# Serial: 25952180776285836048024890241505565794 +# MD5 Fingerprint: fc:11:b8:d8:08:93:30:00:6d:23:f9:7e:eb:52:1e:02 +# SHA1 Fingerprint: 70:17:9b:86:8c:00:a4:fa:60:91:52:22:3f:9f:3e:32:bd:e0:05:62 +# SHA256 Fingerprint: 69:fa:c9:bd:55:fb:0a:c7:8d:53:bb:ee:5c:f1:d5:97:98:9f:d0:aa:ab:20:a2:51:51:bd:f1:73:3e:e7:d1:22 +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr +MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl +cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw +CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h +dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l +cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h +2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E +lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV +ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq +299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t +vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL +dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF +AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR +zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3 +LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd +7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw +++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Label: "QuoVadis Root CA" +# Serial: 985026699 +# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 +# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 +# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz +MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw +IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR +dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp +li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D +rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ +WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug +F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU +xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC +Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv +dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw +ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl +IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh +c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy +ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI +KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T +KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq +y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p +dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD +VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk +fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 +7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R +cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y +mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW +xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK +SnQ2+Q== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2" +# Serial: 1289 +# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b +# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 +# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3" +# Serial: 1478 +# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf +# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 +# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 +# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 +# Label: "Security Communication Root CA" +# Serial: 0 +# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a +# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 +# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY +MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t +dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 +WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD +VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 +9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ +DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 +Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N +QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ +xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G +A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG +kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr +Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 +Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU +JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot +RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== +-----END CERTIFICATE----- + +# Issuer: CN=Sonera Class2 CA O=Sonera +# Subject: CN=Sonera Class2 CA O=Sonera +# Label: "Sonera Class 2 Root CA" +# Serial: 29 +# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb +# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 +# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP +MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx +MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV +BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o +Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt +5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s +3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej +vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu +8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw +DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil +zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ +3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD +FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 +Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 +ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: O=Government Root Certification Authority +# Subject: O=Government Root Certification Authority +# Label: "Taiwan GRCA" +# Serial: 42023070807708724159991140556527066870 +# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e +# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 +# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ +MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow +PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR +IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q +gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy +yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts +F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 +jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx +ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC +VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK +YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH +EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN +Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud +DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE +MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK +UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf +qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK +ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE +JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 +hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 +EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm +nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX +udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz +ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe +LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl +pYYsfPQS +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root CA" +# Serial: 17154717934120587862167794914071425081 +# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 +# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 +# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert High Assurance EV Root CA" +# Serial: 3553400076410547919724730734378100087 +# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a +# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 +# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- + +# Issuer: CN=Class 2 Primary CA O=Certplus +# Subject: CN=Class 2 Primary CA O=Certplus +# Label: "Certplus Class 2 Primary CA" +# Serial: 177770208045934040241468760488327595043 +# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b +# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb +# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw +PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz +cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 +MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz +IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ +ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR +VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL +kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd +EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas +H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 +HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud +DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 +QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu +Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ +AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 +yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR +FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA +ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB +kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Label: "DST Root CA X3" +# Serial: 91299735575339953335919266965803778155 +# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 +# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 +# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Label: "SwissSign Gold CA - G2" +# Serial: 13492815561806991280 +# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 +# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 +# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln +biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF +MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT +d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 +76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ +bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c +6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE +emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd +MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt +MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y +MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y +FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi +aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM +gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB +qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 +lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn +8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 +45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO +UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 +O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC +bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv +GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a +77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC +hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 +92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp +Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w +ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt +Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Label: "SwissSign Silver CA - G2" +# Serial: 5700383053117599563 +# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 +# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb +# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE +BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu +IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow +RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY +U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv +Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br +YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF +nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH +6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt +eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ +c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ +MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH +HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf +jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 +5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB +rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c +wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB +AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp +WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 +xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ +2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ +IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 +aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X +em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR +dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ +OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ +hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy +tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Label: "GeoTrust Primary Certification Authority" +# Serial: 32798226551256963324313806436981982369 +# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf +# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 +# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo +R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx +MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 +AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA +ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 +7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W +kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI +mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ +KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 +6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl +4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K +oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj +UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU +AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA" +# Serial: 69529181992039203566298953787712940909 +# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 +# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 +# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" +# Serial: 33037644167568058970164719475676101450 +# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c +# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 +# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +# Issuer: CN=SecureTrust CA O=SecureTrust Corporation +# Subject: CN=SecureTrust CA O=SecureTrust Corporation +# Label: "SecureTrust CA" +# Serial: 17199774589125277788362757014266862032 +# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 +# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 +# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +# Issuer: CN=Secure Global CA O=SecureTrust Corporation +# Subject: CN=Secure Global CA O=SecureTrust Corporation +# Label: "Secure Global CA" +# Serial: 9751836167731051554232119481456978597 +# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de +# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b +# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO Certification Authority O=COMODO CA Limited +# Label: "COMODO Certification Authority" +# Serial: 104350513648249232941998508985834464573 +# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 +# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b +# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE----- + +# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Label: "Network Solutions Certificate Authority" +# Serial: 116697915152937497490437556386812487904 +# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e +# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce +# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi +MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV +UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO +ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz +c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP +OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl +mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF +BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 +qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw +gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu +bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp +dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 +6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ +h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH +/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN +pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Label: "COMODO ECC Certification Authority" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GA CA" +# Serial: 86718877871133159090080555911823548314 +# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 +# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 +# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----END CERTIFICATE----- + +# Issuer: CN=Certigna O=Dhimyotis +# Subject: CN=Certigna O=Dhimyotis +# Label: "Certigna" +# Serial: 18364802974209362175 +# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff +# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 +# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Label: "Deutsche Telekom Root CA 2" +# Serial: 38 +# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 +# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf +# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc +MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj +IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB +IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE +RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl +U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 +IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU +ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC +QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr +rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S +NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc +QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH +txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP +BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC +AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp +tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa +IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl +6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ +xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc +# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc +# Label: "Cybertrust Global Root" +# Serial: 4835703278459682877484360 +# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 +# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 +# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG +A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh +bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE +ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 +7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS +J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y +HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP +t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz +FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY +XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw +hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js +MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA +A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj +Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx +XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o +omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc +A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Label: "ePKI Root Certification Authority" +# Serial: 28956088682735189655030529057352760477 +# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 +# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 +# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +# Issuer: O=certSIGN OU=certSIGN ROOT CA +# Subject: O=certSIGN OU=certSIGN ROOT CA +# Label: "certSIGN ROOT CA" +# Serial: 35210227249154 +# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 +# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b +# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G3" +# Serial: 28809105769928564313984085209975885599 +# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 +# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd +# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT +MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ +BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 +BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz ++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm +hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn +5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W +JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL +DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC +huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB +AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB +zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN +kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH +SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G +spki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G2" +# Serial: 71758320672825410020661621085256472406 +# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f +# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 +# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp +IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi +BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw +MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig +YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v +dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ +BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 +papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K +DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 +KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox +XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G3" +# Serial: 127614157056681299805556476275995414779 +# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 +# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 +# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB +rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV +BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa +Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl +LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u +MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm +gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 +YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf +b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 +9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S +zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk +OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA +2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW +oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c +KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM +m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu +MdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G2" +# Serial: 80682863203381065782177908751794619243 +# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a +# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 +# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL +MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj +KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 +MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw +NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV +BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL +So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal +tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG +CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT +qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz +rD6ogRLQy7rQkgu2npaqBA+K +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Universal Root Certification Authority" +# Serial: 85209574734084581917763752644031726877 +# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 +# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 +# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" +# Serial: 63143484348153506665311985501458640051 +# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 +# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a +# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp +U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg +SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln +biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm +GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve +fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ +aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj +aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW +kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC +4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga +FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) +# Label: "NetLock Arany (Class Gold) Főtanúsítvány" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G2" +# Serial: 10000012 +# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a +# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 +# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX +DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 +qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp +uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU +Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE +pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp +5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M +UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN +GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy +5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv +6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK +eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 +B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ +BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov +L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG +SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS +CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen +5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 +IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK +gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL ++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL +vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm +bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk +N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC +Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z +ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Label: "Hongkong Post Root CA 1" +# Serial: 1000 +# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca +# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 +# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx +FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg +Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG +A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr +b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ +jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn +PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh +ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 +nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h +q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED +MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC +mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 +7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB +oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs +EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO +fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi +AmvZWg== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Label: "SecureSign RootCA11" +# Serial: 1 +# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 +# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 +# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr +MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG +A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 +MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp +Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD +QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz +i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 +h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV +MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 +UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni +8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC +h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm +KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ +X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr +QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 +pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN +QSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" +# Serial: 6047274297262753887 +# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 +# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa +# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy +MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD +VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv +ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl +AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF +661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 +am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 +ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 +PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS +3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k +SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF +3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM +ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g +StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz +Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB +jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Label: "Chambers of Commerce Root - 2008" +# Serial: 11806822484801597146 +# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 +# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c +# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz +IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz +MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj +dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw +EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp +MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 +28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq +VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q +DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR +5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL +ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a +Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl +UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s ++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 +Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx +hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV +HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 ++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN +YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t +L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy +ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt +IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV +HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w +DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW +PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF +5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 +glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH +FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 +pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD +xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG +tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq +jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De +fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ +d0jQ +-----END CERTIFICATE----- + +# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Label: "Global Chambersign Root - 2008" +# Serial: 14541511773111788494 +# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 +# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c +# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx +MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy +cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG +A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl +BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed +KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 +G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 +zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 +ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG +HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 +Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V +yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e +beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r +6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog +zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW +BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr +ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp +ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk +cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt +YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC +CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI +hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ +UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz +X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x +fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz +a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd +Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd +SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O +AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso +M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge +v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Commercial O=AffirmTrust +# Subject: CN=AffirmTrust Commercial O=AffirmTrust +# Label: "AffirmTrust Commercial" +# Serial: 8608355977964138876 +# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 +# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 +# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Networking O=AffirmTrust +# Subject: CN=AffirmTrust Networking O=AffirmTrust +# Label: "AffirmTrust Networking" +# Serial: 8957382827206547757 +# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f +# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f +# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium O=AffirmTrust +# Subject: CN=AffirmTrust Premium O=AffirmTrust +# Label: "AffirmTrust Premium" +# Serial: 7893706540734352110 +# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 +# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 +# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust +# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust +# Label: "AffirmTrust Premium ECC" +# Serial: 8401224907861490260 +# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d +# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb +# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2011" +# Serial: 0 +# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 +# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d +# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix +RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p +YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw +NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK +EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl +cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz +dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ +fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns +bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD +75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP +FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV +HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp +5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu +b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA +A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p +6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 +dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys +Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI +l7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Label: "Actalis Authentication Root CA" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: O=Trustis Limited OU=Trustis FPS Root CA +# Subject: O=Trustis Limited OU=Trustis FPS Root CA +# Label: "Trustis FPS Root CA" +# Serial: 36053640375399034304724988975563710553 +# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d +# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 +# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL +ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx +MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc +MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ +AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH +iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj +vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA +0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB +OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ +BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E +FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 +GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW +zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 +1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE +f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F +jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN +ZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Label: "EE Certification Centre Root CA" +# Serial: 112324828676200291871926431888494945866 +# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f +# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 +# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 +MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 +czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG +CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy +MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl +ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS +b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy +euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO +bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw +WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d +MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE +1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ +zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB +BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF +BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV +v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG +E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW +iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v +GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Subject: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Label: "E-Tugra Certification Authority" +# Serial: 7667447206703254355 +# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 +# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 +# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC +aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV +BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 +Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz +MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ +BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp +em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY +B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH +D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF +Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo +q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D +k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH +fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut +dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM +ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 +zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX +U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 +Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 +XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF +Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR +HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY +GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c +77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 ++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK +vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 +FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl +yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P +AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD +y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d +NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 14367148294922964480859022125800977897474 +# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e +# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb +# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G3" +# Serial: 10003001 +# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 +# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc +# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Label: "Staat der Nederlanden EV Root CA" +# Serial: 10000013 +# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba +# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb +# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y +MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg +TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS +M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC +UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d +Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p +rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l +pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb +j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC +KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS +/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X +cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH +1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP +px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 +MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u +2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS +v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC +wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy +CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e +vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 +Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa +Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL +eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 +FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc +7uzXLg== +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Label: "Certinomis - Root CA" +# Serial: 1 +# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f +# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 +# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 +-----BEGIN CERTIFICATE----- +MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb +BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz +MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx +FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g +Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 +fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl +LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV +WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF +TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb +5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc +CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri +wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ +wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG +m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 +F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng +WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 +2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF +AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ +0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw +F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS +g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj +qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN +h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ +ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V +btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj +Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ +8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW +gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Label: "AC RAIZ FNMT-RCM" +# Serial: 485876308206448804701554682760554759 +# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d +# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 +# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 1 O=Amazon +# Subject: CN=Amazon Root CA 1 O=Amazon +# Label: "Amazon Root CA 1" +# Serial: 143266978916655856878034712317230054538369994 +# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 +# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 +# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 2 O=Amazon +# Subject: CN=Amazon Root CA 2 O=Amazon +# Label: "Amazon Root CA 2" +# Serial: 143266982885963551818349160658925006970653239 +# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 +# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a +# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 3 O=Amazon +# Subject: CN=Amazon Root CA 3 O=Amazon +# Label: "Amazon Root CA 3" +# Serial: 143266986699090766294700635381230934788665930 +# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 +# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e +# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 4 O=Amazon +# Subject: CN=Amazon Root CA 4 O=Amazon +# Label: "Amazon Root CA 4" +# Serial: 143266989758080763974105200630763877849284878 +# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd +# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be +# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A. +# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A. +# Label: "LuxTrust Global Root 2" +# Serial: 59914338225734147123941058376788110305822489521 +# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c +# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f +# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5 +-----BEGIN CERTIFICATE----- +MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL +BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV +BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw +MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B +LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F +ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem +hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1 +EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn +Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4 +zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ +96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m +j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g +DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+ +8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j +X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH +hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB +KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0 +Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT ++Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL +BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9 +BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO +jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9 +loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c +qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+ +2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/ +JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre +zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf +LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+ +x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6 +oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr +-----END CERTIFICATE----- + +# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" +# Serial: 1 +# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 +# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca +# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Label: "GDCA TrustAUTH R5 ROOT" +# Serial: 9009899650740120186 +# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 +# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 +# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor RootCert CA-1" +# Serial: 15752444095811006489 +# MD5 Fingerprint: 6e:85:f1:dc:1a:00:d3:22:d5:b2:b2:ac:6b:37:05:45 +# SHA1 Fingerprint: ff:bd:cd:e7:82:c8:43:5e:3c:6f:26:86:5c:ca:a8:3a:45:5b:c3:0a +# SHA256 Fingerprint: d4:0e:9c:86:cd:8f:e4:68:c1:77:69:59:f4:9e:a7:74:fa:54:86:84:b6:c4:06:f3:90:92:61:f4:dc:e2:57:5c +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29y +IFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkxMjMxMTcyMzE2WjCB +pDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFuYW1h +IENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUG +A1UECwweVHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZU +cnVzdENvciBSb290Q2VydCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAv463leLCJhJrMxnHQFgKq1mqjQCj/IDHUHuO1CAmujIS2CNUSSUQIpid +RtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4pQa81QBeCQryJ3pS/C3V +seq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0JEsq1pme +9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CV +EY4hgLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorW +hnAbJN7+KIor0Gqw/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/ +DeOxCbeKyKsZn3MzUOcwHwYDVR0jBBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5mDo4Nvu7Zp5I +/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZ +yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts +L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN +zl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor RootCert CA-2" +# Serial: 2711694510199101698 +# MD5 Fingerprint: a2:e1:f8:18:0b:ba:45:d5:c7:41:2a:bb:37:52:45:64 +# SHA1 Fingerprint: b8:be:6d:cb:56:f1:55:b9:63:d4:12:ca:4e:06:34:c7:94:b2:1c:c0 +# SHA256 Fingerprint: 07:53:e9:40:37:8c:1b:d5:e3:83:6e:39:5d:ae:a5:cb:83:9e:50:46:f1:bd:0e:ae:19:51:cf:10:fe:c7:c9:65 +-----BEGIN CERTIFICATE----- +MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEfMB0GA1UEAwwWVHJ1c3RDb3Ig +Um9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEyMzExNzI2MzlaMIGk +MQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEg +Q2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYD +VQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRy +dXN0Q29yIFJvb3RDZXJ0IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCnIG7CKqJiJJWQdsg4foDSq8GbZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+ +QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9NkRvRUqdw6VC0xK5mC8tkq +1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1oYxOdqHp +2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nK +DOObXUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hape +az6LMvYHL1cEksr1/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF +3wP+TfSvPd9cW436cOGlfifHhi5qjxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88 +oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQPeSghYA2FFn3XVDjxklb9tTNM +g9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+CtgrKAmrhQhJ8Z3 +mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh +8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAd +BgNVHQ4EFgQU2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6U +nrybPZx9mCAZ5YwwYrIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/hOsh80QA9z+LqBrWyOrsGS2h60COX +dKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnpkpfbsEZC89NiqpX+ +MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv2wnL +/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RX +CI/hOWB3S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYa +ZH9bDTMJBzN7Bj8RpFxwPIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW +2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dvDDqPys/cA8GiCcjl/YBeyGBCARsaU1q7 +N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYURpFHmygk71dSTlxCnKr3 +Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANExdqtvArB +As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp +5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu +1uwJ +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor ECA-1" +# Serial: 9548242946988625984 +# MD5 Fingerprint: 27:92:23:1d:0a:f5:40:7c:e9:e6:6b:9d:d8:f5:e7:6c +# SHA1 Fingerprint: 58:d1:df:95:95:67:6b:63:c0:f0:5b:1c:17:4d:8b:84:0b:c8:78:bd +# SHA256 Fingerprint: 5a:88:5d:b1:9c:01:d9:12:c5:75:93:88:93:8c:af:bb:df:03:1a:b2:d4:8e:91:ee:15:58:9b:42:97:1d:03:9c +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29y +IEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3MjgwN1owgZwxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3Ig +RUNBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb +3w9U73NjKYKtR8aja+3+XzP4Q1HpGjORMRegdMTUpwHmspI+ap3tDvl0mEDTPwOA +BoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23xFUfJ3zSCNV2HykVh0A5 +3ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmcp0yJF4Ou +owReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/ +wZ0+fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZF +ZtS6mFjBAgMBAAGjYzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAf +BgNVHSMEGDAWgBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEABT41XBVwm8nHc2Fv +civUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u/ukZMjgDfxT2 +AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50 +soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI +WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi +tJ/X5g== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Label: "SSL.com Root Certification Authority RSA" +# Serial: 8875640296558310041 +# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 +# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb +# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com Root Certification Authority ECC" +# Serial: 8495723813297216424 +# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e +# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a +# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority RSA R2" +# Serial: 6248227494352943350 +# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 +# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a +# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority ECC" +# Serial: 3182246526754555285 +# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 +# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d +# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Label: "GlobalSign Root CA - R6" +# Serial: 1417766617973444989252670301619537 +# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae +# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 +# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GC CA" +# Serial: 44084345621038548146064804565436152554 +# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 +# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 +# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +` + +var ErrParseFailed = errors.New("gocertifi: error when parsing certificates") + +// CACerts builds an X.509 certificate pool containing the Mozilla CA +// Certificate bundle. Returns nil on error along with an appropriate error +// code. +func CACerts() (*x509.CertPool, error) { + pool := x509.NewCertPool() + ok := pool.AppendCertsFromPEM([]byte(pemcerts)) + if !ok { + return nil, ErrParseFailed + } + return pool, nil +} diff --git a/vendor/github.com/certifi/gocertifi/certifi_test.go b/vendor/github.com/certifi/gocertifi/certifi_test.go new file mode 100644 index 000000000..a071ad86a --- /dev/null +++ b/vendor/github.com/certifi/gocertifi/certifi_test.go @@ -0,0 +1,10 @@ +package gocertifi + +import "testing" + +func TestGetCerts(t *testing.T) { + cert_pool, err := CACerts() + if (cert_pool == nil) || (err != nil) { + t.Errorf("Failed to return the certificates.") + } +} diff --git a/vendor/github.com/certifi/gocertifi/gen.go b/vendor/github.com/certifi/gocertifi/gen.go new file mode 100644 index 000000000..a3bbbd789 --- /dev/null +++ b/vendor/github.com/certifi/gocertifi/gen.go @@ -0,0 +1,74 @@ +// +build ignore + +package main + +import ( + "io/ioutil" + "log" + "net/http" + "os" + "text/template" + "time" +) + +func main() { + const url = "https://mkcert.org/generate/" + resp, err := http.Get(url) + if err != nil { + log.Fatal(err) + } + if resp.StatusCode != 200 { + log.Fatal("expected 200, got", resp.StatusCode) + } + defer resp.Body.Close() + + bundle, err := ioutil.ReadAll(resp.Body) + + fp, err := os.Create("certifi.go") + if err != nil { + log.Fatal(err) + } + defer fp.Close() + + tmpl.Execute(fp, struct { + Timestamp time.Time + URL string + Bundle string + }{ + Timestamp: time.Now(), + URL: url, + Bundle: string(bundle), + }) +} + +var tmpl = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT +// {{ .Timestamp }} +// {{ .URL }} + +package gocertifi + +//go:generate go run gen.go + +import ( + "crypto/x509" + "errors" +) + +const pemcerts string = ` + "`" + ` +{{ .Bundle }} +` + "`" + ` + +var ErrParseFailed = errors.New("gocertifi: error when parsing certificates") + +// CACerts builds an X.509 certificate pool containing the Mozilla CA +// Certificate bundle. Returns nil on error along with an appropriate error +// code. +func CACerts() (*x509.CertPool, error) { + pool := x509.NewCertPool() + ok := pool.AppendCertsFromPEM([]byte(pemcerts)) + if !ok { + return nil, ErrParseFailed + } + return pool, nil +} +`)) diff --git a/vendor/github.com/coreos/etcd/.dockerignore b/vendor/github.com/coreos/etcd/.dockerignore new file mode 100644 index 000000000..6b8710a71 --- /dev/null +++ b/vendor/github.com/coreos/etcd/.dockerignore @@ -0,0 +1 @@ +.git diff --git a/vendor/github.com/coreos/etcd/.github/ISSUE_TEMPLATE.md b/vendor/github.com/coreos/etcd/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..c8c894c26 --- /dev/null +++ b/vendor/github.com/coreos/etcd/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +# Bug reporting + +A good bug report has some very specific qualities, so please read over our short document on [reporting bugs][report_bugs] before submitting a bug report. + +To ask a question, go ahead and ignore this. + +[report_bugs]: https://github.com/coreos/etcd/blob/master/Documentation/reporting_bugs.md diff --git a/vendor/github.com/coreos/etcd/.github/PULL_REQUEST_TEMPLATE.md b/vendor/github.com/coreos/etcd/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..4fa34e9b3 --- /dev/null +++ b/vendor/github.com/coreos/etcd/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +# Contributing guidelines + +Please read our [contribution workflow][contributing] before submitting a pull request. + +[contributing]: https://github.com/coreos/etcd/blob/master/CONTRIBUTING.md#contribution-flow diff --git a/vendor/github.com/coreos/etcd/.gitignore b/vendor/github.com/coreos/etcd/.gitignore new file mode 100644 index 000000000..1a68387a7 --- /dev/null +++ b/vendor/github.com/coreos/etcd/.gitignore @@ -0,0 +1,22 @@ +/agent-* +/coverage +/covdir +/gopath +/gopath.proto +/go-bindata +/release +/machine* +/bin +.Dockerfile-test +.vagrant +*.etcd +*.log +/etcd +*.swp +/hack/insta-discovery/.env +*.test +tools/functional-tester/docker/bin +hack/scripts-dev/docker-dns/.Dockerfile +hack/scripts-dev/docker-dns-srv/.Dockerfile +hack/tls-setup/certs +.idea diff --git a/vendor/github.com/coreos/etcd/.godir b/vendor/github.com/coreos/etcd/.godir new file mode 100644 index 000000000..00ff6aa80 --- /dev/null +++ b/vendor/github.com/coreos/etcd/.godir @@ -0,0 +1 @@ +github.com/coreos/etcd diff --git a/vendor/github.com/coreos/etcd/.header b/vendor/github.com/coreos/etcd/.header new file mode 100644 index 000000000..0446af6d8 --- /dev/null +++ b/vendor/github.com/coreos/etcd/.header @@ -0,0 +1,13 @@ +// Copyright 2016 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/vendor/github.com/coreos/etcd/.semaphore.sh b/vendor/github.com/coreos/etcd/.semaphore.sh new file mode 100755 index 000000000..1a6c85a62 --- /dev/null +++ b/vendor/github.com/coreos/etcd/.semaphore.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +TEST_SUFFIX=$(date +%s | base64 | head -c 15) + +TEST_OPTS="RELEASE_TEST=y INTEGRATION=y PASSES='build unit release integration_e2e functional' MANUAL_VER=v3.2.11" +if [ "$TEST_ARCH" == "386" ]; then + TEST_OPTS="GOARCH=386 PASSES='build unit integration_e2e'" +fi + +docker run \ + --rm \ + --volume=`pwd`:/go/src/github.com/coreos/etcd \ + gcr.io/etcd-development/etcd-test:go1.8.5 \ + /bin/bash -c "${TEST_OPTS} ./test 2>&1 | tee test-${TEST_SUFFIX}.log" + +! egrep "(--- FAIL:|panic: test timed out|appears to have leaked)" -B50 -A10 test-${TEST_SUFFIX}.log diff --git a/vendor/github.com/coreos/etcd/.travis.yml b/vendor/github.com/coreos/etcd/.travis.yml new file mode 100644 index 000000000..00f2b8102 --- /dev/null +++ b/vendor/github.com/coreos/etcd/.travis.yml @@ -0,0 +1,88 @@ +language: go +go_import_path: github.com/coreos/etcd + +sudo: required + +services: docker + +go: +- 1.8.5 +- tip + +notifications: + on_success: never + on_failure: never + +env: + matrix: + - TARGET=amd64 + - TARGET=amd64-go-tip + - TARGET=darwin-amd64 + - TARGET=windows-amd64 + - TARGET=arm64 + - TARGET=arm + - TARGET=386 + - TARGET=ppc64le + +matrix: + fast_finish: true + allow_failures: + - go: tip + env: TARGET=amd64-go-tip + exclude: + - go: 1.8.5 + env: TARGET=amd64-go-tip + - go: tip + env: TARGET=amd64 + - go: tip + env: TARGET=darwin-amd64 + - go: tip + env: TARGET=windows-amd64 + - go: tip + env: TARGET=arm + - go: tip + env: TARGET=arm64 + - go: tip + env: TARGET=386 + - go: tip + env: TARGET=ppc64le + +before_install: +- docker pull gcr.io/etcd-development/etcd-test:go1.8.5 + +install: +- pushd cmd/etcd && go get -t -v ./... && popd + +script: + - > + case "${TARGET}" in + amd64) + docker run --rm \ + --volume=`pwd`:/go/src/github.com/coreos/etcd gcr.io/etcd-development/etcd-test:go1.8.5 \ + /bin/bash -c "GOARCH=amd64 ./test" + ;; + amd64-go-tip) + GOARCH=amd64 ./test + ;; + darwin-amd64) + docker run --rm \ + --volume=`pwd`:/go/src/github.com/coreos/etcd gcr.io/etcd-development/etcd-test:go1.8.5 \ + /bin/bash -c "GO_BUILD_FLAGS='-a -v' GOOS=darwin GOARCH=amd64 ./build" + ;; + windows-amd64) + docker run --rm \ + --volume=`pwd`:/go/src/github.com/coreos/etcd gcr.io/etcd-development/etcd-test:go1.8.5 \ + /bin/bash -c "GO_BUILD_FLAGS='-a -v' GOOS=windows GOARCH=amd64 ./build" + ;; + 386) + docker run --rm \ + --volume=`pwd`:/go/src/github.com/coreos/etcd gcr.io/etcd-development/etcd-test:go1.8.5 \ + /bin/bash -c "GOARCH=386 PASSES='build unit' ./test" + ;; + *) + # test building out of gopath + docker run --rm \ + --volume=`pwd`:/go/src/github.com/coreos/etcd gcr.io/etcd-development/etcd-test:go1.8.5 \ + /bin/bash -c "GO_BUILD_FLAGS='-a -v' GOARCH='${TARGET}' ./build" + ;; + esac diff --git a/vendor/github.com/coreos/etcd/CONTRIBUTING.md b/vendor/github.com/coreos/etcd/CONTRIBUTING.md new file mode 100644 index 000000000..635f73a30 --- /dev/null +++ b/vendor/github.com/coreos/etcd/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# How to contribute + +etcd is Apache 2.0 licensed and accepts contributions via GitHub pull requests. This document outlines some of the conventions on commit message formatting, contact points for developers, and other resources to help get contributions into etcd. + +# Email and chat + +- Email: [etcd-dev](https://groups.google.com/forum/?hl=en#!forum/etcd-dev) +- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org + +## Getting started + +- Fork the repository on GitHub +- Read the README.md for build instructions + +## Reporting bugs and creating issues + +Reporting bugs is one of the best ways to contribute. However, a good bug report has some very specific qualities, so please read over our short document on [reporting bugs](https://github.com/coreos/etcd/blob/master/Documentation/reporting_bugs.md) before submitting a bug report. This document might contain links to known issues, another good reason to take a look there before reporting a bug. + +## Contribution flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where to base the contribution. This is usually master. +- Make commits of logical units. +- Make sure commit messages are in the proper format (see below). +- Push changes in a topic branch to a personal fork of the repository. +- Submit a pull request to coreos/etcd. +- The PR must receive a LGTM from two maintainers found in the MAINTAINERS file. + +Thanks for contributing! + +### Code style + +The coding style suggested by the Golang community is used in etcd. See the [style doc](https://github.com/golang/go/wiki/CodeReviewComments) for details. + +Please follow this style to make etcd easy to review, maintain and develop. + +### 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 can easily be killed and started for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +