dns.drush.inc 11 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
<?php
// $Id: dns.drush.inc,v 1.4 2009/03/20 16:13:24 adrian Exp $
/**
 * @file
 *    DNS provisioning module.
 *
 * The goal of this module is to manage DNS zonefiles and Resource Records
 * (RRs), for sites that are about to be created.  It uses the provision API to
 * tie into the right places in the site creation work flow.
 */

12 13
include_once(dirname(__FILE__) . '/../provision.service.inc');

14

15 16
/**
 * Implementation of hok_drush_command().
17
 */
18 19 20 21 22 23 24 25 26
function dns_drush_command() {
  $items['provision-zone'] = array(
    'arguments' => array('operation' => dt('The operation to perform on a zone (verify, delete, rr-add, rr-delete)')),
    'description' => dt('Manipulate a zonefile'),
    'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  );
  return $items;
}

27
function drush_dns_provision_zone($action, $zone, $name = null, $type = null, $destination = null) {
28
  switch ($action) {
29 30 31 32 33 34
  case 'create-host':
    $status = d()->service('dns')->create_host($zone);
    break;
  case 'delete-host':
    $status = d()->service('dns')->delete_host($zone);
    break;
35
  case 'verify':
36 37
  case 'create':
    $status = d()->service('dns')->create_zone($zone);
38 39
    break;
  case 'delete':
40
    $status = d()->service('dns')->delete_zone($zone);
41 42
    break;
  case 'rr-add':
43 44 45
    $record = d()->service('dns')->config('zone', $zone)->record_get($name);
    $record[$type] = array_merge($record[$type], array($destination));
    d()->service('dns')->config('zone', $zone)->record_set($name, $record)->write();
46 47
    break;
  case 'rr-modify':
48
    d()->service('dns')->config('zone', $zone)->record_set($name, array($type => array($destination)))->write();
49
    break;
50
  case 'rr-delete':
anarcat's avatar
anarcat committed
51 52 53 54 55
    if ($type) {
      d()->service('dns')->config('zone', $zone)->record_set($name, array($type => NULL))->write();
    } else {
      d()->service('dns')->config('zone', $zone)->record_set($name, NULL)->write();
    }
56
    break;
57 58 59
  default:
    $status = drush_set_error('DRUSH_WRONG_ARGUMENT', dt("wrong argument provided to provision-zone"));
    break;
60
  }
61 62 63
  $status = $status && d()->service('dns')->commit($zone);
  
  return $status;
64 65 66 67 68 69
}

function dns_provision_services() {
  return array('dns' => NULL);
}

70 71


72
class provisionService_dns extends provisionService {
73
  public $service = 'dns';
74 75
  public $slave = null;

76 77 78 79 80 81 82 83 84 85 86 87
  /**
   * Helper function to increment a zone's serial number.
   *
   * @param $serial
   *    A serial in YYYYMMDDnn format. If null, a new serial based on
   *    the date will be generated.
   *
   * @return
   *    The serial, incremented based on current date and index
   */
  function increment_serial($serial = null) {
    $today = date('Ymd');
88 89 90 91 92
    if (is_null($serial)) {
      return $today . '00';
    }
    $date = substr($serial, 0, 8); # Get the YYYYMMDD part
    if ($date != $today) {
93 94 95 96 97 98 99 100 101 102 103
      return $today . '00';
    } else {
      $index = substr($serial, 8, 2); # Get the index part
      if ($index >= 99) {
        drush_set_error("serial number overflow");
      } else {
        $index++;
      }
      return $date . sprintf('%02d', $index);
    }
  }
104 105 106 107 108 109


  function parse_configs() {
    return null;
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
110 111
  function init_server() {
    parent::init_server();
112

113
    // Path for storing data store config files.
114
    $this->server->dns_data_path = $this->server->aegir_root . '/config/dns.d';
115

116 117 118 119 120
    if (!is_null($this->application_name)) {
      $app_dir = "{$this->server->config_path}/{$this->application_name}";
      $this->server->dns_zoned_path = "{$app_dir}/zone.d";
      $this->server->dns_hostd_path = "{$app_dir}/host.d";
    }
121

122
    $this->server->setProperty('slave_servers', array());
123
    $this->server->setProperty('dns_default_mx', null); # XXX: until we get full zone management
124 125 126 127 128
    $this->server->setProperty('dns_ttl', 86400); # 24h
    $this->server->setProperty('dns_refresh', 21600); # 6h
    $this->server->setProperty('dns_retry', 3600); # 1h
    $this->server->setProperty('dns_expire', 604800); # 7d
    $this->server->setProperty('dns_negativettl', 86400); # 24h
129 130
  }

131 132 133 134 135 136 137 138 139 140
  function init_site() {
    parent::init_site();

    $this->context->setProperty('dns_zone', null);
    if (is_null($this->context->dns_zone)) {
      $this->context->dns_zone = $this->guess_zone($this->context->uri);
    }

    $this->context->dns_zone_subdomain = trim(str_replace($this->context->dns_zone, '', $this->context->uri), '.');
  }
141

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
  /**
   * Run a method on each slave server
   *
   * This function does a logical AND on the return status of each of the
   * methods, and returns TRUE only if they all returned something that
   * can be interpreted as TRUE.
   *
   * @todo this is a duplicate of the cluster function of the same name, they
   * need to be merged, but then the cluster_web_server parameter need to be
   * renamed...
   *
   * @see provisionService_http_cluster::_each_server()
   */
  function _each_server($method, $args = array()) {
    // Return True by default.
    $ret = TRUE;
    foreach ($this->server->slave_servers as $server) {
      // If any methods return false, return false for the whole operation.
      $result = call_user_func_array(array(d($server)->service($this->service, $this->context), $method), $args);
      $ret = $ret && $result;
    }
    return $ret;
  }

Adrian Rossouw's avatar
Adrian Rossouw committed
166 167
  function verify_server_cmd() {
    provision_file()->create_dir($this->server->dns_data_path, dt("DNS data store"), 0700);
168

Adrian Rossouw's avatar
Adrian Rossouw committed
169 170 171 172 173
    if (!is_null($this->application_name)) {
      provision_file()->create_dir($this->server->dns_zoned_path, dt("DNS zone configuration"), 0755);
      $this->sync($this->server->dns_zoned_path, array(
        'exclude' => $this->server->dns_zoned_path . '/*',  // Make sure remote directory is created
      )); 
174

Adrian Rossouw's avatar
Adrian Rossouw committed
175 176 177 178
      provision_file()->create_dir($this->server->dns_hostd_path , dt("DNS host configuration"), 0755);
      $this->sync($this->server->dns_hostd_path, array(
        'exclude' => $this->server->dns_hostd_path . '/*',  // Make sure remote directory is created
      ));
179

Adrian Rossouw's avatar
Adrian Rossouw committed
180
      # TODO: create a slave zone path too.
181

Adrian Rossouw's avatar
Adrian Rossouw committed
182 183
      $this->create_config('server');
    } 
184

185 186
  }

187 188 189
  function config_data($config = null, $class = null) {
    $data = parent::config_data($config, $class);
    if (!is_null($this->application_name)) {
Adrian Rossouw's avatar
Adrian Rossouw committed
190
      $data['dns_data_path'] = $this->server->dns_zoned_path;
191 192 193 194 195 196 197 198 199 200
      $data['dns_zoned_path'] = $this->server->dns_zoned_path;
      $data['dns_hostd_path'] = $this->server->dns_hostd_path;
    }

    if ($config == 'host') {
      $data['site_ip_addresses'] = drush_get_option('site_ip_addresses', array(), 'site');
    }

    return $data;
  }
201

202

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
/**
 * Guess in which zone we should create the record
 *
 * This function will examine the existing zones to find to which
 * this host belongs to.
 *
 * @param $host the name of the record to add (e.g. www.example.com)
 *
 * @returns array the record and zone name to add the record to (e.g. www and example.com)
 */
  function guess_zone($host, $return = 'tld') {
    static $zone_cache;

    if (!isset($zone_cache[$host])) {
      $tld = $host;
      $zones = $this->config('server')->record_get();

      $parts = explode(".", $host);
      $subdomain = array();
      $found = FALSE;
      while (!$found && (count($parts) > 2)) {
        $tld = join(".", $parts);
        if (isset($zones[$tld])) {
          $found = TRUE;
        } else {
          $scrap = array_shift($parts);
          $subdomain[] = $scrap;
          drush_log("zone $tld not found, ditching $scrap, count: " . count($parts));
          $found = FALSE;
        } 
      }

      // this is necessary if we hit the limit of two subdomains
236
      $tld = join(".", $parts);
237 238 239 240 241 242 243
      $subdomain = join(".", $subdomain);

      $zone_cache[$host] = array('tld' => $tld, 'subdomain' => $subdomain);
    }
    else {
      $tld = $zone_cache[$host]['tld'];
      $subdomain = $zone_cache[$host]['subdomain'];
244
    }
245

246

247
    drush_log("guess_zone guessed parts $tld, $subdomain");
248 249

    if ($return == 'subdomain') {
250 251 252 253 254
      if (empty($subdomain)) {
        return '@';
      } else {
        return $subdomain;
      }
255 256 257
    }

    return $tld;
258 259
  }

260
  /**
261
   * This creates a zone, which mostly consists of adding the SOA record.
262
   */
263
  function create_zone($zone = null) {
264 265 266 267
    if (is_null($zone) && ($this->context->type == 'site')) {
      $host = $this->context->uri;
      $zone = $this->context->dns_zone;
      $sub = $this->context->dns_zone_subdomain;
268
    }
269
    if (empty($zone)) {
270 271
      return drush_set_error('DRUSH_DNS_NO_ZONE', "Could not determine the zone to create");
    }
272

273 274 275 276
    drush_log(dt("creating zone %zone", array("%zone" => $zone)));
    $this->config('zone', $zone)->write();

    drush_log(dt("recording zone in server configuration"));
277
    $this->config('server')->record_set($zone, $zone)->write();
278

279 280
    // kick zone creation on the slaves
    $this->_each_server("create_zone", $zone);
281 282 283
  }

  /**
284
   * This completely drops a zone, without any checks.
285
   */
286
  function delete_zone($zone) {
287
    $this->config('zone', $zone)->unlink();
288
    $this->config('server')->record_del($zone, $zone)->write();
289 290 291

    // kick zone deletion on slaves
    $this->_each_server("delete_zone", $zone);
292 293
  }

294
    /** 
295 296 297
   * Create a host in DNS.
   *
   * This can do a lot of things, create a zonefile, add a record to a
298 299 300 301 302
   * zonefile, it's going to make its best guess doing the Right
   * Thing.
   *
   * @arg $host string the hostname to create. If null, we look in the
   * current context (should be a site) for a URI.
303
   */
304
  function create_host($host = null) {
305 306 307 308 309
    if (!is_null($host)) {
      $zone = $this->guess_zone($host);
      $sub = $this->guess_zone($host, 'subdomain');
    } 
    elseif ($this->context->type == 'site') {
310
      $host = $this->context->uri;
311 312 313 314 315
      $zone = $this->context->dns_zone;
      $sub = $this->context->dns_zone_subdomain;
    }
    else {
      return drush_set_error('DRUSH_DNS_NO_ZONE', "Could not determine the zone to create");
316
    }
317

318
    $ips = drush_get_option('site_ip_addresses', array(), 'site');
319

320 321
    if (!$ips && count($ips) < 1) {
      drush_log(dt("no IP found for server, trying loopback"));
322
      $ips = array('127.0.0.1');
323
    }
324

325 326 327 328 329 330 331
    // XXX: kill me?
    if (!is_array($ips)) {
      $ips = array($ips); // backward compatibility?
    }

    $this->config('zone', $zone)->record_set($sub, array('A' => $ips));
    
332 333
    $this->create_zone($zone);
    $this->create_config('host');
334 335
  }

336

337 338 339 340
  /**
   * Delete a host from DNS
   *
   * Similar to create host, this will seek and destroy that host throughout zonefiles.
341 342 343
   *
   * @arg $host string the hostname to create. If null, we look in the
   * current context (should be a site) for a URI.
344
   */
345
  function delete_host($host = null) {
346 347 348 349 350
    if (!is_null($host)) {
      $zone = $this->guess_zone($host);
      $sub = $this->guess_zone($host, 'subdomain');
    } 
    elseif ($this->context->type == 'site') {
351
      $host = $this->context->uri;
352 353 354 355 356
      $zone = $this->context->dns_zone;
      $sub = $this->context->dns_zone_subdomain;
    }
    else {
      return drush_set_error('DRUSH_DNS_NO_ZONE', "Could not determine the zone to create");
357
    }
358 359 360

    // remove the records from the zone store
    $this->config('zone', $zone)->
361
      record_set($sub, array('A' => null))->write();
362 363
  }

364 365
}

366 367

include_once('dns.config.inc');