You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2017/12/06 13:54:43 UTC

[GitHub] duynguyen closed pull request #20: Add DC/OS packages for OpenWhisk

duynguyen closed pull request #20: Add DC/OS packages for OpenWhisk
URL: https://github.com/apache/incubator-openwhisk-devtools/pull/20
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/.travis.yml b/.travis.yml
index c56e991..3781b37 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,16 @@
 sudo: required
 
+language: python
+
+python: 3.6
+
 env:
   global:
     - DOCKER_COMPOSE_VERSION: 1.8.1
   matrix:
     - TOOL: docker-compose
     - TOOL: kubernetes
+    - TOOL: dcos-universe
 
 services:
   - docker
diff --git a/dcos-universe/.travis/build.sh b/dcos-universe/.travis/build.sh
new file mode 100755
index 0000000..bcf167c
--- /dev/null
+++ b/dcos-universe/.travis/build.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+SCRIPTDIR=$(cd $(dirname "$0") && pwd)
+MESOSSCRIPTDIR="$SCRIPTDIR/../scripts/"
+
+cd $MESOSSCRIPTDIR
+pip install -r requirements/requirements.txt
+./build.sh
diff --git a/dcos-universe/.travis/setup.sh b/dcos-universe/.travis/setup.sh
new file mode 100755
index 0000000..098eafb
--- /dev/null
+++ b/dcos-universe/.travis/setup.sh
@@ -0,0 +1,5 @@
+# This build assumes Python is already installed
+# see tools/travis/setup.sh
+#!/bin/bash
+
+echo "Python Version:" `python --version`
\ No newline at end of file
diff --git a/dcos-universe/README.md b/dcos-universe/README.md
new file mode 100644
index 0000000..c3aa033
--- /dev/null
+++ b/dcos-universe/README.md
@@ -0,0 +1,47 @@
+# Mesosphere Universe Packages for OpenWhisk
+
+[![Build Status](https://travis-ci.org/openwhisk/openwhisk-devtools.svg?branch=master)](https://travis-ci.org/openwhisk/openwhisk-devtools)
+
+This is the source to generate DC/OS packages for OpenWhisk in the Mesosphere Universe.
+
+
+## How to build and set up packages repository
+
+1. In the universe home directory, run command `./scripts/build.sh`.
+
+2. Upload `/target/repo-up-to-1.8.json` to a host service (e.g. AWS S3)
+
+    * Make sure `Content-Type = application/vnd.dcos.universe.repo+json` in the file's headers.
+
+3. In DC/OS admin console, under System > Overview > Repositories, add the link to the new repository.
+
+## Installing the packages
+
+Packages need to be install in this order:  
+
+1. `apigateway`
+2. `exhibitor`  
+Notes: it could take up to 20 minutes for exhibitor instances to fully start up. Status can be monitored in the exhibitor console UI.
+3. `kafka`: additional configurations are required. Example config.json:   
+
+```
+{
+  "brokers": {
+    "port": 9092
+  },
+  "kafka": {
+    "kafka_zookeeper_uri": "exhibitor-dcos.marathon.mesos:31886",
+    "default_replication_factor": 2
+  }
+}
+```
+
+4. `whisk-couchdb` (CouchDB container with populated data for a minimal setup of OpenWhisk).
+5. `consul`
+6. `registrator`
+7. `whisk-controller`
+8. `whisk-invoker`
+
+## Supported DC/OS Versions
+
+DC/OS v1.8 or later.
diff --git a/dcos-universe/repo/meta/schema/command-schema.json b/dcos-universe/repo/meta/schema/command-schema.json
new file mode 100644
index 0000000..dbb355b
--- /dev/null
+++ b/dcos-universe/repo/meta/schema/command-schema.json
@@ -0,0 +1,20 @@
+{
+  "$schema": "http://json-schema.org/schema#",
+  "oneOf": [
+    {
+      "type": "object",
+      "properties": {
+        "pip": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "title": "Embedded Requirements File",
+          "description": "An array of strings representing of the requirements file to use for installing the subcommand for Pip. Each item is interpreted as a line in the requirements file."
+        }
+      },
+      "additionalProperties": false,
+      "required": ["pip"]
+    }
+  ]
+}
diff --git a/dcos-universe/repo/meta/schema/config-schema.json b/dcos-universe/repo/meta/schema/config-schema.json
new file mode 100644
index 0000000..85eb502
--- /dev/null
+++ b/dcos-universe/repo/meta/schema/config-schema.json
@@ -0,0 +1,150 @@
+{
+    "id": "http://json-schema.org/draft-04/schema#",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Core schema meta-schema",
+    "definitions": {
+        "schemaArray": {
+            "type": "array",
+            "minItems": 1,
+            "items": { "$ref": "#" }
+        },
+        "positiveInteger": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "positiveIntegerDefault0": {
+            "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
+        },
+        "simpleTypes": {
+            "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+        },
+        "stringArray": {
+            "type": "array",
+            "items": { "type": "string" },
+            "minItems": 1,
+            "uniqueItems": true
+        }
+    },
+    "type": "object",
+    "properties": {
+        "id": {
+            "type": "string",
+            "format": "uri"
+        },
+        "$schema": {
+            "type": "string",
+            "format": "uri"
+        },
+        "title": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        },
+        "default": {},
+        "multipleOf": {
+            "type": "number",
+            "minimum": 0,
+            "exclusiveMinimum": true
+        },
+        "maximum": {
+            "type": "number"
+        },
+        "exclusiveMaximum": {
+            "type": "boolean",
+            "default": false
+        },
+        "minimum": {
+            "type": "number"
+        },
+        "exclusiveMinimum": {
+            "type": "boolean",
+            "default": false
+        },
+        "maxLength": { "$ref": "#/definitions/positiveInteger" },
+        "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "pattern": {
+            "type": "string",
+            "format": "regex"
+        },
+        "additionalItems": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" }
+            ],
+            "default": {}
+        },
+        "items": {
+            "anyOf": [
+                { "$ref": "#" },
+                { "$ref": "#/definitions/schemaArray" }
+            ],
+            "default": {}
+        },
+        "maxItems": { "$ref": "#/definitions/positiveInteger" },
+        "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "uniqueItems": {
+            "type": "boolean",
+            "default": false
+        },
+        "maxProperties": { "$ref": "#/definitions/positiveInteger" },
+        "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "required": { "$ref": "#/definitions/stringArray" },
+        "additionalProperties": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" }
+            ],
+            "default": {}
+        },
+        "definitions": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "properties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "patternProperties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "dependencies": {
+            "type": "object",
+            "additionalProperties": {
+                "anyOf": [
+                    { "$ref": "#" },
+                    { "$ref": "#/definitions/stringArray" }
+                ]
+            }
+        },
+        "enum": {
+            "type": "array",
+            "minItems": 1,
+            "uniqueItems": true
+        },
+        "type": {
+            "anyOf": [
+                { "$ref": "#/definitions/simpleTypes" },
+                {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/simpleTypes" },
+                    "minItems": 1,
+                    "uniqueItems": true
+                }
+            ]
+        },
+        "allOf": { "$ref": "#/definitions/schemaArray" },
+        "anyOf": { "$ref": "#/definitions/schemaArray" },
+        "oneOf": { "$ref": "#/definitions/schemaArray" },
+        "not": { "$ref": "#" }
+    },
+    "dependencies": {
+        "exclusiveMaximum": [ "maximum" ],
+        "exclusiveMinimum": [ "minimum" ]
+    },
+    "default": {}
+}
diff --git a/dcos-universe/repo/meta/schema/package-schema.json b/dcos-universe/repo/meta/schema/package-schema.json
new file mode 100644
index 0000000..1cf3eb3
--- /dev/null
+++ b/dcos-universe/repo/meta/schema/package-schema.json
@@ -0,0 +1,206 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+
+  "definitions": {
+
+    "dcosReleaseVersion": {
+      "type": "string",
+      "pattern": "^(?:0|[1-9][0-9]*)(?:\\.(?:0|[1-9][0-9]*))*$",
+      "description": "A string representation of a DC/OS Release Version"
+    },
+
+    "url": {
+      "type": "string",
+      "allOf": [
+        { "format": "uri" },
+        { "pattern": "^https?://" }
+      ]
+    },
+
+
+    "v20Package": {
+      "properties": {
+        "packagingVersion": {
+          "type": "string",
+          "enum": ["2.0"]
+        },
+        "name": {
+          "type": "string"
+        },
+        "version": {
+          "type": "string"
+        },
+        "scm": {
+          "type": "string"
+        },
+        "maintainer": {
+          "type": "string"
+        },
+        "website": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "framework": {
+          "type": "boolean",
+          "default": false,
+          "description": "True if this package installs a new Mesos framework."
+        },
+        "preInstallNotes": {
+          "type": "string",
+          "description": "Pre installation notes that would be useful to the user of this package."
+        },
+        "postInstallNotes": {
+          "type": "string",
+          "description": "Post installation notes that would be useful to the user of this package."
+        },
+        "postUninstallNotes": {
+          "type": "string",
+          "description": "Post uninstallation notes that would be useful to the user of this package."
+        },
+        "tags": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "pattern": "^[^\\s]+$"
+          }
+        },
+        "selected": {
+          "type": "boolean",
+          "description": "Flag indicating if the package is selected in search results",
+          "default": false
+        },
+        "licenses": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "name": {
+                "type": "string",
+                "description": "The name of the license. For example one of [Apache License Version 2.0 | MIT License | BSD License | Proprietary]"
+              },
+              "url": {
+                "$ref": "#/definitions/url",
+                "description": "The URL where the license can be accessed"
+              }
+            },
+            "additionalProperties": false,
+            "required": [
+              "name",
+              "url"
+            ]
+          }
+        }
+      },
+      "required": [
+        "packagingVersion",
+        "name",
+        "version",
+        "maintainer",
+        "description",
+        "tags"
+      ],
+      "additionalProperties": false
+    },
+
+    "v30Package": {
+      "properties": {
+        "packagingVersion": {
+          "type": "string",
+          "enum": ["3.0"]
+        },
+        "name": {
+          "type": "string"
+        },
+        "version": {
+          "type": "string"
+        },
+        "scm": {
+          "type": "string"
+        },
+        "maintainer": {
+          "type": "string"
+        },
+        "website": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "framework": {
+          "type": "boolean",
+          "default": false,
+          "description": "True if this package installs a new Mesos framework."
+        },
+        "preInstallNotes": {
+          "type": "string",
+          "description": "Pre installation notes that would be useful to the user of this package."
+        },
+        "postInstallNotes": {
+          "type": "string",
+          "description": "Post installation notes that would be useful to the user of this package."
+        },
+        "postUninstallNotes": {
+          "type": "string",
+          "description": "Post uninstallation notes that would be useful to the user of this package."
+        },
+        "tags": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "pattern": "^[^\\s]+$"
+          }
+        },
+        "selected": {
+          "type": "boolean",
+          "description": "Flag indicating if the package is selected in search results",
+          "default": false
+        },
+        "licenses": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "name": {
+                "type": "string",
+                "description": "The name of the license. For example one of [Apache License Version 2.0 | MIT License | BSD License | Proprietary]"
+              },
+              "url": {
+                "$ref": "#/definitions/url",
+                "description": "The URL where the license can be accessed"
+              }
+            },
+            "additionalProperties": false,
+            "required": [
+              "name",
+              "url"
+            ]
+          }
+        },
+        "minDcosReleaseVersion": {
+          "$ref": "#/definitions/dcosReleaseVersion",
+          "description": "The minimum DC/OS Release Version the package can run on."
+        }
+      },
+      "required": [
+        "packagingVersion",
+        "name",
+        "version",
+        "maintainer",
+        "description",
+        "tags"
+      ],
+      "additionalProperties": false
+    }
+
+  },
+
+  "type": "object",
+  "oneOf": [
+    { "$ref": "#/definitions/v20Package" },
+    { "$ref": "#/definitions/v30Package" }
+  ]
+
+}
+
diff --git a/dcos-universe/repo/meta/schema/v2-resource-schema.json b/dcos-universe/repo/meta/schema/v2-resource-schema.json
new file mode 100644
index 0000000..c6d6020
--- /dev/null
+++ b/dcos-universe/repo/meta/schema/v2-resource-schema.json
@@ -0,0 +1,55 @@
+{
+  "additionalProperties": false,
+  "properties": {
+    "images": {
+      "type": "object",
+      "properties": {
+        "screenshots": {
+          "items": {
+            "type": "string",
+            "description": "PNG screen URL, preferably 1024 by 1024 pixels."
+          },
+          "type": "array"
+        },
+        "icon-large": {
+          "type": "string",
+          "description": "PNG icon URL, preferably 256 by 256 pixels."
+        },
+        "icon-small": {
+          "type": "string",
+          "description": "PNG icon URL, preferably 48 by 48 pixels."
+        },
+        "icon-medium": {
+          "type": "string",
+          "description": "PNG icon URL, preferably 128 by 128 pixels."
+        }
+      },
+      "additionalProperties": false
+    },
+    "assets": {
+      "type": "object",
+      "properties": {
+        "container": {
+          "type": "object",
+          "properties": {
+            "docker": {
+              "type": "object",
+              "additionalProperties": {
+                "type": "string"
+              }
+            }
+          },
+          "additionalProperties": false
+        },
+        "uris": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "string"
+          }
+        }
+      },
+      "additionalProperties": false
+    }
+  },
+  "$schema": "http://json-schema.org/draft-04/schema#"
+}
diff --git a/dcos-universe/repo/meta/schema/v3-repo-schema.json b/dcos-universe/repo/meta/schema/v3-repo-schema.json
new file mode 100644
index 0000000..b501450
--- /dev/null
+++ b/dcos-universe/repo/meta/schema/v3-repo-schema.json
@@ -0,0 +1,507 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+
+  "definitions": {
+
+
+    "dcosReleaseVersion": {
+      "type": "string",
+      "pattern": "^(?:0|[1-9][0-9]*)(?:\\.(?:0|[1-9][0-9]*))*$",
+      "description": "A string representation of a DC/OS Release Version"
+    },
+
+    "url": {
+      "type": "string",
+      "allOf": [
+        { "format": "uri" },
+        { "pattern": "^https?://" }
+      ]
+    },
+
+    "base64String": {
+      "type": "string",
+      "pattern": "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$"
+    },
+
+    "cliInfo": {
+      "additionalProperties": false,
+      "properties": {
+        "contentHash": {
+          "items": {
+            "$ref": "#/definitions/hash"
+          },
+          "minItems": 1,
+          "type": "array"
+        },
+        "kind": {
+          "enum": [
+            "executable",
+            "zip"
+          ],
+          "type": "string"
+        },
+        "url": {
+          "$ref": "#/definitions/url"
+        }
+      },
+      "required": [
+        "url",
+        "kind",
+        "contentHash"
+      ],
+      "type": "object"
+    },
+
+    "hash": {
+      "additionalProperties": false,
+      "properties": {
+        "algo": {
+          "enum": [
+            "sha256"
+          ],
+          "type": "string"
+        },
+        "value": {
+          "type": "string"
+        }
+      },
+      "required": [
+        "algo",
+        "value"
+      ],
+      "type": "object"
+    },
+
+
+    "marathon": {
+      "type": "object",
+      "properties": {
+        "v2AppMustacheTemplate": {
+          "$ref": "#/definitions/base64String"
+        }
+      },
+      "required": [ "v2AppMustacheTemplate" ],
+      "additionalProperties": false
+    },
+
+
+
+    "v20resource": {
+      "additionalProperties": false,
+      "type": "object",
+      "properties": {
+        "assets": {
+          "type": "object",
+          "properties": {
+            "uris": {
+              "type": "object",
+              "additionalProperties": {
+                "type": "string"
+              }
+            },
+            "container": {
+              "type": "object",
+              "properties": {
+                "docker": {
+                  "type": "object",
+                  "additionalProperties": {
+                    "type": "string"
+                  }
+                }
+              },
+              "additionalProperties": false
+            }
+          },
+          "additionalProperties": false
+        },
+        "images": {
+          "type": "object",
+          "properties": {
+            "icon-small": {
+              "type": "string",
+              "description": "PNG icon URL, preferably 48 by 48 pixels."
+            },
+            "icon-medium": {
+              "type": "string",
+              "description": "PNG icon URL, preferably 128 by 128 pixels."
+            },
+            "icon-large": {
+              "type": "string",
+              "description": "PNG icon URL, preferably 256 by 256 pixels."
+            },
+            "screenshots": {
+              "type": "array",
+              "items": {
+                "type": "string",
+                "description": "PNG screen URL, preferably 1024 by 1024 pixels."
+              }
+            }
+          },
+          "additionalProperties": false
+        }
+      }
+    },
+
+
+    "v30resource": {
+      "additionalProperties": false,
+      "type": "object",
+      "properties": {
+        "assets": {
+          "type": "object",
+          "properties": {
+            "uris": {
+              "type": "object",
+              "additionalProperties": {
+                "type": "string"
+              }
+            },
+            "container": {
+              "type": "object",
+              "properties": {
+                "docker": {
+                  "type": "object",
+                  "additionalProperties": {
+                    "type": "string"
+                  }
+                }
+              },
+              "additionalProperties": false
+            }
+          },
+          "additionalProperties": false
+        },
+        "cli": {
+          "additionalProperties": false,
+          "properties": {
+            "binaries": {
+              "additionalProperties": false,
+              "minProperties": 1,
+              "properties": {
+                "darwin": {
+                  "additionalProperties": false,
+                  "properties": {
+                    "x86-64": {
+                      "$ref": "#/definitions/cliInfo"
+                    }
+                  },
+                  "required": [
+                    "x86-64"
+                  ],
+                  "type": "object"
+                },
+                "linux": {
+                  "additionalProperties": false,
+                  "properties": {
+                    "x86-64": {
+                      "$ref": "#/definitions/cliInfo"
+                    }
+                  },
+                  "required": [
+                    "x86-64"
+                  ],
+                  "type": "object"
+                },
+                "windows": {
+                  "additionalProperties": false,
+                  "properties": {
+                    "x86-64": {
+                      "$ref": "#/definitions/cliInfo"
+                    }
+                  },
+                  "required": [
+                    "x86-64"
+                  ],
+                  "type": "object"
+                }
+              },
+              "type": "object"
+            }
+          },
+          "required": [
+              "binaries"
+          ],
+          "type": "object"
+        },
+        "images": {
+          "type": "object",
+          "properties": {
+            "icon-small": {
+              "type": "string",
+              "description": "PNG icon URL, preferably 48 by 48 pixels."
+            },
+            "icon-medium": {
+              "type": "string",
+              "description": "PNG icon URL, preferably 128 by 128 pixels."
+            },
+            "icon-large": {
+              "type": "string",
+              "description": "PNG icon URL, preferably 256 by 256 pixels."
+            },
+            "screenshots": {
+              "type": "array",
+              "items": {
+                "type": "string",
+                "description": "PNG screen URL, preferably 1024 by 1024 pixels."
+              }
+            }
+          },
+          "additionalProperties": false
+        }
+      }
+    },
+
+
+    "config": {
+      "$ref": "http://json-schema.org/draft-04/schema#"
+    },
+
+
+    "command": {
+      "additionalProperties": false,
+      "required": ["pip"],
+      "properties": {
+        "pip": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "title": "Embedded Requirements File",
+          "description": "[Deprecated v3.x] An array of strings representing of the requirements file to use for installing the subcommand for Pip. Each item is interpreted as a line in the requirements file."
+        }
+      }
+    },
+
+
+
+    "v20Package": {
+      "properties": {
+        "packagingVersion": {
+          "type": "string",
+          "enum": ["2.0"]
+        },
+        "name": {
+          "type": "string"
+        },
+        "version": {
+          "type": "string"
+        },
+        "releaseVersion": {
+          "type": "integer",
+          "description": "Corresponds to the revision index from the universe directory structure",
+          "minimum": 0
+        },
+        "scm": {
+          "type": "string"
+        },
+        "maintainer": {
+          "type": "string"
+        },
+        "website": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "framework": {
+          "type": "boolean",
+          "default": false,
+          "description": "True if this package installs a new Mesos framework."
+        },
+        "preInstallNotes": {
+          "type": "string",
+          "description": "Pre installation notes that would be useful to the user of this package."
+        },
+        "postInstallNotes": {
+          "type": "string",
+          "description": "Post installation notes that would be useful to the user of this package."
+        },
+        "postUninstallNotes": {
+          "type": "string",
+          "description": "Post uninstallation notes that would be useful to the user of this package."
+        },
+        "tags": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "pattern": "^[^\\s]+$"
+          }
+        },
+        "selected": {
+          "type": "boolean",
+          "description": "Flag indicating if the package is selected in search results",
+          "default": false
+        },
+        "licenses": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "name": {
+                "type": "string",
+                "description": "The name of the license. For example one of [Apache License Version 2.0 | MIT License | BSD License | Proprietary]"
+              },
+              "url": {
+                "$ref": "#/definitions/url"
+              }
+            },
+            "additionalProperties": false,
+            "required": [
+              "name",
+              "url"
+            ]
+          }
+        },
+        "marathon": {
+          "$ref": "#/definitions/marathon"
+        },
+        "resource": {
+          "$ref": "#/definitions/v20resource"
+        },
+        "config": {
+          "$ref": "#/definitions/config"
+        },
+        "command": {
+          "$ref": "#/definitions/command"
+        }
+      },
+      "required": [
+        "packagingVersion",
+        "name",
+        "version",
+        "maintainer",
+        "description",
+        "tags"
+      ],
+      "additionalProperties": false
+    },
+
+    "v30Package": {
+      "properties": {
+        "packagingVersion": {
+          "type": "string",
+          "enum": ["3.0"]
+        },
+        "name": {
+          "type": "string"
+        },
+        "version": {
+          "type": "string"
+        },
+        "releaseVersion": {
+          "type": "integer",
+          "description": "Corresponds to the revision index from the universe directory structure",
+          "minimum": 0
+        },
+        "scm": {
+          "type": "string"
+        },
+        "maintainer": {
+          "type": "string"
+        },
+        "website": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "framework": {
+          "type": "boolean",
+          "default": false,
+          "description": "True if this package installs a new Mesos framework."
+        },
+        "preInstallNotes": {
+          "type": "string",
+          "description": "Pre installation notes that would be useful to the user of this package."
+        },
+        "postInstallNotes": {
+          "type": "string",
+          "description": "Post installation notes that would be useful to the user of this package."
+        },
+        "postUninstallNotes": {
+          "type": "string",
+          "description": "Post uninstallation notes that would be useful to the user of this package."
+        },
+        "tags": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "pattern": "^[^\\s]+$"
+          }
+        },
+        "selected": {
+          "type": "boolean",
+          "description": "Flag indicating if the package is selected in search results",
+          "default": false
+        },
+        "licenses": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "name": {
+                "type": "string",
+                "description": "The name of the license. For example one of [Apache License Version 2.0 | MIT License | BSD License | Proprietary]"
+              },
+              "url": {
+                "$ref": "#/definitions/url",
+                "description": "The URL where the license can be accessed"
+              }
+            },
+            "additionalProperties": false,
+            "required": [
+              "name",
+              "url"
+            ]
+          }
+        },
+        "minDcosReleaseVersion": {
+          "$ref": "#/definitions/dcosReleaseVersion",
+          "description": "The minimum DC/OS Release Version the package can run on."
+        },
+        "marathon": {
+          "$ref": "#/definitions/marathon"
+        },
+        "resource": {
+          "$ref": "#/definitions/v30resource"
+        },
+        "config": {
+          "$ref": "#/definitions/config"
+        },
+        "command": {
+          "$ref": "#/definitions/command"
+        }
+      },
+      "required": [
+        "packagingVersion",
+        "name",
+        "version",
+        "releaseVersion",
+        "maintainer",
+        "description",
+        "tags"
+      ],
+      "additionalProperties": false
+    }
+
+
+  },
+
+  "type": "object",
+  "properties": {
+    "packages": {
+      "type": "array",
+      "description": "The list of packages in the repo",
+      "items": {
+        "oneOf": [
+          { "$ref": "#/definitions/v20Package" },
+          { "$ref": "#/definitions/v30Package" }
+        ]
+      }
+    }
+  },
+  "required": [
+    "packages"
+  ],
+  "additionalProperties": false
+}
diff --git a/dcos-universe/repo/meta/schema/v3-resource-schema.json b/dcos-universe/repo/meta/schema/v3-resource-schema.json
new file mode 100644
index 0000000..2bfae29
--- /dev/null
+++ b/dcos-universe/repo/meta/schema/v3-resource-schema.json
@@ -0,0 +1,156 @@
+{
+  "additionalProperties": false,
+  "definitions": {
+    "cliInfo": {
+      "required": [
+        "url",
+        "kind",
+        "contentHash"
+      ],
+      "properties": {
+        "url": {
+          "type": "string"
+        },
+        "contentHash": {
+          "items": {
+            "$ref": "#/definitions/hash"
+          },
+          "minItems": 1,
+          "type": "array"
+        },
+        "kind": {
+          "enum": [
+            "executable",
+            "zip"
+          ],
+          "type": "string"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    },
+    "hash": {
+      "required": [
+        "algo",
+        "value"
+      ],
+      "properties": {
+        "algo": {
+          "enum": [
+            "sha256"
+          ],
+          "type": "string"
+        },
+        "value": {
+          "type": "string"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  },
+  "properties": {
+    "assets": {
+      "properties": {
+        "uris": {
+          "additionalProperties": {
+            "type": "string"
+          },
+          "type": "object"
+        },
+        "container": {
+          "properties": {
+            "docker": {
+              "additionalProperties": {
+                "type": "string"
+              },
+              "type": "object"
+            }
+          },
+          "additionalProperties": false,
+          "type": "object"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    },
+    "images": {
+      "properties": {
+        "icon-small": {
+          "description": "PNG icon URL, preferably 48 by 48 pixels.",
+          "type": "string"
+        },
+        "icon-large": {
+          "description": "PNG icon URL, preferably 256 by 256 pixels.",
+          "type": "string"
+        },
+        "screenshots": {
+          "items": {
+            "description": "PNG screen URL, preferably 1024 by 1024 pixels.",
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "icon-medium": {
+          "description": "PNG icon URL, preferably 128 by 128 pixels.",
+          "type": "string"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    },
+    "cli": {
+      "required": [
+        "binaries"
+      ],
+      "properties": {
+        "binaries": {
+          "minProperties": 1,
+          "properties": {
+            "linux": {
+              "required": [
+                "x86-64"
+              ],
+              "properties": {
+                "x86-64": {
+                  "$ref": "#/definitions/cliInfo"
+                }
+              },
+              "additionalProperties": false,
+              "type": "object"
+            },
+            "windows": {
+              "required": [
+                "x86-64"
+              ],
+              "properties": {
+                "x86-64": {
+                  "$ref": "#/definitions/cliInfo"
+                }
+              },
+              "additionalProperties": false,
+              "type": "object"
+            },
+            "darwin": {
+              "required": [
+                "x86-64"
+              ],
+              "properties": {
+                "x86-64": {
+                  "$ref": "#/definitions/cliInfo"
+                }
+              },
+              "additionalProperties": false,
+              "type": "object"
+            }
+          },
+          "additionalProperties": false,
+          "type": "object"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  },
+  "$schema": "http://json-schema.org/draft-04/schema#"
+}
diff --git a/dcos-universe/repo/meta/schema/version-schema.json b/dcos-universe/repo/meta/schema/version-schema.json
new file mode 100644
index 0000000..c2aadfc
--- /dev/null
+++ b/dcos-universe/repo/meta/schema/version-schema.json
@@ -0,0 +1,12 @@
+{
+  "$schema": "http://json-schema.org/schema#",
+  "type": "object",
+  "properties": {
+    "version": {
+      "type": "string",
+      "pattern": "^[0-9]\\.[0-9]\\.[0-9](-[0-9A-Za-z]+)?$"
+    }
+  },
+  "required": ["version"],
+  "additionalProperties": false
+}
diff --git a/dcos-universe/repo/meta/version.json b/dcos-universe/repo/meta/version.json
new file mode 100644
index 0000000..00f49a4
--- /dev/null
+++ b/dcos-universe/repo/meta/version.json
@@ -0,0 +1,4 @@
+{
+  "version": "3.0.0"
+}
+
diff --git a/dcos-universe/repo/packages/A/apigateway/0/config.json b/dcos-universe/repo/packages/A/apigateway/0/config.json
new file mode 100644
index 0000000..b0d48aa
--- /dev/null
+++ b/dcos-universe/repo/packages/A/apigateway/0/config.json
@@ -0,0 +1,89 @@
+{
+  "properties": {
+    "service": {
+      "description": "Service configuration for API Gateway",
+      "type": "object",
+      "properties": {
+        "name": {
+          "description": "Display name for the service on the DC/OS dashboard",
+          "type": "string",
+          "default": "apigateway"
+        },
+        "cpus": {
+          "description": "CPU allocation for the API Gateway instance",
+          "type": "number",
+          "default": 0.5,
+          "minimum": 0.1
+        },
+        "mem": {
+          "description": "Memory (MB) allocation for the API Gateway instance",
+          "type": "number",
+          "default": 256.0,
+          "minimum": 128.0
+        },
+        "instances": {
+          "description": "Number of API Gateway instances to deploy (one per public agent)",
+          "type": "integer",
+          "default": 1
+        }
+      },
+      "required":[
+        "name",
+        "cpus",
+        "mem",
+        "instances"
+      ]
+    },
+    "environment": {
+      "description": "Environment configuration for API Gateway",
+      "properties": {
+        "marathonHost": {
+          "description": "Specify Marathon endpoint URL to include app name endpoints. This is used for service discovery.",
+          "type": "string",
+          "default": "http://marathon.mesos:8080"
+        }
+      }
+    },
+    "advanced": {
+      "description": "Advanced configuration for API Gateway service",
+      "type": "object",
+      "properties": {
+        "logLevel": {
+          "description": "Specify API Gateway log level (info or debug)",
+          "type": "string",
+          "default": "info"
+        },
+        "remoteConfigAWSKey": {
+          "description": "AWS key if using remote config with S3 source",
+          "type": "string"
+        },
+        "remoteConfigAWSSecret": {
+          "description": "AWS secret if using remote config with S3 source",
+          "type": "string"
+        },
+        "remoteConfig": {
+          "description": "Remote config sync source for all config files (s3://<path>, file://<path> or /<path>)",
+          "type": "string"
+        },
+        "remoteConfigGenerated": {
+          "description": "Remote config sync source for generated files only (s3://<path>, file://<path> or /<path>)",
+          "type": "string",
+          "default": "/var/tmp/apigateway/"
+        },
+        "volumeContainerPath": {
+          "description": "Path in container to mount for config file sync location",
+          "type": "string",
+          "default": "/var/tmp/apigateway"
+        },
+        "volumeHostPath": {
+          "description": "Path on host to mount for config file sync location",
+          "type": "string",
+          "default": "/var/tmp/apigateway"
+        }
+      },
+      "required":[
+        "logLevel"
+      ]
+    }
+  }
+}
\ No newline at end of file
diff --git a/dcos-universe/repo/packages/A/apigateway/0/marathon.json.mustache b/dcos-universe/repo/packages/A/apigateway/0/marathon.json.mustache
new file mode 100644
index 0000000..2066cc9
--- /dev/null
+++ b/dcos-universe/repo/packages/A/apigateway/0/marathon.json.mustache
@@ -0,0 +1,50 @@
+{
+  "id": "/{{service.name}}",
+  "cpus": {{service.cpus}},
+  "mem": {{service.mem}},
+  "instances": {{service.instances}},
+  "constraints": [
+    ["hostname", "UNIQUE"]
+  ],
+  "acceptedResourceRoles": [
+    "slave_public"
+  ],
+  "env": {
+    "LOG_LEVEL": "{{advanced.logLevel}}",
+    "MARATHON_HOST": "{{environment.marathonHost}}",
+    "AWS_ACCESS_KEY_ID": "{{advanced.remoteConfigAWSKey}}",
+    "AWS_SECRET_ACCESS_KEY": "{{advanced.remoteConfigAWSSecret}}",
+    "REMOTE_CONFIG": "{{advanced.remoteConfig}}",
+    "REMOTE_CONFIG_GENERATED": "{{advanced.remoteConfigGenerated}}"
+  },
+  "container": {
+    "type": "DOCKER",
+    "docker": {
+      "image": "{{resource.assets.container.docker.apigateway-docker}}",
+      "network": "HOST"
+    },
+    "volumes": [
+      {
+        "containerPath": "{{advanced.volumeContainerPath}}",
+        "hostPath": "{{advanced.volumeHostPath}}",
+        "mode": "RO"
+      }
+    ]
+  },
+  "healthChecks": [
+    {
+      "path": "/health-check",
+      "port": 80,
+      "protocol": "HTTP",
+      "gracePeriodSeconds": 120,
+      "intervalSeconds": 15,
+      "timeoutSeconds": 10,
+      "maxConsecutiveFailures": 2
+    }
+  ],
+  "labels": {
+    "DCOS_SERVICE_NAME": "{{service.name}}",
+    "DCOS_SERVICE_SCHEME": "http",
+    "DCOS_SERVICE_PORT_INDEX": "0"
+  }
+}
diff --git a/dcos-universe/repo/packages/A/apigateway/0/package.json b/dcos-universe/repo/packages/A/apigateway/0/package.json
new file mode 100644
index 0000000..4e5cba7
--- /dev/null
+++ b/dcos-universe/repo/packages/A/apigateway/0/package.json
@@ -0,0 +1,18 @@
+{
+  "packagingVersion": "3.0",
+  "description": "Adobe I/O API gateway",
+  "framework": false,
+  "maintainer": "dunguyen@adobe.com",
+  "name": "apigateway",
+  "postInstallNotes": "API Gateway has been installed. You might have to stop marathon-lb app if installed due to conflicts on the ports.",
+  "postUninstallNotes": "API Gateway has been uninstalled",
+  "scm": "https://github.com/adobe-apiplatform/apigateway",
+  "tags": [
+    "api",
+    "gateway",
+    "server"
+  ],
+  "version": "1.1.0",
+  "website": "https://github.com/adobe-apiplatform/apigateway",
+  "selected": true
+}
diff --git a/dcos-universe/repo/packages/A/apigateway/0/resource.json b/dcos-universe/repo/packages/A/apigateway/0/resource.json
new file mode 100644
index 0000000..d57b8c6
--- /dev/null
+++ b/dcos-universe/repo/packages/A/apigateway/0/resource.json
@@ -0,0 +1,14 @@
+{
+  "images": {
+    "icon-small": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-small.png?raw=true",
+    "icon-medium": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-medium.png?raw=true",
+    "icon-large": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-large.png?raw=true"
+  },
+  "assets": {
+    "container": {
+      "docker": {
+        "apigateway-docker": "adobeapiplatform/apigateway:1.1.0"
+      }
+    }
+  }
+}
diff --git a/dcos-universe/repo/packages/C/consul/0/config.json b/dcos-universe/repo/packages/C/consul/0/config.json
new file mode 100644
index 0000000..8259d08
--- /dev/null
+++ b/dcos-universe/repo/packages/C/consul/0/config.json
@@ -0,0 +1,51 @@
+{
+  "type": "object",
+  "properties": {
+    "service": {
+      "description": "Consul Configuration Properties",
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "cli-port": {
+          "description": "CLI Port for Consul (default = 8400)",
+          "type": "integer",
+          "default": 8400,
+          "minimum": 0
+        },
+        "http-port": {
+          "description": "HTTP Port for Consul (default = 8500)",
+          "type": "integer",
+          "default": 8500,
+          "minimum": 0
+        },
+        "dns-port": {
+          "description": "DNS Port for Consul (default = 8600)",
+          "type": "integer",
+          "default": 8600,
+          "minimum": 0
+        },
+        "lan-port": {
+          "description": "Serf LAN Port for Consul (default = 8302)",
+          "type": "integer",
+          "default": 8302,
+          "minimum": 0
+        },
+        "name": {
+          "description": "The name of the Consul service instance.",
+          "type": "string",
+          "default": "consul"
+        }
+      },
+      "required": [
+        "cli-port",
+        "http-port",
+        "dns-port",
+        "lan-port",
+        "name"
+      ]
+    }
+  },
+  "required": [
+    "service"
+  ]
+}
diff --git a/dcos-universe/repo/packages/C/consul/0/marathon.json.mustache b/dcos-universe/repo/packages/C/consul/0/marathon.json.mustache
new file mode 100644
index 0000000..d60b2ea
--- /dev/null
+++ b/dcos-universe/repo/packages/C/consul/0/marathon.json.mustache
@@ -0,0 +1,55 @@
+{
+  "id": "/{{service.name}}",
+  "cpus": 0.2,
+  "mem": 512,
+  "instances": 1,
+  "container": {
+    "type": "DOCKER",
+    "docker": {
+      "image": "{{resource.assets.container.docker.consul-docker}}",
+      "network": "BRIDGE",
+      "portMappings": [
+        {
+          "containerPort": {{service.cli-port}},
+          "hostPort": {{service.cli-port}},
+          "servicePort": 0,
+          "protocol": "tcp"
+        },
+        {
+          "containerPort": {{service.http-port}},
+          "hostPort": {{service.http-port}},
+          "servicePort": 0,
+          "protocol": "tcp"
+        },
+        {
+          "containerPort": {{service.dns-port}},
+          "hostPort": {{service.dns-port}},
+          "servicePort": 0,
+          "protocol": "tcp"
+        },
+        {
+          "containerPort": {{service.lan-port}},
+          "hostPort": {{service.lan-port}},
+          "servicePort": 0,
+          "protocol": "tcp"
+        }
+      ]
+    }
+  },
+  "healthChecks": [
+    {
+      "path": "/v1/status/leader",
+      "protocol": "HTTP",
+      "gracePeriodSeconds": 30,
+      "intervalSeconds": 60,
+      "timeoutSeconds": 20,
+      "maxConsecutiveFailures": 3,
+      "port": 8500
+    }
+  ],
+  "labels": {
+    "DCOS_SERVICE_NAME": "{{service.name}}",
+    "DCOS_SERVICE_SCHEME": "http",
+    "DCOS_SERVICE_PORT_INDEX": "1"
+  }
+}
diff --git a/dcos-universe/repo/packages/C/consul/0/package.json b/dcos-universe/repo/packages/C/consul/0/package.json
new file mode 100644
index 0000000..1d4bdcf
--- /dev/null
+++ b/dcos-universe/repo/packages/C/consul/0/package.json
@@ -0,0 +1,18 @@
+{
+  "description": "Consul service running on DC/OS",
+  "framework": true,
+  "maintainer": "dunguyen@adobe.com",
+  "minDcosReleaseVersion": "1.8",
+  "name": "consul",
+  "packagingVersion": "3.0",
+  "postInstallNotes": "DC/OS Consul has been successfully installed!",
+  "postUninstallNotes": "DC/OS Consul service has been uninstalled.",
+  "selected": true,
+  "tags": [
+    "service",
+    "discovery",
+    "configuration",
+    "orchestration"
+  ],
+  "version": "0.7.0"
+}
diff --git a/dcos-universe/repo/packages/C/consul/0/resource.json b/dcos-universe/repo/packages/C/consul/0/resource.json
new file mode 100644
index 0000000..f0480ba
--- /dev/null
+++ b/dcos-universe/repo/packages/C/consul/0/resource.json
@@ -0,0 +1,14 @@
+{
+  "images": {
+    "icon-small": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-small.png?raw=true",
+    "icon-medium": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-medium.png?raw=true",
+    "icon-large": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-large.png?raw=true"
+  },
+  "assets": {
+    "container": {
+      "docker": {
+        "consul-docker": "consul:v0.7.0"
+      }
+    }
+  }
+}
diff --git a/dcos-universe/repo/packages/R/registrator/0/config.json b/dcos-universe/repo/packages/R/registrator/0/config.json
new file mode 100644
index 0000000..4c3db07
--- /dev/null
+++ b/dcos-universe/repo/packages/R/registrator/0/config.json
@@ -0,0 +1,35 @@
+{
+  "type": "object",
+  "properties": {
+    "service": {
+      "description": "CouchDB Configuration Properties",
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "name": {
+          "description": "The name of the Registrator service instance.",
+          "type": "string",
+          "default": "registrator"
+        },
+        "consul-location": {
+          "description": "Host location of consul cluster.",
+          "type": "string",
+          "default": "consul.marathon.mesos"
+        },
+        "consul-port": {
+          "description": "Host HTTP port of consul cluster.",
+          "type": "string",
+          "default": "8500"
+        }
+      },
+      "required": [
+        "name",
+        "consul-location",
+        "consul-port"
+      ]
+    }
+  },
+  "required": [
+    "service"
+  ]
+}
diff --git a/dcos-universe/repo/packages/R/registrator/0/marathon.json.mustache b/dcos-universe/repo/packages/R/registrator/0/marathon.json.mustache
new file mode 100644
index 0000000..1dbbc1f
--- /dev/null
+++ b/dcos-universe/repo/packages/R/registrator/0/marathon.json.mustache
@@ -0,0 +1,37 @@
+{
+  "id": "/{{service.name}}",
+  "cpus": 0.2,
+  "mem": 1024,
+  "instances": 1,
+  "args": [
+    "-resync",
+    "5",
+    "consul://{{service.consul-location}}:{{service.consul-port}}"
+  ],
+  "container": {
+    "type": "DOCKER",
+    "docker": {
+      "image": "{{resource.assets.container.docker.registrator-docker}}"
+    },
+    "volumes": [
+      {
+        "containerPath": "/tmp/docker.sock",
+        "hostPath": "/var/run/docker.sock",
+        "mode": "RO"
+      }
+    ]
+  },
+  "healthChecks": [
+    {
+      "command": { "value": "exit 0" },
+      "protocol": "COMMAND",
+      "gracePeriodSeconds": 30,
+      "intervalSeconds": 60,
+      "timeoutSeconds": 20,
+      "maxConsecutiveFailures": 3
+    }
+  ],
+  "labels": {
+    "DCOS_SERVICE_NAME": "{{service.name}}"
+  }
+}
diff --git a/dcos-universe/repo/packages/R/registrator/0/package.json b/dcos-universe/repo/packages/R/registrator/0/package.json
new file mode 100644
index 0000000..9997a40
--- /dev/null
+++ b/dcos-universe/repo/packages/R/registrator/0/package.json
@@ -0,0 +1,19 @@
+{
+  "description": "Registrator service running on DC/OS",
+  "framework": true,
+  "maintainer": "dunguyen@adobe.com",
+  "minDcosReleaseVersion": "1.8",
+  "name": "registrator",
+  "packagingVersion": "3.0",
+  "postInstallNotes": "DC/OS Registrator has been successfully installed!",
+  "postUninstallNotes": "DC/OS Registrator service has been uninstalled.",
+  "preInstallNotes": "Registrator requires Consul already installed in the same DC/OS cluster.",
+  "selected": true,
+  "tags": [
+    "service",
+    "registry",
+    "bridge",
+    "pluggable"
+  ],
+  "version": "7.0"
+}
diff --git a/dcos-universe/repo/packages/R/registrator/0/resource.json b/dcos-universe/repo/packages/R/registrator/0/resource.json
new file mode 100644
index 0000000..aa378f7
--- /dev/null
+++ b/dcos-universe/repo/packages/R/registrator/0/resource.json
@@ -0,0 +1,14 @@
+{
+  "images": {
+    "icon-small": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-small.png?raw=true",
+    "icon-medium": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-medium.png?raw=true",
+    "icon-large": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-large.png?raw=true"
+  },
+  "assets": {
+    "container": {
+      "docker": {
+        "registrator-docker": "gliderlabs/registrator"
+      }
+    }
+  }
+}
diff --git a/dcos-universe/repo/packages/W/whisk-controller/0/config.json b/dcos-universe/repo/packages/W/whisk-controller/0/config.json
new file mode 100644
index 0000000..38be0dc
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-controller/0/config.json
@@ -0,0 +1,139 @@
+{
+  "type": "object",
+  "properties": {
+    "service": {
+      "description": "OpenWhisk controller Configuration Properties",
+      "type": "object",
+      "properties": {
+        "port": {
+          "description": "Host port for OpenWhisk controller (default = 8888).",
+          "type": "integer",
+          "default": 8888,
+          "minimum": 0
+        },
+        "cpus": {
+          "description": "CPU shares to allocate to each OpenWhisk controller instance.",
+          "type": "number",
+          "minimum": 0.1,
+          "default": 1
+        },
+        "mem": {
+          "description": "Memory (MB) to allocate to each OpenWhisk controller instance.",
+          "type": "number",
+          "minimum": 512.0,
+          "default": 2048.0
+        },
+        "instances": {
+          "description": "Number of OpenWhisk controller instances to run.",
+          "type": "integer",
+          "minimum": 0,
+          "default": 1
+        },
+        "name": {
+          "description": "The name of the OpenWhisk Controller service instance.",
+          "type": "string",
+          "default": "whisk-controller"
+        }
+      },
+      "required": [
+        "port",
+        "cpus",
+        "name",
+        "instances",
+        "mem"
+      ]
+    },
+    "consul": {
+      "description": "Linked Consul properties",
+      "type": "object",
+      "properties": {
+        "location": {
+          "description": "Host location of consul cluster.",
+          "type": "string",
+          "default": "consul.marathon.mesos"
+        },
+        "port": {
+          "description": "Host HTTP port of consul cluster (default = 8500).",
+          "type": "string",
+          "default": "8500"
+        }
+      },
+      "required": [
+        "location",
+        "port"
+      ]
+    },
+    "couchdb": {
+      "description": "Linked CouchDB properties",
+      "type": "object",
+      "properties": {
+        "location": {
+          "description": "Host location of CouchDB cluster.",
+          "type": "string",
+          "default": "whisk-couchdb.marathon.mesos"
+        },
+        "port": {
+          "description": "Host HTTP port of CouchDB cluster (default = 5984).",
+          "type": "string",
+          "default": "5984"
+        },
+        "username": {
+          "description": "CouchDB username.",
+          "type": "string",
+          "default": "whisk_admin"
+        },
+        "password": {
+          "description": "CouchDB password.",
+          "type": "string",
+          "default": "some_passw0rd"
+        }
+      },
+      "required": [
+        "location",
+        "port",
+        "username",
+        "password"
+      ]
+    },
+    "kafka": {
+      "description": "Linked Kafka properties",
+      "type": "object",
+      "properties": {
+        "location": {
+          "description": "Host location of Kafka cluster.",
+          "type": "string",
+          "default": "broker-0.kafka.mesos"
+        },
+        "port": {
+          "description": "Host HTTP port of Kafka cluster (default = 9092).",
+          "type": "string",
+          "default": "9092"
+        }
+      },
+      "required": [
+        "location",
+        "port"
+      ]
+    },
+    "apigateway": {
+      "description": "Linked API Gateway properties",
+      "type": "object",
+      "properties": {
+        "location": {
+          "description": "Host location of API Gateway cluster.",
+          "type": "string",
+          "default": "apigateway.marathon.mesos"
+        },
+        "port": {
+          "description": "Host HTTP port of API Gateway cluster (default = 80).",
+          "type": "string",
+          "default": "80"
+        }
+      },
+      "required": [
+        "location",
+        "port"
+      ]
+    }
+  }
+}
diff --git a/dcos-universe/repo/packages/W/whisk-controller/0/marathon.json.mustache b/dcos-universe/repo/packages/W/whisk-controller/0/marathon.json.mustache
new file mode 100644
index 0000000..89f9d20
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-controller/0/marathon.json.mustache
@@ -0,0 +1,94 @@
+{
+  "id": "/{{service.name}}",
+  "cpus": {{service.cpus}},
+  "mem": {{service.mem}},
+  "instances": {{service.instances}},
+  "env": {
+
+    "WHISK_VERSION_NAME": "local",
+    "WHISK_VERSION_DATE": "09/01/2016",
+    "WHISK_VERSION_BUILDNO": "latest",
+    "WHISK_LOGS_DIR": "/logs",
+
+    "SERVICE_CHECK_HTTP": "/ping",
+    "SERVICE_CHECK_TIMEOUT": "2s",
+    "SERVICE_CHECK_INTERVAL": "15s",
+
+    "DB_PREFIX": "local_",
+    "DB_WHISK_ACTIONS": "local_whisks",
+    "DB_WHISK_AUTHS": "local_subjects",
+    "DB_WHISK_ACTIVATIONS": "local_activations",
+
+    "KAFKA_NUMPARTITIONS": "1",
+
+    "DEFAULTLIMITS_ACTIONS_INVOKES_PERMINUTE": "60000",
+    "DEFAULTLIMITS_ACTIONS_INVOKES_CONCURRENT": "5000",
+    "DEFAULTLIMITS_TRIGGERS_FIRES_PERMINUTE": "60000",
+    "DEFAULTLIMITS_ACTIONS_INVOKES_CONCURRENTINSYSTEM": "5000",
+    "DEFAULTLIMITS_ACTIONS_SEQUENCE_MAXLENGTH": "20",
+    "LIMITS_ACTIONS_INVOKES_PERHOUR": "3600000",
+    "LIMITS_ACTIONS_INVOKES_PERMINUTE": "60000",
+    "LIMITS_ACTIONS_INVOKES_CONCURRENT": "5000",
+    "LIMITS_TRIGGERS_FIRES_PERMINUTE": "60000",
+    "LIMITS_ACTIONS_INVOKES_CONCURRENTINSYSTEM": "5000",
+
+    "COMPONENT_NAME": "controller",
+    "PORT": "{{service.port}}",
+
+    "CONSULSERVER_HOST": "{{consul.location}}",
+    "CONSUL_HOST_PORT4": "{{consul.port}}",
+
+    "KAFKA_HOST": "{{kafka.location}}",
+    "KAFKA_HOST_PORT": "{{kafka.port}}",
+
+    "DB_PROVIDER": "CouchDB",
+    "DB_PROTOCOL": "http",
+    "DB_PORT": "{{couchdb.port}}",
+    "DB_HOST": "{{couchdb.location}}",
+    "DB_USERNAME": "{{couchdb.username}}",
+    "DB_PASSWORD": "{{couchdb.password}}",
+
+    "RUNTIMES_MANIFEST": "{\"runtimes\":{\"nodejs\":[{\"kind\":\"nodejs\",\"image\":{\"name\":\"nodejsaction\"},\"deprecated\":true},{\"kind\":\"nodejs:6\",\"default\":true,\"image\":{\"name\":\"nodejs6action\"}}],\"python\":[{\"kind\":\"python\",\"image\":{\"name\":\"python2action\"}},{\"kind\":\"python:2\",\"default\":true,\"image\":{\"name\":\"python2action\"}},{\"kind\":\"python:3\",\"image\":{\"name\":\"python3action\"}}],\"swift\":[{\"kind\":\"swift\",\"image\":{\"name\":\"swiftaction\"},\"deprecated\":true},{\"kind\":\"swift:3\",\"default\":true,\"image\":{\"name\":\"swift3action\"}}],\"java\":[{\"kind\":\"java\",\"attached\":{\"attachmentName\":\"jarfile\",\"attachmentType\":\"application\/java-archive\"},\"sentinelledLogs\":false,\"requireMain\":true,\"image\":{\"name\":\"java8action\"},\"default\":true}]}}",
+
+    "LOADBALANCER_HOST": "{{apigateway.location}}",
+    "LOADBALANCER_HOST_PORT": "{{apigateway.port}}",
+    "LOADBALANCER_ACTIVATIONCOUNTBEFORENEXTINVOKER": "10",
+    "LOADBALANCER_INVOKERBUSYTHRESHOLD": "16"
+  },
+  "container": {
+    "type": "DOCKER",
+    "docker": {
+      "image": "{{resource.assets.container.docker.whisk-controller}}",
+      "network": "BRIDGE",
+      "portMappings": [
+        {
+          "containerPort": 8888,
+          "hostPort": {{service.port}},
+          "servicePort": 0,
+          "protocol": "tcp"
+        }
+      ]
+    },
+    "volumes": [
+      {
+        "containerPath": "/logs",
+        "hostPath": "~/tmp/openwhisk/controller/logs",
+        "mode": "RW"
+      }
+    ]
+  },
+  "cmd": "/bin/sh -c \"controller/bin/controller 0 >> /dev/stderr\"",
+  "healthChecks": [
+    {
+      "path": "/ping",
+      "protocol": "HTTP",
+      "gracePeriodSeconds": 30,
+      "intervalSeconds": 15,
+      "timeoutSeconds": 2,
+      "maxConsecutiveFailures": 3
+    }
+  ],
+  "labels": {
+    "DCOS_SERVICE_NAME": "{{service.name}}"
+  }
+}
diff --git a/dcos-universe/repo/packages/W/whisk-controller/0/package.json b/dcos-universe/repo/packages/W/whisk-controller/0/package.json
new file mode 100644
index 0000000..eef9183
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-controller/0/package.json
@@ -0,0 +1,19 @@
+{
+  "description": "OpenWhisk Controller service running on DC/OS",
+  "framework": true,
+  "maintainer": "dunguyen@adobe.com",
+  "minDcosReleaseVersion": "1.8",
+  "name": "whisk-controller",
+  "packagingVersion": "3.0",
+  "postInstallNotes": "DC/OS OpenWhisk controller has been successfully installed!",
+  "postUninstallNotes": "DC/OS OpenWhisk controller service has been uninstalled.",
+  "preInstallNotes": "OpenWhisk Controller requires Kafka, CouchDB, Consul, Registrator and APIGateway already installed in the same DC/OS cluster.",
+  "selected": true,
+  "tags": [
+    "openwhisk",
+    "controller",
+    "serverless",
+    "lambda"
+  ],
+  "version": "0.1"
+}
diff --git a/dcos-universe/repo/packages/W/whisk-controller/0/resource.json b/dcos-universe/repo/packages/W/whisk-controller/0/resource.json
new file mode 100644
index 0000000..72bca7f
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-controller/0/resource.json
@@ -0,0 +1,14 @@
+{
+  "images": {
+    "icon-small": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-small.png?raw=true",
+    "icon-medium": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-medium.png?raw=true",
+    "icon-large": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-large.png?raw=true"
+  },
+  "assets": {
+    "container": {
+      "docker": {
+        "whisk-controller": "openwhisk/controller"
+      }
+    }
+  }
+}
diff --git a/dcos-universe/repo/packages/W/whisk-couchdb/0/config.json b/dcos-universe/repo/packages/W/whisk-couchdb/0/config.json
new file mode 100644
index 0000000..4d10955
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-couchdb/0/config.json
@@ -0,0 +1,69 @@
+{
+  "type": "object",
+  "properties": {
+    "service": {
+      "description": "OpenWhisk CouchDB Configuration Properties",
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "couchdb-user": {
+          "description": "CouchDB username.",
+          "type": "string",
+          "default": "whisk_admin"
+        },
+        "couchdb-password": {
+          "description": "CouchDB password.",
+          "type": "string",
+          "default": "some_passw0rd"
+        },
+        "couchdb-port": {
+          "description": "Host port for CouchDB (default = 5984)",
+          "type": "integer",
+          "default": 5984,
+          "minimum": 0
+        },
+        "cpus": {
+          "description": "CPU shares to allocate to each CouchDB instance.",
+          "type": "number",
+          "minimum": 0.1,
+          "default": 0.5
+        },
+        "mem": {
+          "description": "Memory (MB) to allocate to each CouchDB instance.",
+          "type": "number",
+          "minimum": 512.0,
+          "default": 1024.0
+        },
+        "volume-size": {
+          "description": "Size of data volume (MiB) to allocate to each CouchDB instance.",
+          "type": "number",
+          "minimum": 1024,
+          "default": 10240
+        },
+        "instances": {
+          "description": "Number of CouchDB instances to run.",
+          "type": "integer",
+          "minimum": 0,
+          "default": 1
+        },
+        "name": {
+          "description": "The name of the CouchDB service instance.",
+          "type": "string",
+          "default": "whisk-couchdb"
+        }
+      },
+      "required": [
+        "couchdb-user",
+        "couchdb-password",
+        "cpus",
+        "name",
+        "instances",
+        "mem",
+        "volume-size"
+      ]
+    }
+  },
+  "required": [
+    "service"
+  ]
+}
diff --git a/dcos-universe/repo/packages/W/whisk-couchdb/0/marathon.json.mustache b/dcos-universe/repo/packages/W/whisk-couchdb/0/marathon.json.mustache
new file mode 100644
index 0000000..dc45d08
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-couchdb/0/marathon.json.mustache
@@ -0,0 +1,52 @@
+{
+  "id": "/{{service.name}}",
+  "cpus": {{service.cpus}},
+  "mem": {{service.mem}},
+  "instances": {{service.instances}},
+  "env": {
+    "COUCHDB_USER": "{{service.couchdb-user}}",
+    "COUCHDB_PASSWORD": "{{service.couchdb-password}}"
+  },
+  "container": {
+    "type": "DOCKER",
+    "docker": {
+      "image": "{{resource.assets.container.docker.whisk-couchdb-docker}}",
+      "network": "BRIDGE",
+      "portMappings": [
+        {
+          "containerPort": {{service.couchdb-port}},
+          "hostPort": {{service.couchdb-port}},
+          "servicePort": 0,
+          "protocol": "tcp"
+        }
+      ]
+    },
+    "volumes": [
+      {
+        "containerPath": "/usr/local/var/lib/couchdb",
+        "hostPath": "couchdb",
+        "mode": "RW"
+      },
+      {
+        "containerPath": "couchdb",
+        "mode": "RW",
+        "persistent": {
+          "size": {{service.volume-size}}
+        }
+      }
+    ]
+  },
+  "healthChecks": [
+    {
+      "path": "/_stats",
+      "protocol": "HTTP",
+      "gracePeriodSeconds": 30,
+      "intervalSeconds": 60,
+      "timeoutSeconds": 20,
+      "maxConsecutiveFailures": 3
+    }
+  ],
+  "labels": {
+    "DCOS_SERVICE_NAME": "{{service.name}}"
+  }
+}
diff --git a/dcos-universe/repo/packages/W/whisk-couchdb/0/package.json b/dcos-universe/repo/packages/W/whisk-couchdb/0/package.json
new file mode 100644
index 0000000..76cf012
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-couchdb/0/package.json
@@ -0,0 +1,16 @@
+{
+  "description": "OpenWhisk CouchDB service running on DC/OS",
+  "framework": true,
+  "maintainer": "dunguyen@adobe.com",
+  "minDcosReleaseVersion": "1.8",
+  "name": "whisk-couchdb",
+  "packagingVersion": "3.0",
+  "postInstallNotes": "DC/OS OpenWhisk CouchDB has been successfully installed!",
+  "postUninstallNotes": "DC/OS OpenWhisk CouchDB service has been uninstalled.",
+  "selected": true,
+  "tags": [
+    "database",
+    "nosql"
+  ],
+  "version": "1.6"
+}
diff --git a/dcos-universe/repo/packages/W/whisk-couchdb/0/resource.json b/dcos-universe/repo/packages/W/whisk-couchdb/0/resource.json
new file mode 100644
index 0000000..5b5e32c
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-couchdb/0/resource.json
@@ -0,0 +1,14 @@
+{
+  "images": {
+    "icon-small": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-small.png?raw=true",
+    "icon-medium": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-medium.png?raw=true",
+    "icon-large": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-large.png?raw=true"
+  },
+  "assets": {
+    "container": {
+      "docker": {
+        "whisk-couchdb-docker": "adobeapiplatform/whisk-couchdb"
+      }
+    }
+  }
+}
diff --git a/dcos-universe/repo/packages/W/whisk-invoker/0/config.json b/dcos-universe/repo/packages/W/whisk-invoker/0/config.json
new file mode 100644
index 0000000..9295e13
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-invoker/0/config.json
@@ -0,0 +1,145 @@
+{
+  "type": "object",
+  "properties": {
+    "service": {
+      "description": "OpenWhisk Invoker Configuration Properties",
+      "type": "object",
+      "properties": {
+        "port": {
+          "description": "Host port for OpenWhisk invoker (default = 8085)",
+          "type": "integer",
+          "default": 8085,
+          "minimum": 0
+        },
+        "cpus": {
+          "description": "CPU shares to allocate to each OpenWhisk invoker instance.",
+          "type": "number",
+          "minimum": 0.1,
+          "default": 0.5
+        },
+        "mem": {
+          "description": "Memory (MB) to allocate to each OpenWhisk invoker instance.",
+          "type": "number",
+          "minimum": 512.0,
+          "default": 1024.0
+        },
+        "instances": {
+          "description": "Number of OpenWhisk invoker instances to run.",
+          "type": "integer",
+          "minimum": 0,
+          "default": 1
+        },
+        "name": {
+          "description": "The name of the OpenWhisk Invoker service instance.",
+          "type": "string",
+          "default": "whisk-invoker"
+        },
+        "docker_image_prefix": {
+          "description": "The value to be set as DOCKER_IMAGE_PREFIX in environment variables.",
+          "type": "string",
+          "default": "openwhisk"
+        }
+      },
+      "required": [
+        "port",
+        "cpus",
+        "name",
+        "instances",
+        "mem",
+        "docker_image_prefix"
+      ]
+    },
+    "consul": {
+      "description": "Linked Consul properties",
+      "type": "object",
+      "properties": {
+        "location": {
+          "description": "Host location of consul cluster.",
+          "type": "string",
+          "default": "consul.marathon.mesos"
+        },
+        "port": {
+          "description": "Host HTTP port of consul cluster.",
+          "type": "string",
+          "default": "8500"
+        }
+      },
+      "required": [
+        "location",
+        "port"
+      ]
+    },
+    "couchdb": {
+      "description": "Linked CouchDB properties",
+      "type": "object",
+      "properties": {
+        "location": {
+          "description": "Host location of CouchDB cluster.",
+          "type": "string",
+          "default": "whisk-couchdb.marathon.mesos"
+        },
+        "port": {
+          "description": "Host HTTP port of CouchDB cluster (default = 5984).",
+          "type": "string",
+          "default": "5984"
+        },
+        "username": {
+          "description": "CouchDB username.",
+          "type": "string",
+          "default": "whisk_admin"
+        },
+        "password": {
+          "description": "CouchDB password.",
+          "type": "string",
+          "default": "some_passw0rd"
+        }
+      },
+      "required": [
+        "location",
+        "port",
+        "username",
+        "password"
+      ]
+    },
+    "kafka": {
+      "description": "Linked Kafka properties",
+      "type": "object",
+      "properties": {
+        "location": {
+          "description": "Host location of Kafka cluster.",
+          "type": "string",
+          "default": "broker-0.kafka.mesos"
+        },
+        "port": {
+          "description": "Host HTTP port of Kafka cluster.",
+          "type": "string",
+          "default": "9092"
+        }
+      },
+      "required": [
+        "location",
+        "port"
+      ]
+    },
+    "apigateway": {
+      "description": "Linked API Gateway properties",
+      "type": "object",
+      "properties": {
+        "location": {
+          "description": "Host location of API Gateway cluster.",
+          "type": "string",
+          "default": "apigateway.marathon.mesos"
+        },
+        "port": {
+          "description": "Host HTTP port of API Gateway cluster.",
+          "type": "string",
+          "default": "80"
+        }
+      },
+      "required": [
+        "location",
+        "port"
+      ]
+    }
+  }
+}
diff --git a/dcos-universe/repo/packages/W/whisk-invoker/0/marathon.json.mustache b/dcos-universe/repo/packages/W/whisk-invoker/0/marathon.json.mustache
new file mode 100644
index 0000000..e20f215
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-invoker/0/marathon.json.mustache
@@ -0,0 +1,111 @@
+{
+  "id": "/{{service.name}}",
+  "cpus": {{service.cpus}},
+  "mem": {{service.mem}},
+  "instances": {{service.instances}},
+  "constraints": [
+    ["hostname", "UNIQUE"]
+  ],
+  "env": {
+
+    "WHISK_VERSION_NAME": "local",
+    "WHISK_VERSION_DATE": "09/01/2016",
+    "WHISK_VERSION_BUILDNO": "latest",
+    "WHISK_LOGS_DIR": "/logs",
+
+    "SERVICE_CHECK_HTTP": "/ping",
+    "SERVICE_CHECK_TIMEOUT": "2s",
+    "SERVICE_CHECK_INTERVAL": "15s",
+
+    "DB_PREFIX": "local_",
+    "DB_WHISK_ACTIONS": "local_whisks",
+    "DB_WHISK_AUTHS": "local_subjects",
+    "DB_WHISK_ACTIVATIONS": "local_activations",
+
+    "KAFKA_NUMPARTITIONS": "1",
+
+    "DEFAULTLIMITS_ACTIONS_INVOKES_PERMINUTE": "60000",
+    "DEFAULTLIMITS_ACTIONS_INVOKES_CONCURRENT": "5000",
+    "DEFAULTLIMITS_TRIGGERS_FIRES_PERMINUTE": "60000",
+    "DEFAULTLIMITS_ACTIONS_INVOKES_CONCURRENTINSYSTEM": "5000",
+    "DEFAULTLIMITS_ACTIONS_SEQUENCE_MAXLENGTH": "20",
+    "LIMITS_ACTIONS_INVOKES_PERHOUR": "3600000",
+    "LIMITS_ACTIONS_INVOKES_PERMINUTE": "60000",
+    "LIMITS_ACTIONS_INVOKES_CONCURRENT": "5000",
+    "LIMITS_TRIGGERS_FIRES_PERMINUTE": "60000",
+    "LIMITS_ACTIONS_INVOKES_CONCURRENTINSYSTEM": "5000",
+
+    "COMPONENT_NAME": "invoker",
+    "PORT": "{{service.port}}",
+
+    "CONSULSERVER_HOST": "{{consul.location}}",
+    "CONSUL_HOST_PORT4": "{{consul.port}}",
+
+    "KAFKA_HOST": "{{kafka.location}}",
+    "KAFKA_HOST_PORT": "{{kafka.port}}",
+
+    "DB_PROVIDER": "CouchDB",
+    "DB_PROTOCOL": "http",
+    "DB_PORT": "{{couchdb.port}}",
+    "DB_HOST": "{{couchdb.location}}",
+    "DB_USERNAME": "{{couchdb.username}}",
+    "DB_PASSWORD": "{{couchdb.password}}",
+
+    "EDGE_HOST": "{{apigateway.location}}",
+    "WHISK_API_HOST_NAME": "{{apigateway.location}}",
+    "EDGE_HOST_APIPORT": "{{apigateway.port}}",
+
+    "RUNTIMES_MANIFEST": "{\"runtimes\":{\"nodejs\":[{\"kind\":\"nodejs\",\"image\":{\"name\":\"nodejsaction\"},\"deprecated\":true},{\"kind\":\"nodejs:6\",\"default\":true,\"image\":{\"name\":\"nodejs6action\"}}],\"python\":[{\"kind\":\"python\",\"image\":{\"name\":\"python2action\"}},{\"kind\":\"python:2\",\"default\":true,\"image\":{\"name\":\"python2action\"}},{\"kind\":\"python:3\",\"image\":{\"name\":\"python3action\"}}],\"swift\":[{\"kind\":\"swift\",\"image\":{\"name\":\"swiftaction\"},\"deprecated\":true},{\"kind\":\"swift:3\",\"default\":true,\"image\":{\"name\":\"swift3action\"}}],\"java\":[{\"kind\":\"java\",\"attached\":{\"attachmentName\":\"jarfile\",\"attachmentType\":\"application\/java-archive\"},\"sentinelledLogs\":false,\"requireMain\":true,\"image\":{\"name\":\"java8action\"},\"default\":true}]}}",
+
+    "DOCKER_REGISTRY": "",
+    "DOCKER_IMAGE_PREFIX": "{{service.docker_image_prefix}}",
+    "INVOKER_CONTAINER_NETWORK": "bridge",
+    "INVOKER_INSTANCES": "1"
+  },
+  "container": {
+    "type": "DOCKER",
+    "docker": {
+      "image": "{{resource.assets.container.docker.whisk-invoker}}",
+      "network": "BRIDGE",
+      "portMappings": [
+        {
+          "containerPort": 8085,
+          "hostPort": {{service.port}},
+          "servicePort": 0,
+          "protocol": "tcp"
+        }
+      ]
+    },
+    "volumes": [
+      {
+        "containerPath": "/logs",
+        "hostPath": "~/tmp/openwhisk/invoker/logs",
+        "mode": "RW"
+      },
+      {
+        "containerPath": "/var/run/docker.sock",
+        "hostPath": "/var/run/docker.sock",
+        "mode": "RW"
+      },
+      {
+        "containerPath": "/containers",
+        "hostPath": "/var/lib/docker/containers",
+        "mode": "RW"
+      }
+    ]
+  },
+  "cmd": "/bin/sh -c \"/invoker/bin/invoker `hostname | tr -dc '0-9'` >> /dev/stderr\"",
+  "healthChecks": [
+    {
+      "path": "/ping",
+      "protocol": "HTTP",
+      "gracePeriodSeconds": 30,
+      "intervalSeconds": 15,
+      "timeoutSeconds": 2,
+      "maxConsecutiveFailures": 3
+    }
+  ],
+  "labels": {
+    "DCOS_SERVICE_NAME": "{{service.name}}"
+  }
+}
diff --git a/dcos-universe/repo/packages/W/whisk-invoker/0/package.json b/dcos-universe/repo/packages/W/whisk-invoker/0/package.json
new file mode 100644
index 0000000..9968eb1
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-invoker/0/package.json
@@ -0,0 +1,19 @@
+{
+  "description": "OpenWhisk Invoker service running on DC/OS",
+  "framework": true,
+  "maintainer": "dunguyen@adobe.com",
+  "minDcosReleaseVersion": "1.8",
+  "name": "whisk-invoker",
+  "packagingVersion": "3.0",
+  "postInstallNotes": "DC/OS OpenWhisk invoker has been successfully installed!",
+  "postUninstallNotes": "DC/OS OpenWhisk invoker service has been uninstalled.",
+  "preInstallNotes": "OpenWhisk Invoker requires Kafka, CouchDB, Consul, Registrator and APIGateway already installed in the same DC/OS cluster.",
+  "selected": true,
+  "tags": [
+    "openwhisk",
+    "invoker",
+    "serverless",
+    "lambda"
+  ],
+  "version": "0.1"
+}
diff --git a/dcos-universe/repo/packages/W/whisk-invoker/0/resource.json b/dcos-universe/repo/packages/W/whisk-invoker/0/resource.json
new file mode 100644
index 0000000..e1f5067
--- /dev/null
+++ b/dcos-universe/repo/packages/W/whisk-invoker/0/resource.json
@@ -0,0 +1,14 @@
+{
+  "images": {
+    "icon-small": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-small.png?raw=true",
+    "icon-medium": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-medium.png?raw=true",
+    "icon-large": "https://github.com/dcos/dcos-ui/blob/master/plugins/services/src/img/icon-service-default-large.png?raw=true"
+  },
+  "assets": {
+    "container": {
+      "docker": {
+        "whisk-invoker": "openwhisk/invoker"
+      }
+    }
+  }
+}
diff --git a/dcos-universe/scripts/0-validate-version.sh b/dcos-universe/scripts/0-validate-version.sh
new file mode 100755
index 0000000..3239a6e
--- /dev/null
+++ b/dcos-universe/scripts/0-validate-version.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -o errexit -o nounset -o pipefail
+
+SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )";
+UNIVERSE_DIR="$SCRIPTS_DIR/..";
+SCHEMA_DIR=$UNIVERSE_DIR/repo/meta/schema
+
+echo "Validating version...";
+jsonschema -i $UNIVERSE_DIR/repo/meta/version.json $SCHEMA_DIR/version-schema.json;
+
+echo "OK";
+
+
diff --git a/dcos-universe/scripts/build.sh b/dcos-universe/scripts/build.sh
new file mode 100755
index 0000000..770f142
--- /dev/null
+++ b/dcos-universe/scripts/build.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -o errexit -o nounset -o pipefail
+
+SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )";
+REPO_BASE_DIR=${SCRIPTS_DIR}/..
+
+echo "Building the universe!";
+
+mkdir -p ${REPO_BASE_DIR}/target/;
+
+$SCRIPTS_DIR/"0-validate-version.sh";
+$SCRIPTS_DIR/"validate-packages.py";
+$SCRIPTS_DIR/gen-universe.py --repository=${REPO_BASE_DIR}/repo/packages/ --out-dir=${REPO_BASE_DIR}/target/;
diff --git a/dcos-universe/scripts/gen-universe.py b/dcos-universe/scripts/gen-universe.py
new file mode 100755
index 0000000..142f54e
--- /dev/null
+++ b/dcos-universe/scripts/gen-universe.py
@@ -0,0 +1,488 @@
+#!/usr/bin/env python3
+
+from distutils.version import LooseVersion
+import argparse
+import base64
+import collections
+import itertools
+import json
+import pathlib
+import shutil
+import sys
+import tempfile
+import zipfile
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description='This script generates all of the universe objects from '
+        'the universe repository. The files created in --out-dir are: '
+        'universe.json.')
+    parser.add_argument(
+        '--repository',
+        required=True,
+        type=pathlib.Path,
+        help='Path to the top level package directory. E.g. repo/packages')
+    parser.add_argument(
+        '--out-dir',
+        dest='outdir',
+        required=True,
+        type=pathlib.Path,
+        help='Path to the directory to use to store all universe objects')
+    args = parser.parse_args()
+
+    if not args.outdir.is_dir():
+        print('The path in --out-dir [{}] is not a directory. Please create it '
+              'before running this script.'.format(args.outdir))
+        return
+
+    if not args.repository.is_dir():
+        print('The path in --repository [{}] is not a directory.'.format(
+            args.repository))
+        return
+
+    packages = [
+        generate_package_from_path(
+            args.repository,
+            package_name,
+            release_version)
+        for package_name, release_version
+        in enumerate_dcos_packages(args.repository)
+    ]
+
+    # Render entire universe
+    with (args.outdir / 'universe.json').open('w') as universe_file:
+        json.dump({'packages': packages}, universe_file)
+
+    # Render empty json
+    with (args.outdir / 'repo-empty-v3.json').open('w') as universe_file:
+        json.dump({'packages': []}, universe_file)
+
+    # Render 1.8 json
+    with (args.outdir / 'repo-up-to-1.8.json').open('w') as universe_file:
+        json.dump(
+            {'packages': list(filter(filter_1_8, packages))},
+            universe_file)
+
+    # Render zip universe for 1.6.1
+    with tempfile.NamedTemporaryFile() as temp_file:
+        with zipfile.ZipFile(temp_file, mode='w') as zip_file:
+            render_universe_zip(
+                zip_file,
+                filter(filter_1_6_1, packages)
+            )
+
+        shutil.copy(temp_file.name, str(args.outdir / 'repo-up-to-1.6.1.zip'))
+
+    # Render zip universe for 1.7
+    with tempfile.NamedTemporaryFile() as temp_file:
+        with zipfile.ZipFile(temp_file, mode='w') as zip_file:
+            render_universe_zip(
+                zip_file,
+                filter(filter_1_7, packages)
+            )
+
+        shutil.copy(temp_file.name, str(args.outdir / 'repo-up-to-1.7.zip'))
+
+
+def filter_1_6_1(package):
+    """Predicate for checking for 1.6.1 or less packages
+
+    :param package: package dictionary
+    :type package: dict
+    :rtype: bool
+    """
+
+    package_version = LooseVersion(
+        package.get('minDcosReleaseVersion', '0.0')
+    )
+
+    filter_version = LooseVersion("1.6.1")
+
+    return package_version <= filter_version
+
+
+def filter_1_7(package):
+    """Predicate for checking for 1.7 or less packages
+
+    :param package: package dictionary
+    :type package: dict
+    :rtype: bool
+    """
+
+    package_version = LooseVersion(
+        package.get('minDcosReleaseVersion', '0.0')
+    )
+
+    filter_version = LooseVersion("1.7")
+
+    return package_version <= filter_version
+
+
+def filter_1_8(package):
+    """Predicate for checking for 1.8 or less packages
+
+    :param package: package dictionary
+    :type package: dict
+    :rtype: bool
+    """
+
+    package_version = LooseVersion(
+        package.get('minDcosReleaseVersion', '0.0')
+    )
+
+    filter_version = LooseVersion("1.8")
+
+    return package_version <= filter_version
+
+
+def package_path(root, package_name, release_version):
+    """Returns the path to the package directory
+
+    :param root: path to the root of the repository
+    :type root: pathlib.Path
+    :param package_name: name of the package
+    :type package_name: str
+    :param release_version: package release version
+    :type release_version: int
+    :rtype: pathlib.Path
+    """
+
+    return (root /
+            package_name[:1].upper() /
+            package_name /
+            str(release_version))
+
+
+def read_package(path):
+    """Reads the package.json as a dict
+
+    :param path: path to the package
+    :type path: pathlib.Path
+    :rtype: dict
+    """
+
+    path = path / 'package.json'
+
+    with path.open() as file_object:
+        return json.load(file_object)
+
+
+def read_resource(path):
+    """Reads the resource.json as a dict
+
+    :param path: path to the package
+    :type path: pathlib.Path
+    :rtype: dict | None
+    """
+
+    path = path / 'resource.json'
+
+    if path.is_file():
+        with path.open() as file_object:
+            return json.load(file_object)
+
+
+def read_marathon_template(path):
+    """Reads the marathon.json.mustache as a base64 encoded string
+
+    :param path: path to the package
+    :type path: pathlib.Path
+    :rtype: str | None
+    """
+
+    path = path / 'marathon.json.mustache'
+
+    if path.is_file():
+        with path.open(mode='rb') as file_object:
+            return base64.standard_b64encode(file_object.read()).decode()
+
+
+def read_config(path):
+    """Reads the resource.json as a dict
+
+    :param path: path to the package
+    :type path: pathlib.Path
+    :rtype: dict | None
+    """
+
+    path = path / 'config.json'
+
+    if path.is_file():
+        with path.open() as file_object:
+            # Load config file into a OrderedDict to preserve order
+            return json.load(
+                file_object,
+                object_pairs_hook=collections.OrderedDict
+            )
+
+
+def read_command(path):
+    """Reads the command.json as a dict
+
+    :param path: path to the package
+    :type path: pathlib.Path
+    :rtype: dict | None
+    """
+
+    path = path / 'command.json'
+
+    if path.is_file():
+        with path.open() as file_object:
+            return json.load(file_object)
+
+
+def generate_package_from_path(root, package_name, release_version):
+    """Returns v3 package metadata for the specified package
+
+    :param root: path to the root of the repository
+    :type root: pathlib.Path
+    :param package_name: name of the package
+    :type package_name: str
+    :param release_version: package release version
+    :type release_version: int
+    :rtype: dict
+    """
+
+    path = package_path(root, package_name, release_version)
+    return generate_package(
+        release_version,
+        read_package(path),
+        resource=read_resource(path),
+        marathon_template=read_marathon_template(path),
+        config=read_config(path),
+        command=read_command(path))
+
+
+def generate_package(
+        release_version,
+        package,
+        resource,
+        marathon_template,
+        config,
+        command):
+    """Returns v3 package object for package. See
+    repo/meta/schema/v3-repo-schema.json
+
+    :param release_version: package release version
+    :type release_version: int
+    :param package: content of package.json
+    :type package: dict
+    :param resource: content of resource.json
+    :type resource: dict | None
+    :param marathon_template: content of marathon.json.template as base64
+    :type marathon_template: str | None
+    :param config: content of config.json
+    :type config: dict | None
+    :param command: content of command.json
+    :type command: dict | None
+    :rtype: dict
+    """
+
+    package = package.copy()
+    package['releaseVersion'] = release_version
+
+    if resource:
+        package['resource'] = resource
+    if marathon_template:
+        package['marathon'] = {
+            'v2AppMustacheTemplate': marathon_template
+        }
+    if config:
+        package['config'] = config
+    package['command'] = command
+
+    return package
+
+
+def enumerate_dcos_packages(packages_path):
+    """Enumarate all of the package and release version to include
+
+    :param packages_path: the path to the root of the packages
+    :type pacakges_path: str
+    :returns: generator of package name and release version
+    :rtype: gen((str, int))
+    """
+
+    for letter_path in packages_path.iterdir():
+        assert len(letter_path.name) == 1 and letter_path.name.isupper()
+        for package_path in letter_path.iterdir():
+            for release_version in package_path.iterdir():
+                yield (package_path.name, int(release_version.name))
+
+
+def render_universe_zip(zip_file, packages):
+    """Populates a zipfile from a list of universe v3 packages. This function
+    creates directories to be backwards compatible with legacy Cosmos.
+
+    :param zip_file: zipfile where we need to write the packages
+    :type zip_file: zipfile.ZipFile
+    :param packages: list of packages
+    :type packages: [dict]
+    :rtype: None
+    """
+
+    packages = sorted(
+        packages,
+        key=lambda package: (package['name'], package['releaseVersion']))
+
+    root = pathlib.Path('universe')
+
+    create_dir_in_zip(zip_file, root)
+
+    create_dir_in_zip(zip_file, root / 'repo')
+
+    create_dir_in_zip(zip_file, root / 'repo' / 'meta')
+    zip_file.writestr(
+        str(root / 'repo' / 'meta' / 'index.json'),
+        json.dumps(create_index(packages)))
+
+    zip_file.writestr(
+        str(root / 'repo' / 'meta' / 'version.json'),
+        json.dumps({'version': '2.0.0'}))
+
+    packagesDir = root / 'repo' / 'packages'
+    create_dir_in_zip(zip_file, packagesDir)
+
+    currentLetter = ''
+    currentPackageName = ''
+    for package in packages:
+        if currentLetter != package['name'][:1].upper():
+            currentLetter = package['name'][:1].upper()
+            create_dir_in_zip(zip_file, packagesDir / currentLetter)
+
+        if currentPackageName != package['name']:
+            currentPackageName = package['name']
+            create_dir_in_zip(
+                zip_file,
+                packagesDir / currentLetter / currentPackageName)
+
+        package_directory = (
+            packagesDir /
+            currentLetter /
+            currentPackageName /
+            str(package['releaseVersion'])
+        )
+        create_dir_in_zip(zip_file, package_directory)
+
+        write_package_in_zip(zip_file, package_directory, package)
+
+
+def create_dir_in_zip(zip_file, directory):
+    """Create a directory in a zip file
+
+    :param zip_file: zip file where the directory will get created
+    :type zip_file: zipfile.ZipFile
+    :param directory: path for the directory
+    :type directory: pathlib.Path
+    :rtype: None
+    """
+
+    zip_file.writestr(str(directory) + '/', b'')
+
+
+def write_package_in_zip(zip_file, path, package):
+    """Write packages files in the zip file
+
+    :param zip_file: zip file where the files will get created
+    :type zip_file: zipfile.ZipFile
+    :param path: path for the package directory. E.g.
+                 universe/repo/packages/M/marathon/0
+    :type path: pathlib.Path
+    :param package: package information dictionary
+    :type package: dict
+    :rtype: None
+    """
+
+    package = package.copy()
+    package.pop('releaseVersion')
+    package.pop('minDcosReleaseVersion', None)
+    package['packagingVersion'] = "2.0"
+
+    resource = package.pop('resource', None)
+    if resource:
+        cli = resource.pop('cli', None)
+        if cli and 'command' not in package:
+            print(('WARNING: Removing binary CLI from ({}, {}) without a '
+                  'Python CLI').format(package['name'], package['version']))
+        zip_file.writestr(
+            str(path / 'resource.json'),
+            json.dumps(resource))
+
+    marathon_template = package.pop(
+        'marathon',
+        {}
+    ).get(
+        'v2AppMustacheTemplate'
+    )
+    if marathon_template:
+        zip_file.writestr(
+            str(path / 'marathon.json.mustache'),
+            base64.standard_b64decode(marathon_template))
+
+    config = package.pop('config', None)
+    if config:
+        zip_file.writestr(
+            str(path / 'config.json'),
+            json.dumps(config))
+
+    command = package.pop('command', None)
+    if command:
+        zip_file.writestr(
+            str(path / 'command.json'),
+            json.dumps(command))
+
+    zip_file.writestr(
+        str(path / 'package.json'),
+        json.dumps(package))
+
+
+def create_index(packages):
+    """Create an index for all of the packages
+
+    :param packages: list of packages
+    :type packages: [dict]
+    :rtype: dict
+    """
+
+    index = {
+        'version': '2.0.0',
+        'packages': [
+            create_index_entry(same_packages)
+            for _, same_packages
+            in itertools.groupby(packages, key=lambda package: package['name'])
+        ]
+    }
+
+    return index
+
+
+def create_index_entry(packages):
+    """Create index entry from packages with the same name.
+
+    :param packages: list of packages with the same name
+    :type packages: [dict]
+    :rtype: dict
+    """
+
+    entry = {
+        'versions': {}
+    }
+
+    for package in packages:
+        entry.update({
+            'name': package['name'],
+            'currentVersion': package['version'],
+            'description': package['description'],
+            'framework': package.get('framework', False),
+            'tags': package['tags'],
+            'selected': package.get('selected', False)
+        })
+
+        entry['versions'][package['version']] = str(package['releaseVersion'])
+
+    return entry
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/dcos-universe/scripts/generate-config-reference.py b/dcos-universe/scripts/generate-config-reference.py
new file mode 100755
index 0000000..079157d
--- /dev/null
+++ b/dcos-universe/scripts/generate-config-reference.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+"""This script builds a Markdown file containing configuration references for
+all packages (and all package versions) contained in the Mesosphere DC/OS
+Universe repository. It outputs a single file, 'config-reference.md' in the
+current working directory.
+
+  Usage:  ./generate-config-reference.py [/path/to/universe/repo/packages]
+
+"""
+import json
+import os
+import sys
+
+
+def find_config_files(path):
+    config_files = []
+
+    for root, dirs, files in os.walk(path):
+        for f in files:
+            if f == 'config.json':
+                config_files.append(os.path.join(root, f))
+
+    return config_files
+
+
+def main(path):
+    files = find_config_files(path)
+    outfile = open(os.path.join(os.getcwd(), 'config-reference.md'), 'w')
+    outfile.write("# DC/OS Universe Package Configuration Reference\n\n")
+
+    for f in files:
+        with open(f, 'r') as config:
+            package_name = f.split('/')[-3]
+            package_version = f.split('/')[-2]
+            outfile.write("## {} version {}\n\n".format(package_name, package_version))
+            props = json.loads(config.read())['properties']
+
+            for key, value in props.items():
+                if key == "properties":
+                    outfile.write("*Errors encountered when processing config properties. Not all properties may be listed here. Please verify the structure of this package and package version.*\n\n")
+                    continue
+
+                outfile.write("### {} configuration properties\n\n".format(key))
+                outfile.write("| Property | Type | Description | Default Value |\n")
+                outfile.write("|----------|------|-------------|---------------|\n")
+
+                for _, prop in value.items():
+                    if type(prop) is not dict:
+                        continue
+                    for key, details in prop.items():
+                        prop = key
+
+                        try:
+                            typ = details['type']
+                        except KeyError:
+                            typ = "*No type provided.*"
+
+                        try:
+                            desc = details['description']
+                        except KeyError:
+                            desc = "*No description provided.*"
+
+                        try:
+                            default = "`{}`".format(details['default'])
+                            if default == "``":
+                                default = "*Empty string.*"
+                        except KeyError:
+                            default = "*No default.*"
+
+                        outfile.write("| {prop} | {typ} | {desc} | {default} |\n".format(
+                            prop=prop, desc=desc, typ=typ, default=default))
+
+                outfile.write("\n")
+
+    outfile.close()
+
+if __name__ == '__main__':
+    if len(sys.argv) == 2:
+        path = sys.argv[1]
+    else:
+        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../repo/packages')
+
+    main(path)
diff --git a/dcos-universe/scripts/install-git-hooks.sh b/dcos-universe/scripts/install-git-hooks.sh
new file mode 100755
index 0000000..0071ce9
--- /dev/null
+++ b/dcos-universe/scripts/install-git-hooks.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+set -o errexit -o nounset -o pipefail
+
+SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )";
+UNIVERSE_DIR=$SCRIPTS_DIR/..
+HOOKS_DIR=$UNIVERSE_DIR/hooks
+GIT_HOOKS_DIR=$UNIVERSE_DIR/.git/hooks
+
+echo "Installing git hooks...";
+
+for file in $(ls $HOOKS_DIR); do
+  echo "Copying $file";
+  cp "$HOOKS_DIR/$file" $GIT_HOOKS_DIR/;
+  chmod +x "$GIT_HOOKS_DIR/$file";
+done
+
+echo "OK";
+
diff --git a/dcos-universe/scripts/json_dup_key_check.py b/dcos-universe/scripts/json_dup_key_check.py
new file mode 100755
index 0000000..fd192d9
--- /dev/null
+++ b/dcos-universe/scripts/json_dup_key_check.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import json
+
+
+class DuplicatedKeysException(Exception):
+    pass
+
+
+def json_checker(pair):
+    ret = {}
+    for key, value in pair:
+        if key in ret:
+            raise DuplicatedKeysException(
+                "Duplicate key {!r} in json document".format(key))
+        else:
+            ret[key] = value
+    return ret
+
+if len(sys.argv) != 2:
+    sys.stderr.write(
+        "Syntax: {} path/to/file.json\n".format(os.path.basename(__file__)))
+    sys.exit(1)
+
+try:
+    json.load(open(sys.argv[1]), object_pairs_hook=json_checker)
+except DuplicatedKeysException as e:
+    sys.stderr.write("Error validating %s: %s\n" % (sys.argv[1], e.args[0]))
+    sys.exit(1)
diff --git a/dcos-universe/scripts/local-universe.py b/dcos-universe/scripts/local-universe.py
new file mode 100755
index 0000000..4a5b636
--- /dev/null
+++ b/dcos-universe/scripts/local-universe.py
@@ -0,0 +1,314 @@
+#!/usr/bin/env python3
+
+import argparse
+import concurrent.futures
+import contextlib
+import fnmatch
+import json
+import os
+import pathlib
+import shutil
+import subprocess
+import sys
+import tempfile
+import time
+import urllib.error
+import urllib.parse
+import urllib.request
+import zipfile
+
+HTTP_ROOT = "http://master.mesos:8082/"
+DOCKER_ROOT = "master.mesos:5000"
+
+def main():
+    # Docker writes files into the tempdir as root, you need to be running
+    # the script as root to clean these up successfully.
+    if os.getuid() != 0:
+        print("You must run this as root, please `sudo` first.")
+        sys.exit(1)
+
+    # jsonschema is required by the universe build process, make sure it is
+    # installed before running.
+    if not shutil.which("jsonschema"):
+        print("You must first install jsonschema (pip install jsonschema).")
+        sys.exit(1)
+
+    # cosmos requires directories to be saved. python does it only sometimes.
+    # Use zip to make sure it works.
+    if not shutil.which("zip"):
+        print("You must first install `zip`.")
+        sys.exit(1)
+
+    parser = argparse.ArgumentParser(
+        description='This script is able to download the latest artifacts for '
+        'all of the packages in the Universe repository into a zipfile. It '
+        'uses a temporary file to store all of the artifacts as it downloads '
+        'them because of this it requires that your temporary filesystem has '
+        'enough space to store all of the artifact. You can control the path '
+        'to the temporary file by setting the TMPDIR environment variable. '
+        'E.g. TMPDIR=\'.\' ./scripts/local-universe.py ...')
+    parser.add_argument(
+        '--repository',
+        required=True,
+        help='Path to the top level package directory. E.g. repo/packages')
+    parser.add_argument(
+        '--include',
+        default='',
+        help='Command separated list of packages to include. If this option '
+        'is not specified then all packages are downloaded. E.g. '
+        '--include="marathon,chronos"')
+    parser.add_argument(
+        '--selected',
+        action='store_true',
+        default=False,
+        help='Set this to include only selected packages')
+    args = parser.parse_args()
+
+    package_names = [name for name in args.include.split(',') if name != '']
+
+    with tempfile.TemporaryDirectory() as dir_path, \
+            run_docker_registry(dir_path / pathlib.Path("registry")):
+
+        http_artifacts = dir_path / pathlib.Path("http")
+        docker_artifacts = dir_path / pathlib.Path("registry")
+        repo_artifacts = dir_path / pathlib.Path("universe/repo/packages")
+
+        os.makedirs(str(http_artifacts))
+        os.makedirs(str(repo_artifacts))
+
+        failed_packages = []
+        def handle_package(opts):
+            package, path = opts
+            try:
+                prepare_repository(package, path, pathlib.Path(args.repository),
+                    repo_artifacts)
+
+                for url, archive_path in enumerate_http_resources(package, path):
+                    add_http_resource(http_artifacts, url, archive_path)
+
+                for name in enumerate_docker_images(path):
+                    download_docker_image(name)
+                    upload_docker_image(name)
+            except (subprocess.CalledProcessError, urllib.error.HTTPError):
+                print('MISSING ASSETS: {}'.format(package))
+                remove_package(package, dir_path)
+                failed_packages.append(package)
+
+            return package
+
+        with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
+            for package in executor.map(handle_package,
+                    enumerate_dcos_packages(
+                        pathlib.Path(args.repository),
+                        package_names,
+                        args.selected)):
+                print("Completed: {}".format(package))
+
+        build_repository(pathlib.Path(
+            os.path.dirname(os.path.realpath(__file__)), '..', 'scripts'),
+            pathlib.Path(args.repository),
+            pathlib.Path(dir_path, 'universe'))
+
+        build_universe_docker(pathlib.Path(dir_path))
+
+        if failed_packages:
+            print("Errors: {}".format(failed_packages))
+            print("These packages are not included in the image.")
+
+
+def enumerate_dcos_packages(packages_path, package_names, only_selected):
+    """Enumarate all of the package and revision to include
+
+    :param packages_path: the path to the root of the packages
+    :type pacakges_path: str
+    :param package_names: list of package to include. empty list means all
+                         packages
+    :type package_names: [str]
+    :param only_selected: filter the list of packages to only ones that are
+                          selected
+    :type only_selected: boolean
+    :returns: generator of package name and revision
+    :rtype: gen((str, str))
+    """
+
+    for letter_path in packages_path.iterdir():
+        assert len(letter_path.name) == 1 and letter_path.name.isupper()
+        for package_path in letter_path.iterdir():
+
+            largest_revision = max(
+                package_path.iterdir(),
+                key=lambda revision: int(revision.name))
+
+
+            if only_selected:
+                with (largest_revision / 'package.json').open() as json_file:
+                    if json.load(json_file).get('selected', False):
+                        yield (package_path.name, largest_revision)
+
+            elif not package_names or package_path.name in package_names:
+                # Enumerate package if list is empty or package name in list
+                yield (package_path.name, largest_revision)
+
+
+def enumerate_http_resources(package, package_path):
+    with (package_path / 'resource.json').open() as json_file:
+        resource = json.load(json_file)
+
+    for name, url in resource.get('images', {}).items():
+        if name != 'screenshots':
+            yield url, pathlib.Path(package, 'images')
+
+    for name, url in resource.get('assets', {}).get('uris', {}).items():
+        yield url, pathlib.Path(package, 'uris')
+
+    command_path = (package_path / 'command.json')
+    if command_path.exists():
+        with command_path.open() as json_file:
+            commands = json.load(json_file)
+
+        for url in commands.get("pip", []):
+            yield url, pathlib.Path(package, 'commands')
+
+
+def enumerate_docker_images(package_path):
+    with (package_path / 'resource.json').open() as json_file:
+        resource = json.load(json_file)
+
+    dockers = resource.get('assets', {}).get('container', {}).get('docker', {})
+
+    return (name for _, name in dockers.items())
+
+
+@contextlib.contextmanager
+def run_docker_registry(volume_path):
+    print('Start docker registry.')
+    command = [ 'docker', 'run', '-d', '-p', '5000:5000', '--name',
+        'registry', '-v', '{}:/var/lib/registry'.format(volume_path),
+        'registry:2.4.0']
+
+    subprocess.check_call(command)
+
+    try:
+        yield
+    finally:
+        print('Stopping docker registry.')
+        command = [ 'docker', 'rm', '-f', 'registry']
+        subprocess.call(command)
+
+
+def download_docker_image(name):
+    print('Pull docker images: {}'.format(name))
+    command = ['docker', 'pull', name]
+
+    subprocess.check_call(command)
+
+
+def format_image_name(host, name):
+    # Probably has a hostname at the front, get rid of it.
+    if '.' in name.split(':')[0]:
+        return '{}/{}'.format(host, "/".join(name.split("/")[1:]))
+
+    return '{}/{}'.format(host, name)
+
+def upload_docker_image(name):
+    print('Pushing docker image: {}'.format(name))
+    command = ['docker', 'tag', name,
+        format_image_name('localhost:5000', name)]
+
+    subprocess.check_call(command)
+
+    command = ['docker', 'push', format_image_name('localhost:5000', name)]
+
+    subprocess.check_call(command)
+
+
+def build_universe_docker(dir_path):
+    print('Building the universe docker container')
+    current_dir = pathlib.Path(
+        os.path.dirname(os.path.realpath(__file__)))
+    shutil.copyfile(
+        str(current_dir / '..' / 'docker' / 'local-universe' / 'Dockerfile'),
+        str(dir_path / 'Dockerfile'))
+
+    command = [ 'docker', 'build', '-t',
+        'mesosphere/universe:{:.0f}'.format(time.time()),
+        '-t', 'mesosphere/universe:latest', '.' ]
+
+    subprocess.check_call(command, cwd=str(dir_path))
+
+
+def add_http_resource(dir_path, url, base_path):
+    archive_path = (dir_path / base_path /
+        pathlib.Path(urllib.parse.urlparse(url).path).name)
+    print('Adding {} at {}.'.format(url, archive_path))
+    os.makedirs(str(archive_path.parent), exist_ok=True)
+    urllib.request.urlretrieve(url, str(archive_path))
+
+
+def prepare_repository(package, package_path, source_repo, dest_repo):
+    dest_path = dest_repo / package_path.relative_to(source_repo)
+    shutil.copytree(str(package_path), str(dest_path))
+
+    with (package_path / 'resource.json').open() as source_file, \
+            (dest_path / 'resource.json').open('w') as dest_file:
+        resource = json.load(source_file)
+
+        # Change the root for images (ignore screenshots)
+        if 'images' in resource:
+            resource["images"] = {
+                n: urllib.parse.urljoin(
+                    HTTP_ROOT, str(pathlib.PurePath(
+                        package, "images", pathlib.Path(uri).name)))
+                for n,uri in resource.get("images", {}).items() if 'icon' in n}
+
+        # Change the root for asset uris.
+        if 'assets' in resource:
+            resource["assets"]["uris"] = {
+                n: urllib.parse.urljoin(
+                    HTTP_ROOT, str(pathlib.PurePath(
+                        package, "uris", pathlib.Path(uri).name)))
+                for n, uri in resource["assets"].get("uris", {}).items()}
+
+        # Add the local docker repo prefix.
+        if 'container' in resource["assets"]:
+            resource["assets"]["container"]["docker"] = {
+                n: format_image_name(DOCKER_ROOT, image_name)
+                for n, image_name in resource["assets"]["container"].get(
+                    "docker", {}).items() }
+
+        json.dump(resource, dest_file, indent=4)
+
+    command_path = (package_path / 'command.json')
+    if not command_path.exists():
+        return
+
+    with command_path.open() as source_file, \
+            (dest_path / 'command.json').open('w') as dest_file:
+        command = json.load(source_file)
+
+        command['pip'] = [
+            urllib.parse.urljoin(
+                HTTP_ROOT, str(pathlib.PurePath(
+                    package, "commands", pathlib.Path(uri).name)))
+            for uri in command.get("pip", [])
+        ]
+        json.dump(command, dest_file, indent=4)
+
+
+def build_repository(scripts_dir, repo_dir, dest_dir):
+    shutil.copytree(str(scripts_dir), str(dest_dir / "scripts"))
+    shutil.copytree(str(repo_dir / '..' / 'meta'),
+        str(dest_dir / 'repo' / 'meta'))
+
+    command = [ "bash", "scripts/build.sh" ]
+    subprocess.check_call(command, cwd=str(dest_dir))
+
+
+def remove_package(package, base_dir):
+    for root, dirnames, filenames in os.walk(base_dir):
+        for dirname in fnmatch.filter(dirnames, package):
+            shutil.rmtree(os.path.join(root, dirname))
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/dcos-universe/scripts/push-containers.sh b/dcos-universe/scripts/push-containers.sh
new file mode 100755
index 0000000..4d54cbc
--- /dev/null
+++ b/dcos-universe/scripts/push-containers.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+docker_prefix=${DOCKER_PREFIX}
+docker_tag=${DOCKER_TAG:-snapshot-`date +'%Y%m%d-%H%M'`}
+docker_registry=${DOCKER_REGISTRY}
+openwhisk_prefix=${OPENWHISK_PREFIX:-whisk}
+
+echo "docker_prefix=" ${docker_prefix}
+echo "docker_tag=" ${docker_tag}
+echo "docker_registry=" ${docker_registry}
+
+function push_image {
+    final_tag=${docker_registry}${docker_prefix}/$2:${docker_tag}
+    echo "pushing $1 as ${docker_prefix}/$2 into ${final_tag}"
+
+    docker tag $1 ${final_tag}
+    docker push ${final_tag}
+}
+
+push_image whisk/controller whisk-controller
+push_image whisk/invoker whisk-invoker
+push_image adobeapiplatform/whisk-couchdb whisk-couchdb
+push_image adobeapiplatform/nodejs6action nodejs6action
diff --git a/dcos-universe/scripts/requirements/requirements.txt b/dcos-universe/scripts/requirements/requirements.txt
new file mode 100644
index 0000000..68c1bb9
--- /dev/null
+++ b/dcos-universe/scripts/requirements/requirements.txt
@@ -0,0 +1 @@
+jsonschema==2.4.0
diff --git a/dcos-universe/scripts/validate-packages.py b/dcos-universe/scripts/validate-packages.py
new file mode 100755
index 0000000..2f3ac13
--- /dev/null
+++ b/dcos-universe/scripts/validate-packages.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+
+import json
+import jsonschema
+import os
+import sys
+from distutils.version import LooseVersion
+
+SCRIPTS_DIR = os.path.dirname(os.path.realpath(__file__))
+UNIVERSE_DIR = os.path.join(SCRIPTS_DIR, "..")
+PKG_DIR = os.path.join(UNIVERSE_DIR, "repo/packages")
+SCHEMA_DIR = os.path.join(UNIVERSE_DIR, "repo/meta/schema")
+
+
+def eprint(*args, **kwargs):
+    print(*args, file=sys.stderr, **kwargs)
+
+def _get_json_schema(file_name):
+    with open(os.path.join(SCHEMA_DIR, file_name)) as f:
+        return json.loads(f.read())
+
+PACKAGE_JSON_SCHEMA = _get_json_schema('package-schema.json')
+COMMAND_JSON_SCHEMA = _get_json_schema('command-schema.json')
+CONFIG_JSON_SCHEMA = _get_json_schema('config-schema.json')
+V2_RESOURCE_JSON_SCHEMA = _get_json_schema('v2-resource-schema.json')
+V3_RESOURCE_JSON_SCHEMA = _get_json_schema('v3-resource-schema.json')
+
+
+def main():
+    # traverse prefix dirs ("A", "B", etc)
+    for letter in os.listdir(PKG_DIR):
+        prefix_path = os.path.join(PKG_DIR, letter)
+        # traverse each package dir directory (ie "cassandra")
+        for given_package in os.listdir(prefix_path):
+            package_path = os.path.join(prefix_path, given_package)
+            _validate_package(given_package, package_path)
+
+    eprint("\nEverything OK!")
+
+
+def _validate_package(given_package, path):
+    eprint("Validating {}...".format(given_package))
+    for rev in os.listdir(path):
+        _validate_revision(given_package, rev, os.path.join(path, rev))
+
+
+def _validate_revision(given_package, revision, path):
+    eprint("\tValidating revision {}...".format(revision))
+
+    # validate package.json
+    package_json_path = os.path.join(path, 'package.json')
+    eprint("\t\tpackage.json:", end='')
+    if not os.path.isfile(package_json_path):
+        sys.exit("\tERROR\n\nMissing required package.json file")
+    package_json = _validate_json(package_json_path, PACKAGE_JSON_SCHEMA)
+    eprint("\tOK")
+
+    packaging_version = package_json.get("packagingVersion", "2.0")
+
+    # validate command.json
+    command_json_path = os.path.join(path, 'command.json')
+    command_json = None
+    if os.path.isfile(command_json_path):
+        eprint("\t\tcommand.json:", end='')
+        command_json = _validate_json(command_json_path, COMMAND_JSON_SCHEMA)
+        eprint("\tOK")
+
+    # validate config.json
+    config_json_path = os.path.join(path, 'config.json')
+    if os.path.isfile(config_json_path):
+        eprint("\t\tconfig.json:", end='')
+        _validate_json(config_json_path, CONFIG_JSON_SCHEMA)
+        eprint("\tOK")
+
+    # validate existence of required marathon.json for v2
+    if packaging_version == "2.0":
+        marathon_json_path = os.path.join(path, 'marathon.json.mustache')
+        eprint("\t\tmarathon.json.mustache:", end='')
+        if not os.path.isfile(marathon_json_path):
+            sys.exit("\tERROR\n\nMissing required marathon.json.mustache")
+        eprint("\tOK")
+
+    # validate resource.json
+    resource_json_path = os.path.join(path, 'resource.json')
+    resource_json = None
+    if os.path.isfile(resource_json_path):
+        eprint("\t\tresource.json:", end='')
+        if packaging_version == "2.0":
+            resource_json = _validate_json(
+                resource_json_path,
+                V2_RESOURCE_JSON_SCHEMA)
+        else:
+            resource_json = _validate_json(
+                resource_json_path,
+                V3_RESOURCE_JSON_SCHEMA)
+        eprint("\tOK")
+
+    # Validate that we don't drop information during the conversion
+    oldPackage = LooseVersion(
+        package_json.get('minDcosReleaseVersion', "1.0")) < LooseVersion("1.8")
+    if (oldPackage and resource_json and 'cli' in resource_json and
+        command_json is None):
+        sys.exit('\tERROR\n\nA package with CLI specified in resource.json is '
+                 'only supported when minDcosReleaseVersion is greater than '
+                 '1.8.')
+
+def _validate_json(path, schema):
+        with open(path) as f:
+            data = json.loads(f.read())
+
+        _validate_jsonschema(data, schema)
+        return data
+
+
+def _validate_jsonschema(instance, schema):
+    validator = jsonschema.Draft4Validator(schema)
+    errors = list(validator.iter_errors(instance))
+    if len(errors) != 0:
+        sys.exit("\tERROR\n\nValidation error: {}".format(errors))
+
+
+if __name__ == '__main__':
+    sys.exit(main())


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services