Quickly Sync JPG mtime with EXIF CreateDate

I borrowed a few things, made a few changes, and now all my photos have the correct timestamp on the filesystem. Hooray!

The script expects to be in the root directory of the album hierarchy, but that is merely because I am lazy. And it was an excuse to learn about how to find where a bash script lives. Also, a refresher handling spaces in paths in bash using read.

Finally, exiftool is awesome! (perl-Image-ExifTool on Fedora 16.)

#!/bin/bash
 
#
# Script to sync the filesystem date with that from EXIF CreateDate field.
#
 
set -e
 
# http://hintsforums.macworld.com/showpost.php?p=523850&postcount=20
self="$(cd "${0%/*}" 2>/dev/null; echo "$PWD"/"${0##*/}")"
 
# http://www.perlmonks.org/?node_id=767176
find $(dirname $self) -name '*.jpg' -print0 | while read -d $'\0' f
do
  exiftool -S -d "%Y%m%d%H%M.%S" -CreateDate "${f}" \
  | awk '{ print $2 }' \
  | xargs -I % touch -m -t % "${f}"
done

Opscode Chef Xtra: Obtaining network interface data

It is somewhat of a challenge to obtain interface information out of the data Ohai makes available for network interfaces. For information only about inet addresses, the following works:

node_addresses = {}
node[:network][:interfaces].each do |iface, vals|
  vals[:addresses].each do |addr, h|
    next unless h['family'] == 'inet' && !addr.match('127.0.0.1')
    iface_data = Hash.new
    iface_data = h.dup
    iface_data['address'] = addr
    node_addresses[iface] = iface_data
  end
end

Getting a little crazy with FileEdit

In case there is any doubt, you can go nuts with Chef::Util::FileEdit. If one is using search_file_replace, internally it is simply:

new_contents << ((method == 1) ? replace : line.gsub!(exp, replace))

Meaning if need be, I can do something silly:

ruby_block 'fix remi.repo' do
  action :nothing
  block do
    f = Chef::Util::FileEdit.new('/etc/yum.repos.d/remi.repo')
    f.search_file_replace(/\$releasever/, '%i' % major)
    f.search_file_replace(/(\[remi\].*?)enabled=0(.*?\[)/m, '\1enabled=1\2')
    f.write_file
  end
end

Lovely! The above is needed as I only want to enable [remi], but not [remi-test] which resides in the same file. (Of course I could just ship my own .repo file, too. Choices, choices.)

Opscode Chef Xtra: A Deletable Template via a Definition

While there is no delete action recognized by the Chef template resource, it is possible to fake it using a definition. For example, a definition for managing a configuration file for the multifaceted DNS server dnsmasq might look like the following:

define :dnsmasq_conf, :enable => true, :template => 'dnsmasq.conf.erb' do
	include_recipe 'dnsmasq'
 
	conffile = "/etc/dnsmasq.d/#{params[:name]}.conf"
 
	if params[:enable]
		template conffile do
			source params[:template]
			owner 'root'
			group 'root'
			mode 00644
			backup false
			notifies :restart, 'service[dnsmasq]', :delayed
		end
	end
 
	unless params[:enable]
		file conffile do
			action :delete
			notifies :restart, 'service[dnsmasq]', :delayed
			only_if {::File.exists?(conffile)}
		end
	end
end

The above definition follows the usual pattern of either being enabled or disabled. The former uses the expected template resource. The latter leans on the file resource to actually handle deletion of the template, taking care to do so only if the file actually exists first. Definitions allow one to combine resources in all kinds of interesting ways.

Opscode Chef Xtra: Achieving Idempotence in execute Resource

The certainty of outcome offered by other Chef resources is notably lacking from the execute Resource, for Chef has no way of knowing the consequences of the provided shell script fragment. Fortunately, it’s possible to ensure idempotent behavior with the appropriate application of care. As an example, perhaps one needs to load several database dump files for an unenlightened Web based application that has not adopted a migrations based strategy.

How to ensure the dump files are loaded in the correct order, but never more than once, while avoiding duplicate work should a dump file fail to import? One can leverage lexically sorted filenames coupled with lock files in such a scenario as demonstrated below.

db_files = ['func.sql', 'schema.sql', 'data.sql.bz2']
 
db_files_with_idx = db_files.inject({}) do |h, f|
	h[f] = "#{h.keys.length.to_s.rjust(2, '0')}_#{f}"
	h
end
 
db_files_with_idx.each do |name, name_with_idx|
	db_file = "/root/db_files/#{name_with_idx}"
	remote_file db_file do
		action :create_if_missing
		source "http://example.com/#{name}"
	end
end

For simplicity, the database files are defined directly in the recipe, but could be factored out in an attribute. A hash is then created — a candidate for refactoring into a library later — that creates filenames for local storage. Afterward, each file is downloaded using the remote_file Resource.

The output would thus be the following:

irb(main):009:0> pp db_files_with_idx
{"data.sql.bz2"=>"02_data.sql.bz2",
 "schema.sql"=>"01_schema.sql",
 "func.sql"=>"00_func.sql"}

Next, the execute Resource is called upon, but as it is not idempotent on its own, the behavior must be supplied:

execute "load dump" do
	action :run
	cwd '/root'
	command <<-EOT
	# script from below
	EOT
	not_if {::File.exists?('/root/db_files/.finished')}
end

A hint of that exists in the not_if block, which checks for a the existence of a lock file signaling successful completion of the resource. However, more is required. In particular, a mechanism is necessary to handle a failure in the middle of an import. (MySQL is the database in question, in this example.)

for f in $(ls db_files | sort) ; do
	ext=$(echo $f | awk -F. '{print $NF}')
	lck="/root/db_files/.seen_${f}"
	# Skip successfully imported dump
	test -f $lck && continue
		case "${ext}" in
		bz2)
			cmd=bzcat
		;;
		*)
			cmd=cat
	esac
	echo "Loading database dump file: ${f}"
	${cmd} /root/db_files/${f} | /usr/bin/mysql -u root my_db
	ret=$?
	if [ $ret -ne 0 ] ; then
		exit $ret
	else
		touch $lck
	fi
done
touch /root/db_files/.finished

First, the filename names of the database dumps are sorted to match the order defined earlier in the recipe and committed to disk by the remote_file Resource. To add some flexibility, the extension is lopped off using awk, allowing for bzip2 compressed dumps.

Next, a lock file unique to each database dump is tested for existence. If the lock file exists, the dump has been successfully imported and is skipped; as a result, an interruption of the chef-client run by failure or user action will not prevent the recipe from picking up exactly where it left off. Only upon successful importation of the data, as signaled by a return value of 0 from mysql, is a lock file written. Otherwise, the script exits with the non-zero error code, causing the execute Resource to raise an exception.

When success is total, the final lock file referenced in the earlier not_if block is created. Thereafter, the resource shall never run again, unless the lock file is disturbed.

The usage of not_if and only_if in Chef resource definitions along with careful sorting and locking inside the execute Resource brings the loving embrace of idempotent behavior to shell script fragments. Of course, the above could be rewritten entirely in Ruby and run from within a ruby_block Resource, but the same concepts apply and as such is left as an exercise for the reader.

Made the switch to Fedora 15 from Kubuntu 10.10

Finally happened. I made the switch to Fedora. I’d been a Kubuntu user since 2006, but since the switch to Pulse Audio I have had serious problems with sound under Kubuntu in 10.10, 11.04, and 11.10 beta. I have no such trouble with Fedora 15.

I’ll likely be moving all systems I manage over from Debian GNU/Linux to Fedora or CentOS in the coming months. (I realize Ubuntu isn’t exactly Debian. I only ran the former on my laptop, not servers, but I prefer to standardize on a single distribution and it looks like Red Hat derived distributions is where it’s at for me.)

Chase Ink Business Card Rewards goes to Crap

For years, holders of Chase’s Business Card earned 3% cash back on meals, gas, and office supplies. No more, according to junk mail just received offering the new and improved Ink card! New card holders — and I’m sure existing holders eventually — are being subject to a new, crap reward program: Worthless rotational joke rewards. Said program is not news to holders of Chase’s new Freedom card, which also sucks the same way. At least Ink is still 2% cash back on gas and dining.

If things continue this way, I might actually find myself back to using my Discover card. I dropped it back in 2008 when Discover decided to cap 5% rewards on gas and ceased offering cash back in $20 increments.

Google Docs Upload Broken in Opera 11.51

It’s been broken for at least a month, apparently. As long as it lasts, there is apparently a workaround: https://docs.google.com/?action=updoc

Update, 17 June 2012. It works in Opera 12, thankfully!

Google Calendar Agenda Native PDF Generation

I am pleased to see Google Calendar now natively generates a PDF when printing out an agenda. In the past, the print version of an agenda was a clean HTML page. Now it has more colors and clean page breaks. Hooray!

Why is handling amortization so difficult to get right?

So fail boat on recording my student loan in both Gnucash and Moneydance. Years ago, I had success using Gnucash for this, but was forced to file a bug this time. Meanwhile, Moneydance has had a different issue that causes historical loans to calculate incorrect principal and interest, dating back to 2009.

I cannot express my disappointment forcefully enough. More so as I was migrating my stuff back into Gnucash, which took a while, only to find out I have wasted my time.

Update, September 4th. Ended up using Google Docs to generate my amortization schedule and enter it manually. Ultimately discovered that it does not match the lender’s outstanding balance, so I have some further refinements to do with the handy amortization template I found for Docs.