My New Favorite: Fluent Interfaces

I have always been a big fan of the fluent programming style. Lately, I have been using it a lot. For instance, today I built an api to interact with VMWare where you can add new linux or windows machines and configure them fluently. In this post, I am going over an example that I built. The sample is a City Planner city builder. You can add streets and homes to your city. This is the results of the api that we will create.

Let’s start with our city planner. There isn’t much here other than the methods to add a street or a home.

To get started, we need to add some extension methods to start building out our api. Let’s create an extension that can create a street.

This is a standard extension method that hangs of of the City Planner. The one difference that makes all of this work is the return type. The return type is a CityPlanner. If you look back at the methods of the CityPlanner object, you can now call the method again since you have the instance of the Planner that you are working with.

Building a city street is easy ( on a computer), but building a home is a little more challenging. To build that we need to specify how many floors and windows, among other things. Sounds like a candidate for another builder. You can see on the House Planner, there is a method to build the house. One of the parameters is an anonymous function that takes a HouseBulder object and returns a Home. This will allow us to incrementally build our house with the same fluent style.

If you look at the HomeBuilder class, you can see that all of the methods return itself allow you to continue the method chaining.

In the end, we can print out our blueprint for our city.

Welcome to my city!
Streets
	Main Street
	1st Street
Homes
	Home: Floors=3, Windows=0

I enjoy this kind of expressive programming.  I think it is easy on the eyes.  Cheers!

SSH.NET & echo | sudo -S

This is an extension on yesterdays post about getting the test harness to connect to vSphere.  I am going to show how to use SSH.NET to run commands on a server, but more specifically how to make sudo calls remotely.

The goal for today was to take the new fresh vm server and install and configure Kafka.  Sounds easy, right?  I use SSH.NET to upload all of the files that I need as well as a shell script to orchestrate the operations.  We are using systemctl as the daemon manager, so we need the *.service files to be in the right directory.

/usr/lib/systemd/system

There is one snag.  You need to sudo because that directory is protected. Looking at some of the options for sudo, I came across the -S toggle.  This allows sudo to take the password from the standard input.

echo "my_password" | sudo mv myfile /protected_directory/

This will take the password and pipe it into the sudo move command.  I tried a bunch of ways to try to make this work.

using (var client = new SshClient("my_host_server", "my_user_name", "my_password"))
{
     client.Connect();
     var command = 
         client.CreateCommand(
         "echo 'my_password' | sudo -S sh ./home/my_user_name/scripts/install_and_configure.sh");
     
     var output = command.Execute();
     client.Disconnect();
}

This command works great on the server, but it doesn’t work in the SshClient’s session. I tried a bunch of variations of the code above, but none of them worked.  It was pretty frustrating. If you take a peek at the out the Execute() method, you will see the message sorry you need a tty to run sudo.  This is a significant clue.  A tty, or terminal emulator is what you would use if you created a ssh session with putty.   When we are using SSH.Net like we are above, we do not have a virtual terminal running.  That is what the error is telling us.  I started to piece together what might look like a solution after googling the interwebs.

download (4)

We need to somehow emulate a tty terminal using the library.

First thing that we need to do is create a new client and create a session.

SshClient client = new SshClient(server_address, 22, login, password);
client.Connect();

We need to start creating the terminal that will be used by the ShellStream.  First we have to create a dictionary of the terminal modes that we want to enable.

IDictionary<Renci.SshNet.Common.TerminalModes, uint> modes = 
new Dictionary<Renci.SshNet.Common.TerminalModes, uint>();
termkvp.Add(Renci.SshNet.Common.TerminalModes.ECHO, 53);

Adding the terminal mode to 53 or ECHO, will enable the echo functionality.  Now we need to create our ShellStream.  So we need to specify the terminal emulator that we want, the dimensions for the terminal and lastly the modes that we want to enable.

ShellStream shellStream = 
sshClient.CreateShellStream("xterm", 80, 24, 800, 600, 1024, modes);

Now that we have a terminal emulator to work with, we can start sending commands.  There are three commands that we need and they each will have a response that we expect.

  1. Login
  2. Send our sudo command
  3. Send the password

We have already created our session, logged in, so there should be an output waiting for us.  After we send our command, we should expect the password prompt.  Once we know that we are in the right spot, we can forward on the password.

var output = shellStream.Expect(new Regex(@"[$>]")); 

shellStream.WriteLine("sudo sh /home/my_user_name/scripts/install_and_configure.sh"); 
output = shellStream.Expect(new Regex(@"([$#>:])"));
shellStream.WriteLine(password);

At this point, we should have execute our install_and_configure.sh script successfully. Putting it all together:

SshClient client = new SshClient(server_address, 22, login, password);
client.Connect();

IDictionary<Renci.SshNet.Common.TerminalModes, uint> modes = 
new Dictionary<Renci.SshNet.Common.TerminalModes, uint>();
termkvp.Add(Renci.SshNet.Common.TerminalModes.ECHO, 53);

ShellStream shellStream = 
sshClient.CreateShellStream("xterm", 80, 24, 800, 600, 1024, modes);
var output = shellStream.Expect(new Regex(@"[$>]")); 

shellStream.WriteLine("sudo sh /home/my_user_name/scripts/install_and_configure.sh"); 
output = shellStream.Expect(new Regex(@"([$#>:])"));
shellStream.WriteLine(password);
client.Disconnect();

That is pretty much it.  I hope that this helps someone! Cheers