188 lines
5.4 KiB
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
|
|
}
|