Runner service #2
|
|
@ -1,9 +1,10 @@
|
||||||
.kubeconfig
|
.kubeconfig
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
|
||||||
# auto generated files
|
# auto generated files
|
||||||
postgres
|
|
||||||
|
|
||||||
*.pb.go
|
*.pb.go
|
||||||
*.pb.gw.go
|
*.pb.gw.go
|
||||||
*.swagger.json
|
*.swagger.json
|
||||||
|
|
||||||
|
src/**/internal/data/postgres/*.go
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,6 @@ ctlptl 0.8.18
|
||||||
kind 0.18.0
|
kind 0.18.0
|
||||||
kustomize 5.0.1
|
kustomize 5.0.1
|
||||||
kubectl 1.26.3
|
kubectl 1.26.3
|
||||||
|
buf 1.17.0
|
||||||
|
k9s 0.26.3
|
||||||
|
golang 1.19
|
||||||
|
|
|
||||||
9
Makefile
9
Makefile
|
|
@ -8,12 +8,13 @@ GOBIN = $(shell go env GOPATH)/bin
|
||||||
dev: .kubeconfig
|
dev: .kubeconfig
|
||||||
KUBECONFIG=$(KUBECONFIG) tilt up -f ./src/Tiltfile
|
KUBECONFIG=$(KUBECONFIG) tilt up -f ./src/Tiltfile
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clobber
|
||||||
clean:
|
clobber:
|
||||||
KUBECONFIG=$(KUBECONFIG) ctlptl delete registry kind-bh-registry || true
|
KUBECONFIG=$(KUBECONFIG) ctlptl delete registry kind-bh-registry || true
|
||||||
KUBECONFIG=$(KUBECONFIG) ctlptl delete cluster kind-bh-local || true
|
KUBECONFIG=$(KUBECONFIG) ctlptl delete cluster kind-bh-local || true
|
||||||
@rm -f $(KUBECONFIG)
|
@rm -f $(KUBECONFIG)
|
||||||
|
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
docker build --build-arg "SERVICE=runner" -t bh/service-runner -f ./src/runner/Dockerfile .
|
docker build --build-arg "SERVICE=runner" -t bh/service-runner -f ./src/runner/Dockerfile .
|
||||||
|
|
@ -29,7 +30,7 @@ acceptance-test:
|
||||||
.PHONY: gen
|
.PHONY: gen
|
||||||
gen: $(GOBIN)/sqlc buf.lock
|
gen: $(GOBIN)/sqlc buf.lock
|
||||||
@$(GOBIN)/sqlc generate -f ./src/sqlc.yaml
|
@$(GOBIN)/sqlc generate -f ./src/sqlc.yaml
|
||||||
@cd ./src && buf generate
|
@cd ./src && $(GOBIN)/buf generate
|
||||||
|
|
||||||
|
|
||||||
.PHONY: setup
|
.PHONY: setup
|
||||||
|
|
@ -37,7 +38,7 @@ setup: $(GOBIN)/sqlc $(GOBIN)/buf
|
||||||
@asdf install || true
|
@asdf install || true
|
||||||
|
|
||||||
buf.lock: $(GOBIN)/buf
|
buf.lock: $(GOBIN)/buf
|
||||||
@buf mod update ./src
|
@$(GOBIN)/buf mod update ./src
|
||||||
|
|
||||||
$(GOBIN)/buf:
|
$(GOBIN)/buf:
|
||||||
@go install github.com/bufbuild/buf/cmd/buf@v1.17.0
|
@go install github.com/bufbuild/buf/cmd/buf@v1.17.0
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
resources:
|
resources:
|
||||||
- ./runner-deployment.yaml
|
- ./runner-deployment.yaml
|
||||||
|
- ./proxy-admin-deployment.yaml
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: proxy-admin
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
service: proxy-admin
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
service: proxy-admin
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: proxy-admin
|
||||||
|
image: barretthousen/service-proxy-admin:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
command:
|
||||||
|
- /opt/proxy-admin
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
volumeMounts:
|
||||||
|
- name: proxy-admin-config
|
||||||
|
mountPath: /config/
|
||||||
|
volumes:
|
||||||
|
- name: proxy-admin-config
|
||||||
|
configMap:
|
||||||
|
name: proxy-admin-config
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: proxy-admin
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
service: proxy-admin
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: proxy-admin-config
|
||||||
|
data:
|
||||||
|
config.yaml: |
|
||||||
|
log_level: 2
|
||||||
|
port: 80
|
||||||
|
endpoints:
|
||||||
|
runner: local-runner:5001
|
||||||
|
|
@ -6,16 +6,77 @@ spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
service: bh-runner
|
service: runner
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
service: bh-runner
|
service: runner
|
||||||
spec:
|
spec:
|
||||||
|
initContainers:
|
||||||
|
- name: runner-db-migrate
|
||||||
|
image: barretthousen/service-runner:latest
|
||||||
|
command:
|
||||||
|
- /opt/runner
|
||||||
|
args:
|
||||||
|
- -migrate
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "250m"
|
||||||
|
memory: "64Mi"
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /config/
|
||||||
|
name: runner-config
|
||||||
containers:
|
containers:
|
||||||
- name: runner
|
- name: runner
|
||||||
image: barretthousen/service-runner:latest
|
image: barretthousen/service-runner:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 5001
|
||||||
|
name: grpc
|
||||||
|
command:
|
||||||
|
- /opt/runner
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: "250m"
|
cpu: "250m"
|
||||||
memory: "128Mi"
|
memory: "128Mi"
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /config/
|
||||||
|
name: runner-config
|
||||||
|
volumes:
|
||||||
|
- name: runner-config
|
||||||
|
configMap:
|
||||||
|
name: runner-config
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: runner
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
service: runner
|
||||||
|
ports:
|
||||||
|
- port: 5001
|
||||||
|
targetPort: 5001
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: runner-config
|
||||||
|
data:
|
||||||
|
config.yaml: |
|
||||||
|
log_level: 2
|
||||||
|
port: 5001
|
||||||
|
db_service:
|
||||||
|
scheme: postgres
|
||||||
|
port: 5432
|
||||||
|
host: bh-db
|
||||||
|
name: bh
|
||||||
|
user: runner-service
|
||||||
|
password: runner-service
|
||||||
|
db_migrate:
|
||||||
|
scheme: postgres
|
||||||
|
port: 5432
|
||||||
|
host: bh-db
|
||||||
|
name: bh
|
||||||
|
user: postgres
|
||||||
|
password: bh-admin
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: runner
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: runner
|
||||||
|
command:
|
||||||
|
- /go/bin/dlv
|
||||||
|
args:
|
||||||
|
- --headless
|
||||||
|
- --listen=0.0.0.0:2345
|
||||||
|
- --api-version=2
|
||||||
|
- --log
|
||||||
|
- --accept-multiclient
|
||||||
|
#- --log-output=rpc,dap
|
||||||
|
- exec
|
||||||
|
- /opt/runner-debug
|
||||||
|
- --continue
|
||||||
|
ports:
|
||||||
|
- containerPort: 2345
|
||||||
|
|
@ -5,3 +5,6 @@ commonLabels:
|
||||||
environment: local
|
environment: local
|
||||||
|
|
||||||
namePrefix: local-
|
namePrefix: local-
|
||||||
|
|
||||||
|
patchesStrategicMerge:
|
||||||
|
- debug-runner.yaml
|
||||||
|
|
|
||||||
1
go.work
1
go.work
|
|
@ -3,4 +3,5 @@ go 1.19
|
||||||
use (
|
use (
|
||||||
./src/lib
|
./src/lib
|
||||||
./src/runner
|
./src/runner
|
||||||
|
./src/proxy-admin
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
go.uber.org/dig v1.0.0/go.mod h1:z+dSd2TP9Usi48jL8M3v63iSBVkiwtVyMKxMZYYauPg=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="bh-db@localhost" uuid="05aba6c0-168c-47e3-b807-951316a3a483">
|
||||||
|
<driver-ref>postgresql</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:postgresql://localhost:5432/postgres</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/src.iml" filepath="$PROJECT_DIR$/.idea/src.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="DBE_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -1,20 +1,31 @@
|
||||||
FROM golang:1.19-alpine as builder
|
FROM golang:1.19-alpine as development
|
||||||
|
|
||||||
ARG SERVICE
|
|
||||||
|
|
||||||
COPY . /go/src
|
|
||||||
|
|
||||||
WORKDIR /go/src/${SERVICE}
|
|
||||||
|
|
||||||
RUN go mod tidy && go build -v -o /opt/${SERVICE} /go/src/${SERVICE}
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine
|
|
||||||
|
|
||||||
ARG SERVICE
|
ARG SERVICE
|
||||||
|
|
||||||
ENV SERVICE=${SERVICE}
|
ENV SERVICE=${SERVICE}
|
||||||
|
|
||||||
COPY --from=builder /opt/${SERVICE} /opt/${SERVICE}
|
RUN go install github.com/go-delve/delve/cmd/dlv@latest
|
||||||
|
|
||||||
CMD /opt/${SERVICE}
|
COPY . /go/src
|
||||||
|
|
||||||
|
WORKDIR /go/src/${SERVICE}
|
||||||
|
|
||||||
|
RUN go mod tidy \
|
||||||
|
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o /opt/${SERVICE} /go/src/${SERVICE} \
|
||||||
|
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -gcflags="-N -l" -v -o /opt/${SERVICE}-debug /go/src/${SERVICE}
|
||||||
|
# && go build -v -gcflags="all=-N -l" -o /opt/${SERVICE}-debug /go/src/${SERVICE}
|
||||||
|
|
||||||
|
ENTRYPOINT ['/go/bin/dlv']
|
||||||
|
|
||||||
|
CMD ['debug', '/go/src/${SERVICE}']
|
||||||
|
|
||||||
|
|
||||||
|
FROM alpine as production
|
||||||
|
|
||||||
|
ARG SERVICE
|
||||||
|
|
||||||
|
ENV SERVICE=${SERVICE}
|
||||||
|
|
||||||
|
COPY --from=development /opt/${SERVICE} /opt/${SERVICE}
|
||||||
|
|
||||||
|
ENTRYPOINT ['/opt/${SERVICE}']
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
FROM golang:1.19-alpine as builder
|
||||||
|
|
||||||
|
ARG SERVICE
|
||||||
|
|
||||||
|
COPY . /go/src
|
||||||
|
|
||||||
|
WORKDIR /go/src/${SERVICE}
|
||||||
|
|
||||||
|
RUN go mod tidy && go build -v -o /opt/${SERVICE} /go/src/${SERVICE}
|
||||||
50
src/Tiltfile
50
src/Tiltfile
|
|
@ -23,12 +23,12 @@ helm_resource(
|
||||||
labels=["1-ingress"]
|
labels=["1-ingress"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
helm_resource(
|
helm_resource(
|
||||||
'postgres',
|
'postgres',
|
||||||
'bitnami/postgresql',
|
'bitnami/postgresql',
|
||||||
resource_deps=['bitnami'],
|
resource_deps=['bitnami'],
|
||||||
flags=[
|
flags=[
|
||||||
|
'--set', 'fullnameOverride=bh-db',
|
||||||
'--set', 'auth.enablePostgresUser=true',
|
'--set', 'auth.enablePostgresUser=true',
|
||||||
'--set', 'auth.postgresPassword=bh-admin',
|
'--set', 'auth.postgresPassword=bh-admin',
|
||||||
'--set', 'auth.database=bh',
|
'--set', 'auth.database=bh',
|
||||||
|
|
@ -36,39 +36,49 @@ helm_resource(
|
||||||
port_forwards=["5432:5432"],
|
port_forwards=["5432:5432"],
|
||||||
labels=["9-data"])
|
labels=["9-data"])
|
||||||
|
|
||||||
helm_resource(
|
|
||||||
'loki-stack',
|
|
||||||
'grafana/loki-stack',
|
|
||||||
resource_deps=['grafana'],
|
|
||||||
flags=[
|
|
||||||
'--set', 'fluent-bit.enabled=false',
|
|
||||||
'--set', 'promtail.enabled=false',
|
|
||||||
'--set', 'loki.enabled=false',
|
|
||||||
],
|
|
||||||
port_forwards=["3000:80"],
|
|
||||||
labels=["9-monitoring"])
|
|
||||||
|
|
||||||
|
|
||||||
def bh_service(service=""):
|
def bh_service(service="", port_forwards=[], devMode=True, labels=['2-services']):
|
||||||
docker_build(
|
docker_build(
|
||||||
ref="barretthousen/service-{}".format(service),
|
ref="barretthousen/service-{}".format(service),
|
||||||
dockerfile="./Dockerfile.service",
|
dockerfile="./Dockerfile.service",
|
||||||
context=".",
|
context=".",
|
||||||
|
target="development" if devMode else "production",
|
||||||
build_args={
|
build_args={
|
||||||
"SERVICE": service
|
"SERVICE": service
|
||||||
},
|
},
|
||||||
only=[
|
# only=[
|
||||||
"{}".format(service),
|
# "./{}".format(service),
|
||||||
"lib",
|
# "lib",
|
||||||
"Dockerfile.service"
|
# "Dockerfile.service"
|
||||||
]
|
# ]
|
||||||
|
)
|
||||||
|
|
||||||
|
k8s_resource(
|
||||||
|
workload='local-{}'.format(service),
|
||||||
|
port_forwards=port_forwards,
|
||||||
|
labels=labels,
|
||||||
|
resource_deps=['postgres']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bh_service(service="runner", port_forwards=[5001, 2345])
|
||||||
bh_service(service="runner")
|
bh_service(service="proxy-admin", port_forwards=["8082:80"])
|
||||||
|
|
||||||
k8s_yaml(
|
k8s_yaml(
|
||||||
kustomize("../env/local")
|
kustomize("../env/local")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# helm_resource(
|
||||||
|
# 'loki-stack',
|
||||||
|
# 'grafana/loki-stack',
|
||||||
|
# resource_deps=['grafana'],
|
||||||
|
# flags=[
|
||||||
|
# '--set', 'fluent-bit.enabled=false',
|
||||||
|
# '--set', 'promtail.enabled=false',
|
||||||
|
# '--set', 'loki.enabled=false',
|
||||||
|
# ],
|
||||||
|
# port_forwards=["3000:80"],
|
||||||
|
# labels=["9-monitoring"])
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ deps:
|
||||||
- remote: buf.build
|
- remote: buf.build
|
||||||
owner: googleapis
|
owner: googleapis
|
||||||
repository: googleapis
|
repository: googleapis
|
||||||
commit: 5ae7f88519b04fe1965da0f8a375a088
|
commit: cc916c31859748a68fd229a3c8d7a2e8
|
||||||
|
digest: shake256:469b049d0eb04203d5272062636c078decefc96fec69739159c25d85349c50c34c7706918a8b216c5c27f76939df48452148cff8c5c3ae77fa6ba5c25c1b8bf8
|
||||||
- remote: buf.build
|
- remote: buf.build
|
||||||
owner: grpc-ecosystem
|
owner: grpc-ecosystem
|
||||||
repository: grpc-gateway
|
repository: grpc-gateway
|
||||||
commit: a1ecdc58eccd49aa8bea2a7a9022dc27
|
commit: a1ecdc58eccd49aa8bea2a7a9022dc27
|
||||||
|
digest: shake256:efdd86fbdc42e8b7259fe461a49656827a03fb7cba0b3b9eb622ca10654ec6beccb9a051229c1553ccd89ed3e95d69ad4d7c799f1da3f3f1bd447b7947a4893e
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package catalog
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Auction struct {
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
SourceSiteURL string `json:"source_site_url,omitempty"`
|
||||||
|
SourceSiteName string `json:"source_site_name,omitempty"`
|
||||||
|
SourceURL string `json:"source_url,omitempty"`
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
Province string `json:"province,omitempty"`
|
||||||
|
ItemCount int `json:"itemCount,omitempty"`
|
||||||
|
Start time.Time `json:"start,omitempty"`
|
||||||
|
End time.Time `json:"end,omitempty"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
package runner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/domain/catalog"
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
var targetsImpls = map[string]UpcomingAuctionFinder{}
|
||||||
|
|
||||||
|
type UpcomingAuctionFinder interface {
|
||||||
|
fmt.Stringer
|
||||||
|
Find(ctx context.Context, limit int, results chan<- catalog.Auction) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterAuctionFinder(finder UpcomingAuctionFinder) {
|
||||||
|
targetName := finder.String()
|
||||||
|
if _, ok := targetsImpls[targetName]; ok {
|
||||||
|
kernel.FatalErr(fmt.Errorf("target %q has already been registered", targetName))
|
||||||
|
}
|
||||||
|
|
||||||
|
targetsImpls[targetName] = finder
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Domain struct {
|
||||||
|
Storage
|
||||||
|
CatalogService
|
||||||
|
}
|
||||||
|
|
||||||
|
CompleteScrapeJobStatus struct {
|
||||||
|
AuctionCount int
|
||||||
|
Errors string
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage interface {
|
||||||
|
CreateScrapeJob(context.Context, string) (ScrapeJob, error)
|
||||||
|
CompleteScrapeJob(context.Context, int, CompleteScrapeJobStatus) (ScrapeJob, error)
|
||||||
|
GetJobs(context.Context) ([]ScrapeJob, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
CatalogService interface {
|
||||||
|
UpdateUpcomingAuction(catalog.Auction) error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type CatalogServiceStub struct{}
|
||||||
|
|
||||||
|
func (css *CatalogServiceStub) UpdateUpcomingAuction(a catalog.Auction) error {
|
||||||
|
kernel.TraceLog.Printf("Invoke CatalogService[UpdateUpcomingAuction](%+v)", a)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
FindNewUpcomingInput struct {
|
||||||
|
TargetSite string
|
||||||
|
}
|
||||||
|
|
||||||
|
FindNewUpcomingOutput struct {
|
||||||
|
Job ScrapeJob
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (domain Domain) FindNewUpcoming(ctx context.Context, in FindNewUpcomingInput) (out FindNewUpcomingOutput, err error) {
|
||||||
|
for k := range targetsImpls {
|
||||||
|
kernel.TraceLog.Printf("Find Target: %q", k)
|
||||||
|
}
|
||||||
|
finder, ok := targetsImpls[in.TargetSite]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("could not find target matching name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Job, err = domain.Storage.CreateScrapeJob(ctx, in.TargetSite); err != nil {
|
||||||
|
err = fmt.Errorf("could not create new scrape job record: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel.InfoLog.Printf("Scrape Job %d starting", out.Job.ID)
|
||||||
|
|
||||||
|
found := make(chan catalog.Auction)
|
||||||
|
errGroup, innerCtx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
errGroup.Go(func() error {
|
||||||
|
return finder.Find(innerCtx, 0, found)
|
||||||
|
})
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for auction := range found {
|
||||||
|
count++
|
||||||
|
a := auction
|
||||||
|
errGroup.Go(func() error {
|
||||||
|
return domain.CatalogService.UpdateUpcomingAuction(a)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
errMsg := ""
|
||||||
|
if err = errGroup.Wait(); err != nil {
|
||||||
|
err = fmt.Errorf("an issue occurred while finding upcoming items iteration: %w", err)
|
||||||
|
errMsg = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Job, err = domain.Storage.CompleteScrapeJob(ctx, out.Job.ID, CompleteScrapeJobStatus{
|
||||||
|
AuctionCount: count,
|
||||||
|
Errors: errMsg,
|
||||||
|
}); err != nil {
|
||||||
|
err = fmt.Errorf("Could not complete scrape job, failing: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel.InfoLog.Printf("Scrape Job %d completed in %v.", out.Job.ID, out.Job.Completed.Sub(out.Job.Started))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
GetJobsInput struct{}
|
||||||
|
GetJobsOutput struct {
|
||||||
|
Jobs []ScrapeJob
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (domain Domain) GetJobs(ctx context.Context, in GetJobsInput) (out GetJobsOutput, err error) {
|
||||||
|
scrapeJobs, err := domain.Storage.GetJobs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("could not fetch jobs from storage: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out = GetJobsOutput{Jobs: scrapeJobs}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package runner
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type ScrapeJob struct {
|
||||||
|
ID int `json:"id,omitempty"`
|
||||||
|
Started time.Time `json:"startedTs,omitempty"`
|
||||||
|
Completed time.Time `json:"completedTs,omitempty"`
|
||||||
|
TargetSite string `json:"target_site,omitempty"`
|
||||||
|
AuctionsFound int `json:"auctions_found,omitempty"`
|
||||||
|
Errors string `json:"errors,omitempty"`
|
||||||
|
}
|
||||||
|
|
@ -5,9 +5,18 @@ go 1.19
|
||||||
require (
|
require (
|
||||||
github.com/jackc/pgx/v4 v4.18.1
|
github.com/jackc/pgx/v4 v4.18.1
|
||||||
go.uber.org/automaxprocs v1.5.2
|
go.uber.org/automaxprocs v1.5.2
|
||||||
|
go.uber.org/dig v1.16.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||||
|
github.com/joho/godotenv v1.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.4.2
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
github.com/jackc/pgconn v1.14.0 // indirect
|
github.com/jackc/pgconn v1.14.0 // indirect
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||||
|
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
|
@ -11,6 +13,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.4.2 h1:nRqiriLMAC7tz7GzjzUTBHfzdzw6SQ7XvTagkFqe/zU=
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.4.2/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
|
||||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
|
@ -60,6 +64,8 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||||
|
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
|
@ -108,6 +114,8 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||||
|
go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8=
|
||||||
|
go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
|
@ -188,5 +196,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
|
||||||
|
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,30 @@ package kernel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ilyakaznacheev/cleanenv"
|
||||||
_ "go.uber.org/automaxprocs"
|
_ "go.uber.org/automaxprocs"
|
||||||
|
// "go.uber.org/dig"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App interface {
|
type App interface {
|
||||||
Start(context.Context) error
|
Start(context.Context) error
|
||||||
OnStop(context.Context)
|
OnStop(context.Context)
|
||||||
|
GetLogLevel() LogLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppConfig struct {
|
type AppConfig struct {
|
||||||
App
|
LogLevel LogLevel `yaml:"log_level" env:"BH_LOG_LEVEL" env-default:"ERROR" yaml-default:"ERROR"`
|
||||||
LogLevel LogLevel
|
Config interface{} `yaml:"service" env:"BH_SERVICE"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run(parent context.Context, cfg AppConfig) {
|
func Run(parent context.Context, app App) {
|
||||||
SetLogLevel(cfg.LogLevel)
|
SetLogLevel(app.GetLogLevel())
|
||||||
ctx, canceller := context.WithCancel(parent)
|
ctx, canceller := context.WithCancel(parent)
|
||||||
defer canceller()
|
defer canceller()
|
||||||
|
|
||||||
|
|
@ -32,18 +36,45 @@ func Run(parent context.Context, cfg AppConfig) {
|
||||||
defer canceller()
|
defer canceller()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-sig:
|
case signal := <-sig:
|
||||||
|
TraceLog.Printf("[SHUTDOWN TRIGGERED] got shutdown signal: %v", signal)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
TraceLog.Println("[SHUTDOWN TRIGGERED] context exited unexpectedly")
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoLog.Println("Shutting down service")
|
InfoLog.Println("Shutting down service ⛔⚠️😱")
|
||||||
stopCtx, stopCanceller := context.WithTimeout(parent, time.Second*5)
|
stopCtx, stopCanceller := context.WithTimeout(parent, time.Second*5)
|
||||||
defer stopCanceller()
|
defer stopCanceller()
|
||||||
cfg.App.OnStop(stopCtx)
|
app.OnStop(stopCtx)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
InfoLog.Println("Starting service")
|
InfoLog.Println("Starting service 🚀")
|
||||||
if err := cfg.App.Start(ctx); err != nil {
|
|
||||||
FatalErr(err)
|
if err := loadConfig(app); err != nil {
|
||||||
|
ErrorLog.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.Start(ctx); err != nil {
|
||||||
|
ErrorLog.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadConfig(cfg interface{}) error {
|
||||||
|
fp := os.Getenv("BH_APP_CONFIG_PATH")
|
||||||
|
|
||||||
|
if fp == "" {
|
||||||
|
fp = "/config/config.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cleanenv.ReadConfig(fp, cfg); err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("could not read config from file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cleanenv.ReadEnv(cfg); err != nil {
|
||||||
|
return fmt.Errorf("could not read config from env: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package kernel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerBuilder func(grpc.ServiceRegistrar, string)
|
||||||
|
|
||||||
|
var (
|
||||||
|
grpcServerInstance *grpc.Server
|
||||||
|
httpServerInstance *http.Server
|
||||||
|
listener net.Listener
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartGRPCServer(ctx context.Context, port int, sb ServerBuilder, opts ...grpc.ServerOption) (endpoint string, err error) {
|
||||||
|
if grpcServerInstance != nil {
|
||||||
|
err = errors.New("There can only be one GRPC server running at a time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint = fmt.Sprintf("0.0.0.0:%d", port)
|
||||||
|
listener, err = net.Listen("tcp", endpoint)
|
||||||
|
if err != nil {
|
||||||
|
endpoint = ""
|
||||||
|
err = fmt.Errorf("could not start tcp listener: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcServerInstance = grpc.NewServer(opts...)
|
||||||
|
|
||||||
|
sb(grpcServerInstance, endpoint)
|
||||||
|
|
||||||
|
if err = grpcServerInstance.Serve(listener); err != nil {
|
||||||
|
endpoint = ""
|
||||||
|
err = fmt.Errorf("could not serve GRPC server over listener: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoLog.Printf("Listening for GRPC requests on %s", endpoint)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopGRPCServer() error {
|
||||||
|
grpcServerInstance.GracefulStop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartHTTPServer(ctx context.Context, port int, handler http.Handler) (err error) {
|
||||||
|
if httpServerInstance != nil {
|
||||||
|
err = errors.New("There can only be one HTTP server running at a time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := fmt.Sprintf("0.0.0.0:%d", port)
|
||||||
|
listener, err = net.Listen("tcp", endpoint)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("could not start tcp listener: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpServerInstance = &http.Server{
|
||||||
|
Handler: handler,
|
||||||
|
Addr: endpoint,
|
||||||
|
ReadHeaderTimeout: time.Second * 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = httpServerInstance.Serve(listener); err != nil {
|
||||||
|
err = fmt.Errorf("could not serve http over listener: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoLog.Printf("Listening for HTTP requests on %s", endpoint)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopHTTPServer() error {
|
||||||
|
ctx, canceler := context.WithDeadline(context.Background(), time.Now().Add(time.Second*5))
|
||||||
|
defer canceler()
|
||||||
|
return httpServerInstance.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
@ -62,11 +62,13 @@ type Logger interface {
|
||||||
|
|
||||||
func SetLogLevel(ll LogLevel) {
|
func SetLogLevel(ll LogLevel) {
|
||||||
for i := 0; i < len(loggers); i++ {
|
for i := 0; i < len(loggers); i++ {
|
||||||
lg := loggers[i]
|
target := writer
|
||||||
if i > int(ll) {
|
|
||||||
lg.SetOutput(writer)
|
if int(ll) >= i {
|
||||||
} else {
|
target = os.Stdout
|
||||||
lg.SetOutput(os.Stdout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loggers[i].SetOutput(target)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func (pc PostgresConnection) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDBConnection(ctx context.Context, pg PostgresConnection) (conn *pgx.Conn, err error) {
|
func NewDBConnection(ctx context.Context, pg PostgresConnection) (conn *pgx.Conn, err error) {
|
||||||
for retries := 0; retries < 3; retries++ {
|
for retries := 0; retries < 5; retries++ {
|
||||||
if conn, err = pgx.Connect(ctx, pg.String()); err != nil {
|
if conn, err = pgx.Connect(ctx, pg.String()); err != nil {
|
||||||
sleepTime := time.Second * time.Duration(retries)
|
sleepTime := time.Second * time.Duration(retries)
|
||||||
log.Printf("%d attempt(s) to postgres failed, retrying in %v: %v", retries+1, sleepTime, err)
|
log.Printf("%d attempt(s) to postgres failed, retrying in %v: %v", retries+1, sleepTime, err)
|
||||||
|
|
@ -42,3 +42,18 @@ func NewDBConnection(ctx context.Context, pg PostgresConnection) (conn *pgx.Conn
|
||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Migrate(ctx context.Context, pg PostgresConnection, sql string) error {
|
||||||
|
conn, err := NewDBConnection(ctx, pg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer conn.PgConn().Close(ctx)
|
||||||
|
|
||||||
|
if _, err := conn.Exec(ctx, sql); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
module git.vdhsn.com/barretthousen/barretthousen/src/proxy-admin
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0
|
||||||
|
git.vdhsn.com/barretthousen/barretthousen/src/runner v1.0.0
|
||||||
|
)
|
||||||
|
|
||||||
|
replace git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0 => ../lib
|
||||||
|
replace git.vdhsn.com/barretthousen/barretthousen/src/runner v1.0.0 => ../runner
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/runner/api"
|
||||||
|
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyAdminApp struct {
|
||||||
|
LogLevel kernel.LogLevel `yaml:"log_level" env:"BH_LOG_LEVEL" env-default:"0" yaml-default:"0"`
|
||||||
|
Port int `yaml:"port" env:"PROXY_ADMIN_PORT"`
|
||||||
|
Endpoints struct {
|
||||||
|
Runner string `yaml:"runner" env:"RUNNER_ENDPOINT"`
|
||||||
|
} `yaml:"endpoints" env:"PROXY_ADMIN_SERVICES"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *ProxyAdminApp) Start(ctx context.Context) error {
|
||||||
|
grpcMux := runtime.NewServeMux()
|
||||||
|
err := api.RegisterRunnerHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Runner, []grpc.DialOption{
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel.TraceLog.Printf("%+v", app)
|
||||||
|
|
||||||
|
httpServer := &http.Server{
|
||||||
|
Addr: fmt.Sprintf("0.0.0.0:%d", app.Port),
|
||||||
|
ReadHeaderTimeout: time.Second,
|
||||||
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
kernel.TraceLog.Printf("{ \"Client\": \"%s\", \"Path\":\"%s\"} ", r.RemoteAddr, r.URL.Path)
|
||||||
|
grpcMux.ServeHTTP(w, r)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel.InfoLog.Printf("Starting HTTP proxy @ %q", httpServer.Addr)
|
||||||
|
|
||||||
|
return httpServer.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *ProxyAdminApp) OnStop(ctx context.Context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *ProxyAdminApp) GetLogLevel() kernel.LogLevel { return app.LogLevel }
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
kernel.Run(context.Background(), &ProxyAdminApp{
|
||||||
|
LogLevel: kernel.LevelTrace,
|
||||||
|
Port: 80,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -5,9 +5,9 @@ package main;
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
|
|
||||||
option go_package = "git.vdhsn.com/barretthousen/barretthousen/src/lib/services/runner";
|
option go_package = "git.vdhsn.com/barretthousen/barretthousen/src/runner/api";
|
||||||
|
|
||||||
service Accounts {
|
service Runner {
|
||||||
rpc FindNewUpcoming(FindNewUpcomingCommand) returns (JobResult) {
|
rpc FindNewUpcoming(FindNewUpcomingCommand) returns (JobResult) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
put: "/v1/findnewupcoming"
|
put: "/v1/findnewupcoming"
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
log_level: ERROR
|
||||||
|
|
||||||
|
service: {}
|
||||||
|
|
@ -2,20 +2,38 @@ module git.vdhsn.com/barretthousen/barretthousen/src/runner
|
||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0
|
require (
|
||||||
|
git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||||
|
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923
|
||||||
|
google.golang.org/protobuf v1.28.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.4.2 // indirect
|
||||||
|
github.com/joho/godotenv v1.4.0 // indirect
|
||||||
|
golang.org/x/net v0.8.0 // indirect
|
||||||
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
github.com/jackc/pgconn v1.14.0 // indirect
|
github.com/jackc/pgconn v1.14.0
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
|
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgtype v1.14.0 // indirect
|
github.com/jackc/pgtype v1.14.0 // indirect
|
||||||
github.com/jackc/pgx/v4 v4.18.1 // indirect
|
github.com/jackc/pgx/v4 v4.18.1
|
||||||
go.uber.org/automaxprocs v1.5.2 // indirect
|
go.uber.org/automaxprocs v1.5.2 // indirect
|
||||||
golang.org/x/crypto v0.6.0 // indirect
|
golang.org/x/crypto v0.6.0 // indirect
|
||||||
golang.org/x/text v0.7.0 // indirect
|
golang.org/x/text v0.8.0 // indirect
|
||||||
|
google.golang.org/grpc v1.54.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0 => ../lib
|
replace git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0 => ../lib
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||||
|
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
|
|
@ -13,7 +15,17 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.4.2 h1:nRqiriLMAC7tz7GzjzUTBHfzdzw6SQ7XvTagkFqe/zU=
|
||||||
|
github.com/ilyakaznacheev/cleanenv v1.4.2/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
|
||||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||||
|
|
@ -62,12 +74,16 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||||
|
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
|
@ -146,7 +162,10 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
@ -163,6 +182,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
|
@ -173,8 +194,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
|
@ -189,8 +211,18 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds=
|
||||||
|
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
||||||
|
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||||
|
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
|
@ -199,3 +231,5 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
|
||||||
|
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
-- name: GetJobs :many
|
||||||
|
SELECT id,
|
||||||
|
startedTs,
|
||||||
|
completedTs,
|
||||||
|
targetSiteName,
|
||||||
|
auctionsFound,
|
||||||
|
errors
|
||||||
|
FROM runner.scrapejob;
|
||||||
|
|
||||||
|
-- name: GetJobByID :one
|
||||||
|
SELECT id,
|
||||||
|
startedTs,
|
||||||
|
completedTs,
|
||||||
|
targetSiteName,
|
||||||
|
auctionsFound,
|
||||||
|
errors
|
||||||
|
FROM runner.scrapejob
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: CreateScrapeJob :one
|
||||||
|
INSERT INTO runner.scrapejob (
|
||||||
|
targetSiteName
|
||||||
|
) VALUES (
|
||||||
|
$1
|
||||||
|
) RETURNING *;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: CompleteScrapeJob :exec
|
||||||
|
UPDATE runner.scrapejob SET
|
||||||
|
completedTs = $2,
|
||||||
|
auctionsFound = $3,
|
||||||
|
errors = $4
|
||||||
|
WHERE id = $1;
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS runner;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS runner.scrapejob (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
startedTs TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
completedTs TIMESTAMP,
|
||||||
|
targetSiteName VARCHAR(512) NOT NULL,
|
||||||
|
auctionsFound INT NOT NULL DEFAULT 0,
|
||||||
|
errors VARCHAR(5000) NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
|
DO
|
||||||
|
$do$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT FROM pg_catalog.pg_roles -- SELECT list can be empty for this
|
||||||
|
WHERE rolname = 'runner-service') THEN
|
||||||
|
|
||||||
|
CREATE USER "runner-service" WITH PASSWORD 'runner-service';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$do$;
|
||||||
|
|
||||||
|
GRANT CONNECT ON DATABASE bh to "runner-service";
|
||||||
|
GRANT USAGE ON SCHEMA runner TO "runner-service";
|
||||||
|
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA runner TO "runner-service";
|
||||||
|
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA runner TO "runner-service";
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/domain/runner"
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/runner/internal/data/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PGRunnerStorage struct {
|
||||||
|
*postgres.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *PGRunnerStorage) CreateScrapeJob(ctx context.Context, target string) (sj runner.ScrapeJob, err error) {
|
||||||
|
rsj, err := db.Queries.CreateScrapeJob(ctx, target)
|
||||||
|
if err != nil {
|
||||||
|
return runner.ScrapeJob{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return runner.ScrapeJob{
|
||||||
|
ID: int(rsj.ID),
|
||||||
|
Started: rsj.Startedts,
|
||||||
|
TargetSite: rsj.Targetsitename,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *PGRunnerStorage) CompleteScrapeJob(ctx context.Context, ID int, status runner.CompleteScrapeJobStatus) (sj runner.ScrapeJob, err error) {
|
||||||
|
completedTime := time.Now().UTC()
|
||||||
|
if err = db.Queries.CompleteScrapeJob(ctx, postgres.CompleteScrapeJobParams{
|
||||||
|
ID: int32(ID),
|
||||||
|
Completedts: sql.NullTime{
|
||||||
|
Time: completedTime,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Auctionsfound: int32(status.AuctionCount),
|
||||||
|
Errors: status.Errors,
|
||||||
|
}); err != nil {
|
||||||
|
err = fmt.Errorf("could update scrape job in DB: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rsj postgres.RunnerScrapejob
|
||||||
|
if rsj, err = db.Queries.GetJobByID(ctx, int32(ID)); err != nil {
|
||||||
|
err = fmt.Errorf("could not get job by ID: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sj = runner.ScrapeJob{
|
||||||
|
ID: ID,
|
||||||
|
Started: rsj.Startedts,
|
||||||
|
Completed: completedTime,
|
||||||
|
TargetSite: rsj.Targetsitename,
|
||||||
|
AuctionsFound: status.AuctionCount,
|
||||||
|
Errors: status.Errors,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *PGRunnerStorage) GetJobs(ctx context.Context) (results []runner.ScrapeJob, err error) {
|
||||||
|
var jobs []postgres.RunnerScrapejob
|
||||||
|
if jobs, err = db.Queries.GetJobs(ctx); err != nil {
|
||||||
|
err = fmt.Errorf("Couldn't get jobs from DB: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, j := range jobs {
|
||||||
|
results = append(results, runner.ScrapeJob{
|
||||||
|
ID: int(j.ID),
|
||||||
|
Started: j.Startedts,
|
||||||
|
Completed: j.Completedts.Time,
|
||||||
|
TargetSite: j.Targetsitename,
|
||||||
|
AuctionsFound: int(j.Auctionsfound),
|
||||||
|
Errors: j.Errors,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/domain/catalog"
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/domain/runner"
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
kernel.TraceLog.Println("Registering AuctionFinder liveauctioneers")
|
||||||
|
runner.RegisterAuctionFinder(
|
||||||
|
LAAuctionFinder("liveauctioneers"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LAAuctionFinder string
|
||||||
|
|
||||||
|
func (la LAAuctionFinder) String() string {
|
||||||
|
return string(la)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (la LAAuctionFinder) Find(ctx context.Context, limit int, results chan<- catalog.Auction) (err error) {
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
|
fetched := -1
|
||||||
|
total := 0
|
||||||
|
page := uint(0)
|
||||||
|
|
||||||
|
for fetched < limit {
|
||||||
|
var ids LACatalogIDs
|
||||||
|
if ids, total, err = LAGetUpcomingSaleIDs(ctx, GetUpcomingSaleIDsInput{
|
||||||
|
Page: page,
|
||||||
|
Limit: 128,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = total
|
||||||
|
}
|
||||||
|
|
||||||
|
var auctions []catalog.Auction
|
||||||
|
if auctions, err = LAGetSaleInfo(ctx, ids); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fetched += len(auctions)
|
||||||
|
for _, a := range auctions {
|
||||||
|
results <- a
|
||||||
|
}
|
||||||
|
|
||||||
|
if fetched >= limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
page++
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUpcomingSaleIDsInput struct {
|
||||||
|
Page uint
|
||||||
|
Limit uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func LAGetUpcomingSaleIDs(ctx context.Context, in GetUpcomingSaleIDsInput) (ids LACatalogIDs, total int, err error) {
|
||||||
|
if in.Limit == 0 {
|
||||||
|
in.Limit = 128
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"https://search-party-prod.liveauctioneers.com/search/catalogsearch?c=20170802&client=web&client_version=5.0.0&excludedHouses=[]&max_facet_values=0&offset=%d&sort=saleStart&pageSize=%d",
|
||||||
|
in.Page,
|
||||||
|
in.Limit,
|
||||||
|
),
|
||||||
|
|
||||||
|
nil)
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0")
|
||||||
|
req.Header.Set("Accept", "*/*")
|
||||||
|
req.Header.Set("Accept-Encoding", "gzip")
|
||||||
|
req.Header.Set("Cache-Control", "no-cache")
|
||||||
|
|
||||||
|
var res *http.Response
|
||||||
|
if res, err = http.DefaultClient.Do(req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode > 299 {
|
||||||
|
err = fmt.Errorf("Got a bad http status: %d", res.StatusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
var searchResults struct {
|
||||||
|
Payload struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Results []struct {
|
||||||
|
ID int `json:"catid"`
|
||||||
|
} `json:"results"`
|
||||||
|
} `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.NewDecoder(res.Body).Decode(&searchResults); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if searchResults.Payload.Results == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total = searchResults.Payload.Count
|
||||||
|
|
||||||
|
ids = LACatalogIDs(make([]int, len(searchResults.Payload.Results)))
|
||||||
|
for idx, result := range searchResults.Payload.Results {
|
||||||
|
ids[idx] = result.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type LACatalogIDs []int
|
||||||
|
|
||||||
|
func (cip LACatalogIDs) String() string {
|
||||||
|
sb := &strings.Builder{}
|
||||||
|
sb.WriteString("{\"ids\":[")
|
||||||
|
for idx, catID := range cip {
|
||||||
|
fmt.Fprintf(sb, "%d", catID)
|
||||||
|
if idx < len(cip)-1 {
|
||||||
|
sb.WriteString(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString("]}")
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func LAGetSaleInfo(ctx context.Context, catIDs LACatalogIDs) (results []catalog.Auction, err error) {
|
||||||
|
if catIDs == nil || len(catIDs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
|
||||||
|
"https://item-api-prod.liveauctioneers.com/spa/small/catalogs",
|
||||||
|
strings.NewReader(catIDs.String()))
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0")
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
var res *http.Response
|
||||||
|
if res, err = http.DefaultClient.Do(req); err != nil {
|
||||||
|
err = fmt.Errorf("could not complete request: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode >= 300 {
|
||||||
|
err = fmt.Errorf("got non 200 status: %d", res.StatusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var apiResults struct {
|
||||||
|
Data struct {
|
||||||
|
Catalogs []struct {
|
||||||
|
ID int `json:"catid"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ItemCount int `json:"lotsListed"`
|
||||||
|
SaleStartTS int64 `json:"saleStartTs"`
|
||||||
|
Address struct {
|
||||||
|
CountryCode string `json:"country"`
|
||||||
|
Lat float64 `json:"lat"`
|
||||||
|
Long float64 `json:"lng"`
|
||||||
|
State string `json:"state"`
|
||||||
|
City string `json:"city"`
|
||||||
|
} `json:"address"`
|
||||||
|
} `json:"catalogs"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.NewDecoder(res.Body).Decode(&apiResults); err != nil {
|
||||||
|
err = fmt.Errorf("could not parse response: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
results = make([]catalog.Auction, len(apiResults.Data.Catalogs))
|
||||||
|
for idx, c := range apiResults.Data.Catalogs {
|
||||||
|
results[idx] = catalog.Auction{
|
||||||
|
Title: c.Title,
|
||||||
|
Description: c.Description,
|
||||||
|
SourceSiteURL: "https://www.liveauctioneers.com",
|
||||||
|
SourceSiteName: "Live Auctioneers",
|
||||||
|
SourceURL: fmt.Sprintf("https://www.liveauctioneers.com/catalog/%d", c.ID),
|
||||||
|
Start: time.Unix(c.SaleStartTS, 0),
|
||||||
|
End: time.Unix(c.SaleStartTS, 0).Add(time.Hour * 8),
|
||||||
|
ItemCount: c.ItemCount,
|
||||||
|
Country: c.Address.CountryCode,
|
||||||
|
Province: c.Address.City,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func Test_catIDPayload_String(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cip LACatalogIDs
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy path",
|
||||||
|
cip: LACatalogIDs{283257, 283882, 284163},
|
||||||
|
want: "{\"ids\":[283257,283882,284163]}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no items",
|
||||||
|
cip: LACatalogIDs{},
|
||||||
|
want: "{\"ids\":[]}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.cip.String(); got != tt.want {
|
||||||
|
t.Errorf("catIDPayload.String() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/domain/runner"
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/runner/api"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRunnerServer(d *runner.Domain) func(grpcServer grpc.ServiceRegistrar, endpoint string) {
|
||||||
|
return func(grpcServer grpc.ServiceRegistrar, endpoint string) {
|
||||||
|
api.RegisterRunnerServer(grpcServer, &runnerHandler{domain: d})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type runnerHandler struct {
|
||||||
|
api.UnimplementedRunnerServer
|
||||||
|
domain *runner.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rh *runnerHandler) FindNewUpcoming(ctx context.Context, cmd *api.FindNewUpcomingCommand) (*api.JobResult, error) {
|
||||||
|
out, err := rh.domain.FindNewUpcoming(ctx, runner.FindNewUpcomingInput{
|
||||||
|
TargetSite: cmd.TargetSite,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "method FindNewUpcoming failed: %q", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.JobResult{
|
||||||
|
Id: int32(out.Job.ID),
|
||||||
|
AuctionsFound: int32(out.Job.AuctionsFound),
|
||||||
|
CreatedTs: timestamppb.New(out.Job.Started),
|
||||||
|
CompletedTs: timestamppb.New(out.Job.Completed),
|
||||||
|
TargetSiteName: out.Job.TargetSite,
|
||||||
|
Errors: out.Job.Errors,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rh *runnerHandler) GetJobs(ctx context.Context, cmd *api.GetJobsCommand) (*api.JobsResult, error) {
|
||||||
|
out, err := rh.domain.GetJobs(ctx, runner.GetJobsInput{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "method GetJobs failed: %q", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &api.JobsResult{
|
||||||
|
Jobs: []*api.JobResult{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, j := range out.Jobs {
|
||||||
|
result.Jobs = append(result.Jobs, &api.JobResult{
|
||||||
|
Id: int32(j.ID),
|
||||||
|
AuctionsFound: int32(j.AuctionsFound),
|
||||||
|
CreatedTs: timestamppb.New(j.Started),
|
||||||
|
CompletedTs: timestamppb.New(j.Completed),
|
||||||
|
TargetSiteName: j.TargetSite,
|
||||||
|
Errors: j.Errors,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
@ -2,33 +2,113 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/domain/runner"
|
||||||
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/runner/internal"
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/runner/internal/data"
|
||||||
|
"git.vdhsn.com/barretthousen/barretthousen/src/runner/internal/data/postgres"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
"go.uber.org/dig"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
_ "git.vdhsn.com/barretthousen/barretthousen/src/runner/internal/domain/liveauctioneers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
RunnerApp struct {
|
||||||
|
LogLevel kernel.LogLevel `yaml:"log_level" env:"BH_LOG_LEVEL" env-default:"0" yaml-default:"0"`
|
||||||
|
Port int `yaml:"port" env:"RUNNER_PORT"`
|
||||||
|
DB_Service kernel.PostgresConnection `yaml:"db_service" env:"RUNNER_DB_SERVICE"`
|
||||||
|
DB_Migrate kernel.PostgresConnection `yaml:"db_migrate" env:"RUNNER_DB_MIGRATE"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var migrate = flag.Bool("migrate", false, "migrates postgres db")
|
||||||
|
|
||||||
|
//go:embed internal/data/postgres/schema.sql
|
||||||
|
var dbMigrateScript string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
kernel.Run(context.Background(), kernel.AppConfig{
|
flag.Parse()
|
||||||
App: &RunnerApp{},
|
|
||||||
|
kernel.Run(context.Background(), &RunnerApp{
|
||||||
LogLevel: kernel.LevelTrace,
|
LogLevel: kernel.LevelTrace,
|
||||||
|
Port: 5001,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunnerApp struct{}
|
|
||||||
|
|
||||||
func (app *RunnerApp) Start(ctx context.Context) error {
|
func (app *RunnerApp) Start(ctx context.Context) error {
|
||||||
t := time.NewTicker(time.Second)
|
if *migrate {
|
||||||
|
kernel.InfoLog.Printf("running db migrations on %v", app.DB_Migrate)
|
||||||
|
|
||||||
|
kernel.InfoLog.Printf("MIGRATION SCRIPT:\n%q", dbMigrateScript)
|
||||||
|
if err := kernel.Migrate(ctx, app.DB_Migrate, dbMigrateScript); err != nil {
|
||||||
|
return fmt.Errorf("could not execute db migration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
return nil
|
||||||
case <-t.C:
|
|
||||||
kernel.TraceLog.Println("waiting")
|
|
||||||
t.Reset(time.Second)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ioc := dig.New()
|
||||||
|
var err error
|
||||||
|
if err = ioc.Provide(func() kernel.PostgresConnection {
|
||||||
|
return app.DB_Service
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = ioc.Provide(func(pgCfg kernel.PostgresConnection) (*pgx.Conn, error) {
|
||||||
|
return kernel.NewDBConnection(ctx, pgCfg)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioc.Provide(func(pgConn *pgx.Conn) *postgres.Queries {
|
||||||
|
return postgres.New(pgConn)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioc.Provide(func(queries *postgres.Queries) runner.Storage {
|
||||||
|
return &data.PGRunnerStorage{Queries: queries}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioc.Provide(func() runner.CatalogService {
|
||||||
|
return &runner.CatalogServiceStub{}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioc.Provide(func(css runner.CatalogService, rs runner.Storage) *runner.Domain {
|
||||||
|
return &runner.Domain{
|
||||||
|
Storage: rs,
|
||||||
|
CatalogService: css,
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioc.Invoke(func(d *runner.Domain) error {
|
||||||
|
runnerService := internal.NewRunnerServer(d)
|
||||||
|
|
||||||
|
if _, err := kernel.StartGRPCServer(ctx, app.Port, runnerService); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *RunnerApp) OnStop(ctx context.Context) {
|
func (app *RunnerApp) OnStop(ctx context.Context) {
|
||||||
|
if err := kernel.StopGRPCServer(); err != nil {
|
||||||
|
kernel.ErrorLog.Printf("could not gracefully stop GRPC server: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *RunnerApp) GetLogLevel() kernel.LogLevel { return app.LogLevel }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue