refine api, run command and merger (#766)

* mergers code optimize

* api log work with pipe

* remove inbounds/outbounds by tags
*fix rmo flag parse

* cmds description and message optimize
This commit is contained in:
Jebbs 2021-03-11 05:03:26 +08:00 committed by Shelikhoo
parent 2523d77919
commit 7a0af318df
No known key found for this signature in database
GPG Key ID: C4D5E79D22B25316
16 changed files with 194 additions and 125 deletions

View File

@ -8,7 +8,7 @@ func GetExtensions(formatName string) ([]string, error) {
if lowerName == "auto" {
return GetAllExtensions(), nil
}
f, found := mergeLoaderByName[lowerName]
f, found := mergersByName[lowerName]
if !found {
return nil, newError(formatName+" not found", formatName).AtWarning()
}
@ -18,7 +18,7 @@ func GetExtensions(formatName string) ([]string, error) {
// GetAllExtensions get all extensions supported
func GetAllExtensions() []string {
extensions := make([]string, 0)
for _, f := range mergeLoaderByName {
for _, f := range mergersByName {
extensions = append(extensions, f.Extensions...)
}
return extensions

View File

@ -1,40 +0,0 @@
package mergers
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
import (
"strings"
)
// MergeableFormat is a configurable mergeable format of V2Ray config file.
type MergeableFormat struct {
Name string
Extensions []string
Loader MergeLoader
}
// MergeLoader is a utility to merge V2Ray config from external source into a map and returns it.
type MergeLoader func(input interface{}, m map[string]interface{}) error
var (
mergeLoaderByName = make(map[string]*MergeableFormat)
mergeLoaderByExt = make(map[string]*MergeableFormat)
)
// RegisterMergeLoader add a new MergeLoader.
func RegisterMergeLoader(format *MergeableFormat) error {
if _, found := mergeLoaderByName[format.Name]; found {
return newError(format.Name, " already registered.")
}
mergeLoaderByName[format.Name] = format
for _, ext := range format.Extensions {
lext := strings.ToLower(ext)
if f, found := mergeLoaderByExt[lext]; found {
return newError(ext, " already registered to ", f.Name)
}
mergeLoaderByExt[lext] = format
}
return nil
}

View File

@ -12,15 +12,15 @@ import (
// MergeAs load input and merge as specified format into m
func MergeAs(formatName string, input interface{}, m map[string]interface{}) error {
f, found := mergeLoaderByName[formatName]
f, found := mergersByName[formatName]
if !found {
return newError("format loader not found for: ", formatName)
return newError("format merger not found for: ", formatName)
}
return f.Loader(input, m)
return f.Merge(input, m)
}
// Merge loads inputs and merges them into m
// it detects extension for loader selecting, or try all loaders
// it detects extension for merger selecting, or try all mergers
// if no extension found
func Merge(input interface{}, m map[string]interface{}) error {
switch v := input.(type) {
@ -49,7 +49,7 @@ func Merge(input interface{}, m map[string]interface{}) error {
return err
}
case io.Reader:
// read to []byte incase it tries different loaders
// read to []byte incase it tries different mergers
bs, err := ioutil.ReadAll(v)
if err != nil {
return err
@ -69,24 +69,24 @@ func mergeSingleFile(input interface{}, m map[string]interface{}) error {
ext := getExtension(file)
if ext != "" {
lext := strings.ToLower(ext)
f, found := mergeLoaderByExt[lext]
f, found := mergersByExt[lext]
if !found {
return newError("unmergeable format extension: ", ext)
}
return f.Loader(file, m)
return f.Merge(file, m)
}
}
// no extension, try all loaders
for _, f := range mergeLoaderByName {
// no extension, try all mergers
for _, f := range mergersByName {
if f.Name == core.FormatAuto {
continue
}
err := f.Loader(input, m)
err := f.Merge(input, m)
if err == nil {
return nil
}
}
return newError("tried all loaders but failed for: ", input).AtWarning()
return newError("tried all mergers but failed for: ", input).AtWarning()
}
func getExtension(filename string) string {

View File

@ -11,15 +11,17 @@ import (
type jsonConverter func(v []byte) ([]byte, error)
func makeLoader(name string, extensions []string, converter jsonConverter) *MergeableFormat {
return &MergeableFormat{
// makeMerger makes a merger who merge the format by converting it to JSON
func makeMerger(name string, extensions []string, converter jsonConverter) *Merger {
return &Merger{
Name: name,
Extensions: extensions,
Loader: makeConvertToJSONLoader(converter),
Merge: makeToJSONMergeFunc(converter),
}
}
func makeConvertToJSONLoader(converter func(v []byte) ([]byte, error)) MergeLoader {
// makeToJSONMergeFunc makes a merge func who merge the format by converting it to JSON
func makeToJSONMergeFunc(converter func(v []byte) ([]byte, error)) MergeFunc {
return func(input interface{}, target map[string]interface{}) error {
if target == nil {
panic("merge target is nil")

View File

@ -1,32 +1,69 @@
package mergers
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
import (
"strings"
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
)
func init() {
common.Must(RegisterMergeLoader(makeLoader(
common.Must(RegisterMerger(makeMerger(
core.FormatJSON,
[]string{".json", ".jsonc"},
nil,
)))
common.Must(RegisterMergeLoader(makeLoader(
common.Must(RegisterMerger(makeMerger(
core.FormatTOML,
[]string{".toml"},
json.FromTOML,
)))
common.Must(RegisterMergeLoader(makeLoader(
common.Must(RegisterMerger(makeMerger(
core.FormatYAML,
[]string{".yml", ".yaml"},
json.FromYAML,
)))
common.Must(RegisterMergeLoader(
&MergeableFormat{
common.Must(RegisterMerger(
&Merger{
Name: core.FormatAuto,
Extensions: nil,
Loader: Merge,
Merge: Merge,
}),
)
}
// Merger is a configurable format merger for V2Ray config files.
type Merger struct {
Name string
Extensions []string
Merge MergeFunc
}
// MergeFunc is a utility to merge V2Ray config from external source into a map and returns it.
type MergeFunc func(input interface{}, m map[string]interface{}) error
var (
mergersByName = make(map[string]*Merger)
mergersByExt = make(map[string]*Merger)
)
// RegisterMerger add a new Merger.
func RegisterMerger(format *Merger) error {
if _, found := mergersByName[format.Name]; found {
return newError(format.Name, " already registered.")
}
mergersByName[format.Name] = format
for _, ext := range format.Extensions {
lext := strings.ToLower(ext)
if f, found := mergersByExt[lext]; found {
return newError(ext, " already registered to ", f.Name)
}
mergersByExt[lext] = format
}
return nil
}

View File

@ -3,7 +3,7 @@ package mergers
// GetAllNames get names of all formats
func GetAllNames() []string {
names := make([]string, 0)
for _, f := range mergeLoaderByName {
for _, f := range mergersByName {
names = append(names, f.Name)
}
return names

View File

@ -10,15 +10,18 @@ import (
var cmdAddInbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api adi [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
UsageLine: "{{.Exec}} api adi [--server=127.0.0.1:8080] [c1.json] [dir1]...",
Short: "add inbounds",
Long: `
Add inbounds to V2Ray.
> Make sure you have "HandlerService" set in "config.api.services"
of server config.
Arguments:
-format <format>
Specify the input format.
The input format.
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
@ -33,7 +36,8 @@ Arguments:
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
{{.Exec}} {{.LongName}} dir
{{.Exec}} {{.LongName}} c1.json c2.yaml
`,
Run: executeAddInbounds,
}
@ -44,7 +48,7 @@ func executeAddInbounds(cmd *base.Command, args []string) {
cmd.Flag.Parse(args)
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("%s", err)
base.Fatalf("failed to load: %s", err)
}
if len(c.InboundConfigs) == 0 {
base.Fatalf("no valid inbound found")

View File

@ -10,21 +10,27 @@ import (
var cmdRemoveInbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api rmi [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
UsageLine: "{{.Exec}} api rmi [--server=127.0.0.1:8080] [c1.json] [dir1]...",
Short: "remove inbounds",
Long: `
Remove inbounds from V2Ray.
> Make sure you have "HandlerService" set in "config.api.services"
of server config.
Arguments:
-format <format>
Specify the input format.
The input format.
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
-r
Load folders recursively.
-tags
The input are tags instead of config files
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
@ -33,7 +39,9 @@ Arguments:
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name"
{{.Exec}} {{.LongName}} dir
{{.Exec}} {{.LongName}} c1.json c2.yaml
{{.Exec}} {{.LongName}} -tags tag1 tag2
`,
Run: executeRemoveInbounds,
}
@ -41,12 +49,23 @@ Example:
func executeRemoveInbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd)
setSharedConfigFlags(cmd)
isTags := cmd.Flag.Bool("tags", false, "")
cmd.Flag.Parse(args)
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("%s", err)
var tags []string
if *isTags {
tags = cmd.Flag.Args()
} else {
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("failed to load: %s", err)
}
tags = make([]string, 0)
for _, c := range c.InboundConfigs {
tags = append(tags, c.Tag)
}
}
if len(c.InboundConfigs) == 0 {
if len(tags) == 0 {
base.Fatalf("no inbound to remove")
}
@ -54,10 +73,10 @@ func executeRemoveInbounds(cmd *base.Command, args []string) {
defer close()
client := handlerService.NewHandlerServiceClient(conn)
for _, c := range c.InboundConfigs {
fmt.Println("removing:", c.Tag)
for _, tag := range tags {
fmt.Println("removing:", tag)
r := &handlerService.RemoveInboundRequest{
Tag: c.Tag,
Tag: tag,
}
_, err := client.RemoveInbound(ctx, r)
if err != nil {

View File

@ -3,6 +3,7 @@ package api
import (
"io"
"log"
"os"
logService "github.com/v2fly/v2ray-core/v4/app/log/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
@ -15,6 +16,9 @@ var cmdLog = &base.Command{
Long: `
Follow and print logs from v2ray.
> Make sure you have "LoggerService" set in "config.api.services"
of server config.
> It ignores -timeout flag while following logs
Arguments:
@ -33,10 +37,10 @@ Example:
{{.Exec}} {{.LongName}}
{{.Exec}} {{.LongName}} --restart
`,
Run: executeRestartLogger,
Run: executeLog,
}
func executeRestartLogger(cmd *base.Command, args []string) {
func executeLog(cmd *base.Command, args []string) {
var restart bool
cmd.Flag.BoolVar(&restart, "restart", false, "")
setSharedFlags(cmd)
@ -69,7 +73,8 @@ func followLogger() {
if err != nil {
base.Fatalf("failed to follow logger: %s", err)
}
// work with `v2ray api log | grep expr`
log.SetOutput(os.Stdout)
for {
resp, err := stream.Recv()
if err == io.EOF {
@ -78,6 +83,6 @@ func followLogger() {
if err != nil {
base.Fatalf("failed to fetch log: %s", err)
}
log.Print(resp.Message)
log.Println(resp.Message)
}
}

View File

@ -10,15 +10,18 @@ import (
var cmdAddOutbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api ado [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
UsageLine: "{{.Exec}} api ado [--server=127.0.0.1:8080] [c1.json] [dir1]...",
Short: "add outbounds",
Long: `
Add outbounds to V2Ray.
> Make sure you have "HandlerService" set in "config.api.services"
of server config.
Arguments:
-format <format>
Specify the input format.
The input format.
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
@ -33,7 +36,8 @@ Arguments:
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
{{.Exec}} {{.LongName}} dir
{{.Exec}} {{.LongName}} c1.json c2.yaml
`,
Run: executeAddOutbounds,
}
@ -44,7 +48,7 @@ func executeAddOutbounds(cmd *base.Command, args []string) {
cmd.Flag.Parse(args)
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("%s", err)
base.Fatalf("failed to load: %s", err)
}
if len(c.OutboundConfigs) == 0 {
base.Fatalf("no valid outbound found")

View File

@ -10,21 +10,27 @@ import (
var cmdRemoveOutbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api rmo [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
UsageLine: "{{.Exec}} api rmo [--server=127.0.0.1:8080] [c1.json] [dir1]...",
Short: "remove outbounds",
Long: `
Remove outbounds from V2Ray.
> Make sure you have "HandlerService" set in "config.api.services"
of server config.
Arguments:
-format <format>
Specify the input format.
The input format.
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
-r
Load folders recursively.
-tags
The input are tags instead of config files
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
@ -33,20 +39,33 @@ Arguments:
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name"
{{.Exec}} {{.LongName}} dir
{{.Exec}} {{.LongName}} c1.json c2.yaml
{{.Exec}} {{.LongName}} -tags tag1 tag2
`,
Run: executeRemoveOutbounds,
}
func executeRemoveOutbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd)
cmd.Flag.Parse(args)
setSharedConfigFlags(cmd)
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("%s", err)
isTags := cmd.Flag.Bool("tags", false, "")
cmd.Flag.Parse(args)
var tags []string
if *isTags {
tags = cmd.Flag.Args()
} else {
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("failed to load: %s", err)
}
tags = make([]string, 0)
for _, c := range c.OutboundConfigs {
tags = append(tags, c.Tag)
}
}
if len(c.OutboundConfigs) == 0 {
if len(tags) == 0 {
base.Fatalf("no outbound to remove")
}
@ -54,10 +73,10 @@ func executeRemoveOutbounds(cmd *base.Command, args []string) {
defer close()
client := handlerService.NewHandlerServiceClient(conn)
for _, c := range c.OutboundConfigs {
fmt.Println("removing:", c.Tag)
for _, tag := range tags {
fmt.Println("removing:", tag)
r := &handlerService.RemoveOutboundRequest{
Tag: c.Tag,
Tag: tag,
}
_, err := client.RemoveOutbound(ctx, r)
if err != nil {

View File

@ -19,13 +19,16 @@ var cmdStats = &base.Command{
Long: `
Query statistics from V2Ray.
> Make sure you have "StatsService" set in "config.api.services"
of server config.
Arguments:
-regexp
The patterns are using regexp.
-reset
Fetch values then reset statistics counters to 0.
Reset counters to 0 after fetching their values.
-runtime
Get runtime statistics.

View File

@ -23,17 +23,17 @@ var cmdConvert = &base.Command{
Short: "convert config files",
Long: `
Convert config files between different formats. Files are merged
before convert if multiple assigned.
before convert.
Arguments:
-i, -input <format>
Specify the input format.
The input format.
Available values: "auto", "json", "toml", "yaml"
Default: "auto"
-o, -output <format>
Specify the output format
The output format
Available values: "json", "toml", "yaml", "protobuf" / "pb"
Default: "json"
@ -42,15 +42,15 @@ Arguments:
Examples:
{{.Exec}} {{.LongName}} -output=protobuf config.json (1)
{{.Exec}} {{.LongName}} -input=toml config.toml (2)
{{.Exec}} {{.LongName}} "path/to/dir" (3)
{{.Exec}} {{.LongName}} -i yaml -o protobuf c1.yaml <url>.yaml (4)
{{.Exec}} {{.LongName}} -output=protobuf "path/to/dir" (1)
{{.Exec}} {{.LongName}} -o=yaml config.toml (2)
{{.Exec}} {{.LongName}} c1.json c2.json (3)
{{.Exec}} {{.LongName}} -output=yaml c1.yaml <url>.yaml (4)
(1) Convert json to protobuf
(2) Convert toml to json
(3) Merge json files in dir
(4) Merge yaml files and convert to protobuf
(1) Merge all supported files in dir and convert to protobuf
(2) Convert toml to yaml
(3) Merge json files
(4) Merge yaml files
Use "{{.Exec}} help config-merge" for more information about merge.
`,
@ -81,11 +81,11 @@ func executeConvert(cmd *base.Command, args []string) {
m, err := helpers.LoadConfigToMap(cmd.Flag.Args(), inputFormat, confDirRecursively)
if err != nil {
base.Fatalf(err.Error())
base.Fatalf("failed to merge: %s", err)
}
err = merge.ApplyRules(m)
if err != nil {
base.Fatalf(err.Error())
base.Fatalf("failed to apply merge rules: %s", err)
}
var out []byte
@ -93,17 +93,17 @@ func executeConvert(cmd *base.Command, args []string) {
case core.FormatJSON:
out, err = json.Marshal(m)
if err != nil {
base.Fatalf("failed to marshal json: %s", err)
base.Fatalf("failed to convert to json: %s", err)
}
case core.FormatTOML:
out, err = toml.Marshal(m)
if err != nil {
base.Fatalf("failed to marshal json: %s", err)
base.Fatalf("failed to convert to toml: %s", err)
}
case core.FormatYAML:
out, err = yaml.Marshal(m)
if err != nil {
base.Fatalf("failed to marshal json: %s", err)
base.Fatalf("failed to convert to yaml: %s", err)
}
case core.FormatProtobuf, core.FormatProtobufShort:
data, err := json.Marshal(m)
@ -121,7 +121,7 @@ func executeConvert(cmd *base.Command, args []string) {
}
out, err = proto.Marshal(pbConfig)
if err != nil {
base.Fatalf("failed to marshal proto config: %s", err)
base.Fatalf("failed to convert to protobuf: %s", err)
}
default:
base.Errorf("invalid output format: %s", outputFormat)

View File

@ -8,12 +8,12 @@ var docFormat = &base.Command{
UsageLine: "{{.Exec}} format-loader",
Short: "config formats and loading",
Long: `
{{.Exec}} supports different config formats:
{{.Exec}} is equipped with multiple loaders to support different
config formats:
* auto
The default loader, supports all extensions below.
It loads config by format detecting, with mixed
formats support.
The default loader, supports all formats listed below, with
format detecting, and mixed fomats support.
* json (.json, .jsonc)
The json loader, multiple files support, mergeable.
@ -21,14 +21,14 @@ var docFormat = &base.Command{
* toml (.toml)
The toml loader, multiple files support, mergeable.
* yaml (.yml)
* yaml (.yml, .yaml)
The yaml loader, multiple files support, mergeable.
* protobuf / pb (.pb)
Single file support, unmergeable.
The following explains how format loaders behave with examples.
The following explains how format loaders behaves.
Examples:

View File

@ -25,19 +25,27 @@ var CmdRun = &base.Command{
Long: `
Run V2Ray with config.
{{.Exec}} will also use the config directory specified by environment
variable "v2ray.location.confdir". If no config found, it tries
to load config from one of below:
1. The default "config.json" in the current directory
2. The config file from ENV "v2ray.location.config"
3. The stdin if all failed above
Arguments:
-c, -config <file>
Config file for V2Ray. Multiple assign is accepted.
-d, -confdir <dir>
A dir with config files. Multiple assign is accepted.
A directory with config files. Multiple assign is accepted.
-r
Load confdir recursively.
-format <format>
Format of input files. (default "json")
Format of config input. (default "auto")
Examples:

View File

@ -16,19 +16,27 @@ var CmdTest = &base.Command{
Long: `
Test config files, without launching V2Ray server.
{{.Exec}} will also use the config directory specified by environment
variable "v2ray.location.confdir". If no config found, it tries
to load config from one of below:
1. The default "config.json" in the current directory
2. The config file from ENV "v2ray.location.config"
3. The stdin if all failed above
Arguments:
-c, -config <file>
Config file for V2Ray. Multiple assign is accepted.
-d, -confdir <dir>
A dir with config files. Multiple assign is accepted.
A directory with config files. Multiple assign is accepted.
-r
Load confdir recursively.
-format <format>
Format of input files. (default "json")
Format of config input. (default "auto")
Examples: