From 79f01ab96c7e76d8b2b62f2dfa1e5612f737d57b Mon Sep 17 00:00:00 2001 From: Derek Bekoe Date: Sun, 10 Mar 2019 17:54:59 -0700 Subject: [PATCH] Support TLS connections & update README (#12) Add `--demo` option to connect to demo server. Add `--unsecure` option for testing purposes. --- README.md | 112 ++++++++++++++++-------- cmd/configure.go | 7 ++ cmd/root.go | 219 ++++++++++++++++++++++++++++------------------- cmd/version.go | 25 +++--- 4 files changed, 228 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 5b3b646..ecd9b06 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,79 @@ -# convey +

+ Convey +

+

+Version +Build Status +

+
+

A command-line tool that makes sharing pipes between machines easy.

+

Learn more at Convey: Pipe between machines

+
-A command-line tool that makes sharing pipes between machines easy. + +```bash +echo "Hello world" | convey +21f50fba373e11e9990a72000872a940 +``` +```bash +convey 21f50fba373e11e9990a72000872a940 +Hello world +``` + +# Features + +- Pipe between hosts with an idomatic interface using the standard `|` symbol. +- Easily pipe files between hosts. +- Does not require any open ports between your clients. +- Supports colors through [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors). +- Supports Linux, macOS and Windows. +- No dependencies to install. +- Powered by [NATS](https://nats.io/), a CNCF project. + +# Getting Started ## Install -**Install on Linux:** +### Linux ```bash wget -qO convey https://get.convey.sh/linux chmod +x ~/bin/convey +~/bin/convey -h ``` -**Install on Mac OS:** +### macOS ```bash wget -qO ~/bin/convey https://get.convey.sh/macos chmod +x ~/bin/convey +~/bin/convey -h ``` -**Install on Windows:** -> Download from https://get.convey.sh/windows +### Windows +```powershell +Invoke-WebRequest https://get.convey.sh/windows -OutFile convey.exe +convey -h +``` -This will download the latest release for your platform. -Builds are available for the `amd64` architecture. -To directly from GitHub, visit https://github.com/derekbekoe/convey/releases/latest. +## Demo Mode -## Usage +A demo mode is available using the `--demo` flag. -In Terminal 1: ```bash -echo "Hello world" | convey -21f50fba373e11e9990a72000872a940 +echo "Hello world" | convey --demo + ``` - -In Terminal 2: ```bash -convey 21f50fba373e11e9990a72000872a940 +convey --demo Hello world ``` -## Configuration +Demo mode uses the `demo.nats.io` server over a TLS connection with channels expiring after 30 minutes of creation or 10 minutes of inactivity. + +Use this mode for experimental and getting started purposes only. + +# Configuration Set configuration with the `convey configure` command. ```bash @@ -52,44 +88,48 @@ NatsURL: nats://localhost:4223 NatsClusterID: test-cluster ``` -Use the --config flag on the command line to change the config file if needed. +Use the `--config` flag on the command line to change the config file used if needed. -## Development -```bash -go get -u github.com/derekbekoe/convey -cd $GOPATH/src/github.com/derekbekoe/convey -go run main.go -go build -o bin/convey -``` +# NATS Streaming Server -## Host your own NATS Streaming Server +You can host your own [NATS Streaming Server](https://nats.io/documentation/streaming/nats-streaming-intro/) and configure `convey` to use that server. -**Deploy to a local Docker container** +#### Deploy to a local Docker container ```bash docker run -p 4222:4222 nats-streaming:linux convey configure --nats-url nats://localhost:4222 --nats-cluster test-cluster ``` -**Deploy to an Azure Container Instance** +You will need to use the `--unsecure` flag as TLS will not be enabled through this local container. -note: We only include this as an illustration to keep the command simple as traffic is not encrypted. +# Development + +**Set up** ```bash -az container create --image nats-streaming:linux --ports 4222 --ip-address Public -g RG -n nats1 -convey configure --nats-url nats://:4222 --nats-cluster test-cluster +go get -u github.com/derekbekoe/convey +cd $GOPATH/src/github.com/derekbekoe/convey +go run main.go ``` -## Platform Builds +**Multi-platform Builds** ```bash go get github.com/mitchellh/gox gox -ldflags "-X github.com/derekbekoe/convey/cmd.VersionGitCommit=$(git rev-list -1 HEAD) -X github.com/derekbekoe/convey/cmd.VersionGitTag=VERSION" -os="linux darwin" -arch="amd64" -output="bin/{{.Dir}}_{{.OS}}_{{.Arch}}" ``` See https://golang.org/doc/install/source#environment -## FAQ +# Examples + +Click to expand each gif. -**How do I try it out?** +
+Convey with Top +Convey for file piping +Convey with millisecond date +Convey with Cloud Shell +
-Start the local container, download convey, specify the configuration then run. +# License -If you'd like to share between multiple devices, host the server in a location where your devices can access. +Convey source code is available under the [MIT License](LICENSE). diff --git a/cmd/configure.go b/cmd/configure.go index b1bbcdc..ad4e9a3 100644 --- a/cmd/configure.go +++ b/cmd/configure.go @@ -9,9 +9,16 @@ import ( "github.com/spf13/viper" ) +// The NATS URL passed in from command-line var natsURL string + +// The NATS cluster ID passed in from command-line var natsClusterID string + +// Whether short channel names should be used instead of the standard uuid format var useShortName bool + +// Whether the current config file should be overwritten var forceWrite bool func init() { diff --git a/cmd/root.go b/cmd/root.go index f9c20d5..94a26ac 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,38 +28,41 @@ import ( "github.com/docker/docker/pkg/namesgenerator" homedir "github.com/mitchellh/go-homedir" + nats "github.com/nats-io/go-nats" stan "github.com/nats-io/go-nats-streaming" uuid "github.com/satori/go.uuid" "github.com/spf13/cobra" "github.com/spf13/viper" ) +const ( + configKeyNatsURL = "NatsURL" + configKeyNatsClusterID = "NatsClusterID" + configKeyUseShortName = "UseShortName" + demoNatsURL = "tls://demo.nats.io:4443" + demoNatsClusterID = "convey-demo-cluster" +) + +// Path to config file set by user var cfgFile string + +// Whether to log verbose output var verbose bool -const configKeyNatsURL = "NatsURL" -const configKeyNatsClusterID = "NatsClusterID" -const configKeyUseShortName = "UseShortName" +// Whether non-tls connection should be used for NATS connection +var useUnsecure bool -// ETX is End Of Text Sequence -var ETX = []byte{3} +// Whether the demo server and mode should be used +var useDemoMode bool + +// etx is an identifier for End Of Text Sequence +var etx = []byte{3} func errorExit(msg string) { fmt.Fprintln(os.Stderr, msg) os.Exit(1) } -// positionalArgsValidator valids the positional args -func positionalArgsValidator(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return nil - } else if len(args) == 1 { - _, err := uuid.FromString(args[0]) - return err - } - return errors.New("Invalid positional arguments") -} - // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "convey", @@ -72,22 +75,82 @@ var rootCmd = &cobra.Command{ // RootCommandFunc is a handler for the bare application func RootCommandFunc(cmd *cobra.Command, args []string) { if len(args) == 0 { - PublishModeFunc() + publishModeFunc() } else if len(args) == 1 { - SubscribeModeFunc(args[0]) + subscribeModeFunc(args[0]) } else { errorExit("Too many args") } } +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + errorExit(err.Error()) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.convey.yaml)") + rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") + rootCmd.PersistentFlags().BoolVar(&useUnsecure, "unsecure", false, "use unsecured connection (for development purposes only)") + rootCmd.PersistentFlags().BoolVar(&useDemoMode, "demo", false, "use demo mode") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + errorExit(err.Error()) + } + + // Search config in home directory with name ".convey" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".convey") + } + + viper.AutomaticEnv() // read in environment variables that match + + if !verbose { + log.SetOutput(ioutil.Discard) + } + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + log.Println("Config file:", viper.ConfigFileUsed()) + } +} + +// positionalArgsValidator valids the positional args +func positionalArgsValidator(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return nil + } else if len(args) == 1 { + _, err := uuid.FromString(args[0]) + return err + } + return errors.New("Invalid positional arguments") +} + func createChannelNameUUID() string { - u1, err := uuid.NewV1() + u, err := uuid.NewV4() if err != nil { s := fmt.Sprintf("Failed to create channel name: %s\n", err) errorExit(s) } // Remove dashes from UUID to make copy-paste easier in terminal - return strings.Replace(u1.String(), "-", "", -1) + return strings.Replace(u.String(), "-", "", -1) } func createChannelNameShort() string { @@ -95,30 +158,49 @@ func createChannelNameShort() string { } func getClientID(prefix string) string { - u1, err := uuid.NewV1() + u, err := uuid.NewV4() if err != nil { s := fmt.Sprintf("Failed to create client ID: %s\n", err) errorExit(s) } - return fmt.Sprintf("%s-%s", prefix, u1.String()) + return fmt.Sprintf("%s-%s", prefix, u.String()) } -func connectToStan(clientID string) stan.Conn { +func connectToStan(clientID string) (stan.Conn, *nats.Conn) { natsURL := viper.GetString(configKeyNatsURL) natsClusterID := viper.GetString(configKeyNatsClusterID) + if useDemoMode { + log.Printf("Using demo mode") + natsURL = demoNatsURL + natsClusterID = demoNatsClusterID + } + if natsURL == "" || natsClusterID == "" { - s := fmt.Sprintf("The configuration options '%s' and '%s' are not set. Use `convey configure` to set.", + s := fmt.Sprintf("The configuration options '%s' and '%s' are not set. Use `convey configure` to set. Use `--help` for usage.", configKeyNatsURL, configKeyNatsClusterID) errorExit(s) } - sc, err := stan.Connect( + natsSecureOpt := nats.Secure() + + if useUnsecure { + log.Printf("Using unsecure connection to server") + natsSecureOpt = nil + } + + natsConn, err1 := nats.Connect(natsURL, natsSecureOpt) + if err1 != nil { + s := fmt.Sprintf("Failed to connect to NATS server due to error - %s", err1) + errorExit(s) + } + + stanConn, err := stan.Connect( natsClusterID, clientID, - stan.NatsURL(natsURL), + stan.NatsConn(natsConn), stan.SetConnectionLostHandler(func(_ stan.Conn, err error) { log.Printf("Lost connection due to error - %s", err) })) @@ -126,17 +208,24 @@ func connectToStan(clientID string) stan.Conn { s := fmt.Sprintf("Failed to connect to streaming server due to error - %s", err) errorExit(s) } - return sc + + return stanConn, natsConn } -// PublishModeFunc handles publishing messages to message service -func PublishModeFunc() { +// publishModeFunc handles publishing messages to message service +func publishModeFunc() { clientID := getClientID("convey-pub") - sc := connectToStan(clientID) + stanConn, natsConn := connectToStan(clientID) useShortName := viper.GetBool(configKeyUseShortName) channelName := "" - if useShortName { + + if useDemoMode && useShortName { + log.Printf("Short names not allowed in demo mode to prevent possible conflicts") + } + + // Check if should use short names. Short names not allowed in demo mode to prevent possible conflicts. + if useShortName && !useDemoMode { channelName = createChannelNameShort() } else { channelName = createChannelNameUUID() @@ -160,28 +249,29 @@ func PublishModeFunc() { scanner := bufio.NewScanner(bufio.NewReader(os.Stdin)) for scanner.Scan() { line := scanner.Text() - sc.Publish(channelName, []byte(line)) + stanConn.Publish(channelName, []byte(line)) } donePublish <- true }() <-donePublish - sc.Publish(channelName, ETX) - sc.Close() + stanConn.Publish(channelName, etx) + stanConn.Close() + natsConn.Close() } -// SubscribeModeFunc handles reading messages from the message service -func SubscribeModeFunc(channelName string) { +// subscribeModeFunc handles reading messages from the message service +func subscribeModeFunc(channelName string) { clientID := getClientID("convey-sub") - sc := connectToStan(clientID) + stanConn, natsConn := connectToStan(clientID) log.Printf("Subscribing to channel %s\n", channelName) doneSubscribe := make(chan bool) - sub, subErr := sc.Subscribe(channelName, func(m *stan.Msg) { - if reflect.DeepEqual(m.Data, ETX) { + sub, subErr := stanConn.Subscribe(channelName, func(m *stan.Msg) { + if reflect.DeepEqual(m.Data, etx) { doneSubscribe <- true } else { fmt.Println(string(m.Data)) @@ -196,53 +286,6 @@ func SubscribeModeFunc(channelName string) { <-doneSubscribe sub.Unsubscribe() - sc.Close() -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - if err := rootCmd.Execute(); err != nil { - errorExit(err.Error()) - } -} - -func init() { - cobra.OnInitialize(initConfig) - - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.convey.yaml)") - - rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") -} - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - errorExit(err.Error()) - } - - // Search config in home directory with name ".convey" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".convey") - } - - viper.AutomaticEnv() // read in environment variables that match - - if !verbose { - log.SetOutput(ioutil.Discard) - } - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - log.Println("Config file:", viper.ConfigFileUsed()) - } + stanConn.Close() + natsConn.Close() } diff --git a/cmd/version.go b/cmd/version.go index c22a641..29994b1 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -7,10 +7,10 @@ import ( "github.com/spf13/cobra" ) -// VersionGitCommit The git commit this build is from +// VersionGitCommit The git commit this build is from. Do not change variable name as used by CI build through -ldflags. var VersionGitCommit string -// VersionGitTag The git tag this build is from +// VersionGitTag The git tag this build is from. Do not change variable name as used by CI build through -ldflags. var VersionGitTag string func init() { @@ -19,13 +19,16 @@ func init() { var versionCmd = &cobra.Command{ Use: "version", - Short: "Print the version number of Convey", - Run: func(cmd *cobra.Command, args []string) { - if VersionGitCommit != "" { - fmt.Printf("Convey %s -- %s\n\n", VersionGitTag, VersionGitCommit[:7]) - fmt.Printf("Commit: %s\n", VersionGitCommit) - } - fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH) - fmt.Printf("Go version: %s\n", runtime.Version()) - }, + Short: "Print version information", + Run: VersionCommandFunc, +} + +// VersionCommandFunc is a handler for the version command +func VersionCommandFunc(cmd *cobra.Command, args []string) { + if VersionGitCommit != "" { + fmt.Printf("Convey %s -- %s\n\n", VersionGitTag, VersionGitCommit[:7]) + fmt.Printf("Commit: %s\n", VersionGitCommit) + } + fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH) + fmt.Printf("Go version: %s\n", runtime.Version()) }