From 4eca60e32dc196fa6df594ff83e9e5feafae09ec Mon Sep 17 00:00:00 2001 From: Paul Lorenz Date: Thu, 22 Jun 2023 23:54:24 -0400 Subject: [PATCH] Consoildate demo and tutorial stuff under demo. Fixes #1169 --- CHANGELOG.md | 6 + doc/003-local-deploy.md | 4 +- ziti/cmd/cmd.go | 12 +- .../first-service-tutorial.go | 2 +- .../{tutorial => demo}/plain_echo_client.go | 2 +- .../plain_echo_client_cmd.go | 2 +- .../{tutorial => demo}/plain_echo_server.go | 2 +- .../plain_echo_server_cmd.go | 2 +- ziti/cmd/demo/root.go | 27 ++++ ziti/cmd/demo/setup-client.go | 3 +- ...up-multi-router-tunneler-hosted-service.go | 3 +- ziti/cmd/demo/setup-multi-sdk-service.go | 3 +- .../setup-multi-tunneler-hosted-service.go | 3 +- .../demo/setup-router-tunneler-both-sides.go | 3 +- ...p-single-router-tunneler-hosted-service.go | 3 +- ziti/cmd/demo/setup-single-sdk-service.go | 3 +- .../demo/setup-update-config-addressable.go | 3 +- ziti/cmd/demo/setup-update-config-ha.go | 3 +- .../tutorials/first-service.md | 136 +++++++++++------- .../{tutorial => demo}/tutorials/more-info.md | 0 .../{tutorial => demo}/ziti_echo_client.go | 2 +- .../ziti_echo_client_cmd.go | 2 +- .../{tutorial => demo}/ziti_echo_server.go | 2 +- .../ziti_echo_server_cmd.go | 2 +- ziti/cmd/tutorial/root.go | 68 --------- zititest/go.mod | 2 +- zititest/go.sum | 4 +- zititest/tests/echo_test.go | 3 +- 28 files changed, 146 insertions(+), 161 deletions(-) rename ziti/cmd/{tutorial => demo}/first-service-tutorial.go (99%) rename ziti/cmd/{tutorial => demo}/plain_echo_client.go (98%) rename ziti/cmd/{tutorial => demo}/plain_echo_client_cmd.go (99%) rename ziti/cmd/{tutorial => demo}/plain_echo_server.go (98%) rename ziti/cmd/{tutorial => demo}/plain_echo_server_cmd.go (99%) rename ziti/cmd/{tutorial => demo}/tutorials/first-service.md (78%) rename ziti/cmd/{tutorial => demo}/tutorials/more-info.md (100%) rename ziti/cmd/{tutorial => demo}/ziti_echo_client.go (99%) rename ziti/cmd/{tutorial => demo}/ziti_echo_client_cmd.go (99%) rename ziti/cmd/{tutorial => demo}/ziti_echo_server.go (99%) rename ziti/cmd/{tutorial => demo}/ziti_echo_server_cmd.go (99%) delete mode 100644 ziti/cmd/tutorial/root.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f97a75c5..268fe33ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## What's New +### Deprecated Binary Removal This release removes the following deprecated binaries from the release archives. * `ziti-controller` - replaced by `ziti controller` @@ -10,6 +11,11 @@ This release removes the following deprecated binaries from the release archives The release archives now only contain the `ziti` executable. This executable is now at the root of the archive instead of nested under a `ziti` directory. +### Ziti CLI Demo Consolidation + +The ziti CLI functions under `ziti learn`, namely `ziti learn demo` and `ziti learn tutorial` have been consolidate under `ziti demo`. + + # Release 0.28.4 ## Component Updates and Bug Fixes diff --git a/doc/003-local-deploy.md b/doc/003-local-deploy.md index a4d9fc2c0..f57900dc7 100644 --- a/doc/003-local-deploy.md +++ b/doc/003-local-deploy.md @@ -111,10 +111,10 @@ A service is an entity that stores metadata about a server application. The `zit When prompted, select your running edge router `router01`. ```bash -ziti edge tutorial first-service +ziti demo first-service ``` -If you prefer, you may read [the first-service tutorial as a web site](../ziti/cmd/tutorial/tutorials/first-service.md) +If you prefer, you may read [the first-service tutorial as a web site](../ziti/cmd/demo/tutorials/first-service.md) ## Further Exploration diff --git a/ziti/cmd/cmd.go b/ziti/cmd/cmd.go index 36422f450..9169909aa 100644 --- a/ziti/cmd/cmd.go +++ b/ziti/cmd/cmd.go @@ -27,7 +27,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/fabric" "github.com/openziti/ziti/ziti/cmd/install" "github.com/openziti/ziti/ziti/cmd/templates" - "github.com/openziti/ziti/ziti/cmd/tutorial" c "github.com/openziti/ziti/ziti/constants" "github.com/openziti/ziti/ziti/controller" "github.com/openziti/ziti/ziti/internal/log" @@ -131,7 +130,6 @@ func NewCmdRoot(in io.Reader, out, err io.Writer, cmd *cobra.Command) *cobra.Com pkiCommands := NewCmdPKI(out, err) fabricCommand := fabric.NewFabricCmd(p) edgeCommand := edge.NewCmdEdge(out, err) - tutorialCmd := tutorial.NewTutorialCmd(p) demoCmd := demo.NewDemoCmd(p) opsCommands := &cobra.Command{ @@ -143,14 +141,6 @@ func NewCmdRoot(in io.Reader, out, err io.Writer, cmd *cobra.Command) *cobra.Com opsCommands.AddCommand(NewCmdLogFormat(out, err)) opsCommands.AddCommand(NewUnwrapIdentityFileCommand(out, err)) - learnCommands := &cobra.Command{ - Use: "learn ", - Short: "Tutorials and demos to help you learn about Ziti", - } - - learnCommands.AddCommand(demoCmd) - learnCommands.AddCommand(tutorialCmd) - installCommands := []*cobra.Command{ install.NewCmdInstall(out, err), install.NewCmdUpgrade(out, err), @@ -195,7 +185,7 @@ func NewCmdRoot(in io.Reader, out, err io.Writer, cmd *cobra.Command) *cobra.Com { Message: "Learning Ziti", Commands: []*cobra.Command{ - learnCommands, + demoCmd, }, }, } diff --git a/ziti/cmd/tutorial/first-service-tutorial.go b/ziti/cmd/demo/first-service-tutorial.go similarity index 99% rename from ziti/cmd/tutorial/first-service-tutorial.go rename to ziti/cmd/demo/first-service-tutorial.go index 3e37f9830..92a85da47 100644 --- a/ziti/cmd/tutorial/first-service-tutorial.go +++ b/ziti/cmd/demo/first-service-tutorial.go @@ -14,7 +14,7 @@ limitations under the License. */ -package tutorial +package demo import ( _ "embed" diff --git a/ziti/cmd/tutorial/plain_echo_client.go b/ziti/cmd/demo/plain_echo_client.go similarity index 98% rename from ziti/cmd/tutorial/plain_echo_client.go rename to ziti/cmd/demo/plain_echo_client.go index 7ff03c2e5..6c1e6c9b7 100644 --- a/ziti/cmd/tutorial/plain_echo_client.go +++ b/ziti/cmd/demo/plain_echo_client.go @@ -14,7 +14,7 @@ limitations under the License. */ -package tutorial +package demo import ( "fmt" diff --git a/ziti/cmd/tutorial/plain_echo_client_cmd.go b/ziti/cmd/demo/plain_echo_client_cmd.go similarity index 99% rename from ziti/cmd/tutorial/plain_echo_client_cmd.go rename to ziti/cmd/demo/plain_echo_client_cmd.go index 39e4a2729..9f7f1464c 100644 --- a/ziti/cmd/tutorial/plain_echo_client_cmd.go +++ b/ziti/cmd/demo/plain_echo_client_cmd.go @@ -14,7 +14,7 @@ limitations under the License. */ -package tutorial +package demo import ( "github.com/openziti/ziti/ziti/cmd/common" diff --git a/ziti/cmd/tutorial/plain_echo_server.go b/ziti/cmd/demo/plain_echo_server.go similarity index 98% rename from ziti/cmd/tutorial/plain_echo_server.go rename to ziti/cmd/demo/plain_echo_server.go index b59fdff83..8faa2daa8 100644 --- a/ziti/cmd/tutorial/plain_echo_server.go +++ b/ziti/cmd/demo/plain_echo_server.go @@ -14,7 +14,7 @@ limitations under the License. */ -package tutorial +package demo import ( "fmt" diff --git a/ziti/cmd/tutorial/plain_echo_server_cmd.go b/ziti/cmd/demo/plain_echo_server_cmd.go similarity index 99% rename from ziti/cmd/tutorial/plain_echo_server_cmd.go rename to ziti/cmd/demo/plain_echo_server_cmd.go index e76b7788e..dd266e589 100644 --- a/ziti/cmd/tutorial/plain_echo_server_cmd.go +++ b/ziti/cmd/demo/plain_echo_server_cmd.go @@ -14,7 +14,7 @@ limitations under the License. */ -package tutorial +package demo import ( "github.com/openziti/ziti/ziti/cmd/common" diff --git a/ziti/cmd/demo/root.go b/ziti/cmd/demo/root.go index 044976664..a0868d05c 100644 --- a/ziti/cmd/demo/root.go +++ b/ziti/cmd/demo/root.go @@ -21,6 +21,7 @@ import ( "github.com/openziti/ziti/ziti/cmd/common" "github.com/openziti/ziti/ziti/cmd/helpers" "github.com/spf13/cobra" + "time" ) func NewDemoCmd(p common.OptionsProvider) *cobra.Command { @@ -60,6 +61,12 @@ func NewDemoCmd(p common.OptionsProvider) *cobra.Command { }, } + demoCmd.AddCommand(newFirstServiceTutorialCmd(p)) + demoCmd.AddCommand(newPlainEchoServerCmd(p)) + demoCmd.AddCommand(newPlainEchoClientCmd(p)) + demoCmd.AddCommand(newZitiEchoClientCmd(p)) + demoCmd.AddCommand(newZitiEchoServerCmd(p)) + agentCmd.AddCommand(echoServerAgentCmd) echoServerAgentCmd.AddCommand(NewAgentEchoServerUpdateTerminatorCmd(p)) @@ -81,3 +88,23 @@ func NewDemoCmd(p common.OptionsProvider) *cobra.Command { return demoCmd } + +type TutorialOptions struct { + ControllerUrl string + Username string + Password string + NewlinePause time.Duration + AssumeDefault bool +} + +func (self *TutorialOptions) GetControllerUrl() string { + return self.ControllerUrl +} + +func (self *TutorialOptions) GetUsername() string { + return self.Username +} + +func (self *TutorialOptions) GetPassword() string { + return self.Password +} diff --git a/ziti/cmd/demo/setup-client.go b/ziti/cmd/demo/setup-client.go index 72d00c014..a7be84817 100644 --- a/ziti/cmd/demo/setup-client.go +++ b/ziti/cmd/demo/setup-client.go @@ -23,7 +23,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/tutorial" "github.com/spf13/cobra" "time" ) @@ -33,7 +32,7 @@ var clientScriptSource []byte type client struct { api.Options - tutorial.TutorialOptions + TutorialOptions interactive bool } diff --git a/ziti/cmd/demo/setup-multi-router-tunneler-hosted-service.go b/ziti/cmd/demo/setup-multi-router-tunneler-hosted-service.go index 5a688f7c8..00e5230a3 100644 --- a/ziti/cmd/demo/setup-multi-router-tunneler-hosted-service.go +++ b/ziti/cmd/demo/setup-multi-router-tunneler-hosted-service.go @@ -23,7 +23,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/tutorial" "github.com/spf13/cobra" "time" ) @@ -33,7 +32,7 @@ var multiRouterTunnelerHostedScriptSource []byte type multiRouterTunnelerHosted struct { api.Options - tutorial.TutorialOptions + TutorialOptions interactive bool } diff --git a/ziti/cmd/demo/setup-multi-sdk-service.go b/ziti/cmd/demo/setup-multi-sdk-service.go index 85b15385a..c93173e60 100644 --- a/ziti/cmd/demo/setup-multi-sdk-service.go +++ b/ziti/cmd/demo/setup-multi-sdk-service.go @@ -23,7 +23,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/tutorial" "github.com/spf13/cobra" "time" ) @@ -33,7 +32,7 @@ var multiSdkHostedScriptSource []byte type multiSdkHosted struct { api.Options - tutorial.TutorialOptions + TutorialOptions interactive bool } diff --git a/ziti/cmd/demo/setup-multi-tunneler-hosted-service.go b/ziti/cmd/demo/setup-multi-tunneler-hosted-service.go index cc8d1c229..fe22c73e7 100644 --- a/ziti/cmd/demo/setup-multi-tunneler-hosted-service.go +++ b/ziti/cmd/demo/setup-multi-tunneler-hosted-service.go @@ -23,7 +23,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/tutorial" "github.com/spf13/cobra" "time" ) @@ -33,7 +32,7 @@ var multiTunnelerHostedScriptSource []byte type multiTunnelerHosted struct { api.Options - tutorial.TutorialOptions + TutorialOptions interactive bool } diff --git a/ziti/cmd/demo/setup-router-tunneler-both-sides.go b/ziti/cmd/demo/setup-router-tunneler-both-sides.go index 98c546be7..0a0b4c383 100644 --- a/ziti/cmd/demo/setup-router-tunneler-both-sides.go +++ b/ziti/cmd/demo/setup-router-tunneler-both-sides.go @@ -23,7 +23,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/tutorial" "github.com/spf13/cobra" "time" ) @@ -33,7 +32,7 @@ var routerTunnelerBothSidesScriptSource []byte type routerTunnelerBothSides struct { api.Options - tutorial.TutorialOptions + TutorialOptions interactive bool } diff --git a/ziti/cmd/demo/setup-single-router-tunneler-hosted-service.go b/ziti/cmd/demo/setup-single-router-tunneler-hosted-service.go index 36f5ea69c..bb54d9b2d 100644 --- a/ziti/cmd/demo/setup-single-router-tunneler-hosted-service.go +++ b/ziti/cmd/demo/setup-single-router-tunneler-hosted-service.go @@ -23,7 +23,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/tutorial" "github.com/spf13/cobra" "time" ) @@ -33,7 +32,7 @@ var singleRouterTunnelerHostedScriptSource []byte type singleRouterTunnelerHosted struct { api.Options - tutorial.TutorialOptions + TutorialOptions interactive bool } diff --git a/ziti/cmd/demo/setup-single-sdk-service.go b/ziti/cmd/demo/setup-single-sdk-service.go index db95e1640..324c36092 100644 --- a/ziti/cmd/demo/setup-single-sdk-service.go +++ b/ziti/cmd/demo/setup-single-sdk-service.go @@ -23,7 +23,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/tutorial" "github.com/spf13/cobra" "time" ) @@ -33,7 +32,7 @@ var singleSdkHostedScriptSource []byte type singleSdkHosted struct { api.Options - tutorial.TutorialOptions + TutorialOptions interactive bool } diff --git a/ziti/cmd/demo/setup-update-config-addressable.go b/ziti/cmd/demo/setup-update-config-addressable.go index aa7320e5e..f1cdd8f80 100644 --- a/ziti/cmd/demo/setup-update-config-addressable.go +++ b/ziti/cmd/demo/setup-update-config-addressable.go @@ -23,7 +23,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/tutorial" "github.com/spf13/cobra" "time" ) @@ -33,7 +32,7 @@ var updateConfigAddressableSource []byte type updateConfigAddressable struct { api.Options - tutorial.TutorialOptions + TutorialOptions interactive bool } diff --git a/ziti/cmd/demo/setup-update-config-ha.go b/ziti/cmd/demo/setup-update-config-ha.go index b0bc6e961..c7f823dc0 100644 --- a/ziti/cmd/demo/setup-update-config-ha.go +++ b/ziti/cmd/demo/setup-update-config-ha.go @@ -23,7 +23,6 @@ import ( "github.com/openziti/ziti/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/openziti/ziti/ziti/cmd/tutorial" "github.com/spf13/cobra" "time" ) @@ -33,7 +32,7 @@ var updateConfigHASource []byte type updateConfigHA struct { api.Options - tutorial.TutorialOptions + TutorialOptions interactive bool } diff --git a/ziti/cmd/tutorial/tutorials/first-service.md b/ziti/cmd/demo/tutorials/first-service.md similarity index 78% rename from ziti/cmd/tutorial/tutorials/first-service.md rename to ziti/cmd/demo/tutorials/first-service.md index 358fc3484..c5142341c 100644 --- a/ziti/cmd/tutorial/tutorials/first-service.md +++ b/ziti/cmd/demo/tutorials/first-service.md @@ -2,12 +2,18 @@ Hello. In this tutorial we’re going to go explore services, identities and polices. -Please note: this tutorial can be run interactively, by running 'ziti edge tutorial first-service'. It can also be viewed as a web page [here](https://github.com/openziti/ziti/blob/release-next/ziti/cmd/tutorial/tutorials/first-service.md). It may be convenient to be able to view both at the same time. The interactive version will save you a lot of typing or copy/pasting, but content may be easier to read in a web-browser. +Please note: this tutorial can be run interactively, by running 'ziti demo first-service'. It can also be viewed as a web +page [here](https://github.com/openziti/ziti/blob/release-next/ziti/cmd/demo/tutorials/first-service.md). It may be convenient to be able to view both at the same time. The interactive version +will save you a lot of typing or copy/pasting, but content may be easier to read in a web-browser. ## Goals -Let’s say you’re the founder of CloudEcho, the number one provider of echo services. When someone needs to hear back what they’ve said, you are here to make that happen. Any way that a customer needs an echo, be it TCP, UDP or HTTP based echo services, you’ve got them covered. You’ve decided to try integrating Ziti into your services and clients. We’re going to look at a few different ways to do that integration and explore different areas of the Ziti model to build an understanding of how to provide and consume services. In this tutorial we’re going look at how to Zitify the HTTP echo service. + +Let’s say you’re the founder of CloudEcho, the number one provider of echo services. When someone needs to hear back what they’ve said, you are here to make that happen. Any way that a customer needs +an echo, be it TCP, UDP or HTTP based echo services, you’ve got them covered. You’ve decided to try integrating Ziti into your services and clients. We’re going to look at a few different ways to do +that integration and explore different areas of the Ziti model to build an understanding of how to provide and consume services. In this tutorial we’re going look at how to Zitify the HTTP echo +service. @@ -38,7 +44,8 @@ If your session times out you can run ziti edge login again. Great, now that we’re logged in, let’s find our local edge router. -Note that this tutorial assumes that you’ve got an edge router running on the same machine as this tutorial. We'll be using localhost when telling the edge router how to connect to the tutorial services. If you don’t have an edge router running locally, please add one now. +Note that this tutorial assumes that you’ve got an edge router running on the same machine as this tutorial. We'll be using localhost when telling the edge router how to connect to the tutorial +services. If you don’t have an edge router running locally, please add one now. ```action:select-edge-router Pick the name of an edge router to use. It will be referenced in this tutorial as ${edgeRouterName} @@ -46,7 +53,8 @@ Pick the name of an edge router to use. It will be referenced in this tutorial a ### Cleanup Tutorial Entities -Finally, in case you've run this tutorial before or have done other experimentation with this system the tutorial may not run cleanly. To ensure a clean run we're going to clean up any entities that would conflict with entities created as part of this tutorial. +Finally, in case you've run this tutorial before or have done other experimentation with this system the tutorial may not run cleanly. To ensure a clean run we're going to clean up any entities that +would conflict with entities created as part of this tutorial. ```action:ziti ziti edge delete service echo @@ -58,7 +66,8 @@ ziti edge delete service-edge-router-policies where true # Setting up the Echo Service -We want to run our echo service over Ziti. We need to create various entities in Ziti for this to work, but the first is the service itself. The most important property of a Ziti service is its name. The service name is similar to a DNS name. When we connect to a service or host a service, we do so by name. +We want to run our echo service over Ziti. We need to create various entities in Ziti for this to work, but the first is the service itself. The most important property of a Ziti service is its name. +The service name is similar to a DNS name. When we connect to a service or host a service, we do so by name. ## Creating the Echo Service @@ -82,13 +91,13 @@ So, now Ziti has an echo service, but that service doesn’t go anywhere, and we ### Plain Echo Service -Let’s start by assuming we don’t want to make any changes to our server software yet. We’re just going to run an echo server which uses the standard library facilities. Let's start that service now. The source can be found [here](../plain_echo_server.go). +Let’s start by assuming we don’t want to make any changes to our server software yet. We’re just going to run an echo server which uses the standard library facilities. Let's start that service now. +The source can be found [here](../plain_echo_server.go). - ```action:run-plain-echo-server -ziti edge tutorial plain-echo-server +ziti demo plain-echo-server ``` The output for this service will be prefixed with plain-http-echo-server. The service is running on port ${port}. We can hit this service with a browser at @@ -106,14 +115,16 @@ The source for the plain client code is very simple, and can be found here: [her Let's run it and see what the output looks. At this point, we're not sending any traffic over the Ziti network. ```action:ziti templatize=true colorStdOut=false -ziti edge tutorial plain-echo-client --port ${port} trees are tall +ziti demo plain-echo-client --port ${port} trees are tall ``` ### Terminators Overview -Now that we’ve got the service running, we can tell Ziti how to connect to it. We do that by creating a terminator for the service. A terminator represents an off ramp from the Ziti fabric. When we make a connection through Ziti to a service, the fabric will select a terminator to which traffic will be routed for that connection. For some types of terminators, each connection to the service will result in a new connection from the router to the hosting application. For others, an existing connection between the router and the hosting application will be used. +Now that we’ve got the service running, we can tell Ziti how to connect to it. We do that by creating a terminator for the service. A terminator represents an off ramp from the Ziti fabric. When we +make a connection through Ziti to a service, the fabric will select a terminator to which traffic will be routed for that connection. For some types of terminators, each connection to the service will +result in a new connection from the router to the hosting application. For others, an existing connection between the router and the hosting application will be used. ### Creating the Terminator @@ -121,7 +132,8 @@ Let's create the terminator. The arguments to the create terminator command are: 1. **echo**: The service we’re creating the terminator for 1. **${edgeRouterName}**: The edge router we want to use to connect to the application server -1. **tcp:localhost:${port}**: The application server address. The format is protocol:hostname-or-ip:port. The protocol can be any of tcp, udp or tls. This address is what the edge router will try to make a +1. **tcp:localhost:${port}**: The application server address. The format is protocol:hostname-or-ip:port. The protocol can be any of tcp, udp or tls. This address is what the edge router will try to + make a connection to when someone uses the service. ```action:ziti templatize=true @@ -140,12 +152,16 @@ ziti edge list terminators 'service.name="echo"' # Client Application -In order to use the echo service, we need a client application. Our application could embed a Ziti SDK, but unmodified applications can also access Ziti services using a tunneler application. See [Tunnelers](https://openziti.github.io/ziti/clients/tunneler.html) for more information. Even though we're not covering tunnelers here, they are built with the Ziti SDKs. Therefore, all the following setup and configuration applies to them. +In order to use the echo service, we need a client application. Our application could embed a Ziti SDK, but unmodified applications can also access Ziti services using a tunneler application. +See [Tunnelers](https://openziti.github.io/ziti/clients/tunneler.html) for more information. Even though we're not covering tunnelers here, they are built with the Ziti SDKs. Therefore, all the +following setup and configuration applies to them. ## SDK Client -Let's take a look at our SDK client code (viewable [here](../ziti_echo_client.go)). If you compare it with the plain version, you'll notice the actual client code is very similar. The main differences are + +Let's take a look at our SDK client code (viewable [here](../ziti_echo_client.go)). If you compare it with the plain version, you'll notice the actual client code is very similar. The main +differences are 1. We have to load our configuration, so that we can initialize a Ziti Context 2. We have to intercept the Dial in the HTTP client, so we can send it through Ziti @@ -159,26 +175,33 @@ We can't actually run it yet, because we don't have a configuration for it. As a # Identity Setup -The main thing our client needs to connect to the Ziti network is an identity. One of the primary functions of Ziti is controlling access to services. In order for us to access the echo service, our client application needs an identity, so that we can permit that identity access. +The main thing our client needs to connect to the Ziti network is an identity. One of the primary functions of Ziti is controlling access to services. In order for us to access the echo service, our +client application needs an identity, so that we can permit that identity access. ## What is an Identity? -It's tempting to think of an identity as either a user or a device, but neither of those is entirely correct. A user may have multiple devices, each of which accesses the same service. In general a user will have a separate identity for each device from which they access the service. There are a few reasons for this approach: +It's tempting to think of an identity as either a user or a device, but neither of those is entirely correct. A user may have multiple devices, each of which accesses the same service. In general a +user will have a separate identity for each device from which they access the service. There are a few reasons for this approach: * By enrolling each device individually we don’t transfer the credentials between devices. Because we generate the credentials each device, they can’t be intercepted. * If someone compromises one device, we can disable just that identity, rather than everything for a given user. -* Traffic becomes attributable to a given device, which can be useful in a number of ways, including identifying misbehaving applications. +* Traffic becomes attributable to a given device, which can be useful in a number of ways, including identifying misbehaving applications. -If a user belongs to multiple Ziti networks, they will likely have multiple identities per device. In general, multiple identities on the same device for the same user will not cause any conflicts, unlike trying to run multiple VPNs concurrently. Even when using a Ziti tunnelers, each tunneler can host multiple identities concurrently and there will only be problems if multiple services try to claim the same DNS name or IP. +If a user belongs to multiple Ziti networks, they will likely have multiple identities per device. In general, multiple identities on the same device for the same user will not cause any conflicts, +unlike trying to run multiple VPNs concurrently. Even when using a Ziti tunnelers, each tunneler can host multiple identities concurrently and there will only be problems if multiple services try to +claim the same DNS name or IP. Creating the Identity -When creating an identity you can specify the identity type as being one of user, service or device. These types are currently just descriptive, and do not affect how Ziti uses the identities. Let’s say you’re running your own software on your laptop. Since you are the company founder, we’ll name the identity founder-laptop. We're also going to assign the identity a role attribute of management. We can use this later when setting up policies. +When creating an identity you can specify the identity type as being one of user, service or device. These types are currently just descriptive, and do not affect how Ziti uses the identities. Let’s +say you’re running your own software on your laptop. Since you are the company founder, we’ll name the identity founder-laptop. We're also going to assign the identity a role attribute of management. +We can use this later when setting up policies. ```action:ziti ziti edge create identity user founder-laptop -o founder-laptop.jwt --role-attributes management ``` -We have now created a new identity of type user with name founder-laptop. When Ziti created the identity, a one-time-token was also generated, which we’ve stored in founder-client.jwt. We’ll use this token to enroll our identity and create the configuration file for our client application. +We have now created a new identity of type user with name founder-laptop. When Ziti created the identity, a one-time-token was also generated, which we’ve stored in founder-client.jwt. We’ll use this +token to enroll our identity and create the configuration file for our client application. Let’s take a look at our new identity: @@ -186,9 +209,11 @@ Let’s take a look at our new identity: ziti edge list identities 'name="founder-laptop"' ``` -In addition to the id, name and type we can see that identities, like services and edge routers, also have role-attributes. In the next section we'll be configuring service access, and we’ll see how role attributes come into play. +In addition to the id, name and type we can see that identities, like services and edge routers, also have role-attributes. In the next section we'll be configuring service access, and we’ll see how +role attributes come into play. -Before we can use our new identity, we need to enroll it. Enrolling the identity uses up the one-time-token and generates the configuration, keys and certificates that a client needs to use a Ziti network. +Before we can use our new identity, we need to enroll it. Enrolling the identity uses up the one-time-token and generates the configuration, keys and certificates that a client needs to use a Ziti +network. ```action:ziti ziti edge enroll -j founder-laptop.jwt -o founder-laptop.json @@ -196,28 +221,33 @@ ziti edge enroll -j founder-laptop.jwt -o founder-laptop.json The resulting json file has the address of the Ziti controller, as well as the keys and certificates it needs to establish its identity and communicate securely with that controller. -As you saw, the usual flow for enrolling an application in a ziti network has two steps. However, unlike in our approach here, we don’t generally rely on the Ziti CLI to enroll applications and generate a config file. A more conventional flow would be: +As you saw, the usual flow for enrolling an application in a ziti network has two steps. However, unlike in our approach here, we don’t generally rely on the Ziti CLI to enroll applications and +generate a config file. A more conventional flow would be: 1. Create an identity for the application, which includes generating a JWT 1. The application imports JWT, enrolls in the application and stores the configuration in its own format. 1. Two ways that applications can import JWTs are by reading a file or reading a QR code -The reason enrollment is broken into two steps is because we only want the keys and certificates to exist on their final destination. We may transmit the JWT, but once enrollment has completed the JWT cannot be re-used. If someone later steals the JWT, they won’t be able to enroll, as the JWT will no longer be valid. If some intercepts the JWT and uses it to enroll before the intended recipient does, the intended recipient’s enrollment will fail. This lets us know that something is wrong, we can disable the identity, and the intrusion will have been quickly detected. +The reason enrollment is broken into two steps is because we only want the keys and certificates to exist on their final destination. We may transmit the JWT, but once enrollment has completed the JWT +cannot be re-used. If someone later steals the JWT, they won’t be able to enroll, as the JWT will no longer be valid. If some intercepts the JWT and uses it to enroll before the intended recipient +does, the intended recipient’s enrollment will fail. This lets us know that something is wrong, we can disable the identity, and the intrusion will have been quickly detected. ## Trying the Ziti Client + Now that we’re enrolled, let’s try to use our echo service. ```action:ziti failOk=true -ziti edge tutorial ziti-echo-client --identity founder-laptop.json trees are tall +ziti demo ziti-echo-client --identity founder-laptop.json trees are tall ``` Hopefully it comes as no surprise that the echo service wasn’t found, given that we haven’t granted our identity any access yet. # Policy Configuration -The Ziti CLI comes with a tool called policy-advisor to help you figure out if you have correctly configured your policies. Let’s try it out. It can be run either from a service or identity-centric perspective. This following command will check if the founder-laptop identity can access the echo service. +The Ziti CLI comes with a tool called policy-advisor to help you figure out if you have correctly configured your policies. Let’s try it out. It can be run either from a service or identity-centric +perspective. This following command will check if the founder-laptop identity can access the echo service. ```action:ziti ziti edge policy-advisor identities -q founder-laptop echo @@ -240,7 +270,8 @@ First we need to grant access to use the service via a service policy. There are * Dial - allow using a service * Bind - allow hosting a service -For now, we just need a dial policy. Later, when we try hosting the echo service with an SDK embedded application, we’ll need a bind policy as well. We're going to explicitly add our service and identity to this policy. The service we'll reference by name. The identity we'll include by role attribute. +For now, we just need a dial policy. Later, when we try hosting the echo service with an SDK embedded application, we’ll need a bind policy as well. We're going to explicitly add our service and +identity to this policy. The service we'll reference by name. The identity we'll include by role attribute. For a deep dive into policies, see [here](https://docs.openziti.io/docs/learn/core-concepts/security/authorization/policies/overview/). @@ -263,7 +294,7 @@ ziti edge policy-advisor identities -q founder-laptop echo If we try to run the client again, it still won’t work, but we should see a different error: ```action:ziti failOk=true -ziti edge tutorial ziti-echo-client --identity founder-laptop.json trees are tall +ziti demo ziti-echo-client --identity founder-laptop.json trees are tall ``` This error indicates that we have access to the service, but not to any edge routers. @@ -272,13 +303,14 @@ This error indicates that we have access to the service, but not to any edge rou # Edge Router Policy Setup -Now we need to give our client access to one or more edge routers. Edge routers are how client traffic enters a Ziti network. We need to be able to limit which identities can access specific edge routers. Certain edge routers may be dedicated to specific regions, users or services. +Now we need to give our client access to one or more edge routers. Edge routers are how client traffic enters a Ziti network. We need to be able to limit which identities can access specific edge +routers. Certain edge routers may be dedicated to specific regions, users or services. ```action:ziti templatize=true ziti edge create edge-router-policy echo-clients --edge-router-roles '@${edgeRouterName}' --identity-roles '#management' ``` -Taking a look, we should see now see our new edge router policy in place. +Taking a look, we should see now see our new edge router policy in place. ```action:ziti ziti edge list edge-router-policies 'name="echo-clients"' @@ -295,23 +327,25 @@ We should only have one error left, letting us know that the service doesn’t h If we run the client again, we’ll see the same error: ```action:ziti failOk=true -ziti edge tutorial ziti-echo-client --identity founder-laptop.json trees are tall +ziti demo ziti-echo-client --identity founder-laptop.json trees are tall ``` -This is because the edge routers that can be used when establishing a session is the set of edge routers that the identity and service have in common. +This is because the edge routers that can be used when establishing a session is the set of edge routers that the identity and service have in common. Looking at an example: -1. We have an identity which has access to edge routers A, B, C +1. We have an identity which has access to edge routers A, B, C 2. We have a service which can be accessed via edge routers C, D, E 3. That identity can only access the service via edge router C, since that's the only one they have in common -Even though our identity now has access to our edge router, our service does not. +Even though our identity now has access to our edge router, our service does not. # Service Edge Router Policies -In the same way that we can assign edge routers to users, we can also assign them to services. You may wish to do this to conform with local regulations or to give services exclusive access to certain edge routers to meet SLAs. + +In the same way that we can assign edge routers to users, we can also assign them to services. You may wish to do this to conform with local regulations or to give services exclusive access to certain +edge routers to meet SLAs. Since we don’t have any special requirements right now, let’s let the echo service use all edge routers. There’s a special role attribute of #all which will match all entities of a given type. @@ -328,7 +362,7 @@ ziti edge policy-advisor identities -q founder-laptop echo We should now finally be able to run the client and get some validation. ```action:ziti colorStdOut=false -ziti edge tutorial ziti-echo-client --identity founder-laptop.json trees are tall +ziti demo ziti-echo-client --identity founder-laptop.json trees are tall ``` We have our first successful connection through Ziti! **\o/** @@ -350,7 +384,7 @@ Stop the plain echo server We’ve stopped the echo server. If we now run the client, we’ll see an error saying that dialing the service fails. ```action:ziti colorStdOut=false failOk=true -ziti edge tutorial ziti-echo-client --identity founder-laptop.json trees are tall +ziti demo ziti-echo-client --identity founder-laptop.json trees are tall ``` We’re going to also remove the terminator, since that now points to an address where nothing is running. @@ -362,7 +396,7 @@ ziti edge delete terminators where 'service.name="echo"' If we now run the client, we’ll see an error saying that the service has no terminators. ```action:ziti colorStdOut=false failOk=true -ziti edge tutorial ziti-echo-client --identity founder-laptop.json trees are tall +ziti demo ziti-echo-client --identity founder-laptop.json trees are tall ``` ## Hosting Identity @@ -381,14 +415,14 @@ ziti edge enroll -j echo-server.jwt -o echo-server.json ## Ziti Server Code -If you look at the server code, viewable [here](../ziti_echo_server.go), you should notice very few changes from the plain echo server. The actual request processing code is unchanged. +If you look at the server code, viewable [here](../ziti_echo_server.go), you should notice very few changes from the plain echo server. The actual request processing code is unchanged. Let’s try running it and see what happens. ```action:ziti colorStdOut=false failOk=true -ziti edge tutorial ziti-echo-server --identity echo-server.json +ziti demo ziti-echo-server --identity echo-server.json ``` Since we haven’t given this identity access to the server, it fails. @@ -403,7 +437,7 @@ ziti edge policy-advisor identities -q echo-server echo We should only see two errors. -* The identity does not have access to the service +* The identity does not have access to the service * The identity does not have access to any edge routers We already granted the echo service to all edge routers, so that is no longer an issue. @@ -426,7 +460,8 @@ Running the policy advisor again, we should be down to one error: ziti edge policy-advisor identities -q echo-server echo ``` -Finally, we create an edge router policy for the hosting identity. In this case, it has the same servers as the client side, but generally client side and hosting side identities will use different sets of edge routers. +Finally, we create an edge router policy for the hosting identity. In this case, it has the same servers as the client side, but generally client side and hosting side identities will use different +sets of edge routers. ```action:ziti templatize=true ziti edge create edge-router-policy echo-servers --edge-router-roles '@${edgeRouterName}' --identity-roles '#echo-server' @@ -441,36 +476,39 @@ ziti edge policy-advisor identities -q echo-server echo Let’s give it a try now and see what happens: ```action:run-ziti-echo-server -ziti edge tutorial ziti-echo-server +ziti demo ziti-echo-server ``` -Our client should work correctly. Hang on, you may be saying… won’t it fail with a ‘no terminators’ error? After all, we haven’t created a new terminator yet. Well, let’s take a look at our terminators: +Our client should work correctly. Hang on, you may be saying… won’t it fail with a ‘no terminators’ error? After all, we haven’t created a new terminator yet. Well, let’s take a look at our +terminators: ```action:ziti failOk=true ziti edge list terminators 'service.name="echo"' ``` -There’s a terminator present, which looks quite different from the one we created manually. This terminator was created dynamically when the ziti-echo-server application bound the service. Client connections for echo will now get made over the existing network connection that the server application has with the edge router. When you stop the server application, the terminator will be removed. +There’s a terminator present, which looks quite different from the one we created manually. This terminator was created dynamically when the ziti-echo-server application bound the service. Client +connections for echo will now get made over the existing network connection that the server application has with the edge router. When you stop the server application, the terminator will be removed. -This has some interesting benefits. For example, this can make horizontal scale easier to accomplish. When you spin up new instances of the server application, they can connect, dynamically create a terminator and start receiving a portion of the service traffic. When you stop an application, or it dies, the connection goes away, and the terminator is automatically removed. New connections won’t try to connect that application server anymore. +This has some interesting benefits. For example, this can make horizontal scale easier to accomplish. When you spin up new instances of the server application, they can connect, dynamically create a +terminator and start receiving a portion of the service traffic. When you stop an application, or it dies, the connection goes away, and the terminator is automatically removed. New connections won’t +try to connect that application server anymore. Hosting configuration and topologies is a big topic. For now, let’s try out our client with our single server instance running: ```action:ziti colorStdOut=false -ziti edge tutorial ziti-echo-client --identity founder-laptop.json trees are tall +ziti demo ziti-echo-client --identity founder-laptop.json trees are tall ``` -Success, we now have a Ziti SDK-embedded client, communicating with a Ziti SDK-embedded server. +Success, we now have a Ziti SDK-embedded client, communicating with a Ziti SDK-embedded server. Thanks for taking the time to learn about Ziti services, identities and policies! Hopefully you’ve gotten a better idea of: -* How to create and manage services and identities +* How to create and manage services and identities * How terminators connect the Ziti fabric with application servers * How to manage client access with identities and service policies * How edge routers can be assigned across identities with edge router policies * How edge routers can be assigned across services with service edge router polices - See the [Documentation Hub](https://openziti.github.io/) for more resources. diff --git a/ziti/cmd/tutorial/tutorials/more-info.md b/ziti/cmd/demo/tutorials/more-info.md similarity index 100% rename from ziti/cmd/tutorial/tutorials/more-info.md rename to ziti/cmd/demo/tutorials/more-info.md diff --git a/ziti/cmd/tutorial/ziti_echo_client.go b/ziti/cmd/demo/ziti_echo_client.go similarity index 99% rename from ziti/cmd/tutorial/ziti_echo_client.go rename to ziti/cmd/demo/ziti_echo_client.go index df698d7e1..4084e9e3b 100644 --- a/ziti/cmd/tutorial/ziti_echo_client.go +++ b/ziti/cmd/demo/ziti_echo_client.go @@ -14,7 +14,7 @@ limitations under the License. */ -package tutorial +package demo import ( "context" diff --git a/ziti/cmd/tutorial/ziti_echo_client_cmd.go b/ziti/cmd/demo/ziti_echo_client_cmd.go similarity index 99% rename from ziti/cmd/tutorial/ziti_echo_client_cmd.go rename to ziti/cmd/demo/ziti_echo_client_cmd.go index 2a0561db8..72ce70397 100644 --- a/ziti/cmd/tutorial/ziti_echo_client_cmd.go +++ b/ziti/cmd/demo/ziti_echo_client_cmd.go @@ -14,7 +14,7 @@ limitations under the License. */ -package tutorial +package demo import ( "github.com/openziti/ziti/ziti/cmd/common" diff --git a/ziti/cmd/tutorial/ziti_echo_server.go b/ziti/cmd/demo/ziti_echo_server.go similarity index 99% rename from ziti/cmd/tutorial/ziti_echo_server.go rename to ziti/cmd/demo/ziti_echo_server.go index aa786d048..849c6ade8 100644 --- a/ziti/cmd/tutorial/ziti_echo_server.go +++ b/ziti/cmd/demo/ziti_echo_server.go @@ -14,7 +14,7 @@ limitations under the License. */ -package tutorial +package demo import ( "fmt" diff --git a/ziti/cmd/tutorial/ziti_echo_server_cmd.go b/ziti/cmd/demo/ziti_echo_server_cmd.go similarity index 99% rename from ziti/cmd/tutorial/ziti_echo_server_cmd.go rename to ziti/cmd/demo/ziti_echo_server_cmd.go index 7e6dcd30a..87e723fe9 100644 --- a/ziti/cmd/tutorial/ziti_echo_server_cmd.go +++ b/ziti/cmd/demo/ziti_echo_server_cmd.go @@ -14,7 +14,7 @@ limitations under the License. */ -package tutorial +package demo import ( "github.com/openziti/ziti/ziti/cmd/common" diff --git a/ziti/cmd/tutorial/root.go b/ziti/cmd/tutorial/root.go deleted file mode 100644 index 2a735ce8f..000000000 --- a/ziti/cmd/tutorial/root.go +++ /dev/null @@ -1,68 +0,0 @@ -/* - Copyright NetFoundry Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package tutorial - -import ( - "github.com/openziti/ziti/ziti/cmd/common" - "github.com/openziti/ziti/ziti/cmd/edge" - cmdhelper "github.com/openziti/ziti/ziti/cmd/helpers" - "github.com/spf13/cobra" - "time" -) - -func init() { - edge.ExtraEdgeCommands = append(edge.ExtraEdgeCommands, NewTutorialCmd) -} - -func NewTutorialCmd(p common.OptionsProvider) *cobra.Command { - cmd := &cobra.Command{ - Use: "tutorial", - Short: "Interactive tutorials for learning about Ziti", - Run: func(cmd *cobra.Command, args []string) { - err := cmd.Help() - cmdhelper.CheckErr(err) - }, - } - - cmd.AddCommand(newFirstServiceTutorialCmd(p)) - cmd.AddCommand(newPlainEchoServerCmd(p)) - cmd.AddCommand(newPlainEchoClientCmd(p)) - cmd.AddCommand(newZitiEchoClientCmd(p)) - cmd.AddCommand(newZitiEchoServerCmd(p)) - - return cmd -} - -type TutorialOptions struct { - ControllerUrl string - Username string - Password string - NewlinePause time.Duration - AssumeDefault bool -} - -func (self *TutorialOptions) GetControllerUrl() string { - return self.ControllerUrl -} - -func (self *TutorialOptions) GetUsername() string { - return self.Username -} - -func (self *TutorialOptions) GetPassword() string { - return self.Password -} diff --git a/zititest/go.mod b/zititest/go.mod index 593c1a6ab..2bcf1da9c 100644 --- a/zititest/go.mod +++ b/zititest/go.mod @@ -11,7 +11,7 @@ require ( github.com/michaelquigley/pfxlog v0.6.10 github.com/openziti/channel/v2 v2.0.81 github.com/openziti/edge v0.24.348 - github.com/openziti/fablab v0.5.0 + github.com/openziti/fablab v0.5.1 github.com/openziti/fabric v0.23.39 github.com/openziti/foundation/v2 v2.0.26 github.com/openziti/identity v1.0.57 diff --git a/zititest/go.sum b/zititest/go.sum index 6add6d731..388aef12f 100644 --- a/zititest/go.sum +++ b/zititest/go.sum @@ -647,8 +647,8 @@ github.com/openziti/edge v0.24.348 h1:m0zh1n5OBj7Tjemu9XfxdYhbKvo6HdGQMHfC/Zledb github.com/openziti/edge v0.24.348/go.mod h1:wcoBVKqW99/CBg7LjzbHT3dJ8JMlRZelPhbjNcjwMr0= github.com/openziti/edge-api v0.25.29 h1:DbZ0GKpch9ik+XnC0qoP1dOg1AmgUEeWH4VIyOIehrQ= github.com/openziti/edge-api v0.25.29/go.mod h1:mqYquh1RYvHjV/M9MGO4Ng0Xq9ixAH8o8cT5kw5XCWY= -github.com/openziti/fablab v0.5.0 h1:+CMVE08hS62UR3tIfEvMb3XcWwzD1np//cpHHTboVW8= -github.com/openziti/fablab v0.5.0/go.mod h1:VLvs0AAuKAFqy8jhGvs+FgszYEbB456duPOBfRj4peU= +github.com/openziti/fablab v0.5.1 h1:dc3c88vBuJ4OlIyv49T+xUy/GNtvQ0lsEUPE9VH2MCE= +github.com/openziti/fablab v0.5.1/go.mod h1:VLvs0AAuKAFqy8jhGvs+FgszYEbB456duPOBfRj4peU= github.com/openziti/fabric v0.23.39 h1:t6npkfBsPr+8GvF9X/rTLHxGsce5FTa4NoA80MInxYc= github.com/openziti/fabric v0.23.39/go.mod h1:px2zUzQrgObmv6sdXgGJ028Wr45aGcgpFvfIP/qgDFo= github.com/openziti/foundation/v2 v2.0.26 h1:RISGM97aIFnnEkxrcVsO6fnGg9W4ByydSnqWAVkJAqY= diff --git a/zititest/tests/echo_test.go b/zititest/tests/echo_test.go index c18a8742a..4aa844f59 100644 --- a/zititest/tests/echo_test.go +++ b/zititest/tests/echo_test.go @@ -40,10 +40,11 @@ func TestSdkEcho(t *testing.T) { ssh := lib.NewSshConfigFactory(c.GetHost()) remoteConfigFile := "/home/ubuntu/fablab/cfg/" + c.Id + ".json" - echoClientCmd := fmt.Sprintf(`echo "%s" | /home/%s/fablab/bin/ziti learn demo zcat --identity %s ziti:echo 2>&1`, + echoClientCmd := fmt.Sprintf(`echo "%s" | /home/%s/fablab/bin/ziti demo zcat --identity %s ziti:echo 2>&1`, string(data), ssh.User(), remoteConfigFile) output, err := lib.RemoteExec(ssh, echoClientCmd) + t.Logf("test output:\n%s", output) req.NoError(err) //trim the newline ssh added output = strings.TrimRight(output, "\n")