diff --git a/Gopkg.lock b/Gopkg.lock index 29a7fdce..0c5a856d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -10,20 +10,23 @@ version = "v1.0.0" [[projects]] - digest = "1:fed1f537c2f1269fe475a8556c393fe466641682d73ef8fd0491cd3aa1e47bad" + digest = "1:c659a6247d43eca4d74b53fade2e032a8882b055ba5aad02d154189b18ae3739" name = "github.com/certifi/gocertifi" packages = ["."] pruneopts = "UT" - revision = "deb3ae2ef2610fde3330947281941c562861188b" - version = "2018.01.18" + revision = "a6d78f326758ac5f79e9c3eddc38723d07216c3a" + version = "2020.01.04" [[projects]] - digest = "1:60ea23b7ed3a67fac14c3db423da3454b601552e5084fc8adb054dac4c55b1cb" + branch = "master" + digest = "1:dcbad43e7b7aeda88e403ae84473cf2dab4e4568cfc6b4316f2da9f3369f18cf" name = "github.com/cloudtrust/common-service" packages = [ ".", "database", + "database/sqltypes", "errors", + "healthcheck", "http", "idgenerator", "log", @@ -34,16 +37,15 @@ "tracking", ] pruneopts = "UT" - revision = "c70fa40bf8452fc44dd84d8e20d3f037bd14fce1" - version = "v1.2.2" + revision = "e164d81b968e97a3fc079275352764978e35ab42" [[projects]] - digest = "1:a307ae9193bfd1d4c72e27113af4955483ab264db159d3371eed5c4b4c7949ef" + branch = "master" + digest = "1:9f6d4c5d4f68f1195fba4c4fc0b6e10424eaff07965ff0d656dc056f24d5ea04" name = "github.com/cloudtrust/keycloak-client" packages = ["."] pruneopts = "UT" - revision = "2414a4a82a5216bf7f8d4a3eec68431153720ef2" - version = "v1.2.1" + revision = "eaca1582c80f7441ac7baa6d39d68b08e4ac3ffd" [[projects]] digest = "1:d64c893fc7d2c3d395f421b00d21f0adb8ceffc4d3c90299e732b3985ca16eb4" @@ -104,12 +106,12 @@ version = "v0.8.0" [[projects]] - digest = "1:4062bc6de62d73e2be342243cf138cf499b34d558876db8d9430e2149388a4d8" + digest = "1:522a207de5459ab329732b5df6bb6dd7592da9fddf8a99e9f82609e370d82f9b" name = "github.com/go-logfmt/logfmt" packages = ["."] pruneopts = "UT" - revision = "07c9b44f60d7ffdfb7d8efe1ad539965737836dc" - version = "v0.4.0" + revision = "3be5f6aae7db841d31dc5e1b3bb7b4cff19200b3" + version = "v0.5.0" [[projects]] digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65" @@ -183,15 +185,7 @@ version = "v1.7.9" [[projects]] - branch = "master" - digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72" - name = "github.com/kr/logfmt" - packages = ["."] - pruneopts = "UT" - revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" - -[[projects]] - digest = "1:12cb143f2148bf54bcd9fe622abac17325e85eeb1d84b8ec6caf1c80232108fd" + digest = "1:9ff22c26414baf7deaf74f2a788fe7b97666048bcbe346c52cfe823442abbdfb" name = "github.com/lib/pq" packages = [ ".", @@ -199,8 +193,8 @@ "scram", ] pruneopts = "UT" - revision = "3427c32cb71afc948325f299f040e53c1dd78979" - version = "v1.2.0" + revision = "99274577be97ac9b1d95a2d61d566dc9b7cc6a54" + version = "v1.3.0" [[projects]] digest = "1:5a0ef768465592efca0412f7e838cdc0826712f8447e70e6ccc52eb441e9ab13" @@ -285,12 +279,12 @@ version = "v1.2.2" [[projects]] - digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc" + digest = "1:ff4cd55a3666b6ea3a876c9e133bfb54d6c812e725409a773f2c94a0b3a92f4f" name = "github.com/spf13/cast" packages = ["."] pruneopts = "UT" - revision = "8c9545af88b134710ab1cd196795e7f2388358d7" - version = "v1.3.0" + revision = "1ffadf551085444af981432dd0f6d1160c11ec64" + version = "v1.3.1" [[projects]] digest = "1:1b753ec16506f5864d26a28b43703c58831255059644351bbcb019b843950900" @@ -309,12 +303,12 @@ version = "v1.0.5" [[projects]] - digest = "1:0b60fc944fb6a7b6c985832bd341bdb7ed8fe894fea330414e7774bb24652962" + digest = "1:8d0b79a29be9946ea00b2f2b778ec5b36db07453d97abe9d754b25bc2cc49a0e" name = "github.com/spf13/viper" packages = ["."] pruneopts = "UT" - revision = "72b022eb357a56469725dcd03918449e2278d02e" - version = "v1.5.0" + revision = "eabbc68a3ecd5cf8c11a2f84dbda5e7a38493b2f" + version = "v1.6.1" [[projects]] digest = "1:8548c309c65a85933a625be5e7d52b6ac927ca30c56869fae58123b8a77a75e1" @@ -333,7 +327,7 @@ version = "v1.2.0" [[projects]] - digest = "1:f32fa8138a53759550421434c10abd0f0f5006fd28e61632ee36e9d105e39728" + digest = "1:b106ba2e50a6d4308343759f4039cf636869b4d8ccddd248848f17127055d0f3" name = "github.com/uber/jaeger-client-go" packages = [ ".", @@ -355,8 +349,8 @@ "utils", ] pruneopts = "UT" - revision = "5b163d27fabddbb0e7fae4548c8933c379210610" - version = "v2.20.1" + revision = "f2e1f58485aacf2975cdde9c9f5396e6d98c35ba" + version = "v2.21.1" [[projects]] digest = "1:d7b6fd08442b8d26882fae2c859a0d63962d72a533476a217d46743ed30cb803" @@ -384,7 +378,7 @@ "pbkdf2", ] pruneopts = "UT" - revision = "86a70503ff7e82ffc18c7b0de83db35da4791e6a" + revision = "53104e6ec876ad4e22ad27cce588b01392043c1b" [[projects]] branch = "master" @@ -408,7 +402,7 @@ "publicsuffix", ] pruneopts = "UT" - revision = "ef20fe5d793301b553005db740f730d87993f778" + revision = "c0dbc17a35534bf2e581d7a942408dc936316da4" [[projects]] branch = "master" @@ -423,11 +417,11 @@ [[projects]] branch = "master" - digest = "1:72f7bc975a18badcb4b05585929a12e042ab45cb0bee781aaab8044f39bd41f5" + digest = "1:c1b1f11d25f274813ed6642e8eb55237183a3456d4a809cfcb0395d0fc50a46c" name = "golang.org/x/sys" packages = ["unix"] pruneopts = "UT" - revision = "6d18c012aee9febd81bbf9806760c8c4480e870d" + revision = "b016eb3dc98ea7f69ed55e8216b87187067ae621" [[projects]] digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405" @@ -473,7 +467,7 @@ "go/types/typeutil", ] pruneopts = "UT" - revision = "c197fd4bf3712fd6a7d00f39b376956dd5d58449" + revision = "53017a39ae3660ac73c3a5aa30e4af6fd0ed8b6c" [[projects]] digest = "1:602f92501b1cc299f1febc5febb27acdf80c4bc70aaa833942e8000e2e77ddee" @@ -516,7 +510,15 @@ version = "v2.0.3" [[projects]] - digest = "1:8ebd7bc4892e673e8d3c6377c71e8c691b92c5e51cd3159efb8343b580213b70" + digest = "1:c4b5592c342f273e18de68e16957f536f6648f2e90c9d7ece653ac90c22079a9" + name = "gopkg.in/ini.v1" + packages = ["."] + pruneopts = "UT" + revision = "94291fffe2b14f4632ec0e67c1bfecfc1287a168" + version = "v1.51.1" + +[[projects]] + digest = "1:3c4aaca5a82adc021322f239e0cf3f46852bb0b18ae050d0feab2e9e4c527462" name = "gopkg.in/square/go-jose.v2" packages = [ ".", @@ -524,8 +526,8 @@ "json", ] pruneopts = "UT" - revision = "8fd82ff1d52a5162ff23c0a48e2c64a1fa4a3f8f" - version = "v2.4.0" + revision = "4ef0f1b175ccd65472d154e1207c4d7b8102545f" + version = "v2.4.1" [[projects]] digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737" @@ -541,7 +543,9 @@ input-imports = [ "github.com/cloudtrust/common-service", "github.com/cloudtrust/common-service/database", + "github.com/cloudtrust/common-service/database/sqltypes", "github.com/cloudtrust/common-service/errors", + "github.com/cloudtrust/common-service/healthcheck", "github.com/cloudtrust/common-service/http", "github.com/cloudtrust/common-service/idgenerator", "github.com/cloudtrust/common-service/log", diff --git a/Gopkg.toml b/Gopkg.toml index 224a6e49..ae49796d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -27,12 +27,13 @@ [[constraint]] name = "github.com/cloudtrust/common-service" - version = "v1.2.2" - + branch = "master" + #version = "v1.2.2" [[constraint]] name = "github.com/cloudtrust/keycloak-client" - version = "v1.2.1" + branch = "master" + #version = "v1.2.1" [[constraint]] name = "github.com/go-kit/kit" diff --git a/README.md b/README.md index 0e7e52ad..37ad1bec 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,39 @@ It is manadatory to load an authorization JSON file, if no authorization file is The keycloak event-emitter module sends all events to the bridge's event endpoint. The event emitter use HTTP with flatbuffers. +### Monitoring of keycloak-bridge + +An endpoint allows to get a status of the Bridge and its components health. +URL: ```http://{bridge.host}:{bridge.port}/health/check``` + +Status example: +``` +{ + "name": "keycloak-bridge", + "state": "DOWN", + "details": [ + { + "name": "Audit R/W", + "type": "database", + "state": "DOWN", + "message": "bad connection" + }, + { + "name": "Config RO", + "type": "database", + "state": "UP", + "connection": "established" + }, + { + "name": "Keycloak", + "type": "http", + "state": "DOWN", + "message": "Can't hit target: Get http://127.0.0.1:8080: dial tcp 127.0.0.1:8080: connectex: No connection could be made because the target machine actively refused it." + } + ] +} +``` + ## About monitoring Each HTTP request will trigger a set of operations that are going to be logged, measured, tracked and traced. For those information to be usable, we must be able to link the logs, metrics, traces and error report together. We achieve that with a unique correlation ID. For a given request, the same correlation ID will appear on the logs, metrics, traces and error report. diff --git a/cmd/keycloakb/keycloak_bridge.go b/cmd/keycloakb/keycloak_bridge.go index 34890d1a..415d7a2b 100644 --- a/cmd/keycloakb/keycloak_bridge.go +++ b/cmd/keycloakb/keycloak_bridge.go @@ -14,6 +14,9 @@ import ( "syscall" "time" + "github.com/cloudtrust/common-service/database/sqltypes" + "github.com/cloudtrust/common-service/healthcheck" + cs "github.com/cloudtrust/common-service" "github.com/cloudtrust/common-service/database" errorhandler "github.com/cloudtrust/common-service/errors" @@ -261,46 +264,56 @@ func main() { defer tracer.Close() } - var eventsDBConn database.CloudtrustDB + var eventsDBConn sqltypes.CloudtrustDB { var err error - eventsDBConn, err = auditRwDbParams.OpenDatabase() + eventsDBConn, err = database.NewReconnectableCloudtrustDB(auditRwDbParams) if err != nil { logger.Error(ctx, "msg", "could not create R/W DB connection for audit events", "error", err) return } } - var eventsRODBConn database.CloudtrustDB + var eventsRODBConn sqltypes.CloudtrustDB { var err error - eventsRODBConn, err = auditRoDbParams.OpenDatabase() + eventsRODBConn, err = database.NewReconnectableCloudtrustDB(auditRoDbParams) if err != nil { logger.Error(ctx, "msg", "could not create RO DB connection for audit events", "error", err) return } } - var configurationRwDBConn database.CloudtrustDB + var configurationRwDBConn sqltypes.CloudtrustDB { var err error - configurationRwDBConn, err = configRwDbParams.OpenDatabase() + configurationRwDBConn, err = database.NewReconnectableCloudtrustDB(configRwDbParams) if err != nil { logger.Error(ctx, "msg", "could not create DB connection for configuration storage (RW)", "error", err) return } } - var configurationRoDBConn database.CloudtrustDB + var configurationRoDBConn sqltypes.CloudtrustDB { var err error - configurationRoDBConn, err = configRoDbParams.OpenDatabase() + configurationRoDBConn, err = database.NewReconnectableCloudtrustDB(configRoDbParams) if err != nil { logger.Error(ctx, "msg", "could not create DB connection for configuration storage (RO)", "error", err) return } } + // Health check configuration + var healthChecker = healthcheck.NewHealthChecker(keycloakb.ComponentName, logger) + var healthCheckCacheDuration = c.GetDuration("livenessprobe-cache-duration") * time.Second + var httpTimeout = c.GetDuration("livenessprobe-http-timeout") * time.Second + healthChecker.AddDatabase("Audit R/W", eventsDBConn, healthCheckCacheDuration) + healthChecker.AddDatabase("Audit RO", eventsRODBConn, healthCheckCacheDuration) + healthChecker.AddDatabase("Config R/W", configurationRwDBConn, healthCheckCacheDuration) + healthChecker.AddDatabase("Config RO", configurationRoDBConn, healthCheckCacheDuration) + healthChecker.AddHTTPEndpoint("Keycloak", keycloakConfig.AddrAPI, httpTimeout, 200, healthCheckCacheDuration) + // Event service. var eventEndpoints = event.Endpoints{} { @@ -525,6 +538,7 @@ func main() { // Version. route.Handle("/", commonhttp.MakeVersionHandler(keycloakb.ComponentName, ComponentID, keycloakb.Version, Environment, GitCommit)) + route.Handle("/health/check", healthChecker.MakeHandler()) // Event. var eventSubroute = route.PathPrefix("/event").Subrouter() @@ -569,6 +583,7 @@ func main() { // Version. route.Handle("/", http.HandlerFunc(commonhttp.MakeVersionHandler(keycloakb.ComponentName, ComponentID, keycloakb.Version, Environment, GitCommit))) + route.Handle("/health/check", healthChecker.MakeHandler()) // Rights var rightsHandler = configureRightsHandler(keycloakb.ComponentName, ComponentID, idGenerator, authorizationManager, keycloakClient, audienceRequired, tracer, logger) @@ -712,6 +727,7 @@ func main() { // Version. route.Handle("/", http.HandlerFunc(commonhttp.MakeVersionHandler(keycloakb.ComponentName, ComponentID, keycloakb.Version, Environment, GitCommit))) + route.Handle("/health/check", healthChecker.MakeHandler()) // Account var updatePasswordHandler = configureAccountHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(accountEndpoints.UpdatePassword) @@ -852,6 +868,10 @@ func config(ctx context.Context, logger log.Logger) *viper.Viper { // Debug routes enabled. v.SetDefault("pprof-route-enabled", true) + // Liveness probe + v.SetDefault("livenessprobe-http-timeout", 5) + v.SetDefault("livenessprobe-cache-duration", 10) + // First level of override. pflag.String("config-file", v.GetString("config-file"), "The configuration file path can be relative or absolute.") pflag.String("authorization-file", v.GetString("authorization-file"), "The authorization file path can be relative or absolute.") diff --git a/configs/keycloak_bridge.yml b/configs/keycloak_bridge.yml index beb3fd2e..c90173bd 100644 --- a/configs/keycloak_bridge.yml +++ b/configs/keycloak_bridge.yml @@ -139,4 +139,8 @@ jaeger-reporter-logspan: false jaeger-write-interval: 1s # Debug routes -pprof-route-enabled: true \ No newline at end of file +pprof-route-enabled: true + +# Liveness probe +livenessprobe-http-timeout: 5 +livenessprobe-cache-duration: 10 diff --git a/internal/keycloakb/configdbmodule.go b/internal/keycloakb/configdbmodule.go index 104039af..d42931d3 100644 --- a/internal/keycloakb/configdbmodule.go +++ b/internal/keycloakb/configdbmodule.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/json" + "github.com/cloudtrust/common-service/database/sqltypes" errorhandler "github.com/cloudtrust/common-service/errors" "github.com/cloudtrust/keycloak-bridge/internal/dto" ) @@ -19,7 +20,7 @@ const ( // DBConfiguration interface type DBConfiguration interface { Exec(query string, args ...interface{}) (sql.Result, error) - QueryRow(query string, args ...interface{}) *sql.Row + QueryRow(query string, args ...interface{}) sqltypes.SQLRow } type configurationDBModule struct { @@ -52,14 +53,14 @@ func (c *configurationDBModule) GetConfiguration(context context.Context, realmI switch err := row.Scan(&configJSON); err { case sql.ErrNoRows: - return dto.RealmConfiguration{}, errorhandler.Error{ + return config, errorhandler.Error{ Status: 404, Message: ComponentName + "." + MsgErrNotConfigured + "." + RealmConfiguration + "." + realmID, } default: if err != nil { - return dto.RealmConfiguration{}, err + return config, err } err = json.Unmarshal([]byte(configJSON), &config) diff --git a/internal/keycloakb/configdbmodule_test.go b/internal/keycloakb/configdbmodule_test.go index 3911a0da..61cb6267 100644 --- a/internal/keycloakb/configdbmodule_test.go +++ b/internal/keycloakb/configdbmodule_test.go @@ -2,6 +2,10 @@ package keycloakb import ( "context" + "database/sql" + "encoding/json" + "errors" + "strings" "testing" "github.com/cloudtrust/keycloak-bridge/internal/dto" @@ -10,6 +14,12 @@ import ( "github.com/stretchr/testify/assert" ) +func useMockDB(t *testing.T, fn func(DBConfiguration)) { + var mockCtrl = gomock.NewController(t) + defer mockCtrl.Finish() + fn(mock.NewDBConfiguration(mockCtrl)) +} + func TestConfigurationDBModule(t *testing.T) { var mockCtrl = gomock.NewController(t) defer mockCtrl.Finish() @@ -20,3 +30,49 @@ func TestConfigurationDBModule(t *testing.T) { var err = configDBModule.StoreOrUpdate(context.Background(), "realmId", dto.RealmConfiguration{}) assert.Nil(t, err) } + +func TestGetConfiguration(t *testing.T) { + var mockCtrl = gomock.NewController(t) + defer mockCtrl.Finish() + + var mockDB = mock.NewDBConfiguration(mockCtrl) + var mockSQLRow = mock.NewSQLRow(mockCtrl) + + var configDBModule = NewConfigurationDBModule(mockDB) + var realmID = "myrealm" + var expectedError = errors.New("sql") + + { + // No error + var dummyURL = "dummy://path/to/nothing" + var expectedResult = dto.RealmConfiguration{DefaultRedirectURI: &dummyURL} + var jsonBytes, _ = json.Marshal(expectedResult) + var json = string(jsonBytes) + mockDB.EXPECT().QueryRow(gomock.Any(), realmID).Return(mockSQLRow) + mockSQLRow.EXPECT().Scan(gomock.Any()).DoAndReturn(func(dest ...interface{}) error { + var ptrJSON = dest[0].(*string) + *ptrJSON = json + return nil + }) + result, err := configDBModule.GetConfiguration(context.TODO(), realmID) + assert.Nil(t, err) + assert.Equal(t, expectedResult, result) + } + + { + // SQL not found + mockDB.EXPECT().QueryRow(gomock.Any(), realmID).Return(mockSQLRow) + mockSQLRow.EXPECT().Scan(gomock.Any()).Return(sql.ErrNoRows) + _, err := configDBModule.GetConfiguration(context.TODO(), realmID) + assert.NotNil(t, err) + assert.True(t, strings.Contains(err.Error(), MsgErrNotConfigured)) + } + + { + // Unexpected SQL error + mockDB.EXPECT().QueryRow(gomock.Any(), realmID).Return(mockSQLRow) + mockSQLRow.EXPECT().Scan(gomock.Any()).Return(expectedError) + _, err := configDBModule.GetConfiguration(context.TODO(), realmID) + assert.Equal(t, expectedError, err) + } +} diff --git a/internal/keycloakb/eventsdbmodule.go b/internal/keycloakb/eventsdbmodule.go index a49bf89f..4bfc14ab 100644 --- a/internal/keycloakb/eventsdbmodule.go +++ b/internal/keycloakb/eventsdbmodule.go @@ -8,9 +8,9 @@ import ( "strings" "time" + "github.com/cloudtrust/common-service/database/sqltypes" errorhandler "github.com/cloudtrust/common-service/errors" - "github.com/cloudtrust/common-service/database" api "github.com/cloudtrust/keycloak-bridge/api/events" api_stat "github.com/cloudtrust/keycloak-bridge/api/statistics" ) @@ -29,11 +29,11 @@ type EventsDBModule interface { } type eventsDBModule struct { - db database.CloudtrustDB + db sqltypes.CloudtrustDB } // NewEventsDBModule returns an events database module. -func NewEventsDBModule(db database.CloudtrustDB) EventsDBModule { +func NewEventsDBModule(db sqltypes.CloudtrustDB) EventsDBModule { return &eventsDBModule{ db: db, } diff --git a/internal/keycloakb/mock_test.go b/internal/keycloakb/mock_test.go index 8d170aff..6afaea03 100644 --- a/internal/keycloakb/mock_test.go +++ b/internal/keycloakb/mock_test.go @@ -4,3 +4,4 @@ package keycloakb //go:generate mockgen -destination=./mock/configdbinstrumenting.go -package=mock -mock_names=ConfigurationDBModule=ConfigurationDBModule github.com/cloudtrust/keycloak-bridge/internal/keycloakb ConfigurationDBModule //go:generate mockgen -destination=./mock/configdbmodule.go -package=mock -mock_names=DBConfiguration=DBConfiguration github.com/cloudtrust/keycloak-bridge/internal/keycloakb DBConfiguration //go:generate mockgen -destination=./mock/keycloak_client.go -package=mock -mock_names=KeycloakClient=KeycloakClient github.com/cloudtrust/keycloak-bridge/internal/keycloakb KeycloakClient +//go:generate mockgen -destination=./mock/sqltypes.go -package=mock -mock_names=SQLRow=SQLRow github.com/cloudtrust/common-service/database/sqltypes SQLRow diff --git a/pkg/events/mock_test.go b/pkg/events/mock_test.go index 9209e138..fed0b7db 100644 --- a/pkg/events/mock_test.go +++ b/pkg/events/mock_test.go @@ -3,6 +3,6 @@ package events //go:generate mockgen -destination=./mock/component.go -package=mock -mock_names=Component=Component github.com/cloudtrust/keycloak-bridge/pkg/events Component //go:generate mockgen -destination=./mock/dbmodule.go -package=mock -mock_names=EventsDBModule=EventsDBModule github.com/cloudtrust/keycloak-bridge/internal/keycloakb EventsDBModule //go:generate mockgen -destination=./mock/keycloak_client.go -package=mock -mock_names=KeycloakClient=KeycloakClient github.com/cloudtrust/common-service/security KeycloakClient -//go:generate mockgen -destination=./mock/dbevents.go -package=mock -mock_names=CloudtrustDB=DBEvents github.com/cloudtrust/common-service/database CloudtrustDB +//go:generate mockgen -destination=./mock/dbevents.go -package=mock -mock_names=CloudtrustDB=DBEvents github.com/cloudtrust/common-service/database/sqltypes CloudtrustDB //go:generate mockgen -destination=./mock/writedb.go -package=mock -mock_names=EventsDBModule=WriteDBModule github.com/cloudtrust/common-service/database EventsDBModule //go:generate mockgen -destination=./mock/logger.go -package=mock -mock_names=Logger=Logger github.com/cloudtrust/keycloak-bridge/internal/keycloakb Logger diff --git a/pkg/export/storage.go b/pkg/export/storage.go index 9e07c776..1369ea50 100644 --- a/pkg/export/storage.go +++ b/pkg/export/storage.go @@ -3,6 +3,7 @@ package export import ( "database/sql" + "github.com/cloudtrust/common-service/database/sqltypes" internal "github.com/cloudtrust/keycloak-bridge/internal/keycloakb" "github.com/pkg/errors" ) @@ -24,7 +25,7 @@ type StorageModule struct { // DB interface type DB interface { Exec(query string, args ...interface{}) (sql.Result, error) - QueryRow(query string, args ...interface{}) *sql.Row + QueryRow(query string, args ...interface{}) sqltypes.SQLRow } // NewConfigStorageModule returns the storage module.