go_study/fabric-main/cmd/osnadmin/main.go

188 lines
5.4 KiB
Go

/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric/internal/osnadmin"
"github.com/hyperledger/fabric/protoutil"
"gopkg.in/alecthomas/kingpin.v2"
)
func main() {
kingpin.Version("0.0.1")
output, exit, err := executeForArgs(os.Args[1:])
if err != nil {
kingpin.Fatalf("parsing arguments: %s. Try --help", err)
}
fmt.Println(output)
os.Exit(exit)
}
func executeForArgs(args []string) (output string, exit int, err error) {
//
// command line flags
//
app := kingpin.New("osnadmin", "Orderer Service Node (OSN) administration")
orderer := app.Flag("orderer-address", "Admin endpoint of the OSN").Short('o').Required().String()
caFile := app.Flag("ca-file", "Path to file containing PEM-encoded TLS CA certificate(s) for the OSN").String()
clientCert := app.Flag("client-cert", "Path to file containing PEM-encoded X509 public key to use for mutual TLS communication with the OSN").String()
clientKey := app.Flag("client-key", "Path to file containing PEM-encoded private key to use for mutual TLS communication with the OSN").String()
noStatus := app.Flag("no-status", "Remove the HTTP status message from the command output").Default("false").Bool()
channel := app.Command("channel", "Channel actions")
join := channel.Command("join", "Join an Ordering Service Node (OSN) to a channel. If the channel does not yet exist, it will be created.")
joinChannelID := join.Flag("channelID", "Channel ID").Short('c').Required().String()
configBlockPath := join.Flag("config-block", "Path to the file containing an up-to-date config block for the channel").Short('b').Required().String()
list := channel.Command("list", "List channel information for an Ordering Service Node (OSN). If the channelID flag is set, more detailed information will be provided for that channel.")
listChannelID := list.Flag("channelID", "Channel ID").Short('c').String()
remove := channel.Command("remove", "Remove an Ordering Service Node (OSN) from a channel.")
removeChannelID := remove.Flag("channelID", "Channel ID").Short('c').Required().String()
command, err := app.Parse(args)
if err != nil {
return "", 1, err
}
//
// flag validation
//
var (
osnURL string
caCertPool *x509.CertPool
tlsClientCert tls.Certificate
)
// TLS enabled
if *caFile != "" {
osnURL = fmt.Sprintf("https://%s", *orderer)
var err error
caCertPool = x509.NewCertPool()
caFilePEM, err := os.ReadFile(*caFile)
if err != nil {
return "", 1, fmt.Errorf("reading orderer CA certificate: %s", err)
}
if !caCertPool.AppendCertsFromPEM(caFilePEM) {
return "", 1, fmt.Errorf("failed to add ca-file PEM to cert pool")
}
tlsClientCert, err = tls.LoadX509KeyPair(*clientCert, *clientKey)
if err != nil {
return "", 1, fmt.Errorf("loading client cert/key pair: %s", err)
}
} else { // TLS disabled
osnURL = fmt.Sprintf("http://%s", *orderer)
}
var marshaledConfigBlock []byte
if *configBlockPath != "" {
marshaledConfigBlock, err = os.ReadFile(*configBlockPath)
if err != nil {
return "", 1, fmt.Errorf("reading config block: %s", err)
}
err = validateBlockChannelID(marshaledConfigBlock, *joinChannelID)
if err != nil {
return "", 1, err
}
}
//
// call the underlying implementations
//
var resp *http.Response
switch command {
case join.FullCommand():
resp, err = osnadmin.Join(osnURL, marshaledConfigBlock, caCertPool, tlsClientCert)
case list.FullCommand():
if *listChannelID != "" {
resp, err = osnadmin.ListSingleChannel(osnURL, *listChannelID, caCertPool, tlsClientCert)
break
}
resp, err = osnadmin.ListAllChannels(osnURL, caCertPool, tlsClientCert)
case remove.FullCommand():
resp, err = osnadmin.Remove(osnURL, *removeChannelID, caCertPool, tlsClientCert)
}
if err != nil {
return errorOutput(err), 1, nil
}
bodyBytes, err := readBodyBytes(resp.Body)
if err != nil {
return errorOutput(err), 1, nil
}
output, err = responseOutput(!*noStatus, resp.StatusCode, bodyBytes)
if err != nil {
return errorOutput(err), 1, nil
}
return output, 0, nil
}
func responseOutput(showStatus bool, statusCode int, responseBody []byte) (string, error) {
var buffer bytes.Buffer
if showStatus {
fmt.Fprintf(&buffer, "Status: %d\n", statusCode)
}
if len(responseBody) != 0 {
if err := json.Indent(&buffer, responseBody, "", "\t"); err != nil {
return "", err
}
}
return buffer.String(), nil
}
func readBodyBytes(body io.ReadCloser) ([]byte, error) {
bodyBytes, err := io.ReadAll(body)
if err != nil {
return nil, fmt.Errorf("reading http response body: %s", err)
}
body.Close()
return bodyBytes, nil
}
func errorOutput(err error) string {
return fmt.Sprintf("Error: %s\n", err)
}
func validateBlockChannelID(blockBytes []byte, channelID string) error {
block := &common.Block{}
err := proto.Unmarshal(blockBytes, block)
if err != nil {
return fmt.Errorf("unmarshalling block: %s", err)
}
blockChannelID, err := protoutil.GetChannelIDFromBlock(block)
if err != nil {
return err
}
// quick sanity check that the orderer admin is joining
// the channel they think they're joining.
if channelID != blockChannelID {
return fmt.Errorf("specified --channelID %s does not match channel ID %s in config block", channelID, blockChannelID)
}
return nil
}