74 Commits

Author SHA1 Message Date
366dccde12 fix: use go-migrate pkg to init or update db schema
Instead to implement own logic how the database scheme should be updated
or migrated to a newer or older version flucky use now instead the
go-migrate package.
2021-01-30 15:44:21 +01:00
23695b4513 fix: update rows when already exist during an import
When rows in the table devices, sensors, humidities, pressures and
temperatures already exist, they will only be updated.
2020-12-14 20:54:20 +01:00
522fe2746a fix: add or update devices, sensors and measured values
Add additional functions to the repository to add or update devices,
sensors or measured values. Furthermore the test has been adapt to the
new functions.
2020-12-14 20:54:20 +01:00
d0cfdd7102 fix: exclude dockerutils and testutils package 2020-11-07 23:03:11 +01:00
675af77965 feat: import from sqlite or postgresql 2020-11-07 22:59:48 +01:00
592e9b7f5c fix: upgrade dependencies 2020-11-06 23:08:54 +01:00
a2e66ce08b fix: sqlite and postgres, close rows and pass nothing instead nil 2020-11-06 23:07:10 +01:00
1701db7b8e fix: define cache values
The number of measured values in the cache before they are stored in the
database can not be defined over the flag --cached-values.
2020-10-07 23:59:27 +02:00
0fc4aa7c28 fix: postgres columns with timezone
Add timezone for the columns creation_date and update_date, otherwise
it's difficult to compare the times correctly in the unit test.

Furthermore the default value of the creation_date will now be defined
by the postgres implementation of the database interface.
2020-10-07 23:38:27 +02:00
3a090d190e fix: cli temperature read
changes:
- fix: read temperature values without daemon
  Add subcommand to read temperature values without starting the daemon

- fix: implement measured value types
  Replace measured value types with constants

- fix: add sensor pipelines
  Add functions which returns a channel with measured values

- fix: filter measured values from a channel
  Add functions to filter measured values by sensor id or measured
  value types.
2020-09-21 20:05:37 +02:00
7cbd80c726 fix: remove obsolete attributes from config.json
changes:
- removed unused attributes from config.json
2020-09-07 19:12:30 +02:00
c279d288b4 fix: increase jobs to number of cpus 2020-08-25 19:31:30 +02:00
ef9266b010 fix: systemd service unit 2020-08-19 19:15:06 +02:00
6c03bc078b fix: default directory for the sqlite db
The default directory of the sqlite db has now been changed depending of
the user who executes flucky.

The place where the sqlite db will be stored is unchanged for the root
user, but for all other users the sqlite db will now be created to
~/.cache/flucky
2020-07-31 00:02:29 +02:00
8c1bd57ad5 fix: gnu-cc, missing humidity table, restrict repository select 2020-06-27 21:12:00 +02:00
92a776f6ce fix: missing package import 2020-06-27 18:14:49 +02:00
9ee9742aff fix: use own dht pkg 2020-06-21 18:42:33 +02:00
ec7cad4555 feat: add pkg to start postgres container 2020-06-18 21:37:09 +02:00
fa45125b49 fix: dockerutils - add env to container 2020-06-14 22:08:07 +02:00
83fae430b9 fix: disable unit tests in ci 2020-06-10 21:26:22 +02:00
0384bbd574 fix: move sql driver import into main.go 2020-06-10 21:25:52 +02:00
7e018280fb fix: replace travis with drone 2020-06-10 21:19:27 +02:00
a49546bd6d fix: migrate to gitea 2020-06-10 21:13:05 +02:00
940e04371c feat: dockerutils lib to start container images 2020-06-10 09:42:40 +02:00
4931c63c10 fix: add postgres backend
changes:
- Add postgres backend
- Modified or added table attributes.
  UpdateDate, ForeignKeys, Booleans
- Fix test for sqlite and postgres. Compare json instead the struct.
2020-06-01 22:50:05 +02:00
a1c28a0a2e fix: ci steps, disable arm5 and arm7 architectures
changes:
- Remove obsolete architectures and environment variables from the
  Makefile. Remove rpm-build steps. This will be exported in an own
  repository.
- Adapt the travis file. Remove obsolete architecture steps.
2020-06-01 17:57:27 +02:00
10069568f9 fix: renamed storage endpoint into dsn
Changes:
- Renamed storage endpoint into dsn (data source name).
- Add additional dsn fallback property. This dsn will be used in futes
  to store informations, if the main dsn backend does not work
  correctly. For example, if no connection can be established over the
  network to a database.
2020-06-01 12:41:48 +02:00
43e9d00dcb fix: implement repository test
changes:
- Implement repository test for the sqlite backend
- Add testutils package to start container images
- Remove deprecated till_date in measured values
- Renamed columns of the table humidities, pressures and temperatures
2020-06-01 00:56:04 +02:00
11717679bc fix: daemon load sensors from repo instead config 2020-05-22 07:45:14 +02:00
d0e238e64a fix: use repository instead db package 2020-05-21 20:07:32 +02:00
8f1c7b10f7 fix: implement repository pkg 2020-05-21 17:40:24 +02:00
fb916c94ae fix: add, rename and remove sensor
changes:
- Implement function to add, rename and remove sensors
2020-05-17 13:00:51 +02:00
fb8d4dd5eb fix: new implementation
changes:
- Remove cli
  Some cli commands are not complete tested and are deprecated.

- Daemon
  - Old version has a very bad implementation of how to verify, if the
    device or the sensors are in the database insert. The current
    implementation can be improved but this one is betten then the old
    one.
  - Remove complete the cache store implementation. Use a normal array
    and query the length and capacity to determine how the array cache
    must be cleaned.

- Type
  Remove unused types and functions
2020-05-03 14:09:22 +02:00
84d052184e fix(Makefile): container image registry login 2020-01-21 18:26:22 +01:00
0f9b62fb81 fix(ci): list build steps seperate 2020-01-20 23:08:17 +01:00
c679b29051 fix(ci): add codecov 2020-01-20 21:45:58 +01:00
17dda6a987 fix(README): supported backends, sensors and distribution specific repos 2020-01-19 14:48:18 +01:00
cc317e0b6c fix(Makefile): use full container image names 2020-01-19 14:19:14 +01:00
8cc232adc1 fix:_renamed import path to volker-raschek 2020-01-19 13:30:47 +01:00
671a3eb748 fix(pkg/server): logging and save values into postgres 2020-01-18 14:55:43 +01:00
6f45c2957a test(postgres): add missing tests 2020-01-18 14:42:15 +01:00
546139492b fix(Makefile): step to generate html coverage site 2020-01-18 14:41:17 +01:00
3581424bd2 fix(pkg/storage): write creation date in storage pkg 2020-01-18 14:40:47 +01:00
c266117785 fix(pkg/storage/logfile): remove func append - dead code 2020-01-13 22:45:51 +01:00
0765bd29d1 refac(cmd): mergeing, ordering and outsourcing cmd subcommands 2020-01-13 22:32:00 +01:00
0261203395 fix(cmd): remove obsolete semver 2020-01-11 17:24:34 +01:00
12246aae0c fix(pkg/daemon): write values into logfile 2020-01-11 17:24:18 +01:00
002f3e9e25 fix(cmd/sensor): set tick duration for new sensors 2020-01-11 13:18:38 +01:00
40857249c4 test(pkg/cli): Implementation of test to stabilize the code 2020-01-11 13:03:58 +01:00
b595cf1ac8 fix(pkg/cli): GetSensorIDsByMeasuredValues 2020-01-11 12:10:26 +01:00
f1a4ade402 fix(Makefile): remove deprecated test-update step 2020-01-10 22:09:24 +01:00
2cd2188dcb refac(pkg/types): remove deprecated prefix name of struct attributes 2020-01-10 22:03:49 +01:00
95fb1f6745 fix(pkg/sensor): reduce interface functions for better error handling 2020-01-10 19:42:19 +01:00
ca4269fff8 fix(Makefile): generate shell completions on remote system 2020-01-09 21:59:18 +01:00
a507bef108 fix(Makefile, Dockerfile): optimization of the container image building process 2020-01-09 21:59:18 +01:00
f0bba4a202 fix(Makefile): change semver pattern 2020-01-08 21:54:39 +01:00
111dc0e4f1 feat(cmd/completion): generate shell completion files 2020-01-08 21:54:15 +01:00
5f48d29dae fix(Makefile): remove obsolete steps
changes:
- Replace substr func with variable patterns.
- Remove deploy and compression steps
2020-01-08 18:08:55 +01:00
39b3a495ac fix: remove root check
Changes:
- The owner of the flucky binary must be root. To grant access to the
  binary and third party libs, for example wire1, gpio or i2c, the
  binary must have a sticky bit
2019-12-08 15:07:24 +01:00
b125bf432c fix(cli/db): remove obsolete db subcommand 2019-12-08 12:49:21 +01:00
dbef4f8241 fix(pkg/config): use storage endpoints
changes:
- Only one storage endpoint can be defined. This consists of a URL which
  can be used to specify whether the data is to be stored in a file or
  in a database.
2019-12-08 12:49:21 +01:00
afe55b3d33 fix(pkg/daemon): compression and rounding 2019-12-08 12:49:21 +01:00
25dd99cd3d test(pkg/storage): add test for compression and rounding 2019-12-08 12:49:21 +01:00
86f598bc7d fix(test): goldenFiles 2019-12-08 12:49:20 +01:00
a7722e028a fix(pkg/storage/logfile): create logfile if not exists 2019-12-08 12:49:20 +01:00
ea83d43cd6 fix(pkg/logger): exclude logger library into an own repository 2019-12-08 12:49:20 +01:00
bacddcfbf9 fix(Makefile, travis): define correct make steps 2019-12-08 12:49:20 +01:00
4e531b8734 fix(README): make steps 2019-12-08 12:49:20 +01:00
4ed014c0df fix(README): docker pull shield 2019-12-08 12:49:20 +01:00
036ff28175 doc(Makefile): describe some environment variables 2019-12-08 12:49:20 +01:00
04bc3baffe fix(pkg/daemon): save measured values into postgres database if defined 2019-12-08 12:49:19 +01:00
6223f4e79b fix(pkg/db): create or upgrade postgres schema based on semver 2019-12-08 12:49:14 +01:00
f19cc3249a fix(README): add godoc reference 2019-08-27 21:43:10 +02:00
837f6c1cb2 fix(Makefile): container steps 2019-08-26 16:08:52 +02:00
184 changed files with 5986 additions and 6495 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
bin

25
.drone.yml Normal file
View File

@ -0,0 +1,25 @@
kind: pipeline
type: docker
name: amd64
steps:
- name: build-linux-amd64
image: docker.io/volkerraschek/build-image:latest
commands:
- make --jobs=$(nproc) bin/linux/amd64/flucky
when:
event:
- push
- pull_request
- tag
# steps:
# - name: test-unit
# image: docker.io/volkerraschek/build-image:latest
# commands:
# - make test/unit
# when:
# event:
# - push
# - pull_request
# - tag

View File

@ -1,15 +0,0 @@
SERVER_PORT=80
DATABASE_DRIVER=postgres
DATABASE_NAME=postgres
DATABASE_SCHEMA=public
DATABASE_USER=postgres
DATABASE_PASSWORD=postgres
PG_HOST=flucky_db
PG_INTERN_PORT=5432
PG_EXTERN_PORT=5433
PG_NAME=public
PG_USER=postgres
PG_PASSWORD=postgres
TZ=Europe/Berlin

3
.gitignore vendored
View File

@ -11,3 +11,6 @@ flucky.tar.xz
# directories
.vscode/
# coverage files
coverage*

View File

@ -1,22 +0,0 @@
language: go
services:
- docker
jobs:
include:
- stage: build
name: go-build
script: make container/all
deploy:
- provider: script
script: make container/release
# skip_cleanup: true
on:
tags: true
notifications:
email:
on_success: change
on_failure: change

20
Dockerfile Normal file
View File

@ -0,0 +1,20 @@
# ARGs
# ==================================
ARG BASE_IMAGE
ARG BUILD_IMAGE
ARG EXECUTABLE
ARG EXECUTABLE_TARGET
ARG GOPROXY
ARG GOPRIVATE
ARG VERSION
# BUILD
# ==================================
FROM ${BUILD_IMAGE} AS build
COPY . /workspace
RUN make ${EXECUTABLE_TARGET} VERSION=${VERSION} GOPROXY=${GOPROXY} GOPRIVATE=${GOPRIVATE}
# TARGET
# ==================================
FROM ${BASE_IMAGE}
COPY --from=build /workspace/${EXECUTABLE_TARGET} /usr/bin/${EXECUTABLE}

264
Makefile
View File

@ -1,140 +1,196 @@
# UID/GID
# UID or GID is the UNIX user ID or group ID of the user who executes
# make. If the UID or GID is not passed as a make variable, an attempt
# is made to determine it.
UID?=$(shell id --user)
GID?=$(shell id --group)
# VERSION
# If no version is specified as a parameter of make, the last git hash
# value is taken.
VERSION:=$(or ${TRAVIS_TAG}, $(shell git rev-parse --short HEAD)-git)
VERSION?=$(shell git describe --abbrev=0)+$(shell date +'%Y%m%d%H%I%S')
# GOPROXY settings
# If no GOPROXY environment variable available, the pre-defined GOPROXY from go
# env to download and validate go modules is used. Exclude downloading and
# validation of all private modules which are pre-defined in GOPRIVATE. If no
# GOPRIVATE variable defined, the variable of go env is used.
GOPROXY?=$(shell go env GOPROXY)
GOPRIVATE?=$(shell go env GOPRIVATE)
# CONTAINER_RUNTIME
# The CONTAINER_RUNTIME variable will be used to specified the path to a
# container runtime. This is needed to start and run a container images.
CONTAINER_RUNTIME?=$(shell which docker)
# BUILD_IMAGE
BUILD_IMAGE:=volkerraschek/build-image:latest
# GH_USER/GITHUB_TOKEN
# It's the user name from github.com and his token. This token and the username
# can be set over encrypted environment variables in the ci/cd pipeline
GITHUB_USER=volker-raschek
GITHUB_TOKEN?=""
CONTAINER_IMAGE_VERSION?=latest
# EXECUTABLE
# Executable binary which should be compiled for different architecures
EXECUTABLE:=flucky
EXECUTABLE=flucky
# UNIX_EXECUTABLES
# Define all executables for different architectures and operation systems
UNIX_EXECUTABLES := \
darwin/amd64/$(EXECUTABLE) \
freebsd/amd64/$(EXECUTABLE) \
linux/amd64/$(EXECUTABLE) \
linux/arm/5/$(EXECUTABLE) \
linux/arm/7/$(EXECUTABLE) \
# BINARIES
# ==============================================================================
EXECUTABLE_TARGETS:= \
bin/linux/amd64/${EXECUTABLE} \
bin/linux/arm/5/${EXECUTABLE} \
bin/linux/arm/7/${EXECUTABLE} \
bin/tmp/${EXECUTABLE}
# EXECUTABLE_TARGETS
# Include the relative paths to all executables
EXECUTABLE_TARGETS=$(UNIX_EXECUTABLES:%=bin/%)
${EXECUTABLE}: bin/tmp/${EXECUTABLE}
# COMPRSSED_EXECUTABLES
# Append to all defined executables the compression extentions to detect the
# different make steps which compress the binary
COMPRESSED_EXECUTABLES=$(UNIX_EXECUTABLES:%=%.tar.bz2) $(UNIX_EXECUTABLES:%=%.tar.gz) $(UNIX_EXECUTABLES:%=%.tar.xz)
COMPRESSED_EXECUTABLE_TARGETS=$(COMPRESSED_EXECUTABLES:%=bin/%)
bin/linux/amd64/${EXECUTABLE}: bindata
CGO_ENABLED=1 \
GOOS=linux \
GOARCH=amd64 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@}
# PHONY
# To avoid a conflict with an existing file and a defined make step
.PHONY: clean container/${EXECUTABLE_TARGETS} container/test release remote test
all: ${EXECUTABLE}
$(EXECUTABLE): bindata
go build -ldflags "-X main.version=${VERSION}" -o "$@"
bin/tmp/${EXECUTABLE}: bindata
go build -ldflags "-X main.version=${VERSION}" -o "$@"
# arm
bin/linux/arm/5/${EXECUTABLE}: bindata
GOARM=5 GOARCH=arm go build -ldflags "-X main.version=${VERSION}" -o "$@"
CGO_ENABLED=1 \
GOOS=linux \
GOARCH=arm \
GOARM=5 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@}
bin/linux/arm/7/${EXECUTABLE}: bindata
GOARM=7 GOARCH=arm go build -ldflags "-X main.version=${VERSION}" -o "$@"
CC=arm-linux-gnueabihf-gcc \
CGO_ENABLED=1 \
GOOS=linux \
GOARCH=arm \
GOARM=7 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@}
# 386
bin/darwin/386/$(EXECUTABLE): bindata
GOARCH=386 GOOS=darwin go build -ldflags "-X main.version=${VERSION}" -o "$@"
bin/tmp/${EXECUTABLE}: bindata
CGO_ENABLED=1 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@}
bin/linux/386/$(EXECUTABLE): bindata
GOARCH=386 GOOS=linux go build -ldflags "-X main.version=${VERSION}" -o "$@"
# GO-BINDATA
# ==============================================================================
BINDATA_TARGETS := \
pkg/repository/db/postgres/ddl/bindata.go \
pkg/repository/db/postgres/dml/bindata.go \
pkg/repository/db/sqlite3/ddl/bindata.go \
pkg/repository/db/sqlite3/dml/bindata.go
# amd64
bin/freebsd/amd64/$(EXECUTABLE): bindata
GOARCH=amd64 GOOS=freebsd go build -ldflags "-X main.version=${VERSION}" -o "$@"
PHONY+=bindata
bindata: clean ${BINDATA_TARGETS}
bin/darwin/amd64/$(EXECUTABLE): bindata
GOARCH=amd64 GOOS=darwin go build -ldflags "-X main.version=${VERSION}" -o "$@"
pkg/repository/db/postgres/ddl/bindata.go:
go-bindata -pkg postgresddl -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/*
bin/linux/amd64/$(EXECUTABLE): bindata
GOARCH=amd64 GOOS=linux go build -ldflags "-X main.version=${VERSION}" -o "$@"
pkg/repository/db/postgres/dml/bindata.go:
go-bindata -pkg postgresdml -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/*
bindata:
go-bindata -pkg db -o ./pkg/db/bindataSQL.go ./pkg/db/sql/***
go-bindata -pkg goldenfiles -o ./test/goldenfiles/bindata.go ./test/goldenfiles/json/***
%.tar.bz2: %
tar --create --bzip2 --file "$@" "$<"
%.tar.gz: %
tar --create --gunzip --file "$@" "$<"
%.tar.xz: %
tar --create --xz --file "$@" "$<"
test: ${EXECUTABLE}
go test -v ./pkg/...
release: clean
$(MAKE) $(COMPRESSED_EXECUTABLE_TARGETS)
github-release release \
--user ${GITHUB_USER} \
--repo ${EXECUTABLE} \
--tag ${VERSION}
$(foreach FILE,$(COMPRESSED_EXECUTABLES),github-release upload --user ${GITHUB_USER} --repo ${EXECUTABLE} --tag ${VERSION} --name $(subst /,-,$(FILE)) --file bin/$(FILE) --replace;)
pkg/repository/db/sqlite3/ddl/bindata.go:
go-bindata -pkg sqlite3ddl -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/*
pkg/repository/db/sqlite3/dml/bindata.go:
go-bindata -pkg sqlite3dml -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/*
# CLEAN
# ==============================================================================
PHONY+=clean
clean:
rm ${EXECUTABLE} || true
rm --recursive --force bin/ || true
rm --force ${BINDATA_TARGETS} || true
rm --force --recursive bin/ || true
# TEST
# ==============================================================================
PHONY+=test/unit
test/unit: bindata
go test -v -race -coverprofile=coverage.txt -covermode=atomic -timeout 600s -count=1 ./pkg/...
container/all:
$(MAKE) container-run COMMAND=all
PHONY+=test/integration
test/integration:
go test -v -count=1 -timeout 600s ./it/...
test/coverage: test/unit
go tool cover -html=coverage.txt
container/test:
$(MAKE) container-run COMMAND=test
# CONTAINER IMAGE STEPS
# ==============================================================================
PHONY+=container-image/build/amd64
container-image/build/amd64:
${CONTAINER_RUNTIME} build \
--build-arg BASE_IMAGE=docker.io/library/alpine:3.11.2 \
--build-arg BUILD_IMAGE=docker.io/volkerraschek/build-image:latest \
--build-arg EXECUTABLE=${EXECUTABLE} \
--build-arg EXECUTABLE_TARGET=bin/linux/amd64/${EXECUTABLE} \
--build-arg GOPROXY=${GOPROXY} \
--build-arg GOPRIVATE=${GOPRIVATE} \
--build-arg VERSION=${VERSION} \
--file Dockerfile \
--no-cache \
--tag docker.io/volkerraschek/flucky:${CONTAINER_IMAGE_VERSION} \
--tag volkerraschek/flucky:${CONTAINER_IMAGE_VERSION} \
.
container/release:
$(MAKE) container-run COMMAND=release
PHONY+=container-image/push/amd64
container-image/push/amd64: container-image/build/amd64
${CONTAINER_RUNTIME} login docker.io \
--username ${CONTAINER_IMAGE_REGISTRY_USER} \
--password ${CONTAINER_IMAGE_REGISTRY_PASSWORD}
${CONTAINER_RUNTIME} push docker.io/volkerraschek/flucky:${CONTAINER_IMAGE_VERSION}
# CONTAINER STEPS - BINARY
# ==============================================================================
# current os
PHONY+=container-run/${EXECUTABLE}
container-run/${EXECUTABLE}:
$(MAKE) container-run COMMAND=${@:container-run/%=%}
# build all binaries for any operating system
PHONY+=container-run/all
container-run/all:
$(MAKE) container-run COMMAND=${@:container-run/%=%}
PHONY+=${EXECUTABLE_TARGETS:%=container-run/%}
${EXECUTABLE_TARGETS:%=container-run/%}:
$(MAKE) container-run COMMAND=${@:container-run/%=%}
# CONTAINER STEPS - GO-BINDATA
# ==============================================================================
PHONY+=container-run/bindata
container-run/bindata:
$(MAKE) container-run COMMAND=${@:container-run/%=%}
# CONTAINER STEPS - CLEAN
# ==============================================================================
PHONY+=container-run/clean
container-run/clean:
$(MAKE) container-run COMMAND=${@:container-run/%=%}
# CONTAINER STEPS - TEST
# ==============================================================================
PHONY+=container-run/test/unit
container-run/test/unit:
$(MAKE) container-run COMMAND=${@:container-run/%=%}
# GENERAL CONTAINER COMMAND
# ==============================================================================
PHONY+=container-run
container-run:
${CONTAINER_RUNTIME} run \
--rm \
--volume ${PWD}:/workspace \
${BUILD_IMAGE} \
--volume /var/run/docker.sock:/var/run/docker.sock \
docker.io/volkerraschek/build-image:latest \
make ${COMMAND} \
UID=${UID} \
GID=${GID} \
VERSION=${VERSION} \
GITHUB_TOKEN=${GITHUB_TOKEN}
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
VERSION=${VERSION:v%=%}
remote: bin/linux/arm/7/${EXECUTABLE}
scp bin/linux/arm/7/${EXECUTABLE} ${FLUCKY_REMOTE}:/usr/local/bin
ssh ${FLUCKY_REMOTE} "chmod +x /usr/local/bin/flucky"
# REMOTE
# ==============================================================================
PHONY+=${FLUCKY_REMOTE:%=remote/%}
remote/${FLUCKY_REMOTE}: bin/linux/arm/7/${EXECUTABLE}
scp bin/linux/arm/7/${EXECUTABLE} root@${FLUCKY_REMOTE}:/usr/local/bin/${EXECUTABLE}
# ssh root@${FLUCKY_REMOTE} 'mkdir --parent /etc/bash_completion.d || true'
# ssh root@${FLUCKY_REMOTE} 'flucky completion bash > /etc/bash_completion.d/flucky.sh && chmod +x /etc/bash_completion.d/flucky.sh'
# ssh root@${FLUCKY_REMOTE} 'flucky completion zsh > /etc/bash_completion.d/flucky.zsh && chmod +x /etc/bash_completion.d/flucky.zsh'
ssh root@${FLUCKY_REMOTE} 'chmod +x /usr/local/bin/${EXECUTABLE}'
# PHONY
# ==============================================================================
# Declare the contents of the PHONY variable as phony. We keep that information
# in a variable so we can use it in if_changed.
.PHONY: ${PHONY}

View File

@ -1,72 +1,71 @@
# flucky
[![Build Status](https://travis-ci.com/go-flucky/flucky.svg?branch=master)](https://travis-ci.com/go-flucky/flucky)
[![Go Report Card](https://goreportcard.com/badge/github.com/volker-raschek/flucky)](https://goreportcard.com/report/github.com/volker-raschek/flucky)
[![Build Status](https://drone.cryptic.systems/api/badges/volker.raschek/flucky/status.svg)](https://drone.cryptic.systems/volker.raschek/flucky)
[![Docker Pulls](https://img.shields.io/docker/pulls/volkerraschek/flucky)](https://hub.docker.com/r/volkerraschek/flucky)
Flucky is a lightweight program written in go for reading data from sensors, for
example with a banana or raspberry pi. In addition, flucky provides a REST-API
to receive from other flucky installations measured values. The measured values
can be saved in a logfile or into a database. Currently is only postgres
supported.
to receive from other flucky installations measured values. All received
measured values, no matter if they come directly from flucky or from a server
instance of flucky with the provided REST-API, can be stored into a database.
## Supported and planned sensors
| Name | Measured values | Supported |
| ------- | --------------------------------------- | --------- |
| BME280 | humidity, pressure, temperature | Yes |
| DHT11 | temperature | Yes |
| DHT22 | temperature | Yes |
| DS18B20 | temperature | Yes |
| SDS011 | fine dust | Planning |
## Supported and planned backends
| Backend | Supported |
| --------------- | --------------- |
| flucky | WIP |
| Logfile | CSV, JSON, XML |
| MySQL, MariaDB | Planning |
| PostgreSQL | Yes |
| SQLite | Planning |
## Installation
Flucky can be installed over multiple ways, for example with your local package
manager from your linux distribution. At the moment it's only the rpm package
format supported. Alternatively the source code can be compiled to get the
flucky binary.
Flucky can be installed over multiple ways. For example over a specific linux
distributions package or as container. The following table lists all
repositories where flucky can be otained from, but flucky can already be
compiled from source code.
### RPM-Package
To install flucky over a RPM package you can download a specific version from
github.com or configure the RPM-Mirror.
#### RPM-Mirror
```bash
$ cat > /etc/yum.repos.d/flucky.rpm <<EOF
[flucky]
name=flucky
baseurl=http://rpm-mirror.cryptic.systems
gpgcheck=0
EOF
$ yum update && yum install --assumeyes flucky
```
#### Download github.com
```bash
$ VERSION=v0.1.0 \
curl --location https://github.com/volker-raschek/flucky/releases/${VERSION}/flucky.rpm -O flucky-${VERSION}.rpm
$ yum install --assumeyes ./flucky-${VERSION}.rpm
```
| Distribution | Repo's |
| --------------------------- | ------------------------------------------------------------------------------------------- |
| Arch Linux | [armv7](https://arch.cryptic.systems/armv7), [x86_64](https://arch.cryptic.systems/x86_64) |
| Debian, Ubuntu, Linux Mint | Currently not supported |
| RHEL, Fedora, Centos | Planning |
| Container Image | [hub.docker.com](https://hub.docker.com/repository/docker/volkerraschek/flucky) |
### Compiling the source code
An additional ways to install flucky is to compile the source code. There are
two different ways to compile flucky from scratch. The easier ways is
to use the pre-defined container image in the Makefile, which has included all
dependancies to compile flucky. Alternatively, if all dependencies are met,
flucky can also be compiled without the container image. Both variants are
briefly described.
two different ways to compile flucky from scratch. The easier ways is to use the
pre-defined container image, which has included all dependencies to compile
flucky. Alternatively, if all dependencies are met, flucky can also be compiled
without the container image. Both variants are briefly described.
#### Compiling the source code via container image
To compile flucky via container image it's necessary, that a container runtime
is installed. In the Makefile is predefined docker, but it's can be also used
podman. Execute `make container-go-build` to start the compiling process.
is installed. In the `Makefile` is predefined docker, but it's can be also used
podman. Execute `make container-run/flucky` to start the compiling process.
```bash
$ make container-go-build
make container-run COMMAND=go-build
$ make container-run/flucky
make container-run COMMAND=flucky
make[1]: Directory „/home/markus/workspace/flucky“ is entered
/usr/bin/docker run \
--rm \
--volume /home/markus/workspace/flucky:/workspace \
volkerraschek/build-image:latest \
make go-build \
UID=1000 \
GID=1000 \
VERSION=60ee044-git \
GOOS=linux \
GOARCH=amd64
@ -82,10 +81,6 @@ go: finding github.com/satori/go.uuid v1.2.0
#### Compiling the source code without container image
Make sure you have installed go >= v1.12. Execute `make go-build` to compile
Make sure you have installed go >= v1.12. Execute `make flucky` to compile
flucky without a container-image. There should be a similar output as when
compiling flucky via the container image.
## Enhancements
+ Provide flucky additionally as AUR and deb package

51
cli/daemon/daemon.go Normal file
View File

@ -0,0 +1,51 @@
package daemon
import (
"fmt"
"git.cryptic.systems/volker.raschek/flucky/pkg/config"
"git.cryptic.systems/volker.raschek/flucky/pkg/daemon"
"git.cryptic.systems/volker.raschek/go-logger"
"github.com/spf13/cobra"
)
// InitCmd initialize all daemon subcommands
func InitCmd(cmd *cobra.Command) error {
daemonCmd := &cobra.Command{
Use: "daemon",
Short: "Read continuously data from all enabled sensors",
Example: "flucky daemon",
RunE: run,
}
daemonCmd.Flags().Uint("cached-values", 500, "Number of measured values in the cache before they are stored in the database")
cmd.AddCommand(daemonCmd)
return nil
}
func run(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined: %v", err)
}
cachedEntries, err := cmd.Flags().GetUint("cached-values")
if err != nil {
return fmt.Errorf("No cached-entries defined")
}
// logLevel, err := cmd.Flags().GetString("loglevel")
// if err != nil {
// return fmt.Errorf("No loglevel defined: %v", err)
// }
flogger := logger.NewLogger(logger.LogLevelDebug)
cnf, err := config.Read(configFile)
if err != nil {
return err
}
return daemon.Start(cnf, cachedEntries, flogger)
}

81
cli/imp/imp.go Normal file
View File

@ -0,0 +1,81 @@
package imp
import (
"fmt"
"net/url"
"git.cryptic.systems/volker.raschek/flucky/pkg/config"
"git.cryptic.systems/volker.raschek/flucky/pkg/repository"
"git.cryptic.systems/volker.raschek/go-logger"
"github.com/spf13/cobra"
)
var (
importSensors bool
importHumidities bool
importPressures bool
importTemperatures bool
)
func InitCmd(cmd *cobra.Command) error {
importCmd := &cobra.Command{
Use: "import",
Short: "Import data from passed URL",
RunE: importSources,
}
importCmd.Flags().BoolVar(&importSensors, "sensors", true, "Import sensors")
importCmd.Flags().BoolVar(&importHumidities, "humidities", true, "Import humidities")
importCmd.Flags().BoolVar(&importPressures, "pressures", true, "Import pressures")
importCmd.Flags().BoolVar(&importTemperatures, "temperatures", true, "Import temperatures")
cmd.AddCommand(importCmd)
return nil
}
func importSources(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
}
cnf, err := config.Read(configFile)
if err != nil {
return err
}
destURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
logLevelString, err := cmd.Flags().GetString("loglevel")
if err != nil {
return err
}
logLevel, err := logger.ParseLogLevel(logLevelString)
if err != nil {
return err
}
flogger := logger.NewLogger(logLevel)
sourceURL, err := url.Parse(args[0])
if err != nil {
return fmt.Errorf("Failed to parse source url: %w", err)
}
err = repository.Import(sourceURL, destURL, flogger, repository.OptImport{
Sensors: importSensors,
Humidities: importHumidities,
Pressures: importPressures,
Temperatures: importTemperatures,
})
if err != nil {
return fmt.Errorf("Failed to import: %w", err)
}
return nil
}

129
cli/root.go Normal file
View File

@ -0,0 +1,129 @@
package cli
import (
"fmt"
"math"
"os"
"os/user"
"path/filepath"
"time"
"git.cryptic.systems/volker.raschek/flucky/cli/daemon"
imp "git.cryptic.systems/volker.raschek/flucky/cli/imp"
"git.cryptic.systems/volker.raschek/flucky/cli/sensor"
"git.cryptic.systems/volker.raschek/flucky/cli/temperature"
"git.cryptic.systems/volker.raschek/flucky/pkg/config"
"github.com/Masterminds/semver"
uuid "github.com/satori/go.uuid"
"github.com/spf13/cobra"
)
// Execute a
func Execute(version *semver.Version) error {
rootCmd := &cobra.Command{
Use: "flucky",
// Short: "flucky - operate with differen sensors, his values and remote servers to synchronize measured values",
PersistentPreRunE: preRunError,
Version: version.String(),
}
defaultConfigFile, err := getDefaultConfigFile()
if err != nil {
return err
}
rootCmd.PersistentFlags().String("config", defaultConfigFile, "Config file")
rootCmd.PersistentFlags().String("loglevel", "info", "Set the Loglevel. Possible values: debug, info, warn, error, fatal")
subCommands := []func(cmd *cobra.Command) error{
daemon.InitCmd,
imp.InitCmd,
sensor.InitCmd,
temperature.InitCmd,
}
for _, subCommand := range subCommands {
if err := subCommand(rootCmd); err != nil {
return err
}
}
err = rootCmd.Execute()
if err != nil {
return err
}
return nil
}
func preRunError(cmd *cobra.Command, args []string) error {
configFile := cmd.Flag("config").Value.String()
// check if config file exists
if _, err := os.Stat(configFile); os.IsNotExist(err) {
// Time must be truncted for postgres. Postgres currently does not support
// nanoseconds which is automatically include into the go time object
postgresTimeStamp := time.Now()
location, err := time.LoadLocation("Europe/Berlin")
if err != nil {
return err
}
postgresTimeStamp = time.Date(postgresTimeStamp.Year(), postgresTimeStamp.Month(), postgresTimeStamp.Day(), postgresTimeStamp.Hour(), postgresTimeStamp.Minute(), postgresTimeStamp.Second(), int(math.Round(float64(postgresTimeStamp.Nanosecond())/1000000)*1000000), location)
// default cache directory
defaultCacheDir, err := getDefaultCacheDir()
if err != nil {
return err
}
// Default configuration
dsn := fmt.Sprintf("sqlite3://%v/sqlite.db?cache=shared&mode=memory&foreign_keys=on", defaultCacheDir)
cnf := config.Config{
DeviceID: uuid.NewV4().String(),
DSN: dsn,
}
err = config.Write(&cnf, configFile)
if err != nil {
return err
}
}
return nil
}
// getDefaultConfigFile returns the default path to the configuration file of
// flucky
func getDefaultConfigFile() (string, error) {
u, err := user.Current()
if err != nil {
return "", fmt.Errorf("Can not read current user: %v", err)
}
switch u.Uid {
case "0":
return "/etc/flucky/config.json", nil
default:
return filepath.Join(u.HomeDir, ".config/flucky/config.json"), nil
}
}
// getDefaultCacheDir returns the default path to the cache directory where
// flucky stores his measured values.
func getDefaultCacheDir() (string, error) {
u, err := user.Current()
if err != nil {
return "", fmt.Errorf("Can not read current user: %v", err)
}
switch u.Uid {
case "0":
return "/var/cache/flucky", nil
default:
return filepath.Join(u.HomeDir, ".cache/flucky"), nil
}
}

370
cli/sensor/sensor.go Normal file
View File

@ -0,0 +1,370 @@
package sensor
import (
"fmt"
"net/url"
"os"
"git.cryptic.systems/volker.raschek/flucky/pkg/cli"
"git.cryptic.systems/volker.raschek/flucky/pkg/config"
"git.cryptic.systems/volker.raschek/flucky/pkg/repository"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
uuid "github.com/satori/go.uuid"
"github.com/spf13/cobra"
)
// InitCmd initialize all sensor subcommands
func InitCmd(cmd *cobra.Command) error {
sensorCmd := &cobra.Command{
Use: "sensor",
Short: "Manage Sensors",
}
addSensorCmd := &cobra.Command{
Use: "add",
Short: "Add Sensor",
Aliases: []string{"append"},
Args: cobra.ExactArgs(2),
Example: `flucky sensor add --gpio GPIO14 indoor DHT11
flucky sensor add --wire-id 28-011432f0bb3d outdoor DS18B20
flucky sensor add --i2c-bus 1 --i2c-address 0x76 wetter-station BME280`,
RunE: addSensor,
}
addSensorCmd.Flags().Bool("enabled", true, "Enable new sensor")
addSensorCmd.Flags().String("gpio", "", "Defines the GPIO port")
addSensorCmd.Flags().Uint8("i2c-address", 0, "Defines the I2C address on the I2C bus")
addSensorCmd.Flags().Int("i2c-bus", 0, "Defines the I2C bus")
addSensorCmd.Flags().String("location", "", "Location of the sensor")
addSensorCmd.Flags().String("tick-duration", "1m", "Specifies how often measured values should be read from the sensor")
addSensorCmd.Flags().String("wire-id", "", "Defines the Wire-ID")
disableSensorCmd := &cobra.Command{
Use: "disable",
Short: "Disable Sensor",
Args: cobra.MinimumNArgs(1),
Example: "flucky sensor disable outdoor",
RunE: disableSensor,
}
enableSensorCmd := &cobra.Command{
Use: "enable",
Short: "Enable Sensor",
Example: "flucky sensor enable outdoor",
Args: cobra.MinimumNArgs(1),
RunE: enableSensor,
}
listSensorCmd := &cobra.Command{
Use: "list",
Short: "List Sensors",
Aliases: []string{"ls"},
RunE: listSensors,
}
removeSensorCmd := &cobra.Command{
Use: "remove",
Short: "Remove Sensor",
Aliases: []string{"rm"},
Example: "flucky sensor remove outdoor",
Args: cobra.MinimumNArgs(1),
RunE: removeSensor,
}
renameSensorCmd := &cobra.Command{
Use: "rename",
Short: "Rename Sensor",
Args: cobra.ExactArgs(2),
Example: `flucky sensor rename indoor outdoor
flucky sensor rename f98b00ea-a9b2-4e00-924f-113859d0af2d outdoor`,
RunE: renameSensor,
}
for _, subCommand := range []*cobra.Command{
addSensorCmd,
disableSensorCmd,
enableSensorCmd,
listSensorCmd,
removeSensorCmd,
renameSensorCmd,
} {
sensorCmd.AddCommand(subCommand)
}
cmd.AddCommand(sensorCmd)
return nil
}
func addSensor(cmd *cobra.Command, args []string) error {
sensor := &types.Sensor{
ID: uuid.NewV4().String(),
Name: args[0],
Model: args[1],
}
sensorLocation, err := cmd.Flags().GetString("location")
if err != nil {
return err
}
sensor.Location = sensorLocation
sensorEnabled, err := cmd.Flags().GetBool("enabled")
if err != nil {
return err
}
sensor.Enabled = sensorEnabled
sensorGPIO, err := cmd.Flags().GetString("gpio")
if err != nil {
return err
}
if len(sensorGPIO) > 0 {
sensor.GPIONumber = sensorGPIO
}
sensorI2CAddress, err := cmd.Flags().GetUint8("i2c-address")
if err != nil {
return err
}
if sensorI2CAddress > 0 {
sensor.I2CAddress = &sensorI2CAddress
}
sensorI2CBus, err := cmd.Flags().GetInt("i2c-bus")
if err != nil {
return err
}
if sensorI2CBus > 0 {
sensor.I2CBus = &sensorI2CBus
}
sensorTickDuration, err := cmd.Flags().GetString("tick-duration")
if err != nil {
return err
}
sensor.TickDuration = sensorTickDuration
sensorWireID, err := cmd.Flags().GetString("wire-id")
if err != nil {
return err
}
if len(sensorWireID) > 0 {
sensor.WireID = &sensorWireID
}
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
}
cnf, err := config.Read(configFile)
if err != nil {
return err
}
sensor.DeviceID = cnf.DeviceID
dsnURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
// loglevel, err := cmd.Flags().GetString("loglevel")
// if err != nil {
// return fmt.Errorf("No loglevel defined")
// }
flogger := logger.NewLogger(logger.LogLevelDebug)
repo, err := repository.New(dsnURL, flogger)
if err != nil {
return err
}
// add sensor entry to list
err = repo.AddSensors(sensor)
if err != nil {
return err
}
// save new configuration
// err = config.Write(cnf, configFile)
// if err != nil {
// return err
// }
return nil
}
func disableSensor(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
}
cnf, err := config.Read(configFile)
if err != nil {
return err
}
dsnURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
// loglevel, err := cmd.Flags().GetString("loglevel")
// if err != nil {
// return fmt.Errorf("No loglevel defined")
// }
flogger := logger.NewLogger(logger.LogLevelDebug)
repo, err := repository.New(dsnURL, flogger)
if err != nil {
return err
}
return repo.DisableSensorsByNames(args...)
}
func enableSensor(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
}
cnf, err := config.Read(configFile)
if err != nil {
return err
}
dsnURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
// loglevel, err := cmd.Flags().GetString("loglevel")
// if err != nil {
// return fmt.Errorf("No loglevel defined")
// }
flogger := logger.NewLogger(logger.LogLevelDebug)
repo, err := repository.New(dsnURL, flogger)
if err != nil {
return err
}
return repo.EnableSensorsByNames(args...)
}
func listSensors(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
}
cnf, err := config.Read(configFile)
if err != nil {
return err
}
dsnURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
// loglevel, err := cmd.Flags().GetString("loglevel")
// if err != nil {
// return fmt.Errorf("No loglevel defined")
// }
flogger := logger.NewLogger(logger.LogLevelDebug)
repo, err := repository.New(dsnURL, flogger)
if err != nil {
return err
}
// add sensor entry to list
sensors, err := repo.GetSensorsByDeviceID(cnf.DeviceID)
if err != nil {
return err
}
err = cli.PrintSensors(sensors, os.Stdout)
if err != nil {
return err
}
return nil
}
func removeSensor(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
}
cnf, err := config.Read(configFile)
if err != nil {
return err
}
dsnURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
// loglevel, err := cmd.Flags().GetString("loglevel")
// if err != nil {
// return fmt.Errorf("No loglevel defined")
// }
flogger := logger.NewLogger(logger.LogLevelDebug)
repo, err := repository.New(dsnURL, flogger)
if err != nil {
return err
}
return repo.RemoveSensorsByNames(args...)
}
func renameSensor(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
}
cnf, err := config.Read(configFile)
if err != nil {
return err
}
dsnURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
// loglevel, err := cmd.Flags().GetString("loglevel")
// if err != nil {
// return fmt.Errorf("No loglevel defined")
// }
flogger := logger.NewLogger(logger.LogLevelDebug)
repo, err := repository.New(dsnURL, flogger)
if err != nil {
return err
}
return repo.RenameSensors(args[0], args[1])
}

View File

@ -0,0 +1,133 @@
package temperature
import (
"context"
"fmt"
"net/url"
"os"
"git.cryptic.systems/volker.raschek/flucky/pkg/cli"
"git.cryptic.systems/volker.raschek/flucky/pkg/config"
"git.cryptic.systems/volker.raschek/flucky/pkg/repository"
"git.cryptic.systems/volker.raschek/flucky/pkg/sensor"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
"github.com/spf13/cobra"
)
func InitCmd(cmd *cobra.Command) error {
temperatureCmd := &cobra.Command{
Use: "temperature",
Short: "Read and list temperature values",
}
readTemperatureCmd := &cobra.Command{
Use: "read",
Short: "Read temperature values from sensors",
RunE: readTemperature,
}
readTemperatureCmd.Flags().Bool("persist", true, "Persist measured values to the repository")
temperatureCmd.AddCommand(readTemperatureCmd)
cmd.AddCommand(temperatureCmd)
return nil
}
func readTemperature(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
}
persist, err := cmd.Flags().GetBool("persist")
if err != nil {
return fmt.Errorf("Flag persist not defined: %v", err)
}
cnf, err := config.Read(configFile)
if err != nil {
return err
}
dsnURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
logLevelString, err := cmd.Flags().GetString("loglevel")
if err != nil {
return err
}
logLevel, err := logger.ParseLogLevel(logLevelString)
if err != nil {
return err
}
flogger := logger.NewLogger(logLevel)
repo, err := repository.New(dsnURL, flogger)
if err != nil {
return err
}
sensorTypes, err := repo.GetSensors()
if err != nil {
return err
}
sensorTypes, err = types.FilterSensorByMeasuredValueTypes(sensorTypes, types.Temperature)
if err != nil {
return err
}
sensors := make([]sensor.Sensor, 0)
for i := range sensorTypes {
s, err := sensor.New(sensorTypes[i])
if err != nil {
return err
}
sensors = append(sensors, s)
}
measuredValueChannel, errorChannel := sensor.ReadPipeline(context.TODO(), sensors...)
go func() {
for {
select {
case err, open := <-errorChannel:
if !open {
return
}
flogger.Error("%v", err)
}
}
}()
measuredValues := make([]*types.MeasuredValue, 0)
LOOP:
for {
select {
case measuredValue, open := <-measuredValueChannel:
if !open {
break LOOP
}
measuredValues = append(measuredValues, measuredValue)
}
}
err = cli.PrintMeasuredValues(measuredValues, os.Stdout)
if err != nil {
return err
}
if persist {
err = repo.AddMeasuredValues(measuredValues...)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,80 +0,0 @@
package cmd
import (
"fmt"
"math"
"os"
"time"
"github.com/go-flucky/flucky/cmd/compression"
"github.com/go-flucky/flucky/cmd/convert"
"github.com/go-flucky/flucky/cmd/daemon"
"github.com/go-flucky/flucky/cmd/humidity"
"github.com/go-flucky/flucky/cmd/pressure"
"github.com/go-flucky/flucky/cmd/rgbled"
"github.com/go-flucky/flucky/cmd/sensor"
"github.com/go-flucky/flucky/cmd/temperature"
"github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/config"
uuid "github.com/satori/go.uuid"
"github.com/spf13/cobra"
)
var configFile string
var rootCmd = &cobra.Command{
Use: "flucky",
Short: "flucky - operate with differen sensors, his values and remote servers to synchronize measured values",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// check if config file exists
if _, err := os.Stat(configFile); os.IsNotExist(err) {
hostname, err := os.Hostname()
if err != nil {
return fmt.Errorf("Can not locate the hostname: %v", err)
}
// Time must be truncted for postgres
// Postgres currently does not support nanoseconds which is automatically
// include into the go time object
t := time.Now()
l, _ := time.LoadLocation("Europe/Berlin")
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), int(math.Round(float64(t.Nanosecond())/1000000)*1000000), l)
cnf := config.Configuration{
Device: &types.Device{
DeviceID: uuid.NewV4().String(),
DeviceName: hostname,
Logfile: "/var/log/flucky/logfile.csv",
CreationDate: t,
},
}
err = config.Write(&cnf, configFile)
if err != nil {
return err
}
}
return nil
},
}
// Execute a
func Execute(version string) {
rootCmd.Version = version
rootCmd.PersistentFlags().StringVar(&configFile, "config", "/etc/flucky/config.json", "Config file")
compression.InitCmd(rootCmd, &configFile)
convert.InitCmd(rootCmd, &configFile)
daemon.InitCmd(rootCmd, &configFile)
// db.InitCmd(rootCmd, &configFile)
humidity.InitCmd(rootCmd, &configFile)
pressure.InitCmd(rootCmd, &configFile)
rgbled.InitCmd(rootCmd, &configFile)
sensor.InitCmd(rootCmd, &configFile)
temperature.InitCmd(rootCmd, &configFile)
rootCmd.Execute()
}

View File

@ -1,40 +0,0 @@
package compression
import (
"log"
"github.com/go-flucky/flucky/pkg/logfile"
"github.com/spf13/cobra"
)
var compression bool
var configFile *string
var compressionCmd = &cobra.Command{
Use: "compression",
Short: "Compress a logfile",
Args: cobra.ExactArgs(1),
Example: "flucky compression /var/log/flucky/logfile.csv",
Run: func(cmd *cobra.Command, args []string) {
measuredValueLogfile := logfile.New(args[0])
measuredValues, err := measuredValueLogfile.Read()
if err != nil {
log.Fatalln(err)
}
measuredValues = logfile.Compression(measuredValues)
err = measuredValueLogfile.Write(measuredValues)
if err != nil {
log.Fatalln(err)
}
},
}
func InitCmd(cmd *cobra.Command, cnfFile *string) {
configFile = cnfFile
cmd.AddCommand(compressionCmd)
}

View File

@ -1,45 +0,0 @@
package convert
import (
"log"
"github.com/go-flucky/flucky/pkg/logfile"
"github.com/spf13/cobra"
)
var compression bool
var configFile *string
var convertCmd = &cobra.Command{
Use: "convert",
Short: "Convert logfiles into other markup language",
Args: cobra.ExactArgs(2),
Example: "flucky convert /var/log/flucky/logfile.json /var/log/flucky/logfile.csv",
Run: func(cmd *cobra.Command, args []string) {
measuredValuesInput := logfile.New(args[0])
measuredValues, err := measuredValuesInput.Read()
if err != nil {
log.Fatalln(err)
}
if compression {
measuredValues = logfile.Compression(measuredValues)
}
measuredValuesOutput := logfile.New(args[1])
err = measuredValuesOutput.Write(measuredValues)
if err != nil {
log.Fatalln(err)
}
},
}
// Execute a
func InitCmd(cmd *cobra.Command, cnfFile *string) {
configFile = cnfFile
cmd.AddCommand(convertCmd)
convertCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured values")
}

View File

@ -1,47 +0,0 @@
package daemon
import (
"log"
"time"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/daemon"
"github.com/go-flucky/flucky/pkg/logger"
"github.com/spf13/cobra"
)
var cleanCacheInterval string
var compression bool
var configFile *string
var round float64
var temperatureUnit string
var daemonCmd = &cobra.Command{
Use: "daemon",
Short: "Read continuously data from all enabled sensors",
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
duration, err := time.ParseDuration(cleanCacheInterval)
if err != nil {
log.Fatalf("Can not parse clean cache interval into duration time: %v", err)
}
logger := logger.NewDefaultLogger(logger.LogLevelDebug)
daemon.Start(cnf, duration, compression, round, logger)
},
}
func InitCmd(cmd *cobra.Command, cnfFile *string) {
configFile = cnfFile
cmd.AddCommand(daemonCmd)
daemonCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured values")
daemonCmd.Flags().StringVar(&cleanCacheInterval, "clean-cache-interval", "5m", "Minute intervall to clean cache and write measured values into logfile")
daemonCmd.Flags().Float64Var(&round, "round", 0.5, "Round values. The value 0 deactivates the function")
}

View File

@ -1,49 +0,0 @@
package db
import (
"context"
"log"
database "github.com/go-flucky/flucky/pkg/db"
"github.com/go-flucky/flucky/pkg/types"
"github.com/spf13/cobra"
)
var configFile *string
var dbCmd = &cobra.Command{
Use: "db",
Short: "Operates with the configured database",
Run: func(cmd *cobra.Command, args []string) {
postgresDB, err := database.New(database.DBOTypePostgres, "localhost", "5432", "postgres", "postgres", "postgres")
if err != nil {
log.Fatalf("%v", err)
}
ctx := context.Background()
devices := []*types.Device{
&types.Device{
DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c",
DeviceName: "raspberr-pi",
},
&types.Device{
DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c",
DeviceName: "raspberr-pi",
},
}
if err := postgresDB.InsertDevices(ctx, devices); err != nil {
log.Fatalln(err)
}
},
}
// Execute a
func InitCmd(cmd *cobra.Command, cnfFile *string) {
configFile = cnfFile
cmd.AddCommand(dbCmd)
}

View File

@ -1,22 +0,0 @@
package humidity
import (
"github.com/spf13/cobra"
)
var compression bool
var configFile *string
var round float64
var humidityCmd = &cobra.Command{
Use: "humidity",
Short: "Operates with humidity values",
}
// Execute a
func InitCmd(cmd *cobra.Command, cnfFile *string) {
configFile = cnfFile
cmd.AddCommand(humidityCmd)
}

View File

@ -1,54 +0,0 @@
package humidity
import (
"fmt"
"log"
"os"
"github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/logfile"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/spf13/cobra"
)
var listTemperatureCmd = &cobra.Command{
Use: "list",
Short: "List humidity values from different or specified sensors by arguments",
Example: fmt.Sprintf("flucky humidity logs"),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
logfile := logfile.New(cnf.Device.Logfile)
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Logfile(rgbLEDs); err != nil {
log.Fatalln(err)
}
measuredValues, err := logfile.Read()
if err != nil {
log.Fatalln(err)
}
if err := rgbled.Off(rgbLEDs); err != nil {
log.Fatalln(err)
}
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypeHumidity, measuredValues)
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
},
}
func init() {
humidityCmd.AddCommand(listTemperatureCmd)
}

View File

@ -1,75 +0,0 @@
package humidity
import (
"context"
"log"
"os"
"github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/logfile"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/go-flucky/flucky/pkg/sensor"
"github.com/spf13/cobra"
)
var logs bool
var readHumidityCmd = &cobra.Command{
Use: "read",
Short: "Reading air pressure values from different or specified sensors by arguments",
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// fetch all temperature sensors or sensors by args
sensors := make([]sensor.Sensor, 0)
if len(args) == 0 {
sensors = cnf.GetHumiditySensors(config.ENABLED)
} else {
sensors = cnf.GetHumiditySensorsByName(args)
}
if len(sensors) == 0 {
return
}
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Run(rgbLEDs); err != nil {
log.Fatalln(err)
}
ctx := context.Background()
measuredValues, err := sensor.Read(ctx, sensors)
if err != nil {
log.Fatalln(err)
}
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypeHumidity, measuredValues)
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
if logs {
measuredValuesLogfile := logfile.New(cnf.Device.Logfile)
err := logfile.Append(measuredValuesLogfile, compression, round, measuredValues)
if err != nil {
log.Fatalln(err)
}
}
rgbled.Off(rgbLEDs)
},
}
func init() {
humidityCmd.AddCommand(readHumidityCmd)
readHumidityCmd.Flags().BoolVar(&logs, "logs", true, "Log temperature")
readHumidityCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured with logged temperatures")
readHumidityCmd.Flags().Float64VarP(&round, "round", "r", 0.25, "Round values. The value 0 deactivates the function")
}

View File

@ -1,54 +0,0 @@
package pressure
import (
"fmt"
"log"
"os"
"github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/logfile"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/spf13/cobra"
)
var listTemperatureCmd = &cobra.Command{
Use: "list",
Short: "Reading temperature values from different or specified sensors by arguments",
Example: fmt.Sprintf("flucky pressure logs"),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
logfile := logfile.New(cnf.Device.Logfile)
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Logfile(rgbLEDs); err != nil {
log.Fatalln(err)
}
measuredValues, err := logfile.Read()
if err != nil {
log.Fatalln(err)
}
if err := rgbled.Off(rgbLEDs); err != nil {
log.Fatalln(err)
}
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypePressure, measuredValues)
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
},
}
func init() {
pressureCmd.AddCommand(listTemperatureCmd)
}

View File

@ -1,22 +0,0 @@
package pressure
import (
"github.com/spf13/cobra"
)
var compression bool
var configFile *string
var round float64
var pressureCmd = &cobra.Command{
Use: "pressure",
Short: "List air pressure values from different or specified sensors by arguments",
}
// Execute a
func InitCmd(cmd *cobra.Command, cnfFile *string) {
configFile = cnfFile
cmd.AddCommand(pressureCmd)
}

View File

@ -1,75 +0,0 @@
package pressure
import (
"context"
"log"
"os"
"github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/logfile"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/go-flucky/flucky/pkg/sensor"
"github.com/spf13/cobra"
)
var logs bool
var readPressureCmd = &cobra.Command{
Use: "read",
Short: "Operates with air pressure values",
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// fetch all temperature sensors or sensors by args
sensors := make([]sensor.Sensor, 0)
if len(args) == 0 {
sensors = cnf.GetPressureSensors(config.ENABLED)
} else {
sensors = cnf.GetPressureSensorsByName(args)
}
if len(sensors) == 0 {
return
}
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Run(rgbLEDs); err != nil {
log.Fatalln(err)
}
ctx := context.Background()
measuredValues, err := sensor.Read(ctx, sensors)
if err != nil {
log.Fatalln(err)
}
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypePressure, measuredValues)
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
if logs {
measuredValuesLogfile := logfile.New(cnf.Device.Logfile)
err := logfile.Append(measuredValuesLogfile, compression, round, measuredValues)
if err != nil {
log.Fatalln(err)
}
}
rgbled.Off(rgbLEDs)
},
}
func init() {
pressureCmd.AddCommand(readPressureCmd)
readPressureCmd.Flags().BoolVar(&logs, "logs", true, "Log temperature")
readPressureCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured with logged temperatures")
readPressureCmd.Flags().Float64VarP(&round, "round", "r", 0.25, "Round values. The value 0 deactivates the function")
}

View File

@ -1,77 +0,0 @@
package rgbled
import (
"fmt"
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/types"
"github.com/spf13/cobra"
)
var enabled bool
var location string
var addRgbLedCmd = &cobra.Command{
Use: "add",
Short: "Add a RGB-LED",
Aliases: []string{"append"},
Args: cobra.ExactArgs(4),
Example: fmt.Sprintf(`flucky rgb-led add <name> <gpio-for-blue> <gpio-for-green> <gpio-for-red>
flucky rgb-led add my-led GPIO13 GPIO17 GPIO26`),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// determine gpio port
gpioBlue, err := types.StringToGPIO(args[1])
if err != nil {
log.Fatalln(err)
}
gpioGreen, err := types.StringToGPIO(args[2])
if err != nil {
log.Fatalln(err)
}
gpioRed, err := types.StringToGPIO(args[3])
if err != nil {
log.Fatalln(err)
}
// create new sensor struct
rgbLED := &types.RGBLED{
RGBLEDName: args[0],
RGBLEDLocation: location,
RGBLEDEnabled: enabled,
ActionMapping: types.DefaultActionMapping,
BaseColorsToGPIO: map[types.BaseColor]*types.GPIO{
types.BaseColorBlue: &gpioBlue,
types.BaseColorGreen: &gpioGreen,
types.BaseColorRed: &gpioRed,
},
}
// // add sensor entry to list
err = cnf.AddRGBLED(rgbLED)
if err != nil {
log.Fatalln(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
rgbLedCmd.AddCommand(addRgbLedCmd)
addRgbLedCmd.Flags().BoolVarP(&enabled, "enabled", "e", true, "Enable Sensor")
addRgbLedCmd.Flags().StringVarP(&location, "location", "l", "", "Sensor location")
}

View File

@ -1,42 +0,0 @@
package rgbled
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var disableRgbLedCmd = &cobra.Command{
Use: "disable",
Short: "Disable a RGB-LED",
Args: cobra.ExactArgs(1),
Example: `flucky rgb-led disable <name/uuid>
flucky rgb-led disable my-led
flucky rgb-led disable 9f8abfc5-91f3-480c-a42d-b990b6f89e5d`,
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// disable sensor entry to list
err = cnf.DisableRGBLED(args[0])
if err != nil {
log.Fatalln(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
rgbLedCmd.AddCommand(disableRgbLedCmd)
}

View File

@ -1,41 +0,0 @@
package rgbled
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var enableRgbLedCmd = &cobra.Command{
Use: "enable",
Short: "Enable a RGB-LED",
Example: `flucky rgb-led enable <name/uuid>
flucky rgb-led enable my-led
flucky rgb-led enable 9f8abfc5-91f3-480c-a42d-b990b6f89e5d`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// disable sensor entry to list
err = cnf.EnableRGBLED(args[0])
if err != nil {
log.Fatalln(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
rgbLedCmd.AddCommand(enableRgbLedCmd)
}

View File

@ -1,30 +0,0 @@
package rgbled
import (
"log"
"os"
"github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var listRgbLedCmd = &cobra.Command{
Use: "list",
Short: "List RGB-LEDs",
Aliases: []string{"ls"},
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// print sensors on stdout
cli.PrintRGBLEDs(cnf, os.Stdout)
},
}
func init() {
rgbLedCmd.AddCommand(listRgbLedCmd)
}

View File

@ -1,41 +0,0 @@
package rgbled
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/spf13/cobra"
)
var offRgbLedCmd = &cobra.Command{
Use: "off",
Short: "Turn a RGB-LED color off",
Example: `flucky rgb-led off <name/uuid> <blue>
flucky rgb-led off my-led`,
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
rgbLEDs := make([]rgbled.RGBLED, 0)
if len(args) != 0 {
rgbLEDs = cnf.GetRGBLEDsByName(args)
} else {
rgbLEDs = cnf.GetRGBLEDs(config.ENABLED)
}
err = rgbled.Off(rgbLEDs)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
rgbLedCmd.AddCommand(offRgbLedCmd)
}

View File

@ -1,50 +0,0 @@
package rgbled
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/go-flucky/flucky/pkg/types"
"github.com/spf13/cobra"
)
var onRgbLedCmd = &cobra.Command{
Use: "on",
Short: "Turn a RGB-LED color on",
Example: `flucky rgb-led on <names/uuids> <blue/green/purple/red/turquoise/white/yellow>
flucky rgb-led on my-led blue
flucky rgb-led on my-led my-sweet-led white
flucky rgb-led on 1c5b9424-f6e9-4a37-be5c-77e531e94aab red`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
rgbLEDs := make([]rgbled.RGBLED, 0)
if len(args) > 1 {
rgbLEDs = cnf.GetRGBLEDsByName(args[0 : len(args)-1])
} else {
rgbLEDs = cnf.GetRGBLEDs(config.ENABLED)
}
color, err := types.StringToLEDColor(args[len(args)-1])
if err != nil {
log.Fatalln(err)
}
err = rgbled.CustomColor(rgbLEDs, color)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
rgbLedCmd.AddCommand(onRgbLedCmd)
}

View File

@ -1,41 +0,0 @@
package rgbled
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var removeRgbLedCmd = &cobra.Command{
Use: "remove",
Short: "Remove a RGB-LED",
Example: `flucky rgb-led remove <name/uuid>
flucky rgb-led remove my-led
flucky rgb-led remove 9f8abfc5-91f3-480c-a42d-b990b6f89e5d`,
Aliases: []string{"rm"},
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// // add remote entry to list
err = cnf.RemoveRGBLED(args[0])
if err != nil {
log.Fatalln(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
rgbLedCmd.AddCommand(removeRgbLedCmd)
}

View File

@ -1,40 +0,0 @@
package rgbled
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var renameRgbLedCmd = &cobra.Command{
Use: "rename",
Short: "Rename a RGB-LED",
Args: cobra.ExactArgs(2),
Example: `flucky rgb-led disable <name/uuid> <new-name>
flucky rgb-led disable my-led my-sweet-led
flucky rgb-led disable 9f8abfc5-91f3-480c-a42d-b990b6f89e5d my-sweet-led`,
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// rename sensor
err = cnf.RenameRGBLED(args[0], args[1])
if err != nil {
log.Println(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
rgbLedCmd.AddCommand(renameRgbLedCmd)
}

View File

@ -1,20 +0,0 @@
package rgbled
import (
"github.com/spf13/cobra"
)
var configFile *string
var rgbLedCmd = &cobra.Command{
Use: "rgb-led",
Short: "Manage RGB-LEDs",
}
// InitCmd da
func InitCmd(cmd *cobra.Command, cnfFile *string) {
configFile = cnfFile
cmd.AddCommand(rgbLedCmd)
}

View File

@ -1,99 +0,0 @@
package sensor
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/types"
"github.com/spf13/cobra"
)
var enabled bool
var gpioIn string
var i2cAddress uint8
var i2cBus int
var location string
var wireID string
var addSensorCmd = &cobra.Command{
Use: "add",
Short: "Add Sensor",
Aliases: []string{"append"},
Args: cobra.ExactArgs(2),
Example: `flucky sensor add --gpio GPIO14 indoor DHT11
flucky sensor add --wire-id 28-011432f0bb3d outdoor DS18B20
flucky sensor add --i2c-bus 1 --i2c-address 0x76 wetter-station BME280`,
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// determine sensor model
sensorModel, err := types.SelectSensorModel(args[1])
if err != nil {
log.Fatalln(err)
}
// create new sensor struct
sensor := &types.Sensor{
SensorName: args[0],
SensorModel: sensorModel,
SensorLocation: location,
SensorEnabled: enabled,
}
// determine gpio port if set
if gpioIn != "" &&
i2cAddress == 0 &&
i2cBus == 0 &&
wireID == "" {
gpio, err := types.StringToGPIO(gpioIn)
if err != nil {
log.Fatalln(err)
}
sensor.GPIONumber = &gpio
}
// set i2c connection settings
if gpioIn == "" &&
i2cAddress != 0 &&
i2cBus != 0 &&
wireID == "" {
sensor.I2CAddress = &i2cAddress
sensor.I2CBus = &i2cBus
}
// set wire connection settings
if gpioIn == "" &&
i2cAddress == 0 &&
i2cBus == 0 &&
wireID != "" {
sensor.WireID = &wireID
}
// add sensor entry to list
err = cnf.AddSensor(sensor)
if err != nil {
log.Fatalln(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
sensorCmd.AddCommand(addSensorCmd)
addSensorCmd.Flags().BoolVar(&enabled, "enabled", true, "Enable Sensor")
addSensorCmd.Flags().StringVar(&gpioIn, "gpio", "", "GPIO")
addSensorCmd.Flags().Uint8Var(&i2cAddress, "i2c-address", 0, "I2C-Address")
addSensorCmd.Flags().IntVar(&i2cBus, "i2c-bus", 0, "I2C-Bus")
addSensorCmd.Flags().StringVar(&location, "location", "", "Sensor location")
addSensorCmd.Flags().StringVar(&wireID, "wire-id", "", "Wire-ID")
}

View File

@ -1,40 +0,0 @@
package sensor
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var disableSensorCmd = &cobra.Command{
Use: "disable",
Short: "Disable Sensor",
Args: cobra.ExactArgs(1),
Example: "flucky sensor disable outdoor",
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// disable sensor entry to list
err = cnf.DisableSensor(args[0])
if err != nil {
log.Fatalln(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
sensorCmd.AddCommand(disableSensorCmd)
}

View File

@ -1,39 +0,0 @@
package sensor
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var enableSensorCmd = &cobra.Command{
Use: "enable",
Short: "Enable Sensor",
Example: "flucky sensor enable outdoor",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// disable sensor entry to list
err = cnf.EnableSensor(args[0])
if err != nil {
log.Fatalln(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
sensorCmd.AddCommand(enableSensorCmd)
}

View File

@ -1,39 +0,0 @@
package sensor
import (
"log"
"os"
"github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var listSensorCmd = &cobra.Command{
Use: "list",
Short: "List Sensors",
Aliases: []string{"ls"},
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// print sensors on stdout
err = cli.PrintSensors(cnf, os.Stdout)
if err != nil {
log.Fatalln(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
sensorCmd.AddCommand(listSensorCmd)
}

View File

@ -1,39 +0,0 @@
package sensor
import (
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var rmSensorCmd = &cobra.Command{
Use: "remove",
Short: "Remove Sensor",
Example: "flucky sensor rm outdoor",
Aliases: []string{"rm"},
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// // add remote entry to list
err = cnf.RemoveSensor(args[0])
if err != nil {
log.Fatalln(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
sensorCmd.AddCommand(rmSensorCmd)
}

View File

@ -1,39 +0,0 @@
package sensor
import (
"fmt"
"log"
"github.com/go-flucky/flucky/pkg/config"
"github.com/spf13/cobra"
)
var renameSensorCmd = &cobra.Command{
Use: "rename",
Short: "Rename Sensor",
Args: cobra.ExactArgs(2),
Example: fmt.Sprintf("flucky sensor rename indoor outdoor\nflucky sensor rename f98b00ea-a9b2-4e00-924f-113859d0af2d outdoor"),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// rename sensor
err = cnf.RenameSensor(args[0], args[1])
if err != nil {
log.Println(err)
}
// save new configuration
err = config.Write(cnf, *configFile)
if err != nil {
log.Fatalln(err)
}
},
}
func init() {
sensorCmd.AddCommand(renameSensorCmd)
}

View File

@ -1,20 +0,0 @@
package sensor
import (
"github.com/spf13/cobra"
)
var configFile *string
var sensorCmd = &cobra.Command{
Use: "sensor",
Short: "Manage Sensors",
}
// InitCmd da
func InitCmd(cmd *cobra.Command, cnfFile *string) {
configFile = cnfFile
cmd.AddCommand(sensorCmd)
}

View File

@ -1,54 +0,0 @@
package temperature
import (
"fmt"
"log"
"os"
"github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/logfile"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/spf13/cobra"
)
var listTemperatureCmd = &cobra.Command{
Use: "list",
Short: "List temperature values from different or specified sensors by arguments",
Example: fmt.Sprintf("flucky temperature logs"),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
logfile := logfile.New(cnf.Device.Logfile)
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Logfile(rgbLEDs); err != nil {
log.Fatalln(err)
}
measuredValues, err := logfile.Read()
if err != nil {
log.Fatalln(err)
}
if err := rgbled.Off(rgbLEDs); err != nil {
log.Fatalln(err)
}
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypeTemperature, measuredValues)
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
},
}
func init() {
temperatureCmd.AddCommand(listTemperatureCmd)
}

View File

@ -1,79 +0,0 @@
package temperature
import (
"context"
"fmt"
"log"
"os"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/logfile"
"github.com/go-flucky/flucky/pkg/sensor"
"github.com/spf13/cobra"
)
var logs bool
var readTemperatureCmd = &cobra.Command{
Use: "read",
Short: "Reading temperature values from different or specified sensors by arguments",
Example: fmt.Sprintf("flucky temperature read\nflucky temperature read outdoor"),
Run: func(cmd *cobra.Command, args []string) {
// read configuration
cnf, err := config.Read(*configFile)
if err != nil {
log.Fatalln(err)
}
// fetch all temperature sensors or sensors by args
sensors := make([]sensor.Sensor, 0)
if len(args) == 0 {
sensors = cnf.GetTemperatureSensors(config.ENABLED)
} else {
sensors = cnf.GetTemperatureSensorsByName(args)
}
if len(sensors) == 0 {
return
}
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Run(rgbLEDs); err != nil {
log.Fatalln(err)
}
ctx := context.Background()
measuredValues, err := sensor.Read(ctx, sensors)
if err != nil {
rgbled.Error(rgbLEDs)
log.Fatalln(err)
}
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypeTemperature, measuredValues)
// print temperatures on stdout
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
if logs {
measuredValuesLogfile := logfile.New(cnf.Device.Logfile)
err := logfile.Append(measuredValuesLogfile, compression, round, measuredValues)
if err != nil {
rgbled.Error(rgbLEDs)
log.Fatalln(err)
}
}
rgbled.Off(rgbLEDs)
},
}
func init() {
temperatureCmd.AddCommand(readTemperatureCmd)
readTemperatureCmd.Flags().BoolVar(&logs, "logs", true, "Log temperature")
readTemperatureCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured with logged temperatures")
readTemperatureCmd.Flags().Float64VarP(&round, "round", "r", 0.25, "Round values. The value 0 deactivates the function")
}

View File

@ -1,24 +0,0 @@
package temperature
import (
"fmt"
"github.com/spf13/cobra"
)
var compression bool
var configFile *string
var round float64
var temperatureCmd = &cobra.Command{
Use: "temperature",
Short: "Operates with temperature values",
Example: fmt.Sprintf("flucky temperature read\nflucky temperature read outdoor"),
}
// Execute a
func InitCmd(cmd *cobra.Command, cnfFile *string) {
configFile = cnfFile
cmd.AddCommand(temperatureCmd)
}

View File

@ -3,14 +3,12 @@ services:
flucky-db:
container_name: postgres
environment:
- PGTZ=${TZ}
- POSTGRES_PASSWORD=${PG_PASSWORD}
- POSTGRES_USER=${PG_USER}
- POSTGRES_DB=${PG_NAME}
- TZ=${TZ}
image: postgres:11.5-alpine
- PGTZ=Europe/Berlin
- POSTGRES_PASSWORD=postgres
- TZ=Europe/Berlin
image: postgres:13-alpine
ports:
- ${PG_EXTERN_PORT}:${PG_INTERN_PORT}/tcp
- 5432:5432
restart: always
volumes:
- /etc/localtime:/etc/localtime:ro

28
go.mod
View File

@ -1,20 +1,22 @@
module github.com/go-flucky/flucky
module git.cryptic.systems/volker.raschek/flucky
go 1.12
go 1.14
require (
git.cryptic.systems/volker.raschek/dockerutils v0.2.0
git.cryptic.systems/volker.raschek/go-dht v0.1.2
git.cryptic.systems/volker.raschek/go-logger v0.1.0
github.com/Masterminds/semver v1.5.0
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375
github.com/d2r2/go-i2c v0.0.0-20181113114621-14f8dd4e89ce
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc
github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e
github.com/go-flucky/go-dht v0.1.1
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/lib/pq v1.2.0
github.com/golang-migrate/migrate/v4 v4.14.1
github.com/lib/pq v1.9.0
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/satori/go.uuid v1.2.0
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3 // indirect
github.com/stianeikeland/go-rpio v4.2.0+incompatible
github.com/stretchr/testify v1.3.0
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
periph.io/x/periph v3.4.0+incompatible
github.com/spf13/cobra v1.1.1
github.com/stretchr/testify v1.7.0
github.com/ugorji/go v1.1.4 // indirect
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect
)

804
go.sum
View File

@ -1,36 +1,816 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg=
cloud.google.com/go v0.64.0/go.mod h1:xfORb36jGvE+6EexW71nMEtL025s3x6xvuYUKM4JLv4=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/spanner v1.9.0/go.mod h1:xvlEn0NZ5v1iJPYsBnUVRDNvccDxsBTEi16pJRKQVws=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.cryptic.systems/volker.raschek/dockerutils v0.1.0 h1:NjE5BVS0k9XoRj8qZOHNvpjb1+C8Zr4sK/g4Yjw5Of4=
git.cryptic.systems/volker.raschek/dockerutils v0.1.0/go.mod h1:PikYsmSANKHEKJSD2pIY9gPqMpiQh1ufCrU/s/v2I5Q=
git.cryptic.systems/volker.raschek/dockerutils v0.2.0 h1:HJhAfHoyKHOt76T5PxeLvpoA0FCiGu50u4GRwGLGVJs=
git.cryptic.systems/volker.raschek/dockerutils v0.2.0/go.mod h1:c4ZZpD2unnzwr7qHDpVdCmxLGOSnJagli7xNGgTBhk0=
git.cryptic.systems/volker.raschek/go-dht v0.1.2 h1:kGmfpaVUETQhSELCIrKXMjKwuUhQkRUz/7VbLYiTRJA=
git.cryptic.systems/volker.raschek/go-dht v0.1.2/go.mod h1:FUMwxa4cD+ATHPztXJntlO22I0DBTUPtXxfRF0JxXy8=
git.cryptic.systems/volker.raschek/go-logger v0.1.0 h1:JHBDesKBZaXjc2AlqYms1T3dGIX0oNIOBWl4cnVFWIo=
git.cryptic.systems/volker.raschek/go-logger v0.1.0/go.mod h1:GqeuxFj64SAolfj5kpbWup6E1vv37SaH5S+4wa40Tqs=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY=
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.3 h1:ijQT13JedHSHrQGWFcGEwzcNKrAGIiZ+jSD5QQG07SY=
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375 h1:vdUOwcZdV+bBfGUUh5oPPWSzw9p+lBnNSuGgQwGpCH4=
github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375/go.mod h1:3iz1WHlYJU9b4NJei+Q8G7DN3K05arcCMlOQ+qNCDjo=
github.com/d2r2/go-i2c v0.0.0-20181113114621-14f8dd4e89ce h1:Dog7PLNz1fPaXqHPOHonpERqsF57Oh4X76pM80T1GDY=
github.com/d2r2/go-i2c v0.0.0-20181113114621-14f8dd4e89ce/go.mod h1:AwxDPnsgIpy47jbGXZHA9Rv7pDkOJvQbezPuK1Y+nNk=
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc h1:HLRSIWzUGMLCq4ldt0W1GLs3nnAxa5EGoP+9qHgh6j0=
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc/go.mod h1:AwxDPnsgIpy47jbGXZHA9Rv7pDkOJvQbezPuK1Y+nNk=
github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e h1:ZG3JBA6rPRl0xxQ+nNSfO7tor8w+CNCTs05DNJQYbLM=
github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e/go.mod h1:oA+9PUt8F1aKJ6o4YU1T120i7sgo1T6/1LWEEBy0BSs=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-flucky/go-dht v0.1.1 h1:dPz9F5D7oUaTd0GUGTvT4lBH2R043h1bkmhTlpQax1Y=
github.com/go-flucky/go-dht v0.1.1/go.mod h1:Yk/cct+/u+eCS7pB/kc0tc7MrVXdFI4W15MJCj5FRUc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8=
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.2+incompatible h1:vFgEHPqWBTp4pTjdLwjAA4bSo3gvIGOYwuJTlEjVBCw=
github.com/docker/docker v20.10.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-migrate/migrate v1.3.2 h1:QAlFV1QF9zdkzy/jujlBVkVu+L/+k18cg8tuY1/4JDY=
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE=
github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
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.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
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.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/snowflakedb/glog v0.0.0-20180824191149-f5055e6f21ce/go.mod h1:EB/w24pR5VKI60ecFnKqXzxX3dOorz1rnVicQTQrGM0=
github.com/snowflakedb/gosnowflake v1.3.5/go.mod h1:13Ky+lxzIm3VqNDZJdyvu9MCGy+WgRdYFdXp96UcLZU=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stianeikeland/go-rpio v4.2.0+incompatible h1:CUOlIxdJdT+H1obJPsmg8byu7jMSECLfAN9zynm5QGo=
github.com/stianeikeland/go-rpio v4.2.0+incompatible/go.mod h1:Sh81rdJwD96E2wja2Gd7rrKM+XZ9LrwvN2w4IXrqLR8=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/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-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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3 h1:sg8vLDNIxFPHTchfhH1E3AI32BL3f23oie38xUWnJM8=
google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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=
periph.io/x/periph v3.4.0+incompatible h1:5gzxE4ryPq52cdqSw0mErR6pyJK8cBF2qdUAcOWh0bo=
periph.io/x/periph v3.4.0+incompatible/go.mod h1:EWr+FCIU2dBWz5/wSWeiIUJTriYv9v2j2ENBmgYyy7Y=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
periph.io/x/periph v3.6.4+incompatible h1:8FyXTbu9lcMVofz8mf+cj1pzTLN4V6EuPY2EF+DoJF4=
periph.io/x/periph v3.6.4+incompatible/go.mod h1:EWr+FCIU2dBWz5/wSWeiIUJTriYv9v2j2ENBmgYyy7Y=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

25
main.go
View File

@ -3,25 +3,26 @@ package main
import (
"log"
"os"
"os/user"
"github.com/go-flucky/flucky/cmd"
"git.cryptic.systems/volker.raschek/flucky/cli"
"github.com/Masterminds/semver"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
)
var version string
var (
version string
)
func main() {
user, err := user.Current()
sversion, err := semver.NewVersion(version)
if err != nil {
panic(err)
}
if user.Uid != "0" {
log.Println("you need to be root to run this command")
log.Printf("The sematic versioning is invalid: %v", version)
os.Exit(1)
}
cmd.Execute(version)
cli.Execute(sversion)
}

View File

@ -5,74 +5,38 @@ import (
"io"
"text/tabwriter"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
)
// GetSensorsByMeasuredValues returns commulated list of sensors by measured values
func GetSensorsByMeasuredValues(measuredValues []*types.MeasuredValue, cnf *config.Configuration) []*types.Sensor {
sensors := []*types.Sensor{}
for _, measuredValue := range measuredValues {
duplicated := false
foundSensor := &types.Sensor{}
for _, cnfSensor := range cnf.Sensors {
if measuredValue.SensorID == cnfSensor.SensorID {
foundSensor = cnfSensor
// compare if id has already been added to list
for _, sensor := range sensors {
if cnfSensor.SensorID == sensor.SensorID {
duplicated = true
break
}
}
}
}
if duplicated {
continue
}
if foundSensor != nil {
sensors = append(sensors, foundSensor)
continue
} else {
sensors = append(sensors, &types.Sensor{SensorID: measuredValue.SensorID})
}
}
return sensors
}
// PrintRGBLEDs displays a list with all configured RGBLEDs
func PrintRGBLEDs(cnf *config.Configuration, w io.Writer) {
// declare tabwriter
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
// headline
fmt.Fprintln(tw, "name\tlocation\tblue\tgreen\tred\tenabled\taction")
for _, rgbled := range cnf.RGBLEDs {
fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%v\t", rgbled.RGBLEDName, rgbled.RGBLEDLocation, *rgbled.BaseColorsToGPIO[types.BaseColorBlue], *rgbled.BaseColorsToGPIO[types.BaseColorGreen], *rgbled.BaseColorsToGPIO[types.BaseColorRed], rgbled.RGBLEDEnabled)
for action, color := range rgbled.ActionMapping {
fmt.Fprintf(tw, "%v=%v,", action, color)
}
fmt.Fprintf(tw, "\n")
}
tw.Flush()
}
// PrintSensors displays a list with all configured sensors
func PrintSensors(cnf *config.Configuration, w io.Writer) error {
func PrintMeasuredValues(measuredValues []*types.MeasuredValue, w io.Writer) error {
// declar tabwriter
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
fmt.Fprint(tw, "name\tlocation\ttype\twire-id\ti2c-bus\ti2c-address\tgpio\tenabled\n")
fmt.Fprint(tw, "timestamp\ttype\tvalue\n")
for _, sensor := range cnf.Sensors {
fmt.Fprintf(tw, "%v\t%v\t%v\t", sensor.SensorName, sensor.SensorLocation, sensor.SensorModel)
for i := range measuredValues {
fmt.Fprintf(tw, "%v\t%v\t%v\n", measuredValues[i].Date.String(), measuredValues[i].ValueType, measuredValues[i].Value)
}
err := tw.Flush()
if err != nil {
return err
}
return nil
}
// PrintSensors displays a list with all configured sensors
func PrintSensors(sensors []*types.Sensor, w io.Writer) error {
// declar tabwriter
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
fmt.Fprint(tw, "name\tlocation\ttype\twire-id\ti2c-bus\ti2c-address\tgpio\ttick-duration\tenabled\n")
for _, sensor := range sensors {
fmt.Fprintf(tw, "%v\t%v\t%v\t", sensor.Name, sensor.Location, sensor.Model)
if sensor.WireID != nil {
fmt.Fprintf(tw, "%v\t", *sensor.WireID)
@ -87,66 +51,20 @@ func PrintSensors(cnf *config.Configuration, w io.Writer) error {
}
if sensor.I2CAddress != nil {
fmt.Fprintf(tw, "%v\t", *sensor.I2CAddress)
fmt.Fprintf(tw, "%#v\t", *sensor.I2CAddress)
} else {
fmt.Fprintf(tw, "\t")
}
if sensor.GPIONumber != nil {
fmt.Fprintf(tw, "%v\t", *sensor.GPIONumber)
} else {
fmt.Fprintf(tw, "\t")
fmt.Fprintf(tw, "%v\t", sensor.GPIONumber)
fmt.Fprintf(tw, "%v\t%v\n", sensor.TickDuration, sensor.Enabled)
}
fmt.Fprintf(tw, "%v\n", sensor.SensorEnabled)
err := tw.Flush()
if err != nil {
return err
}
tw.Flush()
return nil
}
// PrintMeasuredValues displays a list of measured values
func PrintMeasuredValues(measuredValues []*types.MeasuredValue, cnf *config.Configuration, w io.Writer) {
sensors := GetSensorsByMeasuredValues(measuredValues, cnf)
// sort measured values for every sensor
orderedMeasuredValues := make(map[string][]*types.MeasuredValue)
for _, measuredValue := range measuredValues {
orderedMeasuredValues[measuredValue.SensorID] = append(orderedMeasuredValues[measuredValue.SensorID], measuredValue)
}
// declare tabwriter
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
// headlines
for i, sensor := range sensors {
fmt.Fprintf(tw, "%v\t", sensor.Name())
if i == len(sensors)-1 {
fmt.Fprintf(tw, "\n")
}
}
// find sensor with maximum temperature values
maxLength := 0
for _, orderedMeasuredValue := range orderedMeasuredValues {
if len(orderedMeasuredValue) > maxLength {
maxLength = len(orderedMeasuredValue)
}
}
// body
for i := 0; i < maxLength; i++ {
for _, sensor := range sensors {
if len(orderedMeasuredValues[sensor.SensorID]) > i {
fmt.Fprintf(tw, "%3.3f\t", orderedMeasuredValues[sensor.SensorID][i].Value)
} else {
fmt.Fprint(tw, "\t")
}
}
fmt.Fprint(tw, "\n")
}
tw.Flush()
}

View File

@ -1,59 +1,7 @@
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
)
var validUUID = regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
// Read the configuration file
func Read(configFile string) (*Configuration, error) {
fc := &Configuration{}
f, err := os.Open(configFile)
if err != nil {
return nil, fmt.Errorf("Can not open file %v: %v", configFile, err)
}
defer f.Close()
jsonDecoder := json.NewDecoder(f)
if err := jsonDecoder.Decode(&fc); err != nil {
return nil, fmt.Errorf("Can not unmarshal JSON: %v", err)
}
return fc, nil
}
// Write the configuration into a file, specified by the configuration filepath
func Write(cfg *Configuration, configFile string) error {
if _, err := os.Stat(configFile); os.IsNotExist(err) {
configDir := filepath.Dir(configFile)
err := os.MkdirAll(configDir, os.ModeDir)
if err != nil {
return fmt.Errorf("Can not create config directory %v: %v", configDir, err)
}
}
f, err := os.Create(configFile)
if err != nil {
return fmt.Errorf("Can not write config file: %v", err)
}
defer f.Close()
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
err = encoder.Encode(cfg)
if err != nil {
return fmt.Errorf("Error in encoding struct to json: %v", err)
}
return nil
// Config represent the configuration
type Config struct {
DeviceID string `json:"device_id"`
DSN string `json:"dsn"`
}

View File

@ -1,582 +0,0 @@
package config
import (
"fmt"
"time"
"github.com/go-flucky/flucky/pkg/internal/format"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/go-flucky/flucky/pkg/sensor"
"github.com/go-flucky/flucky/pkg/types"
uuid "github.com/satori/go.uuid"
)
var humiditySensorModels = map[types.SensorModel]types.SensorModel{
types.BME280: types.BME280,
types.DHT11: types.DHT11,
types.DHT22: types.DHT22,
}
var pressureSensorModels = map[types.SensorModel]types.SensorModel{
types.BME280: types.BME280,
}
var temperatureSensorModels = map[types.SensorModel]types.SensorModel{
types.BME280: types.BME280,
types.DHT11: types.DHT11,
types.DHT22: types.DHT22,
types.DS18B20: types.DS18B20,
}
// Configuration of flucky
type Configuration struct {
Device *types.Device `json:"device"`
RGBLEDs []*types.RGBLED `json:"rgb_leds"`
Sensors []*types.Sensor `json:"sensors"`
}
// AddRGBLED add a new RGBLED
func (c *Configuration) AddRGBLED(rgbLED *types.RGBLED) error {
// check if RGBLEDID is a valid UUID string
if !validUUID.MatchString(rgbLED.RGBLEDID) {
rgbLED.RGBLEDID = uuid.NewV4().String()
}
// check if sensor name and sensor uuid already exists
for _, l := range c.RGBLEDs {
if l.RGBLEDName == rgbLED.RGBLEDName {
return fmt.Errorf("RGBLED %v already exists", rgbLED.RGBLEDName)
}
if l.RGBLEDID == rgbLED.RGBLEDID {
return fmt.Errorf("RGBLED %v with UUID %v already exists", rgbLED.RGBLEDName, rgbLED.RGBLEDID)
}
}
// check if sensor has a valid device id
if rgbLED.DeviceID != c.Device.DeviceID {
rgbLED.DeviceID = c.Device.DeviceID
}
// overwrite creation date
rgbLED.CreationDate = time.Now()
// check
c.RGBLEDs = append(c.RGBLEDs, rgbLED)
return nil
}
// AddSensor add a new sensor
func (c *Configuration) AddSensor(sensor *types.Sensor) error {
// check if sensorID is a valid UUID string
if !validUUID.MatchString(sensor.SensorID) {
sensor.SensorID = uuid.NewV4().String()
}
// check if sensor name and sensor uuid already exists
for _, s := range c.Sensors {
if s.SensorName == sensor.SensorName {
return fmt.Errorf("Sensor %v already exists", s.SensorName)
}
if s.SensorID == sensor.SensorID {
return fmt.Errorf("Sensor %v with UUID %v already exists", s.SensorName, s.SensorID)
}
if sensor.WireID != nil {
if *s.WireID == *sensor.WireID {
return fmt.Errorf("Sensor with 1wire-id %v already exists as %v", *s.WireID, s.SensorName)
}
}
}
// check if sensor has a valid device id
if sensor.DeviceID != c.Device.DeviceID {
sensor.DeviceID = c.Device.DeviceID
}
// overwrite creation date
sensor.CreationDate = format.FormatedTime()
//TODO: check if wire sensor exists in /dev/bus/w1/devices
// check
c.Sensors = append(c.Sensors, sensor)
return nil
}
// DisableRGBLED enables a rgb led by its name or its unique UUID
func (c *Configuration) DisableRGBLED(name string) error {
found := false
for _, rgbled := range c.RGBLEDs {
// disable sensor matched after name
if !validUUID.MatchString(name) &&
rgbled.RGBLEDName == name {
rgbled.RGBLEDEnabled = false
found = true
break
}
// disable sensor matched by uuid
if validUUID.MatchString(name) &&
rgbled.RGBLEDID == name {
rgbled.RGBLEDEnabled = false
found = true
break
}
}
if !found {
return fmt.Errorf("Can not found RGB-LED %v", name)
}
return nil
}
// DisableSensor disables a sensor by its name or its unique UUID
func (c *Configuration) DisableSensor(name string) error {
found := false
for _, sensor := range c.Sensors {
// disable sensor matched after name
if !validUUID.MatchString(name) &&
sensor.SensorName == name {
sensor.SensorEnabled = false
found = true
break
}
// remove machted uuid
if validUUID.MatchString(name) &&
sensor.SensorID == name {
sensor.SensorEnabled = false
found = true
break
}
}
if !found {
return fmt.Errorf("Can not found sensor %v", name)
}
return nil
}
// EnableRGBLED enables a rgb led by its name or its unique UUID
func (c *Configuration) EnableRGBLED(name string) error {
found := false
for _, rgbled := range c.RGBLEDs {
// disable sensor matched after name
if !validUUID.MatchString(name) &&
rgbled.RGBLEDName == name {
rgbled.RGBLEDEnabled = true
found = true
break
}
// disable sensor matched by uuid
if validUUID.MatchString(name) &&
rgbled.RGBLEDID == name {
rgbled.RGBLEDEnabled = true
found = true
break
}
}
if !found {
return fmt.Errorf("Can not found RGB-LED %v", name)
}
return nil
}
// EnableSensor enables a sensor by its name or its unique UUID
func (c *Configuration) EnableSensor(name string) error {
found := false
for _, sensor := range c.Sensors {
// disable sensor matched after name
if !validUUID.MatchString(name) &&
sensor.SensorName == name {
sensor.SensorEnabled = true
found = true
break
}
// remove machted uuid
if validUUID.MatchString(name) &&
sensor.SensorID == name {
sensor.SensorEnabled = true
found = true
break
}
}
if !found {
return fmt.Errorf("Can not found sensor %v", name)
}
return nil
}
// GetHumiditySensors returns a list of humidity sensors
func (c *Configuration) GetHumiditySensors(option Option) []sensor.Sensor {
sensors := c.getHumiditySensors()
cachedSensors := make([]*types.Sensor, 0)
switch option {
case ENABLED:
for _, sensor := range sensors {
if sensor.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range sensors {
if !sensor.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
default:
return c.convertSensors(cachedSensors)
}
}
// GetHumiditySensorsByName returns a list of humidity sensors by name,
// uuid or wire-id
func (c *Configuration) GetHumiditySensorsByName(names []string) []sensor.Sensor {
configHumiditySensors := make(map[string]*types.Sensor, 0)
for _, name := range names {
for _, s := range c.getHumiditySensors() {
switch name {
case s.SensorID:
configHumiditySensors[s.SensorID] = s
case s.SensorName:
configHumiditySensors[s.SensorID] = s
}
}
}
humiditySensors := make([]*types.Sensor, 0)
for _, cs := range configHumiditySensors {
humiditySensors = append(humiditySensors, cs)
}
return c.convertSensors(humiditySensors)
}
// GetPressureSensors returns a list of pressure sensors
func (c *Configuration) GetPressureSensors(option Option) []sensor.Sensor {
sensors := c.getPressureSensors()
cachedSensors := make([]*types.Sensor, 0)
switch option {
case ENABLED:
for _, sensor := range sensors {
if sensor.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range sensors {
if !sensor.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
default:
return c.convertSensors(cachedSensors)
}
}
// GetPressureSensorsByName returns a list of pressure sensors by name,
// uuid or wire-id
func (c *Configuration) GetPressureSensorsByName(names []string) []sensor.Sensor {
configPressureSensors := make(map[string]*types.Sensor, 0)
for _, name := range names {
for _, s := range c.getPressureSensors() {
switch name {
case s.SensorID:
configPressureSensors[s.SensorID] = s
case s.SensorName:
configPressureSensors[s.SensorID] = s
}
}
}
pressureSensors := make([]*types.Sensor, 0)
for _, cs := range configPressureSensors {
pressureSensors = append(pressureSensors, cs)
}
return c.convertSensors(pressureSensors)
}
func (c *Configuration) GetRGBLEDs(option Option) []rgbled.RGBLED {
rgbLEDs := c.RGBLEDs
switch option {
case ENABLED:
for i, rgbLED := range c.RGBLEDs {
if !rgbLED.RGBLEDEnabled {
rgbLEDs = append(rgbLEDs[:i], rgbLEDs[i+1:]...)
}
}
return c.convertRGBLEDs(rgbLEDs)
case DISABLED:
for i, rgbLED := range c.RGBLEDs {
if rgbLED.RGBLEDEnabled {
rgbLEDs = append(rgbLEDs[:i], rgbLEDs[i+1:]...)
}
}
return c.convertRGBLEDs(rgbLEDs)
default:
return c.convertRGBLEDs(rgbLEDs)
}
}
func (c *Configuration) GetRGBLEDsByName(names []string) []rgbled.RGBLED {
configRGBLEDs := make(map[string]*types.RGBLED, 0)
for _, name := range names {
for _, led := range c.RGBLEDs {
switch name {
case led.RGBLEDID:
configRGBLEDs[led.RGBLEDID] = led
case led.RGBLEDName:
configRGBLEDs[led.RGBLEDID] = led
}
}
}
rgbLEDs := make([]*types.RGBLED, 0)
for _, rgbLED := range configRGBLEDs {
rgbLEDs = append(rgbLEDs, rgbLED)
}
return c.convertRGBLEDs(rgbLEDs)
}
// GetSensors returns a list of humidity sensors
func (c *Configuration) GetSensors(option Option) []sensor.Sensor {
cachedSensors := make([]*types.Sensor, 0)
switch option {
case ENABLED:
for _, sensor := range c.Sensors {
if sensor.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range c.Sensors {
if !sensor.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
default:
return c.convertSensors(cachedSensors)
}
}
// GetTemperatureSensors returns a list of temperature sensors
func (c *Configuration) GetTemperatureSensors(option Option) []sensor.Sensor {
sensors := c.getTemperatureSensors()
cachedSensors := make([]*types.Sensor, 0)
switch option {
case ENABLED:
for _, sensor := range sensors {
if sensor.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range sensors {
if !sensor.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
default:
return c.convertSensors(cachedSensors)
}
}
// GetTemperatureSensorsByName returns a list of temperature sensors by name,
// uuid or wire-id
func (c *Configuration) GetTemperatureSensorsByName(names []string) []sensor.Sensor {
configTemperatureSensors := make(map[string]*types.Sensor, 0)
for _, name := range names {
for _, s := range c.getTemperatureSensors() {
switch name {
case s.SensorID:
configTemperatureSensors[s.SensorID] = s
case s.SensorName:
configTemperatureSensors[s.SensorID] = s
}
}
}
temperatureSensors := make([]*types.Sensor, 0)
for _, cs := range configTemperatureSensors {
temperatureSensors = append(temperatureSensors, cs)
}
return c.convertSensors(temperatureSensors)
}
// RemoveRGBLED deletes a LED by its name or its unique UUID
func (c *Configuration) RemoveRGBLED(name string) error {
for i, rgbLED := range c.RGBLEDs {
// remove machted name
if !validUUID.MatchString(name) &&
rgbLED.RGBLEDName == name {
c.RGBLEDs = append(c.RGBLEDs[:i], c.RGBLEDs[i+1:]...)
return nil
}
// remove machted uuid
if validUUID.MatchString(name) &&
rgbLED.RGBLEDID == name {
c.RGBLEDs = append(c.RGBLEDs[:i], c.RGBLEDs[i+1:]...)
return nil
}
}
return fmt.Errorf("Can not find RGBLED %v", name)
}
// RemoveSensor deletes a sensor by its name or its unique UUID
func (c *Configuration) RemoveSensor(name string) error {
for i, sensor := range c.Sensors {
// remove machted name
if !validUUID.MatchString(name) &&
sensor.SensorName == name {
c.Sensors = append(c.Sensors[:i], c.Sensors[i+1:]...)
return nil
}
// remove machted uuid
if validUUID.MatchString(name) &&
sensor.SensorID == name {
c.Sensors = append(c.Sensors[:i], c.Sensors[i+1:]...)
return nil
}
}
return fmt.Errorf("Can not find sensor %v", name)
}
// RenameRGBLED renames a sensor identified by the name or the UUID
func (c *Configuration) RenameRGBLED(oldName, newName string) error {
for _, rgbled := range c.RGBLEDs {
if rgbled.RGBLEDName == oldName ||
rgbled.RGBLEDID == oldName {
rgbled.RGBLEDName = newName
return nil
}
}
return fmt.Errorf("Could not find rgb-led %v to replace into with %v", oldName, newName)
}
// RenameSensor renames a sensor identified by the name or the UUID
func (c *Configuration) RenameSensor(oldName, newName string) error {
for _, sensor := range c.Sensors {
if sensor.SensorName == oldName ||
sensor.SensorID == oldName {
sensor.SensorName = newName
return nil
}
}
return fmt.Errorf("Could not find remote %v to replace into with %v", oldName, newName)
}
func (c *Configuration) convertSensors(sensors []*types.Sensor) []sensor.Sensor {
cachedSensors := make([]sensor.Sensor, 0)
for _, s := range sensors {
switch s.SensorModel {
case types.BME280:
cachedSensors = append(cachedSensors, &sensor.BME280{
Sensor: s,
})
case types.DHT11:
cachedSensors = append(cachedSensors, &sensor.DHT11{
Sensor: s,
})
case types.DHT22:
cachedSensors = append(cachedSensors, &sensor.DHT22{
Sensor: s,
})
case types.DS18B20:
cachedSensors = append(cachedSensors, &sensor.DS18B20{
Sensor: s,
})
}
}
return cachedSensors
}
func (c *Configuration) convertRGBLEDs(rgbLEDs []*types.RGBLED) []rgbled.RGBLED {
leds := make([]rgbled.RGBLED, 0)
for _, rgbLED := range rgbLEDs {
leds = append(leds, &rgbled.DefaultRGBLED{
RGBLED: rgbLED,
})
}
return leds
}
func (c *Configuration) getHumiditySensors() []*types.Sensor {
humiditySensors := make([]*types.Sensor, 0)
for _, s := range c.Sensors {
if _, ok := humiditySensorModels[s.SensorModel]; ok {
humiditySensors = append(humiditySensors, s)
}
}
return humiditySensors
}
func (c *Configuration) getPressureSensors() []*types.Sensor {
pressureSensors := make([]*types.Sensor, 0)
for _, s := range c.Sensors {
if _, ok := pressureSensorModels[s.SensorModel]; ok {
pressureSensors = append(pressureSensors, s)
}
}
return pressureSensors
}
func (c *Configuration) getTemperatureSensors() []*types.Sensor {
temperatureSensors := make([]*types.Sensor, 0)
for _, s := range c.Sensors {
if _, ok := temperatureSensorModels[s.SensorModel]; ok {
temperatureSensors = append(temperatureSensors, s)
}
}
return temperatureSensors
}

61
pkg/config/io.go Normal file
View File

@ -0,0 +1,61 @@
package config
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
)
// Decode a configuration from a reader
func Decode(r io.Reader) (*Config, error) {
cnf := new(Config)
jsonDecoder := json.NewDecoder(r)
if err := jsonDecoder.Decode(&cnf); err != nil {
return nil, fmt.Errorf("Can not unmarshal JSON: %v", err)
}
return cnf, nil
}
// Encode a configuration to a writer
func Encode(cnf *Config, w io.Writer) error {
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
err := encoder.Encode(cnf)
if err != nil {
return fmt.Errorf("Error encoding config to json: %v", err)
}
return nil
}
// Read the configuration file
func Read(configFile string) (*Config, error) {
f, err := os.Open(configFile)
if err != nil {
return nil, fmt.Errorf("Can not open file %v: %v", configFile, err)
}
defer f.Close()
return Decode(f)
}
// Write the configuration into a file, specified by the configuration filepath
func Write(cnf *Config, configFile string) error {
if _, err := os.Stat(configFile); os.IsNotExist(err) {
configDir := filepath.Dir(configFile)
err := os.MkdirAll(configDir, 0775)
if err != nil {
return fmt.Errorf("Failed to create config directory %v: %v", configDir, err)
}
}
f, err := os.Create(configFile)
if err != nil {
return fmt.Errorf("Failed not create config file %v: %v", configFile, err)
}
defer f.Close()
return Encode(cnf, f)
}

View File

@ -1,14 +0,0 @@
package config
type Option int
const (
// ALL specified enabled and disabled items
ALL Option = iota + 1
// ENABLED items
ENABLED
// DISABLED items
DISABLED
)

View File

@ -2,129 +2,140 @@ package daemon
import (
"context"
"fmt"
"net/url"
"os"
"os/signal"
"syscall"
"time"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/db"
"github.com/go-flucky/flucky/pkg/logfile"
"github.com/go-flucky/flucky/pkg/logger"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/go-flucky/flucky/pkg/sensor"
"github.com/go-flucky/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/flucky/pkg/config"
"git.cryptic.systems/volker.raschek/flucky/pkg/repository"
"git.cryptic.systems/volker.raschek/flucky/pkg/sensor"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
)
var (
postgresHost = "markus-pc.trier.cryptic.systems"
postgresPort = "5432"
postgresDatabase = "postgres"
postgresUser = "postgres"
postgresPassword = "postgres"
)
func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error {
// Start the daemon
func Start(cnf *config.Configuration, cleanCacheInterval time.Duration, compression bool, round float64, logger logger.Logger) {
// load data source name (dsn)
dsnURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
// Info
logger.Info("Use clean-cache-interval: %v", cleanCacheInterval.String())
logger.Info("Use compression: %v", compression)
logger.Info("Round: %v", round)
repo, err := repository.New(dsnURL, flogger)
if err != nil {
return err
}
ticker := time.Tick(cleanCacheInterval)
// Add
repoDevice, err := repo.GetDevice(cnf.DeviceID)
switch {
case err != nil:
return err
case repoDevice == nil:
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)
hostname, err := os.Hostname()
if err != nil {
return err
}
errorChannel := make(chan error, 0)
measuredValuesChannel := make(chan []*types.MeasuredValue, 0)
err = repo.AddDevices(&types.Device{
ID: cnf.DeviceID,
Name: hostname,
})
if err != nil {
return err
}
ctx := context.Background()
childContext, cancel := context.WithCancel(ctx)
repoDevice, err = repo.GetDevice(cnf.DeviceID)
if err != nil {
return err
}
}
measuredValuesLogfile := logfile.New(cnf.Device.Logfile)
repoSensors, err := repo.GetSensorsByDeviceID(repoDevice.ID)
switch {
case err != nil:
return err
case repoSensors == nil, len(repoSensors) <= 0:
return fmt.Errorf("No sensors found")
}
measuredValuesCache := make([]*types.MeasuredValue, 0)
sensors := make([]sensor.Sensor, 0)
for _, repoSensor := range repoSensors {
if !repoSensor.Enabled || repoSensor.DeviceID != repoDevice.ID {
continue
}
go sensor.ReadContinuously(childContext, cnf.GetSensors(config.ENABLED), measuredValuesChannel, errorChannel)
flogger.Debug("Found sensor %v", repoSensor.Name)
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
sensor, err := sensor.New(repoSensor)
if err != nil {
return err
}
sensors = append(sensors, sensor)
}
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, os.Interrupt, os.Kill)
// Collection
parentCtx := context.Background()
ctx, cancel := context.WithCancel(parentCtx)
measuredValueChannel, errorChannel := sensor.ReadTickingPipeline(ctx, sensors...)
go func() {
for {
err := rgbled.Run(rgbLEDs)
if err != nil {
logger.Error("Can not turn on green info light: %v", err)
}
select {
case err, _ := <-errorChannel:
logger.Error("%v", err)
err = rgbled.Error(rgbLEDs)
if err != nil {
logger.Error("Can not turn on red info light: %v", err)
}
time.Sleep(time.Second * 2)
case <-ticker:
err := rgbled.Logfile(rgbLEDs)
if err != nil {
logger.Error("Can not turn on blue info light: %v", err)
}
// err = logfile.Append(measuredValuesLogfile, compression, round, measuredValuesCache)
postgres, err := db.New(db.DBOTypePostgres, postgresHost, postgresPort, postgresDatabase, postgresUser, postgresPassword)
if err != nil {
err = rgbled.Error(rgbLEDs)
if err != nil {
logger.Error("Can not turn on red info light: %v", err)
}
cancel()
logger.Error("Can not open database connection: %v", err)
}
postgresCtx := context.Background()
err = postgres.InsertMeasuredValues(postgresCtx, measuredValuesCache)
if err != nil {
err = rgbled.Error(rgbLEDs)
if err != nil {
logger.Error("Can not turn on red info light: %v", err)
}
cancel()
logger.Error("Can not save caches measured values in database: %v", err)
}
measuredValuesCache = make([]*types.MeasuredValue, 0)
case measuredValues, _ := <-measuredValuesChannel:
measuredValuesCache = append(measuredValuesCache, measuredValues...)
case killSignal := <-interrupt:
logger.Warn("Daemon was interruped by system signal %v\n", killSignal)
cancel()
err := rgbled.Error(rgbLEDs)
if err != nil {
logger.Error("Can not turn on red info light: %v", err)
}
logger.Warn("Save remaining data from the cache")
err = logfile.Append(measuredValuesLogfile, compression, round, measuredValuesCache)
if err != nil {
logger.Fatal("%v", err)
}
case <-ctx.Done():
return
case err, open := <-errorChannel:
if !open {
return
}
if err != nil {
flogger.Error("%v", err)
}
}
}
}()
measuredValues := make([]*types.MeasuredValue, 0, cachedEntries)
for {
select {
case measuredValue := <-measuredValueChannel:
flogger.Debug("%v\t%v\t%v", measuredValue.ID, measuredValue.ValueType, measuredValue.Value)
measuredValues = append(measuredValues, measuredValue)
if cap(measuredValues) == len(measuredValues) {
flogger.Debug("Flush cache with %v values", len(measuredValues))
err := repo.AddMeasuredValues(measuredValues...)
if err != nil {
flogger.Error("%v", err)
}
measuredValues = make([]*types.MeasuredValue, 0, cachedEntries)
}
case signal := <-interruptChannel:
cancel()
flogger.Info("Stopping daemon: Received process signal %v", signal.String())
flogger.Debug("Flush cache with %v remaining values", len(measuredValues))
err := repo.AddMeasuredValues(measuredValues...)
if err != nil {
flogger.Error("%v", err)
}
flogger.Debug("Close repository")
err = repo.Close()
if err != nil {
flogger.Error("%v", err)
}
return nil
}
}
}

View File

@ -1,36 +0,0 @@
package db
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
type DBOType string
func (dboType DBOType) String() string {
return string(dboType)
}
const (
DBOTypePostgres DBOType = "postgres"
DBOTypeOracle = "oracle"
)
func New(dboType DBOType, host string, port string, database string, user string, password string) (Database, error) {
connStr := fmt.Sprintf("%v://%v:%v@%v:%v/%v?sslmode=disable", dboType.String(), user, password, host, port, database)
newDBO, err := sql.Open(dboType.String(), connStr)
if err != nil {
return nil, err
}
switch dboType {
case "postgres":
return &Postgres{
dbo: newDBO,
}, nil
default:
return nil, fmt.Errorf("Unknown Database Type")
}
}

View File

@ -1,17 +0,0 @@
package db
import (
"errors"
)
var (
errorBeginTransaction = errors.New("Can not start new transaction")
errorGetAsset = errors.New("Can not get asset from go-bindata")
errorRowNotFound = errors.New("Can not find row by given ID")
errorPrepareStatement = errors.New("Can not prepare sql statement")
errorRollbackTransaction = errors.New("Can not rollback transaction")
errorScanRow = errors.New("Can not scan row")
errorStatementExecute = errors.New("Can not execute statement")
errorStatementQuery = errors.New("Can not query statement")
errorUnknownMeasuredValueType = errors.New("Unknown measured value type")
)

View File

@ -1,39 +0,0 @@
package db
import (
"context"
"github.com/go-flucky/flucky/pkg/types"
)
type Database interface {
// Close DB Connction
Close() error
// Delete
DeleteDevices(ctx context.Context, devices []*types.Device) error
DeleteMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
DeleteSensors(ctx context.Context, sensors []*types.Sensor) error
// Insert
InsertDevices(ctx context.Context, devices []*types.Device) error
InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
InsertSensors(ctx context.Context, sensors []*types.Sensor) error
// Select
SelectDeviceByID(ctx context.Context, id string) (*types.Device, error)
SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error)
SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectMeasuredValuesByIDAndType(ctx context.Context, id string, valueType types.MeasuredValueType) (*types.MeasuredValue, error)
SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error)
SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectSensorByID(ctx context.Context, id string) (*types.Sensor, error)
SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error)
SelectTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
// Update
UpdateDevices(ctx context.Context, devices []*types.Device) error
UpdateMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
UpdateSensors(ctx context.Context, sensots []*types.Sensor) error
}

View File

@ -1,481 +0,0 @@
package db
import (
"context"
"database/sql"
"fmt"
"github.com/go-flucky/flucky/pkg/types"
_ "github.com/lib/pq"
)
type Postgres struct {
dbo *sql.DB
}
func (p *Postgres) Close() error {
return p.Close()
}
func (p *Postgres) DeleteDevices(ctx context.Context, devices []*types.Device) error {
asset := "pkg/db/sql/psql/deleteDevice.sql"
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, device := range devices {
_, err := stmt.ExecContext(ctx, &device.DeviceID)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) DeleteSensors(ctx context.Context, sensors []*types.Sensor) error {
asset := "pkg/db/sql/psql/deleteSensor.sql"
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, sensor := range sensors {
_, err := stmt.ExecContext(ctx, &sensor.SensorID)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) DeleteMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
deleteMeasuredValue := func(ctx context.Context, query string, measuredValues []*types.MeasuredValue) error {
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
_, err := stmt.ExecContext(ctx, &measuredValue.ID)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
sortedMeasuredValueTypes := make(map[types.MeasuredValueType][]*types.MeasuredValue)
for _, measuredValue := range measuredValues {
if _, ok := sortedMeasuredValueTypes[measuredValue.ValueType]; !ok {
sortedMeasuredValueTypes[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
}
sortedMeasuredValueTypes[measuredValue.ValueType] = append(sortedMeasuredValueTypes[measuredValue.ValueType], measuredValue)
}
assetFunc := func(queryFile string) (string, error) {
queryBytes, err := Asset(queryFile)
if err != nil {
return "", fmt.Errorf("%v: %v", errorGetAsset, err)
}
return string(queryBytes), nil
}
for measuredValueType, sortedMeasuredValues := range sortedMeasuredValueTypes {
switch measuredValueType {
case types.MeasuredValueTypeHumidity:
query, err := assetFunc("pkg/db/sql/psql/deleteHumidity.sql")
if err != nil {
return err
}
if err := deleteMeasuredValue(ctx, query, sortedMeasuredValues); err != nil {
return err
}
case types.MeasuredValueTypePressure:
query, err := assetFunc("pkg/db/sql/psql/deletePressure.sql")
if err != nil {
return err
}
if err := deleteMeasuredValue(ctx, query, sortedMeasuredValues); err != nil {
return err
}
case types.MeasuredValueTypeTemperature:
query, err := assetFunc("pkg/db/sql/psql/deleteTemperature.sql")
if err != nil {
return err
}
if err := deleteMeasuredValue(ctx, query, sortedMeasuredValues); err != nil {
return err
}
}
}
return nil
}
func (p *Postgres) InsertDevices(ctx context.Context, devices []*types.Device) error {
asset := "pkg/db/sql/psql/insertDevice.sql"
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, device := range devices {
_, err := stmt.ExecContext(ctx, &device.DeviceID, &device.DeviceName, &device.DeviceLocation, &device.CreationDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
sortedMeasuredValueTypes := make(map[types.MeasuredValueType][]*types.MeasuredValue)
for _, measuredValue := range measuredValues {
if _, ok := sortedMeasuredValueTypes[measuredValue.ValueType]; !ok {
sortedMeasuredValueTypes[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
}
sortedMeasuredValueTypes[measuredValue.ValueType] = append(sortedMeasuredValueTypes[measuredValue.ValueType], measuredValue)
}
for measuredValueType, sortedMeasuredValues := range sortedMeasuredValueTypes {
switch measuredValueType {
case types.MeasuredValueTypeHumidity:
if err := p.insertHumidity(ctx, sortedMeasuredValues); err != nil {
return err
}
case types.MeasuredValueTypePressure:
if err := p.insertPressure(ctx, sortedMeasuredValues); err != nil {
return err
}
case types.MeasuredValueTypeTemperature:
if err := p.insertTemperature(ctx, sortedMeasuredValues); err != nil {
return err
}
}
}
return nil
}
func (p *Postgres) insertHumidity(ctx context.Context, measuredValues []*types.MeasuredValue) error {
asset := "pkg/db/sql/psql/insertHumidity.sql"
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.ValueType != types.MeasuredValueTypeHumidity {
continue
}
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) insertPressure(ctx context.Context, measuredValues []*types.MeasuredValue) error {
asset := "pkg/db/sql/psql/insertPressure.sql"
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.ValueType != types.MeasuredValueTypePressure {
continue
}
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) insertTemperature(ctx context.Context, measuredValues []*types.MeasuredValue) error {
asset := "pkg/db/sql/psql/insertTemperature.sql"
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.ValueType != types.MeasuredValueTypeTemperature {
continue
}
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) InsertSensors(ctx context.Context, sensors []*types.Sensor) error {
asset := "pkg/db/sql/psql/insertSensor.sql"
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, sensor := range sensors {
_, err := stmt.ExecContext(ctx, &sensor.SensorID, &sensor.SensorName, &sensor.SensorLocation, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.SensorModel, &sensor.SensorEnabled, &sensor.DeviceID, &sensor.CreationDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) SelectDeviceByID(ctx context.Context, id string) (*types.Device, error) {
asset := "pkg/db/sql/psql/selectDeviceByID.sql"
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
row := stmt.QueryRowContext(ctx, id)
if row == nil {
return nil, errorRowNotFound
}
device := new(types.Device)
err = row.Scan(&device.DeviceID, &device.DeviceName, &device.DeviceLocation, &device.CreationDate)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorScanRow, err)
}
return device, nil
}
func (p *Postgres) SelectMeasuredValuesByIDAndType(ctx context.Context, id string, valueType types.MeasuredValueType) (*types.MeasuredValue, error) {
switch valueType {
case types.MeasuredValueTypeHumidity:
return p.SelectHumidityByID(ctx, id)
case types.MeasuredValueTypePressure:
return p.SelectPressureByID(ctx, id)
case types.MeasuredValueTypeTemperature:
return p.SelectTemperatureByID(ctx, id)
default:
return nil, fmt.Errorf("%v: %v", errorUnknownMeasuredValueType, valueType)
}
}
func (p *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "pkg/db/sql/psql/selectHumidities.sql"
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeHumidity, queryFile, nil)
if err != nil {
return nil, err
}
return measuredValues, nil
}
func (p *Postgres) SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "pkg/db/sql/psql/selectHumidityByID.sql"
args := []interface{}{id}
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeHumidity, queryFile, args)
if err != nil {
return nil, err
}
if len(measuredValues) == 0 {
return nil, fmt.Errorf("%v: %v", errorRowNotFound, id)
}
return measuredValues[0], nil
}
func (p *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "pkg/db/sql/psql/selectPressures.sql"
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypePressure, queryFile, nil)
if err != nil {
return nil, err
}
return measuredValues, nil
}
func (p *Postgres) SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "pkg/db/sql/psql/selectPressureByID.sql"
args := []interface{}{id}
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypePressure, queryFile, args)
if err != nil {
return nil, err
}
if len(measuredValues) == 0 {
return nil, fmt.Errorf("%v: %v", errorRowNotFound, id)
}
return measuredValues[0], nil
}
func (p *Postgres) SelectSensorByID(ctx context.Context, id string) (*types.Sensor, error) {
asset := "pkg/db/sql/psql/selectSensorByID.sql"
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
row := stmt.QueryRowContext(ctx, id)
if row == nil {
return nil, errorRowNotFound
}
sensor := new(types.Sensor)
err = row.Scan(&sensor.SensorID, &sensor.SensorName, &sensor.SensorLocation, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.SensorModel, &sensor.SensorEnabled, &sensor.DeviceID, &sensor.CreationDate)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorScanRow, err)
}
return sensor, nil
}
func (p *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "pkg/db/sql/psql/selectTemperatures.sql"
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeTemperature, queryFile, nil)
if err != nil {
return nil, err
}
return measuredValues, nil
}
func (p *Postgres) SelectTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "pkg/db/sql/psql/selectTemperatureByID.sql"
args := []interface{}{id}
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeTemperature, queryFile, args)
if err != nil {
return nil, err
}
if len(measuredValues) == 0 {
return nil, fmt.Errorf("%v: %v", errorRowNotFound, id)
}
return measuredValues[0], nil
}
func (p *Postgres) selectMeasuredValues(ctx context.Context, measuredValueType types.MeasuredValueType, queryFile string, queryArgs []interface{}) ([]*types.MeasuredValue, error) {
queryBytes, err := Asset(queryFile)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
rows, err := stmt.QueryContext(ctx, queryArgs...)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorStatementQuery, err)
}
measuredValues := make([]*types.MeasuredValue, 0)
for rows.Next() {
measuredValue := new(types.MeasuredValue)
measuredValue.ValueType = measuredValueType
rows.Scan(&measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
measuredValues = append(measuredValues, measuredValue)
}
return measuredValues, nil
}
func (p *Postgres) UpdateDevices(ctx context.Context, devices []*types.Device) error {
return nil
}
func (p *Postgres) UpdateMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
return nil
}
func (p *Postgres) UpdateSensors(ctx context.Context, sensots []*types.Sensor) error {
return nil
}

View File

@ -1,273 +0,0 @@
package db_test
import (
"context"
"testing"
"github.com/go-flucky/flucky/pkg/db"
"github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/test/goldenfiles"
"github.com/stretchr/testify/require"
)
type test struct {
Name string
Test func(*testing.T)
}
var (
database db.Database
postgresContainerImage string = "docker.io/postgres/postgres"
postgresHost string = "localhost"
postgresPort string = "5432"
postgresUser string = "postgres"
postgresPassword string = "postgres"
postgresDatabase string = "postgres"
goldenDevicesFilePath string = "test/goldenfiles/json/goldenDevices.json"
goldenSensorsFilePath string = "test/goldenfiles/json/goldenSensors.json"
goldenMeasuredValuesFilePath string = "test/goldenfiles/json/goldenMeasuredValues.json"
goldenPressuresFilePath string = "test/goldenfiles/json/goldenPressures.json"
goldenHumiditiesFilePath string = "test/goldenfiles/json/goldenHumidities.json"
goldenTemperaturesFilePath string = "test/goldenfiles/json/goldenTemperatures.json"
goldenDevices []*types.Device
goldenSensors []*types.Sensor
goldenMeasuredValues []*types.MeasuredValue
goldenPressures []*types.MeasuredValue
goldenHumidites []*types.MeasuredValue
goldenTemperatures []*types.MeasuredValue
)
func load(t *testing.T) {
require := require.New(t)
d, err := goldenfiles.GetGoldenDevices(goldenDevicesFilePath)
require.NoError(err)
goldenDevices = d
s, err := goldenfiles.GetGoldenSensors(goldenSensorsFilePath)
require.NoError(err)
goldenSensors = s
hum, err := goldenfiles.GetGoldenMeasuredValues(goldenHumiditiesFilePath)
require.NoError(err)
goldenHumidites = hum
mv, err := goldenfiles.GetGoldenMeasuredValues(goldenMeasuredValuesFilePath)
require.NoError(err)
goldenMeasuredValues = mv
pres, err := goldenfiles.GetGoldenMeasuredValues(goldenPressuresFilePath)
require.NoError(err)
goldenPressures = pres
temp, err := goldenfiles.GetGoldenMeasuredValues(goldenTemperaturesFilePath)
require.NoError(err)
goldenTemperatures = temp
}
func TestPostgres(t *testing.T) {
require := require.New(t)
load(t)
db, err := db.New(db.DBOTypePostgres, postgresHost, postgresPort, postgresDatabase, postgresUser, postgresPassword)
database = db
require.Nil(err)
tests := []*test{
&test{
Name: "insertDevices",
Test: testInsertDevices,
},
&test{
Name: "insertSensors",
Test: testInsertSensors,
},
&test{
Name: "insertHumidity",
Test: testInsertHumidity,
},
&test{
Name: "insertPressure",
Test: testInsertPressure,
},
&test{
Name: "insertTemperatures",
Test: testInsertTemperatures,
},
&test{
Name: "deleteHumidities",
Test: testDeleteHumidity,
},
&test{
Name: "deletePressures",
Test: testDeletePressures,
},
&test{
Name: "deleteTemperatures",
Test: testDeleteTemperatures,
},
&test{
Name: "insertMeasuredValues",
Test: testInsertMeasuredValues,
},
&test{
Name: "deleteMeasuredValues",
Test: testDeleteMeasuredValues,
},
&test{
Name: "deleteSensors",
Test: testDeleteSensors,
},
&test{
Name: "deleteDevices",
Test: testDeleteDevices,
},
}
for _, test := range tests {
t.Run(test.Name, test.Test)
}
}
func testInsertDevices(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertDevices(ctx, goldenDevices)
require.NoError(err)
for _, goldenDevice := range goldenDevices {
testDevice, err := database.SelectDeviceByID(ctx, goldenDevice.DeviceID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, goldenDevice, testDevice)
}
}
func testInsertSensors(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertSensors(ctx, goldenSensors)
require.NoError(err)
for _, goldenSensor := range goldenSensors {
testSensor, err := database.SelectSensorByID(ctx, goldenSensor.SensorID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, goldenSensor, testSensor)
}
}
func testInsertHumidity(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertMeasuredValues(ctx, goldenHumidites)
require.NoError(err)
for _, goldenHumidity := range goldenHumidites {
testHumidity, err := database.SelectHumidityByID(ctx, goldenHumidity.ID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{testHumidity}, []*types.MeasuredValue{testHumidity})
}
}
func testInsertMeasuredValues(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertMeasuredValues(ctx, goldenMeasuredValues)
require.NoError(err)
for _, goldenMeasuredValue := range goldenMeasuredValues {
testMeasuredValue, err := database.SelectMeasuredValuesByIDAndType(ctx, goldenMeasuredValue.ID, goldenMeasuredValue.ValueType)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{goldenMeasuredValue}, []*types.MeasuredValue{testMeasuredValue})
}
}
func testInsertPressure(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertMeasuredValues(ctx, goldenPressures)
require.NoError(err)
for _, goldenPressure := range goldenPressures {
testPressure, err := database.SelectPressureByID(ctx, goldenPressure.ID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{testPressure}, []*types.MeasuredValue{testPressure})
}
}
func testInsertTemperatures(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertMeasuredValues(ctx, goldenTemperatures)
require.NoError(err)
for _, goldenTemperature := range goldenTemperatures {
testTemperature, err := database.SelectTemperatureByID(ctx, goldenTemperature.ID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{goldenTemperature}, []*types.MeasuredValue{testTemperature})
}
}
func testDeleteDevices(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteDevices(ctx, goldenDevices)
require.NoError(err)
for _, goldenDevice := range goldenDevices {
_, err := database.SelectDeviceByID(ctx, goldenDevice.DeviceID)
require.Error(err)
}
}
func testDeleteSensors(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteSensors(ctx, goldenSensors)
require.NoError(err)
for _, goldenSensor := range goldenSensors {
_, err := database.SelectDeviceByID(ctx, goldenSensor.SensorID)
require.Error(err)
}
}
func testDeleteHumidity(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteMeasuredValues(ctx, goldenHumidites)
require.NoError(err)
for _, goldenHumidity := range goldenHumidites {
_, err := database.SelectHumidityByID(ctx, goldenHumidity.ID)
require.Error(err)
}
}
func testDeleteMeasuredValues(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteMeasuredValues(ctx, goldenMeasuredValues)
require.NoError(err)
for _, goldenMeasuredValue := range goldenMeasuredValues {
_, err := database.SelectPressureByID(ctx, goldenMeasuredValue.ID)
require.Error(err)
}
}
func testDeletePressures(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteMeasuredValues(ctx, goldenPressures)
require.NoError(err)
for _, goldenPressure := range goldenPressures {
_, err := database.SelectPressureByID(ctx, goldenPressure.ID)
require.Error(err)
}
}
func testDeleteTemperatures(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteMeasuredValues(ctx, goldenTemperatures)
require.NoError(err)
for _, goldenTemperature := range goldenTemperatures {
_, err := database.SelectTemperatureByID(ctx, goldenTemperature.ID)
require.Error(err)
}
}

View File

@ -1,2 +0,0 @@
DELETE FROM humidities
WHERE humidity_id = $1;

View File

@ -1,2 +0,0 @@
DELETE FROM pressures
WHERE pressure_id = $1;

View File

@ -1,2 +0,0 @@
DELETE FROM temperatures
WHERE temperature_id = $1;

View File

@ -1,10 +0,0 @@
INSERT INTO humidities (
humidity_id,
humidity_value,
humidity_from_date,
humidity_till_date,
sensor_id,
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7);

View File

@ -1,10 +0,0 @@
INSERT INTO pressures (
pressure_id,
pressure_value,
pressure_from_date,
pressure_till_date,
sensor_id,
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7);

View File

@ -1,10 +0,0 @@
INSERT INTO temperatures (
temperature_id,
temperature_value,
temperature_from_date,
temperature_till_date,
sensor_id,
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7);

View File

@ -1,146 +0,0 @@
DROP TABLE IF EXISTS devices CASCADE;
DROP TABLE IF EXISTS sensors CASCADE;
DROP TABLE IF EXISTS humidities CASCADE;
DROP TABLE IF EXISTS pressures CASCADE;
DROP TABLE IF EXISTS temperatures CASCADE;
-- +----------------------------------------+
-- | TABLES |
-- +----------------------------------------+
CREATE TABLE IF NOT EXISTS devices(
device_id CHAR(36) CONSTRAINT pk_devices PRIMARY KEY,
device_name VARCHAR(32) NOT NULL,
device_location VARCHAR(32),
device_last_contact TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS sensors (
sensor_id CHAR(36) CONSTRAINT pk_sensors PRIMARY KEY,
sensor_name VARCHAR(32) NOT NULL,
sensor_location VARCHAR(32) NOT NULL,
wire_id VARCHAR(15),
i2c_bus VARCHAR(255),
i2c_address VARCHAR(12),
gpio_number VARCHAR(6),
sensor_model VARCHAR(16) NOT NULL,
sensor_enabled BOOLEAN DEFAULT TRUE NOT NULL,
sensor_last_contact TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
device_id CHAR(36),
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS humidities (
humidity_id CHAR(36) CONSTRAINT pk_humidities PRIMARY KEY,
humidity_value NUMERIC(9,3) NOT NULL,
humidity_from_date TIMESTAMP WITH TIME ZONE NOT NULL,
humidity_till_date TIMESTAMP WITH TIME ZONE,
sensor_id CHAR(36),
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE
);
CREATE TABLE IF NOT EXISTS pressures (
pressure_id CHAR(36) CONSTRAINT pk_pressures PRIMARY KEY,
pressure_value NUMERIC(10,3) NOT NULL,
pressure_from_date TIMESTAMP WITH TIME ZONE NOT NULL,
pressure_till_date TIMESTAMP WITH TIME ZONE,
sensor_id CHAR(36),
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE
);
CREATE TABLE IF NOT EXISTS temperatures (
temperature_id CHAR(36) CONSTRAINT pk_temperatures PRIMARY KEY,
temperature_value NUMERIC(5,3) NOT NULL,
temperature_from_date TIMESTAMP WITH TIME ZONE NOT NULL,
temperature_till_date TIMESTAMP WITH TIME ZONE,
sensor_id CHAR(36),
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE
);
-- +----------------------------------------+
-- | FOREIGN-KEYS |
-- +----------------------------------------+
ALTER TABLE sensors
ADD FOREIGN KEY (device_id)
REFERENCES devices(device_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE humidities
ADD FOREIGN KEY (sensor_id)
REFERENCES sensors(sensor_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE pressures
ADD FOREIGN KEY (sensor_id)
REFERENCES sensors(sensor_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE temperatures
ADD FOREIGN KEY (sensor_id)
REFERENCES sensors(sensor_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
-- +----------------------------------------+
-- | Trigger-Functions |
-- +----------------------------------------+
CREATE OR REPLACE FUNCTION device_last_contact()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE devices
SET device_last_contact = CURRENT_TIMESTAMP
WHERE device_id = NEW.device_id;
RETURN NEW;
END;
$BODY$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION sensor_last_contact()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE sensors
SET sensor_last_contact = CURRENT_TIMESTAMP,
sensor_enabled = true
WHERE sensor_id = NEW.sensor_id;
RETURN NEW;
END;
$BODY$ LANGUAGE plpgsql;
-- +----------------------------------------+
-- | Trigger |
-- +----------------------------------------+
DROP TRIGGER IF EXISTS ai_humidities ON humidities;
DROP TRIGGER IF EXISTS ai_pressure ON pressures;
DROP TRIGGER IF EXISTS ai_temperatures ON temperatures;
CREATE TRIGGER au_sensors
AFTER UPDATE
ON sensors
FOR EACH ROW
EXECUTE PROCEDURE device_last_contact();
CREATE TRIGGER ai_humidities
AFTER INSERT
ON humidities
FOR EACH ROW
EXECUTE PROCEDURE sensor_last_contact();
CREATE TRIGGER ai_pressures
AFTER INSERT
ON pressures
FOR EACH ROW
EXECUTE PROCEDURE sensor_last_contact();
CREATE TRIGGER ai_temperatures
AFTER INSERT
ON temperatures
FOR EACH ROW
EXECUTE PROCEDURE sensor_last_contact();

View File

@ -1,10 +0,0 @@
SELECT
humidity_id,
humidity_value,
humidity_from_date,
humidity_till_date,
sensor_id,
creation_date,
update_date
FROM
humidities;

View File

@ -1,12 +0,0 @@
SELECT
humidity_id,
humidity_value,
humidity_from_date,
humidity_till_date,
sensor_id,
creation_date,
update_date
FROM
humidities
WHERE
humidity_id = $1;

View File

@ -1,12 +0,0 @@
SELECT
pressure_id,
pressure_value,
pressure_from_date,
pressure_till_date,
sensor_id,
creation_date,
update_date
FROM
pressures
WHERE
pressure_id = $1;

View File

@ -1,10 +0,0 @@
SELECT
pressure_id,
pressure_value,
pressure_from_date,
pressure_till_date,
sensor_id,
creation_date,
update_date
FROM
pressures;

View File

@ -1,12 +0,0 @@
SELECT
temperature_id,
temperature_value,
temperature_from_date,
temperature_till_date,
sensor_id,
creation_date,
update_date
FROM
temperatures
WHERE
temperature_id = $1;

View File

@ -1,10 +0,0 @@
SELECT
temperature_id,
temperature_value,
temperature_from_date,
temperature_till_date,
sensor_id,
creation_date,
update_date
FROM
temperatures;

View File

@ -1,16 +0,0 @@
package collect
func Errors(errorChannel <-chan error) []error {
errorList := make([]error, 0)
for {
select {
case err, more := <-errorChannel:
if more {
errorList = append(errorList, err)
continue
}
default:
return errorList
}
}
}

View File

@ -1,20 +0,0 @@
package collect
import (
"github.com/go-flucky/flucky/pkg/types"
)
func MeasuredValues(measuredValuesChannel <-chan []*types.MeasuredValue) []*types.MeasuredValue {
cachedMeasuredValues := make([]*types.MeasuredValue, 0)
for {
select {
case measuredValues, more := <-measuredValuesChannel:
if more {
cachedMeasuredValues = append(cachedMeasuredValues, measuredValues...)
continue
}
default:
return cachedMeasuredValues
}
}
}

View File

@ -1,18 +0,0 @@
package prittyprint
import "fmt"
func FormatErrors(errors []error) error {
if len(errors) > 0 {
errMsg := ""
for i, err := range errors {
if i == 0 {
errMsg = fmt.Sprintf("%v", err.Error())
} else {
errMsg = fmt.Sprintf("%v\n%v", errMsg, err.Error())
}
}
return fmt.Errorf(errMsg)
}
return nil
}

View File

@ -1,129 +0,0 @@
package logfile
import (
"encoding/csv"
"fmt"
"os"
"strconv"
"time"
"github.com/go-flucky/flucky/pkg/internal/format"
"github.com/go-flucky/flucky/pkg/types"
)
type csvLogfile struct {
logfile string
}
func (cl *csvLogfile) Read() ([]*types.MeasuredValue, error) {
if _, err := os.Stat(cl.logfile); os.IsNotExist(err) {
return nil, fmt.Errorf("%v: %v", errorLogfileNotFound, cl.logfile)
}
f, err := os.Open(cl.logfile)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorLogfileOpen, cl.logfile)
}
defer f.Close()
r := csv.NewReader(f)
records, err := r.ReadAll()
if err != nil {
return nil, fmt.Errorf("%v %v: %v", errorLogfileDecode, cl.logfile, err)
}
measuredValues := make([]*types.MeasuredValue, 0)
for _, record := range records {
// ValueType
valueType, err := types.SelectMeasuredValueType(record[1])
if err != nil {
return nil, fmt.Errorf("%v %v: %v", errorParseFloat, record[1], err)
}
// Value
value, err := strconv.ParseFloat(record[2], 64)
if err != nil {
return nil, fmt.Errorf("%v %v: %v", errorParseFloat, record[2], err)
}
// Times
times := make([]time.Time, 0)
for _, i := range []int{3, 4} {
time, err := time.Parse(format.TimeFormat, record[i])
if err != nil {
return nil, fmt.Errorf("%v %v: %v", errorParseTime, record[i], err)
}
times = append(times, time)
}
measuredValue := &types.MeasuredValue{
ID: record[0],
ValueType: *valueType,
Value: value,
FromDate: times[0],
TillDate: times[1],
SensorID: record[5],
}
// Creation date
creationDate, err := time.Parse(format.TimeFormat, record[6])
if err != nil {
return nil, fmt.Errorf("%v %v: %v", errorParseTime, record[6], err)
}
measuredValue.CreationDate = creationDate
if record[7] != "null" {
updateDate, err := time.Parse(format.TimeFormat, record[7])
if err != nil {
return nil, fmt.Errorf("%v %v: %v", errorParseTime, record[7], err)
}
measuredValue.UpdateDate = &updateDate
}
measuredValues = append(measuredValues, measuredValue)
}
return measuredValues, nil
}
func (cl *csvLogfile) Write(measuredValues []*types.MeasuredValue) error {
f, err := os.Create(cl.logfile)
if err != nil {
return fmt.Errorf("%v: %v", errorLogfileCreate, cl.logfile)
}
defer f.Close()
writeCreationDate(measuredValues)
w := csv.NewWriter(f)
for _, measuredValue := range measuredValues {
record := []string{
measuredValue.ID,
fmt.Sprintf("%v", measuredValue.ValueType),
fmt.Sprintf("%v", measuredValue.Value),
measuredValue.FromDate.Format(format.TimeFormat),
measuredValue.TillDate.Format(format.TimeFormat),
measuredValue.SensorID,
}
record = append(record, measuredValue.CreationDate.Format(format.TimeFormat))
if measuredValue.UpdateDate != nil {
record = append(record, measuredValue.UpdateDate.Format(format.TimeFormat))
} else {
record = append(record, "null")
}
w.Write(record)
}
w.Flush()
return nil
}

View File

@ -1,28 +0,0 @@
package logfile
import "errors"
var (
errorLogfileCreate = errors.New("Can not create logfile")
errorLogfileDecode = errors.New("Can not decode from reader")
errorLogfileEncode = errors.New("Can not encode from writer")
errorLogfileMarshal = errors.New("Can not marshal values")
errorLogfileNotFound = errors.New("Can not find logfile")
errorLogfileOpen = errors.New("Can not open logfile")
errorLogfileRead = errors.New("Can not read from given reader")
errorLogfileUnmarshal = errors.New("Can not unmarshal values")
errorLogfileWrite = errors.New("Can not write with given writer")
errorParseFloat = errors.New("Can not parse float")
errorParseMeasurementUnit = errors.New("Can not parse mesaurement unit")
errorParseTime = errors.New("Can not parse time")
errorNoValidHumidityID = errors.New("No valid humidity id detected or available")
errorNoValidMesuredValue = errors.New("No mesured value detected or available")
errorNoValidSensorID = errors.New("No sensor id detected or available")
errorNoValidTemperatureID = errors.New("No valid temperature id detected or available")
errorNoValidTime = errors.New("No time detected or available")
errorNoValidTimePeriods = errors.New("No valid time periods")
errorTypeSwitch = errors.New("Can not detect type via type switch")
)

View File

@ -1,11 +0,0 @@
package logfile
import (
"github.com/go-flucky/flucky/pkg/types"
)
// Logfile is an interface for various logfiles
type Logfile interface {
Read() ([]*types.MeasuredValue, error)
Write(measuredValues []*types.MeasuredValue) error
}

View File

@ -1,60 +0,0 @@
package logfile
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/go-flucky/flucky/pkg/types"
)
type jsonLogfile struct {
logfile string
}
func (jl *jsonLogfile) Read() ([]*types.MeasuredValue, error) {
if _, err := os.Stat(jl.logfile); os.IsNotExist(err) {
return nil, fmt.Errorf("%v: %v", errorLogfileNotFound, jl.logfile)
}
f, err := os.Open(jl.logfile)
if err != nil {
return nil, fmt.Errorf("%v %v: %v", errorLogfileOpen, jl.logfile, err)
}
measuredValues := make([]*types.MeasuredValue, 0)
if err := json.NewDecoder(f).Decode(&measuredValues); err != nil {
return nil, fmt.Errorf("%v %v: %v", errorLogfileDecode, jl.logfile, err)
}
return measuredValues, nil
}
func (jl *jsonLogfile) Write(measuredValues []*types.MeasuredValue) error {
if _, err := os.Stat(filepath.Dir(jl.logfile)); os.IsNotExist(err) {
if err := os.MkdirAll(filepath.Dir(jl.logfile), 755); err != nil {
return fmt.Errorf("Directory for the logfile can not be created: %v", err)
}
}
writeCreationDate(measuredValues)
f, err := os.Create(jl.logfile)
if err != nil {
return fmt.Errorf("%v %v: %v", errorLogfileCreate, jl.logfile, err)
}
jsonEncoder := json.NewEncoder(f)
jsonEncoder.SetIndent("", " ")
err = jsonEncoder.Encode(measuredValues)
if err != nil {
return fmt.Errorf("%v %v: %v", errorLogfileEncode, jl.logfile, err)
}
return nil
}

View File

@ -1,132 +0,0 @@
package logfile
import (
"math"
"path/filepath"
"sort"
"github.com/go-flucky/flucky/pkg/internal/format"
"github.com/go-flucky/flucky/pkg/types"
)
// var validUUID = regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
// Append adds an array of several measured values to a logfile
func Append(logfile Logfile, compression bool, round float64, measuredValues []*types.MeasuredValue) error {
if round != 0 {
for _, measuredValue := range measuredValues {
measuredValue.Value = math.Round(measuredValue.Value/round) * round
}
}
allMeasuredValues, err := logfile.Read()
if err != nil {
return err
}
allMeasuredValues = append(allMeasuredValues, measuredValues...)
if compression {
allMeasuredValues = Compression(allMeasuredValues)
}
err = logfile.Write(allMeasuredValues)
if err != nil {
return err
}
return nil
}
// Compression the measured values. The system checks whether the measured values
// of the same type correspond to those of the predecessor. If this is the case,
// the current value is discarded and the validity date of the previous value is
// set to that of the current value. This means that no information is lost.
// Only the validity period of the measured value is increased.
func Compression(measuredValues []*types.MeasuredValue) []*types.MeasuredValue {
compressedMeasuredValues := make([]*types.MeasuredValue, 0)
lastMeasuredValuesBySensors := make(map[string]map[types.MeasuredValueType]*types.MeasuredValue, 0)
// Sort all measured values according to the start time of the validity date
// in order to successfully implement the subsequent compression.
sort.SliceStable(measuredValues, func(i int, j int) bool {
return measuredValues[i].FromDate.Before(measuredValues[j].TillDate)
})
now := format.FormatedTime()
for _, measuredValue := range measuredValues {
if _, ok := lastMeasuredValuesBySensors[measuredValue.SensorID]; !ok {
lastMeasuredValuesBySensors[measuredValue.SensorID] = make(map[types.MeasuredValueType]*types.MeasuredValue, 0)
}
if _, ok := lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType]; !ok {
lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType] = measuredValue
continue
}
if lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType].Value == measuredValue.Value {
lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType].TillDate = measuredValue.TillDate
lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType].UpdateDate = &now
} else if lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType].Value != measuredValue.Value {
compressedMeasuredValues = append(compressedMeasuredValues, lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType])
delete(lastMeasuredValuesBySensors[measuredValue.SensorID], measuredValue.ValueType)
lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType] = measuredValue
}
}
// Copy all remaining entries from the map into the cache array
for _, lastMeasuredValuesBySensor := range lastMeasuredValuesBySensors {
for _, measuredValueType := range types.MeasuredValueTypes {
if measuredValue, ok := lastMeasuredValuesBySensor[measuredValueType]; ok {
compressedMeasuredValues = append(compressedMeasuredValues, measuredValue)
}
}
}
// Sort all measured values again to include the measured values from the
// cache.
sort.SliceStable(compressedMeasuredValues, func(i int, j int) bool {
return compressedMeasuredValues[i].FromDate.Before(compressedMeasuredValues[j].FromDate)
})
return compressedMeasuredValues
}
// New returns a log file with basic functions for reading and writing data. The
// file extension of the logfile is taken into account to format the logfile
// into the correct format.
func New(logfile string) Logfile {
ext := filepath.Ext(logfile)
switch ext {
case ".csv":
return &csvLogfile{
logfile: logfile,
}
case ".json":
return &jsonLogfile{
logfile: logfile,
}
case ".xml":
return &xmlLogfile{
logfile: logfile,
}
default:
return &jsonLogfile{
logfile: logfile,
}
}
}
func writeCreationDate(measuredValues []*types.MeasuredValue) error {
for _, measuredValue := range measuredValues {
now := format.FormatedTime()
measuredValue.CreationDate = now
}
return nil
}

View File

@ -1 +0,0 @@
package logfile_test

View File

@ -1,19 +0,0 @@
package logfile
import (
"encoding/xml"
"github.com/go-flucky/flucky/pkg/types"
)
// MeasuredValues is an XML Wrapper for an array of measured values
type MeasuredValues struct {
XMLName xml.Name `xml:"measured_values"`
MeasuredValues []*MeasuredValue `xml:"measured_value"`
}
// MeasuredValue is an XML Wrapper for the original measured value struct
type MeasuredValue struct {
XMLName xml.Name `xml:"measured_value"`
*types.MeasuredValue
}

View File

@ -1,74 +0,0 @@
package logfile
import (
"encoding/xml"
"fmt"
"os"
"github.com/go-flucky/flucky/pkg/types"
)
type xmlLogfile struct {
logfile string
}
func (xl *xmlLogfile) GetLogfile() string {
return xl.logfile
}
func (xl *xmlLogfile) Read() ([]*types.MeasuredValue, error) {
if _, err := os.Stat(xl.logfile); os.IsNotExist(err) {
return nil, fmt.Errorf("%v: %v", errorLogfileNotFound, xl.logfile)
}
f, err := os.Open(xl.logfile)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorLogfileOpen, xl.logfile)
}
defer f.Close()
measuredValues := new(MeasuredValues)
if err := xml.NewDecoder(f).Decode(&measuredValues); err != nil {
return nil, fmt.Errorf("%v: %v", errorLogfileDecode, err)
}
cachedMeasuredValues := make([]*types.MeasuredValue, 0)
for _, measuredValue := range measuredValues.MeasuredValues {
cachedMeasuredValues = append(cachedMeasuredValues, measuredValue.MeasuredValue)
}
return cachedMeasuredValues, nil
}
func (xl *xmlLogfile) Write(measuredValues []*types.MeasuredValue) error {
f, err := os.Create(xl.logfile)
if err != nil {
return fmt.Errorf("%v: %v", errorLogfileCreate, xl.logfile)
}
defer f.Close()
writeCreationDate(measuredValues)
cachedMeasuredValues := new(MeasuredValues)
for _, measuredValue := range measuredValues {
cachedMeasuredValue := &MeasuredValue{
MeasuredValue: measuredValue,
}
cachedMeasuredValues.MeasuredValues = append(cachedMeasuredValues.MeasuredValues, cachedMeasuredValue)
}
bytes, err := xml.MarshalIndent(cachedMeasuredValues, "", " ")
if err != nil {
return fmt.Errorf("%v: %v", errorLogfileMarshal, err)
}
_, err = f.Write(bytes)
if err != nil {
return fmt.Errorf("%v: %v", errorLogfileWrite, err)
}
return nil
}

View File

@ -1,93 +0,0 @@
package logger
import (
"fmt"
"io"
"io/ioutil"
"os"
"sync"
"time"
)
type defaultLogger struct {
logLevel LogLevel
mutex *sync.Mutex
stdout io.Writer
stderr io.Writer
}
func (dl *defaultLogger) Debug(f string, v ...interface{}) {
dl.log(LogLevelDebug, f, v...)
}
func (dl *defaultLogger) Info(f string, v ...interface{}) {
dl.log(LogLevelInfo, f, v...)
}
func (dl *defaultLogger) Warn(f string, v ...interface{}) {
dl.log(LogLevelWarn, f, v...)
}
func (dl *defaultLogger) Error(f string, v ...interface{}) {
dl.log(LogLevelError, f, v...)
}
func (dl *defaultLogger) Fatal(f string, v ...interface{}) {
dl.log(LogLevelFatal, f, v...)
os.Exit(1)
}
func (dl *defaultLogger) log(ll LogLevel, f string, v ...interface{}) {
if dl.logLevel > ll {
return
}
layout := "2006/01/02 15:04:05"
dw := dl.stdout
prefix := ""
switch ll {
case LogLevelDebug:
prefix = "DEBUG"
case LogLevelInfo:
prefix = "INFO"
case LogLevelWarn:
prefix = "WARN"
case LogLevelError:
prefix = "ERROR"
dw = dl.stderr
case LogLevelFatal:
prefix = "FATAL"
dw = dl.stderr
}
dl.mutex.Lock()
fmt.Fprintf(dw, "%v %v: %v\n", time.Now().Format(layout), prefix, fmt.Sprintf(f, v...))
dl.mutex.Unlock()
}
func NewDefaultLogger(logLevel LogLevel) Logger {
return &defaultLogger{
logLevel: logLevel,
mutex: new(sync.Mutex),
stdout: os.Stdout,
stderr: os.Stderr,
}
}
func NewCustomLogger(loglevel LogLevel, out io.Writer, err io.Writer) Logger {
return &defaultLogger{
logLevel: loglevel,
mutex: new(sync.Mutex),
stdout: out,
stderr: err,
}
}
func NewSilentLogger() Logger {
return &defaultLogger{
logLevel: LogLevelDebug,
mutex: new(sync.Mutex),
stdout: ioutil.Discard,
stderr: ioutil.Discard,
}
}

View File

@ -1,69 +0,0 @@
package logger_test
import (
"bytes"
"strings"
"testing"
"github.com/go-flucky/flucky/pkg/logger"
"github.com/stretchr/testify/require"
)
func TestLogger(t *testing.T) {
require := require.New(t)
stdoutBuffer := new(bytes.Buffer)
stderrBuffer := new(bytes.Buffer)
// LogLevelDebug
l := logger.NewCustomLogger(logger.LogLevelDebug, stdoutBuffer, stderrBuffer)
l.Debug("DEBUG")
require.NotEmpty(stdoutBuffer.Bytes())
require.Empty(stderrBuffer.Bytes())
stdoutBuffer.Reset()
stderrBuffer.Reset()
// LogLevelInfo
l = logger.NewCustomLogger(logger.LogLevelInfo, stdoutBuffer, stderrBuffer)
l.Debug("DEBUG")
require.Empty(stdoutBuffer.Bytes())
require.Empty(stderrBuffer.Bytes())
stdoutBuffer.Reset()
stderrBuffer.Reset()
l.Info("INFO")
require.NotEmpty(stdoutBuffer.Bytes())
require.Empty(stderrBuffer.Bytes())
stringArray := strings.Split(stdoutBuffer.String(), ": ")
require.Equal(2, len(stringArray))
require.Equal("INFO\n", stringArray[1])
stdoutBuffer.Reset()
stderrBuffer.Reset()
// LogLevelWarn
l = logger.NewCustomLogger(logger.LogLevelWarn, stdoutBuffer, stderrBuffer)
l.Warn("WARN")
require.NotEmpty(stdoutBuffer.Bytes())
require.Empty(stderrBuffer.Bytes())
stringArray = strings.Split(stdoutBuffer.String(), ": ")
require.Equal(2, len(stringArray))
require.Equal("WARN\n", stringArray[1])
stdoutBuffer.Reset()
stderrBuffer.Reset()
// LogLevelError
l = logger.NewCustomLogger(logger.LogLevelError, stdoutBuffer, stderrBuffer)
l.Error("ERROR")
require.Empty(stdoutBuffer.Bytes())
require.NotEmpty(stderrBuffer.Bytes())
stringArray = strings.Split(stderrBuffer.String(), ": ")
require.Equal(2, len(stringArray))
require.Equal("ERROR\n", stringArray[1])
}

View File

@ -1,9 +0,0 @@
package logger
type Logger interface {
Debug(string, ...interface{})
Info(string, ...interface{})
Warn(string, ...interface{})
Error(string, ...interface{})
Fatal(string, ...interface{})
}

View File

@ -1,11 +0,0 @@
package logger
type LogLevel int
const (
LogLevelDebug LogLevel = iota + 1
LogLevelInfo
LogLevelWarn
LogLevelError
LogLevelFatal
)

134
pkg/repository/db/db.go Normal file
View File

@ -0,0 +1,134 @@
package db
import (
"context"
"database/sql"
"fmt"
"net/url"
"os"
"path/filepath"
postgresdml "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/postgres/dml"
sqlite3dml "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/sqlite3/dml"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
)
// Database is a general interface for a database backend like postgres, oracle
// or sqlite
type Database interface {
Close() error
DeleteDevices(ctx context.Context, deviceIDs ...string) error
DeleteSensors(ctx context.Context, sensorIDs ...string) error
InsertDevices(ctx context.Context, devices ...*types.Device) error
InsertOrUpdateDevices(ctx context.Context, devices ...*types.Device) error
InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error
InsertOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error
InsertSensors(ctx context.Context, sensors ...*types.Sensor) error
InsertOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error
Migrate(ctx context.Context) error
SelectDevice(ctx context.Context, deviceID string) (*types.Device, error)
SelectDevices(ctx context.Context) ([]*types.Device, error)
SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error)
SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error)
SelectSensor(ctx context.Context, sensorID string) (*types.Sensor, error)
SelectSensors(ctx context.Context) ([]*types.Sensor, error)
SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error)
UpdateDevices(ctx context.Context, devices ...*types.Device) error
UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error
}
// New returns a new database backend interface
func New(databaseURL *url.URL, flogger logger.Logger) (Database, error) {
// Check of nil pointer
for _, parameter := range []interface{}{
databaseURL,
flogger,
} {
if parameter == nil {
return nil, fmt.Errorf("Parameter does not be nil")
}
}
var (
database Database
err error
)
switch databaseURL.Scheme {
case "postgres":
// postgres://[user]:[password]@[host]:[port]/[path]?[query]
newDBO, err := sql.Open(databaseURL.Scheme, databaseURL.String())
if err != nil {
return nil, err
}
queries := make(map[string]string, 0)
for _, assetName := range postgresdml.AssetNames() {
body, err := postgresdml.Asset(assetName)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v, %w", assetName, err)
}
queries[assetName] = string(body)
}
database = &Postgres{
databaseURL: databaseURL,
dbo: newDBO,
flogger: flogger,
queries: queries,
}
case "sqlite3":
// Create directory if not exist
if _, err := os.Stat(filepath.Dir(databaseURL.Path)); os.IsNotExist(err) {
err := os.MkdirAll(filepath.Dir(databaseURL.Path), 0755)
if err != nil {
return nil, err
}
}
// enable foreign keys
values := databaseURL.Query()
values.Set("_foreign_keys", "on")
customRawURL := fmt.Sprintf("file://%v?%v", databaseURL.Path, values.Encode())
sqlDB, err := sql.Open("sqlite3", customRawURL)
if err != nil {
return nil, err
}
queries := make(map[string]string, 0)
for _, assetName := range sqlite3dml.AssetNames() {
body, err := sqlite3dml.Asset(assetName)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v, %w", assetName, err)
}
queries[assetName] = string(body)
}
database = &SQLite{
databaseURL: databaseURL,
dbo: sqlDB,
flogger: flogger,
queries: queries,
}
default:
return nil, fmt.Errorf("Unsupported database scheme: %v", databaseURL.Scheme)
}
// Initialize database scheme if not exists
err = database.Migrate(context.Background())
if err != nil {
return nil, err
}
return database, nil
}

View File

@ -0,0 +1,951 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"net/url"
"time"
postgresddl "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/postgres/ddl"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
"github.com/golang-migrate/migrate/v4"
bindata "github.com/golang-migrate/migrate/v4/source/go_bindata"
)
// Postgres implementation
type Postgres struct {
databaseURL *url.URL
dbo *sql.DB
flogger logger.Logger
queries map[string]string
}
// Close closes the database and prevents new queries from starting. Close then
// waits for all queries that have started processing on the server to finish.
func (postgres *Postgres) Close() error {
return postgres.dbo.Close()
}
// DeleteDevices from the database
func (postgres *Postgres) DeleteDevices(ctx context.Context, deviceIDs ...string) error {
queryFile := "deleteDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, deviceID := range deviceIDs {
_, err = stmt.Exec(deviceID)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// DeleteSensors from the database
func (postgres *Postgres) DeleteSensors(ctx context.Context, sensorIDs ...string) error {
queryFile := "deleteSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, sensorID := range sensorIDs {
_, err = stmt.Exec(sensorID)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// InsertDevices into the database
func (postgres *Postgres) InsertDevices(ctx context.Context, devices ...*types.Device) error {
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
err = postgres.insertDevices(tx, devices...)
if err != nil {
return err
}
return tx.Commit()
}
func (postgres *Postgres) insertDevices(tx *sql.Tx, devices ...*types.Device) error {
queryFile := "insertDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, device := range devices {
if device.CreationDate.Equal(time.Time{}) {
device.CreationDate = time.Now()
}
_, err = stmt.Exec(
&device.ID,
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return nil
}
// InsertMeasuredValues into the database
func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error {
splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0)
for _, measuredValue := range measuredValues {
if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok {
splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
}
splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
// General insert function
insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error {
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.CreationDate.Equal(time.Time{}) {
measuredValue.CreationDate = time.Now()
}
_, err := stmt.Exec(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.Date,
&measuredValue.SensorID,
&measuredValue.CreationDate,
&measuredValue.UpdateDate,
)
if err != nil {
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return nil
}
for measuredValueType, measuredValues := range splittedMeasuredValues {
var queryFile string
switch measuredValueType {
case types.Humidity:
queryFile = "insertHumidity.sql"
case types.Pressure:
queryFile = "insertPressure.sql"
case types.Temperature:
queryFile = "insertTemperature.sql"
default:
tx.Rollback()
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
}
err := insert(tx, queryFile, measuredValues)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
func (postgres *Postgres) InsertOrUpdateDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "insertOrUpdateDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
for _, device := range devices {
if device.CreationDate.Equal(time.Time{}) {
device.CreationDate = time.Now()
}
_, err = tx.Exec(
query,
&device.ID,
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// InsertOrUpdateMeasuredValues into the database
func (postgres *Postgres) InsertOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error {
splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0)
for _, measuredValue := range measuredValues {
if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok {
splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
}
splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
// General insert function
insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error {
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.CreationDate.Equal(time.Time{}) {
measuredValue.CreationDate = time.Now()
}
_, err := stmt.Exec(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.Date,
&measuredValue.SensorID,
&measuredValue.CreationDate,
&measuredValue.UpdateDate,
)
if err != nil {
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return nil
}
for measuredValueType, measuredValues := range splittedMeasuredValues {
var queryFile string
switch measuredValueType {
case types.Humidity:
queryFile = "insertOrUpdateHumidity.sql"
case types.Pressure:
queryFile = "insertOrUpdatePressure.sql"
case types.Temperature:
queryFile = "insertOrUpdateTemperature.sql"
default:
tx.Rollback()
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
}
err := insert(tx, queryFile, measuredValues)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// InsertOrUpdateSensors into the database
func (postgres *Postgres) InsertOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "insertOrUpdateSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, sensor := range sensors {
if sensor.CreationDate.Equal(time.Time{}) {
sensor.CreationDate = time.Now()
}
_, err = stmt.Exec(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// InsertSensors into the database
func (postgres *Postgres) InsertSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "insertSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, sensor := range sensors {
if sensor.CreationDate.Equal(time.Time{}) {
sensor.CreationDate = time.Now()
}
_, err = stmt.Exec(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// Migrate creates all required tables if not exist
func (postgres *Postgres) Migrate(ctx context.Context) error {
assetSource := bindata.Resource(postgresddl.AssetNames(), func(query string) ([]byte, error) {
return postgresddl.Asset(query)
})
sourceDriver, err := bindata.WithInstance(assetSource)
if err != nil {
return err
}
m, err := migrate.NewWithSourceInstance("bindata", sourceDriver, postgres.databaseURL.String())
if err != nil {
return err
}
err = m.Up()
switch {
case errors.Is(err, migrate.ErrNoChange):
return nil
default:
return err
}
}
// SelectDevice from database
func (postgres *Postgres) SelectDevice(ctx context.Context, id string) (*types.Device, error) {
queryFile := "selectDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
devices, err := postgres.selectDevices(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if len(devices) == 0 {
return nil, nil
}
return devices[0], nil
}
// SelectDevices from the database
func (postgres *Postgres) SelectDevices(ctx context.Context) ([]*types.Device, error) {
queryFile := "selectDevices.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
devices, err := postgres.selectDevices(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
return devices, nil
}
func (postgres *Postgres) selectDevices(tx *sql.Tx, query string, args ...interface{}) ([]*types.Device, error) {
stmt, err := tx.Prepare(query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
devices := make([]*types.Device, 0)
for rows.Next() {
device := new(types.Device)
err = rows.Scan(
&device.ID,
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
devices = append(devices, device)
}
return devices, nil
}
// SelectHumidity returns humidity from the database
func (postgres *Postgres) SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectHumidity.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Humidity
}
return measuredValues[0], nil
}
// SelectHumidities returns humidities from the database
func (postgres *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectHumidities.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Humidity
}
return measuredValues, nil
}
func (postgres *Postgres) selectMeasuredValue(tx *sql.Tx, query string, args ...interface{}) ([]*types.MeasuredValue, error) {
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return nil, err
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
tx.Rollback()
return nil, err
}
measuredValues := make([]*types.MeasuredValue, 0)
for rows.Next() {
measuredValue := new(types.MeasuredValue)
err := rows.Scan(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.Date,
&measuredValue.SensorID,
&measuredValue.CreationDate,
&measuredValue.UpdateDate,
)
if err != nil {
tx.Rollback()
return nil, err
}
measuredValues = append(measuredValues, measuredValue)
}
return measuredValues, nil
}
// SelectPressure returns pressure from the database
func (postgres *Postgres) SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectPressure.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Pressure
}
return measuredValues[0], nil
}
// SelectPressures returns pressure from the database
func (postgres *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectPressures.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Pressure
}
return measuredValues, nil
}
// SelectSensor from database
func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) {
queryFile := "selectSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
sensors, err := postgres.selectSensors(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
if len(sensors) == 0 {
return nil, nil
}
return sensors[0], nil
}
// SelectSensors from the database
func (postgres *Postgres) SelectSensors(ctx context.Context) ([]*types.Sensor, error) {
queryFile := "selectSensors.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
sensors, err := postgres.selectSensors(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
return sensors, nil
}
func (postgres *Postgres) selectSensors(tx *sql.Tx, query string, args ...interface{}) ([]*types.Sensor, error) {
stmt, err := tx.Prepare(query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
sensors := make([]*types.Sensor, 0)
for rows.Next() {
sensor := new(types.Sensor)
err = rows.Scan(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
sensors = append(sensors, sensor)
}
return sensors, nil
}
// SelectTemperature returns temperatures from the database
func (postgres *Postgres) SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectTemperature.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Temperature
}
return measuredValues[0], nil
}
// SelectTemperatures returns temperatures from the database
func (postgres *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectTemperatures.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Temperature
}
return measuredValues, nil
}
// UpdateDevices updates a device in the database
func (postgres *Postgres) UpdateDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "updateDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return err
}
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return err
}
defer stmt.Close()
for _, device := range devices {
now := time.Now()
device.UpdateDate = &now
_, err := stmt.Exec(
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
&device.ID,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// UpdateSensors updates a sensor in the database
func (postgres *Postgres) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "updateSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return err
}
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return err
}
defer stmt.Close()
for _, sensor := range sensors {
now := time.Now()
sensor.UpdateDate = &now
_, err := stmt.Exec(
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
&sensor.ID,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}

View File

@ -0,0 +1 @@
DROP TABLE devices;

View File

@ -0,0 +1,8 @@
CREATE TABLE devices (
device_id CHAR(36) NOT NULL,
device_name VARCHAR(64) NOT NULL,
device_location VARCHAR(64),
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE,
CONSTRAINT pk_devices PRIMARY KEY(device_id)
);

View File

@ -0,0 +1 @@
DROP TABLE sensors;

View File

@ -0,0 +1,17 @@
CREATE TABLE sensors (
sensor_id CHAR(36) NOT NULL,
sensor_name VARCHAR(64) NOT NULL,
sensor_location VARCHAR(64),
wire_id VARCHAR(64),
i2c_bus VARCHAR(255),
i2c_address VARCHAR(12),
gpio_number VARCHAR(6),
sensor_model VARCHAR(16) NOT NULL,
sensor_enabled BOOLEAN DEFAULT TRUE NOT NULL,
tick_duration VARCHAR(6) NOT NULL,
device_id CHAR(36) NOT NULL,
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE,
CONSTRAINT pk_sensors PRIMARY KEY(sensor_id),
CONSTRAINT fk_sensors_device_id FOREIGN KEY (device_id) REFERENCES devices(device_id) ON DELETE CASCADE ON UPDATE CASCADE
);

View File

@ -0,0 +1 @@
DROP TABLE humidities;

View File

@ -0,0 +1,10 @@
CREATE TABLE humidities (
id CHAR(36) NOT NULL,
value NUMERIC(10,3) NOT NULL,
date TIMESTAMP WITH TIME ZONE NOT NULL,
sensor_id CHAR(36) NOT NULL,
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE,
CONSTRAINT pk_humidites PRIMARY KEY (id),
CONSTRAINT pk_humidites_sensor_id FOREIGN KEY(sensor_id) REFERENCES sensors(sensor_id) ON DELETE CASCADE ON UPDATE CASCADE
);

View File

@ -0,0 +1 @@
DROP TABLE pressures;

View File

@ -0,0 +1,10 @@
CREATE TABLE pressures (
id CHAR(36) NOT NULL,
value NUMERIC(10,3) NOT NULL,
date TIMESTAMP WITH TIME ZONE NOT NULL,
sensor_id CHAR(36) NOT NULL,
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE,
CONSTRAINT pk_pressures PRIMARY KEY(id),
CONSTRAINT pk_pressures_sensor_id FOREIGN KEY(sensor_id) REFERENCES sensors(sensor_id) ON DELETE CASCADE ON UPDATE CASCADE
);

View File

@ -0,0 +1 @@
DROP TABLE temperatures;

View File

@ -0,0 +1,10 @@
CREATE TABLE temperatures (
id CHAR(36) NOT NULL,
value NUMERIC(10,3) NOT NULL,
date TIMESTAMP WITH TIME ZONE NOT NULL,
sensor_id CHAR(36) NOT NULL,
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE,
CONSTRAINT pk_temperatures PRIMARY KEY (id),
CONSTRAINT fk_temperatures_sensor_id FOREIGN KEY (sensor_id) REFERENCES sensors(sensor_id) ON DELETE CASCADE ON UPDATE CASCADE
);

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