github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/main/commands/all/convert.go (about)

     1  package all
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/xmplusdev/xmcore/common"
    14  	"github.com/xmplusdev/xmcore/common/buf"
    15  	"github.com/xmplusdev/xmcore/infra/conf"
    16  	"github.com/xmplusdev/xmcore/infra/conf/serial"
    17  	"github.com/xmplusdev/xmcore/main/commands/base"
    18  	"google.golang.org/protobuf/proto"
    19  )
    20  
    21  var cmdConvert = &base.Command{
    22  	UsageLine: "{{.Exec}} convert [json file] [json file] ...",
    23  	Short:     "Convert multiple json config to protobuf",
    24  	Long: `
    25  Convert multiple json config to protobuf.
    26  
    27  Examples:
    28  
    29      {{.Exec}} convert config.json c1.json c2.json <url>.json
    30  	`,
    31  }
    32  
    33  func init() {
    34  	cmdConvert.Run = executeConvert // break init loop
    35  }
    36  
    37  func executeConvert(cmd *base.Command, args []string) {
    38  	unnamedArgs := cmdConvert.Flag.Args()
    39  	if len(unnamedArgs) < 1 {
    40  		base.Fatalf("empty config list")
    41  	}
    42  
    43  	conf := &conf.Config{}
    44  	for _, arg := range unnamedArgs {
    45  		fmt.Fprintf(os.Stderr, "Read config: %s", arg)
    46  		r, err := loadArg(arg)
    47  		common.Must(err)
    48  		c, err := serial.DecodeJSONConfig(r)
    49  		if err != nil {
    50  			base.Fatalf(err.Error())
    51  		}
    52  		conf.Override(c, arg)
    53  	}
    54  
    55  	pbConfig, err := conf.Build()
    56  	if err != nil {
    57  		base.Fatalf(err.Error())
    58  	}
    59  
    60  	bytesConfig, err := proto.Marshal(pbConfig)
    61  	if err != nil {
    62  		base.Fatalf("failed to marshal proto config: %s", err)
    63  	}
    64  
    65  	if _, err := os.Stdout.Write(bytesConfig); err != nil {
    66  		base.Fatalf("failed to write proto config: %s", err)
    67  	}
    68  }
    69  
    70  // loadArg loads one arg, maybe an remote url, or local file path
    71  func loadArg(arg string) (out io.Reader, err error) {
    72  	var data []byte
    73  	switch {
    74  	case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
    75  		data, err = FetchHTTPContent(arg)
    76  
    77  	case arg == "stdin:":
    78  		data, err = io.ReadAll(os.Stdin)
    79  
    80  	default:
    81  		data, err = os.ReadFile(arg)
    82  	}
    83  
    84  	if err != nil {
    85  		return
    86  	}
    87  	out = bytes.NewBuffer(data)
    88  	return
    89  }
    90  
    91  // FetchHTTPContent dials https for remote content
    92  func FetchHTTPContent(target string) ([]byte, error) {
    93  	parsedTarget, err := url.Parse(target)
    94  	if err != nil {
    95  		return nil, newError("invalid URL: ", target).Base(err)
    96  	}
    97  
    98  	if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" {
    99  		return nil, newError("invalid scheme: ", parsedTarget.Scheme)
   100  	}
   101  
   102  	client := &http.Client{
   103  		Timeout: 30 * time.Second,
   104  	}
   105  	resp, err := client.Do(&http.Request{
   106  		Method: "GET",
   107  		URL:    parsedTarget,
   108  		Close:  true,
   109  	})
   110  	if err != nil {
   111  		return nil, newError("failed to dial to ", target).Base(err)
   112  	}
   113  	defer resp.Body.Close()
   114  
   115  	if resp.StatusCode != 200 {
   116  		return nil, newError("unexpected HTTP status code: ", resp.StatusCode)
   117  	}
   118  
   119  	content, err := buf.ReadAllToBytes(resp.Body)
   120  	if err != nil {
   121  		return nil, newError("failed to read HTTP response").Base(err)
   122  	}
   123  
   124  	return content, nil
   125  }