Saturday, November 2, 2013

Understanding (That There Exists) An Opposing View

If you are passionate about anything, chances are you’ve found yourself wondering “how in the world could that person believe x about an issue?!”, where x is avoiding semicolons in javascript, using tabs instead of spaces, being pro-gun, anti-cupcake, etc.

I used to find some of those moments very stressful. I’d be sitting there with lots of “evidence” and “research” to support my position, fuming about some idiot who can’t use apostrophes correctly and is frickin’ ridiculous with his/her hyperbole and logical fallacies…and get nowhere.

I finally figured out a little exercise that helps me in those tense situations: I just consider that the other person probably sees me as just as insane as I see him/her. That other person is more or less like me (rational, thoughtful, educated, kind, etc.) but disagrees with me the same way I disagree with him/her. And that’s fine.

“But I’m right!” you say.

Maybe you are. So what? Go tweet about it with loud, emphatic language! What good is that going to do? It makes you look like an ass when you’re right, and an ass when you’re wrong. Save your energy and hope that your noncombativeness will be reciprocated. To quote Dale Carnegie (more from him in a minute), “You can't win an argument. You can't because if you lose it, you lose it; and if you win it, you lose it.”

A baby step on the way to figuring all this out was more or less the development of a coping mechanism. I made a decision to not engage in fruitless debate. I noticed that many arguments I was having on Twitter and Facebook were fruitless…so I just stopped enabling myself to get all worked up over things 140 characters at a time.

Later, I had a realization when a friend help a position I found to be indefensible. As friends often do, we had an easy, dispassionate conversation. Neither of us won over the other person and that wasn’t the goal—we were just friends talking. That is when the real trick I described above sunk in: me and that other dude are similar.

Then this happened again when I was listening to a podcast I’d been listening to for a long time. My views usually line up well with the host’s (I listen for entertainment, not education). But then he passionately, and repeatedly started taking a position I am strongly opposed to. I literally said “Whoa, what?!” to myself the first time I heard it. Then I went though the whole exercise: this crazy smart, well informed guy has an insane position on this issue. Whoa! But that’s ok!

Now, suppose you actually want to change some hearts and minds on an issue. That’s really, really hard, and I’m not going to tackle that here. I can recommend these two books I recently finished, though: Switch and How To Win Friends and Influence People (aka HTWFAIP).

Both titles walk you through the basics of understanding problems, working with people, and affecting change. They use lots of anecdotes, guidelines, and tips.

If you’re not familiar with either, you might be surprised to hear that I found Switch to be more mechanical and duplicitous than HTWFAIP. It seems to focus more on the steps necessary to analyze situations and maneuver others into your line of thinking (or outright manipulate them). It also helpfully provides a framework for approaching and solving problems, and a vocabulary for talking about them. This has been very useful at work—we read it as a group and now refer to it and use its terms all the time.

HTWFAIP on the other hand sounds like it’d be all about subterfuge but is actually entirely genuine. It has a slightly different goal of getting people to like you and it encourages this almost exclusively by teaching you to be a nicer person. Seriously! It offers many tips and admits that they will be effective only if you are sincere.

Relating this back to the post, here’s another Dale Carnegie quote from HTWFAIP about arguments:

“I have come to the conclusion that there is only one way under high heaven to get the best of an argument— and that is to avoid it. Avoid it as you would avoid rattlesnakes and earthquakes.”

Both are good, easy reads, and great for groups. I suggest reading FTWFAIP first, then Switch.

Monday, September 9, 2013

Notify New Relic of Deployments with Chef

You can tell New Relic about your deployments and they’ll add vertical lines to the graphs at the corresponding times. This is super helpful as the (often dramatic) impact of a deployment becomes easy to grok.

The documentation on the Events > Deployments page is very helpful, but I still had to tinker with my message to New Relic’s API to get it to work. Here’s what I ended up with:

At the end of my recipe, post to the API with the http_request resource:

http_request "notify_new_relic" do
  action :post
  url "https://rpm.newrelic.com/deployments.xml"
  headers "x-api-key" => "#{node["newrelic"]["apikey"]}"
  message "application_id" => "#{node["newrelic"]["appid"]}"
end

I’m loading the key and application id from attributes. And it works (this is dev…no traffic there :))!

deployment-markers

This was crazy simple to do, though it might be better implemented as a report handler. If you go that route please share.

Tuesday, June 18, 2013

How to use services in a NopCommerce Plugin that it doesn’t use by default

Suppose you want to override some behavior in a NopCommerce service via a plugin. You would start by subclassing the service that has the behavior you want to override. But what if your new code requires access to something that the existing service doesn’t know about?

The answer turns out to be pretty simple: just add it to your constructor, and the dependency resolver will figure it out for you. (You don’t need to worry about it.)

Here’s an example. I want to override the PictureService. So I started with this:

    public class MyPictureService : PictureService
    {
        // constructor
        public MyPictureService(
            IRepository<Picture> pictureRepository, 
            IRepository<ProductPicture> productPictureRepository,
            ISettingService settingService, 
            IWebHelper webHelper, 
            ILogger logger, 
            IEventPublisher eventPublisher, 
            MediaSettings mediaSettings,
            : base(pictureRepository, productPictureRepository, settingService, 
                webHelper, logger, eventPublisher, mediaSettings)
        {
        }

        // my overrides
    }

So you can see that we get a lot of stuff by default. If we want to use any of those services, we need to create class variables for them (the base class marks them private), like so:

    public class MyPictureService : PictureService
    {
        protected readonly ILoggerService Logger;

        // constructor
        public MyPictureService(
            IRepository<Picture> pictureRepository, 
            IRepository<ProductPicture> productPictureRepository,
            ISettingService settingService, 
            IWebHelper webHelper, 
            ILogger logger, 
            IEventPublisher eventPublisher, 
            MediaSettings mediaSettings,
            : base(pictureRepository, productPictureRepository, settingService, 
                webHelper, logger, eventPublisher, mediaSettings)
        {
            Logger = logger;
        }

        // my overrides
    }

And if I want to add a service or repo that’s not there already? Just add it:

    public class MyPictureService : PictureService
    {
        protected readonly ILoggerService Logger;
        protected readonly ISpecificationAttributeService SpecificationAttributeService;

        // constructor
        public MyPictureService(
            IRepository<Picture> pictureRepository, 
            IRepository<ProductPicture> productPictureRepository,
            ISettingService settingService, 
            IWebHelper webHelper, 
            ILogger logger, 
            IEventPublisher eventPublisher, 
            MediaSettings mediaSettings,
            // just add it to the constructor!
            ISpecificationAttributeService specificationAttributeService)
            : base(pictureRepository, productPictureRepository, settingService, 
                webHelper, logger, eventPublisher, mediaSettings)
        {
            Logger = logger;

            // and use it
            SpecificationAttributeService = specificationAttributeService;
        }

        // my overrides
    }

Have fun!

Friday, June 14, 2013

Chef: Cannot find a resource for powershell on windows version 6.1.7600 (solved)

While working on some deployment stuff I ran into this error:

Cannot find a resource for powershell on windows version 6.1.7600

The solution turned out to be pretty obvious, and easy: include the powershell cookbook. For some reason I assumed it came in as part of the windows cookbook, but no. So just include it in your metadata.rb file like so:

name             '...'
maintainer       '...'
maintainer_email '...'
description      '...'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version          '0.1.0'
depends          'windows'
depends          'powershell'

Of course, if you’re using Chef Solo, you’ll want to download your dependent cookbooks, too.

Monday, March 11, 2013

My Top 9 Favorite Podcasts (Updated March 2013)

Although I sample a ridiculously large number of podcasts, these are the select few that I subscribe to and listen to regularly. I’ve presented them in order of “fewest unplayed episodes remaining” (which suggests how much I like each one). Again, simply being on this list means I’m a regular listener—don’t avoid the ones at the bottom—they are excellent.

I was going to write a lengthy description/review for each but, let’s be honest, you weren’t going to read it. Just go listen to some of these!

Thursday, February 21, 2013

How do I add a MongoDB replicaset/node with auth/credentials to MMS monitor on 10gen?

Want to get in on that sweet, sweet MMS MongoDB monitoring action?

image[20]

Of course you do! But are your mongo instances setup to require authentication? Bummer—the instructions won’t work for you out of the box. Here’s what you’re missing.

  1. Go ahead and setup the agent as instructed
  2. Go into the MMS management site
  3. Add your host manually

    image_thumb[8]
  4. Enter the hostname, db username, and db password. I suggest creating a new user for this (it must be a user on the admin database!)

    image_thumb[9]

Now frantically check the hosts, agents, and pings tabs for updates. It might take a few minutes. Check the Agent Log tab for errors :).

Once you get the one host working, it will add the other hosts in the replicaset, too. You can then install the agent on those machines and add a password with the pencil icon to each new row directly.

How do I add another group to my MongoDB MMS monitor on 10gen?

10gen has some seriously awesome (free) monitoring software: MMS.

image

You’re supposed to add your machines to a group, and use a different group for each logical grouping of machines. For instance, one group for one production cluster and another group for a different cluster. But how do you add a new group!?

I admit the obvious answer eluded me. You do it from the Admins page. Once there you’ll see a little “Add New Group” link:

image

Once you do that you can switch among your groups with the select list in the upper-left corner.

Tuesday, February 19, 2013

Building Dual-Screen Wallpapers for Windows 7 with PowerShell

If you have two monitors you have probably noticed that you can’t easily put a different wallpaper on each screen in Windows 7 (this works out of the box in Windows 8). This is annoying. Sure you can fix this with third party tools, but I want to go a different route.

The key to getting this to work is to recognize that if you choose a background image that is exactly the same size of your combined monitor real estate, it will automatically be stretched between the two screens. For instance, I have two screens, each running at 1280x1024. If I pick a wallpaper that is twice that width, 2560x1024, Windows will do what I want.

Of course the problem then is finding wallpapers that fit that size. Google can help with discovering sites that tailor specifically to this problem.

But what if you already have single-monitor wallpapers that you want to use? All you really need to do is combine them into a single image.



I did this recently to an entire collection of Calvin and Hobbes wallpapers. I wanted to show a different comic on each screen, but not always the same two at a time—I wanted to mimic the Windows 8 strategy of seemingly random wallpapers on random screens.

I combined two images manually, side by side at 2560x1024 once to confirm it would work, and then wrote a few lines of PowerShell to generate every possible combination with the help of the amazing ImageMagick library (specifically the montage command). The idea being that if I just pre-generate all the options, they will appear to be randomly assembled as they cycle through.

Run this script in the dirctory containing all your single wallpapers and it will combine them into the dual-screen variety.

Obviously this is crude--add your own flow control, resume support for large batches, etc....

$imgs = Get-ChildItem * -include *.jpg,*.png -exclude dual*

for ($i=0; $i -lt $imgs.Length; $i++){ 
    for ($j=0; $j -lt $imgs.Length; $j++){ 
        if($i -eq $j){ continue; }
        $left = $imgs[$i].Name
        $right = $imgs[$j].Name
        $extension = "jpg"

        $destination = [string]::Format("dual/dual-{0}-{1}.{2}", $i, $j, $extension)

        if(Test-Path $destination){
            echo "Skipping $destination"
        }
        else{
            echo "Running: montage -geometry 1280x1024 $left $right $destination"
            montage -geometry 1280x1024 $left $right $destination
        }
    }
}

Change that -geometry part to fit your system, obviously, and yeah... just loop over everything :).

Setting up a Samsung Wireless Laser Printer with a Phone or Tablet (i.e. Without a Computer)

We have finally ditched our money pit inkjet printer in favor of a slightly more economical laser printer. We opted for a very cheap, but capable Samsung ML-2165W. It's monochrome, fast, and supports wireless connectivity.

If you want builtin duplex or color be prepared to drop some extra coin.

Our goal was cheap, fast printing and laser printers seem to do the trick there with a much lower $/page cost than inkjet.

But enough about that. How do you get the damn thing setup? The instructions tell you to pop the CD into your computer and go to town. What if you don't have a CD-ROM drive? Or a computer? Luckily you can do it with just a web browser, though it's admittedly not the simplest process. Here's how I did it with my iPhone/iPad:

First, setup the printer and turn it on. Don't hook up the USB cable to anything.

Next, go get the Samsung print app for your phone or tablet. Do this before the next step because you need to be online to do it.

The printer will broadcast a WiFi network for ad-hoc printing. Connect to that with your phone or tablet--it'll be something like "PortThru". Just look for an open network with a good signal that looks like it was named by a corporation.

Once you're connected to the printer open the print app. We don't actually want to print anything yet (though you could!). Go through the motions like you are going to print something though, because we want to know the printer's IP address. This will show up when you go to select the printer.

Note the IP address and head over to your web browser. Enter the IP address in the address bar to get to the printer's management console.

Login with user "admin", password "sec00000". If that doesn't work, do a Google search for "default password <your printer model>" and try that.

Once you're logged in, go to the settings page and do the wireless setup. This will let you choose your WiFi network from a list. When complete, your printer (or at least its network) will reboot. Give it a minute.

Your phone or tablet will probably automatically reconnect to your usual WiFi network.

Launch that printer app again and go to print something else. This should allow you to choose your printer again, but this time with a normal network address (10.0.*.*, 192.168.*.*, etc.). This means that the printer is now on the network and available to everyone. Good job!

Now go kill some trees!



Automatically Connect to the Replica Set Primary with Mongo Shell

So you got your fancy MongoDB Replica Set running, did you, Mr. Fancypants? Before too long you might run into an issue: how do you connect to the primary node when the primary can change?

Luckily our applications don’t have this problem because their drivers are smart and automatically connect to the primary. The Mongo shell doesn’t do that, though.

Here’s a library to help with that. Throw this into a file called replicaSetConnector.js:

var ReplicaSetConnector = (function() { 
    var RSC = function(options) {
    
        // private method for handling the dirty work of connecting 
        // and authenticating to mongo
        var connectAndAuth = function(host) {
            if(options.debug){
                print("Connecting to " 
                    + host + "/" + options.database 
                    + " as " + options.username + ":" + options.password);
            }
            
            var connection = new Mongo(host);
            var database = connection.getDB(options.database);
            database.auth(options.username, options.password);    
            
            return database;    
        };
        
        this.connect = function() {
            // db needs to be a global variable for subsequent shell commands to work :)
            // connect to the given host, which could be any node of the replica set
            db = connectAndAuth(options.initialHost);
            
            // load some basic replica set information, which will tell us 
            // if we're on the primary, and if not where it is
            var rsInfo = db.isMaster();

            // if we're already on the primary, we're done. Otherwise, change our
            // connection to the primary
            if(!rsInfo.ismaster){
                if(options.debug) print("You're not on master...");
                db = connectAndAuth(rsInfo.primary);
                if(options.debug) print("...or are you ;)");
            }
        };
    };
        
    return RSC;
})();

That’s a library we’ll reuse bunches of times. Now make another file for your environment, e.g. prod.js with this in it:

(new ReplicaSetConnector({ 
    initialHost: 'one-of-your-replica-set-nodes:27017', 
    database: "database-name", 
    username: "user", 
    password: "secret...",
    debug: true})).connect();

And we’re finally read to connect with a mongo shell like so:

%> mongo --shell --nodb replicaSetConnector.js prod.js
MongoDB shell version: 2.2.2
type "help" for help
loading file: replicaSetConnector.js
loading file: prod.js
Connecting to one-of-your-replica-set-nodes:27017/database-name as user:secret...
You're not on master...
Connecting to another-one-of-your-replica-set-nodes:27017/database-name as user:secret
...or are you ;)
>

Yes, now you are on master and the db object is set for you to begin executing commands. If you want to be extra terse on the command line, you can alias that command to something shorter, add the library to your mongorc file, or make Windows shortcut.

Tuesday, February 5, 2013

Chef on Windows Error: tar: Cannot fork: Function not implemented (Solved)

I’m diving into the fun world of Chef. But I’m doing it on Windows, which has been…not smooth. Here’s my latest error:

C:\Users\mharen\Code\chef-repo>knife cookbook site install getting-started
Installing getting-started to C:/Users/mharen/Code/chef-repo/cookbooks
Checking out the master branch.
Pristine copy branch (chef-vendor-getting-started) exists, switching to it.
Downloading getting-started from the cookbooks site at version 0.4.0 to C:/Users/mharen/Code/chef-repo/cookbooks/getting-started.tar.gz
Cookbook saved: C:/Users/mharen/Code/chef-repo/cookbooks/getting-started.tar.gz
Removing pre-existing version.
Uncompressing getting-started version 0.4.0.
ERROR: Mixlib::ShellOut::ShellCommandFailed: Expected process to exit with [0], but received '2'
---- Begin output of tar zxvf C:/Users/mharen/Code/chef-repo/cookbooks/getting-started.tar.gz ----
STDOUT:
STDERR: tar: Cannot fork: Function not implemented
tar: Error is not recoverable: exiting now
---- End output of tar zxvf C:/Users/mharen/Code/chef-repo/cookbooks/getting-started.tar.gz ----
Ran tar zxvf C:/Users/mharen/Code/chef-repo/cookbooks/getting-started.tar.gz returned 2

Lame. After wasting an embarrassingly large amount of time on this issue, I figured it out: I had multiple installs of the `tar` command:

C:\Users\mharen\Code\chef-repo>which -a tar
C:\Program Files (x86)\Gow\bin\tar.EXE
C:\chef\bin\tar.EXE
c:\Git\bin\tar.EXE

And apparently some of them suck. Hard. Since I’m playing with Chef, let’s just use that one—it probably works. This was as easy as updating my PATH variable to place `c:\chef\bin` at the beginning instead of the end (really just before the others found by `which`):

image

Once you fix the path, close and reopen your cmd window and try the command again:

C:\Users\mharen\Code\chef-repo>which -a tar
C:\chef\bin\tar.EXE
c:\Git\bin\tar.EXE
C:\Program Files (x86)\Gow\bin\tar.EXE

C:\Users\mharen\Code\chef-repo>knife cookbook site install getting-started
Installing getting-started to C:/Users/mharen/Code/chef-repo/cookbooks
Checking out the master branch.
Pristine copy branch (chef-vendor-getting-started) exists, switching to it.
Downloading getting-started from the cookbooks site at version 0.4.0 to C:/Users/mharen/Code/chef-repo/cookbooks/getting-started.tar.gz
Cookbook saved: C:/Users/mharen/Code/chef-repo/cookbooks/getting-started.tar.gz
Removing pre-existing version.
Uncompressing getting-started version 0.4.0.
removing downloaded tarball
1 files updated, committing changes
Creating tag cookbook-site-imported-getting-started-0.4.0
Checking out the master branch.
Updating b5a1d0d..42a8168
Fast-forward
 cookbooks/getting-started/README.rdoc              |  4 +++
 cookbooks/getting-started/attributes/default.rb    |  1 +
 cookbooks/getting-started/metadata.json            | 29 ++++++++++++++++++++++
 cookbooks/getting-started/metadata.rb              |  6 +++++
 cookbooks/getting-started/recipes/default.rb       | 23 +++++++++++++++++
 .../templates/default/chef-getting-started.txt.erb |  5 ++++
 6 files changed, 68 insertions(+)
 create mode 100644 cookbooks/getting-started/README.rdoc
 create mode 100644 cookbooks/getting-started/attributes/default.rb
 create mode 100644 cookbooks/getting-started/metadata.json
 create mode 100644 cookbooks/getting-started/metadata.rb
 create mode 100644 cookbooks/getting-started/recipes/default.rb
 create mode 100644 cookbooks/getting-started/templates/default/chef-getting-started.txt.erb
Cookbook getting-started version 0.4.0 successfully installed

C:\Users\mharen\Code\chef-repo>

All fixed :)