You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/02/09 16:36:21 UTC

[02/22] incubator-brooklyn git commit: CLI better default, port/https control, and version reporting

CLI better default, port/https control, and version reporting

* adds config key for web-console port
* adds CLI for https
* port defaults to 8443+ if no port specified and using https
* default if no CLI options is to show help and an error
* some https test fixes
* discovers and reports Brooklyn OSGi metadata in log, including git SHA1 (and `brooklyn info`)


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/d423522c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/d423522c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/d423522c

Branch: refs/heads/master
Commit: d423522cacf97c3fefa0df3c641718a49f9103a5
Parents: ec74489
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Jan 27 17:47:41 2015 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Jan 27 17:47:41 2015 +0000

----------------------------------------------------------------------
 .../src/main/java/brooklyn/BrooklynVersion.java | 222 +++++++++++++++----
 .../main/java/brooklyn/util/http/HttpTool.java  |   5 +
 .../test/java/brooklyn/BrooklynVersionTest.java |  49 +++-
 .../main/java/brooklyn/cli/AbstractMain.java    |  14 +-
 usage/cli/src/main/java/brooklyn/cli/Main.java  |  26 ++-
 .../cli/src/test/java/brooklyn/cli/CliTest.java |   2 +-
 .../brooklyn/cli/CloudExplorerLiveTest.java     |  11 +-
 .../brooklyn/launcher/BrooklynLauncher.java     |  15 +-
 .../brooklyn/launcher/BrooklynWebServer.java    |  13 +-
 .../brooklyn/launcher/WebAppRunnerTest.java     |   4 +-
 .../java/brooklyn/rest/BrooklynWebConfig.java   |   7 +-
 .../brooklyn/rest/resources/ServerResource.java |  15 ++
 .../provider/ExplicitUsersSecurityProvider.java |   2 +
 .../main/java/brooklyn/test/HttpTestUtils.java  |   4 +-
 14 files changed, 328 insertions(+), 61 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/core/src/main/java/brooklyn/BrooklynVersion.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/BrooklynVersion.java b/core/src/main/java/brooklyn/BrooklynVersion.java
index 8ef6a61..3e7645a 100644
--- a/core/src/main/java/brooklyn/BrooklynVersion.java
+++ b/core/src/main/java/brooklyn/BrooklynVersion.java
@@ -19,80 +19,218 @@
 package brooklyn;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static java.lang.String.format;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
 import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.jar.Attributes;
+
+import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.osgi.Osgis;
+import brooklyn.util.osgi.Osgis.ManifestHelper;
+import brooklyn.util.text.Strings;
+
+/**
+ * Wraps the version of Brooklyn.
+ * <p>
+ * Also retrieves the SHA-1 from any OSGi bundle, and checks that the maven and osgi versions match.
+ */
 public class BrooklynVersion {
 
   private static final Logger log = LoggerFactory.getLogger(BrooklynVersion.class);
   
-  private static final String VERSION_RESOURCE_FILE = "META-INF/maven/io.brooklyn/brooklyn-core/pom.properties";
-  private static final String VERSION_PROPERTY_NAME = "version";
+  private static final String MVN_VERSION_RESOURCE_FILE = "META-INF/maven/org.apache.brooklyn/brooklyn-core/pom.properties";
+  private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
+  private static final String BROOKLYN_CORE_SYMBOLIC_NAME = "org.apache.brooklyn.core";
+  
+  private static final String MVN_VERSION_PROPERTY_NAME = "version";
+  private static final String OSGI_VERSION_PROPERTY_NAME = Attributes.Name.IMPLEMENTATION_VERSION.toString();
+  private static final String OSGI_SHA1_PROPERTY_NAME = "Implementation-SHA-1";
+
 
+  private final static String VERSION_FROM_STATIC = "0.7.0-SNAPSHOT"; // BROOKLYN_VERSION
+  private static final AtomicReference<Boolean> IS_DEV_ENV = new AtomicReference<Boolean>();
+  
   public static final BrooklynVersion INSTANCE = new BrooklynVersion();
+  
+  private final Properties versionProperties = new Properties();
+  
+  public BrooklynVersion() {
+      // we read the maven pom metadata and osgi metadata and make sure it's sensible
+      // everything is put into a single map for now (good enough, but should be cleaned up)
+      readPropertiesFromMavenResource(BrooklynVersion.class.getClassLoader());
+      readPropertiesFromOsgiResource(BrooklynVersion.class.getClassLoader(), "org.apache.brooklyn.core");
+      // TODO there is also build-metadata.properties used in ServerResource /v1/server/version endpoint
+      // see comments on that about folding it into this class instead
 
-  private final String versionFromClasspath;
-  // static useful when running from the IDE
-  // TODO is the classpath version ever useful? should we always use the static?
-  private final String versionFromStatic = "0.7.0-SNAPSHOT"; // BROOKLYN_VERSION
-  private final String version;
+      checkVersions();
+  }
 
-  public BrooklynVersion() {
-    this.versionFromClasspath = readVersionPropertyFromClasspath(BrooklynVersion.class.getClassLoader());
-    if (isValid(versionFromClasspath)) {
-        this.version = versionFromClasspath;
-        if (!this.version.equals(versionFromStatic)) {
-            // should always be the same, and we can drop classpath detection ...
-            log.warn("Version detected from classpath as "+versionFromClasspath+" (preferring that), but in code it is recorded as "+versionFromStatic);
-        }
-    } else {
-        this.version = versionFromStatic;
-    }
+  public void checkVersions() {
+      String mvnVersion = getVersionFromMavenProperties();
+      if (mvnVersion!=null && !VERSION_FROM_STATIC.equals(mvnVersion)) {
+          throw new IllegalStateException("Version error: maven "+mvnVersion+" / code "+VERSION_FROM_STATIC);
+      }
+      
+      String osgiVersion = versionProperties.getProperty(OSGI_VERSION_PROPERTY_NAME);
+      // TODO does the OSGi version include other slightly differ gubbins/style ?
+      if (osgiVersion!=null && !VERSION_FROM_STATIC.equals(osgiVersion)) {
+          throw new IllegalStateException("Version error: osgi "+osgiVersion+" / code "+VERSION_FROM_STATIC);
+      }
   }
-  
+
+  /** Returns version as inferred from classpath/osgi, if possible, or 0.0.0-SNAPSHOT.
+   * See also {@link #getVersionFromMavenProperties()} and {@link #getVersionFromOsgiManifest()}.
+   * @deprecated since 0.7.0, in favour of the more specific methods (and does anyone need that default value?)
+   */
+  @Deprecated
   public String getVersionFromClasspath() {
-    return versionFromClasspath;
+      String v = getVersionFromMavenProperties();
+      if (Strings.isNonBlank(v)) return v;
+      v = getVersionFromOsgiManifest();
+      if (Strings.isNonBlank(v)) return v;
+      return "0.0.0-SNAPSHOT";
   }
   
-  public String getVersion() {
-    return version;
+  @Nullable
+  public String getVersionFromMavenProperties() {
+      return versionProperties.getProperty(MVN_VERSION_PROPERTY_NAME);
+  }
+
+  @Nullable
+  public String getVersionFromOsgiManifest() {
+      return versionProperties.getProperty(OSGI_VERSION_PROPERTY_NAME);
   }
   
-  public String getVersionFromStatic() {
-    return versionFromStatic;
+  @Nullable
+  /** SHA1 of the last commit to brooklyn at the time this build was made.
+   * For SNAPSHOT builds of course there may have been further non-committed changes. */
+  public String getSha1FromOsgiManifest() {
+      return versionProperties.getProperty(OSGI_SHA1_PROPERTY_NAME);
   }
-
-  public boolean isSnapshot() {
-      return (getVersion().indexOf("-SNAPSHOT")>=0);
+  
+  public String getVersion() {
+    return VERSION_FROM_STATIC;
   }
   
-  private static boolean isValid(String v) {
-    if (v==null) return false;
-    if (v.equals("0.0.0") || v.equals("0.0")) return false;
-    if (v.startsWith("0.0.0-") || v.startsWith("0.0-")) return false;
-    return true;
+  public boolean isSnapshot() {
+      return (getVersion().indexOf("-SNAPSHOT")>=0);
   }
-
-  private String readVersionPropertyFromClasspath(ClassLoader resourceLoader) {
-    Properties versionProperties = new Properties();
+    
+  private void readPropertiesFromMavenResource(ClassLoader resourceLoader) {
     try {
-      InputStream versionStream = resourceLoader.getResourceAsStream(VERSION_RESOURCE_FILE);
-      if (versionStream==null) return null;
+      InputStream versionStream = resourceLoader.getResourceAsStream(MVN_VERSION_RESOURCE_FILE);
+      if (versionStream==null) {
+          if (isDevelopmentEnvironment()) {
+              // allowed for dev env
+              log.trace("No maven resource file "+MVN_VERSION_RESOURCE_FILE+" available");
+          } else {
+              log.warn("No maven resource file "+MVN_VERSION_RESOURCE_FILE+" available");
+          }
+          return;
+      }
       versionProperties.load(checkNotNull(versionStream));
-    } catch (IOException exception) {
-      throw new IllegalStateException(format("Unable to load version resource file '%s'", VERSION_RESOURCE_FILE), exception);
+    } catch (IOException e) {
+      log.warn("Error reading maven resource file "+MVN_VERSION_RESOURCE_FILE+": "+e, e);
     }
-    return checkNotNull(versionProperties.getProperty(VERSION_PROPERTY_NAME), VERSION_PROPERTY_NAME);
+  }
+
+  /** reads properties from brooklyn-core's manifest */
+  private void readPropertiesFromOsgiResource(ClassLoader resourceLoader, String symbolicName) {
+      Enumeration<URL> paths;
+      try {
+          paths = BrooklynVersion.class.getClassLoader().getResources(MANIFEST_PATH);
+      } catch (IOException e) {
+          // shouldn't happen
+          throw Exceptions.propagate(e);
+      }
+      while (paths.hasMoreElements()) {
+          URL u = paths.nextElement();
+          try {
+              ManifestHelper mh = Osgis.ManifestHelper.forManifest(u.openStream());
+              if (BROOKLYN_CORE_SYMBOLIC_NAME.equals(mh.getSymbolicName())) {
+                  Attributes attrs = mh.getManifest().getMainAttributes();
+                  for (Object key: attrs.keySet()) {
+                      // key is an Attribute.Name; toString converts to string
+                      versionProperties.put(key.toString(), attrs.getValue(key.toString()));
+                  }
+                  return;
+              }
+          } catch (Exception e) {
+              Exceptions.propagateIfFatal(e);
+              log.warn("Error reading OSGi manifest from "+u+" when determining version properties: "+e, e);
+          }
+      }
+      if (isDevelopmentEnvironment()) {
+          // allowed for dev env
+          log.trace("No OSGi manifest available to determine version properties");
+      } else {
+          log.warn("No OSGi manifest available to determine version properties");
+      }
+  }
+
+  /** 
+   * Returns whether this is a Brooklyn dev environment,
+   * specifically core/target/classes/ is on the classpath for the org.apache.brooklyn.core project.
+   * <p>
+   * In a packaged or library build of Brooklyn (normal usage) this should return false,
+   * and all OSGi components should be available.
+   */
+  public static boolean isDevelopmentEnvironment() {
+      Boolean isDevEnv = IS_DEV_ENV.get();
+      if (isDevEnv!=null) return isDevEnv;
+      synchronized (IS_DEV_ENV) {
+          isDevEnv = computeIsDevelopmentEnvironment();
+          IS_DEV_ENV.set(isDevEnv);
+          return isDevEnv;
+      }
+  }
+  
+  private static boolean computeIsDevelopmentEnvironment() {
+      Enumeration<URL> paths;
+      try {
+          paths = BrooklynVersion.class.getClassLoader().getResources(MANIFEST_PATH);
+      } catch (IOException e) {
+          // shouldn't happen
+          throw Exceptions.propagate(e);
+      }
+      while (paths.hasMoreElements()) {
+          URL u = paths.nextElement();
+          if (u.getPath().endsWith("core/target/classes/META-INF/MANIFEST.MF")) {
+              try {
+                  ManifestHelper mh = Osgis.ManifestHelper.forManifest(u.openStream());
+                  if (BROOKLYN_CORE_SYMBOLIC_NAME.equals(mh.getSymbolicName())) {
+                      log.debug("Brooklyn debug environment detected; core manifest is at: "+u);
+                      return true;
+                  }
+              } catch (Exception e) {
+                  Exceptions.propagateIfFatal(e);
+                  log.warn("Error reading manifest to determine whether this is a development environment: "+e, e);
+              }
+          }
+      }
+      return false;
+  }
+
+  public void logSummary() {
+      log.debug("Brooklyn version "+getVersion()+" (git SHA1 "+getSha1FromOsgiManifest()+")");
+  }
+
+  /** @deprecated since 0.7.0, redundant with {@link #get()} */ @Deprecated
+  public static String getVersionFromStatic() {
+      return VERSION_FROM_STATIC;
   }
 
   public static String get() {
-    return INSTANCE.version;
+    return getVersionFromStatic();
   }
   
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/core/src/main/java/brooklyn/util/http/HttpTool.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/http/HttpTool.java b/core/src/main/java/brooklyn/util/http/HttpTool.java
index b57c157..a812cfc 100644
--- a/core/src/main/java/brooklyn/util/http/HttpTool.java
+++ b/core/src/main/java/brooklyn/util/http/HttpTool.java
@@ -65,6 +65,7 @@ import org.apache.http.util.EntityUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.util.crypto.SslTrustUtils;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.net.URLParamEncoder;
 import brooklyn.util.text.Strings;
@@ -80,6 +81,10 @@ public class HttpTool {
 
     private static final Logger LOG = LoggerFactory.getLogger(HttpTool.class);
 
+    /** Apache HTTP commons utility for trusting all.
+     * <p>
+     * For generic java HTTP usage, see {@link SslTrustUtils#trustAll(java.net.URLConnection)} 
+     * and static constants in the same class. */
     public static class TrustAllStrategy implements TrustStrategy {
         @Override
         public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/core/src/test/java/brooklyn/BrooklynVersionTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/BrooklynVersionTest.java b/core/src/test/java/brooklyn/BrooklynVersionTest.java
index ef9f9a6..887d8e4 100644
--- a/core/src/test/java/brooklyn/BrooklynVersionTest.java
+++ b/core/src/test/java/brooklyn/BrooklynVersionTest.java
@@ -19,18 +19,63 @@
 package brooklyn;
 
 import static org.testng.Assert.assertEquals;
+
+import java.net.URL;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import brooklyn.util.text.Strings;
+
 public class BrooklynVersionTest {
 
+  private static final Logger log = LoggerFactory.getLogger(BrooklynVersionTest.class);
+    
   @Test
   public void testGetVersion() {
-      assertEquals(BrooklynVersion.get(), BrooklynVersion.INSTANCE.getVersionFromStatic());
+      assertEquals(BrooklynVersion.get(), BrooklynVersion.INSTANCE.getVersion());
   }
     
   @Test
   public void testGetHardcodedClasspathVersion() {
-    assertEquals(BrooklynVersion.INSTANCE.getVersionFromClasspath(), "0.0.0-SNAPSHOT");
+      @SuppressWarnings("deprecation")
+      String v = BrooklynVersion.INSTANCE.getVersionFromClasspath();
+      Assert.assertTrue(BrooklynVersion.get().equals(v) || "0.0.0-SNAPSHOT".equals(v), v);
+  }
+  
+  @Test
+  public void testGetFromMaven() {
+      String v = BrooklynVersion.INSTANCE.getVersionFromMavenProperties();
+      Assert.assertTrue(v==null || BrooklynVersion.get().equals(v), v);
+  }
+  
+  @Test
+  public void testGetFromOsgi() {
+      String v = BrooklynVersion.INSTANCE.getVersionFromOsgiManifest();
+      Assert.assertTrue(v==null || BrooklynVersion.get().equals(v), v);
+  }
+  
+  @Test
+  public void testGetOsgiSha1() {
+      String sha1 = BrooklynVersion.INSTANCE.getSha1FromOsgiManifest();
+      log.info("sha1: "+sha1);
+      if (Strings.isNonBlank(sha1) || BrooklynVersion.isDevelopmentEnvironment())
+          return;
+      // we might not have a SHA1 if it's a standalone source build; just log warn in that case
+      log.warn("This build does not have git SHA1 information.");
+      // (can't assert anything, except that sha1 lookup doesn't NPE)
+  }
+  
+  @Test
+  public void testDevEnv() {
+      URL sp = getClass().getClassLoader().getResource("brooklyn/config/sample.properties");
+      if (sp==null) Assert.fail("Can't find test resources");
+      
+      log.info("Test for dev env: "+"Dev env? "+BrooklynVersion.isDevelopmentEnvironment()+"; path "+sp);
+      Assert.assertEquals(sp.getPath().endsWith("classes/brooklyn/config/sample.properties"), BrooklynVersion.isDevelopmentEnvironment(),
+          "Dev env? "+BrooklynVersion.isDevelopmentEnvironment()+"; path "+sp);
   }
   
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/cli/src/main/java/brooklyn/cli/AbstractMain.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/AbstractMain.java b/usage/cli/src/main/java/brooklyn/cli/AbstractMain.java
index d2b9169..1378c82 100644
--- a/usage/cli/src/main/java/brooklyn/cli/AbstractMain.java
+++ b/usage/cli/src/main/java/brooklyn/cli/AbstractMain.java
@@ -165,10 +165,13 @@ public abstract class AbstractMain {
 
             System.out.println(BANNER);
             System.out.println("Version:  " + BrooklynVersion.get());
+            if (BrooklynVersion.INSTANCE.isSnapshot()) {
+                System.out.println("Git SHA1: " + BrooklynVersion.INSTANCE.getSha1FromOsgiManifest());
+            }
             System.out.println("Website:  http://brooklyn.incubator.apache.org");
             System.out.println("Source:   https://github.com/apache/incubator-brooklyn");
             System.out.println();
-            System.out.println("Copyright 2011-2014 The Apache Software Foundation.");
+            System.out.println("Copyright 2011-2015 The Apache Software Foundation.");
             System.out.println("Licensed under the Apache 2.0 License");
             System.out.println();
 
@@ -176,6 +179,15 @@ public abstract class AbstractMain {
         }
     }
 
+    public static class DefaultInfoCommand extends InfoCommand {
+        @Override
+        public Void call() throws Exception {
+            super.call();
+            System.out.println("ERROR: No command specified.");
+            System.out.println();
+            throw new ParseException("No command specified.");
+        }
+    }
 
     /** method intended for overriding when the script filename is different 
      * @return the name of the script the user has invoked */

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/cli/src/main/java/brooklyn/cli/Main.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java
index 8b9480a..7a79721 100644
--- a/usage/cli/src/main/java/brooklyn/cli/Main.java
+++ b/usage/cli/src/main/java/brooklyn/cli/Main.java
@@ -31,11 +31,13 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
 import java.util.Collection;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.BrooklynVersion;
 import brooklyn.catalog.BrooklynCatalog;
 import brooklyn.cli.CloudExplorer.BlobstoreGetBlobCommand;
 import brooklyn.cli.CloudExplorer.BlobstoreListContainerCommand;
@@ -103,6 +105,8 @@ public class Main extends AbstractMain {
     public static final Logger log = LoggerFactory.getLogger(Main.class);
 
     public static void main(String... args) {
+        log.debug("Launching Brooklyn via CLI, with "+Arrays.toString(args));
+        BrooklynVersion.INSTANCE.logSummary();
         new Main().execCli(args);
     }
 
@@ -207,9 +211,14 @@ public class Main extends AbstractMain {
         public String locations;
 
         @Option(name = { "-p", "--port" }, title = "port number",
-                description = "Specifies the port to be used by the Brooklyn Management Console")
-        public String port = "8081+";
+                description = "Specifies the port to be used by the Brooklyn Management Console; "
+                    + "default is 8081+ for http, 8443+ for https.")
+        public String port;
 
+        @Option(name = { "--https" },
+            description = "Specifies that the server should start on https.")
+        public boolean useHttps = false;
+        
         @Option(name = { "-nc", "--noConsole" },
                 description = "Whether to start the web console")
         public boolean noConsole = false;
@@ -519,12 +528,19 @@ public class Main extends AbstractMain {
             BrooklynLauncher launcher;
             launcher = BrooklynLauncher.newInstance();
             launcher.localBrooklynPropertiesFile(localBrooklynProperties)
-                    .webconsolePort(port)
-                    .webconsole(!noConsole)
                     .ignorePersistenceErrors(ignorePersistenceErrors)
                     .ignoreWebErrors(ignoreWebErrors)
                     .ignoreAppErrors(ignoreAppErrors)
                     .locations(Strings.isBlank(locations) ? ImmutableList.<String>of() : JavaStringEscapes.unwrapJsonishListIfPossible(locations));
+            
+            launcher.webconsole(!noConsole);
+            if (useHttps) {
+                // true sets it; false (not set) leaves it blank and falls back to config key
+                // (no way currently to override config key, but that could be added)
+                launcher.webconsoleHttps(useHttps);
+            }
+            launcher.webconsolePort(port);
+            
             if (noGlobalBrooklynProperties) {
                 log.debug("Configuring to disable global brooklyn.properties");
                 launcher.globalBrooklynPropertiesFile(null);
@@ -825,7 +841,7 @@ public class Main extends AbstractMain {
     protected CliBuilder<BrooklynCommand> cliBuilder() {
         CliBuilder<BrooklynCommand> builder = Cli.<BrooklynCommand>builder(cliScriptName())
                 .withDescription("Brooklyn Management Service")
-                .withDefaultCommand(InfoCommand.class)
+                .withDefaultCommand(DefaultInfoCommand.class)
                 .withCommands(
                         HelpCommand.class,
                         InfoCommand.class,

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/cli/src/test/java/brooklyn/cli/CliTest.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/test/java/brooklyn/cli/CliTest.java b/usage/cli/src/test/java/brooklyn/cli/CliTest.java
index 8af432d..79e1b56 100644
--- a/usage/cli/src/test/java/brooklyn/cli/CliTest.java
+++ b/usage/cli/src/test/java/brooklyn/cli/CliTest.java
@@ -267,7 +267,7 @@ public class CliTest {
         assertTrue(details.contains("app=null"), details);   
         assertTrue(details.contains("script=null"), details);
         assertTrue(details.contains("location=null"), details);
-        assertTrue(details.contains("port=8081"), details);
+        assertTrue(details.contains("port=null"), details);
         assertTrue(details.contains("noConsole=false"), details);
         assertTrue(details.contains("noConsoleSecurity=false"), details);
         assertTrue(details.contains("noShutdownOnExit=false"), details);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/cli/src/test/java/brooklyn/cli/CloudExplorerLiveTest.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/test/java/brooklyn/cli/CloudExplorerLiveTest.java b/usage/cli/src/test/java/brooklyn/cli/CloudExplorerLiveTest.java
index 3d3b140..6069f74 100644
--- a/usage/cli/src/test/java/brooklyn/cli/CloudExplorerLiveTest.java
+++ b/usage/cli/src/test/java/brooklyn/cli/CloudExplorerLiveTest.java
@@ -21,6 +21,7 @@ package brooklyn.cli;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 import io.airlift.command.Cli;
+import io.airlift.command.ParseException;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -28,6 +29,7 @@ import java.io.InputStream;
 import java.io.PrintStream;
 import java.util.List;
 
+import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.Test;
 
@@ -49,8 +51,13 @@ public class CloudExplorerLiveTest {
     }
 
     @Test
-    public void testNoArgsReturnsInfo() throws Exception {
-        call(new String[0]);
+    public void testNoArgsThrows() throws Exception {
+        try {
+            call(new String[0]);
+            Assert.fail("No args should fail");
+        } catch (ParseException e) {
+            Assert.assertTrue(e.toString().contains("No command specified"), ""+e);
+        }
     }
 
     // A user running these tests might not have any instances; so don't assert that there will be one

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index 64326c9..9cc8ea2 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -144,6 +144,7 @@ public class BrooklynLauncher {
     private boolean startWebApps = true;
     private boolean startBrooklynNode = false;
     private PortRange port = null;
+    private Boolean useHttps = null;
     private InetAddress bindAddress = null;
     private InetAddress publicAddress = null;
     private Map<String,String> webApps = new LinkedHashMap<String,String>();
@@ -335,12 +336,13 @@ public class BrooklynLauncher {
      * As {@link #webconsolePort(PortRange)} taking a string range
      */
     public BrooklynLauncher webconsolePort(String port) {
+        if (port==null) return webconsolePort((PortRange)null);
         return webconsolePort(PortRanges.fromString(port));
     }
 
     /**
      * Specifies the port where the web console (and any additional webapps specified) will listen;
-     * default "8081+" (or "8443+" for https) being the first available >= 8081.
+     * default (null) means "8081+" being the first available >= 8081 (or "8443+" for https).
      */ 
     public BrooklynLauncher webconsolePort(PortRange port) {
         this.port = port;
@@ -348,6 +350,14 @@ public class BrooklynLauncher {
     }
 
     /**
+     * Specifies whether the webconsole should use https.
+     */ 
+    public BrooklynLauncher webconsoleHttps(Boolean useHttps) {
+        this.useHttps = useHttps;
+        return this;
+    }
+
+    /**
      * Specifies the NIC where the web console (and any additional webapps specified) will be bound;
      * default 0.0.0.0, unless no security is specified (e.g. users) in which case it is localhost.
      */ 
@@ -721,7 +731,8 @@ public class BrooklynLauncher {
             webServer = new BrooklynWebServer(webconsoleFlags, managementContext);
             webServer.setBindAddress(bindAddress);
             webServer.setPublicAddress(publicAddress);
-            webServer.setPort(port);
+            if (port!=null) webServer.setPort(port);
+            if (useHttps!=null) webServer.setHttpsEnabled(useHttps);
             webServer.putAttributes(brooklynProperties);
             if (skipSecurityFilter != Boolean.TRUE) {
                 webServer.setSecurityFilter(BrooklynPropertiesSecurityFilter.class);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
index a2595cd..97e93b4 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
@@ -212,6 +212,9 @@ public class BrooklynWebServer {
     }
 
     public BrooklynWebServer setPort(Object port) {
+        if (port==null) {
+            this.requestedPort = null;
+        }
         if (getActualPort()>0)
             throw new IllegalStateException("Can't set port after port has been assigned to server (using "+getActualPort()+")");
         this.requestedPort = TypeCoercions.coerce(port, PortRange.class);
@@ -223,6 +226,11 @@ public class BrooklynWebServer {
         return webappTempDir;
     }
     
+    public BrooklynWebServer setHttpsEnabled(Boolean httpsEnabled) {
+        this.httpsEnabled = httpsEnabled;
+        return this;
+    }
+    
     public boolean getHttpsEnabled() {
         if (httpsEnabled!=null) return httpsEnabled;
         httpsEnabled = managementContext.getConfig().getConfig(BrooklynWebConfig.HTTPS_REQUIRED);
@@ -338,7 +346,10 @@ public class BrooklynWebServer {
         if (actualPort == -1){
             PortRange portRange = requestedPort;
             if (portRange==null) {
-                portRange = getHttpsEnabled()? httpsPort : httpPort;
+                portRange = managementContext.getConfig().getConfig(BrooklynWebConfig.WEB_CONSOLE_PORT);
+            }
+            if (portRange==null) {
+                portRange = getHttpsEnabled() ? httpsPort : httpPort;
             }
             actualPort = LocalhostMachineProvisioningLocation.obtainPort(getAddress(), portRange);
             if (actualPort == -1) 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/launcher/src/test/java/brooklyn/launcher/WebAppRunnerTest.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/java/brooklyn/launcher/WebAppRunnerTest.java b/usage/launcher/src/test/java/brooklyn/launcher/WebAppRunnerTest.java
index cc44db7..cc2af2b 100644
--- a/usage/launcher/src/test/java/brooklyn/launcher/WebAppRunnerTest.java
+++ b/usage/launcher/src/test/java/brooklyn/launcher/WebAppRunnerTest.java
@@ -70,7 +70,7 @@ public class WebAppRunnerTest {
         Map attributes = MutableMap.copyOf( (Map) bigProps.get("attributes") );
         bigProps.put("attributes", attributes);
 
-        BrooklynProperties brooklynProperties = BrooklynProperties.Factory.newDefault();
+        BrooklynProperties brooklynProperties = BrooklynProperties.Factory.newEmpty();
         brooklynProperties.putAll(bigProps);
         brooklynProperties.put("brooklyn.webconsole.security.provider","brooklyn.rest.security.provider.AnyoneSecurityProvider");
         brooklynProperties.put("brooklyn.webconsole.security.https.required","false");
@@ -145,7 +145,7 @@ public class WebAppRunnerTest {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
 
         BrooklynLauncher launcher = BrooklynLauncher.newInstance()
-                .globalBrooklynPropertiesFile(null)
+                .brooklynProperties(BrooklynProperties.Factory.newEmpty())
                 .brooklynProperties("brooklyn.webconsole.security.provider","brooklyn.rest.security.provider.AnyoneSecurityProvider")
                 .webapp("/hello", "hello-world.war")
                 .start();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java b/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java
index 67fd069..289837a 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java
@@ -22,6 +22,7 @@ import brooklyn.config.ConfigKey;
 import brooklyn.config.ConfigMap;
 import brooklyn.config.ConfigPredicates;
 import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.location.PortRange;
 import brooklyn.rest.security.provider.DelegatingSecurityProvider;
 import brooklyn.rest.security.provider.ExplicitUsersSecurityProvider;
 
@@ -67,7 +68,11 @@ public class BrooklynWebConfig {
 
     public final static ConfigKey<Boolean> HTTPS_REQUIRED = ConfigKeys.newBooleanConfigKey(
             BASE_NAME+".security.https.required",
-            "Whether HTTPS is required", false); 
+            "Whether HTTPS is required; false here can be overridden by CLI option", false); 
+
+    public final static ConfigKey<PortRange> WEB_CONSOLE_PORT = ConfigKeys.newConfigKey(PortRange.class,
+        BASE_NAME+".port",
+        "Port/range for the web console to listen on; can be overridden by CLI option");
 
     public final static ConfigKey<String> KEYSTORE_URL = ConfigKeys.newStringConfigKey(
             BASE_NAME+".security.keystore.url",

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
index eeed1b8..8eaf321 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
@@ -219,6 +219,21 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
 
     @Override
     public VersionSummary getVersion() {
+        // TODO reconcile this with BrooklynVersion reading from the OSGi manifest
+        // @ahgittin / @sjcorbett to decide, is there need for this in addition to the OSGi manifest?
+        // TODO as part of this call should we have a strategy for reporting downstream builds in this call?
+        // for instance, we could look for
+        // * ALL "brooklyn-build-metadata.properties" files on the classpath
+        // * and/or ALL items on classpath with a custom header (eg "Brooklyn-OSGi-Feature-Name: My Project") in MANIFEST.MF
+        // i tend to favour the latter as MANIFEST.MF is already recognised; is there a reason to introduce a new file?
+        // whichever we do, i think:
+        // * "build-metadata.properties" is probably the wrong name
+        // * we should include brooklyn.version and a build timestamp in this file
+        // * the authority for brooklyn should probably be core rather than brooklyn-rest-server
+        
+        // TODO in version summary, maybe also have:
+        // features: [ { name: my-project, version: 0.1.0-SNAPSHOT, git-sha1: xxx }, ... ]
+        // osgi: [ /* similar metadata extracted from all active osgi bundles */ ]
         InputStream input = ResourceUtils.create().getResourceFromUrl("classpath://build-metadata.properties");
         Properties properties = new Properties();
         String gitSha1 = null, gitBranch = null;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
index fc5e3c6..e09a42c 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
@@ -57,6 +57,8 @@ public class ExplicitUsersSecurityProvider extends AbstractSecurityProvider impl
         allowedUsers = new LinkedHashSet<String>();
         String users = properties.getConfig(BrooklynWebConfig.USERS);
         if (users == null) {
+            // TODO unfortunately this is only activated *when* someone tries to log in
+            // (NB it seems like this class is not even instantiated until first log in)
             LOG.warn("REST has no users configured; no one will be able to log in!");
         } else if ("*".equals(users)) {
             LOG.info("REST allowing any user (so long as valid password is set)");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d423522c/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java
----------------------------------------------------------------------
diff --git a/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java b/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java
index ceee367..71eff35 100644
--- a/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java
+++ b/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java
@@ -39,12 +39,12 @@ import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLSession;
 
-import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.crypto.SslTrustUtils;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.stream.Streams;
 import brooklyn.util.time.Time;
@@ -326,7 +326,7 @@ public class HttpTestUtils {
     
     public static String getContent(String url) {
         try {
-            return DefaultGroovyMethods.getText(new URL(url).openStream());
+            return Streams.readFullyString(SslTrustUtils.trustAll(new URL(url).openConnection()).getInputStream());
         } catch (Exception e) {
             throw Throwables.propagate(e);
         }