[ISSUE-67015020] query by config id, type filter for "/configurations" (#20)

* [ISSUE-67015020] query by config id, type filter for "/configurations"

* [ISSUE-67015020] address comments, use apid-core utils
diff --git a/api.go b/api.go
index f676aaa..4ffc12e 100644
--- a/api.go
+++ b/api.go
@@ -15,6 +15,7 @@
 
 import (
 	"bytes"
+	"database/sql"
 	"encoding/json"
 	"fmt"
 	"github.com/gorilla/mux"
@@ -38,14 +39,17 @@
 )
 
 const (
-	deploymentsEndpoint = "/configurations"
-	blobEndpointPath    = "/blobs"
-	blobEndpoint        = blobEndpointPath + "/{blobId}"
+	deploymentsEndpoint  = "/configurations"
+	blobEndpointPath     = "/blobs"
+	blobEndpoint         = blobEndpointPath + "/{blobId}"
+	deploymentIdEndpoint = deploymentsEndpoint + "/{configId}"
 )
 
 const (
 	API_ERR_BAD_BLOCK = iota + 1
 	API_ERR_INTERNAL
+	API_ERR_BAD_CONFIG_ID
+	API_ERR_NOT_FOUND
 )
 
 const (
@@ -102,14 +106,15 @@
 }
 
 type apiManager struct {
-	dbMan               dbManagerInterface
-	deploymentsEndpoint string
-	blobEndpoint        string
-	eTag                int64
-	deploymentsChanged  chan interface{}
-	addSubscriber       chan chan deploymentsResult
-	removeSubscriber    chan chan deploymentsResult
-	apiInitialized      bool
+	dbMan                dbManagerInterface
+	deploymentsEndpoint  string
+	blobEndpoint         string
+	deploymentIdEndpoint string
+	eTag                 int64
+	deploymentsChanged   chan interface{}
+	addSubscriber        chan chan deploymentsResult
+	removeSubscriber     chan chan deploymentsResult
+	apiInitialized       bool
 }
 
 func (a *apiManager) InitAPI() {
@@ -118,6 +123,7 @@
 	}
 	services.API().HandleFunc(a.deploymentsEndpoint, a.apiGetCurrentDeployments).Methods("GET")
 	services.API().HandleFunc(a.blobEndpoint, a.apiReturnBlobData).Methods("GET")
+	services.API().HandleFunc(a.deploymentIdEndpoint, a.apiHandleConfigId).Methods("GET")
 	a.apiInitialized = true
 	log.Debug("API endpoints initialized")
 }
@@ -231,6 +237,43 @@
 
 }
 
+func (a *apiManager) apiHandleConfigId(w http.ResponseWriter, r *http.Request) {
+	vars := mux.Vars(r)
+	configId := vars["configId"]
+	config, err := a.dbMan.getConfigById(configId)
+	if err != nil {
+		if err == sql.ErrNoRows {
+			a.writeError(w, http.StatusNotFound, API_ERR_NOT_FOUND, "cannot find the configuration")
+		} else {
+			log.Errorf("apiHandleConfigId: %v", err)
+			a.writeInternalError(w, err.Error())
+		}
+		return
+	}
+	configDetail := ApiDeploymentDetails{
+		Self:            getHttpHost() + a.deploymentsEndpoint + "/" + config.ID,
+		Name:            config.Name,
+		Type:            config.Type,
+		Revision:        config.Revision,
+		BeanBlobUrl:     getBlobUrl(config.BlobID),
+		Org:             config.OrgID,
+		Env:             config.EnvID,
+		ResourceBlobUrl: getBlobUrl(config.BlobResourceID),
+		Path:            config.Path,
+		Created:         convertTime(config.Created),
+		Updated:         convertTime(config.Updated),
+	}
+
+	b, err := json.Marshal(configDetail)
+	if err != nil {
+		log.Errorf("unable to marshal config: %v", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	log.Debugf("sending configuration %s", b)
+	w.Write(b)
+}
+
 func (a *apiManager) apiGetCurrentDeployments(w http.ResponseWriter, r *http.Request) {
 
 	// If returning without a bundle (immediately or after timeout), status = 404
@@ -240,6 +283,7 @@
 	// If timeout > 0 AND there is no deployment (or new deployment) available (per If-None-Match), then
 	// block for up to the specified number of seconds until a new deployment becomes available.
 	b := r.URL.Query().Get("block")
+	typeFilter := r.URL.Query().Get("type")
 	var timeout int
 	if b != "" {
 		var err error
@@ -266,7 +310,7 @@
 
 	// send results if different eTag
 	if eTag != ifNoneMatch {
-		a.sendReadyDeployments(w)
+		a.sendReadyDeployments(typeFilter, w)
 		return
 	}
 
@@ -285,7 +329,7 @@
 		if result.err != nil {
 			a.writeInternalError(w, "Database error")
 		} else {
-			a.sendDeployments(w, result.deployments, result.eTag)
+			a.sendDeployments(w, result.deployments, result.eTag, typeFilter)
 		}
 
 	case <-time.After(time.Duration(timeout) * time.Second):
@@ -294,22 +338,22 @@
 		if ifNoneMatch != "" {
 			w.WriteHeader(http.StatusNotModified)
 		} else {
-			a.sendReadyDeployments(w)
+			a.sendReadyDeployments(typeFilter, w)
 		}
 	}
 }
 
-func (a *apiManager) sendReadyDeployments(w http.ResponseWriter) {
+func (a *apiManager) sendReadyDeployments(typeFilter string, w http.ResponseWriter) {
 	eTag := a.getETag()
-	deployments, err := a.dbMan.getReadyDeployments()
+	deployments, err := a.dbMan.getReadyDeployments(typeFilter)
 	if err != nil {
 		a.writeInternalError(w, fmt.Sprintf("Database error: %s", err.Error()))
 		return
 	}
-	a.sendDeployments(w, deployments, eTag)
+	a.sendDeployments(w, deployments, eTag, typeFilter)
 }
 
-func (a *apiManager) sendDeployments(w http.ResponseWriter, dataDeps []DataDeployment, eTag string) {
+func (a *apiManager) sendDeployments(w http.ResponseWriter, dataDeps []DataDeployment, eTag string, typeFilter string) {
 
 	apiDeps := ApiDeploymentResponse{}
 	apiDepDetails := make([]ApiDeploymentDetails, 0)
@@ -334,6 +378,10 @@
 	}
 	apiDeps.ApiDeploymentsResponse = apiDepDetails
 
+	if typeFilter != "" {
+		apiDeps.Self += "?type=" + typeFilter
+	}
+
 	b, err := json.Marshal(apiDeps)
 	if err != nil {
 		log.Errorf("unable to marshal deployments: %v", err)
diff --git a/api_test.go b/api_test.go
index ccd3b0c..84c1cb5 100644
--- a/api_test.go
+++ b/api_test.go
@@ -14,18 +14,19 @@
 package apiGatewayConfDeploy
 
 import (
+	"database/sql"
 	"encoding/json"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-
-	"crypto/rand"
 	"fmt"
+	"github.com/apid/apid-core/util"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
+	"io/ioutil"
 	mathrand "math/rand"
+	"net/http"
+	"net/url"
 	"os"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -43,13 +44,14 @@
 		testCount += 1
 		dummyDbMan = &dummyDbManager{}
 		testApiMan = &apiManager{
-			dbMan:               dummyDbMan,
-			deploymentsEndpoint: deploymentsEndpoint + strconv.Itoa(testCount),
-			blobEndpoint:        blobEndpointPath + strconv.Itoa(testCount) + "/{blobId}",
-			eTag:                int64(testCount * 10),
-			deploymentsChanged:  make(chan interface{}, 5),
-			addSubscriber:       make(chan chan deploymentsResult),
-			removeSubscriber:    make(chan chan deploymentsResult),
+			dbMan:                dummyDbMan,
+			deploymentsEndpoint:  deploymentsEndpoint + strconv.Itoa(testCount),
+			blobEndpoint:         blobEndpointPath + strconv.Itoa(testCount) + "/{blobId}",
+			deploymentIdEndpoint: deploymentsEndpoint + strconv.Itoa(testCount) + "/{configId}",
+			eTag:                 int64(testCount * 10),
+			deploymentsChanged:   make(chan interface{}, 5),
+			addSubscriber:        make(chan chan deploymentsResult),
+			removeSubscriber:     make(chan chan deploymentsResult),
 		}
 		testApiMan.InitAPI()
 		time.Sleep(100 * time.Millisecond)
@@ -114,6 +116,40 @@
 
 		})
 
+		It("should get configs by filter", func() {
+			typeFilter := "ORGANIZATION"
+			// setup http client
+			uri, err := url.Parse(apiTestUrl)
+			Expect(err).Should(Succeed())
+			uri.Path = deploymentsEndpoint + strconv.Itoa(testCount)
+			uri.RawQuery = "type=" + typeFilter
+			// set test data
+			dep := makeTestDeployment()
+
+			dummyDbMan.configurations = make(map[string]*DataDeployment)
+			dummyDbMan.configurations[typeFilter] = dep
+			detail := makeExpectedDetail(dep, strings.Split(uri.String(), "?")[0])
+
+			// http get
+			res, err := http.Get(uri.String())
+			Expect(err).Should(Succeed())
+			defer res.Body.Close()
+			Expect(res.StatusCode).Should(Equal(http.StatusOK))
+
+			// parse response
+			var depRes ApiDeploymentResponse
+			body, err := ioutil.ReadAll(res.Body)
+			Expect(err).Should(Succeed())
+			err = json.Unmarshal(body, &depRes)
+			Expect(err).Should(Succeed())
+
+			// verify response
+			Expect(depRes.Kind).Should(Equal(kindCollection))
+			Expect(depRes.Self).Should(Equal(uri.String()))
+			Expect(depRes.ApiDeploymentsResponse).Should(Equal([]ApiDeploymentDetails{*detail}))
+
+		})
+
 		It("should get 304 for no change", func() {
 
 			// setup http client
@@ -253,7 +289,7 @@
 
 			// set test data
 			testFile, err := ioutil.TempFile(bundlePath, "test")
-			randString := GenerateUUID()
+			randString := util.GenerateUUID()
 			testFile.Write([]byte(randString))
 			err = testFile.Close()
 			Expect(err).Should(Succeed())
@@ -274,6 +310,89 @@
 		})
 	})
 
+	Context("GET /configurations/{configId}", func() {
+		It("should get configuration according to {configId}", func() {
+			// setup http client
+			uri, err := url.Parse(apiTestUrl)
+			Expect(err).Should(Succeed())
+			uri.Path = deploymentsEndpoint + strconv.Itoa(testCount) + "/3ecd351c-1173-40bf-b830-c194e5ef9038"
+
+			//setup test data
+			dummyDbMan.err = nil
+			dummyDbMan.configurations = make(map[string]*DataDeployment)
+			expectedConfig := &DataDeployment{
+				ID:             "3ecd351c-1173-40bf-b830-c194e5ef9038",
+				OrgID:          "73fcac6c-5d9f-44c1-8db0-333efda3e6e8",
+				EnvID:          "ada76573-68e3-4f1a-a0f9-cbc201a97e80",
+				BlobID:         "gcs:SHA-512:8fcc902465ccb32ceff25fa9f6fb28e3b314dbc2874c0f8add02f4e29c9e2798d344c51807aa1af56035cf09d39c800cf605d627ba65723f26d8b9c83c82d2f2",
+				BlobResourceID: "gcs:SHA-512:0c648779da035bfe0ac21f6268049aa0ae74d9d6411dadefaec33991e55c2d66c807e06f7ef84e0947f7c7d63b8c9e97cf0684cbef9e0a86b947d73c74ae7455",
+				Type:           "ENVIRONMENT",
+				Name:           "test",
+				Revision:       "",
+				Path:           "/organizations/Org1//environments/test/",
+				Created:        "2017-06-27 03:14:46.018+00:00",
+				CreatedBy:      "defaultUser",
+				Updated:        "2017-06-27 03:14:46.018+00:00",
+				UpdatedBy:      "defaultUser",
+			}
+			dummyDbMan.configurations[expectedConfig.ID] = expectedConfig
+			// http get
+			res, err := http.Get(uri.String())
+			Expect(err).Should(Succeed())
+			defer res.Body.Close()
+			Expect(res.StatusCode).Should(Equal(http.StatusOK))
+
+			// parse response
+			var depRes ApiDeploymentDetails
+			body, err := ioutil.ReadAll(res.Body)
+			Expect(err).Should(Succeed())
+			err = json.Unmarshal(body, &depRes)
+			Expect(err).Should(Succeed())
+
+			// verify response
+			Expect(depRes.Self).Should(ContainSubstring(expectedConfig.ID))
+			Expect(depRes.Org).Should(Equal(expectedConfig.OrgID))
+			Expect(depRes.Name).Should(Equal(expectedConfig.Name))
+			Expect(depRes.Type).Should(Equal(expectedConfig.Type))
+			Expect(depRes.Revision).Should(Equal(expectedConfig.Revision))
+			Expect(depRes.BeanBlobUrl).Should(ContainSubstring(expectedConfig.BlobID))
+			Expect(depRes.ResourceBlobUrl).Should(ContainSubstring(expectedConfig.BlobResourceID))
+			Expect(depRes.Path).Should(Equal(expectedConfig.Path))
+			Expect(depRes.Created).Should(Equal(convertTime(expectedConfig.Created)))
+			Expect(depRes.Updated).Should(Equal(convertTime(expectedConfig.Updated)))
+		})
+
+		It("should get error responses", func() {
+			// setup http client
+			uri, err := url.Parse(apiTestUrl)
+			Expect(err).Should(Succeed())
+
+			//setup test data
+			testData := [][]interface{}{
+				{util.GenerateUUID(), sql.ErrNoRows},
+				{util.GenerateUUID(), fmt.Errorf("test error")},
+			}
+			expectedCode := []int{
+				http.StatusNotFound,
+				http.StatusInternalServerError,
+			}
+
+			for i, data := range testData {
+				if data[1] != nil {
+					dummyDbMan.err = data[1].(error)
+				}
+				dummyDbMan.configurations = make(map[string]*DataDeployment)
+				dummyDbMan.configurations[data[0].(string)] = &DataDeployment{}
+				// http get
+				uri.Path = deploymentsEndpoint + strconv.Itoa(testCount) + "/" + data[0].(string)
+				res, err := http.Get(uri.String())
+				Expect(err).Should(Succeed())
+				Expect(res.StatusCode).Should(Equal(expectedCode[i]))
+				res.Body.Close()
+			}
+		})
+	})
+
 })
 
 func setTestDeployments(dummyDbMan *dummyDbManager, self string) []ApiDeploymentDetails {
@@ -298,9 +417,9 @@
 
 func makeTestDeployment() *DataDeployment {
 	dep := &DataDeployment{
-		ID:             GenerateUUID(),
-		OrgID:          GenerateUUID(),
-		EnvID:          GenerateUUID(),
+		ID:             util.GenerateUUID(),
+		OrgID:          util.GenerateUUID(),
+		EnvID:          util.GenerateUUID(),
 		BlobID:         testBlobId,
 		BlobResourceID: "",
 		Type:           "virtual-host",
@@ -338,6 +457,8 @@
 	localFSLocation  string
 	fileResponse     chan string
 	version          string
+	configurations   map[string]*DataDeployment
+	err              error
 }
 
 func (d *dummyDbManager) setDbVersion(version string) {
@@ -352,8 +473,11 @@
 	return d.unreadyBlobIds, nil
 }
 
-func (d *dummyDbManager) getReadyDeployments() ([]DataDeployment, error) {
-	return d.readyDeployments, nil
+func (d *dummyDbManager) getReadyDeployments(typeFilter string) ([]DataDeployment, error) {
+	if typeFilter == "" {
+		return d.readyDeployments, nil
+	}
+	return []DataDeployment{*(d.configurations[typeFilter])}, nil
 }
 
 func (d *dummyDbManager) updateLocalFsLocation(blobId, localFsLocation string) error {
@@ -374,15 +498,6 @@
 	return d.localFSLocation, nil
 }
 
-func GenerateUUID() string {
-
-	buff := make([]byte, 16)
-	numRead, err := rand.Read(buff)
-	if numRead != len(buff) || err != nil {
-		panic(err)
-	}
-	/* uuid v4 spec */
-	buff[6] = (buff[6] | 0x40) & 0x4F
-	buff[8] = (buff[8] | 0x80) & 0xBF
-	return fmt.Sprintf("%x-%x-%x-%x-%x", buff[0:4], buff[4:6], buff[6:8], buff[8:10], buff[10:])
+func (d *dummyDbManager) getConfigById(id string) (*DataDeployment, error) {
+	return d.configurations[id], d.err
 }
diff --git a/bundle_test.go b/bundle_test.go
index 7946f96..30760cc 100644
--- a/bundle_test.go
+++ b/bundle_test.go
@@ -19,6 +19,7 @@
 
 	"bytes"
 	"encoding/json"
+	"github.com/apid/apid-core/util"
 	"github.com/gorilla/mux"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
@@ -96,7 +97,7 @@
 
 	It("should download blob according to id", func() {
 		// download blob
-		id := GenerateUUID()
+		id := util.GenerateUUID()
 		testBundleMan.enqueueRequest(testBundleMan.makeDownloadRequest(id))
 		received := <-dummyDbMan.fileResponse
 		Expect(received).Should(Equal(id))
@@ -110,7 +111,7 @@
 		testBundleMan.bundleRetryDelay = 50 * time.Millisecond
 
 		// download blobs
-		id := GenerateUUID()
+		id := util.GenerateUUID()
 		testBundleMan.enqueueRequest(testBundleMan.makeDownloadRequest(id))
 		received := <-dummyDbMan.fileResponse
 		Expect(received).Should(Equal(id))
@@ -126,7 +127,7 @@
 		testBundleMan.markDeploymentFailedAfter = 200 * time.Millisecond
 
 		// download blobs
-		id := GenerateUUID()
+		id := util.GenerateUUID()
 		req := testBundleMan.makeDownloadRequest(id)
 		Expect(req.markFailedAt.After(time.Now())).Should(BeTrue())
 		testBundleMan.enqueueRequest(req)
diff --git a/data.go b/data.go
index 564ce6d..d424e2b 100644
--- a/data.go
+++ b/data.go
@@ -49,9 +49,10 @@
 	setDbVersion(string)
 	initDb() error
 	getUnreadyBlobs() ([]string, error)
-	getReadyDeployments() ([]DataDeployment, error)
+	getReadyDeployments(typeFilter string) ([]DataDeployment, error)
 	updateLocalFsLocation(string, string) error
 	getLocalFSLocation(string) (string, error)
+	getConfigById(string) (*DataDeployment, error)
 }
 
 type dbManager struct {
@@ -99,6 +100,31 @@
 	return nil
 }
 
+func (dbc *dbManager) getConfigById(id string) (config *DataDeployment, err error) {
+	row := dbc.getDb().QueryRow(`
+	SELECT 	a.id,
+			a.organization_id,
+			a.environment_id,
+			a.bean_blob_id,
+			a.resource_blob_id,
+			a.type,
+			a.name,
+			a.revision,
+			a.path,
+			a.created_at,
+			a.created_by,
+			a.updated_at,
+			a.updated_by
+		FROM metadata_runtime_entity_metadata as a
+		WHERE a.id = ?;
+	`, id)
+	config, err = dataDeploymentsFromRow(row)
+	if err != nil {
+		return nil, err
+	}
+	return config, nil
+}
+
 // getUnreadyDeployments() returns array of resources that are not yet to be processed
 func (dbc *dbManager) getUnreadyBlobs() (ids []string, err error) {
 
@@ -135,11 +161,15 @@
 	return
 }
 
-func (dbc *dbManager) getReadyDeployments() ([]DataDeployment, error) {
+func (dbc *dbManager) getReadyDeployments(typeFilter string) ([]DataDeployment, error) {
 
 	// An alternative statement is in get_ready_deployments.sql
 	// Need testing with large data volume to determine which is better
-	rows, err := dbc.getDb().Query(`
+
+	var rows *sql.Rows
+	var err error
+	if typeFilter == "" {
+		rows, err = dbc.getDb().Query(`
 		SELECT 	a.id,
 			a.organization_id,
 			a.environment_id,
@@ -179,6 +209,49 @@
 		)
 	;
 	`)
+	} else {
+		rows, err = dbc.getDb().Query(`
+		SELECT 	a.id,
+			a.organization_id,
+			a.environment_id,
+			a.bean_blob_id,
+			a.resource_blob_id,
+			a.type,
+			a.name,
+			a.revision,
+			a.path,
+			a.created_at,
+			a.created_by,
+			a.updated_at,
+			a.updated_by
+		FROM metadata_runtime_entity_metadata as a
+		WHERE a.type = ?
+		AND a.id IN (
+			SELECT
+					a.id
+				FROM metadata_runtime_entity_metadata as a
+				INNER JOIN apid_blob_available as b
+				ON a.resource_blob_id = b.id
+				WHERE a.resource_blob_id IS NOT NULL AND a.resource_blob_id != ""
+			INTERSECT
+				SELECT
+					a.id
+				FROM metadata_runtime_entity_metadata as a
+				INNER JOIN apid_blob_available as b
+				ON a.bean_blob_id = b.id
+				WHERE a.resource_blob_id IS NOT NULL AND a.resource_blob_id != ""
+
+			UNION
+				SELECT
+					a.id
+				FROM metadata_runtime_entity_metadata as a
+				INNER JOIN apid_blob_available as b
+				ON a.bean_blob_id = b.id
+				WHERE a.resource_blob_id IS NULL OR a.resource_blob_id = ""
+		)
+	;
+	`, typeFilter)
+	}
 
 	if err != nil {
 		log.Errorf("DB Query for project_runtime_blob_metadata failed %v", err)
@@ -186,7 +259,7 @@
 	}
 	defer rows.Close()
 
-	deployments, err := dataDeploymentsFromRow(rows)
+	deployments, err := dataDeploymentsFromRows(rows)
 	if err != nil {
 		return nil, err
 	}
@@ -244,7 +317,7 @@
 	return
 }
 
-func dataDeploymentsFromRow(rows *sql.Rows) ([]DataDeployment, error) {
+func dataDeploymentsFromRows(rows *sql.Rows) ([]DataDeployment, error) {
 	tmp, err := structFromRows(reflect.TypeOf((*DataDeployment)(nil)).Elem(), rows)
 	if err != nil {
 		return nil, err
@@ -275,3 +348,35 @@
 	}
 	return slice.Interface(), nil
 }
+
+func dataDeploymentsFromRow(row *sql.Row) (*DataDeployment, error) {
+	tmp, err := structFromRow(reflect.TypeOf((*DataDeployment)(nil)).Elem(), row)
+	if err != nil {
+		if err != sql.ErrNoRows {
+			log.Errorf("Error in dataDeploymentsFromRow: %v", err)
+		}
+		return nil, err
+	}
+	config := tmp.(DataDeployment)
+	return &config, nil
+}
+
+func structFromRow(t reflect.Type, row *sql.Row) (interface{}, error) {
+	num := t.NumField()
+	cols := make([]interface{}, num)
+	for i := range cols {
+		cols[i] = new(sql.NullString)
+	}
+	v := reflect.New(t).Elem()
+	err := row.Scan(cols...)
+	if err != nil {
+		return nil, err
+	}
+	for i := range cols {
+		p := cols[i].(*sql.NullString)
+		if p.Valid {
+			v.Field(i).SetString(p.String)
+		}
+	}
+	return v.Interface(), nil
+}
diff --git a/data_test.go b/data_test.go
index abd9fc9..fbbb0a7 100644
--- a/data_test.go
+++ b/data_test.go
@@ -19,6 +19,7 @@
 	"github.com/apid/apid-core/data"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
+	"reflect"
 	"strconv"
 	"sync"
 	"time"
@@ -90,7 +91,7 @@
 		})
 
 		It("should get empty slice if no deployments are ready", func() {
-			deps, err := testDbMan.getReadyDeployments()
+			deps, err := testDbMan.getReadyDeployments("")
 			Expect(err).Should(Succeed())
 			Expect(len(deps)).Should(BeZero())
 		})
@@ -123,14 +124,41 @@
 			Expect(location).Should(Equal(testBlobLocalFsPrefix + testBlobId))
 		})
 
-		It("should succefully get all ready deployments", func() {
+		It("should get configuration by Id", func() {
+			config, err := testDbMan.getConfigById("3ecd351c-1173-40bf-b830-c194e5ef9038")
+			Expect(err).Should(Succeed())
+			expectedResponse := &DataDeployment{
+				ID:             "3ecd351c-1173-40bf-b830-c194e5ef9038",
+				OrgID:          "73fcac6c-5d9f-44c1-8db0-333efda3e6e8",
+				EnvID:          "ada76573-68e3-4f1a-a0f9-cbc201a97e80",
+				BlobID:         "gcs:SHA-512:8fcc902465ccb32ceff25fa9f6fb28e3b314dbc2874c0f8add02f4e29c9e2798d344c51807aa1af56035cf09d39c800cf605d627ba65723f26d8b9c83c82d2f2",
+				BlobResourceID: "gcs:SHA-512:0c648779da035bfe0ac21f6268049aa0ae74d9d6411dadefaec33991e55c2d66c807e06f7ef84e0947f7c7d63b8c9e97cf0684cbef9e0a86b947d73c74ae7455",
+				Type:           "ENVIRONMENT",
+				Name:           "test",
+				Revision:       "",
+				Path:           "/organizations/Org1//environments/test/",
+				Created:        "2017-06-27 03:14:46.018+00:00",
+				CreatedBy:      "defaultUser",
+				Updated:        "2017-06-27 03:14:46.018+00:00",
+				UpdatedBy:      "defaultUser",
+			}
+			Expect(config).ShouldNot(BeNil())
+			Expect(reflect.DeepEqual(expectedResponse, config)).Should(BeTrue())
+		})
+
+		It("should get non-nil error for nonexistent Id", func() {
+			_, err := testDbMan.getConfigById("3ecd351c-aaaa-40bf-b830-c194e5ef9038")
+			Expect(err).ShouldNot(Succeed())
+		})
+
+		It("should successfully get all ready configurations", func() {
 
 			err := testDbMan.updateLocalFsLocation(readyBlobId, testBlobLocalFsPrefix+readyBlobId)
 			Expect(err).Should(Succeed())
 			err = testDbMan.updateLocalFsLocation(readyResourceId, testBlobLocalFsPrefix+readyResourceId)
 			Expect(err).Should(Succeed())
 
-			deps, err := testDbMan.getReadyDeployments()
+			deps, err := testDbMan.getReadyDeployments("")
 			Expect(err).Should(Succeed())
 			Expect(len(deps)).Should(Equal(2))
 			for _, dep := range deps {
@@ -141,6 +169,28 @@
 			}
 		})
 
+		It("should get ready configurations by type filter", func() {
+
+			err := testDbMan.updateLocalFsLocation(readyBlobId, testBlobLocalFsPrefix+readyBlobId)
+			Expect(err).Should(Succeed())
+			err = testDbMan.updateLocalFsLocation(readyResourceId, testBlobLocalFsPrefix+readyResourceId)
+			Expect(err).Should(Succeed())
+
+			deps, err := testDbMan.getReadyDeployments("ORGANIZATION")
+			Expect(err).Should(Succeed())
+			Expect(len(deps)).Should(Equal(1))
+			Expect(deps[0].ID).Should(Equal("319963ff-217e-4ecc-8d6e-c3665e962d1e"))
+
+			deps, err = testDbMan.getReadyDeployments("ENVIRONMENT")
+			Expect(err).Should(Succeed())
+			Expect(len(deps)).Should(Equal(1))
+			Expect(deps[0].ID).Should(Equal("1dc4895e-6494-4b59-979f-5f4c89c073b4"))
+
+			deps, err = testDbMan.getReadyDeployments("INVALID-TYPE")
+			Expect(err).Should(Succeed())
+			Expect(len(deps)).Should(Equal(0))
+		})
+
 		It("should succefully get all unready blob ids", func() {
 
 			err := testDbMan.updateLocalFsLocation(readyBlobId, testBlobLocalFsPrefix+readyBlobId)
@@ -193,10 +243,10 @@
 		'',
 		'gcs:SHA-512:39ca7ae89bb9468af34df8bc873748b4035210c91bcc01359c092c1d51364b5f3df06bc69a40621acfaa46791af9ea41bc0f3429a84738ba1a7c8d394859601a',
 		NULL,
-		'ORGANIZATION',
+		'ENVIRONMENT',
 		'Org1',
 		'',
-		'/organizations/Org1/',
+		'/organizations/edgex01//environments/prod/',
 		'2017-06-27 03:14:45.748+00:00',
 		'defaultUser',
 		'2017-06-27 03:15:03.557+00:00',
diff --git a/init.go b/init.go
index 037e5ea..bf85583 100644
--- a/init.go
+++ b/init.go
@@ -133,14 +133,15 @@
 	// initialize api manager
 
 	apiMan := &apiManager{
-		dbMan:               dbMan,
-		deploymentsEndpoint: deploymentsEndpoint,
-		blobEndpoint:        blobEndpoint,
-		eTag:                0,
-		deploymentsChanged:  make(chan interface{}, 5),
-		addSubscriber:       make(chan chan deploymentsResult),
-		removeSubscriber:    make(chan chan deploymentsResult),
-		apiInitialized:      false,
+		dbMan:                dbMan,
+		deploymentsEndpoint:  deploymentsEndpoint,
+		blobEndpoint:         blobEndpoint,
+		deploymentIdEndpoint: deploymentIdEndpoint,
+		eTag:                 0,
+		deploymentsChanged:   make(chan interface{}, 5),
+		addSubscriber:        make(chan chan deploymentsResult),
+		removeSubscriber:     make(chan chan deploymentsResult),
+		apiInitialized:       false,
 	}
 
 	// initialize bundle manager
diff --git a/listener_test.go b/listener_test.go
index d270be0..55a71c5 100644
--- a/listener_test.go
+++ b/listener_test.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"github.com/apid/apid-core"
+	"github.com/apid/apid-core/util"
 	"github.com/apigee-labs/transicator/common"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
@@ -61,7 +62,7 @@
 			unreadyBlobIds := make([]string, 0)
 			blobMap := make(map[string]int)
 			for i := 0; i < 1+rand.Intn(10); i++ {
-				id := GenerateUUID()
+				id := util.GenerateUUID()
 				blobMap[id] = 1
 				unreadyBlobIds = append(unreadyBlobIds, id)
 			}
@@ -172,12 +173,12 @@
 			blobIdOld := make(map[string]int)
 			for i := 0; i < 1+rand.Intn(10); i++ {
 				depNew := makeTestDeployment()
-				depNew.BlobID = GenerateUUID()
-				depNew.BlobResourceID = GenerateUUID()
+				depNew.BlobID = util.GenerateUUID()
+				depNew.BlobResourceID = util.GenerateUUID()
 
 				depOld := makeTestDeployment()
-				depOld.BlobID = GenerateUUID()
-				depOld.BlobResourceID = GenerateUUID()
+				depOld.BlobID = util.GenerateUUID()
+				depOld.BlobResourceID = util.GenerateUUID()
 
 				change := common.Change{
 					Operation: common.Update,
@@ -221,14 +222,14 @@
 
 			for i := 0; i < 1+rand.Intn(10); i++ {
 				depNew := makeTestDeployment()
-				depNew.BlobID = GenerateUUID()
-				depNew.BlobResourceID = GenerateUUID()
+				depNew.BlobID = util.GenerateUUID()
+				depNew.BlobResourceID = util.GenerateUUID()
 
 				depOld := makeTestDeployment()
 
 				if rand.Intn(2) == 0 {
 					// blob id changed
-					depOld.BlobID = GenerateUUID()
+					depOld.BlobID = util.GenerateUUID()
 					blobIdChangedNew[depNew.BlobID]++
 					blobIdChangedOld[depOld.BlobID]++
 				} else {
@@ -238,7 +239,7 @@
 
 				if rand.Intn(2) == 0 {
 					// blob id changed
-					depOld.BlobResourceID = GenerateUUID()
+					depOld.BlobResourceID = util.GenerateUUID()
 					blobIdChangedNew[depNew.BlobResourceID]++
 					blobIdChangedOld[depOld.BlobResourceID]++
 				} else {