fedwatch – running scripts based on fedmsg messages

March 21, 2014 in automation, fedmsg, fedora, projects

Fedora fedmsg infrastructure can give you a lot of interesting information nearly in real-time. For example:

  • Koji build starts/finishes/fails
  • FAS account creation and membership changes
  • Changes in Fedora packages git repositories
  • Changes of package ownership in pkgdb
  • Changes to bodhi updates

Full list of topics is of course online, including the data in those messages.

It doesn’t take a genius to realize some of these messages can be used to do interesting stuff. For example Java SIG uses buildsys.repo.done to generate minimal installation size of several packages each time rawhide buildroot is regenerated. That way we immediately see if some new dependency creeps in.

To make all of this more simple I created a separate library and utility for monitoring specified topics on fedmsg bus and then running scripts in configured directory with data from fedmsg message passed as arguments. Introducing: fedwatch.

Installation

I am planning to package fedwatch for Fedora and EPEL. In the meantime feel free to use my Copr repo.

If you have up-to-date dnf-plugins-core you can just do dnf copr enable sochotni/fedwatch fedora-<ver>-<arch>

If you are not using dnf you can download appropriate repository for your distribution manually (F20, rawhide, EPEL 6 supported) and then just run yum install fedwatch .

Usage

# fedwatch --help
usage: fedwatch [-h] [--config-file CONFIG_FILE] [--script-dir SCRIPT_DIR]
                [--debug]

Run tasks on fedmsg changes

optional arguments:
  -h, --help            show this help message and exit
  --config-file CONFIG_FILE
                        Configuration file for topic selection and data
                        mapping (default: /etc/fedwatch.conf)
  --script-dir SCRIPT_DIR
                        Directory with scripts to run for fedmsg messages
                        (default: /etc/fedwatch.d)
  --debug               Run with debug output (default: False)

Configuration file format is that of standard ini file (ConfigParser format for Pythonistas). Example:

[org.fedoraproject.prod.git.receive]
arg1=msg/commit/username
arg2=msg/commit/repo
arg3=msg/commit/branch
arg4=msg/commit/rev
arg5=msg/commit/summary

When fedwatch receives a git.receive message it will convert fedmsg message data into arguments for running scripts based on above configuration. Keys in the configuration file are ignored and values are evaluated as XPath expressions inside the message data. So msg/commit/username means message[‘msg’][‘commit’][‘username’].

Taking an example fedmsg commit message from fedmsg documentation:

{ 'i': 1,
  'msg': { 'commit': { 'branch': 'master',
                       'email': 'mjw@redhat.com',
                       'message': 'Clear CFLAGS CXXFLAGS LDFLAGS.\n\n-This is a bit of a hammer.',
                       'name': 'Mark Wielaard',
                       'repo': 'valgrind',
                       'rev': '7a98f80d9b61ce167e4ef8129c81ed9284ecf4e1',
                       'stats': { 'files': { 'valgrind.spec': { 'deletions': 2,
                                                                'insertions': 1,
                                                                'lines': 3}},
                                  'total': { 'deletions': 2,
                                             'files': 1,
                                             'insertions': 1,
                                             'lines': 3}},
                       'summary': 'Clear CFLAGS CXXFLAGS LDFLAGS.',
                       'username': 'mjw'}},
  'timestamp': 1344350850.886738,
  'topic': 'org.fedoraproject.prod.git.receive'}

For the above configuration file and this specific message, fedwatch would execute scripts in /etc/fedwatch.d/ with following arguments:

<script> org.fedoraproject.prod.git.receive mjw valgrind master \
       7a98f80d9b61ce167e4ef8129c81ed9284ecf4e1 'Clear CFLAGS CXXFLAGS LDFLAGS.'

And example script to handle the above message could be /etc/fedwatch.d/10-git-receive.sh:

#!/bin/sh

if [[ "$1" != "org.fedoraproject.prod.git.receive" ]];then
    exit 0
fi

username=$1
pkgname=$3
branch=$2

# do stuff

I hope the configuration file is easy to understand. Package contains systemd and SysV init integration so you can run it as a system-wide service. In that case I suggest you change default (empty) configuration in /etc/fedwatch.conf and add some handling scripts into /etc/fedwatch.d/.

And of course if you find some interesting use cases for fedwatch, let me know!

fedora-review – Package reviews made easier

November 11, 2011 in fedora, json, packaging, projects, python, review, tool

For package to be included in default Fedora repositories, it has to go through process called package review. If you’ve done a few package reviews you know big chunks of this process are repeated ad-nausea in every review.
There have been quite a few tools aimed at automating and simplifying this process. However they all had one major flaw. They have been designed for reviewing specific class of packages, be it Perl, Python or generic C/C++ packages. Few us us decided to change this.
We used Tim Lauridsen’s FedoraReview package as a base for our work and started adding new features and tweaks. Current work has a website on fedorahosted where you’ll find all important information. Full feature list would be quite long, but I’ll list a few major things:
  • Bugzilla integration
  • Mock integration
  • JSON api for external plugins (more info further down)
  • Several automated tests
The tool runs all checks/tests on spec file and rpms and writes output into a text file. Snippet of the output looks like this:

Package Review
==============

Key:
- = N/A
x = Check
! = Problem
? = Not evaluated



==== Generic ====
...
[ ]: MUST License field in the package spec file matches the actual license.
[ ]: MUST License file installed when any subpackage combination is installed.
[!]: MUST Package consistently uses macros (instead of hard-coded directory names).
Using both %{buildroot} and $RPM_BUILD_ROOT
...
[x]: SHOULD Spec use %global instead of %define.
...

==== Java ====
[!]: MUST If package uses "-Dmaven.local.depmap" explain why it was needed in a comment
[!]: MUST If package uses "-Dmaven.test.skip=true" explain why it was needed in a comment

Issues:
[!]: MUST Package consistently uses macros (instead of hard-coded directory names).
Using both %{buildroot} and $RPM_BUILD_ROOT
[!]: MUST If package uses "-Dmaven.local.depmap" explain why it was needed in a comment
[!]: MUST If package uses "-Dmaven.test.skip=true" explain why it was needed in a comment
We display only relevant results. In other words if there are no post/postun scriptlets there is no reason to include sanity output checking in the template. This will make more and more sense as we add more checks.

JSON API

So how is it that different people will be able to write additional plugins for this review tool? We provide a relatively simple JSON api through stdin/stdout.
So to create a new check plugin you create a script or program in your language of choice. There is only one requirement:
  • Programming language has to have JSON format support

When the review tool runs your plugin it will send following message on its stdin:


{
"supported_api": 1,
"pkgname": "package name",
"version": "package version",
"release": "package release",
"srpm":"path/to/srpm",
"spec":{ path: "path/to/spec",
"text": "spec text with expanded macros"},
"rpms":[ "path/to/rpm", ...],
"rpmlint": "rpmlint output",
"build_dir": "/path/to/src/directory/after/build"
}

When your plugin is done with checks it returns following message by writing it to stdout:


{
"command": "results",
"supported_api": 1,
"checks":
[
{
"name": "CheckName",
"url": "URL to guidelines usually",
"group": "Group for this test.(Java, Perl, etc.)",
"text": "Check description that shows on review template",
"deprecates":["DeprecatedTest", ...]
"type": "MUST"|"SHOULD",
"result": "pass"|"fail"|"inconclusive",
"extra_output": "text",
},
...
]
}

If the plugin closes stdout without writing anything there, it means there were no relevant automated tests to run and no non-automated tests to include in template for manual evaluation. This is useful so we don’t include for example Perl-related test output for Java packages and vice-versa.

Roadmap

While the tool is already usable and soon to be packaged in Fedora, there are still quite a few things we want to improve:

  • Add more functions to API (currently there is just get_section)
  • Automate all automatable tests currently available
  • Get rid of redundant tests (don’t duplicate rpmlint)
  • Add more tests of course!
  • Maybe add templating support?

We have currently 3-4 active committers, checks for C/C++, generic, Java, R packages. There is already and example external plugin written in Perl. If you have any improvement ideas, bugreports or just want to tell us we suck because we should have done X…get in touch!

Accessing WebDAV calendar from commandline

March 11, 2011 in open source, programming, projects, python, software

Have you tried to access Zimbra or Google Calendar from command line? I have. And I couldn’t find any normal command line client that would be able to read and write these calendars, display alerts etc. Well there is a googlecl project, but it’s specific for Google Calendar and is not using standard WebDav iCal access methods.

Thus I set out to create console application that would fulfil my needs. What are my requirements?:

  • Read/write access to Google Calendar and Zimbra (at least)
  • Multiple remote calendars
  • Working alerts
  • Nice ncurses UI (but also ability to just display some info and quit)
  • Correct handling of timezones
  • Integration with mail client (open ics files received by email)
  • I guess a lot more :-)

I had a look at existing python modules that work on iCalendar, WebDAV and combination of both. There are quite a few of them, but I just didn’t like their APIs. They were usually complex and required knowledge of iCal specification. So I decided to create simplified module that would be easy to understand (even if not so powerful).

I named the project pywebcal (yes, unimaginative) and it’s now on github. I would LOVE some input. I know it’s far from perfect (or complete), but let’s see. For now it offers read-only support for Zimbra (Google should work too but I haven’t tested in a while).

You can have a look at the example directory that contains one simple example you can run in-place and see if it works :-) I did my best to create proper test cases covering problems with timezones and whatnot, and this helped me quite a lot with recent refactoring. I am now using vobject library as my backend and it is rather nice to use. Plan is to allow access to vobject components so that my simplified API is not preventing some advanced modifications.

Next step is obviously to start working on ncurses application itself. Anyone wants to help?

PyQTrailer revisited

November 10, 2010 in en, fedora, open source, projects, pyqt, python, software

Some time ago, I wrote about my little project: apple.com trailer downloader. Apple is still not very open-source friendly as far as its trailer website is concerned. So all points I made in my original post still stand. To my surprise this little project is still alive and kicking, with new ideas for improvements coming and coming :-). What is even more important: it seems that so far no breakage happened due to apple changing something on the web.

Since the first version I released almost 6 months ago several new features appeared. Some of them include:

  • Parallel downloading of trailers
  • Ability to run movie player (mplayer, vlc, etc) without downloading file to HDD
  • Lot of customisation/performance options added
  • Working support for trailer search
  • Localisation support
  • Python 3 support

Latest version (0.5.2) is available in Gentoo repositories already, and should hit Fedora updates in next day or so (this will be delayed due to new package acceptance criteria though). Enjoy.

Uploading original photos from Digikam to Flickr

August 28, 2010 in en, kde, open source, photography, programming, projects, qt, software

I have been using Digikam for managing my photos for some time. It’s pretty neat software and development is progressing quite fast. You can think of it as Lightroom, just without the neat non-destructive editing. But that is coming too, thanks to Google and another round of Summer of Code participants. But I wouldn’t be writing this blog post if Digikam was flawless would I? :-)

First I’ll describe my work-flow in few short bullet-points :-)

  • Shoot a LOT of photos
  • Delete almost as many photos
  • Geotag, tag/keyword and rate remaining photos
  • If it was party or something similar upload to Facebook right away
  • Pick few photos and improve them a bit with Gimp (nothing fancy, just crop/levels)
  • Upload all photos to Flickr as a backup/sharing place

Digikam enables me to work like this, except the last point. Why? Because apparently Digikam developers don’t think anyone would update their photos to Flickr without first resizing/recompressing them. This  how flickr export dialog looks like this:

See the problem? Even if I don’t chose “Resize photos before uploading” Digikam will still re-compress them which is a Really-Bad-Thing(tm) to do with jpeg files. I had some previous experience with Qt3 and even Qt4. so I though it might be a good idea to look into fixing this small annoyance. I will not bore you with the details how I checked out svn repository with git and rest of the stuff. Here is the result:

If you un-check “Send original file (no resizing)” original settings will appear and you can resize/recompress as much as you want (Blasphemy! Madness!). The patch is not flawless, because it won’t prevent you from trying to upload RAW files to Flickr, but it’s good enough for me :-) It’s not even that big, stats are like this:

 flickrexport/flickrtalker.cpp |   18 ++++++++++++——
 flickrexport/flickrtalker.h   |    2 +-
 flickrexport/flickrwidget.cpp |   34 ++++++++++++++++++++++++++——–
 flickrexport/flickrwidget.h   |    5 +++++
 flickrexport/flickrwindow.cpp |    4 ++++
 flickrexport/flickrwindow.h   |    1 +
 6 files changed, 49 insertions(+), 15 deletions(-)

You can download the patch from my Dropbox for now until the bugreport I created some time ago will get sorted out (don’t hold your breath too much though). The patch applies cleanly across all versions of kipi-plugins I tried (from 0.8.something to 1.4.0). Happy uploading.

Downloading trailers on Linux – final solution

May 10, 2010 in en, linux, open source, projects, python, software

I love films. All of them to be exact. I believe you just have to be in the right mood and you would enjoy even few of the worst movies ever made. Even though I am a proponent of open source philosophy, we as a society are obviously not ready to embrace it in entertainment industry just yet.

This is where www.apple.com/trailers comes into play. Apple made great deals with movie studios and you can watch/download newest movie trailers. Well…sort of. Apple employs variety of restrictions which makes this site next to useless on a Linux desktop. It hides links to trailers themselves behind reference files so that when you download with your favorite browser, you will only get small reference file not the trailer itself. And that is after you circumvent user-agent protection. Because apple believes nothing but iTunes/iPad/iOtherAppleStuff should access these trailers. There are scripts around that can make downloading possible for Linux users. I have been using Apple Trailer Download script for Greasemonkey for quite some time, but it always stopped working after some time.

Another opportunity for me I guess. I have been trying to improve my Python-fu for some time so what better way then a small project like this? I started last weekend after I found out Apple actually publishes JSON data of trailers on its site. This made access quite easy from python and is quite error-prone to changes of website itself (as long as Apple doesn’t pull whole JSON thingy…but they are actively using it too). Long story short…there are two outputs from this endeavor:

  • pytrailer – python module to simplify access to movies on apple.com/trailers
  • pyqtrailer – Qt4 interface that displays poster, movie information and enables downloading of trailers

You can report bugs on respective websites (there are quite a few now, but basic downloading for hd trailers works). If you want to try it out just running:

# easy_install pyqtrailer
should work as long as you have PyQt4 installed. You can just run pyqtrailer now and you should see something like this:

That’s it. I will improve/fix it a bit but don’t expect too much :-)

Mobile (not so) open standards

August 25, 2009 in en, linux, lock-in, mobile, problem, projects, rant

Yesterday I promised I’ll talk about why I hate mobile phones. Of course I didn’t mean all of them. Just the ones I have to deal with. Why? Well my mobile phone kind of died few days ago. I have a Nokia N73 and it’s really quite good phone even if it’s a bit old by today’s standards. You control the phone by using “joystick” kind of thing in the upper part of keyboard. I decided to include image so you don’t have to look for it :-)

So this joystick stopped working (even slightest touch would be evaluated as pushing it, therefore it was unusable). I didn’t have my backup phone with me, but one friend gave me her battered Siemens S55. So what was the problem? Well I have the same sim card for almost 10 years now. Back then only 100 contacts would fit on it. I have almost 300 contacts in my N73. So how do I get all contacts from one phone to the other? Normally I could just send them through bluetooth, but since I couldn’t really control my N73 this was out of question. I was barely able to turn on the bluetooth. I thought that I’ll use SyncML interface to get vCards from N73 to my computer and then sync them again to the S55. In the end I kind of did, but boy was that an unpleasant experience!

So what exactly happened? I installed OpenSync libraries and tools and using multisyncgui I created sync group with one side being file-sync plugin and other was syncml-obex-client plugin. Configuraion of file-sync plugin was mostly just changing path to directory where I wanted to sync. Final version looked like this:





/tmp/sync
contact
vcard30


Configuration for syncml-obex-client appeared to be much more challenging. It appears that Nokia N73 has two quirks:

  • It only talks to SyncML client if it says its name is “PC Suite”
  • It contains a bug that causes it to freeze after certain amount of data if configuration is not correct

First of these quirks is mentioned in almost every tutorial on data synchronization in Linux. However the second one caused me to lose quite some time. My Nokia N73 would freeze after synchronizing approximately 220-240 contacts. To continue working I had to restart the whole phone.In the end I found out that I need to set parameter recvLimit to 10000 in order to synchronize everything. Final setting for syncml-obex-client looks like this:




2
00:1B:33:3A:D1:37

13
0
PC Suite
1
1


1
0
0
10000
0

Contacts
contact
vcard21


So after all that I was able to get vCards from my N73 to my notebook. For every vCard OpenSync created file in directory /tmp/sync. Now came the interesting part. How to get these vCards to Siemens S55?

Simple Google search on Siemens S55 and synchronization in Linux seemed to suggest that tool most suited to do the job was scmxx. This little app is specialized on certain Siemens phones. According to some manuals it was supposed to be able to upload vCards themselves, however I couldn’t get it to work as scmxx was complaining about invalid command line arguments.After some testing I found out that it could access and change sim card phone numbers.

Unfortunately for me, my sim card has limit of 100 phone numbers, each with 14 character identifier (name). This meant I needed to convert vCards from N73 to special format that scmxx used. Mentioned format looked something like this:


1,"09116532168","Jones Rob"
2,"09223344567","Moore John"
...

First column being number of slot that will be overwritten by new information, second column is number and third one name of contact (less than 15 characters).

So I fired up vim and started coding conversion script. It didn’t take long and I had my contact in the old-new phone. There are a lot of hard-coded things in that script since I don’t plan to ever use it again but you can download it from my dropbox. Consider it public domain, and if anyone asks I didn’t have anything to do with it :-)


import os
import re

MAX_CONTACTS=100

class PbEntry(object):

def __init__(self, name, tel, year, month, day):
self.name = name
self.tel = tel
self.year = year
self.month = month
self.day = day

def cmp_pb(e1, e2):
if e1.year > e2.year:
return -1
elif e1.year return 1
else:
if e1.month > e2.month:
return -1
elif e1.month return 1
return 0


telRe = re.compile('TEL(;TYPE=\w+)*:([*#+0-9]+)', re.M)
revRe = re.compile('REV:(\d{4})(\d{2})(\d{2}).*', re.M)
nameRe = re.compile('^N:(.*);(.*);;;', re.M)
def get_entry_from_text(text):
ret = nameRe.search(text)
surname = None
name = None
tel = None
rev = None
if ret:
surname = ret.group(1)
name = ret.group(2)

ret = telRe.search(text)
if ret:
tel = ret.group(len(ret.groups()))

if surname and name:
fn = "%s %s" % (surname,name)
elif surname:
fn = surname
else:
fn = name

if fn:
ret = re.search('(.{0,14}).*', fn)
fn = ret.group(1)


ret = revRe.search(text)
year = ret.group(1)
month = ret.group(2)
day = ret.group(3)

return PbEntry(fn, tel, year, month, day)


entries = []

files = os.listdir('/tmp/sync')
for file in files:
fh = open('/tmp/sync/%s' % file, 'r')
content = fh.read()
entry = get_entry_from_text(content)
entries.append(entry)

entries = sorted(entries, cmp=cmp_pb)

i = 1
for entry in entries:
print '%d,"%s","%s"' % (i, entry.tel, entry.name)
i = i + 1
if i > MAX_CONTACTS:
break

I had my share of incompatibilities between mobile phones, computers and other devices. Fortunately most of devices being sold today use open communication protocols for sharing of data (and other stuff). Too bad people had to put so much energy into reverse engineering proprietary solutions in the past. Just ranting about this vendor lock-in could be spread on quite a few pages. Imagine having 300+ contacts and calendar information in your phone of brand X. When you are buying your new phone, you would be able to synchronize your data only if you bought new phone also from brand X. Would that affect your decision? It sure would affect mine.

Now I have a choice. After fixing my old N73 I will start looking into new phone. So far HTC Hero looks pretty cool and reviews are not half bad.

Final thoughts on GSoC

August 24, 2009 in en, google, gsoc, linux, open source, projects, software engineering

So this year’s Google Summer of Code is officially over. Today 19:00 UTC was deadline for sending in evaluations for both mentors and students. Therefore I think some kind of summary what was happening and what I was doing is in order.

I was working on implementing neat idea that would allow previously impossible things for Gentoo users. Original name for the idea was “Tree-wide collision checking and provided files database”. You can find it on Gentoo wiki still. I later named the project collagen (as in collision generator). Of course implemented system is quite a bit different from original wiki idea. Some things were added, some were removed. If you want to relive how I worked on my project, you can read my weekly reports at gentoo-soc mailing list (I will not repeat them here). Some information was aggregated also on soc.gentooexperimental.org. As final “pencils down” date approached I created final bugreports of features not present in delivered release (and bugs there were present for that matter). Neither missing features, nor present bugs are a real show-stopper, they mostly affect performance. And more importantly I plan to continue my work on this project and perhaps other Gentoo projects. I guess some research what those projects are is in order :-)

Before GSoC I kind of had an idea how open-source projects work since I’ve participated with some to a degree. However I underestimated a lot of things, and now I would do them differently. But that’s a good thing. I kind of like the idea that no project is a failed one as long as you learn something from it. It reminds me of recent Jeff Atwood’s post about Microsoft Bob and other disasters of software engineering. To quote him:

The only truly failed project is the one where you didn’t learn anything along the way.

I believe I have learned a lot. I believe that if I started collagen now, it would be much better in the end. And the best thing is that I can still do that. I get to continue my project and learn some more. If I learned anything during my work on collagen it’s this:

If you develop something in language without strong type checking CREATE THE DAMN UNIT TESTS! It will make you life later on much easier.

In next episode: Why I think Gmail is corrupting minds of people and why I hate mobile phones

Technical decorating that makes sense

July 26, 2009 in en, gsoc, open source, projects, python

I have been using Python for several small-ish project in past year or two. In that time I have never found reason to really use decorators. You might have seen them in Java source codes in form of
@deprecated
void getVal()
{
//code
}

Python has same syntax, but because it’s scripting language certain things are different (read: more flexible :-) ).

Now to the main thing. Where did I use it? As I was deciding what ORM library/tool to use in my GSoC project, I came to conclusion that I could probably use Django DB backend to work with database. This way I would avoid doing the same thing (ORM) twice. Once for backend and once again for web interface later on. As always, things are not as straightforward as they seem in the beginning. Django is web framework and it’s tied with its database backend. In other words, the backend was not created with standalone usage in mind. Some things get a bit hairy because of that. These things are mostly connection management and exception handling.

Stackoverflow to the rescue (once again). There was already a question regarding use of only db part of django framework. Normally function like this in django:


would have to become this:


def add_package(self, name):
reset_queries()
try:
p = Package.objects.filter(name=name)
if len(p) > 0:
return p[0].id
p = Package(name=name)
p.save()
return p.id
except:
_rollback_on_exception()
finally:
close_connection()

Imagine that this exception handling, rollbacks and connection closing would have to be in every function. A bit ugly isn’t it? We cannot really use inheritance to our advantage, but we could use metaclass(es). I like look of decorators a bit better, so that’s what I used. So final code looks like this:


def dbquery(f):
def newfunc(*args, **kwargs):
reset_queries()
try:
return f(*args, **kwargs)
except Exception, e:
_rollback_on_exception()
raise e
return newfunc

@dbquery
def add_package(self, name):
p = Package.objects.filter(name=name)
if len(p) > 0:
return p[0].id
p = Package(name=name)
p.save()
return p.id
For other functions we could just add simple @dbquery in the beginning and voila, problem solved. Maybe there are even cleaner and/or better ways to do the same thing but at least I finally found non-trivial use for decorators.

Mount me, but be careful please!

June 30, 2009 in en, gsoc, howto, linux, open source, problem, projects, security, software

First a bold note. I already have repository on Gentoo infrastructure for working on my GSoC project. Check it out if you want.

Last time I mentioned I won’t go into technical details of my GSoC project any more on this blog. For that you can keep an eye on my project on gentooexperimental and/or gentoo mailing lists, namely gentoo-qa and gentoo-soc. But there is one interesting thing I found out while working on Collagen.

One part of my project was automation of creating of chroot environment for compiling packages. For this I created simple shell script that you can see in my repository. I will pick out one line out of previous version of this script:

mount -o bind,ro "$DIR1" "$DIR2"

What does this line do? Or more specifically what should it do? It should create a virtual copy of conents of directory DIR1 inside directory DIR2. Copy in DIR2 should be read-only, that means no creating new files, no changing of files and so on. This command succeeds and we as far as we know everything should work OK right? Wrong!

Command mentioned above actually fails silently. There is a bug in current linux kernels (2.6.30 as of this day). When you execure mount with “-o bind,ro” as arguments, the “ro” part is silently ignored. Unfortunately it is added to /etc/mtab even if it was ignored. Therefore you would not see that DIR2 is writable unless you tried writing to it yourself. Current proper way to create read-only bind mounts is therefore this:

mount -o bind "$DIR1" "$DIR2"
mount -o remount,ro "$DIR2"

There is issue of race conditions with this approach, but in most situations that should not be a problem. You can find more information about read-only bind mounts in LWN article about the topic.