net.bluewizardhat.yamlcfn.sg.YamlParser.java Source code

Java tutorial

Introduction

Here is the source code for net.bluewizardhat.yamlcfn.sg.YamlParser.java

Source

/*
 * Copyright (C) 2016 BlueWizardHat
 *
 * 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 net.bluewizardhat.yamlcfn.sg;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import lombok.extern.slf4j.Slf4j;
import net.bluewizardhat.yamlcfn.sg.data.yaml.Alias;
import net.bluewizardhat.yamlcfn.sg.data.yaml.Alias.AliasType;
import net.bluewizardhat.yamlcfn.sg.data.yaml.Param;
import net.bluewizardhat.yamlcfn.sg.data.yaml.Param.ParamType;
import net.bluewizardhat.yamlcfn.sg.data.yaml.PortSpec;
import net.bluewizardhat.yamlcfn.sg.data.yaml.Protocol;
import net.bluewizardhat.yamlcfn.sg.data.yaml.SecurityGroup;
import net.bluewizardhat.yamlcfn.sg.data.yaml.Tag;
import net.bluewizardhat.yamlcfn.sg.data.yaml.UnresolvedConnection;
import net.bluewizardhat.yamlcfn.sg.data.yaml.UnresolvedConnection.CidrWithPort;
import net.bluewizardhat.yamlcfn.sg.data.yaml.UnresolvedConnection.RefWithPort;
import net.bluewizardhat.yamlcfn.sg.data.yaml.UnresolvedConnection.ValueWithPortInstantiator;
import net.bluewizardhat.yamlcfn.sg.data.yaml.ValueHolder;
import net.bluewizardhat.yamlcfn.sg.data.yaml.YamlFile;

@Slf4j
public class YamlParser {
    private ObjectMapper mapper = new ObjectMapper(new YAMLFactory());

    private YamlFile yamlFile = new YamlFile();

    @SuppressWarnings("unchecked")
    public YamlFile parse(InputStream in) throws IOException {
        List<Map<String, Map<String, ?>>> list = mapper.readValue(in, List.class);

        list.forEach(line -> {
            Entry<String, ?> entry = line.entrySet().iterator().next();
            switch (entry.getKey()) {
            case "description":
                parseDescription(entry.getValue());
                break;
            case "alias":
                parseAlias((Map<String, ?>) entry.getValue());
                break;
            case "param":
                parseParam((Map<String, ?>) entry.getValue());
                break;
            case "defaults":
                parseDefaults((Map<String, ?>) entry.getValue());
                break;
            case "securitygroup":
                parseSecurityGroup((Map<String, ?>) entry.getValue());
                break;
            default:
                throw new IllegalArgumentException("Unknown entry '" + entry.getKey() + "'");
            }
        });
        return yamlFile;
    }

    private void parseDescription(Object values) {
        log.trace("Parsing 'description' : {}", values);
        if (values != null) {
            yamlFile.setDescription(String.valueOf(values));
        }
        log.debug("Description set to: \"{}\"", yamlFile.getDescription());
    }

    private void parseAlias(Map<String, ?> values) {
        log.trace("Parsing 'alias' : {}", values);
        String name = String.valueOf(values.get("name"));
        String type = String.valueOf(values.get("type"));
        String value = String.valueOf(values.get("value"));
        yamlFile.checkExists(name);
        Alias alias = new Alias(name, AliasType.from(type), value);
        yamlFile.getAliases().put(name, alias);
        log.debug("Added alias '{}' : {}", name, alias);
    }

    private void parseParam(Map<String, ?> values) {
        log.trace("Parsing 'param' : {}", values);
        String name = String.valueOf(values.get("name"));
        String type = String.valueOf(values.get("type"));

        String description = values.containsKey("description") ? String.valueOf(values.get("description")) : null;
        String defaultValue = values.containsKey("default") ? String.valueOf(values.get("default")) : null;
        yamlFile.checkExists(name);
        Param param = new Param(name, description, ParamType.from(type), defaultValue);
        yamlFile.getParameters().put(name, param);
        log.debug("Added parameter '{}' : {}", name, param);

    }

    private void parseDefaults(Map<String, ?> values) {
        log.trace("Parsing 'defaults' : {}", values);
        SecurityGroup securityGroup = parseSecurityGroup("defaults", new SecurityGroup("defaults"), values);
        yamlFile.setDefaults(securityGroup);
        log.debug("Set defaults: {}", securityGroup);
    }

    private void parseSecurityGroup(Map<String, ?> values) {
        log.trace("Parsing 'securitygroup' : {}", values);
        String name = (String) values.get("name");
        yamlFile.checkExists(name);
        SecurityGroup securityGroup = parseSecurityGroup(name, yamlFile.getDefaults(), values);
        if (securityGroup.getDescription() == null) {
            throw new IllegalArgumentException("Securitygroup '" + name + "' is missing a description");
        }
        if (securityGroup.getVpc() == null) {
            throw new IllegalArgumentException("Securitygroup '" + name + "' is missing a vpc");
        }
        yamlFile.getSecurityGroups().put(securityGroup.getName(), securityGroup);
        log.debug("Added securitygroup '{}': {}", securityGroup.getName(), securityGroup);
    }

    @SuppressWarnings("unchecked")
    private SecurityGroup parseSecurityGroup(String name, SecurityGroup defaults, Map<String, ?> values) {
        log.trace("  Creating sg from default: {}", defaults);
        log.trace("  Creating sg from values: {}", values);
        SecurityGroup securityGroup = defaults.copy(name);
        if (values != null) {
            if (values.containsKey("description")) {
                securityGroup.setDescription((String) values.get("description"));
            }
            if (values.containsKey("inbound")) {
                // log.debug("  Parsing inbound: {}", values.get("inbound"));
                parseConnections(securityGroup.getInbound(), (List<?>) values.get("inbound"), false);
            }
            if (values.containsKey("outbound")) {
                // log.debug("  Parsing outbound: {}", values.get("outbound"));
                parseConnections(securityGroup.getOutbound(), (List<?>) values.get("outbound"), true);
            }
            if (values.containsKey("vpc")) {
                String vpc = (String) values.get("vpc");
                securityGroup.setVpc(vpc);
            }
            if (values.containsKey("tags")) {
                parseTags(securityGroup.getTags(), (List<Map<String, ?>>) values.get("tags"));
            }
        }
        return securityGroup;
    }

    @SuppressWarnings("unchecked")
    private void parseConnections(List<UnresolvedConnection> connections, List<?> list, boolean allowNamesOnly) {
        list.forEach(e -> {
            if (e instanceof String) {
                if (!allowNamesOnly) {
                    throw new IllegalArgumentException("inbound rules must specify type and ports");
                }
                connections.add(new UnresolvedConnection.Ref((String) e));
            } else if (e instanceof Map) {
                Map<String, String> m = (Map<String, String>) e;
                String ref = m.get("ref");
                String cidr = m.get("cidr");
                String type = m.get("type");
                String ports = m.get("ports");
                if (ref != null) {
                    parsePorts(connections, ref, Protocol.from(type), ports, RefWithPort::new);
                } else if (cidr != null) {
                    parsePorts(connections, cidr, Protocol.from(type), ports, CidrWithPort::new);
                } else {
                    throw new IllegalArgumentException("Unable to parse: " + m);
                }
            }
        });
    }

    private void parsePorts(List<UnresolvedConnection> connections, String ref, Protocol protocol, String ports,
            ValueWithPortInstantiator instantiator) {
        log.trace("Parsing ports: '{}'", ports);
        String[] pa = ports.split(",");
        for (String p : pa) {
            String trimmed = p.trim();
            PortSpec portspec = parseNumericPortsRange(trimmed).orElseGet(() -> PortSpec.commonPort(trimmed));

            if (portspec != null) {
                log.trace("Resolved '{}' = {}", trimmed, portspec);
                connections.add(instantiator.instantiate(ref, protocol, portspec));
            } else {
                Alias alias = yamlFile.alias(trimmed);
                if (alias == null) {
                    throw new IllegalArgumentException("Unknown port " + trimmed);
                } else if (alias.getType() != AliasType.PORTS) {
                    throw new IllegalArgumentException("'" + trimmed + "' is not a ports alias");
                }

                String[] aliasParts = alias.getValue().split(",");
                for (String aliasPart : aliasParts) {
                    portspec = parseNumericPortsRange(aliasPart.trim())
                            .orElseThrow(() -> new IllegalArgumentException(
                                    "Unable to parse port alias '" + alias.getName() + "': " + aliasPart));
                    log.trace("Resolved '{}' = {}", trimmed, portspec);
                    connections.add(instantiator.instantiate(ref, protocol, portspec));
                }
            }
        }
    }

    private static final Pattern numericPorts = Pattern.compile("^(\\d+)(-(-1|\\d+))?$");

    private Optional<PortSpec> parseNumericPortsRange(String range) {
        Matcher matcher = numericPorts.matcher(range);
        if (matcher.matches()) {
            String fromPort = matcher.group(1);
            String toPort = matcher.group(3);
            return Optional.of(toPort != null ? new PortSpec(Integer.parseInt(fromPort), Integer.parseInt(toPort))
                    : new PortSpec(Integer.parseInt(fromPort)));
        }
        return Optional.empty();
    }

    @SuppressWarnings("unchecked")
    private void parseTags(List<Tag> tags, List<Map<String, ?>> values) {
        log.trace("  Parsing tags: {}", values);
        values.forEach(m -> {
            String key = (String) m.get("key");
            Object value = m.get("value");
            if (value instanceof String) {
                tags.add(new Tag(key, ValueHolder.of((String) value)));
            } else if (value instanceof Map) {
                Map<String, ?> func = (Map<String, String>) value;
                String fn = (String) func.get("fn");
                if (!"join".equals(fn)) {
                    throw new IllegalArgumentException("Unknown function: '" + fn + "'");
                }
                String delimiter = (String) func.get("delimiter");
                List<String> fvalues = (List<String>) func.get("values");
                tags.add(new Tag(key, ValueHolder.of(delimiter, fvalues)));
            }
        });
    }
}