-
Notifications
You must be signed in to change notification settings - Fork 3
/
redis.go
176 lines (159 loc) · 4.95 KB
/
redis.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright 2023 Cisco Systems, Inc. and its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
package embedded
import (
"context"
"crypto/tls"
"fmt"
"github.com/alicebob/miniredis/v2"
"github.com/cisco-open/go-lanai/test"
"github.com/cisco-open/go-lanai/test/apptest"
"github.com/cisco-open/go-lanai/test/suitetest"
"math/rand"
"testing"
"time"
)
var kCtxEmbeddedRedis = struct{}{}
/*******************
Public
*******************/
type RedisOptions func(cfg *RedisConfig)
type RedisConfig struct {
// Port must between 32768 and 65535
Port int
// TLS when set, the redis server is run in TLS mode.
// Note: duo to internal implementation, When running in TLS mode, the Port is ignored
TLS *tls.Config
}
// Redis start redis at random port (32768-65535) on test package level.
// The actual port get be get using CurrentRedisPort
func Redis(opts ...RedisOptions) suitetest.PackageOptions {
return suitetest.TestOptions(WithRedis(opts...))
}
// WithRedis start redis at random port (32768-65535) on per test basis
// The actual port get be get using CurrentRedisPort
func WithRedis(opts ...RedisOptions) test.Options {
//nolint:gosec // Not security related
r := rand.New(rand.NewSource(time.Now().UnixNano()))
cfg := RedisConfig{
Port: 0x7fff + r.Intn(0x7fff) + 1,
}
for _, fn := range opts {
fn(&cfg)
}
return redisWithConfig(&cfg)
}
func EnableTLS(certs ...func(src *TLSCerts)) RedisOptions {
tlsCfg, e := ServerTLSWithCerts(certs...)
if e != nil {
logger.Warnf(`unable to enable TLS: %v`, e)
}
return func(cfg *RedisConfig) {
cfg.TLS = tlsCfg
}
}
// RedisWithPort start redis at given port (must between 32768 and 65535) on test package level.
// Deprecated, use Redis(...) to set RedisConfig.Port
func RedisWithPort(port int) suitetest.PackageOptions {
return Redis(func(cfg *RedisConfig) {
cfg.Port = port
})
}
// CurrentRedisPort getter to return embedded redis port. returns -1 if it's not initialized or started
func CurrentRedisPort(ctx context.Context) (port int) {
port = -1
srv, ok := ctx.Value(kCtxEmbeddedRedis).(*miniredis.Miniredis)
if !ok {
return
}
ret := doWithEmbeddedRedis(srv, func(srv *miniredis.Miniredis) interface{} {
return srv.Server().Addr().Port
})
switch v := ret.(type) {
case int:
return v
}
return
}
// CurrentRedisServer getter to return embedded redis. returns nil if it's not initialized or started
func CurrentRedisServer(ctx context.Context) *miniredis.Miniredis {
srv, _ := ctx.Value(kCtxEmbeddedRedis).(*miniredis.Miniredis)
return srv
}
/*******************
Internals
*******************/
// redisWithConfig start redis based on given RedisConfig
func redisWithConfig(cfg *RedisConfig) test.Options {
return test.WithOptions(
test.Setup(func(ctx context.Context, t *testing.T) (context.Context, error) {
s, e := startEmbeddedRedis(cfg)
if e != nil {
return ctx, e
}
return context.WithValue(ctx, kCtxEmbeddedRedis, s), nil
}),
apptest.WithDynamicProperties(map[string]apptest.PropertyValuerFunc{
"redis.addrs": func(ctx context.Context) interface{} {
return fmt.Sprintf("127.0.0.1:%d", CurrentRedisPort(ctx))
},
}),
test.Teardown(func(ctx context.Context, t *testing.T) error {
if s, ok := ctx.Value(kCtxEmbeddedRedis).(*miniredis.Miniredis); ok {
stopEmbeddedRedis(s)
}
return nil
}),
)
}
func startEmbeddedRedis(cfg *RedisConfig) (server *miniredis.Miniredis, err error) {
switch {
case cfg.TLS != nil:
// TLS mode
server = miniredis.NewMiniRedis()
err = server.StartTLS(cfg.TLS)
case cfg.Port <= 0x7fff && cfg.Port != 0:
err = fmt.Errorf("invalid embedded redis port [%d], should be > 0x7fff", cfg.Port)
default:
// Default mode
server = miniredis.NewMiniRedis()
addr := fmt.Sprintf("127.0.0.1:%d", cfg.Port)
err = server.StartAddr(addr)
}
if err == nil {
logger.Infof("Embedded Redis started at %s", server.Addr())
}
return
}
func stopEmbeddedRedis(server *miniredis.Miniredis) {
if server != nil {
addr := server.Addr()
server.Close()
logger.Infof("Embedded Redis stopped at %s", addr)
}
}
// doWithEmbeddedRedis perform locking on miniredis.Miniredis, bail the operation if server is not started
func doWithEmbeddedRedis(server *miniredis.Miniredis, fn func(srv *miniredis.Miniredis) interface{}) interface{} {
if server == nil {
return nil
}
server.Lock()
defer server.Unlock()
if s := server.Server(); s != nil {
return fn(server)
}
return nil
}