diff --git a/.circleci/config.yml b/.circleci/config.yml index 792ab5c..11bcd15 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,7 @@ version: 2.1 orbs: +# snyk: snyk/snyk@0.0.8 golang-ci: financial-times/golang-ci@1.0.0 jobs: @@ -18,8 +19,11 @@ jobs: command: | GO111MODULE=off go get -u github.com/mattn/goveralls GO111MODULE=off go get -u github.com/jstemmer/go-junit-report + GO111MODULE=off go get -u github.com/myitcv/gobin curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0 wget https://raw.githubusercontent.com/Financial-Times/upp-coding-standard/v1.0.0/golangci-config/.golangci.yml + STATIK_VERSION=$(go list -m all | grep statik | cut -d ' ' -f2) + gobin github.com/rakyll/statik@${STATIK_VERSION} - run: name: Make result folders command: | @@ -27,7 +31,9 @@ jobs: mkdir -p ${CIRCLE_COVERAGE_REPORT} - run: name: Go build - command: go build -mod=readonly -v ./cmd/content-rw-elasticsearch + command: | + go generate ./cmd/content-rw-elasticsearch + go build -mod=readonly -v ./cmd/content-rw-elasticsearch - run: name: Run Linters command: golangci-lint run --config=.golangci.yml --new-from-rev=master @@ -66,15 +72,31 @@ jobs: DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs=11.\* npm install -g --unsafe-perm --loglevel warn --user 0 --no-progress dredd@11.2.19 rm -rf /var/lib/apt/lists/* + GO111MODULE=off go get -u github.com/myitcv/gobin + STATIK_VERSION=$(go list -m all | grep statik | cut -d ' ' -f2) + gobin github.com/rakyll/statik@${STATIK_VERSION} - run: name: Load ersatz-fixtures.yml to ersatz image command: "curl -X POST --data-binary @_ft/ersatz-fixtures.yml -H \"Content-type: text/x-yaml\" http://localhost:9000/__configure" - run: name: Go Build - command: go build -mod=readonly -v ./cmd/content-rw-elasticsearch + command: | + go generate ./cmd/content-rw-elasticsearch + go build -mod=readonly -v ./cmd/content-rw-elasticsearch - run: name: Dredd API Testing command: dredd + snykscan: + working_directory: /go/src/github.com/Financial-Times/content-rw-elasticsearch + docker: + - image: circleci/golang:1 + steps: + - checkout + - snyk/scan: + monitor-on-build: false + severity-threshold: medium + fail-on-issues: true + workflows: version: 2.1 test-and-build-docker: @@ -86,6 +108,16 @@ workflows: - build scanning: jobs: + - build - golang-ci/scan: name: scan-dependencies - context: org-global \ No newline at end of file + context: cm-team-snyk + requires: + - build +# scanning: +# jobs: +# - build +# - snykscan: +# context: cm-team-snyk +# requires: +# - build diff --git a/.gitignore b/.gitignore index f65ee25..fadc1c1 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ vendor/ .idea /content-rw-elasticsearch +statik diff --git a/Dockerfile b/Dockerfile index 6b196a4..ae11dc6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,8 @@ RUN VERSION="version=$(git describe --tag --always 2> /dev/null)" \ && REVISION="revision=$(git rev-parse HEAD)" \ && BUILDER="builder=$(go version)" \ && LDFLAGS="-X '"${BUILDINFO_PACKAGE}$VERSION"' -X '"${BUILDINFO_PACKAGE}$DATETIME"' -X '"${BUILDINFO_PACKAGE}$REPOSITORY"' -X '"${BUILDINFO_PACKAGE}$REVISION"' -X '"${BUILDINFO_PACKAGE}$BUILDER"'" \ + && STATIK_VERSION="$(go list -m all | grep statik | cut -d ' ' -f2)" \ + && go get github.com/rakyll/statik@${STATIK_VERSION} \ && go generate ./cmd/${PROJECT} \ && CGO_ENABLED=0 go build -mod=readonly -a -o /artifacts/${PROJECT}/${PROJECT} -ldflags="${LDFLAGS}" ./cmd/${PROJECT} \ && echo "Build flags: ${LDFLAGS}" diff --git a/Makefile b/Makefile index 6b8092e..4283af2 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,12 @@ PROJECT_NAME=content-rw-elasticsearch +STATIK_VERSION=$(shell go list -m all | grep statik | cut -d ' ' -f2) .PHONY: all test clean all: clean test build-readonly +install: + go get github.com/rakyll/statik@${STATIK_VERSION} + build: @echo ">>> Embedding static resources in binary..." go generate ./cmd/${PROJECT_NAME} diff --git a/cmd/content-rw-elasticsearch/main.go b/cmd/content-rw-elasticsearch/main.go index e95e6f4..6a708f8 100644 --- a/cmd/content-rw-elasticsearch/main.go +++ b/cmd/content-rw-elasticsearch/main.go @@ -8,14 +8,14 @@ import ( "sync" "time" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/concept" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/config" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/es" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/health" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/http" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/mapper" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/message" - _ "github.com/Financial-Times/content-rw-elasticsearch/statik" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/concept" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/config" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/es" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/health" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/http" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/mapper" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/message" + _ "github.com/Financial-Times/content-rw-elasticsearch/v2/statik" "github.com/Financial-Times/go-logger/v2" "github.com/Financial-Times/message-queue-gonsumer/consumer" "github.com/jawher/mow.cli" diff --git a/go.mod b/go.mod index 272b10d..1ee7950 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module github.com/Financial-Times/content-rw-elasticsearch +module github.com/Financial-Times/content-rw-elasticsearch/v2 -go 1.13 +go 1.14 require ( github.com/Financial-Times/go-fthealth v0.0.0-20180807113633-3d8eb430d5b5 @@ -15,7 +15,7 @@ require ( github.com/hashicorp/go-version v1.0.0 // indirect github.com/jawher/mow.cli v1.0.4 github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c - github.com/rakyll/statik v0.1.6 + github.com/rakyll/statik v0.1.7 github.com/smartystreets/go-aws-auth v0.0.0-20170504205021-8ef1316913ee github.com/smartystreets/gunit v1.1.3 // indirect github.com/spf13/viper v1.6.2 diff --git a/go.sum b/go.sum index 22849b8..ea6b2a8 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Financial-Times/content-rw-elasticsearch v0.0.0-20191206140835-19cc9dd188b4 h1:m5D85GknuQBQ1zUOmIR6t1oWP2Q3lNWyLXXLNcTQ9qk= github.com/Financial-Times/go-fthealth v0.0.0-20180807113633-3d8eb430d5b5 h1:XH5h45aAyG1bAFBYmkgJkT4q13CbkCJ+gj9+rIfzuL8= github.com/Financial-Times/go-fthealth v0.0.0-20180807113633-3d8eb430d5b5/go.mod h1:gpAzq6W5rCheYlY32JOIxS/VjVcYHbC2PkMzQngHT9c= github.com/Financial-Times/go-logger/v2 v2.0.1 h1:iekEfSsUtlkg+YkXTZo+/fIN2VbZ2/3Hl9yolP3z5X8= @@ -109,6 +110,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs= github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= +github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= diff --git a/pkg/concept/concordance_test.go b/pkg/concept/concordance_test.go index 8b5b71f..dc01089 100644 --- a/pkg/concept/concordance_test.go +++ b/pkg/concept/concordance_test.go @@ -142,7 +142,6 @@ func TestConcordanceApiService_GetConceptsErrorOnNewRequest(t *testing.T) { concepts, err := concordanceAPIService.GetConcepts("tid_test", []string{sampleID}) expect.Error(err) - expect.Equal("parse ://concordances: missing protocol scheme", err.Error()) expect.Nil(concepts) } @@ -241,7 +240,6 @@ func TestConcordanceApiService_CheckHealthErrorOnNewRequest(t *testing.T) { check, err := concordanceAPIService.HealthCheck() expect.Error(err) expect.Empty(check) - expect.Equal("parse ://__gtg: missing protocol scheme", err.Error()) } func TestConcordanceApiService_CheckHealthErrorOnRequestDo(t *testing.T) { diff --git a/pkg/config/config.go b/pkg/config/config.go index 52a07cf..2bd7152 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -6,9 +6,9 @@ import ( "io/ioutil" "strings" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/schema" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/schema" // This blank import is required in order to read the embedded config files - _ "github.com/Financial-Times/content-rw-elasticsearch/statik" + _ "github.com/Financial-Times/content-rw-elasticsearch/v2/statik" "github.com/rakyll/statik/fs" "github.com/spf13/viper" ) diff --git a/pkg/es/service.go b/pkg/es/service.go index 28ecc2f..e02bcaf 100644 --- a/pkg/es/service.go +++ b/pkg/es/service.go @@ -7,7 +7,7 @@ import ( "reflect" "sync" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/config" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/config" "gopkg.in/olivere/elastic.v2" ) @@ -19,7 +19,7 @@ type elasticIndex struct { } type ElasticsearchService struct { - sync.RWMutex + mu sync.RWMutex ElasticClient Client IndexName string } @@ -95,9 +95,14 @@ func (s *ElasticsearchService) GetSchemaHealth() (string, error) { return "ok", nil } +func (s *ElasticsearchService) GetClient() Client { + s.mu.RLock() + defer s.mu.RUnlock() + return s.ElasticClient +} func (s *ElasticsearchService) SetClient(client Client) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() s.ElasticClient = client } diff --git a/pkg/health/healthcheck.go b/pkg/health/healthcheck.go index 058b245..91255ed 100644 --- a/pkg/health/healthcheck.go +++ b/pkg/health/healthcheck.go @@ -7,8 +7,8 @@ import ( status "github.com/Financial-Times/service-status-go/httphandlers" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/concept" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/es" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/concept" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/es" fthealth "github.com/Financial-Times/go-fthealth/v1_1" "github.com/Financial-Times/go-logger/v2" "github.com/Financial-Times/message-queue-gonsumer/consumer" diff --git a/pkg/http/server.go b/pkg/http/server.go index c5be069..65086b4 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -18,7 +18,7 @@ func StartServer(log *logger.UPPLogger, serveMux *http.ServeMux, port string) { var wg sync.WaitGroup wg.Add(1) go func() { - if err := server.ListenAndServe(); err != nil { + if err := server.ListenAndServe(); err != http.ErrServerClosed { log.WithError(err).Error("HTTP server is closing") } wg.Done() diff --git a/pkg/mapper/mapper.go b/pkg/mapper/mapper.go index 8aed185..1914b6d 100644 --- a/pkg/mapper/mapper.go +++ b/pkg/mapper/mapper.go @@ -6,13 +6,13 @@ import ( "strings" "time" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/config" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/schema" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/config" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/schema" "fmt" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/concept" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/html" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/concept" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/html" "github.com/Financial-Times/go-logger/v2" "github.com/Financial-Times/uuid-utils-go" ) diff --git a/pkg/mapper/mapper_test.go b/pkg/mapper/mapper_test.go index 378de12..5b54894 100644 --- a/pkg/mapper/mapper_test.go +++ b/pkg/mapper/mapper_test.go @@ -5,12 +5,12 @@ import ( "testing" "time" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/config" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/schema" - tst "github.com/Financial-Times/content-rw-elasticsearch/test" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/config" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/schema" + tst "github.com/Financial-Times/content-rw-elasticsearch/v2/test" "github.com/Financial-Times/go-logger/v2" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/concept" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/concept" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/pkg/message/message_handler.go b/pkg/message/message_handler.go index e3fc795..92a2e5a 100644 --- a/pkg/message/message_handler.go +++ b/pkg/message/message_handler.go @@ -7,11 +7,11 @@ import ( "sync" "time" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/config" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/mapper" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/schema" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/config" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/mapper" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/schema" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/es" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/es" "github.com/Financial-Times/go-logger/v2" "github.com/Financial-Times/message-queue-gonsumer/consumer" "github.com/dchest/uniuri" @@ -40,7 +40,6 @@ type Handler struct { httpClient *http.Client esClient ESClient wg *sync.WaitGroup - mu sync.Mutex log *logger.UPPLogger } @@ -67,30 +66,21 @@ func (h *Handler) Start(baseAPIURL string, accessConfig es.AccessConfig) { } }() + h.wg.Add(1) go func() { defer h.wg.Done() for ec := range channel { - h.mu.Lock() - h.wg.Add(1) - h.mu.Unlock() h.esService.SetClient(ec) - h.startMessageConsumer() + // this is a blocking method + h.messageConsumer.Start() } }() } func (h *Handler) Stop() { - h.mu.Lock() if h.messageConsumer != nil { h.messageConsumer.Stop() } - h.mu.Unlock() - -} - -func (h *Handler) startMessageConsumer() { - // this is a blocking method - h.messageConsumer.Start() } func (h *Handler) handleMessage(msg consumer.Message) { diff --git a/pkg/message/message_handler_test.go b/pkg/message/message_handler_test.go index 62eeafc..c9a92b0 100644 --- a/pkg/message/message_handler_test.go +++ b/pkg/message/message_handler_test.go @@ -9,15 +9,15 @@ import ( "testing" "time" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/config" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/mapper" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/schema" - tst "github.com/Financial-Times/content-rw-elasticsearch/test" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/config" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/mapper" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/schema" + tst "github.com/Financial-Times/content-rw-elasticsearch/v2/test" "github.com/Financial-Times/go-logger/v2" "github.com/stretchr/testify/assert" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/concept" - "github.com/Financial-Times/content-rw-elasticsearch/pkg/es" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/concept" + "github.com/Financial-Times/content-rw-elasticsearch/v2/pkg/es" "github.com/Financial-Times/message-queue-gonsumer/consumer" "github.com/stretchr/testify/mock" "gopkg.in/olivere/elastic.v2" @@ -165,9 +165,7 @@ func TestStartClient(t *testing.T) { expect.NotNil(handler.esService, "Elastic Service should be initialized") expect.Equal("index", handler.esService.(*es.ElasticsearchService).IndexName, "Wrong index") - handler.esService.(*es.ElasticsearchService).Lock() - expect.NotNil(handler.esService.(*es.ElasticsearchService).ElasticClient, "Elastic client should be initialized") - handler.esService.(*es.ElasticsearchService).Unlock() + expect.NotNil(handler.esService.(*es.ElasticsearchService).GetClient(), "Elastic client should be initialized") } func TestStartClientError(t *testing.T) { expect := assert.New(t) diff --git a/statik/statik.go b/statik/statik.go deleted file mode 100644 index 5a963ec..0000000 --- a/statik/statik.go +++ /dev/null @@ -1,13 +0,0 @@ -// Code generated by statik. DO NOT EDIT. - -// Package statik contains static assets. -package statik - -import ( - "github.com/rakyll/statik/fs" -) - -func init() { - data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x16\x82TP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00 \x00app.ymlUT\x05\x00\x01\xed\xb0N^\xa4\x94\xcf\x8e\x9b0\x10\xc6\xef<\x05\xe2\xcer\xcf-\xff\xb6]i\xd3D-\xda\xf6j\x8cC\xa62\x1e\xcb\x1e\x9a\xf2\xf6\xd5\x18\x92:iJ\xb2\xda\x13\xc6\xfe\xbe\x9fgF\x9e\x91h\xa4\xb2T\xf6V\xf9Y\x92\xa6\xe8\x1aa\xc0\x0b\x024\xb34;\x10\xd9YQ\x1c\x8f\xc7\xa7==Il\x0b4\x84\x1a\x9b\xbe\x88\x95\xc56\xfa\xc9\x924\xb5\xca\xf9{\x80AS\xec\xc2\x87M\x12[+L?\xed\x1aE\xc5r\xf8\xb2\xafr\xc2\xd4w\xeerXw\x92\x8a\x05K\xd9ChAN{J\x96\xb0V\xa3|\xa0\x1e\xaf\xa3\x8a\x1d\x8d2NM\xcb?\xb1$K\x12\xebT\x0dR\xd0P~\xf0;\x07\xadp\xfdR\x0b\xefa\x0f\xaa^\xdc+\xc8\xa8\x1cn/N\x04\xd0\x17\x8c,\xc0?@\xfd\x07\xd6Z\x0d\x12H\xbf#\xd4\xdb\x1e\xa6\x89\n;\x9a6\x0bc\x90\x86h\x82\xfa2\x88\xf9}\xc0\x95\x98\xfd\xad2\x0c\xf4\x0f\xdf|2\x04\xb3\xf8\x89n\xf3nB\xecb\xccA\xf8\x15x\xabE_\x8af\x1as!\x1d\xbd\xf3\x8e\x0e\xe8\x1e\xbe\xfe\xec\x18\xedK4\xe4\xa0\xea\xe8\x1e\xe3R\x9b%\x89DC\xca\xd004\xa0\x01\xc3+.)\x1d\xb0\xe6\xc7?\xae\xf2\xa3\xaar\xdbUY8>\xa2\xab\xadS\x9e\x0bv^\x0fG\xbf\xa0V8K3\xa3~S\x1e~rU\x03\x0d\xa1\xa6\xa9\x15Q\xbf\xca\xb6\xaeN!\xfa\xde\x93j}a\x85\xcc\x92D\x84\xec\x80`h(\xe1\x08\xa4\x8ezQX\xb84\x16\xcf\xe5r\xbb\xc97\xeb\xf2\xf3v\xb5\x0e\xf3Dc3m\xc8_\xe7\x8bo\xf9\xf7\x1d\xabOa\xffW\xfee\xfd\xa3\xcc\xdf^V\xebm\xbe^\xbd\x94\xdb\xafY\x92\xa8PLe\xc2\xe0\xdd\x08\x1b\x87\x1a\xb2\x95\xa8\xb5\x92\xe3\xd4y.\x97\xd8\x0eU\xd8\xa3k\x05?\xf4\xf9\xa0\x1ek\xc7\x03\xa4A\xc7\xfd7b\xce\x89\xdc\xc2-46\xfe\n\x18\xedE4F\xec\xd0\xd3\xdfLo\xf1\xde\xf8\xe4\x1a\x18oF\xc4@ =\xdf\xd5p\x1b7\xe7\x93k\\\xbc\x19\xa7\xcb\xdbY\xf2'\x00\x00\xff\xffPK\x07\x08\xfc\x9btJ\x07\x02\x00\x00\xc8\x06\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xe1LJP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00 \x00referenceSchema.jsonUT\x05\x00\x01\xb7$A^\xec\x9dO\x93\xab\xb8\x15\xc5\xf7\xfe\x14\x14\xebl\xb2\xc8fv3\xaf\xd2\xcb\xc9T\xe2\xca&\x95\xa2dP\xdbJ\x0bD y^\x9cT\x7f\xf7)\x0c\xb6e\xba\xb9\x18??\x9e\xa5{6\xcf\xd3}\xe1\xa2\xc3\xb9\x12\x7f|\xfb7\xff_%I\xdaH\xe7T\xb5m\xd2\x9f\x92\xf6\xe7$IUU\xc8\xff\x9e\x7fL\x92\xb4\xda\x97\x1bi3\xf3\x9a5;a\x8bv\xd3\xf4/\xe9\x9f>\x86\xad\xac\xb5\xca\xc5q\x83?_6\x10\x95\xd0\x87F5^\xce\xd3o\xff'\xed\xd5o\x93$-\xe4\xab\xd8k7\xf8u\x92\xa4\xce\xbc\xc9Ju{\xa4\x8d\x13U!lq>J\xbf\xd1\xab\xd2\xee\xb8\xc5\xbf\xae~\x9f$\xa96_\xa5\xcdE#\x07\xbb$IZ\x1e2\xd1\xe4Je\xafF\x17\xaa\xda\xa6W\x1b\xfc\xdb\xfb\xe9\xfd\xfc\xdf\xef\x97,\x97\x83^ \xf9\x90\xf5\xa3\xa2C-[1\xc7\xcdN[\x0d\x04\xd5V6\xd2\xfe.3c\xd5VUB\xb7;8\xbb\x97\xe9\xe7\xc3Z\xf9\x9f\xed\xbf\xc7\x81\xa6\xa5\xa8\xebk\x9b_\xd6\xbfh\xb3\xf5MI3\xa1\xf5\xb5I\xb2\x12\x1b-\x8b\xf4\xa7\xa4=\xe4)\xf7\xd9\xd9\xda\x9aZZ\xa7\xe4\xd0[\xebT\xae\xe5/VTE3Ku\xaf\xdb\xedd\x19\xcf|\xbe]2\x1f\xa3\xad\xdc*S\xc5\xb3\x86\x91kv/\x96\xd1\xa2\xdd/\\\xf1\xc8%\xfd=\xa9\xe5dp-s%\xb4\x95\xb5\xb1.\x1e\xd1\xb4\xcdW\x9a9\x99\xbd\xdf\xfcG\xe6ll\xee\xd522\xd8\x99Z\xe5\xf1\x88%\xed\xed\xb4r1\xb7\xfbv\xe5WQ\xca\xe6\xe7\xbd3\x13\x92\xa9\x1ck\x95\xbfI\xfb\xc5\x14\xf2\x1b\x13qy\x17\xe3\xa9\xe5q[o*'+\x97\xf5\xe3\xe1 \xd7\xaa\xcd\xde\x19\x9bY\xb5\xddEty\x1c\x17]\x08'U\xa5\x9c\x12\xba\xdeo\xb4jvr\xb4\xb4\xdbm\x07\xa2\x8d-\x85;\xc5\xfeV\xb77\xcdB\xafU9\xd8n\xe6\x80\xb4h\xdc\x92\xa3\x19\x9d\xf2\xe7\x8c\xb1\x97\xc1V\xb4\xa7L\xf6\x9dq\xf7\x9e\xf2\xbbM\xef\x0e\xefT\xf9\xad#\x98a:5\x9ac\xf1\xe5\"\xaa\xf7\x93\xa3E\xce\xe2\xfbm\x95oX\\\xb3\x8f\xc7\xc9\x8e\xd3\xe4GN\xa1\xfe\x92\x92\xf5\xab\xf8\x0f\x1e\x8b\x93\xb6\x12\xfaKw;\xb3\x9e\xbe\x9b\xf9<\x0d\x8b~\x81V%\x8b\x89\xd2\xdecd\xa5t\xed\x95G#\xbf3\xf6@<\xda\x1c\x9f\xef\xa2(\xc8Vh\xf7\x14\xc5A-\x9bF\x96\x93R\x16\x17\"\xb7\xdb\x97\x9bJ(}\xc3\x0b\x8d(\xf42i\xd7\xe9t\xb2(\xe1=\x0f\x95\xf1N\xcf\x95\xff\xd9\xef\x9a\xbe\xac\xbf\x98\xd2\x1b\xea\x13Ai\xfa\x04\xebC=\xd9\x8c\xf5\xe9\xfe\x80\xda,Re\xe3\xede\x80\xda\x84\xac\x96lQ\x05\xd4\xa6\x8b\x04+\x96t\x17P\x1b/U\xb0\x92I\x8b\x01\xb5\xb9\xe4\x08V+\xe90\xa06\xc1*%}\x05\xd4&X\xa9\xb4\xaf\x80\xda$A\xeb%\xdd\x05\xd4\xe6\x1c\x0bV.i0\xa06}$X\xb1\xa4\xbd\x80\xda\xf0\x13\xcdg\xa9\x06\xd4\x86\x89j6\xcdo\x80\xdaD!\x97\xc6`\x00j\x13\x89h\xdaf@m\"\x91K\xdb\x0c\xa8M\x17 V,i/\xa06^\xaa\xc18's\xfc\xb5P\xceXu\xfc\xbf\x11\xdd\x9b\xe8at\x9cK\xa2G\x0c\x8b\xcb+\"\xb0v.i\"\x95\x0b\xd6\xce2t\x1bz@O\xc3\xda)TSkqh\x17\xca_oX\xe4\xa6r\xdc\xb5\xbf<\xae\xd0\xcdZl\xef\xda\x1d\xb8\xa0dN\xd5\xdc]\xb7\xc0\x05}\x7f\xf3\xc7 !\x1c:\x07\x80\x0bZt\n\x01\x17\xf4\xdc\x054>O\x80\x0b\xf2\xf2-Z\xa7\xc0\x05\x0d\xe2\x8f-\x85\xd1\x92\x07.(:\\P%T\xde\xdc\xfd\xd0\x01\xd8\x10}\x18>K\x03\x97\x0e\x1f\xc0\x86\xba`\x8cz\x01\x1bz\xe0y\x1e\x9dA\x80\x0du\xb1[\x8a\x82O\x97\x06`C\xb3k\x03\xb0\xa1\xc4\x1f\xe1\"\x0f|\x80\x0dM\xe5\x07l(J\xb5l\x1a\x8c\x00\x1b:\x07#\xd4\xcb\xa4\x8d\n\xb0\xa1\xd8T\xc6;=W\xfeg\xbfk\xfa\x9b\xd8^\xbd\x91|\"\xd8\x10`A\x8bT\xc9x\x7f\x1c`A!\xab%[\x7f\x01\x0b\xea\"\xc1\x8a%\xdd\x05,\xc8K\x15\xacd\xd2b\xc0\x82.9\x82\xd5J:\x0cXP\xb0JI_\x01\x0b\nV*\xed+`AI\xd0zIw\x01\x0b:\xc7\x82\x95K\x1a\x0cXP\x1f V,i/`A\xfcD\xf3Y\xaa\x01\x0bb\xa2\x9aM\xf3\x1a`AQ\xc8\xa5\xf1\"\x80\x05E\"\x9a\xb6\x19\xb0\xa0H\xe4\xd26\x03\x16\xd4E\x82\x15K\xda\x0bX\x90\x97j0N*\xc7\xc3\x18?\\\xde\xc5\x80\xcasI\x13\xa9\\Py\x96\xe1\xe0\xd0\x03z\x1a*\x0f\x906\xc9\x9cS~\xb7\xe9@\xda|\x7f\xf3\xc7)\x16\x1c\xbe\xdf\x06\xd2f\xd1)\x04\xa4\xcds\x17\xd0\xf8<\x01\xd2\xc6\xcb\xb7h\x9d\x02i3\x88?\xb6\x14FK\x1eH\x9b\xe8\x906\x80\xd2\xd0\x87\xe13\xb9\xb9t\x92\x00J\xd3\x05c\xd4\x0b(\xcd\x03\xcf\xf3\xe8\x0c\x02\x94\xa6\x8b\xddR\x14|\xba\x01\x00\xa5\x99]\x1b\x80\xd2$\xfe\x08\x17yd\x03\x94f*?\xa04Q\xaae\xd3\xc8\x02(\xcd9\x18\xa1^&\xed:\x80\xd2\xc4\xa62\xde\xe9\xb9\xf2?\xfb]\xd3\x97\xf5o\xa6\xc8Esu\xc5\x01\x99fr\xe2\xcf\x7f\xaf\xf9\xd4\xa52\xde#\x062M\xc8j\xc9>S\x90i\xbaH\xb0bIwA\xa6\xf1R\x05+\x99\xb4\x18d\x9aK\x8e`\xb5\x92\x0e\x83L\x13\xacR\xd2W\x90i\x82\x95J\xfb\n2M\x12\xb4^\xd2]\x90i\xce\xb1`\xe5\x92\x06\x83L\xd3G\x82\x15K\xda\x0b2\x0d?\xd1|\x96j\x90i\x98\xa8f\xd3\xc1\x062M\x14ri\x96\x05\xc84\x91\x88\xa6m\x06\x99&\x12\xb9\xb4\xcd \xd3t\x91`\xc5\x92\xf6\x82L\xe3\xa5\x1a\x8c\x93\xca\x012\xcd\xccZ\x03\x99\xe6\x92&R\xb9 \xd3,\xc3\x82\xa1\x07\x042\xcd)\xbeT\x19\x80Ls5\x1a\x90i\"\x14\xc9\xe2\x9a}<\x0e\xc84\xd7c\x01\x99\xe6\xf6y\x022\x8d\x97o\xd1:\x05\x99f\x10\x7fl)\x8c\x96<\xc84 \xd3\x80L3y\xa00'7\x97N\x12\x90i\xba`\x8czA\xa6y\xe0y\x1e\x9dA \xd3t\xb1[\x8a\x82O7\x00\xc84\xb3k\x03d\x9a\xc4\x1f\xe1\"\x8fl \xd3L\xe5\x07\x99&J\xb5l\x1aY@\xa69\x07#\xd4\xcb\xa4]\x07d\x9a\xd8T\xc6;=W\xfeg\xbfk\xfa\xb2\xfe\xa7*\xa4\xf1%\x80K39\xed\xe7\xbf\xd5|\xeaB\x19\xef\x10\x03\x97&d\xb5d\x97)\xb84]$X\xb1\xa4\xbb\xe0\xd2x\xa9\x82\x95LZ\x0c.\xcd%G\xb0ZI\x87\xc1\xa5 V)\xe9+\xb84\xc1J\xa5}\x05\x97& Z/\xe9.\xb84\xe7X\xb0rI\x83\xc1\xa5\xe9#\xc1\x8a%\xed\x05\x97\x86\x9fh>K5\xb84LT\xb3\xe9_\x03\x97&\n\xb94\xc9\x02\\\x9aHD\xd36\x83K\x13\x89\\\xdafpi\xbaH\xb0bI{\xc1\xa5\xf1R\x0d\xc6I\xe5\x00\x97ff\xad\x81KsI\x13\xa9\\pi\x96!\xc1\xd0\x03\x02\x97\xe6\x14_\xaa\x0c\xc0\xa5\xb9\x1a\x0d\xb84\x11\x8adq\xcd>\x1e\x07\\\x9a\xeb\xb1\x80Ks\xfb<\x01\x97\xc6\xcb\xb7h\x9d\x82K3\x88?\xb6\x14FK\x1e\\\x1api\xc0\xa5\x99