Repository: moov-io/customers
Pull request #61 (2019-12-26 19:29:27)

cmd/server: upgrade OFAC -> Watchman to v0.13.0
Fixes: https://github.com/moov-io/customers/issues/60


Patch:
diff --git a/cmd/server/approval_ofac.go b/cmd/server/approval_ofac.go
index 8f0bd59..af33d10 100644
--- a/cmd/server/approval_ofac.go
+++ b/cmd/server/approval_ofac.go
@@ -15,7 +15,7 @@ import (
 	moovhttp "github.com/moov-io/base/http"
 	"github.com/moov-io/customers"
 	client "github.com/moov-io/customers/client"
-	ofac "github.com/moov-io/ofac/client"
+	watchman "github.com/moov-io/watchman/client"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
@@ -30,8 +30,8 @@ type ofacSearchResult struct {
 }
 
 type ofacSearcher struct {
-	repo       customerRepository
-	ofacClient OFACClient
+	repo           customerRepository
+	watchmanClient WatchmanClient
 }
 
 // storeCustomerOFACSearch performs OFAC searches against the Customer's name and nickname if populated.
@@ -44,13 +44,13 @@ func (s *ofacSearcher) storeCustomerOFACSearch(cust *client.Customer, requestID
 		return errors.New("nil Customer")
 	}
 
-	sdn, err := s.ofacClient.Search(ctx, formatCustomerName(cust), requestID)
+	sdn, err := s.watchmanClient.Search(ctx, formatCustomerName(cust), requestID)
 	if err != nil {
 		return fmt.Errorf("ofacSearcher.storeCustomerOFACSearch: name search for customer=%s: %v", cust.ID, err)
 	}
-	var nickSDN *ofac.Sdn
+	var nickSDN *watchman.OfacSdn
 	if cust.NickName != "" {
-		nickSDN, err = s.ofacClient.Search(ctx, cust.NickName, requestID)
+		nickSDN, err = s.watchmanClient.Search(ctx, cust.NickName, requestID)
 		if err != nil {
 			return fmt.Errorf("ofacSearcher.storeCustomerOFACSearch: nickname search for customer=%s: %v", cust.ID, err)
 		}
diff --git a/cmd/server/main.go b/cmd/server/main.go
index f93f81f..7eafaac 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -101,15 +101,15 @@ func main() {
 	cloudProvider := strings.ToLower(os.Getenv("CLOUD_PROVIDER"))
 	signer := setupStorageBucket(logger, bucketName, cloudProvider)
 
-	// Create our OFAC searcher
-	ofacClient := newOFACClient(logger, os.Getenv("OFAC_ENDPOINT"))
-	if ofacClient == nil {
-		panic("No OFAC client created, see OFAC_ENDPOINT")
+	// Create our Watchman client
+	watchmanClient := newWatchmanClient(logger, os.Getenv("WATCHMAN_ENDPOINT"))
+	if watchmanClient == nil {
+		panic("No Watchman client created, see WATCHMAN_ENDPOINT")
 	}
-	adminServer.AddLivenessCheck("ofac", ofacClient.Ping)
+	adminServer.AddLivenessCheck("watchman", watchmanClient.Ping)
 	ofac := &ofacSearcher{
-		repo:       customerRepo,
-		ofacClient: ofacClient,
+		repo:           customerRepo,
+		watchmanClient: watchmanClient,
 	}
 
 	// Register our admin routes
diff --git a/cmd/server/ofac.go b/cmd/server/ofac.go
deleted file mode 100644
index e602add..0000000
--- a/cmd/server/ofac.go
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2019 The Moov Authors
-// Use of this source code is governed by an Apache License
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"context"
-	"fmt"
-	"time"
-
-	"github.com/antihax/optional"
-	"github.com/moov-io/base/http/bind"
-	"github.com/moov-io/base/k8s"
-	ofac "github.com/moov-io/ofac/client"
-
-	"github.com/go-kit/kit/log"
-)
-
-type OFACClient interface {
-	Ping() error
-
-	Search(ctx context.Context, name string, requestID string) (*ofac.Sdn, error)
-}
-
-type moovOFACClient struct {
-	underlying *ofac.APIClient
-	logger     log.Logger
-}
-
-func (c *moovOFACClient) Ping() error {
-	// create a context just for this so ping requests don't require the setup of one
-	ctx, cancelFn := context.WithTimeout(context.TODO(), 10*time.Second)
-	defer cancelFn()
-
-	resp, err := c.underlying.OFACApi.Ping(ctx)
-	if resp != nil && resp.Body != nil {
-		resp.Body.Close()
-	}
-	if resp == nil {
-		return fmt.Errorf("ofac.Ping: failed: %v", err)
-	}
-	if resp.StatusCode < 200 || resp.StatusCode > 299 {
-		return fmt.Errorf("ofac.Ping: got status: %s", resp.Status)
-	}
-	return err
-}
-
-// Search returns the top OFAC match given the provided options across SDN names and AltNames
-func (c *moovOFACClient) Search(ctx context.Context, name string, requestID string) (*ofac.Sdn, error) {
-	search, resp, err := c.underlying.OFACApi.Search(ctx, &ofac.SearchOpts{
-		Q:          optional.NewString(name),
-		Limit:      optional.NewInt32(1),
-		XRequestID: optional.NewString(requestID),
-	})
-	if err != nil {
-		return nil, fmt.Errorf("ofac.Search: %v", err)
-	}
-	resp.Body.Close()
-	if resp.StatusCode < 200 || resp.StatusCode > 299 {
-		return nil, fmt.Errorf("ofac.Search: customer=%q (status code: %d): %v", name, resp.StatusCode, err)
-	}
-	// We prefer to return the SDN, but if there's an AltName with a higher match return that instead.
-	if (len(search.SDNs) > 0 && len(search.AltNames) > 0) && ((search.AltNames[0].Match > 0.1) && (search.AltNames[0].Match > search.SDNs[0].Match)) {
-		alt := search.AltNames[0]
-
-		// AltName matched higher than SDN names, so return the SDN of the matched AltName
-		sdn, resp, err := c.underlying.OFACApi.GetSDN(ctx, alt.EntityID, &ofac.GetSDNOpts{
-			XRequestID: optional.NewString(requestID),
-		})
-		resp.Body.Close()
-		if err != nil {
-			return nil, fmt.Errorf("ofac.Search: found alt name: %v", err)
-		}
-		sdn.Match = alt.Match // copy match from original search (GetSDN doesn't do string matching)
-		c.logger.Log("ofac", fmt.Sprintf("AltName=%s,SDN=%s had higher match than SDN=%s", alt.AlternateID, alt.EntityID, search.SDNs[0].EntityID), "requestID", requestID)
-		return &sdn, nil
-	} else {
-		if len(search.SDNs) > 0 {
-			return &search.SDNs[0], nil // return the SDN which had a higher match than any AltNames
-		}
-	}
-	return nil, nil // no OFAC results found, so cust not blocked
-}
-
-// newOFACClient returns an OFACClient instance and will default to using the OFAC address in
-// moov's standard Kubernetes setup.
-//
-// endpoint is a DNS record responsible for routing us to an OFAC instance.
-// Example: http://ofac.apps.svc.cluster.local:8080
-func newOFACClient(logger log.Logger, endpoint string) OFACClient {
-	conf := ofac.NewConfiguration()
-	conf.BasePath = "http://localhost" + bind.HTTP("ofac")
-
-	if k8s.Inside() {
-		conf.BasePath = "http://ofac.apps.svc.cluster.local:8080"
-	}
-	if endpoint != "" {
-		conf.BasePath = endpoint // override from provided OFAC_ENDPOINT env variable
-	}
-
-	logger.Log("ofac", fmt.Sprintf("using %s for OFAC address", conf.BasePath))
-
-	return &moovOFACClient{
-		underlying: ofac.NewAPIClient(conf),
-		logger:     logger,
-	}
-}
diff --git a/cmd/server/watchman.go b/cmd/server/watchman.go
new file mode 100644
index 0000000..df116fc
--- /dev/null
+++ b/cmd/server/watchman.go
@@ -0,0 +1,108 @@
+// Copyright 2019 The Moov Authors
+// Use of this source code is governed by an Apache License
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/antihax/optional"
+	"github.com/moov-io/base/http/bind"
+	"github.com/moov-io/base/k8s"
+	watchman "github.com/moov-io/watchman/client"
+
+	"github.com/go-kit/kit/log"
+)
+
+type WatchmanClient interface {
+	Ping() error
+
+	Search(ctx context.Context, name string, requestID string) (*watchman.OfacSdn, error)
+}
+
+type moovWatchmanClient struct {
+	underlying *watchman.APIClient
+	logger     log.Logger
+}
+
+func (c *moovWatchmanClient) Ping() error {
+	// create a context just for this so ping requests don't require the setup of one
+	ctx, cancelFn := context.WithTimeout(context.TODO(), 10*time.Second)
+	defer cancelFn()
+
+	resp, err := c.underlying.WatchmanApi.Ping(ctx)
+	if resp != nil && resp.Body != nil {
+		resp.Body.Close()
+	}
+	if resp == nil {
+		return fmt.Errorf("watchman.Ping: failed: %v", err)
+	}
+	if resp.StatusCode < 200 || resp.StatusCode > 299 {
+		return fmt.Errorf("watchman.Ping: got status: %s", resp.Status)
+	}
+	return err
+}
+
+// Search returns the top Watchman match given the provided options across SDN names and AltNames
+func (c *moovWatchmanClient) Search(ctx context.Context, name string, requestID string) (*watchman.OfacSdn, error) {
+	search, resp, err := c.underlying.WatchmanApi.Search(ctx, &watchman.SearchOpts{
+		Q:          optional.NewString(name),
+		Limit:      optional.NewInt32(1),
+		XRequestID: optional.NewString(requestID),
+	})
+	if err != nil {
+		return nil, fmt.Errorf("watchman.Search: %v", err)
+	}
+	resp.Body.Close()
+	if resp.StatusCode < 200 || resp.StatusCode > 299 {
+		return nil, fmt.Errorf("watchman.Search: customer=%q (status code: %d): %v", name, resp.StatusCode, err)
+	}
+	// We prefer to return the SDN, but if there's an AltName with a higher match return that instead.
+	if (len(search.SDNs) > 0 && len(search.AltNames) > 0) && ((search.AltNames[0].Match > 0.1) && (search.AltNames[0].Match > search.SDNs[0].Match)) {
+		alt := search.AltNames[0]
+
+		// AltName matched higher than SDN names, so return the SDN of the matched AltName
+		sdn, resp, err := c.underlying.WatchmanApi.GetSDN(ctx, alt.EntityID, &watchman.GetSDNOpts{
+			XRequestID: optional.NewString(requestID),
+		})
+		resp.Body.Close()
+		if err != nil {
+			return nil, fmt.Errorf("watchman.Search: found alt name: %v", err)
+		}
+		sdn.Match = alt.Match // copy match from original search (GetSDN doesn't do string matching)
+		c.logger.Log("watchman", fmt.Sprintf("AltName=%s,SDN=%s had higher match than SDN=%s", alt.AlternateID, alt.EntityID, search.SDNs[0].EntityID), "requestID", requestID)
+		return &sdn, nil
+	} else {
+		if len(search.SDNs) > 0 {
+			return &search.SDNs[0], nil // return the SDN which had a higher match than any AltNames
+		}
+	}
+	return nil, nil // no Watchman results found, so cust not blocked
+}
+
+// newWatchmanClient returns an WatchmanClient instance and will default to using the Watchman address in
+// moov's standard Kubernetes setup.
+//
+// endpoint is a DNS record responsible for routing us to an Watchman instance.
+// Example: http://watchman.apps.svc.cluster.local:8080
+func newWatchmanClient(logger log.Logger, endpoint string) WatchmanClient {
+	conf := watchman.NewConfiguration()
+	conf.BasePath = "http://localhost" + bind.HTTP("watchman")
+
+	if k8s.Inside() {
+		conf.BasePath = "http://watchman.apps.svc.cluster.local:8080"
+	}
+	if endpoint != "" {
+		conf.BasePath = endpoint // override from provided Watchman_ENDPOINT env variable
+	}
+
+	logger.Log("watchman", fmt.Sprintf("using %s for Watchman address", conf.BasePath))
+
+	return &moovWatchmanClient{
+		underlying: watchman.NewAPIClient(conf),
+		logger:     logger,
+	}
+}
diff --git a/go.mod b/go.mod
index c101e33..a348844 100644
--- a/go.mod
+++ b/go.mod
@@ -10,14 +10,14 @@ require (
 	github.com/gorilla/mux v1.7.3
 	github.com/hashicorp/vault/api v1.0.1
 	github.com/lopezator/migrator v0.2.0
-	github.com/mattn/go-sqlite3 v1.10.0
-	github.com/moov-io/base v0.11.0-rc1
-	github.com/moov-io/ofac v0.12.0
-	github.com/ory/dockertest/v3 v3.5.1
-	github.com/prometheus/client_golang v1.1.0
+	github.com/mattn/go-sqlite3 v1.13.0
+	github.com/moov-io/base v0.11.0-rc1.0.20191121181647-cd3e7a9609db
+	github.com/moov-io/watchman v0.13.0
+	github.com/ory/dockertest/v3 v3.5.2
+	github.com/prometheus/client_golang v1.2.1
 	gocloud.dev v0.17.0
 	gocloud.dev/secrets/vault v0.15.0
-	golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
+	golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6
 )
 
 replace go4.org v0.0.0-20190430205326-94abd6928b1d => go4.org v0.0.0-20190313082347-94abd6928b1d
diff --git a/go.sum b/go.sum
index 422c03e..48f44dd 100644
--- a/go.sum
+++ b/go.sum
@@ -45,8 +45,11 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
+github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6 h1:uZuxRZCz65cG1o6K/xUqImNcYKtmk9ylqaH0itMSvzA=
 github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
 github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
@@ -60,6 +63,7 @@ github.com/aws/aws-sdk-go v1.19.16/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
 github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.19.45 h1:jAxmC8qqa7mW531FDgM8Ahbqlb3zmiHgTpJU6fY3vJ0=
 github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/bbalet/stopwords v1.0.0/go.mod h1:sAWrQoDMfqARGIn4s6dp7OW7ISrshUD8IP2q3KoqPjc=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
@@ -70,6 +74,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
 github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
 github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
+github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=
@@ -234,8 +240,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
-github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
+github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -254,12 +260,10 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/moov-io/base v0.10.0 h1:9A/N0E+oB5nqxTMWszN06Az1PRAkW9EgQ4UQ/94S1IU=
-github.com/moov-io/base v0.10.0/go.mod h1:pPu/TAc9PkaaegbREVEeDHsGqyAlvji9vqTuARuAnd0=
-github.com/moov-io/base v0.11.0-rc1 h1:rrxOvxhIVVpMF8V3TWq+m6EMjNUvHxlkbekb1OXOdhs=
-github.com/moov-io/base v0.11.0-rc1/go.mod h1:0bE/WJZ0zqe3YrGCx+wBT6DApgb2ZVsnP7F0TzTt7o0=
-github.com/moov-io/ofac v0.12.0 h1:wRAepVJDc4iMdZy41mMKTD/YxpfhkzSu0Mi9qoxJ2E8=
-github.com/moov-io/ofac v0.12.0/go.mod h1:WEJCy1cN57rcaIBO63kHQyu/1eSEJNN7Rh1lSRgJ6s0=
+github.com/moov-io/base v0.11.0-rc1.0.20191121181647-cd3e7a9609db h1:O6qBkf9lubIX+/7WboVLEXYKXKPx8soVRjdtuXiDDss=
+github.com/moov-io/base v0.11.0-rc1.0.20191121181647-cd3e7a9609db/go.mod h1:oXZRZBQiO7HX+Wc7Q3jJHLfkDPxrMXRbgYhURQqfXD8=
+github.com/moov-io/watchman v0.13.0 h1:RjK4WoitbIQAIdtyEapfFFGYAYjWqiSN4Er8M3O6bW4=
+github.com/moov-io/watchman v0.13.0/go.mod h1:N52J5PeoJ3KraMF1gLsJSnJ7ecAKwCzCodl38SI6CtU=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
@@ -274,8 +278,9 @@ github.com/opencontainers/runc v1.0.0-rc5 h1:rYjdzMDXVly2Av0RLs3nf/iVkaWh2UrDhuT
 github.com/opencontainers/runc v1.0.0-rc5/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
-github.com/ory/dockertest/v3 v3.5.1 h1:FeTWKqL8RbZ2mijD+kiRuuNV0eT676O77S7jwgSjb7A=
-github.com/ory/dockertest/v3 v3.5.1/go.mod h1:vp8R9lFWwhRy6FmDW1c0/OKcbgq36GDatp/J8qtQmIk=
+github.com/ory/dockertest/v3 v3.5.2 h1:qMLgyU6+vTfCZOWkZjkeoFX5ozRJl8oYursATi2fzHM=
+github.com/ory/dockertest/v3 v3.5.2/go.mod h1:vp8R9lFWwhRy6FmDW1c0/OKcbgq36GDatp/J8qtQmIk=
+github.com/pariz/gountries v0.0.0-20191029140926-233bc78cf5b5/go.mod h1:U0ETmPPEsfd7CpUKNMYi68xIOL8Ww4jPZlaqNngcwqs=
 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -289,24 +294,26 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
 github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
-github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
+github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
+github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
-github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
+github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
-github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
+github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
+github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rickar/cal v1.0.1 h1:Tyjkk4sBvVC3gcXCgLowEM53R2eVfFcoi1gtQuocrmk=
 github.com/rickar/cal v1.0.1/go.mod h1:3GBx8OBrvh4/y/JTxM0e1bUUIHMnqILl1rMANHWExxQ=
@@ -343,7 +350,7 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go4.org v0.0.0-20190313082347-94abd6928b1d/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
+go4.org v0.0.0-20191010144846-132d2879e1e9/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
 gocloud.dev v0.15.0/go.mod h1:ShXCyJaGrJu9y/7a6+DSCyBb9MFGZ1P5wwPa0Wu6w34=
 gocloud.dev v0.17.0 h1:UuDiCphYsiNhRNLtgHVL/eZheQeCt00hL3XjDfbt820=
 gocloud.dev v0.17.0/go.mod h1:tIHTRdR1V5dlD8sTkzYdTGizBJ314BDykJ8KmadEXwo=
@@ -393,6 +400,8 @@ golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4Iltr
 golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
@@ -418,9 +427,10 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190620070143-6f217b454f45 h1:Dl2hc890lrizvUppGbRWhnIh2f8jOTCQpY5IKWRS0oM=
 golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
+golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -492,6 +502,7 @@ gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=


Test patch:
diff --git a/cmd/server/approval_ofac_test.go b/cmd/server/approval_ofac_test.go
index 116be7c..f30799d 100644
--- a/cmd/server/approval_ofac_test.go
+++ b/cmd/server/approval_ofac_test.go
@@ -16,24 +16,24 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/moov-io/base"
 	client "github.com/moov-io/customers/client"
-	ofac "github.com/moov-io/ofac/client"
+	watchman "github.com/moov-io/watchman/client"
 )
 
-func createTestOFACSearcher(repo customerRepository, ofacClient OFACClient) *ofacSearcher {
+func createTestOFACSearcher(repo customerRepository, watchmanClient WatchmanClient) *ofacSearcher {
 	if repo == nil {
 		repo = &testCustomerRepository{}
 	}
-	if ofacClient == nil {
-		ofacClient = &testOFACClient{}
+	if watchmanClient == nil {
+		watchmanClient = &testWatchmanClient{}
 	}
-	return &ofacSearcher{repo: repo, ofacClient: ofacClient}
+	return &ofacSearcher{repo: repo, watchmanClient: watchmanClient}
 }
 
 func TestOFACSearcher__nil(t *testing.T) {
 	repo := createTestCustomerRepository(t)
 	defer repo.close()
 
-	ofacClient := &testOFACClient{}
+	ofacClient := &testWatchmanClient{}
 	searcher := createTestOFACSearcher(repo, ofacClient)
 
 	if err := searcher.storeCustomerOFACSearch(nil, "requestID"); err == nil {
@@ -45,10 +45,10 @@ func TestOFACSearcher__storeCustomerOFACSearch(t *testing.T) {
 	repo := createTestCustomerRepository(t)
 	defer repo.close()
 
-	ofacClient := &testOFACClient{}
+	ofacClient := &testWatchmanClient{}
 	searcher := createTestOFACSearcher(repo, ofacClient)
 
-	ofacClient.sdn = &ofac.Sdn{
+	ofacClient.sdn = &watchman.OfacSdn{
 		EntityID: "1241421",
 		SdnName:  "Jane Doe",
 		Match:    0.99,
@@ -129,10 +129,10 @@ func TestOFACApproval__refresh(t *testing.T) {
 			Match:    1.0,
 		},
 	}
-	testOFACClient := &testOFACClient{}
+	testWatchmanClient := &testWatchmanClient{}
 	ofac := &ofacSearcher{
-		repo:       repo,
-		ofacClient: testOFACClient,
+		repo:           repo,
+		watchmanClient: testWatchmanClient,
 	}
 
 	addOFACRoutes(logger, router, repo, ofac)
@@ -179,10 +179,10 @@ func TestOFACApproval__refreshErr(t *testing.T) {
 			Match:    0.88,
 		},
 	}
-	testOFACClient := &testOFACClient{err: errors.New("bad error")}
+	testWatchmanClient := &testWatchmanClient{err: errors.New("bad error")}
 	ofac := &ofacSearcher{
-		repo:       repo,
-		ofacClient: testOFACClient,
+		repo:           repo,
+		watchmanClient: testWatchmanClient,
 	}
 
 	addOFACRoutes(logger, router, repo, ofac)
diff --git a/cmd/server/approval_test.go b/cmd/server/approval_test.go
index 86a51bd..4331fc9 100644
--- a/cmd/server/approval_test.go
+++ b/cmd/server/approval_test.go
@@ -19,7 +19,7 @@ import (
 	"github.com/moov-io/base/admin"
 	"github.com/moov-io/customers"
 	client "github.com/moov-io/customers/client"
-	ofac "github.com/moov-io/ofac/client"
+	watchman "github.com/moov-io/watchman/client"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
@@ -199,8 +199,8 @@ func TestCustomers__validCustomerStatusTransitionError(t *testing.T) {
 		Status: customers.ReviewRequired.String(),
 	}
 	repo := &testCustomerRepository{}
-	ofacClient := &testOFACClient{}
-	searcher := createTestOFACSearcher(repo, ofacClient)
+	client := &testWatchmanClient{}
+	searcher := createTestOFACSearcher(repo, client)
 
 	ssn := &SSN{customerID: cust.ID, encrypted: []byte("secret")}
 
@@ -210,7 +210,7 @@ func TestCustomers__validCustomerStatusTransitionError(t *testing.T) {
 	}
 	repo.err = nil
 
-	ofacClient.err = errors.New("bad error")
+	client.err = errors.New("bad error")
 	if err := validCustomerStatusTransition(cust, ssn, customers.OFAC, repo, searcher, ""); err == nil {
 		t.Error("expected error, but got none")
 	}
@@ -244,8 +244,8 @@ func TestCustomers__validCustomerStatusTransitionOFAC(t *testing.T) {
 
 	// OFAC transition with no stored result
 	repo.ofacSearchResult = nil
-	if c, ok := searcher.ofacClient.(*testOFACClient); ok {
-		c.sdn = &ofac.Sdn{
+	if c, ok := searcher.watchmanClient.(*testWatchmanClient); ok {
+		c.sdn = &watchman.OfacSdn{
 			EntityID: "12124",
 		}
 	}
diff --git a/cmd/server/ofac_test.go b/cmd/server/watchman_test.go
similarity index 68%
rename from cmd/server/ofac_test.go
rename to cmd/server/watchman_test.go
index 22efd49..d70a411 100644
--- a/cmd/server/ofac_test.go
+++ b/cmd/server/watchman_test.go
@@ -13,42 +13,42 @@ import (
 
 	"github.com/moov-io/base"
 	"github.com/moov-io/base/docker"
-	ofac "github.com/moov-io/ofac/client"
+	watchman "github.com/moov-io/watchman/client"
 
 	"github.com/go-kit/kit/log"
 	"github.com/ory/dockertest/v3"
 )
 
-type testOFACClient struct {
-	sdn *ofac.Sdn
+type testWatchmanClient struct {
+	sdn *watchman.OfacSdn
 
 	// error to be returned instead of field from above
 	err error
 }
 
-func (c *testOFACClient) Ping() error {
+func (c *testWatchmanClient) Ping() error {
 	return c.err
 }
 
-func (c *testOFACClient) Search(_ context.Context, name string, _ string) (*ofac.Sdn, error) {
+func (c *testWatchmanClient) Search(_ context.Context, name string, _ string) (*watchman.OfacSdn, error) {
 	if c.err != nil {
 		return nil, c.err
 	}
 	return c.sdn, nil
 }
 
-type ofacDeployment struct {
+type watchmanDeployment struct {
 	res    *dockertest.Resource
-	client OFACClient
+	client WatchmanClient
 }
 
-func (d *ofacDeployment) close(t *testing.T) {
+func (d *watchmanDeployment) close(t *testing.T) {
 	if err := d.res.Close(); err != nil {
 		t.Error(err)
 	}
 }
 
-func spawnOFAC(t *testing.T) *ofacDeployment {
+func spawnWatchman(t *testing.T) *watchmanDeployment {
 	// no t.Helper() call so we know where it failed
 
 	if testing.Short() {
@@ -58,48 +58,48 @@ func spawnOFAC(t *testing.T) *ofacDeployment {
 		t.Skip("Docker not enabled")
 	}
 
-	// Spawn OFAC docker image
+	// Spawn Watchman docker image
 	pool, err := dockertest.NewPool("")
 	if err != nil {
 		t.Fatal(err)
 	}
 	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
-		Repository: "moov/ofac",
-		Tag:        "v0.12.0",
+		Repository: "moov/watchman",
+		Tag:        "v0.13.0",
 		Cmd:        []string{"-http.addr=:8080"},
 	})
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	client := newOFACClient(log.NewNopLogger(), fmt.Sprintf("http://localhost:%s", resource.GetPort("8080/tcp")))
+	client := newWatchmanClient(log.NewNopLogger(), fmt.Sprintf("http://localhost:%s", resource.GetPort("8080/tcp")))
 	err = pool.Retry(func() error {
 		return client.Ping()
 	})
 	if err != nil {
 		t.Fatal(err)
 	}
-	return &ofacDeployment{resource, client}
+	return &watchmanDeployment{resource, client}
 }
 
-func TestOFAC__client(t *testing.T) {
+func TestWatchman__client(t *testing.T) {
 	endpoint := ""
-	if client := newOFACClient(log.NewNopLogger(), endpoint); client == nil {
+	if client := newWatchmanClient(log.NewNopLogger(), endpoint); client == nil {
 		t.Fatal("expected non-nil client")
 	}
 
-	// Spawn an OFAC Docker image and ping against it
-	deployment := spawnOFAC(t)
+	// Spawn an Watchman Docker image and ping against it
+	deployment := spawnWatchman(t)
 	if err := deployment.client.Ping(); err != nil {
 		t.Fatal(err)
 	}
 	deployment.close(t) // close only if successful
 }
 
-func TestOFAC__search(t *testing.T) {
+func TestWatchman__search(t *testing.T) {
 	ctx := context.TODO()
 
-	deployment := spawnOFAC(t)
+	deployment := spawnWatchman(t)
 
 	// Search query that matches an SDN higher than an AltName
 	sdn, err := deployment.client.Search(ctx, "Nicolas Maduro", base.ID())
@@ -122,8 +122,8 @@ func TestOFAC__search(t *testing.T) {
 	deployment.close(t) // close only if successful
 }
 
-func TestOFAC_ping(t *testing.T) {
-	client := &testOFACClient{}
+func TestWatchman_ping(t *testing.T) {
+	client := &testWatchmanClient{}
 
 	// Ping tests
 	if err := client.Ping(); err != nil {