Skip to content
This repository was archived by the owner on Jun 12, 2024. It is now read-only.

Commit 8af741c

Browse files
authored
Add more packages: crypto, config, data/db, validation (#11)
**What** - Added `crypto` for encryption helpers - Added `config` package with common config section implementations - Added `data/db` that contains all kinds of utils working with db and its types - Added `validation` with common validation rules - Improved `testing` adding bytes helper - Move `pkg/data/db` to `pkg/db` - Fix floppy test
1 parent 813bcea commit 8af741c

39 files changed

+1350
-360
lines changed

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
module github.com/contiamo/go-base
22

33
require (
4+
github.com/Masterminds/squirrel v1.1.0
5+
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
46
github.com/bakins/net-http-recover v0.0.0-20141007104922-6cba69d01459
7+
github.com/cenkalti/backoff v2.2.1+incompatible
58
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
9+
github.com/dgrijalva/jwt-go v3.2.0+incompatible
610
github.com/go-chi/chi v4.0.2+incompatible
711
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible
812
github.com/golang/protobuf v1.3.2
@@ -16,6 +20,7 @@ require (
1620
github.com/opentracing/opentracing-go v1.1.0
1721
github.com/pkg/errors v0.8.1
1822
github.com/prometheus/client_golang v1.1.0
23+
github.com/robfig/cron v1.2.0
1924
github.com/rs/cors v1.7.0
2025
github.com/satori/go.uuid v1.2.0
2126
github.com/sirupsen/logrus v1.4.2

go.sum

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
22
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3+
github.com/Masterminds/squirrel v1.1.0 h1:baP1qLdoQCeTw3ifCdOq2dkYc6vGcmRdaociKLbEJXs=
4+
github.com/Masterminds/squirrel v1.1.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA=
35
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
46
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
7+
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
8+
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
59
github.com/bakins/net-http-recover v0.0.0-20141007104922-6cba69d01459 h1:EoeZjn5IKppnD1q/+SXExqb0DxBVEQMBHTVpHFZN258=
610
github.com/bakins/net-http-recover v0.0.0-20141007104922-6cba69d01459/go.mod h1:QpFZUHntFabd1Oro3/Exapv58wgWN3Sej2NbOVYg/1E=
711
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
812
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
913
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
1014
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
15+
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
16+
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
1117
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
1218
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
1319
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
1420
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1521
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1622
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23+
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
24+
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
1725
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
1826
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
1927
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@@ -51,6 +59,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
5159
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
5260
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
5361
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
62+
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
63+
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
64+
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
65+
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
5466
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
5567
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
5668
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@@ -83,6 +95,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
8395
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
8496
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
8597
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
98+
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
99+
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
86100
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
87101
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
88102
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=

pkg/config/authorization.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package config
2+
3+
// Authorization contains all the authorization-related parameters
4+
type Authorization struct {
5+
// HeaderName is the name of the header where the authorization middleware is supposed
6+
// to be looking for a JWT token
7+
HeaderName string `json:"headerName"`
8+
}

pkg/config/database.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"strings"
7+
8+
"github.com/pkg/errors"
9+
)
10+
11+
var defaultPorts = map[string]uint32{
12+
"postgres": 5432,
13+
}
14+
15+
// Database contains all the configuration parameters for a database
16+
type Database struct {
17+
// Host of the database server
18+
Host string `json:"host"`
19+
// Port of the database server, if empty, we will attempt to parse the port from the host value,
20+
// if Host does not have a port value, then we will use the default value from the db driver
21+
Port uint32 `json:"port"`
22+
// Name is the name of the database on the host
23+
Name string `json:"name"`
24+
// Username to access the database
25+
Username string `json:"username"`
26+
// PasswordPath is a path to the file where the password is stored
27+
PasswordPath string `json:"passwordPath"`
28+
// PoolSize is the max number of concurrent connections to the database,
29+
// <=0 is unlimited
30+
PoolSize int `json:"poolSize"`
31+
// DriverName is the database driver name e.g. postgres
32+
DriverName string `json:"driverName"`
33+
}
34+
35+
// GetPassword gets the database password from PasswordPath
36+
func (cfg *Database) GetPassword() (string, error) {
37+
if cfg.PasswordPath == "" {
38+
return "", nil
39+
}
40+
passwordBytes, err := ioutil.ReadFile(cfg.PasswordPath)
41+
if err != nil {
42+
return "", errors.Wrapf(err, "can not read the database password file `%s`", cfg.PasswordPath)
43+
}
44+
45+
return strings.TrimSpace(string(passwordBytes)), nil
46+
}
47+
48+
// GetHost returns the host name of the underlying db
49+
func (cfg *Database) GetHost() string {
50+
if cfg.Host != "" {
51+
return cfg.Host
52+
}
53+
54+
return "localhost"
55+
}
56+
57+
// GetPort returns the port of the underlying db
58+
func (cfg *Database) GetPort() uint32 {
59+
if cfg.Port != 0 {
60+
return cfg.Port
61+
}
62+
63+
return defaultPorts[cfg.DriverName]
64+
}
65+
66+
// GetConnectionString returns the formed connection string
67+
func (cfg *Database) GetConnectionString() (connStr string, err error) {
68+
connStr = "sslmode=disable "
69+
70+
if cfg.Host != "" {
71+
connStr += fmt.Sprintf("host=%v ", cfg.Host)
72+
}
73+
74+
if cfg.Port != 0 {
75+
connStr += fmt.Sprintf("port=%d ", cfg.Port)
76+
}
77+
78+
if cfg.Name != "" {
79+
connStr += fmt.Sprintf("dbname=%v ", cfg.Name)
80+
}
81+
if cfg.Username != "" {
82+
connStr += fmt.Sprintf("user=%v ", cfg.Username)
83+
}
84+
if cfg.PasswordPath != "" {
85+
pw, err := cfg.GetPassword()
86+
if err != nil {
87+
return "", err
88+
}
89+
if pw != "" {
90+
connStr += fmt.Sprintf("password=%v ", pw)
91+
}
92+
}
93+
94+
return strings.TrimSpace(connStr), nil
95+
}

pkg/config/database_test.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package config
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestDatabaseGetPassword(t *testing.T) {
10+
t.Run("Returns the password when it's found", func(t *testing.T) {
11+
cfg := Database{PasswordPath: "./testdata/password"}
12+
password, err := cfg.GetPassword()
13+
require.NoError(t, err)
14+
require.Equal(t, "password", password)
15+
})
16+
t.Run("Returns no password when it's not set", func(t *testing.T) {
17+
cfg := Database{}
18+
password, err := cfg.GetPassword()
19+
require.NoError(t, err)
20+
require.Empty(t, password)
21+
})
22+
23+
t.Run("Returns the error when the password is not found", func(t *testing.T) {
24+
cfg := Database{PasswordPath: "./testdata/invalid"}
25+
password, err := cfg.GetPassword()
26+
require.Error(t, err)
27+
require.Empty(t, password)
28+
require.Equal(t, "can not read the database password file `./testdata/invalid`: open ./testdata/invalid: no such file or directory", err.Error())
29+
})
30+
}
31+
32+
func TestDatabaseGetHost(t *testing.T) {
33+
t.Run("Returns the host name when specified", func(t *testing.T) {
34+
host := "some.host"
35+
cfg := Database{Host: host}
36+
require.Equal(t, host, cfg.GetHost())
37+
})
38+
t.Run("Returns `localhost` when name is not specified", func(t *testing.T) {
39+
cfg := Database{}
40+
require.Equal(t, "localhost", cfg.GetHost())
41+
})
42+
}
43+
44+
func TestDatabaseGetPort(t *testing.T) {
45+
t.Run("Returns specified port when it's set", func(t *testing.T) {
46+
var port uint32 = 80
47+
cfg := Database{Port: port}
48+
require.Equal(t, port, cfg.GetPort())
49+
})
50+
t.Run("Returns default port when it's not set", func(t *testing.T) {
51+
cfg := Database{DriverName: "postgres"}
52+
require.Equal(t, uint32(5432), cfg.GetPort())
53+
})
54+
t.Run("Returns 0 when the port is not set and the default value is not found", func(t *testing.T) {
55+
cfg := Database{}
56+
require.Equal(t, uint32(0), cfg.GetPort())
57+
})
58+
}
59+
60+
func TestDatabaseGetConnectionString(t *testing.T) {
61+
cases := []struct {
62+
name string
63+
cfg Database
64+
expected string
65+
err string
66+
}{
67+
{
68+
name: "Returns the full connection string when all is set",
69+
cfg: Database{
70+
Host: "example.com",
71+
Port: 80,
72+
Name: "database",
73+
Username: "root",
74+
PasswordPath: "./testdata/password",
75+
},
76+
expected: "sslmode=disable host=example.com port=80 dbname=database user=root password=password",
77+
},
78+
{
79+
name: "Returns a connection string when Host is not set",
80+
cfg: Database{
81+
Port: 80,
82+
Name: "database",
83+
Username: "root",
84+
PasswordPath: "./testdata/password",
85+
},
86+
expected: "sslmode=disable port=80 dbname=database user=root password=password",
87+
},
88+
{
89+
name: "Returns a connection string when Port is not set",
90+
cfg: Database{
91+
Host: "example.com",
92+
Name: "database",
93+
Username: "root",
94+
PasswordPath: "./testdata/password",
95+
},
96+
expected: "sslmode=disable host=example.com dbname=database user=root password=password",
97+
},
98+
{
99+
name: "Returns a connection string when Name is not set",
100+
cfg: Database{
101+
Host: "example.com",
102+
Port: 80,
103+
Username: "root",
104+
PasswordPath: "./testdata/password",
105+
},
106+
expected: "sslmode=disable host=example.com port=80 user=root password=password",
107+
},
108+
{
109+
name: "Returns a connection string when Username is not set",
110+
cfg: Database{
111+
Host: "example.com",
112+
Name: "database",
113+
Port: 80,
114+
PasswordPath: "./testdata/password",
115+
},
116+
expected: "sslmode=disable host=example.com port=80 dbname=database password=password",
117+
},
118+
{
119+
name: "Returns a connection string when PasswordPath is not set",
120+
cfg: Database{
121+
Host: "example.com",
122+
Name: "database",
123+
Port: 80,
124+
Username: "root",
125+
},
126+
expected: "sslmode=disable host=example.com port=80 dbname=database user=root",
127+
},
128+
{
129+
name: "Returns an error when PasswordPath is wrong",
130+
cfg: Database{
131+
Host: "example.com",
132+
Name: "database",
133+
Port: 80,
134+
Username: "root",
135+
PasswordPath: "./testdata/invalid",
136+
},
137+
err: "can not read the database password file `./testdata/invalid`: open ./testdata/invalid: no such file or directory",
138+
},
139+
}
140+
141+
for _, tc := range cases {
142+
t.Run(tc.name, func(t *testing.T) {
143+
str, err := tc.cfg.GetConnectionString()
144+
if tc.err != "" {
145+
require.Error(t, err)
146+
require.Equal(t, tc.err, err.Error())
147+
return
148+
}
149+
require.NoError(t, err)
150+
require.Equal(t, tc.expected, str)
151+
})
152+
}
153+
}

pkg/config/http.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package config
2+
3+
// HTTP contains all configuration parameters for HTTP
4+
type HTTP struct {
5+
// Address to listen for the HTTP server
6+
Address string `json:"address"`
7+
}

pkg/config/jwt.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package config
2+
3+
import (
4+
"crypto/rsa"
5+
"io/ioutil"
6+
7+
jwt "github.com/dgrijalva/jwt-go"
8+
"github.com/pkg/errors"
9+
)
10+
11+
// JWT contains all JWT related parameters
12+
type JWT struct {
13+
// PublicKeyPath is a path to the public key for JWT signature verification
14+
PublicKeyPath string `json:"publicKeyPath"`
15+
}
16+
17+
// GetPublicKey gets the encryption key from a given path
18+
func (j *JWT) GetPublicKey() (publicKey *rsa.PublicKey, err error) {
19+
if j.PublicKeyPath == "" {
20+
return nil, errors.New("path to the public key file is empty")
21+
}
22+
23+
keyBytes, err := ioutil.ReadFile(j.PublicKeyPath)
24+
if err != nil {
25+
return nil, errors.Wrapf(err, "can not read the public key file `%s`", j.PublicKeyPath)
26+
}
27+
28+
return jwt.ParseRSAPublicKeyFromPEM(keyBytes)
29+
}

0 commit comments

Comments
 (0)