21 Commits

Author SHA1 Message Date
bd3e9ff43d fix: golangci-lint and gosec warnings 2021-05-16 23:02:46 +02:00
a745311b7b fix: rename completion file 2021-05-16 22:09:19 +02:00
05d1cea40b fix 2021-04-10 17:44:20 +02:00
c22decf1a9 fix 2021-04-10 17:40:49 +02:00
d59c738aa3 fix 2021-04-10 17:37:08 +02:00
df0745c449 fix 2021-04-10 17:33:34 +02:00
1e4f5a9dfd WIP 2021-04-10 17:29:18 +02:00
f0d4c613ba fix: remove paring semver 2021-04-10 17:02:43 +02:00
4f2c03b0d5 fix: Makefile install target 2021-04-10 16:52:29 +02:00
12f05ec68e fix: Makefile targets 2021-04-10 16:06:47 +02:00
93bbc9d6b9 feat: add sub-command to generate completions 2021-04-10 10:30:37 +02:00
6e6a1cbbb6 fix: adapt go proxy settings 2021-04-09 17:11:51 +02:00
9207833a71 fix: add missing error handling 2021-04-09 16:55:35 +02:00
749f2697c7 feat: import from sqlite or postgres 2021-04-09 16:52:25 +02:00
8c2090a316 fix: rename ddl assets 2021-04-06 19:40:56 +02:00
a48ae72b4b fix: postgres embed - use direct sql statements instead of asset paths 2021-04-06 19:19:29 +02:00
b7e7e55916 fix: sqlite3 embed - use direct sql statements instead of asset paths 2021-04-06 19:19:25 +02:00
53d4a78adc refac: sort sqlite and postgres funcs 2021-03-21 22:31:00 +01:00
fb23874422 fix: remove deprecated repository code 2021-03-21 22:22:40 +01:00
344e6641d8 fix: remove integration step 2021-03-21 21:47:58 +01:00
7a88aaac0c refac: use embed instead of go-bindata, secure closing of transactions 2021-03-21 21:43:53 +01:00
116 changed files with 3041 additions and 2724 deletions

View File

@ -6,13 +6,25 @@ steps:
- name: build-linux-amd64
image: docker.io/volkerraschek/build-image:latest
commands:
- make --jobs=$(nproc) bin/linux/amd64/flucky
- make
when:
event:
- push
- pull_request
- tag
- name: trigger
image: plugins/downstream
settings:
server: https://drone.cryptic.systems
token:
from_secret: drone_token
params:
- VERSION=${DRONE_COMMIT_HASH}
fork: true
repositories:
- flucky/PKGBUILD@master
# steps:
# - name: test-unit
# image: docker.io/volkerraschek/build-image:latest

9
.gitignore vendored
View File

@ -1,13 +1,6 @@
# absolute files
bin
.env
flucky
flucky.rpm
flucky.tar.bz2
flucky.tar.gz
flucky.tar.xz
# relative files
**/bindata*.go
# directories
.vscode/

29
.golangci.yml Normal file
View File

@ -0,0 +1,29 @@
run:
skip-dirs:
- it
timeout: 10m
tests: true
linters:
disable-all: true
enable:
# Default
- deadcode
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
# Additionally linters
- bodyclose
- misspell
- nilerr
- rowserrcheck
- sqlclosecheck
- unparam
- whitespace

View File

@ -12,7 +12,9 @@ ARG VERSION
# ==================================
FROM ${BUILD_IMAGE} AS build
COPY . /workspace
RUN make ${EXECUTABLE_TARGET} VERSION=${VERSION} GOPROXY=${GOPROXY} GOPRIVATE=${GOPRIVATE}
ENV GOPROXY=${GOPROXY}
ENV GOPRIVATE=${GOPRIVATE}
RUN make ${EXECUTABLE_TARGET} VERSION=${VERSION}
# TARGET
# ==================================

188
Makefile
View File

@ -1,24 +1,9 @@
# VERSION
# If no version is specified as a parameter of make, the last git hash
# value is taken.
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)
EXECUTABLE:=flucky
# 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)
CONTAINER_IMAGE_VERSION?=latest
# EXECUTABLE
EXECUTABLE=flucky
DESTDIR?=
PREFIX?=/usr/local
# BINARIES
# ==============================================================================
@ -30,164 +15,105 @@ EXECUTABLE_TARGETS:= \
${EXECUTABLE}: bin/tmp/${EXECUTABLE}
bin/linux/amd64/${EXECUTABLE}: bindata
bin/linux/amd64/${EXECUTABLE}:
CGO_ENABLED=1 \
GOOS=linux \
GOARCH=amd64 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
GOPROXY=$(shell go env GOPROXY) \
GOPRIVATE=$(shell go env GOPRIVATE) \
go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@}
bin/linux/arm/5/${EXECUTABLE}: bindata
bin/linux/arm/5/${EXECUTABLE}:
CGO_ENABLED=1 \
GOOS=linux \
GOARCH=arm \
GOARM=5 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
GOPROXY=$(shell go env GOPROXY) \
GOPRIVATE=$(shell go env GOPRIVATE) \
go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@}
bin/linux/arm/7/${EXECUTABLE}: bindata
bin/linux/arm/7/${EXECUTABLE}:
CC=arm-linux-gnueabihf-gcc \
CGO_ENABLED=1 \
GOOS=linux \
GOARCH=arm \
GOARM=7 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
GOPROXY=$(shell go env GOPROXY) \
GOPRIVATE=$(shell go env GOPRIVATE) \
go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@}
bin/tmp/${EXECUTABLE}: bindata
bin/tmp/${EXECUTABLE}:
CGO_ENABLED=1 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
GOPROXY=$(shell go env GOPROXY) \
GOPRIVATE=$(shell go env GOPRIVATE) \
go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@}
# GO-BINDATA
# COMPLETIONS
# ==============================================================================
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
bin/tmp/${EXECUTABLE}.sh: bin/tmp/${EXECUTABLE}
bin/tmp/${EXECUTABLE} completion bash > ${@}
PHONY+=bindata
bindata: clean ${BINDATA_TARGETS}
bin/tmp/${EXECUTABLE}.fish: bin/tmp/${EXECUTABLE}
bin/tmp/${EXECUTABLE} completion fish > ${@}
pkg/repository/db/postgres/ddl/bindata.go:
go-bindata -pkg postgresddl -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/*
bin/tmp/${EXECUTABLE}.zsh: bin/tmp/${EXECUTABLE}
bin/tmp/${EXECUTABLE} completion zsh > ${@}
pkg/repository/db/postgres/dml/bindata.go:
go-bindata -pkg postgresdml -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/*
# UN/INSTALL
# ==============================================================================
PHONY+=install
install: bin/tmp/${EXECUTABLE} bin/tmp/${EXECUTABLE}.sh bin/tmp/${EXECUTABLE}.fish bin/tmp/${EXECUTABLE}.zsh
install --directory ${DESTDIR}${PREFIX}/bin
install --mode 755 bin/tmp/${EXECUTABLE} ${DESTDIR}${PREFIX}/bin/${EXECUTABLE}
pkg/repository/db/sqlite3/ddl/bindata.go:
go-bindata -pkg sqlite3ddl -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/*
install --directory ${DESTDIR}/etc/bash_completion.d
install --mode 755 bin/tmp/${EXECUTABLE}.sh ${DESTDIR}/etc/bash_completion.d/${EXECUTABLE}.sh
pkg/repository/db/sqlite3/dml/bindata.go:
go-bindata -pkg sqlite3dml -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/*
install --directory ${DESTDIR}/usr/share/fish/vendor_completions.d
install --mode 644 bin/tmp/${EXECUTABLE}.fish ${DESTDIR}/usr/share/fish/vendor_completions.d/${EXECUTABLE}.fish
install --directory ${DESTDIR}/usr/lib/systemd/system
install --mode 644 systemd/${EXECUTABLE}.service ${DESTDIR}/usr/lib/systemd/system/${EXECUTABLE}.service
install --directory ${DESTDIR}/usr/share/licenses/${EXECUTABLE}
install --mode 644 LICENSE ${DESTDIR}/usr/share/licenses/${EXECUTABLE}/LICENSE
PHONY+=uninstall
uninstall:
-rm --recursive --force \
${DESTDIR}${PREFIX}/bin/${EXECUTABLE} \
${DESTDIR}/etc/bash_completion.d/${EXECUTABLE}.sh \
${DESTDIR}/usr/lib/systemd/system/${EXECUTABLE}.service \
${DESTDIR}/usr/share/fish/vendor_completions.d/${EXECUTABLE}.fish \
${DESTDIR}/usr/share/licenses/${EXECUTABLE}/LICENSE
# CLEAN
# ==============================================================================
PHONY+=clean
clean:
rm --force ${BINDATA_TARGETS} || true
rm --force --recursive bin/ || true
# TEST
# ==============================================================================
PHONY+=test/unit
test/unit: bindata
test/unit:
go test -v -race -coverprofile=coverage.txt -covermode=atomic -timeout 600s -count=1 ./pkg/...
PHONY+=test/integration
test/integration:
go test -v -count=1 -timeout 600s ./it/...
PHONY+=test/coverage
test/coverage: test/unit
go tool cover -html=coverage.txt
# CONTAINER IMAGE STEPS
# GOLANGCI-LINT
# ==============================================================================
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} \
.
PHONY+=golangci-lint
golangci-lint:
golangci-lint run --concurrency=$(shell nproc)
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
# GOSEC
# ==============================================================================
# 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 \
--volume /var/run/docker.sock:/var/run/docker.sock \
docker.io/volkerraschek/build-image:latest \
make ${COMMAND} \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
VERSION=${VERSION:v%=%}
# 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+=gosec
gosec:
gosec $(shell pwd)/...
# PHONY
# ==============================================================================

View File

@ -0,0 +1,34 @@
package completion
import (
"os"
"github.com/spf13/cobra"
)
// InitCmd initialize all daemon subcommands
func InitCmd(cmd *cobra.Command) error {
completionCmd := &cobra.Command{
Use: "completion",
Short: "Generate completion script",
Example: "flucky completion [bash|zsh|fish]",
ValidArgs: []string{"bash", "zsh", "fish"},
Args: cobra.ExactValidArgs(1),
RunE: run,
}
cmd.AddCommand(completionCmd)
return nil
}
func run(cmd *cobra.Command, args []string) error {
switch args[0] {
case "bash":
return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true)
}
return nil
}

View File

@ -1,6 +1,7 @@
package imp
import (
"context"
"fmt"
"net/url"
@ -10,25 +11,15 @@ import (
"github.com/spf13/cobra"
)
var (
importSensors bool
importHumidities bool
importPressures bool
importTemperatures bool
)
func InitCmd(cmd *cobra.Command) error {
importCmd := &cobra.Command{
Use: "import",
Args: cobra.RangeArgs(1, 2),
Short: "Import data from passed URL",
RunE: importSources,
Example: `import sqlite3:///var/cache/flucky/sqlite3.db
import sqlite3:///var/cache/flucky/sqlite3.db postgres://user:password@host:port/database?sslmode=disable`,
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
@ -45,11 +36,6 @@ func importSources(cmd *cobra.Command, args []string) error {
return err
}
destURL, err := url.Parse(cnf.DSN)
if err != nil {
return err
}
logLevelString, err := cmd.Flags().GetString("loglevel")
if err != nil {
return err
@ -62,20 +48,39 @@ func importSources(cmd *cobra.Command, args []string) error {
flogger := logger.NewLogger(logLevel)
sourceURL, err := url.Parse(args[0])
var (
srcURL *url.URL
destURL *url.URL
)
srcURL, err = url.Parse(args[0])
if err != nil {
return fmt.Errorf("Failed to parse source url: %w", err)
return 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)
switch len(args) {
case 1:
destURL, err = url.Parse(cnf.DSN)
if err != nil {
return err
}
case 2:
destURL, err = url.Parse(args[1])
if err != nil {
return err
}
}
return nil
srcRepository, err := repository.New(srcURL, flogger)
if err != nil {
return err
}
destRepository, err := repository.New(destURL, flogger)
if err != nil {
return err
}
return destRepository.Import(context.Background(), srcRepository)
}

View File

@ -8,25 +8,23 @@ import (
"path/filepath"
"time"
"git.cryptic.systems/volker.raschek/flucky/cli/completion"
"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 {
func Execute(version string) error {
rootCmd := &cobra.Command{
Use: "flucky",
// Short: "flucky - operate with differen sensors, his values and remote servers to synchronize measured values",
Use: "flucky",
PersistentPreRunE: preRunError,
Version: version.String(),
Version: version,
}
defaultConfigFile, err := getDefaultConfigFile()
@ -38,6 +36,7 @@ func Execute(version *semver.Version) error {
rootCmd.PersistentFlags().String("loglevel", "info", "Set the Loglevel. Possible values: debug, info, warn, error, fatal")
subCommands := []func(cmd *cobra.Command) error{
completion.InitCmd,
daemon.InitCmd,
imp.InitCmd,
sensor.InitCmd,
@ -59,13 +58,11 @@ func Execute(version *semver.Version) error {
}
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
// Time must be truncated 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")

View File

@ -1,6 +1,7 @@
package sensor
import (
"context"
"fmt"
"net/url"
"os"
@ -97,7 +98,6 @@ flucky sensor rename f98b00ea-a9b2-4e00-924f-113859d0af2d outdoor`,
}
func addSensor(cmd *cobra.Command, args []string) error {
sensor := &types.Sensor{
ID: uuid.NewV4().String(),
Name: args[0],
@ -184,7 +184,7 @@ func addSensor(cmd *cobra.Command, args []string) error {
}
// add sensor entry to list
err = repo.AddSensors(sensor)
err = repo.AddSensors(context.Background())
if err != nil {
return err
}
@ -199,7 +199,6 @@ func addSensor(cmd *cobra.Command, args []string) error {
}
func disableSensor(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
@ -227,11 +226,24 @@ func disableSensor(cmd *cobra.Command, args []string) error {
return err
}
return repo.DisableSensorsByNames(args...)
s, err := repo.GetSensorsByNames(context.Background(), args...)
if err != nil {
return err
}
for i := range s {
s[i].Enabled = false
}
err = repo.UpdateSensors(context.Background(), s...)
if err != nil {
return err
}
return nil
}
func enableSensor(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
@ -259,11 +271,24 @@ func enableSensor(cmd *cobra.Command, args []string) error {
return err
}
return repo.EnableSensorsByNames(args...)
s, err := repo.GetSensorsByNames(context.Background(), args...)
if err != nil {
return err
}
for i := range s {
s[i].Enabled = true
}
err = repo.UpdateSensors(context.Background(), s...)
if err != nil {
return err
}
return nil
}
func listSensors(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
@ -292,7 +317,7 @@ func listSensors(cmd *cobra.Command, args []string) error {
}
// add sensor entry to list
sensors, err := repo.GetSensorsByDeviceID(cnf.DeviceID)
sensors, err := repo.GetSensorsByDeviceIDs(context.Background(), cnf.DeviceID)
if err != nil {
return err
}
@ -306,7 +331,6 @@ func listSensors(cmd *cobra.Command, args []string) error {
}
func removeSensor(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("No config file defined")
@ -334,11 +358,10 @@ func removeSensor(cmd *cobra.Command, args []string) error {
return err
}
return repo.RemoveSensorsByNames(args...)
return repo.RemoveSensorsByNames(context.Background(), 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")
@ -366,5 +389,19 @@ func renameSensor(cmd *cobra.Command, args []string) error {
return err
}
return repo.RenameSensors(args[0], args[1])
s, err := repo.GetSensorsByNames(context.Background(), args[0])
if err != nil {
return err
}
for i := range s {
s[i].Name = args[1]
}
err = repo.UpdateSensors(context.Background(), s...)
if err != nil {
return err
}
return nil
}

View File

@ -72,7 +72,9 @@ func readTemperature(cmd *cobra.Command, args []string) error {
return err
}
sensorTypes, err := repo.GetSensors()
ctx := context.Background()
sensorTypes, err := repo.GetSensors(ctx)
if err != nil {
return err
}
@ -95,26 +97,22 @@ func readTemperature(cmd *cobra.Command, args []string) error {
go func() {
for {
select {
case err, open := <-errorChannel:
if !open {
return
}
flogger.Error("%v", err)
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)
measuredValue, open := <-measuredValueChannel
if !open {
break LOOP
}
measuredValues = append(measuredValues, measuredValue)
}
err = cli.PrintMeasuredValues(measuredValues, os.Stdout)
@ -123,7 +121,7 @@ LOOP:
}
if persist {
err = repo.AddMeasuredValues(measuredValues...)
err = repo.AddMeasuredValues(ctx, measuredValues...)
if err != nil {
return err
}

9
go.mod
View File

@ -1,22 +1,19 @@
module git.cryptic.systems/volker.raschek/flucky
go 1.14
go 1.16
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-20191123181816-73a8a799d6bc
github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e
github.com/golang-migrate/migrate/v4 v4.14.1
github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574
github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16
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 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
)

58
go.sum
View File

@ -34,8 +34,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
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=
@ -47,11 +45,6 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX
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=
@ -60,7 +53,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
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=
@ -84,14 +76,11 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
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=
@ -106,19 +95,16 @@ github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc h1:HLRSIWzUGMLCq4ldt0W
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/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 h1:DBuH/9GFaWbDRa42qsut/hbQu+srAQ0rPWnUoiGX7CA=
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=
@ -149,14 +135,11 @@ github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PL
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-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574 h1:YEVMe8861NCZxTMzTI5BCobpwGpt1Md6D8v00jDc68w=
github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574/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=
@ -197,8 +180,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
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 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
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=
@ -221,10 +204,9 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
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 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
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=
@ -283,6 +265,8 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/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/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16 h1:sDjlyV4OzJYR2ZdLAAKZwV6N/CcX60xbGnPZzKJOZ50=
github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16/go.mod h1:lzH77MbyyahK7YO90wGRb65i9xLSoy2fD0dUSm23yMs=
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=
@ -310,11 +294,9 @@ github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq
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=
@ -337,6 +319,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
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 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
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=
@ -359,9 +342,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
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=
@ -405,41 +386,31 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
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/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/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=
@ -516,8 +487,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
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=
@ -540,8 +509,6 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R
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=
@ -575,7 +542,6 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w
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=
@ -602,9 +568,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
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=
@ -618,8 +582,8 @@ 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 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
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=
@ -739,7 +703,6 @@ google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3 h1:sg8vLDNIxFPHTch
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=
@ -751,7 +714,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
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=
@ -779,7 +741,6 @@ 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=
@ -789,6 +750,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
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 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
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=

View File

@ -2,10 +2,8 @@ package main
import (
"log"
"os"
"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"
@ -18,11 +16,8 @@ var (
)
func main() {
sversion, err := semver.NewVersion(version)
err := cli.Execute(version)
if err != nil {
log.Printf("The sematic versioning is invalid: %v", version)
os.Exit(1)
log.Println(err)
}
cli.Execute(sversion)
}

View File

@ -9,7 +9,6 @@ import (
)
func PrintMeasuredValues(measuredValues []*types.MeasuredValue, w io.Writer) error {
// declar tabwriter
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
@ -29,7 +28,6 @@ func PrintMeasuredValues(measuredValues []*types.MeasuredValue, w io.Writer) err
// 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)

View File

@ -31,20 +31,21 @@ func Encode(cnf *Config, w io.Writer) error {
// Read the configuration file
func Read(configFile string) (*Config, error) {
/* #nosec */
f, err := os.Open(configFile)
if err != nil {
return nil, fmt.Errorf("Can not open file %v: %v", configFile, err)
}
defer f.Close()
defer func() { _ = 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)
/* #nosec */
err := os.MkdirAll(configDir, 0775)
if err != nil {
return fmt.Errorf("Failed to create config directory %v: %v", configDir, err)
@ -55,7 +56,7 @@ func Write(cnf *Config, configFile string) error {
if err != nil {
return fmt.Errorf("Failed not create config file %v: %v", configFile, err)
}
defer f.Close()
defer func() { _ = f.Close() }()
return Encode(cnf, f)
}

View File

@ -6,6 +6,7 @@ import (
"net/url"
"os"
"os/signal"
"syscall"
"git.cryptic.systems/volker.raschek/flucky/pkg/config"
"git.cryptic.systems/volker.raschek/flucky/pkg/repository"
@ -15,7 +16,6 @@ import (
)
func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error {
// load data source name (dsn)
dsnURL, err := url.Parse(cnf.DSN)
if err != nil {
@ -27,8 +27,10 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
return err
}
ctx := context.Background()
// Add
repoDevice, err := repo.GetDevice(cnf.DeviceID)
repoDevice, err := repo.GetDeviceByID(ctx, cnf.DeviceID)
switch {
case err != nil:
return err
@ -39,7 +41,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
return err
}
err = repo.AddDevices(&types.Device{
err = repo.AddDevices(ctx, &types.Device{
ID: cnf.DeviceID,
Name: hostname,
})
@ -47,13 +49,13 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
return err
}
repoDevice, err = repo.GetDevice(cnf.DeviceID)
repoDevice, err = repo.GetDeviceByID(ctx, cnf.DeviceID)
if err != nil {
return err
}
}
repoSensors, err := repo.GetSensorsByDeviceID(repoDevice.ID)
repoSensors, err := repo.GetSensorsByDeviceIDs(ctx, repoDevice.ID)
switch {
case err != nil:
return err
@ -77,7 +79,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
}
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, os.Interrupt, os.Kill)
signal.Notify(interruptChannel, syscall.SIGTERM)
// Collection
parentCtx := context.Background()
@ -110,7 +112,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
if cap(measuredValues) == len(measuredValues) {
flogger.Debug("Flush cache with %v values", len(measuredValues))
err := repo.AddMeasuredValues(measuredValues...)
err := repo.AddMeasuredValues(ctx, measuredValues...)
if err != nil {
flogger.Error("%v", err)
}
@ -124,7 +126,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
flogger.Debug("Flush cache with %v remaining values", len(measuredValues))
err := repo.AddMeasuredValues(measuredValues...)
err := repo.AddMeasuredValues(ctx, measuredValues...)
if err != nil {
flogger.Error("%v", err)
}

View File

@ -1,14 +1,11 @@
package format
import (
"errors"
"math"
"time"
)
var (
errorPraseTime = errors.New("Can not parse time")
TimeFormat = "2006-01-02T15:04:05.999999Z07:00"
)
@ -19,5 +16,4 @@ func FormatedTime() time.Time {
t := time.Now()
l, _ := time.LoadLocation("Europe/Berlin")
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), int(math.Round(float64(t.Nanosecond())/1000000)*1000000), l)
}

View File

@ -1,134 +0,0 @@
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

@ -1,951 +0,0 @@
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

@ -1,939 +0,0 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"net/url"
sqlite3ddl "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/sqlite3/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"
)
// SQLite implementation
type SQLite 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 (sqlite *SQLite) Close() error {
return sqlite.dbo.Close()
}
// DeleteDevices from the database
func (sqlite *SQLite) DeleteDevices(ctx context.Context, deviceIDs ...string) error {
queryFile := "deleteDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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 (sqlite *SQLite) DeleteSensors(ctx context.Context, sensorIDs ...string) error {
queryFile := "deleteSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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()
}
func (sqlite *SQLite) ExistDevice(ctx context.Context, deviceID string) (bool, error) {
queryFile := "existDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return false, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return false, fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
if err != nil {
return false, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query()
if err != nil {
return false, fmt.Errorf("Failed to query statement: %v", err)
}
defer rows.Close()
var t bool
for rows.Next() {
rows.Scan(&t)
}
return t, nil
}
func (sqlite *SQLite) ExistDevices(ctx context.Context, deviceIDs ...string) (map[string]bool, error) {
return nil, nil
}
// InsertDevices into the database
func (sqlite *SQLite) InsertDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "insertDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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 _, device := range devices {
_, 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 tx.Commit()
}
// InsertMeasuredValues into the database
func (sqlite *SQLite) 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 := sqlite.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 := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-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 {
_, 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()
}
// InsertOrUpdateMeasuredValues into the database
func (sqlite *SQLite) 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 := sqlite.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 := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-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 {
_, 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()
}
// InsertOrUpdateDevices into the database
func (sqlite *SQLite) InsertOrUpdateDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "insertOrUpdateDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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 _, device := range devices {
_, 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 tx.Commit()
}
// InsertOrUpdateSensors into the database
func (sqlite *SQLite) InsertOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "insertOrUpdateSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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 {
_, 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 (sqlite *SQLite) InsertSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "insertSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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 {
_, 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 (sqlite *SQLite) Migrate(ctx context.Context) error {
assetSource := bindata.Resource(sqlite3ddl.AssetNames(), func(query string) ([]byte, error) {
return sqlite3ddl.Asset(query)
})
sourceDriver, err := bindata.WithInstance(assetSource)
if err != nil {
return err
}
m, err := migrate.NewWithSourceInstance("bindata", sourceDriver, sqlite.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 (sqlite *SQLite) SelectDevice(ctx context.Context, id string) (*types.Device, error) {
queryFile := "selectDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
devices, err := sqlite.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 (sqlite *SQLite) SelectDevices(ctx context.Context) ([]*types.Device, error) {
queryFile := "selectDevices.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
devices, err := sqlite.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 (sqlite *SQLite) 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)
}
defer rows.Close()
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 (sqlite *SQLite) SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectHumidity.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.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 (sqlite *SQLite) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectHumidities.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.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 (sqlite *SQLite) 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
}
defer rows.Close()
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 (sqlite *SQLite) SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectPressure.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.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 (sqlite *SQLite) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectPressures.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.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 (sqlite *SQLite) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) {
queryFile := "selectSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
sensors, err := sqlite.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 (sqlite *SQLite) SelectSensors(ctx context.Context) ([]*types.Sensor, error) {
queryFile := "selectSensors.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
sensors, err := sqlite.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 (sqlite *SQLite) 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)
}
defer rows.Close()
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 (sqlite *SQLite) SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectTemperature.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.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 (sqlite *SQLite) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectTemperatures.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.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 (sqlite *SQLite) UpdateDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "updateDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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 {
_, 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 (sqlite *SQLite) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "updateSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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 {
_, 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()
}

1102
pkg/repository/postgres.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
DELETE FROM devices
WHERE device_name = $1;

View File

@ -0,0 +1,2 @@
DELETE FROM sensors
WHERE sensor_name = $1;

View File

@ -0,0 +1,9 @@
SELECT
device_id,
device_name,
device_location,
creation_date,
update_date
FROM
devices
WHERE device_name = $1;

View File

@ -0,0 +1,18 @@
SELECT
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date,
update_date
FROM
sensors
WHERE
device_id = $1;

View File

@ -0,0 +1,18 @@
SELECT
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date,
update_date
FROM
sensors
WHERE
sensor_model = $1;

View File

@ -0,0 +1,18 @@
SELECT
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date,
update_date
FROM
sensors
WHERE
sensor_name = $1;

View File

@ -0,0 +1,100 @@
package postgres
import "embed"
var (
DDLAssetPath = "ddl"
//go:embed ddl/*.sql
DDLAssets embed.FS
//go:embed dml/deleteDeviceByID.sql
DeleteDeviceByIDSQL string
//go:embed dml/deleteDeviceByName.sql
DeleteDeviceByNameSQL string
//go:embed dml/deleteSensorByID.sql
DeleteSensorByIDSQL string
//go:embed dml/deleteSensorByName.sql
DeleteSensorByNameSQL string
//go:embed dml/insertDevice.sql
InsertDeviceSQL string
//go:embed dml/insertHumidity.sql
InsertHumiditySQL string
//go:embed dml/insertOrUpdateDevice.sql
InsertOrUpdateDeviceSQL string
//go:embed dml/insertOrUpdateHumidity.sql
InsertOrUpdateHumiditySQL string
//go:embed dml/insertOrUpdatePressure.sql
InsertOrUpdatePressureSQL string
//go:embed dml/insertOrUpdateSensor.sql
InsertOrUpdateSensorSQL string
//go:embed dml/insertOrUpdateTemperature.sql
InsertOrUpdateTemperatureSQL string
//go:embed dml/insertPressure.sql
InsertPressureSQL string
//go:embed dml/insertSensor.sql
InsertSensorSQL string
//go:embed dml/insertTemperature.sql
InsertTemperatureSQL string
//go:embed dml/selectDeviceByID.sql
SelectDeviceByIDSQL string
//go:embed dml/selectDeviceByName.sql
SelectDeviceByNameSQL string
//go:embed dml/selectDevices.sql
SelectDevicesSQL string
//go:embed dml/selectHumidities.sql
SelectHumiditiesSQL string
//go:embed dml/selectHumidityByID.sql
SelectHumidityByIDSQL string
//go:embed dml/selectPressureByID.sql
SelectPressureByIDSQL string
//go:embed dml/selectPressures.sql
SelectPressuresSQL string
//go:embed dml/selectSensorByID.sql
SelectSensorByIDSQL string
//go:embed dml/selectSensors.sql
SelectSensorsSQL string
//go:embed dml/selectSensorsByDeviceID.sql
SelectSensorsByDeviceIDSQL string
//go:embed dml/selectSensorsByModel.sql
SelectSensorsByModelSQL string
//go:embed dml/selectSensorsByName.sql
SelectSensorsByNameSQL string
//go:embed dml/selectTemperatureByID.sql
SelectTemperatureByIDSQL string
//go:embed dml/selectTemperatures.sql
SelectTemperaturesSQL string
//go:embed dml/updateDevice.sql
UpdateDeviceSQL string
//go:embed dml/updateSensor.sql
UpdateSensorSQL string
)

View File

@ -4,339 +4,81 @@ import (
"context"
"fmt"
"net/url"
"strings"
"git.cryptic.systems/volker.raschek/flucky/pkg/repository/db"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
)
// Repository represent a repository where all devices, sensors and measured
// values are stored.
type Repository struct {
database db.Database
type Repository interface {
AddDevices(ctx context.Context, devices ...*types.Device) error
AddMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error
AddOrUpdateDevices(ctx context.Context, devices ...*types.Device) error
AddOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error
AddOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error
AddSensors(ctx context.Context, sensors ...*types.Sensor) error
Close() error
GetDeviceByID(ctx context.Context, deviceID string) (*types.Device, error)
GetDeviceByName(ctx context.Context, name string) (*types.Device, error)
GetDevices(ctx context.Context) ([]*types.Device, error)
GetHumidities(ctx context.Context) ([]*types.MeasuredValue, error)
GetHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error)
GetPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
GetPressures(ctx context.Context) ([]*types.MeasuredValue, error)
GetSensorByID(ctx context.Context, sensorID string) (*types.Sensor, error)
GetSensorsByDeviceIDs(ctx context.Context, deviceIDs ...string) ([]*types.Sensor, error)
GetSensorsByModels(ctx context.Context, sensorModels ...string) ([]*types.Sensor, error)
GetSensorsByNames(ctx context.Context, sensorModels ...string) ([]*types.Sensor, error)
GetSensors(ctx context.Context) ([]*types.Sensor, error)
GetTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
GetTemperatures(ctx context.Context) ([]*types.MeasuredValue, error)
Import(ctx context.Context, src Repository) error
Migrate(ctx context.Context) error
RemoveDevicesByIDs(ctx context.Context, deviceIDs ...string) error
RemoveDevicesByNames(ctx context.Context, names ...string) error
RemoveSensorsByIDs(ctx context.Context, sensorIDs ...string) error
RemoveSensorsByNames(ctx context.Context, names ...string) error
UpdateDevices(ctx context.Context, devices ...*types.Device) error
UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error
}
// AddDevices to the repository
func (repo *Repository) AddDevices(devices ...*types.Device) error {
return repo.database.InsertDevices(context.Background(), devices...)
}
// AddMeasuredValues to the repository
func (repo *Repository) AddMeasuredValues(measuredValues ...*types.MeasuredValue) error {
return repo.database.InsertMeasuredValues(context.Background(), measuredValues...)
}
// AddOrUpdateDevices to the repository
func (repo *Repository) AddOrUpdateDevices(devices ...*types.Device) error {
return repo.database.InsertOrUpdateDevices(context.Background(), devices...)
}
// AddOrUpdateMeasuredValues to the repository
func (repo *Repository) AddOrUpdateMeasuredValues(measuredValues ...*types.MeasuredValue) error {
return repo.database.InsertOrUpdateMeasuredValues(context.Background(), measuredValues...)
}
// AddOrUpdateSensors to the repository
func (repo *Repository) AddOrUpdateSensors(sensors ...*types.Sensor) error {
return repo.database.InsertOrUpdateSensors(context.Background(), sensors...)
}
// AddSensors to the repository
func (repo *Repository) AddSensors(sensors ...*types.Sensor) error {
return repo.database.InsertSensors(context.Background(), sensors...)
}
// Close closes the repository and prevents new queries from starting. Close
// then waits for all queries that have started processing on the server to
// finish.
func (repo *Repository) Close() error {
return repo.database.Close()
}
// DisableSensorsByNames disable all sensors which match bei their name
func (repo *Repository) DisableSensorsByNames(sensorNames ...string) error {
sensors, err := repo.GetSensors()
if err != nil {
return err
}
matchedSensors := make([]*types.Sensor, 0)
for _, sensor := range sensors {
for _, sensorName := range sensorNames {
if strings.Compare(sensor.Name, sensorName) == 0 {
sensor.Enabled = false
matchedSensors = append(matchedSensors, sensor)
}
// New returns a new database backend interface
func New(databaseURL *url.URL, flogger logger.Logger) (Repository, error) {
switch databaseURL.Scheme {
case "postgres":
repo, err := NewPostgres(PostgresOpts{
DatabaseURL: databaseURL,
Logger: flogger,
})
if err != nil {
return nil, err
}
}
return repo.UpdateSensors(matchedSensors...)
}
// EnableSensorsByNames enable all sensors which match bei their name
func (repo *Repository) EnableSensorsByNames(sensorNames ...string) error {
sensors, err := repo.GetSensors()
if err != nil {
return err
}
matchedSensors := make([]*types.Sensor, 0)
for _, sensor := range sensors {
for _, sensorName := range sensorNames {
if strings.Compare(sensor.Name, sensorName) == 0 {
sensor.Enabled = true
matchedSensors = append(matchedSensors, sensor)
}
// Initialize database scheme if not exists
err = repo.Migrate(context.Background())
if err != nil {
return nil, err
}
}
return repo.UpdateSensors(matchedSensors...)
}
return repo, nil
// GetDevice returns a device by his id. If no device has been found, the
// function returns nil.
func (repo *Repository) GetDevice(deviceID string) (*types.Device, error) {
return repo.database.SelectDevice(context.Background(), deviceID)
}
// GetDevices returns all devices. If no devices has been found, the function
// returns nil.
func (repo *Repository) GetDevices() ([]*types.Device, error) {
return repo.database.SelectDevices(context.Background())
}
// GetHumidity returns a humidity value by id
func (repo *Repository) GetHumidity(humidityID string) (*types.MeasuredValue, error) {
return repo.database.SelectHumidity(context.Background(), humidityID)
}
// GetPressure returns a pressure value by id
func (repo *Repository) GetPressure(pressureID string) (*types.MeasuredValue, error) {
return repo.database.SelectPressure(context.Background(), pressureID)
}
// GetSensor returns a sensor by his id. If no sensor has been found, the
// function returns nil.
func (repo *Repository) GetSensor(sensorID string) (*types.Sensor, error) {
return repo.database.SelectSensor(context.Background(), sensorID)
}
// GetSensors returns all sensors. If no sensors has been found, the function
// returns nil.
func (repo *Repository) GetSensors(models ...string) ([]*types.Sensor, error) {
sensors, err := repo.database.SelectSensors(context.Background())
switch {
case err != nil:
return nil, err
case len(models) > 0:
cachedSensors := make([]*types.Sensor, 0)
LOOP:
for i := range sensors {
for j := range models {
if strings.ToLower(sensors[i].Model) == strings.ToLower(models[j]) {
cachedSensors = append(cachedSensors, sensors[i])
continue LOOP
}
}
case "sqlite3":
repo, err := NewSQLite(SQLiteOpts{
DatabaseURL: databaseURL,
Logger: flogger,
})
if err != nil {
return nil, err
}
return cachedSensors, nil
case len(models) <= 0:
fallthrough
// Initialize database scheme if not exists
err = repo.Migrate(context.Background())
if err != nil {
return nil, err
}
return repo, nil
default:
return sensors, err
return nil, fmt.Errorf("Unsupported repository scheme: %v", databaseURL.Scheme)
}
}
// GetSensorsByDeviceID returns all sensors by a device id. If no sensor has
// been found by the id, the function returns nil.
func (repo *Repository) GetSensorsByDeviceID(deviceID string) ([]*types.Sensor, error) {
cachedSensors := make([]*types.Sensor, 0)
sensors, err := repo.GetSensors()
if err != nil {
return nil, err
}
for _, sensor := range sensors {
if strings.Compare(sensor.DeviceID, deviceID) == 0 {
cachedSensors = append(cachedSensors, sensor)
}
}
return cachedSensors, nil
}
// GetTemperature returns a temperature value by id
func (repo *Repository) GetTemperature(pressureID string) (*types.MeasuredValue, error) {
return repo.database.SelectTemperature(context.Background(), pressureID)
}
// RemoveDevices removes devices by their ids from the repository. Additional
// all sensors and measured values, which are in relation with the device
// respectively the sensors will also be deleted.
func (repo *Repository) RemoveDevices(deviceIDs ...string) error {
return repo.database.DeleteDevices(context.Background(), deviceIDs...)
}
// RenameSensors all sensors which match by their current name to the new name
func (repo *Repository) RenameSensors(oldName string, newName string) error {
sensors, err := repo.GetSensors()
if err != nil {
return err
}
matchedSensors := make([]*types.Sensor, 0)
for _, sensor := range sensors {
if strings.Compare(sensor.Name, oldName) == 0 {
sensor.Name = newName
matchedSensors = append(matchedSensors, sensor)
}
}
return repo.UpdateSensors(matchedSensors...)
}
// RemoveSensors removes sensors by their ids from the repository. Additional
// all measured values, which are in relation with the sensor will also be
// deleted.
func (repo *Repository) RemoveSensors(sensorIDs ...string) error {
return repo.database.DeleteSensors(context.Background(), sensorIDs...)
}
// RemoveSensorsByNames removes all sensors which match bei their name
func (repo *Repository) RemoveSensorsByNames(sensorNames ...string) error {
sensors, err := repo.GetSensors()
if err != nil {
return err
}
matchedSensorIDs := make([]string, 0)
for _, sensor := range sensors {
for _, sensorName := range sensorNames {
if strings.Compare(sensor.Name, sensorName) == 0 {
matchedSensorIDs = append(matchedSensorIDs, sensor.ID)
}
}
}
return repo.RemoveSensors(matchedSensorIDs...)
}
// UpdateDevices update devices which are stored into the repository by their
// id. The id of the device can not be updated.
func (repo *Repository) UpdateDevices(devices ...*types.Device) error {
return repo.database.UpdateDevices(context.Background(), devices...)
}
// UpdateSensors update sensors which are stored into the repository by their
// id. The id of the sensor can not be updated.
func (repo *Repository) UpdateSensors(sensors ...*types.Sensor) error {
return repo.database.UpdateSensors(context.Background(), sensors...)
}
// New returns a new repository based on the data source name (dsn)
func New(dsnURL *url.URL, flogger logger.Logger) (*Repository, error) {
database, err := db.New(dsnURL, flogger)
if err != nil {
return nil, err
}
return &Repository{
database: database,
}, nil
}
type OptImport struct {
Sensors bool
Humidities bool
Pressures bool
Temperatures bool
}
func Import(sourceDSNURL *url.URL, destDNSURL *url.URL, flogger logger.Logger, optImport OptImport) error {
importMap := map[string]bool{
"humidities": optImport.Humidities,
"pressures": optImport.Pressures,
"temperatures": optImport.Temperatures,
}
// enable sensors if one measured value is enabled
if !optImport.Sensors {
for key, value := range importMap {
if value {
flogger.Info("Enable import option sensors. It's required as foreign key for %v", key)
optImport.Sensors = true
}
}
}
// Initialize source repository
sourceRepo, err := New(sourceDSNURL, flogger)
if err != nil {
return fmt.Errorf("Failed to open the source repo: %w", err)
}
defer sourceRepo.Close()
// Initialize destination repository
destRepo, err := New(destDNSURL, flogger)
if err != nil {
return fmt.Errorf("Failed to open the destination repo: %w", err)
}
defer destRepo.Close()
// AddOrUpdate: Devices
ctx := context.Background()
devices, err := sourceRepo.database.SelectDevices(ctx)
if err != nil {
return fmt.Errorf("Failed to fetch devices from source repo: %w", err)
}
flogger.Debug("Found %v devices", len(devices))
for i := range devices {
err := destRepo.AddOrUpdateDevices(devices[i])
if err != nil {
return fmt.Errorf("Failed to add or update device %v into dest repo: %w", devices[i].Name, err)
}
}
// AddOrUpdate: Sensors
if optImport.Sensors {
sensors, err := sourceRepo.database.SelectSensors(ctx)
if err != nil {
return fmt.Errorf("Failed to fetch sensors from source repo: %w", err)
}
flogger.Debug("Found %v sensors", len(sensors))
for i := range sensors {
err := destRepo.AddOrUpdateSensors(sensors[i])
if err != nil {
return fmt.Errorf("Failed to add or update sensor %v into dest repo: %w", sensors[i].Name, err)
}
}
}
for key, f := range map[string]func(context.Context) ([]*types.MeasuredValue, error){
"humidities": sourceRepo.database.SelectHumidities,
"pressures": sourceRepo.database.SelectPressures,
"temperatures": sourceRepo.database.SelectTemperatures,
} {
if importMap[key] {
measuredValues, err := f(ctx)
if err != nil {
return fmt.Errorf("Failed to select %v from source repo: %w", key, err)
}
flogger.Debug("Found %v %v values", len(measuredValues), key)
err = destRepo.AddOrUpdateMeasuredValues(measuredValues...)
if err != nil {
return fmt.Errorf("Failed to add or update %v into dest repo: %w", key, err)
}
}
}
return nil
}

View File

@ -31,15 +31,13 @@ func TestPostgresBackend(t *testing.T) {
require.NoError(err)
rand.Seed(time.Now().Unix())
postgresHostPort := rand.Intn(10024-1024) + 1024
postgresDBPasswort := "postgres"
postgresContainerID, err := dockerClient.NewBuilder("postgres:13-alpine").
Port(fmt.Sprintf("%v:5432/tcp", postgresHostPort)).
Pull().
AddEnv("PGTZ", "Europe/Berlin").
AddEnv("POSTGRES_PASSWORD", postgresDBPasswort).
AddEnv("TZ", "Europe/Berlin").
Mount("/etc/localtime", "/etc/localtime").
Pull().
Start(context.Background())
cleanup := func() {
dockerClient.ContainerRemoveByIDs(context.Background(), postgresContainerID)
@ -49,8 +47,12 @@ func TestPostgresBackend(t *testing.T) {
time.Sleep(time.Second * 10)
// inspect container to get his container ip
cjson, err := dockerClient.ContainerInspect(context.Background(), postgresContainerID)
require.NoError(err)
// postgres://[user]:[password]@[host]:[port]/[path]?[query]
dsnURL, err := url.Parse(fmt.Sprintf("postgres://postgres:%v@127.0.0.1:%v?sslmode=disable", postgresDBPasswort, postgresHostPort))
dsnURL, err := url.Parse(fmt.Sprintf("postgres://postgres:%v@%v:5432?sslmode=disable", postgresDBPasswort, cjson.NetworkSettings.IPAddress))
require.NoError(err)
repo, err := repository.New(dsnURL, logger.NewLogger(logger.LogLevelDebug))
@ -75,7 +77,8 @@ func TestSQLiteBackend(t *testing.T) {
testBackend(t, repo)
}
func testBackend(t *testing.T, repo *repository.Repository) {
func testBackend(t *testing.T, repo repository.Repository) {
ctx := context.Background()
require := require.New(t)
location := uuid.NewV4().String()
expectedDevices := []*types.Device{
@ -94,33 +97,50 @@ func testBackend(t *testing.T, repo *repository.Repository) {
}
// Test: AddDevice
err := repo.AddDevices(expectedDevices...)
err := repo.AddDevices(ctx, expectedDevices...)
require.NoError(err)
// Test: GetDevices
devices, err := repo.GetDevices()
devices, err := repo.GetDevices(ctx)
require.NoError(err)
require.Len(devices, len(expectedDevices))
require.JSONEq(jsonEncoder(expectedDevices), jsonEncoder(devices))
// Test: GetDevice
device, err := repo.GetDevice(expectedDevices[0].ID)
// Test: GetDeviceByID
device, err := repo.GetDeviceByID(ctx, expectedDevices[0].ID)
require.NoError(err)
require.JSONEq(jsonEncoder(expectedDevices[0]), jsonEncoder(device))
// Test: RemoveDevice
err = repo.RemoveDevices(expectedDevices[0].ID)
// Test: GetDeviceByName
device, err = repo.GetDeviceByName(ctx, expectedDevices[0].Name)
require.NoError(err)
require.JSONEq(jsonEncoder(expectedDevices[0]), jsonEncoder(device))
// Test: RemoveDevicesByIDs
err = repo.RemoveDevicesByIDs(ctx, expectedDevices[0].ID)
require.NoError(err)
devices, err = repo.GetDevices()
devices, err = repo.GetDevices(ctx)
require.NoError(err)
require.Len(devices, 1)
device, err = repo.GetDevice(expectedDevices[0].ID)
device, err = repo.GetDeviceByID(ctx, expectedDevices[0].ID)
require.NoError(err)
require.Nil(device)
err = repo.AddDevices(expectedDevices[0])
// Test: RemoveDevicesByNames
err = repo.RemoveDevicesByNames(ctx, expectedDevices[0].Name)
require.NoError(err)
devices, err = repo.GetDevices(ctx)
require.NoError(err)
require.Len(devices, 1)
device, err = repo.GetDeviceByID(ctx, expectedDevices[0].ID)
require.NoError(err)
require.Nil(device)
err = repo.AddDevices(ctx, expectedDevices[0])
require.NoError(err)
// Test: Update Devices
@ -132,10 +152,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
CreationDate: *timeNow(require),
}
err = repo.UpdateDevices(expectedDevice)
err = repo.UpdateDevices(ctx, expectedDevice)
require.NoError(err)
device, err = repo.GetDevice(expectedDevice.ID)
device, err = repo.GetDeviceByID(ctx, expectedDevice.ID)
require.NoError(err)
require.NotEmpty(device)
require.Equal(expectedDevice.ID, device.ID)
@ -150,12 +170,12 @@ func testBackend(t *testing.T, repo *repository.Repository) {
Location: &location,
CreationDate: *timeNow(require),
}
err = repo.AddOrUpdateDevices(expectedDevice)
err = repo.AddOrUpdateDevices(ctx, expectedDevice)
require.NoError(err)
// time.Sleep(time.Minute * 10)
device, err = repo.GetDevice(expectedDevice.ID)
device, err = repo.GetDeviceByID(ctx, expectedDevice.ID)
require.NoError(err)
require.NotEmpty(device)
require.Equal(expectedDevice.ID, device.ID)
@ -169,10 +189,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
Location: &location,
CreationDate: *timeNow(require),
}
err = repo.AddOrUpdateDevices(expectedDevice)
err = repo.AddOrUpdateDevices(ctx, expectedDevice)
require.NoError(err)
device, err = repo.GetDevice(expectedDevice.ID)
device, err = repo.GetDeviceByID(ctx, expectedDevice.ID)
require.NoError(err)
require.NotEmpty(device)
require.Equal(expectedDevice.ID, device.ID)
@ -222,97 +242,80 @@ func testBackend(t *testing.T, repo *repository.Repository) {
)
// Test: AddSensors
err = repo.AddSensors(expectedSensors...)
err = repo.AddSensors(ctx, expectedSensors...)
require.NoError(err)
// Test: GetSensors
sensors, err := repo.GetSensors()
sensors, err := repo.GetSensors(ctx)
require.NoError(err)
require.Len(sensors, len(expectedSensors))
sensors, err = repo.GetSensors("BME280")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[2]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensors("DS18B20")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[1]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensors("DHT11")
// Test: GetSensorsByNames
sensors, err = repo.GetSensorsByNames(ctx, "01fbdbe9-cebf-42ed-8065-bf4882ccf76b")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensors("DHT11", "DS18B20")
// Test: GetSensorsByModels
sensors, err = repo.GetSensorsByModels(ctx, "BME280")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[2]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensorsByModels(ctx, "DS18B20")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[1]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensorsByModels(ctx, "DHT11")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensorsByModels(ctx, "DHT11", "DS18B20")
require.NoError(err)
require.Len(sensors, 2)
require.JSONEq(jsonEncoder(expectedSensors[0:2]), jsonEncoder(sensors[0:2]))
// Test: GetSensor
sensor, err := repo.GetSensor(expectedSensors[0].ID)
// Test: GetSensorByID
sensor, err := repo.GetSensorByID(ctx, expectedSensors[0].ID)
require.NoError(err)
require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensor))
// Test: GetSensorsByDeviceID
sensors, err = repo.GetSensorsByDeviceID("ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f")
sensors, err = repo.GetSensorsByDeviceIDs(ctx, "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f")
require.NoError(err)
require.Len(sensors, 2)
require.JSONEq(jsonEncoder(expectedSensors[0:2]), jsonEncoder(sensors))
// Test: RemoveSensors
err = repo.RemoveSensors(expectedSensors[0].ID)
// Test: RemoveSensorsByIDs
err = repo.RemoveSensorsByIDs(ctx, expectedSensors[0].ID)
require.NoError(err)
sensors, err = repo.GetSensors()
sensors, err = repo.GetSensors(ctx)
require.NoError(err)
require.Len(sensors, 2)
sensors, err = repo.GetSensorsByDeviceID("ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f")
sensors, err = repo.GetSensorsByDeviceIDs(ctx, "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f")
require.NoError(err)
require.Len(sensors, 1)
sensor, err = repo.GetSensor(expectedSensors[0].ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensors[0].ID)
require.NoError(err)
require.Nil(sensor)
// Test: RemoveSensorsByNames
err = repo.RemoveSensorsByNames(expectedSensors[1].Name)
err = repo.RemoveSensorsByNames(ctx, expectedSensors[1].Name)
require.NoError(err)
sensors, err = repo.GetSensors()
sensors, err = repo.GetSensors(ctx)
require.NoError(err)
require.Len(sensors, 1)
sensor, err = repo.GetSensor(expectedSensors[1].ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensors[1].ID)
require.NoError(err)
require.Nil(sensor)
// Test: RenameSensor
err = repo.RenameSensors(expectedSensors[2].Name, "Hello")
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensors[2].ID)
require.Equal("Hello", sensor.Name)
require.NotNil(sensor)
// Test: DisableSensorsByNames
err = repo.DisableSensorsByNames("Hello")
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensors[2].ID)
require.False(sensor.Enabled)
require.NotNil(sensor)
// Test: EnableSensorsByName
err = repo.EnableSensorsByNames("Hello")
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensors[2].ID)
require.True(sensor.Enabled)
require.NotNil(sensor)
// Test: UpdateSensors
expectedSensor := &types.Sensor{
ID: "8c74397f-8e60-4c9d-960d-3197747cef9a",
@ -327,10 +330,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
CreationDate: *timeNow(require),
}
err = repo.UpdateSensors(expectedSensor)
err = repo.UpdateSensors(ctx, expectedSensor)
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensor.ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensor.ID)
require.NoError(err)
require.NotNil(sensor)
// require.JSONEq(jsonEncoder(expectedSensor), jsonEncoder(sensor))
@ -349,10 +352,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
CreationDate: *timeNow(require),
}
err = repo.AddOrUpdateSensors(expectedSensor)
err = repo.AddOrUpdateSensors(ctx, expectedSensor)
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensor.ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensor.ID)
require.NoError(err)
require.NotEmpty(sensor)
require.Equal(expectedSensor.ID, sensor.ID)
@ -378,10 +381,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
CreationDate: *timeNow(require),
}
err = repo.AddOrUpdateSensors(expectedSensor)
err = repo.AddOrUpdateSensors(ctx, expectedSensor)
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensor.ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensor.ID)
require.NoError(err)
require.NotEmpty(sensor)
require.Equal(expectedSensor.ID, sensor.ID)
@ -427,7 +430,7 @@ func testBackend(t *testing.T, repo *repository.Repository) {
)
// Test: AddMeasuredValues
err = repo.AddMeasuredValues(expectedMeasuredValues...)
err = repo.AddMeasuredValues(ctx, expectedMeasuredValues...)
require.NoError(err)
for i := range expectedMeasuredValues {
@ -439,15 +442,15 @@ func testBackend(t *testing.T, repo *repository.Repository) {
switch expectedMeasuredValues[i].ValueType {
case types.Humidity:
measuredValue, err = repo.GetHumidity(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetHumidityByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
case types.Pressure:
measuredValue, err = repo.GetPressure(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetPressureByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
case types.Temperature:
measuredValue, err = repo.GetTemperature(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetTemperatureByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
}
@ -489,7 +492,7 @@ func testBackend(t *testing.T, repo *repository.Repository) {
},
}
err = repo.AddOrUpdateMeasuredValues(expectedMeasuredValues...)
err = repo.AddOrUpdateMeasuredValues(ctx, expectedMeasuredValues...)
require.NoError(err)
for i := range expectedMeasuredValues {
@ -501,15 +504,15 @@ func testBackend(t *testing.T, repo *repository.Repository) {
switch expectedMeasuredValues[i].ValueType {
case types.Humidity:
measuredValue, err = repo.GetHumidity(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetHumidityByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
case types.Pressure:
measuredValue, err = repo.GetPressure(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetPressureByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
case types.Temperature:
measuredValue, err = repo.GetTemperature(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetTemperatureByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
}

1179
pkg/repository/sqlite.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
DELETE FROM devices
WHERE device_name = $1;

View File

@ -0,0 +1,2 @@
DELETE FROM sensors
WHERE sensor_name = $1;

View File

@ -0,0 +1,9 @@
SELECT
device_id,
device_name,
device_location,
creation_date,
update_date
FROM
devices
WHERE device_name = $1;

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