Wednesday, May 29, 2013

Puppet: service checking on OS X without using the inbuilt service stanza

Normal service enforcement is easy with puppet, it looks like this for an OS X launchdaemon:
class something::myservice {

  File { owner => 'root', group => 'wheel', mode => '0644' }
  
  file { 'myservice_launchd_plist':
    ensure => file,
    path   => '/Library/LaunchDaemons/com.blah.myservice.plist',
    source => 'puppet:///modules/something/myservice/myservice_launchd.plist',
  }
   
  service { 'com.blah.myservice':
    ensure  => 'running',
    enable  => true,
    require => File['myservice_launchd_plist'],
  }
   
}
Which is great, until you don't want to manage that plist inside of puppet. In my case it was getting installed separately. If all the clients don't get upgraded properly then laying down the new plist with puppet (which points to different paths per version) will break old versions. It also means the plist needs to be updated in two places: inside puppet and inside the package for each release. So it's a hassle. I just want a simple check to restart it if it isn't running.

First attempt:
class something::myservice {

  File { owner => 'root', group => 'wheel', mode => '0644' }
  
  file { 'myservice_launchd_plist':
    path   => '/Library/LaunchDaemons/com.blah.myservice.plist',
  }
   
  service { 'com.blah.myservice':
    ensure  => 'running',
    enable  => true,
    require => File['myservice_launchd_plist'],
  }
   
}
This works fine, until the plist isn't there: i.e. the install failed for some reason, or this machine didn't get myservice installed. In that situation puppet will exit with an error code, so puppet management is effectively broken. No good. So, can we replicate what the service stanza is doing with a couple of simple exec statements? Seems easy...
class something::myservice {

  File { owner => 'root', group => 'wheel', mode => '0644' }

  exec { 'myservice':
    onlyif  => ['! /bin/launchctl list com.blah.myservice &> /dev/null',
                '/bin/test -f /Library/LaunchDaemons/com.blah.myservice.plist'],
    command => '/bin/launchctl load /Library/LaunchDaemons/com.blah.myservice.plist',
  }

}
This will run launchctl load if the service isn't running and the plist is actually there. The problem is puppet is overzealous in its command checking and will fail with this error:
Could not evaluate: Could not find command '!'
OK, what if we do onlyif and unless. Documentation is silent on what happens if you do this. It does appear to work:
class something::myservice {

  File { owner => 'root', group => 'wheel', mode => '0644' }

  exec { 'myservice':
    onlyif  => '/bin/test -f /Library/LaunchDaemons/com.blah.myservice.plist'
    command => '/bin/launchctl load /Library/LaunchDaemons/com.blah.myservice.plist',
    unless  => '/bin/launchctl list com.blah.myservice &> /dev/null',
  }

}
But both conditions always need to be evaluated, and I'm not sure this is actually going to work in the future. In my testing 'onlyif' was run before 'unless' but I wouldn't rely on that either. So lets just work around the broken commandline checking by adding a NOP with true &&:
class something::myservice {

  File { owner => 'root', group => 'wheel', mode => '0644' }

  exec { 'myservice':
    path    => ["/bin", "/usr/bin"],
    onlyif  => ['true && ! /bin/launchctl list com.blah.myservice &> /dev/null',
                '/bin/test -f /Library/LaunchDaemons/com.blah.myservice.plist'],
    command => '/bin/launchctl load /Library/LaunchDaemons/com.blah.myservice.plist',
  }

}
The path is necessary since the commandline checking wants the first part of the command to be an absolute path, or the path specified in 'path'. Since true is an inbuilt part of bash we need to specify path.

Friday, May 17, 2013

pdb and gdb conditional breakpoints

Use a debugger enough and you'll eventually be in a loop looking for a certain condition. And it's too tedious to walk through all iterations of the loop to get to the one you want. Enter conditional breakpoints. In GDB you'd use something like this (borrowed from here):
(gdb) br test.cpp:2
Breakpoint 1 at 0x1234: file test.cpp, line 2.
(gdb) cond 1 i==2147483648
(gdb) run
Or this for strings:
break test2.cpp:10 if strcmp(y,"hello") == 0
In the python debugger the syntax is a little different:
b(reak) ([file:]lineno | function) [, condition]
Like this:
(Pdb) break blah.py:1, somevalue=2

Tuesday, May 14, 2013

python set a variable conditional on another variable

Tiny python snippet. If you're doing this:
if value:
  output = value
else:
  output = 'somethingelse'
Do this instead:
output = value or 'somethingelse'

Monday, May 13, 2013

Basic python chmod for windows

A basic python version of chmod for Windows using pywin32:

import ntsecuritycon
import win32security

DACL_PRESENT = 1
DACL_DEFAULT = 0

def WinChmod(filename, acl_list, user="SYSTEM"):
  """Provide chmod-like functionality for windows.

  Doco links:
    goo.gl/n7YR1
    goo.gl/rDv81
    goo.gl/hDobb

  Args:
    filename: target filename for acl
    acl_list: list of ntsecuritycon acl strings to be applied with bitwise OR.
              e.g. ["FILE_GENERIC_READ", "FILE_GENERIC_WRITE"]
    user: username string
  Raises:
    AttributeError: if a bad permission is passed
    RuntimeError: if filename doesn't exist
  """
  if not os.path.exists(filename):
    raise RuntimeError("filename %s does not exist" % filename)

  acl_bitmask = int()
  for acl in acl_list:
    acl_bitmask |= getattr(ntsecuritycon, acl)

  dacl = win32security.ACL()
  win_user, _, _ = win32security.LookupAccountName("", user)

  dacl.AddAccessAllowedAce(win32security.ACL_REVISION,
                           acl_bitmask, win_user)

  security_descriptor = win32security.GetFileSecurity(
      filename, win32security.DACL_SECURITY_INFORMATION)

  # Tell windows to set the acl and mark it as explicitly set
  security_descriptor.SetSecurityDescriptorDacl(DACL_PRESENT, dacl,
                                                DACL_DEFAULT)
  win32security.SetFileSecurity(filename,
                                win32security.DACL_SECURITY_INFORMATION,
                                security_descriptor)

Tuesday, May 7, 2013

Python: stub out windows libraries for testing on linux

Here's a quick way to stub out windows libraries when you are testing on Linux. This code creates empty modules using imp.
  import imp
  import sys

  def testMethodWithWindowsCall():
    StubOutWindowsLibs()
    import windows_utils
  
  def StubOutWindowsLibs():
    pywintypes = imp.new_module("pywintypes")
    pywintypes.error = Exception
    sys.modules["pywintypes"] = pywintypes

Monday, May 6, 2013

Google coding style guides

I didn't actually realise how important code style guides were until I saw one consistently applied with style-checking tools: it makes a big difference to code quality. If you don't have a style guide, you could do a lot worse than the google style guides, which have been open-sourced and are regularly updated as languages evolve. The Google python guide is here, which is very similar to Guido's python style guide.

Chrome browser commandline switches

Chrome has a prodigious number of command line switches to control all sorts of behaviour. I recently ran into this one, which I think will be handy. From the review log:
Added the flag --host-resolver-rules=XXX in r39342.

Where XXX is a comma separated list of rules that control how hostnames are resolved.

For example:
    "MAP * 127.0.0.1" --> Forces all hostnames to be resolved to 127.0.0.1
    "MAP *.google.com proxy" --> Forces all google.com subdomains to be
                                 resolved to "proxy".
    "MAP test.com [::1]:77 --> Forces "test.com" to resolve to IPv6 loopback.
                               Will also force the port of the resulting
                               socket address to be 77.
    "MAP * baz, EXCLUDE www.google.com" --> Remaps everything to "baz",
                                            except for "www.google.com".