Package com.puppetlabs.geppetto.catalog.util

Source Code of com.puppetlabs.geppetto.catalog.util.CatalogRspecGenerator

/**
* Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*   Puppet Labs
*/
package com.puppetlabs.geppetto.catalog.util;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeMap;

import com.puppetlabs.geppetto.catalog.Catalog;
import com.puppetlabs.geppetto.catalog.CatalogResource;
import com.puppetlabs.geppetto.catalog.CatalogResourceParameter;
import com.puppetlabs.geppetto.common.CharSequences;
import com.puppetlabs.geppetto.common.stats.IntegerCluster;
import com.puppetlabs.geppetto.common.util.GeneratorUtil;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.TreeMultimap;

/**
* Generates a Catalog in puppet-rspec "form" (essentially an executable asserter that a catalog for a host is
* equal to a baseline catalog).
*
*/
public class CatalogRspecGenerator {

  private static class TitleComparator implements Comparator<CatalogResource> {

    @Override
    public int compare(CatalogResource a, CatalogResource b) {
      return a.getTitle().compareToIgnoreCase(b.getTitle());
    }

  }

  private static class TypeComparator implements Comparator<String> {

    @Override
    public int compare(String a, String b) {
      return a.compareToIgnoreCase(b);
    }

  }

  private static Function<String, String> fJavaStrToRubyStr = new Function<String, String>() {
    public String apply(String s) {
      return GeneratorUtil.getRubyStringLiteral(s);
    }
  };

  private String classNameOfResource(CatalogResource r) {
    if(!"Class".equals(r.getType()))
      throw new IllegalArgumentException("Can not produce classname from a resource of non Class type");
    return initialLowerCase(r.getTitle());
  }

  public void generate(Catalog catalog, Appendable out) throws IOException {
    headerBlurb(catalog, out);
    requirements(out);

    // Describe a host - that is what the entire catalog is related to, the "it" in Rspec tests
    // refers to a catalog compiled for this instance (don't want to do it over and over again)
    //
    out.append("describe ");
    GeneratorUtil.emitRubyStringLiteral(out, catalog.getName());
    out.append(", :type => :host do\n");

    // set empty facts and expect them to be injected - must be set to something to get them injected
    // TODO: this looks really stupid, how about having a magic "inject" fact ?
    //
    out.append(indent(1)).append("# Set facts to empty hash, and expect them to be injected\n");
    out.append(indent(1)).append("let(:facts) { { } }\n");
    out.append("\n");
    generateAll(catalog, out);
    out.append("end\n");
  }

  public void generateAll(Catalog catalog, Appendable out) throws IOException {
    TreeMultimap<String, CatalogResource> sorted = TreeMultimap.create(new TypeComparator(), new TitleComparator());
    for(CatalogResource r : catalog.getResources()) {
      // transform these per type (sorted on type)
      sorted.put(r.getType(), r);
    }
    // classes are processed separately
    SortedSet<CatalogResource> classes = sorted.get("Class");
    generateClasses(classes, out);
    sorted.removeAll("Class");
    generateResources(sorted, out);

  }

  private void generateClasses(SortedSet<CatalogResource> classes, Appendable out) throws IOException {
    out.append(indent(1)).append("# Classes (in alphabetical order)\n");
    for(CatalogResource r : classes) {
      String className = classNameOfResource(r);

      if("main".equals(className))
        // skip main (it does not exist in reality)
        continue;

      out.append(indent(1)).append("it ");
      GeneratorUtil.emitRubyStringLiteral(out, "class " + className);
      out.append(" do\n");
      out.append(indent(2)).append("should include_class(");
      GeneratorUtil.emitRubyStringLiteral(out, className);
      out.append(")\n");
      out.append(indent(1)).append("end\n");
    }
    out.append("\n");
  }

  /**
   * @param sorted
   * @param out
   */
  private void generateResources(TreeMultimap<String, CatalogResource> sorted, Appendable out) throws IOException {
    out.append(indent(1)).append("# Resources per type and title (alphabetically)\n");

    boolean first = true;
    for(String type : sorted.keySet()) {
      if(!first) {
        out.append("\n");
        first = false;
      }
      String matcherName = typeToMatcherName(type);
      String matcher = "contain_" + matcherName;
      for(CatalogResource r : sorted.get(type)) {
        out.append(indent(1)).append("it ");
        GeneratorUtil.emitRubyStringLiteral(out, "resource " + matcherName + " " + r.getTitle());
        out.append(" do\n");
        out.append(indent(2)).append("should ").append(matcher).append("(");
        GeneratorUtil.emitRubyStringLiteral(out, r.getTitle());
        out.append(")");

        if(r.getParameters().size() < 1) {
          // TODO: There is no way to check that there are no additional parameters set
          // without explicitly listing all that should not be set
          out.append("\n");
        }
        else {
          // prepare data, a cluster for padding calculation, and a treemap for sorting parameters
          IntegerCluster cluster = new IntegerCluster(20);
          TreeMap<String, List<String>> sortedParameters = Maps.newTreeMap();
          for(CatalogResourceParameter p : r.getParameters()) {
            cluster.add(p.getName().length());
            sortedParameters.put(p.getName(), p.getValue());
          }
          out.append(".with(\n");
          boolean firstItem = true;

          for(Entry<String, List<String>> entry : sortedParameters.entrySet()) {

            if(firstItem)
              firstItem = false;
            else
              out.append(",\n");

            String name = entry.getKey();
            out.append(indent(3));
            GeneratorUtil.emitRubyStringLiteral(out, name);
            out.append(padding(name, cluster)).append("=> ");
            List<String> values = entry.getValue();
            if(values.size() == 1) {
              out.append(fJavaStrToRubyStr.apply(values.get(0))).append("");
            }
            else {
              // values is an array
              // TODO: It can also be a hash - need to see how that is encoded
              out.append("[");
              Joiner.on(',').appendTo(out, Iterables.transform(values, fJavaStrToRubyStr));
              out.append("]");
            }

          }

          out.append('\n').append(indent(2)).append(")\n");
        }

        out.append(indent(1)).append("end\n");
      }
    }
  }

  public void headerBlurb(Catalog catalog, Appendable out) throws IOException {
    out.append("# Puppet RSpec Tests asserting that a catalog for a given host is compliant with\n");
    out.append("# rules derived from a baseline catalog obtained for host: '").append(catalog.getName()).append(
      "'\n");
    out.append("# Generated by: com.puppetlabs.geppetto.catalog.util.CatalogRspecGenerator\n");
    out.append("# Generated at: ").append(SimpleDateFormat.getDateInstance().format(new Date())).append("\n");
    out.append("\n");
  }

  private CharSequence indent(int nIndents) {
    return CharSequences.spaces(nIndents * 2);
  }

  private String initialLowerCase(String s) {
    StringBuilder builder = new StringBuilder(s);
    builder.setCharAt(0, Character.toLowerCase(builder.charAt(0)));
    int offset = 0;
    int pos;

    while((pos = s.indexOf("::", offset)) != -1 && s.length() >= pos + 2) {
      builder.setCharAt(pos + 2, Character.toLowerCase(builder.charAt(pos + 2)));
      offset = pos + 3;
    }
    return builder.toString();
  }

  /**
   * Returns a sequences of spaces that pads the string s to it's cluster's max + 1
   *
   * @param s
   * @param cluster
   * @return
   */
  private CharSequence padding(String s, IntegerCluster cluster) {
    return CharSequences.spaces(cluster.clusterMax(s.length()) - s.length() + 1);
  }

  private void requirements(Appendable out) throws IOException {
    out.append("require 'spec_helper'\n");
    out.append("\n");
  }

  private String typeToMatcherName(String type) {
    return initialLowerCase(type.replaceAll("::", "__"));
  }
}
TOP

Related Classes of com.puppetlabs.geppetto.catalog.util.CatalogRspecGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.