         @describe
             cb : (err, x) =>
                 opts.cb(err, x?.status)
+
+    attach_disk: (opts) =>
+        opts = defaults opts,
+            disk : required
+            cb   : required
+        if not opts.disk instanceof Disk
+            opts.cb("disk must be an instance of Disk")
+            return
+        dbg = @dbg("attach_disk")
+        dbg("starting...")
+        @_vm.attachDisk opts.disk._disk, (err, operation, apiResponse) =>
+            handle_operation(err, operation, (->dbg("done")), opts.cb)
+
+    detach_disk: (opts) =>
+        opts = defaults opts,
+            disk : required
+            cb   : required
+        if not opts.disk instanceof Disk
+            opts.cb("disk must be an instance of Disk")
+            return
+        dbg = @dbg("detach_disk")
+        dbg("starting...")
+        vm_data = disk_data = undefined
+        async.series([
+            (cb) =>
+                dbg("getting disk and vm metadata in parallel")
+                async.parallel([
+                    (cb) =>
+                        @get_metadata
+                            cb : (err, x) =>
+                                vm_data = x; cb(err)
+                    (cb) =>
+                        opts.disk.get_metadata
+                            cb : (err, x) =>
+                                disk_data = x; cb(err)
+                ], cb)
+            (cb) =>
+                deviceName = undefined
+                for x in vm_data.disks
+                    if x.source == disk_data.selfLink
+                        deviceName = x.deviceName
+                        break
+                dbg("determining deviceName='#{deviceName}'")
+                if not deviceName
+                    dbg("already done -- disk not connected to this machine")
+                    cb()
+                    return
+                # weird hack around what might be a bug in GCE code (NOT SURE YET)
+                # It's strange we have to make the disk from the deviceName rather than
+                # the actual disk name.  It seems like the node.js api authors got confused.
+                disk = @gcloud._gce.zone(@zone).disk(deviceName)
+                @_vm.detachDisk disk, (err, operation, apiResponse) =>
+                    handle_operation(err, operation, (->dbg("done")), cb)
+        ], opts.cb)
 
 class Disk
     constructor: (@gcloud, @name, @zone=DEFAULT_ZONE) ->

        dbg("starting")
        @_disk.delete (err, operation, apiResponse) =>
            handle_operation(err, operation, (->dbg('done')), opts.cb)

class Snapshot
    constructor: (@gcloud, @name) ->

    else
        operation.onComplete onCompleteOpts, (err, metadata) ->
            done()
            #winston.debug("onComplete #{misc.to_json(err)}, #{misc.to_json(metadata)}")
            if err
                cb?(err)
            else if metadata.error
