mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
refactor: notification providers factory
This commit is contained in:
@@ -1,22 +1,9 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/internal/notify/providers"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
pDingTalkBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/dingtalkbot"
|
||||
pDiscordBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/discordbot"
|
||||
pEmail "github.com/certimate-go/certimate/pkg/core/notifier/providers/email"
|
||||
pLarkBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/larkbot"
|
||||
pMattermost "github.com/certimate-go/certimate/pkg/core/notifier/providers/mattermost"
|
||||
pSlackBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/slackbot"
|
||||
pTelegramBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/telegrambot"
|
||||
pWebhook "github.com/certimate-go/certimate/pkg/core/notifier/providers/webhook"
|
||||
pWeComBot "github.com/certimate-go/certimate/pkg/core/notifier/providers/wecombot"
|
||||
xhttp "github.com/certimate-go/certimate/pkg/utils/http"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
type notifierProviderOptions struct {
|
||||
@@ -26,157 +13,13 @@ type notifierProviderOptions struct {
|
||||
}
|
||||
|
||||
func createNotifierProvider(options *notifierProviderOptions) (core.Notifier, error) {
|
||||
/*
|
||||
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new constant, please keep ASCII order.
|
||||
*/
|
||||
switch options.Provider {
|
||||
case domain.NotificationProviderTypeDingTalkBot:
|
||||
{
|
||||
access := domain.AccessConfigForDingTalkBot{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
return pDingTalkBot.NewNotifierProvider(&pDingTalkBot.NotifierProviderConfig{
|
||||
WebhookUrl: access.WebhookUrl,
|
||||
Secret: access.Secret,
|
||||
})
|
||||
}
|
||||
|
||||
case domain.NotificationProviderTypeDiscordBot:
|
||||
{
|
||||
access := domain.AccessConfigForDiscordBot{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
return pDiscordBot.NewNotifierProvider(&pDiscordBot.NotifierProviderConfig{
|
||||
BotToken: access.BotToken,
|
||||
ChannelId: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.ChannelId),
|
||||
})
|
||||
}
|
||||
|
||||
case domain.NotificationProviderTypeEmail:
|
||||
{
|
||||
access := domain.AccessConfigForEmail{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
return pEmail.NewNotifierProvider(&pEmail.NotifierProviderConfig{
|
||||
SmtpHost: access.SmtpHost,
|
||||
SmtpPort: access.SmtpPort,
|
||||
SmtpTls: access.SmtpTls,
|
||||
Username: access.Username,
|
||||
Password: access.Password,
|
||||
SenderAddress: access.SenderAddress,
|
||||
SenderName: access.SenderName,
|
||||
ReceiverAddress: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "receiverAddress", access.ReceiverAddress),
|
||||
})
|
||||
}
|
||||
|
||||
case domain.NotificationProviderTypeLarkBot:
|
||||
{
|
||||
access := domain.AccessConfigForLarkBot{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
return pLarkBot.NewNotifierProvider(&pLarkBot.NotifierProviderConfig{
|
||||
WebhookUrl: access.WebhookUrl,
|
||||
})
|
||||
}
|
||||
|
||||
case domain.NotificationProviderTypeMattermost:
|
||||
{
|
||||
access := domain.AccessConfigForMattermost{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
return pMattermost.NewNotifierProvider(&pMattermost.NotifierProviderConfig{
|
||||
ServerUrl: access.ServerUrl,
|
||||
Username: access.Username,
|
||||
Password: access.Password,
|
||||
ChannelId: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.ChannelId),
|
||||
})
|
||||
}
|
||||
|
||||
case domain.NotificationProviderTypeSlackBot:
|
||||
{
|
||||
access := domain.AccessConfigForSlackBot{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
return pSlackBot.NewNotifierProvider(&pSlackBot.NotifierProviderConfig{
|
||||
BotToken: access.BotToken,
|
||||
ChannelId: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.ChannelId),
|
||||
})
|
||||
}
|
||||
|
||||
case domain.NotificationProviderTypeTelegramBot:
|
||||
{
|
||||
access := domain.AccessConfigForTelegramBot{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
return pTelegramBot.NewNotifierProvider(&pTelegramBot.NotifierProviderConfig{
|
||||
BotToken: access.BotToken,
|
||||
ChatId: xmaps.GetOrDefaultInt64(options.ProviderServiceConfig, "chatId", access.ChatId),
|
||||
})
|
||||
}
|
||||
|
||||
case domain.NotificationProviderTypeWebhook:
|
||||
{
|
||||
access := domain.AccessConfigForWebhook{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
mergedHeaders := make(map[string]string)
|
||||
if defaultHeadersString := access.HeadersString; defaultHeadersString != "" {
|
||||
h, err := xhttp.ParseHeaders(defaultHeadersString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse webhook headers: %w", err)
|
||||
}
|
||||
for key := range h {
|
||||
mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key)
|
||||
}
|
||||
}
|
||||
if extendedHeadersString := xmaps.GetString(options.ProviderServiceConfig, "headers"); extendedHeadersString != "" {
|
||||
h, err := xhttp.ParseHeaders(extendedHeadersString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse webhook headers: %w", err)
|
||||
}
|
||||
for key := range h {
|
||||
mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key)
|
||||
}
|
||||
}
|
||||
|
||||
return pWebhook.NewNotifierProvider(&pWebhook.NotifierProviderConfig{
|
||||
WebhookUrl: access.Url,
|
||||
WebhookData: xmaps.GetOrDefaultString(options.ProviderServiceConfig, "webhookData", access.DataString),
|
||||
Method: access.Method,
|
||||
Headers: mergedHeaders,
|
||||
AllowInsecureConnections: access.AllowInsecureConnections,
|
||||
})
|
||||
}
|
||||
|
||||
case domain.NotificationProviderTypeWeComBot:
|
||||
{
|
||||
access := domain.AccessConfigForWeComBot{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
return pWeComBot.NewNotifierProvider(&pWeComBot.NotifierProviderConfig{
|
||||
WebhookUrl: access.WebhookUrl,
|
||||
})
|
||||
}
|
||||
provider, err := providers.Registries.Get(options.Provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported notifier provider '%s'", options.Provider)
|
||||
return provider(&providers.ProviderFactoryOptions{
|
||||
AccessConfig: options.ProviderAccessConfig,
|
||||
ProviderConfig: options.ProviderServiceConfig,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
)
|
||||
|
||||
type ProviderFactoryFunc func(options *ProviderFactoryOptions) (core.Notifier, error)
|
||||
|
||||
type ProviderFactoryOptions struct {
|
||||
AccessConfig map[string]any
|
||||
ProviderConfig map[string]any
|
||||
}
|
||||
|
||||
type Registry[T comparable] interface {
|
||||
Register(T, ProviderFactoryFunc) error
|
||||
Get(T) (ProviderFactoryFunc, error)
|
||||
}
|
||||
|
||||
type registry[T comparable] struct {
|
||||
factories map[T]ProviderFactoryFunc
|
||||
}
|
||||
|
||||
func (r *registry[T]) Register(name T, factory ProviderFactoryFunc) error {
|
||||
if _, exists := r.factories[name]; exists {
|
||||
return fmt.Errorf("provider '%v' already registered", name)
|
||||
}
|
||||
|
||||
r.factories[name] = factory
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *registry[T]) Get(name T) (ProviderFactoryFunc, error) {
|
||||
if factory, exists := r.factories[name]; exists {
|
||||
return factory, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("provider '%v' not registered", name)
|
||||
}
|
||||
|
||||
func newRegistry[T comparable]() Registry[T] {
|
||||
return ®istry[T]{factories: make(map[T]ProviderFactoryFunc)}
|
||||
}
|
||||
|
||||
var Registries = newRegistry[domain.NotificationProviderType]()
|
||||
@@ -0,0 +1,27 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/notifier/providers/dingtalkbot"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Registries.Register(domain.NotificationProviderTypeDingTalkBot, func(options *ProviderFactoryOptions) (core.Notifier, error) {
|
||||
access := domain.AccessConfigForDingTalkBot{}
|
||||
if err := xmaps.Populate(options.AccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := dingtalkbot.NewNotifierProvider(&dingtalkbot.NotifierProviderConfig{
|
||||
WebhookUrl: access.WebhookUrl,
|
||||
Secret: access.Secret,
|
||||
})
|
||||
return provider, err
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/notifier/providers/discordbot"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Registries.Register(domain.NotificationProviderTypeDiscordBot, func(options *ProviderFactoryOptions) (core.Notifier, error) {
|
||||
access := domain.AccessConfigForDiscordBot{}
|
||||
if err := xmaps.Populate(options.AccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := discordbot.NewNotifierProvider(&discordbot.NotifierProviderConfig{
|
||||
BotToken: access.BotToken,
|
||||
ChannelId: xmaps.GetOrDefaultString(options.ProviderConfig, "channelId", access.ChannelId),
|
||||
})
|
||||
return provider, err
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/notifier/providers/email"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Registries.Register(domain.NotificationProviderTypeEmail, func(options *ProviderFactoryOptions) (core.Notifier, error) {
|
||||
access := domain.AccessConfigForEmail{}
|
||||
if err := xmaps.Populate(options.AccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := email.NewNotifierProvider(&email.NotifierProviderConfig{
|
||||
SmtpHost: access.SmtpHost,
|
||||
SmtpPort: access.SmtpPort,
|
||||
SmtpTls: access.SmtpTls,
|
||||
Username: access.Username,
|
||||
Password: access.Password,
|
||||
SenderAddress: access.SenderAddress,
|
||||
SenderName: access.SenderName,
|
||||
ReceiverAddress: xmaps.GetOrDefaultString(options.ProviderConfig, "receiverAddress", access.ReceiverAddress),
|
||||
})
|
||||
return provider, err
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/notifier/providers/larkbot"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Registries.Register(domain.NotificationProviderTypeLarkBot, func(options *ProviderFactoryOptions) (core.Notifier, error) {
|
||||
access := domain.AccessConfigForLarkBot{}
|
||||
if err := xmaps.Populate(options.AccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := larkbot.NewNotifierProvider(&larkbot.NotifierProviderConfig{
|
||||
WebhookUrl: access.WebhookUrl,
|
||||
})
|
||||
return provider, err
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/notifier/providers/mattermost"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Registries.Register(domain.NotificationProviderTypeMattermost, func(options *ProviderFactoryOptions) (core.Notifier, error) {
|
||||
access := domain.AccessConfigForMattermost{}
|
||||
if err := xmaps.Populate(options.AccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := mattermost.NewNotifierProvider(&mattermost.NotifierProviderConfig{
|
||||
ServerUrl: access.ServerUrl,
|
||||
Username: access.Username,
|
||||
Password: access.Password,
|
||||
ChannelId: xmaps.GetOrDefaultString(options.ProviderConfig, "channelId", access.ChannelId),
|
||||
})
|
||||
return provider, err
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
slackbot "github.com/certimate-go/certimate/pkg/core/notifier/providers/slackbot"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Registries.Register(domain.NotificationProviderTypeSlackBot, func(options *ProviderFactoryOptions) (core.Notifier, error) {
|
||||
access := domain.AccessConfigForSlackBot{}
|
||||
if err := xmaps.Populate(options.AccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := slackbot.NewNotifierProvider(&slackbot.NotifierProviderConfig{
|
||||
BotToken: access.BotToken,
|
||||
ChannelId: xmaps.GetOrDefaultString(options.ProviderConfig, "channelId", access.ChannelId),
|
||||
})
|
||||
return provider, err
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/notifier/providers/telegrambot"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Registries.Register(domain.NotificationProviderTypeTelegramBot, func(options *ProviderFactoryOptions) (core.Notifier, error) {
|
||||
access := domain.AccessConfigForTelegramBot{}
|
||||
if err := xmaps.Populate(options.AccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := telegrambot.NewNotifierProvider(&telegrambot.NotifierProviderConfig{
|
||||
BotToken: access.BotToken,
|
||||
ChatId: xmaps.GetOrDefaultInt64(options.ProviderConfig, "chatId", access.ChatId),
|
||||
})
|
||||
return provider, err
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/notifier/providers/webhook"
|
||||
xhttp "github.com/certimate-go/certimate/pkg/utils/http"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Registries.Register(domain.NotificationProviderTypeWebhook, func(options *ProviderFactoryOptions) (core.Notifier, error) {
|
||||
access := domain.AccessConfigForWebhook{}
|
||||
if err := xmaps.Populate(options.AccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
mergedHeaders := make(map[string]string)
|
||||
if defaultHeadersString := access.HeadersString; defaultHeadersString != "" {
|
||||
h, err := xhttp.ParseHeaders(defaultHeadersString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse webhook headers: %w", err)
|
||||
}
|
||||
for key := range h {
|
||||
mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key)
|
||||
}
|
||||
}
|
||||
if extendedHeadersString := xmaps.GetString(options.ProviderConfig, "headers"); extendedHeadersString != "" {
|
||||
h, err := xhttp.ParseHeaders(extendedHeadersString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse webhook headers: %w", err)
|
||||
}
|
||||
for key := range h {
|
||||
mergedHeaders[http.CanonicalHeaderKey(key)] = h.Get(key)
|
||||
}
|
||||
}
|
||||
|
||||
provider, err := webhook.NewNotifierProvider(&webhook.NotifierProviderConfig{
|
||||
WebhookUrl: access.Url,
|
||||
WebhookData: xmaps.GetOrDefaultString(options.ProviderConfig, "webhookData", access.DataString),
|
||||
Method: access.Method,
|
||||
Headers: mergedHeaders,
|
||||
AllowInsecureConnections: access.AllowInsecureConnections,
|
||||
})
|
||||
return provider, err
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/notifier/providers/wecombot"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := Registries.Register(domain.NotificationProviderTypeWeComBot, func(options *ProviderFactoryOptions) (core.Notifier, error) {
|
||||
access := domain.AccessConfigForWeComBot{}
|
||||
if err := xmaps.Populate(options.AccessConfig, &access); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := wecombot.NewNotifierProvider(&wecombot.NotifierProviderConfig{
|
||||
WebhookUrl: access.WebhookUrl,
|
||||
})
|
||||
return provider, err
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user