Add tool for pulling bugs from Bugzilla.
authorRichard W.M. Jones <rjones@redhat.com>
Wed, 16 Nov 2016 13:40:39 +0000 (13:40 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Wed, 16 Nov 2016 13:57:00 +0000 (13:57 +0000)
.gitignore
Makefile.am
common-rules.mk
configure.ac
poll_bugzilla.ml [new file with mode: 0644]
todo-schema.sql

index 6cd3a5f..282de7e 100644 (file)
@@ -29,6 +29,7 @@ Makefile.in
 /m4/ltversion.m4
 /m4/lt~obsolete.m4
 /missing
+/poll-bugzilla
 /stamp-h1
 /todo
 /todo.spec
index 7aaa7b2..80cd1ac 100644 (file)
@@ -24,7 +24,7 @@ EXTRA_DIST = \
        META.in \
        README
 
-all_sources = $(TODO_SOURCES_ML)
+all_sources = $(TODO_SOURCES_ML) $(POLL_BUGZILLA_SOURCES_ML)
 
 # You must adjust this to point to the right database host.  The
 # database itself is hard-coded as 'todo'.  Setting the environment
@@ -32,7 +32,7 @@ all_sources = $(TODO_SOURCES_ML)
 #export PGHOST=todo
 export PGDATABASE=todo
 
-bin_PROGRAMS = todo
+bin_PROGRAMS = todo poll-bugzilla
 
 TODO_SOURCES_ML = \
        todo_config.ml \
@@ -59,6 +59,21 @@ todo_LINK = \
            $(OCAMLFIND) $(OCAMLBEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) \
                $(OCAMLLINKFLAGS) $(TODO_OBJECTS) -o $@
 
+POLL_BUGZILLA_SOURCES_ML = \
+       poll_bugzilla.ml
+if !HAVE_OCAMLOPT
+POLL_BUGZILLA_OBJECTS = $(POLL_BUGZILLA_SOURCES_ML:.ml=.cmo)
+else
+POLL_BUGZILLA_OBJECTS = $(POLL_BUGZILLA_SOURCES_ML:.ml=.cmx)
+endif
+
+poll_bugzilla_SOURCES = dummy.c
+poll_bugzilla_DEPENDENCIES = $(POLL_BUGZILLA_OBJECTS) $(top_srcdir)/ocaml-link.sh
+poll_bugzilla_LINK = \
+       $(top_srcdir)/ocaml-link.sh -- \
+           $(OCAMLFIND) $(OCAMLBEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) \
+               $(OCAMLLINKFLAGS) $(POLL_BUGZILLA_OBJECTS) -o $@
+
 BUILT_SOURCES = dummy.c
 dummy.c:
        rm -f $@
index 853efad..704edd8 100644 (file)
@@ -19,7 +19,7 @@ CLEANFILES = *~ *.cmi *.cmo *.cmx *.cma *.cmxa *.o
 
 OCAMLFLAGS = -g -warn-error CDEFLMPSUVYZX-3
 OCAMLPACKAGES = \
-       -package unix,calendar,pgocaml.syntax \
+       -package unix,calendar,pcre,pgocaml.syntax \
        -syntax camlp4o \
        -I $(top_builddir)
 
index 7a01621..b1f0b60 100644 (file)
@@ -86,6 +86,12 @@ if test "x$OCAML_PKG_pgocaml" = "xno"; then
     AC_MSG_ERROR([You must install the 'ocaml-pgocaml' library])
 fi
 
+dnl Check for OCaml package PCRE (required).
+AC_CHECK_OCAML_PKG(pcre)
+if test "x$OCAML_PKG_pcre" = "xno"; then
+    AC_MSG_ERROR([You must install the 'ocaml-pcre' library])
+fi
+
 dnl Check for POD (for manual pages).
 AC_CHECK_PROG(POD2MAN,pod2man,pod2man,no)
 AM_CONDITIONAL([HAVE_POD2MAN],
diff --git a/poll_bugzilla.ml b/poll_bugzilla.ml
new file mode 100644 (file)
index 0000000..5b51e1c
--- /dev/null
@@ -0,0 +1,129 @@
+(* Poll Bugzilla, find new tasks, and add them to the database.
+ * This runs from a cron job.
+ *)
+
+open CalendarLib
+open Unix
+open Printf
+
+let quote = Filename.quote
+
+let products = [
+  "Red Hat Enterprise Linux 6",
+  Printer.Date.from_string "2016-11-14";
+  "Red Hat Enterprise Linux 7",
+  Printer.Date.from_string "2017-04-03";
+]
+
+let assigned_to = "rjones@redhat.com"
+let states = ["NEW";"ASSIGNED";"ON_DEV";"POST";"MODIFIED"]
+
+let () =
+  let rex = Pcre.regexp "^(\\d+) (\\S+) (.*)$" in
+  let errors = ref 0 in
+
+  let dbh = PGOCaml.connect ~database:"todo" () in
+
+  List.iter (
+    fun (product, my_deadline) ->
+      (* Query bugzilla. *)
+      let lines =
+        let cmd =
+          sprintf "bugzilla query -p %s -a %s -t %s --outputformat='%%{bug_id} %%{component} %%{short_desc}'"
+                  (quote product) (quote assigned_to)
+                  (quote (String.concat "," states)) in
+        let chan = open_process_in cmd in
+        let lines = ref [] in
+        (try while true do lines := input_line chan :: !lines done
+         with End_of_file -> ());
+        let stat = close_process_in chan in
+        (match stat with
+         | WEXITED 0 -> ()
+         | WEXITED i ->
+            eprintf "error: bugzilla command: exited with error %d\n%!" i;
+            incr errors
+         | WSIGNALED i ->
+            eprintf "error: bugzilla command: killed by signal %d\n%!" i;
+            incr errors
+         | WSTOPPED i ->
+            eprintf "error: bugzilla command: stopped by signal %d\n%!" i;
+            incr errors
+        );
+        List.rev !lines in
+
+      if lines <> [] then (
+        (* The output is <BUG> <COMPONENT> <DESCRIPTION>.
+         * The component is turned into a tag.
+         *)
+        List.iter (
+          fun line ->
+            try
+              let subs = Pcre.exec ~rex line in
+              let bugid = Int32.of_string (Pcre.get_substring subs 1) in
+              let component = Pcre.get_substring subs 2 in
+              let description = Pcre.get_substring subs 3 in
+
+              PGOCaml.begin_work dbh;
+
+              (* If the bug doesn't exist in the tasks table, add it. *)
+              let rows = PGSQL(dbh) "select id from tasks
+                                      where rhbz = $bugid" in
+              let taskid =
+                match rows with
+                | [id] -> id
+                | [] ->
+                   printf "new task: RHBZ#%ld %s %s\n%!"
+                          bugid component description;
+                   PGSQL(dbh) "insert into tasks (description, rhbz)
+                                          values ($description, $bugid)";
+                   PGOCaml.serial4 dbh "tasks_id_seq"
+                | _ -> assert false in
+
+              (* If the component doesn't exist as a tag, create it. *)
+              let rows =
+                PGSQL(dbh) "select id from tags where name = $component" in
+              let tagid =
+                match rows with
+                | [id] -> id
+                | [] ->
+                   printf "new tag: %s\n%!" component;
+                   PGSQL(dbh) "insert into tags (name, colour)
+                                         values ($component, 'blue')";
+                   PGOCaml.serial4 dbh "tags_id_seq"
+                | _ -> assert false in
+
+              (* If the bug is not tagged with the component, tag it.
+               * If the bug changes component, this creates a second
+               * tag, which is intentional.
+               *)
+              let rows =
+                PGSQL(dbh) "select 1 from tags_tasks, tasks, tags
+                             where tags_tasks.taskid = tasks.id
+                               and tags_tasks.tagid = tags.id
+                               and tasks.rhbz = $bugid
+                               and tags.name = $component" in
+              if rows <> [Some 1_l] then
+                PGSQL(dbh) "insert into tags_tasks (tagid, taskid)
+                                            values ($tagid, $taskid)";
+
+              (* Add the bug to the todo table with the appropriate
+               * deadline.
+               *)
+              let rows =
+                PGSQL(dbh) "select 1 from todo where taskid = $taskid" in
+              if rows <> [Some 1_l] then
+                PGSQL(dbh) "insert into todo (taskid, deadline)
+                                      values ($taskid, $my_deadline::date)";
+
+              PGOCaml.commit dbh
+            with
+              Not_found ->
+                eprintf "error: line did not match regular expression: %s\n%!"
+                        line;
+                incr errors
+        ) lines
+      )
+  ) products;
+
+  if !errors > 0 then
+    exit 1
index eb4492b..5c9aeb2 100644 (file)
@@ -49,12 +49,14 @@ create table retired (
 create table tags (
     id serial not null primary key,
     name text not null,
-    colour text not null references colours (colour)
+    colour text not null references colours (colour),
+    unique (name)
 );
 
 create table tags_tasks (
     tagid integer not null references tags (id),
-    taskid integer not null references tasks (id)
+    taskid integer not null references tasks (id),
+    unique (tagid, taskid)
 );
 
 create table colours (