com.widen.valet.importer.ImportZone.java Source code

Java tutorial

Introduction

Here is the source code for com.widen.valet.importer.ImportZone.java

Source

/*
 * Copyright 2010 Widen Enterprises, Inc.
 * Madison, Wisconsin USA -- www.widen.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.widen.valet.importer;

import com.widen.valet.*;
import com.widen.valet.util.ListUtil;
import com.widen.valet.util.NameQueryByRoute53APIService;
import com.widen.valet.util.NameQueryService;
import com.widen.valet.util.NameQueryServiceImpl;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * Smart sync zone from file.
 *
 * <p><b>Only tested using Windows 2003 DNS server files!</b>
 *
 * <p>Resources will be one-way synced with file. Route53 entries will be created or modified to match
 * zone file. Additional records present in Route53 will not be deleted.
 *
 * <p>Zones are <b>not</b> auto-created. Use ImportBulkZones to auto-create Route53 hosted zones.
 *
 * <p>Uses <b>importdns.properties</b>; see importdns.properties.sample for details.
 */
public class ImportZone {
    private final Logger log = LoggerFactory.getLogger(ImportZone.class);

    private final String awsAccessKey;

    private final String awsPrivateKey;

    private final String importFile;

    private final String route53ZoneId;

    private final int defaultTTL;

    private final boolean dryRun;

    private final String nameServer;

    private NameQueryService queryService;

    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();

        InputStream stream = ImportZone.class.getResourceAsStream("importdns.properties");

        if (stream == null) {
            throw new RuntimeException("File importdns.properties not found!");
        }

        properties.load(stream);

        new ImportZone(properties).run();
    }

    public ImportZone(Properties properties) {
        awsAccessKey = getAndVerifyProperty("widen.valet.aws-access-key", properties);
        awsPrivateKey = getAndVerifyProperty("widen.valet.aws-private-key", properties);
        importFile = getAndVerifyProperty("widen.valet.import-file", properties);
        route53ZoneId = getAndVerifyProperty("widen.valet.aws-route53-zone-id", properties);
        defaultTTL = Integer.parseInt(getAndVerifyProperty("widen.valet.default-ttl", properties));
        dryRun = Boolean.parseBoolean(getAndVerifyProperty("widen.valet.dry-run", properties));
        nameServer = getAndVerifyProperty("widen.valet.aws-name-server", properties);
    }

    private String getAndVerifyProperty(String key, Properties properties) {
        String prop = properties.getProperty(key);

        if (StringUtils.isBlank(prop)) {
            throw new RuntimeException("Property key " + key + " cannot be blank.");
        }

        return prop;
    }

    public void run() throws IOException {
        Route53Driver driver = new Route53Driver(awsAccessKey, awsPrivateKey);

        Zone zone = driver.zoneDetails(route53ZoneId);

        if ("route53rrs".equals(nameServer)) {
            queryService = new NameQueryByRoute53APIService(driver, zone);
        } else {
            queryService = new NameQueryServiceImpl(nameServer);
        }

        final List<ZoneUpdateAction> actions = createZoneUpdateActions(zone);

        log.info("Zone Update Actions Created:\n{}", StringUtils.join(actions, "\n"));

        if (dryRun) {
            log.info("Dry run complete.");
            return;
        }

        int batch = 0;

        final int MAX_CHANGE_RECORDS_ALLOWED = 100;

        for (List<ZoneUpdateAction> processingList : ListUtil.split(actions, MAX_CHANGE_RECORDS_ALLOWED)) {
            batch++;

            ZoneChangeStatus sync = driver.updateZone(zone,
                    "Sync zone records from file" + importFile + " batch " + batch, processingList);

            driver.waitForSync(sync);
        }

        log.info("Zone import complete and INSYNC!");
    }

    private List<ZoneUpdateAction> createZoneUpdateActions(Zone zone) throws IOException {
        List<String> lines = IOUtils.readLines(new FileInputStream(importFile));

        boolean foundZoneRecords = false;

        List<ZoneUpdateAction> actions = new ArrayList<ZoneUpdateAction>();

        for (String l : lines) {
            if (!foundZoneRecords) {
                if (StringUtils.contains(l, ";  Zone records")) {
                    foundZoneRecords = true;
                }

                continue;
            }

            if (StringUtils.isNotBlank(l) && !l.startsWith(";")) {
                log.debug("processing line: {}", l);

                List<ZoneUpdateAction> actionForRecord = parseRecord(l, zone.name, actions);

                actions.addAll(actionForRecord);
            }
        }

        return actions;
    }

    private List<ZoneUpdateAction> parseRecord(String record, String zone, List<ZoneUpdateAction> existing) {
        final List<String> split = ZoneFileLineSplitter.splitLine(record);

        final String rawName = split.get(0);

        final String name;

        if ("@".equals(rawName)) {
            name = zone.toLowerCase();
        } else {
            name = String.format("%s.%s", rawName, zone).toLowerCase();
        }

        final RecordType type = RecordType.valueOf(split.get(1));

        final String value = split.get(2).toLowerCase();

        NameQueryService.LookupRecord lookupRecord = queryService.lookup(name, type);

        if (!lookupRecord.exists) {
            ZoneUpdateAction action = ZoneUpdateAction.createAction(name, type, defaultTTL, value);

            return Arrays.asList(mergeAction(action, existing));
        } else if (lookupRecord.valueEqual(value)) {
            log.debug("{} {} {} is current", new Object[] { name, type, lookupRecord.values });

            return Collections.emptyList();
        } else {
            ZoneUpdateAction delete = ZoneUpdateAction.deleteAction(name, type, lookupRecord.ttl,
                    lookupRecord.values.toArray(new String[] {}));

            ZoneUpdateAction create = ZoneUpdateAction.createAction(name, type, defaultTTL, value);

            return Arrays.asList(mergeAction(delete, existing), mergeAction(create, existing));
        }
    }

    ZoneUpdateAction mergeAction(ZoneUpdateAction action, List<ZoneUpdateAction> existing) {
        int itemToMerge = Collections.binarySearch(existing, action);

        if (itemToMerge >= 0) {
            List<String> resources = existing.get(itemToMerge).resourceRecord;

            existing.remove(itemToMerge);

            return ZoneUpdateAction.mergeResources(action, resources);
        } else {
            return action;
        }
    }

}