Skip to content

Commit

Permalink
feat: implements RelayConfigV2
Browse files Browse the repository at this point in the history
  • Loading branch information
yukimochi committed Mar 21, 2024
1 parent 3d22988 commit 3e2eb60
Show file tree
Hide file tree
Showing 2 changed files with 321 additions and 0 deletions.
178 changes: 178 additions & 0 deletions models/config_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package models

import (
"context"
"crypto/rsa"
"errors"
"fmt"
"net/url"
"time"

"github.com/redis/go-redis/v9"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
)

type ServerConfig struct {
Domain *url.URL
Bind string
PrivateKey *rsa.PrivateKey
}

type ServiceConfig struct {
Name string
Summary string
IconURL *url.URL
ImageURL *url.URL
}

type RelayConfigV2 struct {
serverConfig *ServerConfig
serviceConfig *ServiceConfig
redisOptions *redis.Options
jobConcurrency int
}

type RelayConfigV2BuilderOptions struct {
WithServerConfig bool
WithJobConcurrency bool
}

func buildServerConfig() (*ServerConfig, error) {
domain, err := url.ParseRequestURI("https://" + viper.GetString("RELAY_DOMAIN"))
if err != nil {
return nil, errors.New("RELAY_DOMAIN: " + err.Error())
}

privateKey, err := readPrivateKeyRSA(viper.GetString("ACTOR_PEM"))
if err != nil {
return nil, errors.New("ACTOR_PEM: " + err.Error())
}

return &ServerConfig{
Domain: domain,
Bind: viper.GetString("RELAY_BIND"),
PrivateKey: privateKey,
}, nil
}

func buildServiceConfig() (*ServiceConfig, error) {
// Required fields
name := viper.GetString("RELAY_SERVICENAME")
summary := viper.GetString("RELAY_SUMMARY")

// Optional fields
iconURL, err := url.ParseRequestURI(viper.GetString("RELAY_ICON"))
if err != nil {
logrus.Warn("RELAY_ICON: INVALID OR EMPTY. THIS COLUMN IS DISABLED.")
iconURL = nil
}
imageURL, err := url.ParseRequestURI(viper.GetString("RELAY_IMAGE"))
if err != nil {
logrus.Warn("RELAY_IMAGE: INVALID OR EMPTY. THIS COLUMN IS DISABLED.")
imageURL = nil
}

return &ServiceConfig{
Name: name,
Summary: summary,
IconURL: iconURL,
ImageURL: imageURL,
}, nil
}

func NewRelayConfigV2(options RelayConfigV2BuilderOptions) (*RelayConfigV2, error) {
result := RelayConfigV2{}

serviceOptions, err := buildServiceConfig()
if err != nil {
return nil, err
}
result.serviceConfig = serviceOptions

redisOptions, err := redis.ParseURL(viper.GetString("REDIS_URL"))
if err != nil {
return nil, errors.New("REDIS_URL: " + err.Error())
}
result.redisOptions = redisOptions

// Works with API Server
if options.WithServerConfig {
serverOptions, err := buildServerConfig()
if err != nil {
return nil, err
}
result.serverConfig = serverOptions
} else {
result.serverConfig = nil
}

// Works with Job Worker
if options.WithJobConcurrency {
viper.SetDefault("JOB_CONCURRENCY", 10)
jobConcurrency := viper.GetInt("JOB_CONCURRENCY")
if jobConcurrency < 1 {
return nil, errors.New("JOB_CONCURRENCY: Invalid Value")
}
result.jobConcurrency = jobConcurrency
} else {
result.jobConcurrency = 0
}

return &result, nil
}

// ServerConfig is API Server options.
func (config *RelayConfigV2) ServerConfig() (*ServerConfig, error) {
if config.serverConfig != nil {
return config.serverConfig, nil
}
return nil, errors.New("this configuration does not have ServerConfig")
}

// ServiceConfig is Relay Service options.
func (config *RelayConfigV2) ServiceConfig() *ServiceConfig {
return config.serviceConfig
}

// RedisOptions is Redis options.
func (config *RelayConfigV2) RedisOptions() *redis.Options {
return config.redisOptions
}

// JobConcurrency is Job concurrency.
func (config *RelayConfigV2) JobConcurrency() (int, error) {
if config.jobConcurrency == 0 {
return 0, errors.New("this configuration does not have JobConcurrency")
}
return config.jobConcurrency, nil
}

func (config *RelayConfigV2) DumpWelcomeMessage(moduleName string, version string) string {
message := fmt.Sprintf(`Welcome to Activity-Relay %s - %s\n`, version, moduleName)
message += fmt.Sprintf(`- Configuration\n`)
message += fmt.Sprintf(`RELAY NAME : %s\n`, config.serviceConfig.Name)
message += fmt.Sprintf(`REDIS URL : %s\n`, config.redisOptions.Addr)
if config.serverConfig != nil {
message += fmt.Sprintf(`RELAY DOMAIN : %s\n`, config.serverConfig.Domain.Host)
message += fmt.Sprintf(`BIND ADDRESS : %s\n`, config.serverConfig.Bind)
}
if config.jobConcurrency != 0 {
message += fmt.Sprintf(`JOB_CONCURRENCY : %d\n`, config.jobConcurrency)
}

return message
}

func (config *RelayConfigV2) NewRedisClient(ctx context.Context) (*redis.Client, error) {
redisClient := redis.NewClient(config.redisOptions)

pCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

_, err := redisClient.Ping(pCtx).Result()
if err != nil {
return nil, err
}
return redisClient, nil
}
143 changes: 143 additions & 0 deletions models/config_v2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package models

import (
"context"
"strings"
"testing"

"github.com/spf13/viper"
)

func TestNewRelayConfigV2(t *testing.T) {
t.Run("success generate valid options", func(t *testing.T) {
relayConfig, err := NewRelayConfigV2(RelayConfigV2BuilderOptions{
WithServerConfig: true,
WithJobConcurrency: true,
})
if err != nil {
t.Fatal(err)
}

// ServerConfig
if relayConfig.serverConfig.Domain.Host != "relay.toot.yukimochi.jp" {
t.Error("fail - parse: RelayConfig.serverConfig.Domain")
}
if relayConfig.serverConfig.Bind != "0.0.0.0:8080" {
t.Error("fail - parse: RelayConfig.serverConfig.Bind")
}
if relayConfig.serverConfig.PrivateKey == nil {
t.Error("fail - parse: RelayConfig.serverConfig.PrivateKey")
}

// ServiceConfig
if relayConfig.serviceConfig.Name != "YUKIMOCHI Toot Relay Service" {
t.Error("fail - parse: RelayConfig.serviceConfig.Name")
}
if relayConfig.serviceConfig.Summary != "YUKIMOCHI Toot Relay Service is Running by Activity-Relay" {
t.Error("fail - parse: RelayConfig.serviceConfig.Summary")
}
if relayConfig.serviceConfig.IconURL.String() != "https://example.com/example_icon.png" {
t.Error("fail - parse: RelayConfig.serviceConfig.IconURL")
}
if relayConfig.serviceConfig.ImageURL.String() != "https://example.com/example_image.png" {
t.Error("fail - parse: RelayConfig.serviceConfig.ImageURL")
}

// RedisOptions
if relayConfig.redisOptions == nil {
t.Error("fail - parse: RelayConfig.redisOptions")
}

// JobConcurrency
if relayConfig.jobConcurrency != 50 {
t.Error("fail - parse: RelayConfig.jobConcurrency")
}
})

t.Run("success generate valid options without serverConfig", func(t *testing.T) {
relayConfig, err := NewRelayConfigV2(RelayConfigV2BuilderOptions{
WithServerConfig: false,
WithJobConcurrency: true,
})
if err != nil {
t.Fatal(err)
}

// ServerConfig
if relayConfig.serverConfig != nil {
t.Error("fail - parse: RelayConfig.serverConfig")
}
})

t.Run("success generate valid options without jobConcurrency", func(t *testing.T) {
relayConfig, err := NewRelayConfigV2(RelayConfigV2BuilderOptions{
WithServerConfig: true,
WithJobConcurrency: false,
})
if err != nil {
t.Fatal(err)
}

// JobConcurrency
if relayConfig.jobConcurrency != 0 {
t.Error("fail - parse: RelayConfig.jobConcurrency")
}
})

t.Run("fail invalid options", func(t *testing.T) {
invalidExamples := map[string]string{
"ACTOR_PEM@notFound": "../misc/test/notfound.pem",
"REDIS_URL@invalidURL": "",
}

for key, invalidValue := range invalidExamples {
viperKey := strings.Split(key, "@")[0]
validValue := viper.GetString(viperKey)

viper.Set(viperKey, invalidValue)
_, err := NewRelayConfig()
if err == nil {
t.Error("fail - invalid value should be raise error : " + key)
}

viper.Set(viperKey, validValue)
}
})
}

func TestNewRedisClient(t *testing.T) {
t.Run("success create client for reachable redis serer", func(t *testing.T) {
relayConfig, err := NewRelayConfigV2(RelayConfigV2BuilderOptions{
WithServerConfig: false,
WithJobConcurrency: false,
})
if err != nil {
t.Fatal(err)
}

_, err = relayConfig.NewRedisClient(context.Background())
if err != nil {
t.Error("fail - create client for reachable redis serer")
}
})

t.Run("fail create client for unreachable redis serer", func(t *testing.T) {
validURL := viper.GetString("REDIS_URL")
viper.Set("REDIS_URL", "redis://localhost:6380")

relayConfig, err := NewRelayConfigV2(RelayConfigV2BuilderOptions{
WithServerConfig: false,
WithJobConcurrency: false,
})
if err != nil {
t.Fatal(err)
}

_, err = relayConfig.NewRedisClient(context.Background())
if err == nil {
t.Error("fail - create client for unreachable redis serer")
}

viper.Set("REDIS_URL", validURL)
})
}

0 comments on commit 3e2eb60

Please sign in to comment.