app-init
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"mysql-tunnel/internal/config"
|
||||
"mysql-tunnel/internal/logger"
|
||||
"mysql-tunnel/internal/mysqlserver"
|
||||
"mysql-tunnel/internal/navicat"
|
||||
"mysql-tunnel/internal/tunnel"
|
||||
)
|
||||
|
||||
type TestReport struct {
|
||||
TunnelOK bool
|
||||
RemoteOK bool
|
||||
QueryOK bool
|
||||
Lines []string
|
||||
}
|
||||
|
||||
func (r TestReport) OK() bool { return r.TunnelOK && r.RemoteOK && r.QueryOK }
|
||||
|
||||
func (r TestReport) Text() string {
|
||||
s := ""
|
||||
for i, line := range r.Lines {
|
||||
if i > 0 {
|
||||
s += "\r\n"
|
||||
}
|
||||
s += line
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func NewTunnelClient(cfg *config.Config) *navicat.Client {
|
||||
httpClient := tunnel.NewHTTPClient(tunnel.Options{
|
||||
Timeout: cfg.RequestTimeout,
|
||||
TLSInsecureSkipVerify: cfg.TLSInsecureSkipVerify,
|
||||
})
|
||||
return navicat.NewClient(httpClient, navicat.ClientOptions{
|
||||
URL: cfg.TunnelURL,
|
||||
EncodeBase64: cfg.EncodeBase64,
|
||||
Retries: 1,
|
||||
Target: navicat.Target{
|
||||
Host: cfg.RemoteHost,
|
||||
Port: cfg.RemotePort,
|
||||
User: cfg.RemoteUsername,
|
||||
Password: cfg.RemotePassword,
|
||||
Database: cfg.RemoteDatabase,
|
||||
Charset: cfg.RemoteCharset,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func Serve(ctx context.Context, cfg *config.Config, log *logger.Logger) error {
|
||||
navi := NewTunnelClient(cfg)
|
||||
srv := mysqlserver.New(mysqlserver.Options{
|
||||
Host: cfg.ListenHost,
|
||||
Port: cfg.ListenPort,
|
||||
Username: cfg.LocalUsername,
|
||||
Password: cfg.LocalPassword,
|
||||
DefaultDB: cfg.RemoteDatabase,
|
||||
MaxConnections: cfg.MaxConnections,
|
||||
Timeout: cfg.RequestTimeout,
|
||||
LogSQL: cfg.LogSQL,
|
||||
}, navi, log)
|
||||
if log != nil {
|
||||
log.Info("server starting", fmt.Sprintf("%s:%d", cfg.ListenHost, cfg.ListenPort))
|
||||
}
|
||||
return srv.ListenAndServe(ctx)
|
||||
}
|
||||
|
||||
func Test(ctx context.Context, cfg *config.Config, log *logger.Logger) TestReport {
|
||||
var r TestReport
|
||||
navi := NewTunnelClient(cfg)
|
||||
r.Lines = append(r.Lines, "Tunnel URL: "+config.RedactedTunnelURL(cfg.TunnelURL))
|
||||
|
||||
conn, err := navi.Connect(ctx)
|
||||
switch {
|
||||
case err == nil:
|
||||
r.TunnelOK = true
|
||||
r.RemoteOK = true
|
||||
r.Lines = append(r.Lines, "Tunnel: OK", "Remote MySQL: OK")
|
||||
if conn != nil && conn.Header.UncertainVersion {
|
||||
r.Lines = append(r.Lines, fmt.Sprintf(" (script version %d is not 202/206; continuing)", conn.Header.Version))
|
||||
}
|
||||
if log != nil && conn != nil {
|
||||
log.Info("tunnel connect ok", conn.Conn.ServerInfo)
|
||||
}
|
||||
case isTunnelLayer(err):
|
||||
r.Lines = append(r.Lines, "Tunnel: FAIL", " "+logger.RedactError(err), "Remote MySQL: FAIL", "Query SELECT 1: FAIL")
|
||||
if log != nil {
|
||||
log.Error("tunnel connect failed", logger.RedactError(err))
|
||||
}
|
||||
return r
|
||||
default:
|
||||
r.TunnelOK = true
|
||||
r.Lines = append(r.Lines, "Tunnel: OK", "Remote MySQL: FAIL", " "+logger.RedactError(err), "Query SELECT 1: FAIL")
|
||||
if log != nil {
|
||||
log.Error("remote mysql connect failed", logger.RedactError(err))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
q, err := navi.Query(ctx, cfg.RemoteDatabase, "SELECT 1")
|
||||
if err != nil {
|
||||
r.Lines = append(r.Lines, "Query SELECT 1: FAIL", " "+logger.RedactError(err))
|
||||
return r
|
||||
}
|
||||
res, ok := navicat.LastResultSet(q)
|
||||
if !ok || res.IsError() || len(res.Rows) != 1 || len(res.Rows[0]) != 1 || res.Rows[0][0].Text() != "1" {
|
||||
msg := "unexpected result"
|
||||
if res.IsError() {
|
||||
msg = fmt.Sprintf("mysql errno %d: %s", res.Errno, res.Message)
|
||||
}
|
||||
r.Lines = append(r.Lines, "Query SELECT 1: FAIL", " "+msg)
|
||||
return r
|
||||
}
|
||||
r.QueryOK = true
|
||||
r.Lines = append(r.Lines, "Query SELECT 1: OK")
|
||||
if log != nil {
|
||||
log.Info("query completed rows=1")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func isTunnelLayer(err error) bool {
|
||||
ne, ok := err.(*navicat.Error)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if ne.Kind == "http" {
|
||||
return true
|
||||
}
|
||||
switch ne.Errno {
|
||||
case navicat.ErrnoUnsupportedPHP, navicat.ErrnoInvalidParams, navicat.ErrnoMySQLMissing, navicat.ErrnoInvalidToken:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user