A powershell module for managing your ruby environments on Windows.
I’ve been working with Ruby in a Windows environment for a little over a year now. I’m sad to say that community support for Windows developers is lackluster. We are second-class citizens.
The most frustrating example of this is the lack of decent ruby version switchers. rvm doesn’t install on Windows at all. Ditto for rbenv. uru is a valiant attempt, but it is cumbersome to install and it’s API is less than intuitive.
The Need
This wouldn’t much of an issue if ruby installations were backwards compatible, but that is not the case. Even minor version releases of Ruby can incur breaking changes ruining your execution environment.
For development purposes, it’s a good idea to install the new ruby version, switch your ruby environment, then run all your tests on all your projects to verify compatibility. If you need to roll back, just switch your ruby environment and everything is good.
The Strategy
As I started digging into how tools like rvm and rbenv work, I became surprised a the difficuly of reimplementing them on Windows. Aside from the installation features (e.g. rvm install ruby-version
), ruby version management is basically just editing the PATH
variable. In other words, the great barrier, the monumental technical challenge that prevents anyone from developing an easy-to-install , easy-to-use ruby version switcher is: string manipulation.
The Requirements
A ruby version switcher needs to know the location of installed rubies. It needs to be able to alter the PATH
for the current session such that the desired ruby is the one being used.
Note
It is not the norm for Windows developers to think about altering their terminal session. It is the norm that alterations to the PATH are permanent. Reorienting our thinking around editing our Session as against our Environment has many benefits which I won’t go into here–except to say that it makes the issue of version switching much simpler.
My Solution
I wrote a powershell module called psundle-ruby (compatible with psundle) to discover and manage ruby versions. If you have not looked at psundle
, I encourage you to do so as it makes installation of this module as simple as:
Install-PsundleModule 'crmckenzie' 'psundle-ruby'
If you have already installed rubies in your Windows environment, you can execute Register-RubiesInPath
to make Psundle aware of them. Otherwise you can invoke Register-Ruby
for each ruby location not found in your PATH
.
Invoke Use-Ruby
to switch to the desired version. The argument to Use-Ruby
is an expression. The command switches to the first ruby it finds that matches the expression.
For example, if I have the following rubies installed on my machine:
* ruby-1.9.3
* ruby-2
* ruby-2.1
* ruby-2.2
I can invoke Use-Ruby "1"
to switch to ruby-1.9.3. Invoking Use-Ruby "2"
will switch me to ruby-2.
Invoke Set-DefaultRuby
to permanently alter your PATH
variable to automatically select the chosen ruby.
Committment to Maintain
I commit to maintaining this powershell module through the end of 2016. If you find issues, please report them or (even better) submit a pull-request. I will reevaluate my committment at the end of 2016 based on the level of interest and usage of this module.
At Redacted Industries we use Chef to deploy our applications to various environments. An environment for us is a complete set of servers and applications configured to talk to one another. Environments are designed to mirror prod as much as possible.
The majority of our applications are written in C# and target the Windows operating system. Accordingly, developers are assigned a windows VDI and given access to a spate of tools for Windows-based development.
Our DevOps group on the other hand primarily works in Chef & Ruby. Their standard-issue hardware is a Macbook Pro.
Ruby on Windows
Ruby is less than awesome on Windows. There are a host of issues, but the main problem is that gem does not want to install binaries to the host OS. Rather, gems that require C-compilation are built from source when they are installed on the target OS. Gem developers do not always test their C-compilation on both Linux & Windows so Windows compilation is often neglected.
The community attitude toward this problem tends toward “Show me the PR!” This is a typical attitude in open-source, but few modern developers have the stamina to master the vagaries of C-compilers so the reality is these sorts of problems are seldom touched.
Despite these problems, I am able to develop Ruby applications on Windows with relative ease. It takes some time and effort to learn where the dragons are and slay them, but it can be done.
ChefDK & Embedded Rubies
Ruby devs often want to build gems against different versions of Ruby. Controlling which version of ruby you’re using at any one time is a challenge. There are tools such as rvm & rbenv to help but the tools are not awesome. To further complicate matters, OS/X comes with its own embedded Ruby as does ChefDK.
It is a challenge to keep straight which code is supposed to be installed in and run in the context of which version or Ruby, especially since Chef can be used to install versions of Ruby different than what it is running under. Further, the ChefDK is not designed to play-nice with other Rubies. In discussions with the developers at OpsCode, they say that the ChefDK is designed for people who are not going to be developing Ruby applications in any environment other than Chef. It becomes problematic when the Chef docs tell you to install certain gems and you end up installing them into the wrong Ruby. Gah!
Docker
If you haven’t read about Docker yet, stop reading this blog and go read about Docker. Docker lets you create lightweight VMs known as containers. A container isn’t really a VM–it’s a process. I think of it as a process that thinks it’s a VM.
What if we could create a docker container pre-configured with the ChefDK such that the Chef tools are deployed correctly in a way that is isolated from my other Rubies? Ideally, I’d be able to point the ChefDK container to my local source files on Windows. I can still be on the network, have access to email and company chat, use my favorite text editors–but when I need chef commands, I can duck into the container context long enough to do what I need to do there and get out.
Sounds awesome!
The DockerFile
A Dockerfile is a description of an image that you wish to build. Here is a sample:
FROM ubuntu
MAINTAINER Chris McKenzie <crmckenzie@redacted.com>
RUN apt-get update
RUN apt-get install -y curl git build-essential libxml2-dev libxslt-dev wget lsb-release
# RUN curl -L https://www.opscode.com/chef/install.sh | sudo bash
### INSTALL CHEFDK
RUN wget https://opscode-omnibus-packages.s3.amazonaws.com/ubuntu/12.04/x86_64/chefdk_0.6.2-1_amd64.deb
RUN dpkg -i chefdk_0.6.2-1_amd64.deb && rm chefdk_0.6.2-1_amd64.deb
RUN chef verify
### CLEANUP
RUN apt-get autoremove
RUN apt-get clean
First, you use the Dockerfile to build the image.
docker build -t chef-workstation .
The Powershell Script
To make the Docker image usable, I need to create containers from it. Containers are instances of an image that you can use. Containers are disposable. To that end let’s write some powershell to wrap up complex docker commands into something I can call easily.
$username = $env:UserName
function Invoke-Knife() {
$cmd = "docker run --entrypoint=knife --rm -w='/root/src' -v /c/Users/$username/.chef:/root/.chef -v /c/Users/$username/src:/root/src -it chef-workstation $args"
write-debug $cmd
Invoke-Expression $cmd
}
function Invoke-Chef(){
$cmd = "docker run --entrypoint=chef --rm -w='/root/src' -v /c/Users/$username/.chef:/root/.chef -v /c/Users/$username/src:/root/src -it chef-workstation $args"
write-debug $cmd
Invoke-Expression $cmd
}
set-alias knife Invoke-Knife
set-alias chef Invoke-Chef
This script defines 2 functions: Invoke-Knife
and Invoke-Chef
. Let’s break this command down step by step.
- docker run
This command runs a container
- –entrypoint=knife
Tells Docker to execute 'knife' automatically when the container is created.
- –rm
Tells Docker to remove the container after its process stops.
- -w=’/root/src’
Tells Docker to run 'knife' in the working directory '/root/src'
- -v /c/Users/$username/.chef:/root/.chef
Tells Docker to share the .chef directory from the Host OS to '/root/.chef'
- -v /c/Users/$username/src:/root/src
Tells Docker to share the src directory from the Host OS to '/root/src'
- -it chef-workstation
Tells Docker to allocate a tty for the container process and create the container from the 'chef-workstation' image
- $args
This is the powershell variable containing the arguments passed to `Invoke-Knife`. These arguments are simply forwarded to knife when the container is executed.
What Happens Next?
When I execute ‘knife search “:“‘, ‘knife’ is an alias that executes Invoke-Knife–which starts the container and passes ‘search “:“‘ to the knife executable in the container. As soon as the ‘knife’ process finishes its work and emits its results, the container is shut down and deleted. If I execute ‘chef generate cookbook fredbob’, a similar process happens except that the ‘chef’ executable creates a cookbook in ‘/root/src’ on the container–which is mapped to my source directory on Windows. Both executables use the chef credentials I’ve defined in my .chef directory on my Host OS.
Feedback
I’m putting this out there because I’ve found it helpful, however there may be simpler, better ways of doing things. I’m open to comments and suggestions for other ways to resolve any of these issues, or for more interesting ways to use Docker.
Release Notes
0.3.0
- Richmond was failing on binary files. Richmond can now be configured to ignore
or target specific files or directories via a .richmond file found in the root
of the directory being processed.
Configuration
Richmond will look for a file called .richmond in the root directory you are running against.
If it finds the .richmond file, it will load it.
You can use the select or reject methods to change which files Richmond will include or exlude
in it’s processing.
# Richmond doesn't like binary files
Richmond.reject do |file|
file.match /images/
end
# I only want to parse .rb files
Richmond.select do |file|
file.match /\.rb$/
end
The Context
I’m building a Ruby API in Sinatra and publishing it’s documentation using Swagger. I didn’t really like any of the tools to generate swagger docs from the Ruby code so at first I was handwriting the JSON files myself. Then I decided that it would be easier to manage in YAML.
YAML was definitely a better solution than handwriting the JSON, but I was still wishing that the documentation for the models and API’s were embedded in the code they described.
The Solution
I decided that the documentation would live with the code come hell or high water. I figured I could use Ruby’s block comments with a little bit of special formatting to identify documentation sections throughout the code base and lace them all together into one or more output files. The result is Richmond (named after the character in the IT Crowd who would have benefitted from some documentation telling him what all the blinking lights did).
Usage
After you install the gem, you can execute the gem from the command-line like so:
richmond /dir/to/scan
You can find the code for richmond on github, as well as additional documentation.
Vim
I’m assuming you’d rather not compile Vim for Windows yourself. I don’t blame you. I tried it and it’s a nightmare.
These are manual steps, unfortunately. 🙁
For the best vim experience you should get the version compiled with Ruby and Python support.
Ruby and Python support does not necessarily mean that you will be coding in Ruby or Python, but many of the plugins that you will want to use require vim integration with Ruby and Python.
These instructions assume you are working in a Powershell console and that Chocolatey is installed.
Install Ruby
choco install ruby -Version 2.0.0.59800
If you need other version of ruby, go ahead and install them now.
Uru
You only need to install uru if you are using multiple rubies. Rvm does not work on Windows and Pik is no longer supported. Uru works fine.
You’ll need to add your ruby installations to uru using
uru admin add /path/to/ruby/bin
Installing Python
choco install Python2
Install Vim
Now you are ready to install vim.
Unfortunately, the version available on Chocolately.org is hard to get to work with Ruby and Python on Windows.
However, Alexander Shukaev (Haroogan) has compiled a version that works nicely.
Extract the zip file where you want it and make sure the location of vim.exe is in the path before any other vims on your system (for example, if you have msysgit installed).
Verification
If this works, you should be able to execute the following commands in vim.
:echo has('ruby') => 1
:echo has('python') => 1
Install .vimrc
I keep my .vimrc file as a gist.
I clone it into my C:\git directory.
git clone https://gist.github.com/crmckenzie/4913add34cd30abd4b93 vimrc
Then I create a symlink from my $HOME directory to the vimrc file. This allows me to maintain the file across machines using git as a synchronization tool.
From powershell
cmd /C mklink .vimrc C:\git\vimrc\.vimrc
The “cmd /C” section is necessary in Powershell because not all cmd.exe commands have been ported yet.
Install Vundle
Vundle is a package manager for Vim. It uses github as a package source. This is about as easy as it gets 🙂
git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim
Install Bundles
Open vim and run
:BundleInstall
This should download the bundles specified in .vimrc
I’ll maintain this documentation in my configure-win-dev-workstation repo.