Skip to content

Commit a9bdb89

Browse files
committed
feat: initial commit
0 parents  commit a9bdb89

File tree

15 files changed

+1208
-0
lines changed

15 files changed

+1208
-0
lines changed

.gitignore

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Node dependencies
2+
node_modules/
3+
4+
# Environment files
5+
.env
6+
.env.*
7+
!.env.example
8+
9+
# Generated folders
10+
.build
11+
12+
# Temporary files
13+
*.tmp
14+
*.temp
15+
16+
# IDE files
17+
.idea/
18+
.vscode/
19+
*.swp
20+
*.swo
21+
*.iml
22+
23+
# OS files
24+
.DS_Store
25+
Thumbs.db

cmd/server/main.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log"
6+
"runtime"
7+
8+
"github.com/3box/go-mirror/common/config"
9+
"github.com/3box/go-mirror/common/container"
10+
"github.com/3box/go-mirror/common/logging"
11+
"github.com/3box/go-mirror/server"
12+
)
13+
14+
func main() {
15+
serverCtx := context.Background()
16+
ctr, err := container.BuildContainer(serverCtx)
17+
if err != nil {
18+
log.Fatalf("Failed to build container: %v", err)
19+
}
20+
21+
if err = ctr.Invoke(func(
22+
c *config.Config,
23+
l logging.Logger,
24+
s server.Server,
25+
) error {
26+
l.Infow("starting db api",
27+
"architecture", runtime.GOARCH,
28+
"operating system", runtime.GOOS,
29+
"go version", runtime.Version(),
30+
)
31+
32+
s.Run()
33+
return nil
34+
}); err != nil {
35+
log.Fatalf("Failed to start server: %v", err)
36+
}
37+
}

common/cert/cert.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package cert
2+
3+
import (
4+
"crypto/tls"
5+
"net/http"
6+
)
7+
8+
type CertManager interface {
9+
GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
10+
GetHTTPHandler() http.Handler
11+
}

common/cert/lets_encrypt.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package cert
2+
3+
import (
4+
"crypto/tls"
5+
"net/http"
6+
7+
"golang.org/x/crypto/acme"
8+
"golang.org/x/crypto/acme/autocert"
9+
10+
"github.com/3box/go-mirror/common/config"
11+
"github.com/3box/go-mirror/common/logging"
12+
)
13+
14+
type acmeCertManager struct {
15+
logger logging.Logger
16+
certConfig *config.CertConfig
17+
manager *autocert.Manager
18+
}
19+
20+
func NewACMECertManager(cfg *config.Config, logger logging.Logger) (CertManager, error) {
21+
certConfig := cfg.Cert
22+
if !certConfig.Enabled {
23+
return nil, nil
24+
}
25+
26+
manager := &autocert.Manager{
27+
Cache: autocert.DirCache(certConfig.CacheDir),
28+
Prompt: autocert.AcceptTOS,
29+
HostPolicy: autocert.HostWhitelist(certConfig.Domains...),
30+
}
31+
32+
if certConfig.TestMode {
33+
manager.Client = &acme.Client{
34+
DirectoryURL: "https://acme-staging-v02.api.letsencrypt.org/directory",
35+
}
36+
}
37+
38+
return &acmeCertManager{
39+
logger: logger,
40+
certConfig: &certConfig,
41+
manager: manager,
42+
}, nil
43+
}
44+
45+
func (_this *acmeCertManager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
46+
return _this.manager.GetCertificate(hello)
47+
}
48+
49+
func (_this *acmeCertManager) GetHTTPHandler() http.Handler {
50+
return _this.manager.HTTPHandler(nil)
51+
}

common/config/config.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package config
2+
3+
import (
4+
"strings"
5+
6+
"github.com/spf13/viper"
7+
8+
"github.com/3box/go-mirror/common/logging"
9+
)
10+
11+
type Config struct {
12+
Cert CertConfig
13+
Proxy ProxyConfig
14+
}
15+
16+
type CertConfig struct {
17+
Enabled bool
18+
Domains []string
19+
CacheDir string
20+
TestMode bool
21+
ListenAddr string
22+
}
23+
24+
type ProxyConfig struct {
25+
TargetURL string
26+
MirrorURL string
27+
ListenAddr string
28+
TLSEnabled bool
29+
}
30+
31+
func LoadConfig(logger logging.Logger) (*Config, error) {
32+
// Create a new viper instance with the experimental bind struct feature enabled
33+
v := viper.NewWithOptions(
34+
viper.ExperimentalBindStruct(),
35+
// This was necessary to get viper to recognize the nested struct fields
36+
viper.EnvKeyReplacer(strings.NewReplacer(".", "_")),
37+
)
38+
v.SetEnvPrefix("GO_MIRROR")
39+
v.AutomaticEnv()
40+
41+
// Unmarshal environment variables into the config struct
42+
var cfg Config
43+
if err := v.Unmarshal(&cfg); err != nil {
44+
return nil, err
45+
}
46+
47+
logger.Infow("config loaded successfully",
48+
"config", cfg,
49+
)
50+
51+
return &cfg, nil
52+
}

common/container/container.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package container
2+
3+
import (
4+
"context"
5+
6+
"go.uber.org/dig"
7+
8+
"github.com/3box/go-mirror/common/cert"
9+
"github.com/3box/go-mirror/common/config"
10+
"github.com/3box/go-mirror/common/logging"
11+
"github.com/3box/go-mirror/common/metric"
12+
"github.com/3box/go-mirror/controllers"
13+
"github.com/3box/go-mirror/server"
14+
)
15+
16+
func BuildContainer(ctx context.Context) (*dig.Container, error) {
17+
container := dig.New()
18+
var err error
19+
20+
// Provide logging
21+
if err = container.Provide(logging.NewLogger); err != nil {
22+
return nil, err
23+
}
24+
25+
// Provide context
26+
if err = container.Provide(func() context.Context {
27+
return ctx
28+
}); err != nil {
29+
return nil, err
30+
}
31+
32+
// Provide config
33+
if err = container.Provide(config.LoadConfig); err != nil {
34+
return nil, err
35+
}
36+
37+
// Provide metrics
38+
if err = container.Provide(metric.NewOTelMetricService); err != nil {
39+
return nil, err
40+
}
41+
42+
// Provide cert manager
43+
if err = container.Provide(cert.NewACMECertManager); err != nil {
44+
return nil, err
45+
}
46+
47+
// Provide handlers
48+
if err = container.Provide(controllers.NewProxyController); err != nil {
49+
return nil, err
50+
}
51+
52+
// Provide server
53+
if err = container.Provide(server.NewServer); err != nil {
54+
return nil, err
55+
}
56+
57+
return container, nil
58+
}

common/logging/logger.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package logging
2+
3+
// Logger is the common interface for logging
4+
type Logger interface {
5+
Debugf(template string, args ...interface{})
6+
Debugw(msg string, args ...interface{})
7+
Errorf(template string, args ...interface{})
8+
Errorw(msg string, args ...interface{})
9+
Fatalf(template string, args ...interface{})
10+
Infow(msg string, args ...interface{})
11+
Infof(template string, args ...interface{})
12+
Warnf(template string, args ...interface{})
13+
Warnw(msg string, args ...interface{})
14+
Sync() error
15+
}

common/logging/zap_logger.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package logging
2+
3+
import (
4+
"embed"
5+
"log"
6+
"os"
7+
8+
"gopkg.in/yaml.v3"
9+
10+
"go.uber.org/zap"
11+
)
12+
13+
//go:embed zap_logger.yml
14+
var zapYamlFile embed.FS
15+
16+
func NewLogger() Logger {
17+
configYaml, err := zapYamlFile.ReadFile("zap_logger.yml")
18+
if err != nil {
19+
log.Fatalf("logger: failed to read logger configuration: %s", err)
20+
}
21+
var zapConfig *zap.Config
22+
if err = yaml.Unmarshal(configYaml, &zapConfig); err != nil {
23+
log.Fatalf("logger: failed to unmarshal zap logger configuration: %s", err)
24+
}
25+
26+
level := zap.NewAtomicLevelAt(zap.InfoLevel)
27+
logLevel := os.Getenv("LOG_LEVEL")
28+
if len(logLevel) > 0 {
29+
if parsedLevel, err := zap.ParseAtomicLevel(logLevel); err != nil {
30+
log.Fatalf("logger: error parsing log level %s: %v", logLevel, err)
31+
} else {
32+
level = parsedLevel
33+
}
34+
}
35+
zapConfig.Level = level
36+
zapConfig.Encoding = "json"
37+
baseLogger := zap.Must(zapConfig.Build())
38+
sugaredLogger := baseLogger.Sugar()
39+
return sugaredLogger
40+
}

common/logging/zap_logger.yml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
level: "debug"
2+
encoding: "json"
3+
development: true
4+
encoderConfig:
5+
messageKey: "Msg"
6+
levelKey: "Level"
7+
timeKey: "Time"
8+
nameKey: "Name"
9+
callerKey: "Caller"
10+
stacktraceKey: "St"
11+
levelEncoder: "capital"
12+
timeEncoder: "iso8601"
13+
durationEncoder: "string"
14+
callerEncoder: "short"
15+
outputPaths:
16+
- "stdout"
17+
errorOutputPaths:
18+
- "stdout"

common/metric/metric.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package metric
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/gin-gonic/gin"
8+
9+
"go.opentelemetry.io/otel/attribute"
10+
)
11+
12+
type MetricService interface {
13+
GetPrometheusHandler() gin.HandlerFunc
14+
RecordRequest(ctx context.Context, name, method, path string, attrs ...attribute.KeyValue) error
15+
RecordDuration(ctx context.Context, name string, duration time.Duration, attrs ...attribute.KeyValue) error
16+
}
17+
18+
const (
19+
MetricProxyRequest = "proxy_request"
20+
MetricMirrorRequest = "mirror_request"
21+
)

0 commit comments

Comments
 (0)