CORD-1551 - updated maas makefiles and multi-stage dockerfile

Change-Id: I0bab86e0207edb12f553ddcfe040882f04f34f25
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..bc05a80
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,63 @@
+SUBDIRS := automation config-generator harvester ip-allocator provisioner switchq
+TARGETS := build publish clean test
+PUBLIC_IMAGES := consul@sha256:0dc990ff3c44d5b5395475bcc5ebdae4fc8b67f69e17942a8b9793b3df74d290
+I2 := consul:latest
+I3 := consul
+
+ANSIBLE_ARGS?=
+MAKE_CONFIG?=config.mk
+
+# [EXPERIMENTAL] Deployment via make is currently experimental
+DEPLOY_INVENTORY=deploy-inv
+DEPLOY_CONFIG=deploy-vars
+
+# expands to lists of of the form:
+# <target>_TARGETS := <subdir1>_<target> <subdir2>_<target>
+$(foreach TARGET, $(TARGETS), $(eval $(TARGET)_TARGETS := $(addsuffix _$(TARGET), $(SUBDIRS))))
+
+ifeq ($(realpath $(MAKE_CONFIG)),)
+$(info Makefile configuration not found, defaults will be used.)
+else
+$(info Using makefile configuration "$(MAKE_CONFIG)")
+endif
+
+define recursive_rule
+$1_$2:
+	$$(MAKE) MAKE_CONFIG=$(realpath $(MAKE_CONFIG)) CONFIG=$(realpath $(CONFIG)) -C $1 $2
+endef
+
+$(foreach SUBDIR, $(SUBDIRS), $(foreach TARGET, $(TARGETS), $(eval $(call recursive_rule,$(SUBDIR),$(TARGET)))))
+
+$(foreach TARGET, $(TARGETS), $(eval $(TARGET): $($(TARGET)_TARGETS)))
+
+include help.mk
+
+ifneq ($(realpath $(MAKE_CONFIG)),)
+include $(MAKE_CONFIG)
+endif
+
+define public_image_rules
+$2.image:
+	docker pull $1
+	@touch $$@
+
+$2.publish: $2.image
+	docker tag $1 $(DOCKER_REGISTRY)/$2:$(DEPLOY_DOCKER_TAG)
+	docker push $(DOCKER_REGISTRY)/$2:$(DEPLOY_DOCKER_TAG)
+	@touch $$@
+
+publish: $2.publish
+endef
+
+$(foreach PUBLIC_IMAGE, $(PUBLIC_IMAGES), $(eval $(call public_image_rules,$(PUBLIC_IMAGE),$(word 1,$(subst @, ,$(subst :, ,$(PUBLIC_IMAGE)))))))
+
+prime:
+	ansible-playbook -i $(DEPLOY_INVENTORY) --extra-vars=@$(DEPLOY_CONFIG) $(ANSIBLE_ARGS) prime-node.yml
+
+deploy: publish
+	ansible-playbook -i $(DEPLOY_INVENTORY) --extra-vars=@$(DEPLOY_CONFIG) $(ANSIBLE_ARGS) head-node.yml
+
+config:
+	@echo "hello"
+
+clean:
diff --git a/automation/Dockerfile b/automation/Dockerfile.automation
similarity index 81%
rename from automation/Dockerfile
rename to automation/Dockerfile.automation
index 32f1513..856c19f 100644
--- a/automation/Dockerfile
+++ b/automation/Dockerfile.automation
@@ -11,13 +11,17 @@
 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
-FROM golang:1.7-alpine
+FROM golang:1.7-alpine as builder
 MAINTAINER Open Networking Laboratory <info@onlab.us>
 
-RUN mkdir /service
 WORKDIR /go
 ADD . /go/src/gerrit.opencord.org/maas/cord-maas-automation
-RUN go build -o /service/entry-point gerrit.opencord.org/maas/cord-maas-automation
+RUN go build -o /build/entry-point gerrit.opencord.org/maas/cord-maas-automation
+
+FROM alpine:3.5
+MAINTAINER Open Networking Laboratory <info@onlab.us>
+
+COPY --from=builder /build/entry-point /service/entry-point
 
 LABEL org.label-schema.name="automation" \
       org.label-schema.description="Provides automation of the compute node deployment and provisioning process" \
diff --git a/automation/Dockerfile.release b/automation/Dockerfile.release
deleted file mode 100644
index 6e24b05..0000000
--- a/automation/Dockerfile.release
+++ /dev/null
@@ -1,26 +0,0 @@
-## Copyright 2017 Open Networking Laboratory
-##
-## Licensed under the Apache License, Version 2.0 (the "License");
-## you may not use this file except in compliance with the License.
-## You may obtain a copy of the License at
-##
-## http://www.apache.org/licenses/LICENSE-2.0
-##
-## Unless required by applicable law or agreed to in writing, software
-## distributed under the License is distributed on an "AS IS" BASIS,
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-## See the License for the specific language governing permissions and
-## limitations under the License.
-FROM alpine:3.5
-MAINTAINER Open Networking Laboratory <info@onlab.us>
-
-ADD entry-point /service/entry-point
-
-LABEL org.label-schema.name="automation" \
-      org.label-schema.description="Provides automation of the compute node deployment and provisioning process" \
-      org.label-schema.vcs-url="https://gerrit.opencord.org/maas" \
-      org.label-schema.vendor="Open Networking Laboratory" \
-      org.label-schema.schema-version="1.0"
-
-WORKDIR /service
-ENTRYPOINT ["/service/entry-point"]
diff --git a/automation/Makefile b/automation/Makefile
index 275f611..a5bee6e 100644
--- a/automation/Makefile
+++ b/automation/Makefile
@@ -1,38 +1,5 @@
-IMAGE_NAME=cord-maas-automation
-BINARY=entry-point
-BUILD_TAG=build
-PACKAGE_TAG=candidate
+IMAGES+=automation
 
-.PHONY: help
-help:
-	@echo "build     - create the binary"
-	@echo "package   - package the binary into a docker container"
-	@echo "clean     - remove tempory files and build artifacts"
-	@echo "help      - this message"
+automation.image: Dockerfile.automation *.go vendor/vendor.json
 
-BUILD_DATE=$(shell date -u +%Y-%m-%dT%TZ)
-VCS_REF=$(shell git log --pretty=format:%H -n 1)
-VCS_REF_DATE=$(shell git log --pretty=format:%cd --date=format:%FT%T%z -n 1)
-BRANCHES=$(shell repo --color=never --no-pager branches 2>/dev/null | wc -l)
-STATUS=$(shell repo --color=never --no-pager status . | tail -n +2 | wc -l)
-MODIFIED=$(shell test $(BRANCHES) -eq 0 && test $(STATUS) -eq 0 || echo "[modified]")
-BRANCH=$(shell repo --color=never --no-pager info -l -o | grep 'Manifest branch:' | awk '{print $$NF}')
-VERSION=$(BRANCH)$(MODIFIED)
-
-.PHONY: build
-build:
-	docker build -t $(IMAGE_NAME):$(BUILD_TAG) .
-
-.PHONY: package
-package:
-	$(eval BUILD_ID := $(shell docker create $(IMAGE_NAME):$(BUILD_TAG)))
-	$(eval BINDIR := $(shell mktemp -d))
-	docker cp $(BUILD_ID):/service/$(BINARY) $(BINDIR)/$(BINARY)
-	cp Dockerfile.release $(BINDIR)
-	docker build -f $(BINDIR)/Dockerfile.release -t $(IMAGE_NAME):$(PACKAGE_TAG) --label org.label-schema.build-date=$(BUILD_DATE) --label org.label-schema.vcs-ref=$(VCS_REF) --label org.label-schema.vcs-ref-date=$(VCS_REF_DATE) --label org.label-schema.version=$(VERSION) $(BINDIR)
-	docker rm -f $(BUILD_ID)
-	rm -r $(BINDIR)
-
-.PHONY: clean
-clean:
-	@echo ""
+include ../rules.mk
diff --git a/build.gradle b/build.gradle
index d6e3479..9d4b45c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -137,102 +137,72 @@
 
 task buildSwitchqImage(type: Exec) {
     workingDir 'switchq'
-    commandLine 'make', 'build', 'package'
-}
-
-task tagSwitchqImage(type: Exec) {
-   dependsOn buildSwitchqImage
-   commandLine "docker", 'tag', 'cord-maas-switchq:candidate', "$targetReg/cord-maas-switchq:$targetTag"
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'build'
 }
 
 task publishSwitchqImage(type: Exec) {
-    dependsOn tagSwitchqImage
-    commandLine "docker", 'push', "$targetReg/cord-maas-switchq:$targetTag"
+    workingDir 'switchq'
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'publish'
 }
 
 // IP Allocator Image
 
 task buildAllocationImage(type: Exec) {
     workingDir 'ip-allocator'
-    commandLine 'make', 'build', 'package'
-}
-
-task tagAllocationImage(type: Exec) {
-   dependsOn buildAllocationImage
-   commandLine "docker", 'tag', 'cord-ip-allocator:candidate', "$targetReg/cord-ip-allocator:$targetTag"
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'build'
 }
 
 task publishAllocationImage(type: Exec) {
-    dependsOn tagAllocationImage
-    commandLine "docker", 'push', "$targetReg/cord-ip-allocator:$targetTag"
+    workingDir 'ip-allocator'
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'publish'
 }
 
 // Provisioner Image
 
 task buildProvisionerImage(type: Exec) {
     workingDir 'provisioner'
-    commandLine 'make', 'build', 'package'
-}
-
-task tagProvisionerImage(type: Exec) {
-   dependsOn buildProvisionerImage
-   commandLine "docker", 'tag', 'cord-provisioner:candidate', "$targetReg/cord-provisioner:$targetTag"
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'build'
 }
 
 task publishProvisionerImage(type: Exec) {
-    dependsOn tagProvisionerImage
-    commandLine "docker", 'push', "$targetReg/cord-provisioner:$targetTag"
+    workingDir 'provisioner'
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'publish'
 }
 
 // Config Generator Image
 
 task buildConfigGeneratorImage(type: Exec) {
     workingDir 'config-generator'
-    commandLine 'make', 'build', 'package'
-}
-
-task tagConfigGeneratorImage(type: Exec) {
-   dependsOn buildConfigGeneratorImage
-   commandLine "docker", 'tag', 'config-generator:candidate', "$targetReg/config-generator:$targetTag"
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'build'
 }
 
 task publishConfigGeneratorImage(type: Exec) {
-    dependsOn tagConfigGeneratorImage
-    commandLine "docker", 'push', "$targetReg/config-generator:$targetTag"
+    workingDir 'config-generator'
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'publish'
 }
 
 // Automation Image
 
 task buildAutomationImage(type: Exec) {
     workingDir 'automation'
-    commandLine 'make', 'build', 'package'
-}
-
-task tagAutomationImage(type: Exec) {
-    dependsOn buildAutomationImage
-    commandLine "docker", 'tag', 'cord-maas-automation:candidate', "$targetReg/cord-maas-automation:$targetTag"
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'build'
 }
 
 task publishAutomationImage(type: Exec) {
-    dependsOn tagAutomationImage
-    commandLine "docker", 'push', "$targetReg/cord-maas-automation:$targetTag"
+    workingDir 'automation'
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'publish'
 }
 
 // DHCP Harvester Images
 
 task buildHarvesterImage(type: Exec) {
     workingDir 'harvester'
-    commandLine 'make', 'build', 'package'
-}
-
-task tagHarvesterImage(type: Exec) {
-    dependsOn buildHarvesterImage
-    commandLine "docker", 'tag', 'cord-dhcp-harvester:candidate', "$targetReg/cord-dhcp-harvester:$targetTag"
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'build'
 }
 
 task publishHarvesterImage(type: Exec) {
-    dependsOn tagHarvesterImage
-    commandLine "docker", 'push', "$targetReg/cord-dhcp-harvester:$targetTag"
+    workingDir 'harvester'
+    commandLine 'make', "DOCKER_TAG=$targetTag", "DOCKER_REGISTRY=$targetReg", 'publish'
 }
 
 // ~~~~~~~~~~~~~~~~~~~ Global tasks ~~~~~~~~~~~~~~~~~~~~~~~
@@ -261,15 +231,6 @@
     dependsOn buildSwitchqImage
 }
 
-task tagImages {
-    dependsOn tagHarvesterImage
-    dependsOn tagAutomationImage
-    dependsOn tagAllocationImage
-    dependsOn tagProvisionerImage
-    dependsOn tagConfigGeneratorImage
-    dependsOn tagSwitchqImage
-}
-
 task publish {
     //FIXME: This works because the upstream project primes the nodes before running this.
     comps.each { name, spec -> if (spec.type == 'image') { dependsOn "publish" + name } }
@@ -281,7 +242,6 @@
     dependsOn publishSwitchqImage
 }
 
-
 // ~~~~~~~~~~~~~~~~~~~ Deployment / Test Tasks  ~~~~~~~~~~~~~~~~~~~~~~~
 
 List.metaClass.asParam = { prefix, sep ->
diff --git a/config-generator/Dockerfile b/config-generator/Dockerfile.generator
similarity index 80%
rename from config-generator/Dockerfile
rename to config-generator/Dockerfile.generator
index 58dc80a..45ab66f 100644
--- a/config-generator/Dockerfile
+++ b/config-generator/Dockerfile.generator
@@ -11,17 +11,20 @@
 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
-FROM golang:1.7-alpine
+FROM golang:1.7-alpine as builder
 MAINTAINER Open Networking Laboratory <info@onlab.us>
 
-RUN mkdir /service
-
 WORKDIR /go
 ADD . /go/src/gerrit.opencord.org/maas/config-generator
-RUN go build -o /service/entry-point  gerrit.opencord.org/maas/config-generator
+RUN go build -o /build/entry-point gerrit.opencord.org/maas/config-generator
+
+FROM alpine:3.5
+MAINTAINER Open Networking Laboratory <info@onlab.us>
+
+COPY --from=builder /build/entry-point /service/entry-point
 
 # copy templates to the image
-COPY netconfig.tpl /service/netconfig.tpl
+COPY netconfig.tpl /service
 
 EXPOSE 1337
 
diff --git a/config-generator/Dockerfile.release b/config-generator/Dockerfile.release
deleted file mode 100644
index fcfdabc..0000000
--- a/config-generator/Dockerfile.release
+++ /dev/null
@@ -1,32 +0,0 @@
-## Copyright 2017 Open Networking Laboratory
-##
-## Licensed under the Apache License, Version 2.0 (the "License");
-## you may not use this file except in compliance with the License.
-## You may obtain a copy of the License at
-##
-## http://www.apache.org/licenses/LICENSE-2.0
-##
-## Unless required by applicable law or agreed to in writing, software
-## distributed under the License is distributed on an "AS IS" BASIS,
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-## See the License for the specific language governing permissions and
-## limitations under the License.
-FROM alpine:3.5
-MAINTAINER Open Networking Laboratory <info@onlab.us>
-
-RUN mkdir /service
-ADD entry-point /service/entry-point
-
-# copy templates to the image
-COPY netconfig.tpl /service
-
-EXPOSE 1337
-
-LABEL org.label-schema.name="generator" \
-      org.label-schema.description="Provides generation of the fabric configuration" \
-      org.label-schema.vcs-url="https://gerrit.opencord.org/maas" \
-      org.label-schema.vendor="Open Networking Laboratory" \
-      org.label-schema.schema-version="1.0"
-
-WORKDIR /service
-ENTRYPOINT ["/service/entry-point"]
diff --git a/config-generator/Makefile b/config-generator/Makefile
index 0b2bde8..810bbe9 100644
--- a/config-generator/Makefile
+++ b/config-generator/Makefile
@@ -1,38 +1,5 @@
-IMAGE_NAME=config-generator
-BINARY=entry-point
-BUILD_TAG=build
-PACKAGE_TAG=candidate
+IMAGES+=generator
 
-.PHONY: help
-help:
-	@echo "build     - create the binary"
-	@echo "package   - package the binary into a docker container"
-	@echo "clean     - remove tempory files and build artifacts"
-	@echo "help      - this message"
+generator.image: netconfig.tpl Dockerfile.generator *.go vendor/vendor.json
 
-BUILD_DATE=$(shell date -u +%Y-%m-%dT%TZ)
-VCS_REF=$(shell git log --pretty=format:%H -n 1)
-VCS_REF_DATE=$(shell git log --pretty=format:%cd --date=format:%FT%T%z -n 1)
-BRANCHES=$(shell repo --color=never --no-pager branches 2>/dev/null | wc -l)
-STATUS=$(shell repo --color=never --no-pager status . | tail -n +2 | wc -l)
-MODIFIED=$(shell test $(BRANCHES) -eq 0 && test $(STATUS) -eq 0 || echo "[modified]")
-BRANCH=$(shell repo --color=never --no-pager info -l -o | grep 'Manifest branch:' | awk '{print $$NF}')
-VERSION=$(BRANCH)$(MODIFIED)
-
-.PHONY: build
-build:
-	docker build -t $(IMAGE_NAME):$(BUILD_TAG) .
-
-.PHONY: package
-package:
-	$(eval BUILD_ID := $(shell docker create $(IMAGE_NAME):$(BUILD_TAG)))
-	$(eval BINDIR := $(shell mktemp -d))
-	docker cp $(BUILD_ID):/service/$(BINARY) $(BINDIR)/$(BINARY)
-	cp Dockerfile.release netconfig.tpl $(BINDIR)
-	docker build -f $(BINDIR)/Dockerfile.release -t $(IMAGE_NAME):$(PACKAGE_TAG) --label org.label-schema.build-date=$(BUILD_DATE) --label org.label-schema.vcs-ref=$(VCS_REF) --label org.label-schema.vcs-ref-date=$(VCS_REF_DATE) --label org.label-schema.version=$(VERSION) $(BINDIR)
-	docker rm -f $(BUILD_ID)
-	rm -r $(BINDIR)
-
-.PHONY: clean
-clean:
-	@echo ""
+include ../rules.mk
diff --git a/deploy-vars b/deploy-vars
new file mode 100644
index 0000000..b8511fa
--- /dev/null
+++ b/deploy-vars
@@ -0,0 +1,65 @@
+---
+# Password to assign to compute nodes for user `ubuntu`. Defaults to random value if not set
+password_compute_node: ubuntu
+
+#fabric_include_names
+fabric_include_module_types: i40e, mlx4_en
+#fabric_include_bus_types
+
+#fabric_exclude_names
+#fabric_exclude_module_types
+#fabric_exclude_bus_types
+
+#fabric_ignore_names
+#fabric_ignore_module_types
+#fabric_ignore_bus_types
+
+#management_include_names
+#management_include_module_types
+#management_include_bus_types
+#management_exclude_names
+#management_exclude_module_types
+#management_exclude_bus_types
+#management_ignore_names
+#management_ignore_module_types
+#management_ignore_bus_types
+
+fabric_iface: fabric
+management_iface: mgmtbr
+external_iface: eth2
+iface_file: /etc/network/interfaces
+
+fabric_ip: 10.2.1.1/24
+management_ip: 10.2.0.1/24
+external_ip: 192.168.10.3/24
+
+external_gw: 192.168.10.1
+#management_gw
+
+#external_bc
+#management_bc
+
+power_helper_user: cord
+power_helper_host: ''
+
+admin_email: admin@cord.lab
+password_maas_admin: admin
+maas_user: cord
+password_maas_user: cord
+maas_email: cord@cord.lab
+
+domain: cord.lab
+upstream_dns: 8.8.8.8 8.8.8.4
+
+management_network: 10.2.0.0/24
+bridge_network: 172.42.0.0/24
+bridge_name: mgmtbr
+
+fabric_range_low: 10.2.1.2
+fabric_range_high: 10.2.1.100
+management_range_low: 10.2.0.2
+management_range_high: 10.2.0.128
+
+deploy_docker_tag: candidate
+logging_host: 10.100.198.201
+
diff --git a/harvester/Dockerfile b/harvester/Dockerfile.harvester
similarity index 78%
rename from harvester/Dockerfile
rename to harvester/Dockerfile.harvester
index 4cdc692..1d14e61 100644
--- a/harvester/Dockerfile
+++ b/harvester/Dockerfile.harvester
@@ -11,15 +11,19 @@
 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
-FROM golang:1.7-alpine
+FROM golang:1.7-alpine as builder
 MAINTAINER Open Networking Laboratory <info@onlab.us>
 
+WORKDIR /go
+ADD . /go/src/gerrit.opencord.org/maas/harvester
+RUN go build -o /build/entry-point gerrit.opencord.org/maas/harvester
+
+FROM alpine:3.5
+MAINTAINER "Open Networking Laboratory <info@onlab.us>"
+
 RUN apk --update add bind
 
-RUN mkdir /service
-WORKDIR /go
-ADD . /go/src/gerrit.opencord.com/maas/harvester
-RUN go build -o /service/entry-point gerrit.opencord.com/maas/harvester
+COPY --from=builder /build/entry-point /service/entry-point
 
 LABEL org.label-schema.name="harvester" \
       org.label-schema.description="Provides DHCP harvesting and insertion into DNS" \
diff --git a/harvester/Dockerfile.release b/harvester/Dockerfile.release
deleted file mode 100644
index 1089ddc..0000000
--- a/harvester/Dockerfile.release
+++ /dev/null
@@ -1,28 +0,0 @@
-## Copyright 2017 Open Networking Laboratory
-##
-## Licensed under the Apache License, Version 2.0 (the "License");
-## you may not use this file except in compliance with the License.
-## You may obtain a copy of the License at
-##
-## http://www.apache.org/licenses/LICENSE-2.0
-##
-## Unless required by applicable law or agreed to in writing, software
-## distributed under the License is distributed on an "AS IS" BASIS,
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-## See the License for the specific language governing permissions and
-## limitations under the License.
-FROM alpine:3.5
-MAINTAINER Open Networking Laboratory <info@onlab.us>
-
-RUN apk --update add bind
-
-ADD entry-point /service/entry-point
-
-LABEL org.label-schema.name="harvester" \
-      org.label-schema.description="Provides DHCP harvesting and insertion into DNS" \
-      org.label-schema.vcs-url="https://gerrit.opencord.org/maas" \
-      org.label-schema.vendor="Open Networking Laboratory" \
-      org.label-schema.schema-version="1.0"
-
-WORKDIR /service
-ENTRYPOINT ["/service/entry-point"]
diff --git a/harvester/Makefile b/harvester/Makefile
index 39e2868..ffdcb98 100644
--- a/harvester/Makefile
+++ b/harvester/Makefile
@@ -1,38 +1,5 @@
-IMAGE_NAME=cord-dhcp-harvester
-BINARY=entry-point
-BUILD_TAG=build
-PACKAGE_TAG=candidate
+IMAGES+=harvester
 
-.PHONY: help
-help:
-	@echo "build     - create the binary"
-	@echo "package   - package the binary into a docker container"
-	@echo "clean     - remove tempory files and build artifacts"
-	@echo "help      - this message"
+harvester.image: Dockerfile.harvester *.go vendor/vendor.json
 
-BUILD_DATE=$(shell date -u +%Y-%m-%dT%TZ)
-VCS_REF=$(shell git log --pretty=format:%H -n 1)
-VCS_REF_DATE=$(shell git log --pretty=format:%cd --date=format:%FT%T%z -n 1)
-BRANCHES=$(shell repo --color=never --no-pager branches 2>/dev/null | wc -l)
-STATUS=$(shell repo --color=never --no-pager status . | tail -n +2 | wc -l)
-MODIFIED=$(shell test $(BRANCHES) -eq 0 && test $(STATUS) -eq 0 || echo "[modified]")
-BRANCH=$(shell repo --color=never --no-pager info -l -o | grep 'Manifest branch:' | awk '{print $$NF}')
-VERSION=$(BRANCH)$(MODIFIED)
-
-.PHONY: build
-build:
-	docker build -t $(IMAGE_NAME):$(BUILD_TAG) .
-
-.PHONY: package
-package:
-	$(eval BUILD_ID := $(shell docker create $(IMAGE_NAME):$(BUILD_TAG)))
-	$(eval BINDIR := $(shell mktemp -d))
-	docker cp $(BUILD_ID):/service/$(BINARY) $(BINDIR)/$(BINARY)
-	cp Dockerfile.release $(BINDIR)
-	docker build -f $(BINDIR)/Dockerfile.release -t $(IMAGE_NAME):$(PACKAGE_TAG) --label org.label-schema.build-date=$(BUILD_DATE) --label org.label-schema.vcs-ref=$(VCS_REF) --label org.label-schema.vcs-ref-date=$(VCS_REF_DATE) --label org.label-schema.version=$(VERSION) $(BINDIR)
-	docker rm -f $(BUILD_ID)
-	rm -r $(BINDIR)
-
-.PHONY: clean
-clean:
-	@echo ""
+include ../rules.mk
diff --git a/help.mk b/help.mk
new file mode 100644
index 0000000..f8ecad5
--- /dev/null
+++ b/help.mk
@@ -0,0 +1,15 @@
+.DEFAULT_GOAL := help
+
+help:
+	@echo "Available targets:"
+	@echo "    build     - builds any artifacts"
+	@echo "    publish   - publishes any built artifacts to a deployment server"
+	@echo "    clean     - remove tempory files and build artifacts"
+	@echo "    test      - executes any unit tests on the project"
+	@echo "    help      - this message"
+	@echo ""
+	@echo "Available environment variables:"
+	@echo "    PROJECT_PREFIX - defines a prefix to prepend to all Docker image names"
+	@echo "    PACKAGE_TAG    - defines the TAG to use on the public Docker image that is the build artifact"
+	@echo "    REGISTRY       - name of the registry to which to publish Docker images"
+	@echo "    DOCKER_ARGS    - additional arguments to pass to the Docker build command"
diff --git a/ip-allocator/Dockerfile b/ip-allocator/Dockerfile.allocator
similarity index 81%
rename from ip-allocator/Dockerfile
rename to ip-allocator/Dockerfile.allocator
index 27167f1..9108ffa 100644
--- a/ip-allocator/Dockerfile
+++ b/ip-allocator/Dockerfile.allocator
@@ -11,12 +11,17 @@
 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
-FROM golang:1.7-alpine
+FROM golang:1.7-alpine as builder
 MAINTAINER Open Networking Laboratory <info@onlab.us>
 
 WORKDIR /go
 ADD . /go/src/gerrit.opencord.org/maas/cord-ip-allocator
-RUN go build -o /service/entry-point gerrit.opencord.org/maas/cord-ip-allocator
+RUN go build -o /build/entry-point gerrit.opencord.org/maas/cord-ip-allocator
+
+FROM alpine:3.5
+MAINTAINER Open Networking Laboratory <info@onlab.us>
+
+copy --from=builder  /build/entry-point /service/entry-point
 
 LABEL org.label-schema.name="allocator" \
       org.label-schema.description="Provides IP address allocation for fabric interfaces" \
diff --git a/ip-allocator/Dockerfile.release b/ip-allocator/Dockerfile.release
deleted file mode 100644
index 3dc00f1..0000000
--- a/ip-allocator/Dockerfile.release
+++ /dev/null
@@ -1,27 +0,0 @@
-## Copyright 2017 Open Networking Laboratory
-##
-## Licensed under the Apache License, Version 2.0 (the "License");
-## you may not use this file except in compliance with the License.
-## You may obtain a copy of the License at
-##
-## http://www.apache.org/licenses/LICENSE-2.0
-##
-## Unless required by applicable law or agreed to in writing, software
-## distributed under the License is distributed on an "AS IS" BASIS,
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-## See the License for the specific language governing permissions and
-## limitations under the License.
-FROM alpine:3.5
-MAINTAINER Open Networking Laboratory <info@onlab.us>
-
-RUN mkdir /service
-ADD entry-point /service/entry-point
-
-LABEL org.label-schema.name="allocator" \
-      org.label-schema.description="Provides IP address allocation for fabric interfaces" \
-      org.label-schema.vcs-url="https://gerrit.opencord.org/maas" \
-      org.label-schema.vendor="Open Networking Laboratory" \
-      org.label-schema.schema-version="1.0"
-
-WORKDIR /service
-ENTRYPOINT ["/service/entry-point"]
diff --git a/ip-allocator/Makefile b/ip-allocator/Makefile
index ad1834c..4ec083f 100644
--- a/ip-allocator/Makefile
+++ b/ip-allocator/Makefile
@@ -1,38 +1,5 @@
-IMAGE_NAME=cord-ip-allocator
-BINARY=entry-point
-BUILD_TAG=build
-PACKAGE_TAG=candidate
+IMAGES+=allocator
 
-.PHONY: help
-help:
-	@echo "build     - create the binary"
-	@echo "package   - package the binary into a docker container"
-	@echo "clean     - remove tempory files and build artifacts"
-	@echo "help      - this message"
+allocator.image: Dockerfile.allocator *.go vendor/vendor.json
 
-BUILD_DATE=$(shell date -u +%Y-%m-%dT%TZ)
-VCS_REF=$(shell git log --pretty=format:%H -n 1)
-VCS_REF_DATE=$(shell git log --pretty=format:%cd --date=format:%FT%T%z -n 1)
-BRANCHES=$(shell repo --color=never --no-pager branches 2>/dev/null | wc -l)
-STATUS=$(shell repo --color=never --no-pager status . | tail -n +2 | wc -l)
-MODIFIED=$(shell test $(BRANCHES) -eq 0 && test $(STATUS) -eq 0 || echo "[modified]")
-BRANCH=$(shell repo --color=never --no-pager info -l -o | grep 'Manifest branch:' | awk '{print $$NF}')
-VERSION=$(BRANCH)$(MODIFIED)
-
-.PHONY: build
-build:
-	docker build -t $(IMAGE_NAME):$(BUILD_TAG) .
-
-.PHONY: package
-package:
-	$(eval BUILD_ID := $(shell docker create $(IMAGE_NAME):$(BUILD_TAG)))
-	$(eval BINDIR := $(shell mktemp -d))
-	docker cp $(BUILD_ID):/service/$(BINARY) $(BINDIR)/$(BINARY)
-	cp Dockerfile.release $(BINDIR)
-	docker build -f $(BINDIR)/Dockerfile.release -t $(IMAGE_NAME):$(PACKAGE_TAG) --label org.label-schema.build-date=$(BUILD_DATE) --label org.label-schema.vcs-ref=$(VCS_REF) --label org.label-schema.vcs-ref-date=$(VCS_REF_DATE) --label org.label-schema.version=$(VERSION) $(BINDIR)
-	docker rm -f $(BUILD_ID)
-	rm -r $(BINDIR)
-
-.PHONY: clean
-clean:
-	@echo ""
+include ../rules.mk
diff --git a/provisioner/Dockerfile b/provisioner/Dockerfile
deleted file mode 100644
index 91f697b..0000000
--- a/provisioner/Dockerfile
+++ /dev/null
@@ -1,30 +0,0 @@
-## Copyright 2016 Open Networking Laboratory
-##
-## Licensed under the Apache License, Version 2.0 (the "License");
-## you may not use this file except in compliance with the License.
-## You may obtain a copy of the License at
-##
-## http://www.apache.org/licenses/LICENSE-2.0
-##
-## Unless required by applicable law or agreed to in writing, software
-## distributed under the License is distributed on an "AS IS" BASIS,
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-## See the License for the specific language governing permissions and
-## limitations under the License.
-FROM golang:1.7-alpine
-MAINTAINER Open Networking Laboratory <info@onlab.us>
-
-RUN mkdir -p /root/.ssh /service
-COPY ssh-config /root/.ssh/config
-ADD . /go/src/gerrit.opencord.org/maas/cord-provisioner
-
-WORKDIR /go
-RUN go build -o /service/entry-point  gerrit.opencord.org/maas/cord-provisioner
-
-LABEL org.label-schema.name="provisioner" \
-      org.label-schema.description="Provides provisioning of compute and switch nodes for CORD" \
-      org.label-schema.vcs-url="https://gerrit.opencord.org/maas" \
-      org.label-schema.vendor="Open Networking Laboratory" \
-      org.label-schema.schema-version="1.0"
-
-ENTRYPOINT ["/service/entry-point"]
diff --git a/provisioner/Dockerfile.release b/provisioner/Dockerfile.provisioner
similarity index 77%
rename from provisioner/Dockerfile.release
rename to provisioner/Dockerfile.provisioner
index 188896c..1aa5742 100644
--- a/provisioner/Dockerfile.release
+++ b/provisioner/Dockerfile.provisioner
@@ -1,4 +1,4 @@
-## Copyright 2017 Open Networking Laboratory
+## Copyright 2016 Open Networking Laboratory
 ##
 ## Licensed under the Apache License, Version 2.0 (the "License");
 ## you may not use this file except in compliance with the License.
@@ -11,15 +11,21 @@
 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
+FROM golang:1.7-alpine as builder
+MAINTAINER Open Networking Laboratory <info@onlab.us>
+
+WORKDIR /go
+ADD . /go/src/gerrit.opencord.org/maas/cord-provisioner
+RUN go build -o /build/entry-point gerrit.opencord.org/maas/cord-provisioner
+
 FROM alpine:3.5
 MAINTAINER Open Networking Laboratory <info@onlab.us>
 
 # Base image information borrowed by official golang wheezy Dockerfile
 RUN apk --update add ansible openssh-client sshpass curl py2-netaddr rsync
-RUN mkdir -p /root/.ssh /service /etc/ansible
 COPY ssh-config /root/.ssh/config
 COPY ansible.cfg /etc/ansible/ansible.cfg
-ADD entry-point /service/entry-point
+COPY --from=builder /build/entry-point /service/entry-point
 
 LABEL org.label-schema.name="provisioner" \
       org.label-schema.description="Provides provisioning of compute and switch nodes for CORD" \
diff --git a/provisioner/Makefile b/provisioner/Makefile
index 0a8bdcf..95e1504 100644
--- a/provisioner/Makefile
+++ b/provisioner/Makefile
@@ -1,38 +1,5 @@
-IMAGE_NAME=cord-provisioner
-BINARY=entry-point
-BUILD_TAG=build
-PACKAGE_TAG=candidate
+IMAGES+=provisioner
 
-.PHONY: help
-help:
-	@echo "build     - create the binary"
-	@echo "package   - package the binary into a docker container"
-	@echo "clean     - remove tempory files and build artifacts"
-	@echo "help      - this message"
+provisioner.image: ssh-config ansible.cfg Dockerfile.provisioner *.go vendor/vendor.json
 
-BUILD_DATE=$(shell date -u +%Y-%m-%dT%TZ)
-VCS_REF=$(shell git log --pretty=format:%H -n 1)
-VCS_REF_DATE=$(shell git log --pretty=format:%cd --date=format:%FT%T%z -n 1)
-BRANCHES=$(shell repo --color=never --no-pager branches 2>/dev/null | wc -l)
-STATUS=$(shell repo --color=never --no-pager status . | tail -n +2 | wc -l)
-MODIFIED=$(shell test $(BRANCHES) -eq 0 && test $(STATUS) -eq 0 || echo "[modified]")
-BRANCH=$(shell repo --color=never --no-pager info -l -o | grep 'Manifest branch:' | awk '{print $$NF}')
-VERSION=$(BRANCH)$(MODIFIED)
-
-.PHONY: build
-build:
-	docker build -t $(IMAGE_NAME):$(BUILD_TAG) .
-
-.PHONY: package
-package:
-	$(eval BUILD_ID := $(shell docker create $(IMAGE_NAME):$(BUILD_TAG)))
-	$(eval BINDIR := $(shell mktemp -d))
-	docker cp $(BUILD_ID):/service/$(BINARY) $(BINDIR)/$(BINARY)
-	cp Dockerfile.release ssh-config ansible.cfg $(BINDIR)
-	docker build -f $(BINDIR)/Dockerfile.release -t $(IMAGE_NAME):$(PACKAGE_TAG) --label org.label-schema.build-date=$(BUILD_DATE) --label org.label-schema.vcs-ref=$(VCS_REF) --label org.label-schema.vcs-ref-date=$(VCS_REF_DATE) --label org.label-schema.version=$(VERSION) $(BINDIR)
-	docker rm -f $(BUILD_ID)
-	rm -r $(BINDIR)
-
-.PHONY: clean
-clean:
-	@echo ""
+include ../rules.mk
diff --git a/roles/compute-node/defaults/main.yml b/roles/compute-node/defaults/main.yml
index 8bb3905..1b224a1 100644
--- a/roles/compute-node/defaults/main.yml
+++ b/roles/compute-node/defaults/main.yml
@@ -1,6 +1,7 @@
 ---
 
-pub_ssh_key: "{{ lookup('file', '/etc/maas/.ssh/cord_rsa.pub') }}"
+pub_ssh_key_file_location: "{{ pub_ssh_key_location | default ('/etc/maas/.ssh') }}"
+pub_ssh_key: "{{ lookup('file', pub_ssh_key_file_location+'/cord_rsa.pub') }}"
 
 compute_node:
     password: "{{password_compute_node | default(lookup('password', 'passwords/compute_node.txt chars=ascii_letters,digits'))}}"
diff --git a/roles/compute-node/files/remove-maas-components b/roles/compute-node/files/remove-maas-components
index 5adf79e..f3b09e7 100755
--- a/roles/compute-node/files/remove-maas-components
+++ b/roles/compute-node/files/remove-maas-components
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 KEEP_DOCKER=0
-DOCKER_ENGINE="docker-engine"
+DOCKER_ENGINE="docker-ce"
 DOCKER_REGISTRY="/docker-registry /docker-registry-mirror"
 CONTAINER_LIST=$(docker ps -qa)
 
@@ -17,15 +17,11 @@
     shift
 done
 
-CONTAINER_LIST=$(docker ps --format '{{.ID}} {{.Names}}' | grep -v ' registry$' | grep -v ' registry-mirror$' | awk '{print $1}')
-
-docker kill $CONTAINER_LIST
-docker rm -f $CONTAINER_LIST
-if [ $KEEP_DOCKER -eq 0 ]; then
-    docker rmi -f $(docker images -aq)
-fi
-
-sudo apt-get remove --purge -y bind9 apache2 $DOCKER_ENGINE ansible $(dpkg --get-selections | grep maas | cut -f1)
+docker rm -f $(docker ps -aq)
+docker rmi -f $(docker images -q)
+docker volume rm -f $(docker volume ls -q)
+docker network rm $(docker network ls -q)
+sudo apt-get remove --purge -y bind9 apache2 apt-cacher-ng $DOCKER_ENGINE ansible $(dpkg --get-selections | grep maas | cut -f1)
 
 sudo rm -rf \
     /etc/maas \
@@ -41,6 +37,7 @@
     /etc/apt/sources.list.d/ppa_maas_stable_trusty.list \
     /etc/apt/sources.list.d/ppa_ansible_ansible_trusty.list \
     /etc/network/if-pre-up.d/nat \
+    /etc/apt/apt.conf.d/03apt-cacher-ng \
     $DOCKER_REGISTRY
 
 sudo apt-get update -y
diff --git a/roles/compute-node/files/remove-xos-components b/roles/compute-node/files/remove-xos-components
index 2f2cf07..d18eebe 100755
--- a/roles/compute-node/files/remove-xos-components
+++ b/roles/compute-node/files/remove-xos-components
@@ -24,6 +24,12 @@
 
 sudo apt-get remove --purge -y $(dpkg --get-selections | grep "nagioas\|juju\|nova\|neutron" | awk '{print $1}') &&sudo apt-get autoremove -y && sudo rm -rf /etc/juju /etc/neutron /home/ubuntu/.juju && sudo find / -name "*juju*" -exec rm -r \{\} \; && sudo rm -f /var/lib/uvtool/libvirt/images/*
 
+sudo rm -rf \
+    /opt/cord* \
+    /opt/onos_* \
+    /opt/credentials \
+    /opt/images
+
 OVS=$(which ovs-vsctl)
 
 if [ ! -z $OVS ]; then
diff --git a/roles/compute-node/tasks/main.yml b/roles/compute-node/tasks/main.yml
index fe2b1ae..fb5b9bb 100644
--- a/roles/compute-node/tasks/main.yml
+++ b/roles/compute-node/tasks/main.yml
@@ -108,7 +108,7 @@
 - name: Ensure SSH Key Pair
   become: yes
   copy:
-    src: "/etc/maas/.ssh/{{item.src}}"
+    src: "{{pub_ssh_key_file_location}}/{{item.src}}"
     dest: "{{ansible_env['PWD']}}/.ssh/{{item.dest}}"
     owner: "{{ ansible_user_id }}"
     group: "docker"
diff --git a/roles/fabric-switch/defaults/main.yml b/roles/fabric-switch/defaults/main.yml
index d63e3ab..c43e606 100644
--- a/roles/fabric-switch/defaults/main.yml
+++ b/roles/fabric-switch/defaults/main.yml
@@ -1 +1,2 @@
-pub_ssh_key: "{{ lookup('file', '/etc/maas/.ssh/cord_rsa.pub') }}"
+pub_ssh_key_file_location: "{{ pub_ssh_key_location | default ('/etc/maas/.ssh') }}"
+pub_ssh_key: "{{ lookup('file', pub_ssh_key_file_location+'/cord_rsa.pub') }}"
diff --git a/roles/head-node/tasks/main.yml b/roles/head-node/tasks/main.yml
index 04a7e8c..8cd1f9a 100644
--- a/roles/head-node/tasks/main.yml
+++ b/roles/head-node/tasks/main.yml
@@ -131,7 +131,7 @@
 - name: Copy SSH Key Pair for POD
   become: yes
   copy:
-    src: /etc/maas/.ssh/{{item}}
+    src: "{{pub_ssh_key_file_location}}/{{item}}"
     dest: /etc/maas/.ssh/{{item}}
     owner: maas
     group: maas
diff --git a/roles/maas/defaults/main.yml b/roles/maas/defaults/main.yml
index 266dc98..7d677a8 100644
--- a/roles/maas/defaults/main.yml
+++ b/roles/maas/defaults/main.yml
@@ -2,6 +2,7 @@
 
 accton_as5712_54x: 'https://www.dropbox.com/s/pl3cvr9olnaufw5/ONL-2.0.0_ONL-OS_2017-01-04.0024-8d23df5_AMD64_INSTALLED_INSTALLER'
 accton_as6712_32x: 'https://www.dropbox.com/s/pl3cvr9olnaufw5/ONL-2.0.0_ONL-OS_2017-01-04.0024-8d23df5_AMD64_INSTALLED_INSTALLER'
+pub_ssh_key_file_location: "{{ pub_ssh_key_location | default ('/etc/maas/.ssh') }}"
 
 virtualbox:
     # CHANGE:
@@ -18,7 +19,7 @@
     user: "{{ maas_user | default('cord') }}"
     user_password: "{{ password_maas_user | default(lookup('password', 'passwords/maas_user.txt chars=ascii_letters,digits')) }}"
     user_email: "{{ maas_email | default('cord@cord.lab') }}"
-    user_sshkey: "{{ lookup('file', '/etc/maas/.ssh/cord_rsa.pub') }}"
+    user_sshkey: "{{ lookup('file', pub_ssh_key_file_location+'/cord_rsa.pub') }}"
 
     # CHANGE:
     #   'domain' specifies the domain name configured in to MAAS
diff --git a/roles/maas/tasks/main.yml b/roles/maas/tasks/main.yml
index bb6aab9..915ae27 100644
--- a/roles/maas/tasks/main.yml
+++ b/roles/maas/tasks/main.yml
@@ -43,12 +43,12 @@
     state: absent
   with_items:
     - { name: "storage", image: "docker-registry:5000/consul:{{ docker.tag }}" }
-    - { name: "allocator", image: "docker-registry:5000/cord-ip-allocator:{{ docker.tag }}" }
-    - { name: "provisioner", image: "docker-registry:5000/cord-provisioner:{{ docker.tag }}" }
+    - { name: "allocator", image: "docker-registry:5000/cord-maas-allocator:{{ docker.tag }}" }
+    - { name: "provisioner", image: "docker-registry:5000/cord-maas-provisioner:{{ docker.tag }}" }
     - { name: "switchq", image: "docker-registry:5000/cord-maas-switchq:{{ docker.tag }}" }
     - { name: "automation", image: "docker-registry:5000/cord-maas-automation:{{ docker.tag }}" }
-    - { name: "generator", image: "docker-registry:5000/config-generator:{{ docker.tag }}" }
-    - { name: "harvester", image: "docker-registry:5000/cord-dhcp-harvester:{{ docker.tag }}" }
+    - { name: "generator", image: "docker-registry:5000/cord-maas--generator:{{ docker.tag }}" }
+    - { name: "harvester", image: "docker-registry:5000/cord-maas-harvester:{{ docker.tag }}" }
 
 - name: MAAS Repository
   become: yes
@@ -129,6 +129,11 @@
   register: maas_user_exists
   changed_when: false
 
+- name: Debug
+  become: yes
+  debug:
+    msg: "USER: {{maas.user}} and {{maas.user_password}} and {{maas_user_exists.stdout}}"
+
 - name: MAAS User
   become: yes
   command: maas-region-admin createadmin --username={{ maas.user }} --password={{ maas.user_password }} --email={{ maas.user_email }}
diff --git a/roles/maas/templates/automation-compose.yml.j2 b/roles/maas/templates/automation-compose.yml.j2
index 1644c11..83e96bd 100644
--- a/roles/maas/templates/automation-compose.yml.j2
+++ b/roles/maas/templates/automation-compose.yml.j2
@@ -15,7 +15,7 @@
     restart: unless-stopped
 
   allocator:
-    image: "docker-registry:5000/cord-ip-allocator:{{ docker.tag }}"
+    image: "docker-registry:5000/opencord/maas-allocator:{{ docker.tag }}"
     container_name: allocator
     ports:
       - "4242:4242"
@@ -35,7 +35,7 @@
     restart: unless-stopped
 
   provisioner:
-    image: "docker-registry:5000/cord-provisioner:{{ docker.tag }}"
+    image: "docker-registry:5000/opencord/maas-provisioner:{{ docker.tag }}"
     container_name: provisioner
     dns: {{ mgmt_ip_address.stdout }}
     ports:
@@ -68,7 +68,7 @@
     restart: unless-stopped
 
   switchq:
-    image: "docker-registry:5000/cord-maas-switchq:{{ docker.tag }}"
+    image: "docker-registry:5000/opencord/maas-switchq:{{ docker.tag }}"
     container_name: switchq
     ports:
       - "4244:4244"
@@ -93,7 +93,7 @@
     restart: unless-stopped
 
   automation:
-    image: "docker-registry:5000/cord-maas-automation:{{ docker.tag }}"
+    image: "docker-registry:5000/opencord/maas-automation:{{ docker.tag }}"
     container_name: automation
     labels:
       - "lab.solution=CORD"
@@ -127,7 +127,7 @@
     restart: unless-stopped
 
   harvester:
-      image: "docker-registry:5000/cord-dhcp-harvester:{{ docker.tag }}"
+      image: "docker-registry:5000/opencord/maas-harvester:{{ docker.tag }}"
       container_name: harvester
       restart: always
       labels:
@@ -157,7 +157,7 @@
       restart: unless-stopped
 
   generator:
-    image: "docker-registry:5000/config-generator:{{ docker.tag }}"
+    image: "docker-registry:5000/opencord/maas-generator:{{ docker.tag }}"
     container_name: generator
     ports:
       - "4245:4245"
diff --git a/roles/ssh-key/defaults/main.yml b/roles/ssh-key/defaults/main.yml
new file mode 100644
index 0000000..309b31c
--- /dev/null
+++ b/roles/ssh-key/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+pub_ssh_key_file_location: "{{ pub_ssh_key_location | default ('/etc/maas/.ssh') }}"
diff --git a/roles/ssh-key/tasks/main.yml b/roles/ssh-key/tasks/main.yml
index ee20ce3..44ce66e 100644
--- a/roles/ssh-key/tasks/main.yml
+++ b/roles/ssh-key/tasks/main.yml
@@ -19,3 +19,24 @@
     key: "{{lookup('file', '~/.ssh/id_rsa.pub')}}"
   tags:
     - establish_ssh_keys
+
+- name: Ensure key pair storage
+  become: yes
+  local_action: file path={{pub_ssh_key_file_location}} mode="0755" state=directory
+
+- name: Validate existing key pair
+  become: yes
+  local_action: stat path={{pub_ssh_key_file_location}}/cord_rsa
+  register: key_pair
+
+- name: Generate key pair
+  become: yes
+  local_action: command ssh-keygen -b 2048 -t rsa -N "" -C cord@cord.lab -f {{pub_ssh_key_file_location}}/cord_rsa
+  when: not key_pair.stat.exists
+
+- name: Ensure privacy of key pair
+  become: yes
+  local_action: file path="{{pub_ssh_key_file_location}}/{{item.name}}" mode="{{item.mode}}"
+  with_items:
+    - { "name": "cord_rsa", "mode": "0644" }
+    - { "name": "cord_rsa.pub", "mode": "0644" }
diff --git a/rules.mk b/rules.mk
new file mode 100644
index 0000000..a47303e
--- /dev/null
+++ b/rules.mk
@@ -0,0 +1,48 @@
+.PRECIOUS: Dockerfile.image
+
+ifneq ($(MAKE_CONFIG),)
+include $(MAKE_CONFIG)
+endif
+
+PROJECT_PREFIX?=opencord/maas-
+
+ifeq ($(DOCKER_TAG),)
+DOCKER_TAG:=candidate
+endif
+
+BUILD_DATE=$(shell date -u +%Y-%m-%dT%TZ)
+VCS_REF=$(shell git log --pretty=format:%H -n 1)
+VCS_REF_DATE=$(shell git log --pretty=format:%cd --date=format:%FT%T%z -n 1)
+BRANCHES=$(shell repo --color=never --no-pager branches 2>/dev/null | wc -l)
+STATUS=$(shell repo --color=never --no-pager status . | tail -n +2 | wc -l)
+MODIFIED=$(shell test $(BRANCHES) -eq 0 && test $(STATUS) -eq 0 || echo "[modified]")
+BRANCH=$(shell repo --color=never --no-pager info -l -o | grep 'Manifest branch:' | awk '{print $$NF}')
+VERSION=$(BRANCH)$(MODIFIED)
+
+include ../help.mk
+
+build: $(addsuffix .image,$(IMAGES))
+
+publish: $(addsuffix .publish,$(IMAGES))
+
+test:
+	@echo "Really should have some tests"
+
+%.image : Dockerfile.%
+	docker build $(DOCKER_ARGS) -f Dockerfile.$(basename $@) \
+		-t $(PROJECT_PREFIX)$(basename $@):$(DOCKER_TAG) \
+		--label org.label-schema.build-date=$(BUILD_DATE) \
+		--label org.label-schema.vcs-ref=$(VCS_REF) \
+		--label org.label-schema.vcs-ref-date=$(VCS_REF_DATE) \
+		--label org.label-schema.version=$(VERSION) .
+
+%.publish :
+ifdef DOCKER_REGISTRY
+	$(eval BASENAME := $(basename $@):$(DOCKER_TAG))
+	docker tag $(PROJECT_PREFIX)$(BASENAME) $(DOCKER_REGISTRY)/$(PROJECT_PREFIX)$(BASENAME)
+	docker push $(DOCKER_REGISTRY)/$(PROJECT_PREFIX)$(BASENAME)
+else
+	@echo "No registry was specified, cannot PUSH image"
+endif
+
+clean:
diff --git a/switchq/Dockerfile b/switchq/Dockerfile
deleted file mode 100644
index 993fa41..0000000
--- a/switchq/Dockerfile
+++ /dev/null
@@ -1,30 +0,0 @@
-## Copyright 2017 Open Networking Laboratory
-##
-## Licensed under the Apache License, Version 2.0 (the "License");
-## you may not use this file except in compliance with the License.
-## You may obtain a copy of the License at
-##
-## http://www.apache.org/licenses/LICENSE-2.0
-##
-## Unless required by applicable law or agreed to in writing, software
-## distributed under the License is distributed on an "AS IS" BASIS,
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-## See the License for the specific language governing permissions and
-## limitations under the License.
-FROM golang:1.7-alpine
-MAINTAINER Open Networking Laboratory <info@onlab.us>
-
-RUN mkdir /switchq /service
-WORKDIR /go
-ADD . /go/src/gerrit.opencord.com/maas/switchq
-COPY vendors.json /switchq/vendors.json
-RUN go build -o /service/entry-point gerrit.opencord.com/maas/switchq
-
-LABEL org.label-schema.name="switchq" \
-      org.label-schema.description="Provides fabric switch discovery and provisioning" \
-      org.label-schema.vcs-url="https://gerrit.opencord.org/maas" \
-      org.label-schema.vendor="Open Networking Laboratory" \
-      org.label-schema.schema-version="1.0"
-
-WORKDIR /service
-ENTRYPOINT ["/service/entry-point"]
diff --git a/switchq/Dockerfile.release b/switchq/Dockerfile.switchq
similarity index 79%
rename from switchq/Dockerfile.release
rename to switchq/Dockerfile.switchq
index 77ec075..48529ec 100644
--- a/switchq/Dockerfile.release
+++ b/switchq/Dockerfile.switchq
@@ -11,12 +11,18 @@
 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
+FROM golang:1.7-alpine as builder
+MAINTAINER Open Networking Laboratory <info@onlab.us>
+
+WORKDIR /go
+ADD . /go/src/gerrit.opencord.org/maas/switchq
+RUN go build -o /build/entry-point gerrit.opencord.org/maas/switchq
+
 FROM alpine:3.5
 MAINTAINER Open Networking Laboratory <info@onlab.us>
 
-RUN mkdir -p /switchq /service
 COPY vendors.json /switchq/vendors.json
-ADD entry-point /service/entry-point
+COPY --from=builder /build/entry-point /service/entry-point
 
 LABEL org.label-schema.name="switchq" \
       org.label-schema.description="Provides fabric switch discovery and provisioning" \
diff --git a/switchq/Makefile b/switchq/Makefile
index a28b665..c9b7984 100644
--- a/switchq/Makefile
+++ b/switchq/Makefile
@@ -1,38 +1,5 @@
-IMAGE_NAME=cord-maas-switchq
-BINARY=entry-point
-BUILD_TAG=build
-PACKAGE_TAG=candidate
+IMAGES+=switchq
 
-.PHONY: help
-help:
-	@echo "build     - create the binary"
-	@echo "package   - package the binary into a docker container"
-	@echo "clean     - remove tempory files and build artifacts"
-	@echo "help      - this message"
+switchq.image: vendors.json Dockerfile.switchq *.go vendor/vendor.json
 
-BUILD_DATE=$(shell date -u +%Y-%m-%dT%TZ)
-VCS_REF=$(shell git log --pretty=format:%H -n 1)
-VCS_REF_DATE=$(shell git log --pretty=format:%cd --date=format:%FT%T%z -n 1)
-BRANCHES=$(shell repo --color=never --no-pager branches 2>/dev/null | wc -l)
-STATUS=$(shell repo --color=never --no-pager status . | tail -n +2 | wc -l)
-MODIFIED=$(shell test $(BRANCHES) -eq 0 && test $(STATUS) -eq 0 || echo "[modified]")
-BRANCH=$(shell repo --color=never --no-pager info -l -o | grep 'Manifest branch:' | awk '{print $$NF}')
-VERSION=$(BRANCH)$(MODIFIED)
-
-.PHONY: build
-build:
-	docker build -t $(IMAGE_NAME):$(BUILD_TAG) .
-
-.PHONY: package
-package:
-	$(eval BUILD_ID := $(shell docker create $(IMAGE_NAME):$(BUILD_TAG)))
-	$(eval BINDIR := $(shell mktemp -d))
-	docker cp $(BUILD_ID):/service/$(BINARY) $(BINDIR)/$(BINARY)
-	cp Dockerfile.release vendors.json $(BINDIR)
-	docker build -f $(BINDIR)/Dockerfile.release -t $(IMAGE_NAME):$(PACKAGE_TAG) --label org.label-schema.build-date=$(BUILD_DATE) --label org.label-schema.vcs-ref=$(VCS_REF) --label org.label-schema.vcs-ref-date=$(VCS_REF_DATE) --label org.label-schema.version=$(VERSION) $(BINDIR)
-	docker rm -f $(BUILD_ID)
-	rm -r $(BINDIR)
-
-.PHONY: clean
-clean:
-	@echo ""
+include ../rules.mk