mirror of
https://github.com/v2fly/v2ray-core.git
synced 2025-01-02 07:26:24 -05:00
V5: YAML support (rebased from 9367e9b1f2
)
This commit is contained in:
parent
96a4ab8a77
commit
557b0c3353
@ -1,7 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdAPI calls an API in an V2Ray process
|
// CmdAPI calls an API in an V2Ray process
|
||||||
|
@ -3,10 +3,11 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
handlerService "v2ray.com/core/app/proxyman/command"
|
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
"v2ray.com/core/infra/conf"
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
"v2ray.com/core/infra/conf/serial"
|
"github.com/v2fly/v2ray-core/v4/infra/conf"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdAddInbounds = &base.Command{
|
var cmdAddInbounds = &base.Command{
|
||||||
@ -42,7 +43,7 @@ func executeAddInbounds(cmd *base.Command, args []string) {
|
|||||||
|
|
||||||
ins := make([]conf.InboundDetourConfig, 0)
|
ins := make([]conf.InboundDetourConfig, 0)
|
||||||
for _, arg := range unnamedArgs {
|
for _, arg := range unnamedArgs {
|
||||||
r, err := loadArg(arg)
|
r, err := cmdarg.LoadArg(arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to load %s: %s", arg, err)
|
base.Fatalf("failed to load %s: %s", arg, err)
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
handlerService "v2ray.com/core/app/proxyman/command"
|
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
"v2ray.com/core/infra/conf/serial"
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdRemoveInbounds = &base.Command{
|
var cmdRemoveInbounds = &base.Command{
|
||||||
@ -41,7 +42,7 @@ func executeRemoveInbounds(cmd *base.Command, args []string) {
|
|||||||
|
|
||||||
tags := make([]string, 0)
|
tags := make([]string, 0)
|
||||||
for _, arg := range unnamedArgs {
|
for _, arg := range unnamedArgs {
|
||||||
if r, err := loadArg(arg); err == nil {
|
if r, err := cmdarg.LoadArg(arg); err == nil {
|
||||||
conf, err := serial.DecodeJSONConfig(r)
|
conf, err := serial.DecodeJSONConfig(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to decode %s: %s", arg, err)
|
base.Fatalf("failed to decode %s: %s", arg, err)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
logService "v2ray.com/core/app/log/command"
|
logService "github.com/v2fly/v2ray-core/v4/app/log/command"
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdRestartLogger = &base.Command{
|
var cmdRestartLogger = &base.Command{
|
||||||
|
@ -3,10 +3,11 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
handlerService "v2ray.com/core/app/proxyman/command"
|
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
"v2ray.com/core/infra/conf"
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
"v2ray.com/core/infra/conf/serial"
|
"github.com/v2fly/v2ray-core/v4/infra/conf"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdAddOutbounds = &base.Command{
|
var cmdAddOutbounds = &base.Command{
|
||||||
@ -42,7 +43,7 @@ func executeAddOutbounds(cmd *base.Command, args []string) {
|
|||||||
|
|
||||||
outs := make([]conf.OutboundDetourConfig, 0)
|
outs := make([]conf.OutboundDetourConfig, 0)
|
||||||
for _, arg := range unnamedArgs {
|
for _, arg := range unnamedArgs {
|
||||||
r, err := loadArg(arg)
|
r, err := cmdarg.LoadArg(arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to load %s: %s", arg, err)
|
base.Fatalf("failed to load %s: %s", arg, err)
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
handlerService "v2ray.com/core/app/proxyman/command"
|
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
"v2ray.com/core/infra/conf/serial"
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdRemoveOutbounds = &base.Command{
|
var cmdRemoveOutbounds = &base.Command{
|
||||||
@ -41,7 +42,7 @@ func executeRemoveOutbounds(cmd *base.Command, args []string) {
|
|||||||
|
|
||||||
tags := make([]string, 0)
|
tags := make([]string, 0)
|
||||||
for _, arg := range unnamedArgs {
|
for _, arg := range unnamedArgs {
|
||||||
if r, err := loadArg(arg); err == nil {
|
if r, err := cmdarg.LoadArg(arg); err == nil {
|
||||||
conf, err := serial.DecodeJSONConfig(r)
|
conf, err := serial.DecodeJSONConfig(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to decode %s: %s", arg, err)
|
base.Fatalf("failed to decode %s: %s", arg, err)
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"v2ray.com/core/commands/base"
|
|
||||||
"v2ray.com/core/common/buf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, cmd *base.Command, args []string) string
|
type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, cmd *base.Command, args []string) string
|
||||||
@ -45,63 +38,6 @@ func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func())
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadArg loads one arg, maybe an remote url, or local file path
|
|
||||||
func loadArg(arg string) (out io.Reader, err error) {
|
|
||||||
var data []byte
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
|
||||||
data, err = fetchHTTPContent(arg)
|
|
||||||
|
|
||||||
case arg == "stdin:":
|
|
||||||
data, err = ioutil.ReadAll(os.Stdin)
|
|
||||||
|
|
||||||
default:
|
|
||||||
data, err = ioutil.ReadFile(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out = bytes.NewBuffer(data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchHTTPContent dials https for remote content
|
|
||||||
func fetchHTTPContent(target string) ([]byte, error) {
|
|
||||||
parsedTarget, err := url.Parse(target)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" {
|
|
||||||
return nil, fmt.Errorf("invalid scheme: %s", parsedTarget.Scheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
resp, err := client.Do(&http.Request{
|
|
||||||
Method: "GET",
|
|
||||||
URL: parsedTarget,
|
|
||||||
Close: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to dial to %s", target)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := buf.ReadAllToBytes(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read HTTP response")
|
|
||||||
}
|
|
||||||
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func showResponese(m proto.Message) {
|
func showResponese(m proto.Message) {
|
||||||
msg := ""
|
msg := ""
|
||||||
bs, err := proto.Marshal(m)
|
bs, err := proto.Marshal(m)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
statsService "v2ray.com/core/app/stats/command"
|
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdGetStats = &base.Command{
|
var cmdGetStats = &base.Command{
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
statsService "v2ray.com/core/app/stats/command"
|
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdQueryStats = &base.Command{
|
var cmdQueryStats = &base.Command{
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
statsService "v2ray.com/core/app/stats/command"
|
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdSysStats = &base.Command{
|
var cmdSysStats = &base.Command{
|
||||||
|
@ -17,7 +17,6 @@ func init() {
|
|||||||
tls.CmdTLS,
|
tls.CmdTLS,
|
||||||
cmdUUID,
|
cmdUUID,
|
||||||
cmdVerify,
|
cmdVerify,
|
||||||
cmdMerge,
|
|
||||||
|
|
||||||
// documents
|
// documents
|
||||||
docFormat,
|
docFormat,
|
||||||
|
@ -2,33 +2,54 @@ package all
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
"google.golang.org/protobuf/proto"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdConvert = &base.Command{
|
var cmdConvert = &base.Command{
|
||||||
UsageLine: "{{.Exec}} convert [-r] [c1.json] [<url>.json] [dir1] ...",
|
CustomFlags: true,
|
||||||
Short: "Convert multiple json config to protobuf",
|
UsageLine: "{{.Exec}} convert [c1.json] [<url>.json] [dir1] ...",
|
||||||
|
Short: "Convert config files",
|
||||||
Long: `
|
Long: `
|
||||||
Convert JSON config to protobuf.
|
Convert config files between different formats. Files are merged
|
||||||
|
before convert if multiple assigned.
|
||||||
If multiple JSON files or folders specified, it merges them first, then convert.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
|
-i, -input
|
||||||
|
Specify the input format.
|
||||||
|
Available values: "json", "yaml"
|
||||||
|
Default: "json"
|
||||||
|
|
||||||
|
-o, -output
|
||||||
|
Specify the output format
|
||||||
|
Available values: "json", "yaml", "protobuf" / "pb"
|
||||||
|
Default: "json"
|
||||||
|
|
||||||
-r
|
-r
|
||||||
Load confdir recursively.
|
Load confdir recursively.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
{{.Exec}} {{.LongName}} config.json
|
{{.Exec}} {{.LongName}} -output=protobuf config.json (1)
|
||||||
{{.Exec}} {{.LongName}} c1.json c2.json
|
{{.Exec}} {{.LongName}} -output=yaml config.json (2)
|
||||||
{{.Exec}} {{.LongName}} c1.json https://url.to/c2.json
|
{{.Exec}} {{.LongName}} -input=yaml config.yaml (3)
|
||||||
{{.Exec}} {{.LongName}} "path/to/json_dir"
|
{{.Exec}} {{.LongName}} "path/to/dir" (4)
|
||||||
|
{{.Exec}} {{.LongName}} -i yaml -o protobuf c1.yaml <url>.yaml (5)
|
||||||
|
|
||||||
|
(1) Convert json to protobuf
|
||||||
|
(2) Convert json to yaml
|
||||||
|
(3) Convert yaml to json
|
||||||
|
(4) Merge json files in dir
|
||||||
|
(5) Merge yaml files and convert to protobuf
|
||||||
|
|
||||||
|
Use "{{.Exec}} help config-merge" for more information about merge.
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,36 +57,76 @@ func init() {
|
|||||||
cmdConvert.Run = executeConvert // break init loop
|
cmdConvert.Run = executeConvert // break init loop
|
||||||
}
|
}
|
||||||
|
|
||||||
var convertReadDirRecursively = cmdConvert.Flag.Bool("r", false, "")
|
var (
|
||||||
|
inputFormat string
|
||||||
|
outputFormat string
|
||||||
|
confDirRecursively bool
|
||||||
|
)
|
||||||
|
var formatExtensions = map[string][]string{
|
||||||
|
"json": {".json", ".jsonc"},
|
||||||
|
"yaml": {".yaml", ".yml"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func setConfArgs(cmd *base.Command) {
|
||||||
|
cmd.Flag.StringVar(&inputFormat, "input", "json", "")
|
||||||
|
cmd.Flag.StringVar(&inputFormat, "i", "json", "")
|
||||||
|
cmd.Flag.StringVar(&outputFormat, "output", "json", "")
|
||||||
|
cmd.Flag.StringVar(&outputFormat, "o", "json", "")
|
||||||
|
cmd.Flag.BoolVar(&confDirRecursively, "r", true, "")
|
||||||
|
}
|
||||||
func executeConvert(cmd *base.Command, args []string) {
|
func executeConvert(cmd *base.Command, args []string) {
|
||||||
|
setConfArgs(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
unnamed := cmd.Flag.Args()
|
unnamed := cmd.Flag.Args()
|
||||||
files := resolveFolderToFiles(unnamed, *convertReadDirRecursively)
|
inputFormat = strings.ToLower(inputFormat)
|
||||||
|
outputFormat = strings.ToLower(outputFormat)
|
||||||
|
|
||||||
|
files := resolveFolderToFiles(unnamed, formatExtensions[inputFormat], confDirRecursively)
|
||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
base.Fatalf("empty config list")
|
base.Fatalf("empty config list")
|
||||||
}
|
}
|
||||||
|
m := mergeConvertToMap(files, inputFormat)
|
||||||
|
|
||||||
data, err := merge.FilesToJSON(files)
|
var (
|
||||||
if err != nil {
|
out []byte
|
||||||
base.Fatalf("failed to load json: %s", err)
|
err error
|
||||||
}
|
)
|
||||||
r := bytes.NewReader(data)
|
switch outputFormat {
|
||||||
cf, err := serial.DecodeJSONConfig(r)
|
case "json":
|
||||||
if err != nil {
|
out, err = json.Marshal(m)
|
||||||
base.Fatalf("failed to decode json: %s", err)
|
if err != nil {
|
||||||
|
base.Fatalf("failed to marshal json: %s", err)
|
||||||
|
}
|
||||||
|
case "yaml":
|
||||||
|
out, err = yaml.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to marshal json: %s", err)
|
||||||
|
}
|
||||||
|
case "pb", "protobuf":
|
||||||
|
data, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to marshal json: %s", err)
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
cf, err := serial.DecodeJSONConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to decode json: %s", err)
|
||||||
|
}
|
||||||
|
pbConfig, err := cf.Build()
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
out, err = proto.Marshal(pbConfig)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to marshal proto config: %s", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
base.Errorf("invalid output format: %s", outputFormat)
|
||||||
|
base.Errorf("Run '%s help %s' for details.", base.CommandEnv.Exec, cmd.LongName())
|
||||||
|
base.Exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
pbConfig, err := cf.Build()
|
if _, err := os.Stdout.Write(out); err != nil {
|
||||||
if err != nil {
|
|
||||||
base.Fatalf(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
bytesConfig, err := proto.Marshal(pbConfig)
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to marshal proto config: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stdout.Write(bytesConfig); err != nil {
|
|
||||||
base.Fatalf("failed to write proto config: %s", err)
|
base.Fatalf("failed to write proto config: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
113
commands/all/convert_confs.go
Normal file
113
commands/all/convert_confs.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mergeConvertToMap(files []string, format string) map[string]interface{} {
|
||||||
|
var (
|
||||||
|
m map[string]interface{}
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch inputFormat {
|
||||||
|
case "json":
|
||||||
|
m, err = merge.FilesToMap(files)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to load json: %s", err)
|
||||||
|
}
|
||||||
|
case "yaml":
|
||||||
|
bs, err := yamlsToJSONs(files)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to convert yaml to json: %s", err)
|
||||||
|
}
|
||||||
|
m, err = merge.BytesToMap(bs)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to merge converted json: %s", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
base.Errorf("invalid input format: %s", format)
|
||||||
|
base.Errorf("Run '%s help %s' for details.", base.CommandEnv.Exec, cmdConvert.LongName())
|
||||||
|
base.Exit()
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveFolderToFiles expands folder path (if any and it exists) to file paths.
|
||||||
|
// Any other paths, like file, even URL, it returns them as is.
|
||||||
|
func resolveFolderToFiles(paths []string, extensions []string, recursively bool) []string {
|
||||||
|
dirReader := readConfDir
|
||||||
|
if recursively {
|
||||||
|
dirReader = readConfDirRecursively
|
||||||
|
}
|
||||||
|
files := make([]string, 0)
|
||||||
|
for _, p := range paths {
|
||||||
|
i, err := os.Stat(p)
|
||||||
|
if err == nil && i.IsDir() {
|
||||||
|
files = append(files, dirReader(p, extensions)...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files = append(files, p)
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfDir(dirPath string, extensions []string) []string {
|
||||||
|
confs, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to read dir %s: %s", dirPath, err)
|
||||||
|
}
|
||||||
|
files := make([]string, 0)
|
||||||
|
for _, f := range confs {
|
||||||
|
ext := filepath.Ext(f.Name())
|
||||||
|
for _, e := range extensions {
|
||||||
|
if strings.EqualFold(ext, e) {
|
||||||
|
files = append(files, filepath.Join(dirPath, f.Name()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFolderFiles get files in the folder and it's children
|
||||||
|
func readConfDirRecursively(dirPath string, extensions []string) []string {
|
||||||
|
files := make([]string, 0)
|
||||||
|
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
ext := filepath.Ext(path)
|
||||||
|
for _, e := range extensions {
|
||||||
|
if strings.EqualFold(ext, e) {
|
||||||
|
files = append(files, path)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to read dir %s: %s", dirPath, err)
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
func yamlsToJSONs(files []string) ([][]byte, error) {
|
||||||
|
jsons := make([][]byte, 0)
|
||||||
|
for _, file := range files {
|
||||||
|
bs, err := cmdarg.LoadArgToBytes(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
j, err := json.FromYAML(bs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
jsons = append(jsons, j)
|
||||||
|
}
|
||||||
|
return jsons, nil
|
||||||
|
}
|
@ -14,7 +14,7 @@ var docFormat = &base.Command{
|
|||||||
The default loader, multiple config files support.
|
The default loader, multiple config files support.
|
||||||
|
|
||||||
* yaml (.yml)
|
* yaml (.yml)
|
||||||
The yaml loader (coming soon?), multiple config files support.
|
The yaml loader, multiple config files support.
|
||||||
|
|
||||||
* protobuf / pb (.pb)
|
* protobuf / pb (.pb)
|
||||||
Single conifg file support. If multiple files assigned,
|
Single conifg file support. If multiple files assigned,
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
package all
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/commands/base"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cmdMerge = &base.Command{
|
|
||||||
UsageLine: "{{.Exec}} merge [-r] [c1.json] [url] [dir1] ...",
|
|
||||||
Short: "Merge json files into one",
|
|
||||||
Long: `
|
|
||||||
Merge JSON files into one.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
-r
|
|
||||||
Load confdir recursively.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
{{.Exec}} {{.LongName}} c1.json c2.json
|
|
||||||
{{.Exec}} {{.LongName}} c1.json https://url.to/c2.json
|
|
||||||
{{.Exec}} {{.LongName}} "path/to/json_dir"
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cmdMerge.Run = executeMerge
|
|
||||||
}
|
|
||||||
|
|
||||||
var mergeReadDirRecursively = cmdMerge.Flag.Bool("r", false, "")
|
|
||||||
|
|
||||||
func executeMerge(cmd *base.Command, args []string) {
|
|
||||||
unnamed := cmd.Flag.Args()
|
|
||||||
files := resolveFolderToFiles(unnamed, *mergeReadDirRecursively)
|
|
||||||
if len(files) == 0 {
|
|
||||||
base.Fatalf("empty config list")
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := merge.FilesToJSON(files)
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf(err.Error())
|
|
||||||
}
|
|
||||||
if _, err := os.Stdout.Write(data); err != nil {
|
|
||||||
base.Fatalf(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveFolderToFiles expands folder path (if any and it exists) to file paths.
|
|
||||||
// Any other paths, like file, even URL, it returns them as is.
|
|
||||||
func resolveFolderToFiles(paths []string, recursively bool) []string {
|
|
||||||
dirReader := readConfDir
|
|
||||||
if recursively {
|
|
||||||
dirReader = readConfDirRecursively
|
|
||||||
}
|
|
||||||
files := make([]string, 0)
|
|
||||||
for _, p := range paths {
|
|
||||||
i, err := os.Stat(p)
|
|
||||||
if err == nil && i.IsDir() {
|
|
||||||
files = append(files, dirReader(p)...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files = append(files, p)
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfDir(dirPath string) []string {
|
|
||||||
confs, err := ioutil.ReadDir(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to read dir %s: %s", dirPath, err)
|
|
||||||
}
|
|
||||||
files := make([]string, 0)
|
|
||||||
for _, f := range confs {
|
|
||||||
ext := filepath.Ext(f.Name())
|
|
||||||
if ext == ".json" || ext == ".jsonc" {
|
|
||||||
files = append(files, filepath.Join(dirPath, f.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFolderFiles get files in the folder and it's children
|
|
||||||
func readConfDirRecursively(dirPath string) []string {
|
|
||||||
files := make([]string, 0)
|
|
||||||
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
ext := filepath.Ext(path)
|
|
||||||
if ext == ".json" || ext == ".jsonc" {
|
|
||||||
files = append(files, path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to read dir %s: %s", dirPath, err)
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
@ -5,15 +5,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var docMerge = &base.Command{
|
var docMerge = &base.Command{
|
||||||
UsageLine: "{{.Exec}} json-merge",
|
UsageLine: "{{.Exec}} config-merge",
|
||||||
Short: "json merge logic",
|
Short: "config merge logic",
|
||||||
Long: `
|
Long: `
|
||||||
Merging of JSON configs is applied in following commands:
|
Merging of config files is applied in following commands:
|
||||||
|
|
||||||
{{.Exec}} run -c c1.json -c c2.json ...
|
{{.Exec}} run -c c1.json -c c2.json ...
|
||||||
{{.Exec}} merge c1.json https://url.to/c2.json ...
|
{{.Exec}} test -c c1.yaml -c c2.yaml ...
|
||||||
{{.Exec}} convert c1.json dir1 ...
|
{{.Exec}} convert c1.json dir1 ...
|
||||||
|
|
||||||
|
Support of yaml is implemented by converting yaml to json,
|
||||||
|
both merge and load. So we take json as example here.
|
||||||
|
|
||||||
Suppose we have 2 JSON files,
|
Suppose we have 2 JSON files,
|
||||||
|
|
||||||
The 1st one:
|
The 1st one:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"v2ray.com/core/commands/base"
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdTLS holds all tls sub commands
|
// CmdTLS holds all tls sub commands
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package merge
|
package cmdarg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -11,29 +11,36 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/buf"
|
"github.com/v2fly/v2ray-core/v4/common/buf"
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadArg loads one arg, maybe an remote url, or local file path
|
// LoadArg loads one arg, maybe an remote url, or local file path
|
||||||
func loadArg(arg string) (out io.Reader, err error) {
|
func LoadArg(arg string) (out io.Reader, err error) {
|
||||||
var data []byte
|
bs, err := LoadArgToBytes(arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = bytes.NewBuffer(bs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadArgToBytes loads one arg to []byte, maybe an remote url, or local file path
|
||||||
|
func LoadArgToBytes(arg string) (out []byte, err error) {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
||||||
data, err = fetchHTTPContent(arg)
|
out, err = FetchHTTPContent(arg)
|
||||||
case (arg == "stdin:"):
|
case (arg == "stdin:"):
|
||||||
data, err = ioutil.ReadAll(os.Stdin)
|
out, err = ioutil.ReadAll(os.Stdin)
|
||||||
default:
|
default:
|
||||||
data, err = ioutil.ReadFile(arg)
|
out, err = ioutil.ReadFile(arg)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
out = bytes.NewBuffer(data)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchHTTPContent dials https for remote content
|
// FetchHTTPContent dials https for remote content
|
||||||
func fetchHTTPContent(target string) ([]byte, error) {
|
func FetchHTTPContent(target string) ([]byte, error) {
|
||||||
parsedTarget, err := url.Parse(target)
|
parsedTarget, err := url.Parse(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("invalid URL: ", target).Base(err)
|
return nil, newError("invalid URL: ", target).Base(err)
|
||||||
@ -67,12 +74,3 @@ func fetchHTTPContent(target string) ([]byte, error) {
|
|||||||
|
|
||||||
return content, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decode(r io.Reader) (map[string]interface{}, error) {
|
|
||||||
c := make(map[string]interface{})
|
|
||||||
err := serial.DecodeJSON(r, &c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package jsonem
|
package cmdarg
|
||||||
|
|
||||||
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/v2fly/v2ray-core/v4/common"
|
"github.com/v2fly/v2ray-core/v4/common"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/buf"
|
"github.com/v2fly/v2ray-core/v4/common/buf"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
"github.com/v2fly/v2ray-core/v4/main/confloader"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigFormat is a configurable format of V2Ray config file.
|
// ConfigFormat is a configurable format of V2Ray config file.
|
||||||
@ -109,7 +108,7 @@ func init() {
|
|||||||
Loader: func(input interface{}) (*Config, error) {
|
Loader: func(input interface{}) (*Config, error) {
|
||||||
switch v := input.(type) {
|
switch v := input.(type) {
|
||||||
case cmdarg.Arg:
|
case cmdarg.Arg:
|
||||||
r, err := confloader.LoadConfig(v[0])
|
r, err := cmdarg.LoadArg(v[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
47
go.mod
47
go.mod
@ -1,51 +1,28 @@
|
|||||||
module github.com/v2fly/v2ray-core/v4
|
module github.com/v2fly/v2ray-core/v4
|
||||||
|
|
||||||
go 1.17
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/google/go-cmp v0.5.6
|
github.com/google/go-cmp v0.5.6
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/jhump/protoreflect v1.9.0
|
github.com/jhump/protoreflect v1.8.2
|
||||||
github.com/lucas-clemente/quic-go v0.23.0
|
github.com/lucas-clemente/quic-go v0.21.1
|
||||||
github.com/miekg/dns v1.1.43
|
github.com/miekg/dns v1.1.42
|
||||||
github.com/pires/go-proxyproto v0.6.0
|
github.com/pires/go-proxyproto v0.5.0
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c
|
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08
|
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08
|
||||||
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848
|
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
||||||
go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a
|
go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55
|
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273
|
||||||
google.golang.org/grpc v1.40.0
|
google.golang.org/grpc v1.38.0
|
||||||
google.golang.org/protobuf v1.27.1
|
google.golang.org/protobuf v1.26.0
|
||||||
h12.io/socks v1.0.3
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
h12.io/socks v1.0.2
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/cheekybits/genny v1.0.0 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect
|
|
||||||
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
|
|
||||||
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
|
|
||||||
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
|
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
|
||||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
|
||||||
github.com/xtaci/smux v1.5.15 // indirect
|
|
||||||
golang.org/x/mod v0.4.2 // indirect
|
|
||||||
golang.org/x/text v0.3.6 // indirect
|
|
||||||
golang.org/x/tools v0.1.1 // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect
|
|
||||||
)
|
)
|
||||||
|
49
infra/conf/json/yaml.go
Normal file
49
infra/conf/json/yaml.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FromYAML convert yaml to json
|
||||||
|
func FromYAML(v []byte) ([]byte, error) {
|
||||||
|
m1 := make(map[interface{}]interface{})
|
||||||
|
err := yaml.Unmarshal(v, &m1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m2 := convert(m1)
|
||||||
|
j, err := json.Marshal(m2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return j, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert(m map[interface{}]interface{}) map[string]interface{} {
|
||||||
|
res := map[string]interface{}{}
|
||||||
|
for k, v := range m {
|
||||||
|
var value interface{}
|
||||||
|
switch v2 := v.(type) {
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
value = convert(v2)
|
||||||
|
case []interface{}:
|
||||||
|
for i, el := range v2 {
|
||||||
|
if m, ok := el.(map[interface{}]interface{}); ok {
|
||||||
|
v2[i] = convert(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = v2
|
||||||
|
default:
|
||||||
|
value = v
|
||||||
|
}
|
||||||
|
key := "null"
|
||||||
|
if k != nil {
|
||||||
|
key = fmt.Sprint(k)
|
||||||
|
}
|
||||||
|
res[key] = value
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
145
infra/conf/json/yaml_test.go
Normal file
145
infra/conf/json/yaml_test.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestYMLToJSON_V2Style(t *testing.T) {
|
||||||
|
input := `
|
||||||
|
log:
|
||||||
|
loglevel: debug
|
||||||
|
inbounds:
|
||||||
|
- port: 10800
|
||||||
|
listen: 127.0.0.1
|
||||||
|
protocol: socks
|
||||||
|
settings:
|
||||||
|
udp: true
|
||||||
|
outbounds:
|
||||||
|
- protocol: vmess
|
||||||
|
settings:
|
||||||
|
vnext:
|
||||||
|
- address: example.com
|
||||||
|
port: 443
|
||||||
|
users:
|
||||||
|
- id: '98a15fa6-2eb1-edd5-50ea-cfc428aaab78'
|
||||||
|
streamSettings:
|
||||||
|
network: tcp
|
||||||
|
security: tls
|
||||||
|
`
|
||||||
|
expected := `
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "debug"
|
||||||
|
},
|
||||||
|
"inbounds": [{
|
||||||
|
"port": 10800,
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"protocol": "socks",
|
||||||
|
"settings": {
|
||||||
|
"udp": true
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"outbounds": [{
|
||||||
|
"protocol": "vmess",
|
||||||
|
"settings": {
|
||||||
|
"vnext": [{
|
||||||
|
"port": 443,
|
||||||
|
"address": "example.com",
|
||||||
|
"users": [{
|
||||||
|
"id": "98a15fa6-2eb1-edd5-50ea-cfc428aaab78"
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "tcp",
|
||||||
|
"security": "tls"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
bs, err := FromYAML([]byte(input))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
json.Unmarshal(bs, &m)
|
||||||
|
assertResult(t, m, expected)
|
||||||
|
}
|
||||||
|
func TestYMLToJSON_ValueTypes(t *testing.T) {
|
||||||
|
input := `
|
||||||
|
boolean:
|
||||||
|
- TRUE
|
||||||
|
- FALSE
|
||||||
|
- true
|
||||||
|
- false
|
||||||
|
float:
|
||||||
|
- 3.14
|
||||||
|
- 6.8523015e+5
|
||||||
|
int:
|
||||||
|
- 123
|
||||||
|
- 0b1010_0111_0100_1010_1110
|
||||||
|
null:
|
||||||
|
nodeName: 'node'
|
||||||
|
parent: ~ # ~ for null
|
||||||
|
string:
|
||||||
|
- 哈哈
|
||||||
|
- 'Hello world'
|
||||||
|
- newline
|
||||||
|
newline2 # multi-line string
|
||||||
|
date:
|
||||||
|
- 2018-02-17 # yyyy-MM-dd
|
||||||
|
datetime:
|
||||||
|
- 2018-02-17T15:02:31+08:00 # ISO 8601 time
|
||||||
|
mixed:
|
||||||
|
- true
|
||||||
|
- false
|
||||||
|
- 1
|
||||||
|
- 0
|
||||||
|
- null
|
||||||
|
- hello
|
||||||
|
# arbitrary keys
|
||||||
|
1: 0
|
||||||
|
true: false
|
||||||
|
TRUE: TRUE
|
||||||
|
"str": "hello"
|
||||||
|
`
|
||||||
|
expected := `
|
||||||
|
{
|
||||||
|
"boolean": [true, false, true, false],
|
||||||
|
"float": [3.14, 685230.15],
|
||||||
|
"int": [123, 685230],
|
||||||
|
"null": {
|
||||||
|
"nodeName": "node",
|
||||||
|
"parent": null
|
||||||
|
},
|
||||||
|
"string": ["哈哈", "Hello world", "newline newline2"],
|
||||||
|
"date": ["2018-02-17"],
|
||||||
|
"datetime": ["2018-02-17T15:02:31+08:00"],
|
||||||
|
"mixed": [true,false,1,0,null,"hello"],
|
||||||
|
"1": 0,
|
||||||
|
"true": true,
|
||||||
|
"str": "hello"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
bs, err := FromYAML([]byte(input))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
json.Unmarshal(bs, &m)
|
||||||
|
assertResult(t, m, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertResult(t *testing.T, value map[string]interface{}, expected string) {
|
||||||
|
e := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal([]byte(expected), &e)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(value, e) {
|
||||||
|
bs, _ := json.Marshal(value)
|
||||||
|
t.Fatalf("expected:\n%s\n\nactual:\n%s", expected, string(bs))
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,10 @@ package merge
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FilesToJSON merges multiple jsons files into one json, accepts remote url, or local file path
|
// FilesToJSON merges multiple jsons files into one json, accepts remote url, or local file path
|
||||||
@ -65,9 +69,9 @@ func BytesToMap(args [][]byte) (m map[string]interface{}, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadFiles(args []string) (map[string]interface{}, error) {
|
func loadFiles(args []string) (map[string]interface{}, error) {
|
||||||
conf := make(map[string]interface{})
|
c := make(map[string]interface{})
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
r, err := loadArg(arg)
|
r, err := cmdarg.LoadArg(arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -75,11 +79,11 @@ func loadFiles(args []string) (map[string]interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = mergeMaps(conf, m); err != nil {
|
if err = mergeMaps(c, m); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return conf, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadBytes(args [][]byte) (map[string]interface{}, error) {
|
func loadBytes(args [][]byte) (map[string]interface{}, error) {
|
||||||
@ -96,3 +100,12 @@ func loadBytes(args [][]byte) (map[string]interface{}, error) {
|
|||||||
}
|
}
|
||||||
return conf, nil
|
return conf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decode(r io.Reader) (map[string]interface{}, error) {
|
||||||
|
c := make(map[string]interface{})
|
||||||
|
err := serial.DecodeJSON(r, &c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
@ -56,7 +56,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func setConfigFlags(cmd *base.Command) {
|
func setConfigFlags(cmd *base.Command) {
|
||||||
configFormat = cmd.Flag.String("format", "json", "")
|
configFormat = cmd.Flag.String("format", "", "")
|
||||||
configDirRecursively = cmd.Flag.Bool("r", false, "")
|
configDirRecursively = cmd.Flag.Bool("r", false, "")
|
||||||
|
|
||||||
cmd.Flag.Var(&configFiles, "config", "")
|
cmd.Flag.Var(&configFiles, "config", "")
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package confloader
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
configFileLoader func(string) (io.Reader, error)
|
|
||||||
extconfigLoader func([]string, io.Reader) (io.Reader, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
EffectiveConfigFileLoader configFileLoader
|
|
||||||
EffectiveExtConfigLoader extconfigLoader
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadConfig reads from a path/url/stdin
|
|
||||||
// actual work is in external module
|
|
||||||
func LoadConfig(file string) (io.Reader, error) {
|
|
||||||
if EffectiveConfigFileLoader == nil {
|
|
||||||
newError("external config module not loaded, reading from stdin").AtInfo().WriteToLog()
|
|
||||||
return os.Stdin, nil
|
|
||||||
}
|
|
||||||
return EffectiveConfigFileLoader(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadExtConfig calls v2ctl to handle multiple config
|
|
||||||
// the actual work also in external module
|
|
||||||
func LoadExtConfig(files []string, reader io.Reader) (io.Reader, error) {
|
|
||||||
if EffectiveExtConfigLoader == nil {
|
|
||||||
return nil, newError("external config module not loaded").AtError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return EffectiveExtConfigLoader(files, reader)
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package confloader
|
|
||||||
|
|
||||||
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
|
||||||
|
|
||||||
type errPathObjHolder struct{}
|
|
||||||
|
|
||||||
func newError(values ...interface{}) *errors.Error {
|
|
||||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
|
||||||
}
|
|
9
main/confloader/external/errors.generated.go
vendored
9
main/confloader/external/errors.generated.go
vendored
@ -1,9 +0,0 @@
|
|||||||
package external
|
|
||||||
|
|
||||||
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
|
||||||
|
|
||||||
type errPathObjHolder struct{}
|
|
||||||
|
|
||||||
func newError(values ...interface{}) *errors.Error {
|
|
||||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
|
||||||
}
|
|
87
main/confloader/external/external.go
vendored
87
main/confloader/external/external.go
vendored
@ -1,87 +0,0 @@
|
|||||||
package external
|
|
||||||
|
|
||||||
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/buf"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/platform/ctlcmd"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/confloader"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ConfigLoader(arg string) (out io.Reader, err error) {
|
|
||||||
var data []byte
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
|
||||||
data, err = FetchHTTPContent(arg)
|
|
||||||
|
|
||||||
case arg == "stdin:":
|
|
||||||
data, err = ioutil.ReadAll(os.Stdin)
|
|
||||||
|
|
||||||
default:
|
|
||||||
data, err = ioutil.ReadFile(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out = bytes.NewBuffer(data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func FetchHTTPContent(target string) ([]byte, error) {
|
|
||||||
parsedTarget, err := url.Parse(target)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("invalid URL: ", target).Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" {
|
|
||||||
return nil, newError("invalid scheme: ", parsedTarget.Scheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
resp, err := client.Do(&http.Request{
|
|
||||||
Method: "GET",
|
|
||||||
URL: parsedTarget,
|
|
||||||
Close: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to dial to ", target).Base(err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return nil, newError("unexpected HTTP status code: ", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := buf.ReadAllToBytes(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to read HTTP response").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) {
|
|
||||||
buf, err := ctlcmd.Run(append([]string{"convert"}, files...), reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.NewReader(buf.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
confloader.EffectiveConfigFileLoader = ConfigLoader
|
|
||||||
confloader.EffectiveExtConfigLoader = ExtConfigLoader
|
|
||||||
}
|
|
@ -74,10 +74,10 @@ import (
|
|||||||
// The following line loads JSON from v2ctl
|
// The following line loads JSON from v2ctl
|
||||||
// _ "github.com/v2fly/v2ray-core/v4/main/json"
|
// _ "github.com/v2fly/v2ray-core/v4/main/json"
|
||||||
// The following line loads JSON internally
|
// The following line loads JSON internally
|
||||||
_ "github.com/v2fly/v2ray-core/v4/main/jsonem"
|
_ "github.com/v2fly/v2ray-core/v4/main/json"
|
||||||
|
|
||||||
// Load config from file or http(s)
|
// YAML config support.
|
||||||
_ "github.com/v2fly/v2ray-core/v4/main/confloader/external"
|
_ "github.com/v2fly/v2ray-core/v4/main/yaml"
|
||||||
|
|
||||||
// commands
|
// commands
|
||||||
_ "github.com/v2fly/v2ray-core/v4/commands/all"
|
_ "github.com/v2fly/v2ray-core/v4/commands/all"
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
package json
|
|
||||||
|
|
||||||
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
core "github.com/v2fly/v2ray-core/v4"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/confloader"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
|
|
||||||
Name: []string{"JSON"},
|
|
||||||
Extension: []string{".json", ".jsonc"},
|
|
||||||
Loader: func(input interface{}) (*core.Config, error) {
|
|
||||||
switch v := input.(type) {
|
|
||||||
case cmdarg.Arg:
|
|
||||||
r, err := confloader.LoadExtConfig(v, os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to execute v2ctl to convert config file.").Base(err).AtWarning()
|
|
||||||
}
|
|
||||||
return core.LoadConfig("protobuf", "", r)
|
|
||||||
case io.Reader:
|
|
||||||
r, err := confloader.LoadExtConfig([]string{"stdin:"}, os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to execute v2ctl to convert config file.").Base(err).AtWarning()
|
|
||||||
}
|
|
||||||
return core.LoadConfig("protobuf", "", r)
|
|
||||||
default:
|
|
||||||
return nil, newError("unknown type")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package jsonem
|
package json
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
69
main/yaml/yaml.go
Normal file
69
main/yaml/yaml.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
core "github.com/v2fly/v2ray-core/v4"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
|
||||||
|
Name: []string{"YAML"},
|
||||||
|
Extension: []string{".yml", ".yaml"},
|
||||||
|
Loader: func(input interface{}) (*core.Config, error) {
|
||||||
|
switch v := input.(type) {
|
||||||
|
case cmdarg.Arg:
|
||||||
|
bs, err := yamlsToJSONs(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := merge.BytesToJSON(bs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
cf, err := serial.DecodeJSONConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cf.Build()
|
||||||
|
case io.Reader:
|
||||||
|
bs, err := ioutil.ReadAll(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bs, err = json.FromYAML(bs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return serial.LoadJSONConfig(bytes.NewBuffer(bs))
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknow type")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func yamlsToJSONs(files []string) ([][]byte, error) {
|
||||||
|
jsons := make([][]byte, 0)
|
||||||
|
for _, file := range files {
|
||||||
|
bs, err := cmdarg.LoadArgToBytes(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
j, err := json.FromYAML(bs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
jsons = append(jsons, j)
|
||||||
|
}
|
||||||
|
return jsons, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user