You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by mi...@apache.org on 2018/05/18 18:03:04 UTC

[incubator-trafficcontrol] branch master updated: Add TO Go deliveryservicesregexes

This is an automated email from the ASF dual-hosted git repository.

mitchell852 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 2486016  Add TO Go deliveryservicesregexes
2486016 is described below

commit 2486016d3e6a45d1b70948ee43393b2dc7fd3784
Author: Robert Butts <ro...@apache.org>
AuthorDate: Tue Apr 24 08:37:55 2018 -0600

    Add TO Go deliveryservicesregexes
---
 lib/go-tc/deliveryservice_regexes.go               |  34 ++
 traffic_ops/traffic_ops_golang/api/change_log.go   |  42 +-
 .../deliveryservicesregexes.go                     | 591 +++++++++++++++++++++
 traffic_ops/traffic_ops_golang/routes.go           |  13 +-
 4 files changed, 660 insertions(+), 20 deletions(-)

diff --git a/lib/go-tc/deliveryservice_regexes.go b/lib/go-tc/deliveryservice_regexes.go
index df214fe..117d61f 100644
--- a/lib/go-tc/deliveryservice_regexes.go
+++ b/lib/go-tc/deliveryservice_regexes.go
@@ -31,3 +31,37 @@ type DeliveryServiceRegex struct {
 	SetNumber int    `json:"setNumber"`
 	Pattern   string `json:"pattern"`
 }
+
+type DeliveryServiceRegexV12 struct {
+	DeliveryServiceRegex
+}
+
+type DeliveryServiceRegexV13 struct {
+	DeliveryServiceRegexV12
+}
+
+type DeliveryServiceIDRegexResponse struct {
+	Response []DeliveryServiceIDRegex `json:"response"`
+}
+
+type DeliveryServiceIDRegex struct {
+	ID        int    `json:"id"`
+	Type      int    `json:"type"`
+	TypeName  string `json:"typeName"`
+	SetNumber int    `json:"setNumber"`
+	Pattern   string `json:"pattern"`
+}
+
+type DeliveryServiceIDRegexV12 struct {
+	DeliveryServiceIDRegex
+}
+
+type DeliveryServiceIDRegexV13 struct {
+	DeliveryServiceIDRegexV12
+}
+
+type DeliveryServiceRegexPost struct {
+	Type      int    `json:"type"`
+	SetNumber int    `json:"setNumber"`
+	Pattern   string `json:"pattern"`
+}
diff --git a/traffic_ops/traffic_ops_golang/api/change_log.go b/traffic_ops/traffic_ops_golang/api/change_log.go
index f2fb18f..1b30429 100644
--- a/traffic_ops/traffic_ops_golang/api/change_log.go
+++ b/traffic_ops/traffic_ops_golang/api/change_log.go
@@ -51,28 +51,34 @@ const (
 )
 
 func CreateChangeLog(level string, action string, i Identifier, user auth.CurrentUser, db *sqlx.DB) error {
-	keys, _ := i.GetKeys()
-	keysString := "{ "
-	for key, value := range keys {
-		keysString += key + ":" + fmt.Sprintf("%v", value) + " "
+	t, ok := i.(ChangeLogger)
+	if !ok {
+		keys, _ := i.GetKeys()
+		return CreateChangeLogBuildMsg(level, action, user, db, i.GetType(), i.GetAuditName(), keys)
+	}
+	msg, err := t.ChangeLogMessage(action)
+	if err != nil {
+		log.Errorf("%++v creating log message for %++v", err, t)
+		keys, _ := i.GetKeys()
+		return CreateChangeLogBuildMsg(level, action, user, db, i.GetType(), i.GetAuditName(), keys)
 	}
-	keysString += "}"
-	message := action + " " + i.GetType() + ": " + i.GetAuditName() + " keys: " + keysString
-	// if the object has its own log message generation, use it
-	if t, ok := i.(ChangeLogger); ok {
-		m, err := t.ChangeLogMessage(action)
-		if err != nil {
-			log.Errorf("error %++v creating log message for %++v", err, t)
-			// use the default message in this case
-		} else {
-			message = m
-		}
+	return CreateChangeLogMsg(level, user, db, msg)
+}
+
+func CreateChangeLogBuildMsg(level string, action string, user auth.CurrentUser, db *sqlx.DB, objType string, auditName string, keys map[string]interface{}) error {
+	keyStr := "{ "
+	for key, value := range keys {
+		keyStr += key + ":" + fmt.Sprintf("%v", value) + " "
 	}
+	keyStr += "}"
+	msg := action + " " + objType + ": " + auditName + " keys: " + keyStr
+	return CreateChangeLogMsg(level, user, db, msg)
+}
 
+func CreateChangeLogMsg(level string, user auth.CurrentUser, db *sqlx.DB, msg string) error {
 	query := `INSERT INTO log (level, message, tm_user) VALUES ($1, $2, $3)`
-	log.Debugf("about to exec %s with %s", query, message)
-	_, err := db.Exec(query, level, message, user.ID)
-	if err != nil {
+	log.Debugf("about to exec %s with %s", query, msg)
+	if _, err := db.Exec(query, level, msg, user.ID); err != nil {
 		log.Errorf("received error: %++v from audit log insertion", err)
 		return err
 	}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservicesregexes/deliveryservicesregexes.go b/traffic_ops/traffic_ops_golang/deliveryservicesregexes/deliveryservicesregexes.go
new file mode 100644
index 0000000..8284242
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservicesregexes/deliveryservicesregexes.go
@@ -0,0 +1,591 @@
+package deliveryservicesregexes
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import (
+	"database/sql"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
+	// "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+	// "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tovalidate"
+
+	// "github.com/asaskevich/govalidator"
+	// "github.com/go-ozzo/ozzo-validation"
+	"github.com/jmoiron/sqlx"
+	// "github.com/lib/pq"
+)
+
+func Get(dbx *sqlx.DB) http.HandlerFunc {
+	db := dbx.DB
+	return func(w http.ResponseWriter, r *http.Request) {
+		handleErrs := tc.GetHandleErrorsFunc(w, r)
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			log.Errorf("unable to retrieve current user from context: %s", err)
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		q := `
+SELECT ds.xml_id, ds.tenant_id, dsr.set_number, r.pattern, rt.name as type
+FROM deliveryservice_regex as dsr
+JOIN deliveryservice as ds ON dsr.deliveryservice = ds.id
+JOIN regex as r ON dsr.regex = r.id
+JOIN type as rt ON r.type = rt.id
+`
+		rows, err := db.Query(q)
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("querying: "+err.Error()))
+			return
+		}
+		dsRegexes := map[tc.DeliveryServiceName][]tc.DeliveryServiceRegex{}
+		for rows.Next() {
+			// if (!$tenant_utils->is_ds_resource_accessible($tenants_data, $ds->tenant_id)) {
+			// 	next;
+			// }
+			dsName := ""
+			dsTenantID := 0
+			setNumber := 0
+			pattern := ""
+			typeName := ""
+			if err = rows.Scan(&dsName, &dsTenantID, &setNumber, &pattern, &typeName); err != nil {
+				handleErrs(http.StatusInternalServerError, errors.New("querying: "+err.Error()))
+				return
+			}
+			if ok, err := tenant.IsResourceAuthorizedToUser(dsTenantID, *user, dbx); !ok {
+				continue
+			} else if err != nil {
+				handleErrs(http.StatusInternalServerError, errors.New("checking tenancy: "+err.Error()))
+				return
+			}
+			dsRegexes[tc.DeliveryServiceName(dsName)] = append(dsRegexes[tc.DeliveryServiceName(dsName)], tc.DeliveryServiceRegex{
+				Type:      typeName,
+				SetNumber: setNumber,
+				Pattern:   pattern,
+			})
+		}
+		respRegexes := []tc.DeliveryServiceRegexes{}
+		for dsName, regexes := range dsRegexes {
+			respRegexes = append(respRegexes, tc.DeliveryServiceRegexes{DSName: string(dsName), Regexes: regexes})
+		}
+		respBts, err := json.Marshal(&tc.DeliveryServiceRegexResponse{Response: respRegexes})
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("marshalling JSON: "+err.Error()))
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(respBts)
+	}
+}
+
+func DSGet(dbx *sqlx.DB) http.HandlerFunc {
+	db := dbx.DB
+	return func(w http.ResponseWriter, r *http.Request) {
+		handleErrs := tc.GetHandleErrorsFunc(w, r)
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			log.Errorf("unable to retrieve current user from context: %s", err)
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		params, err := api.GetCombinedParams(r)
+		if err != nil {
+			log.Errorf("unable to get parameters from request: %s", err)
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		dsIDStr, ok := params["dsid"]
+		if !ok {
+			log.Errorln("no delivery service ID")
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		dsID, err := strconv.Atoi(dsIDStr)
+		if err != nil {
+			log.Errorln("Delivery service ID '" + dsIDStr + "' not an integer")
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+
+		q := `
+SELECT ds.tenant_id, dsr.set_number, r.id, r.pattern, rt.id as type, rt.name as type_name
+FROM deliveryservice_regex as dsr
+JOIN deliveryservice as ds ON dsr.deliveryservice = ds.id
+JOIN regex as r ON dsr.regex = r.id
+JOIN type as rt ON r.type = rt.id
+WHERE ds.ID = $1
+ORDER BY dsr.set_number ASC
+`
+		rows, err := db.Query(q, dsID)
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("querying: "+err.Error()))
+			return
+		}
+		regexes := []tc.DeliveryServiceIDRegex{}
+		for rows.Next() {
+			dsTenantID := 0
+			setNumber := 0
+			id := 0
+			pattern := ""
+			typeID := 0
+			typeName := ""
+			if err = rows.Scan(&dsTenantID, &setNumber, &id, &pattern, &typeID, &typeName); err != nil {
+				handleErrs(http.StatusInternalServerError, errors.New("querying: "+err.Error()))
+				return
+			}
+			if ok, err := tenant.IsResourceAuthorizedToUser(dsTenantID, *user, dbx); !ok {
+				continue
+			} else if err != nil {
+				handleErrs(http.StatusInternalServerError, errors.New("checking tenancy: "+err.Error()))
+				return
+			}
+			regexes = append(regexes, tc.DeliveryServiceIDRegex{
+				ID:        id,
+				Type:      typeID,
+				TypeName:  typeName,
+				SetNumber: setNumber,
+				Pattern:   pattern,
+			})
+		}
+		respBts, err := json.Marshal(&tc.DeliveryServiceIDRegexResponse{Response: regexes})
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("marshalling JSON: "+err.Error()))
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(respBts)
+	}
+}
+
+func DSGetID(dbx *sqlx.DB) http.HandlerFunc {
+	db := dbx.DB
+	return func(w http.ResponseWriter, r *http.Request) {
+		handleErrs := tc.GetHandleErrorsFunc(w, r)
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			log.Errorf("unable to retrieve current user from context: %s", err)
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		params, err := api.GetCombinedParams(r)
+		if err != nil {
+			log.Errorf("unable to get parameters from request: %s", err)
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		dsIDStr, ok := params["dsid"]
+		if !ok {
+			log.Errorln("no delivery service ID")
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		dsID, err := strconv.Atoi(dsIDStr)
+		if err != nil {
+			log.Errorln("Delivery service ID '" + dsIDStr + "' not an integer")
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		regexIDStr, ok := params["regexid"]
+		if !ok {
+			log.Errorln("no regex ID")
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		regexID, err := strconv.Atoi(regexIDStr)
+		if err != nil {
+			log.Errorln("Regex ID '" + regexIDStr + "' not an integer")
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+
+		q := `
+SELECT ds.tenant_id, dsr.set_number, r.id, r.pattern, rt.id as type, rt.name as type_name
+FROM deliveryservice_regex as dsr
+JOIN deliveryservice as ds ON dsr.deliveryservice = ds.id
+JOIN regex as r ON dsr.regex = r.id
+JOIN type as rt ON r.type = rt.id
+WHERE ds.ID = $1
+AND r.ID = $2
+ORDER BY dsr.set_number ASC
+`
+		rows, err := db.Query(q, dsID, regexID)
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("querying: "+err.Error()))
+			return
+		}
+		regexes := []tc.DeliveryServiceIDRegex{}
+		for rows.Next() {
+			dsTenantID := 0
+			setNumber := 0
+			id := 0
+			pattern := ""
+			typeID := 0
+			typeName := ""
+			if err = rows.Scan(&dsTenantID, &setNumber, &id, &pattern, &typeID, &typeName); err != nil {
+				handleErrs(http.StatusInternalServerError, errors.New("querying: "+err.Error()))
+				return
+			}
+			if ok, err := tenant.IsResourceAuthorizedToUser(dsTenantID, *user, dbx); !ok {
+				continue
+			} else if err != nil {
+				handleErrs(http.StatusInternalServerError, errors.New("checking tenancy: "+err.Error()))
+				return
+			}
+			regexes = append(regexes, tc.DeliveryServiceIDRegex{
+				ID:        id,
+				Type:      typeID,
+				TypeName:  typeName,
+				SetNumber: setNumber,
+				Pattern:   pattern,
+			})
+		}
+		respBts, err := json.Marshal(&tc.DeliveryServiceIDRegexResponse{Response: regexes})
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("marshalling JSON: "+err.Error()))
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(respBts)
+	}
+}
+
+func Post(dbx *sqlx.DB) http.HandlerFunc {
+	db := dbx.DB
+	return func(w http.ResponseWriter, r *http.Request) {
+		handleErrs := tc.GetHandleErrorsFunc(w, r)
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("unable to retrieve current user from context: "+err.Error()))
+			return
+		}
+		params, err := api.GetCombinedParams(r)
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("unable to get parameters from request: "+err.Error()))
+			return
+		}
+		dsIDStr, ok := params["dsid"]
+		if !ok {
+			handleErrs(http.StatusInternalServerError, errors.New("no deliveryservice ID"))
+			return
+		}
+		dsID, err := strconv.Atoi(dsIDStr)
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("deliveryservice ID not an integer"))
+			return
+		}
+		dsTenantID := 0
+		if err := db.QueryRow(`SELECT tenant_id from deliveryservice where id = $1`, dsID).Scan(&dsTenantID); err != nil {
+			if err != sql.ErrNoRows {
+				log.Errorln("getting deliveryservice name: " + err.Error())
+			}
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		if ok, err := tenant.IsResourceAuthorizedToUser(dsTenantID, *user, dbx); !ok {
+			handleErrs(http.StatusInternalServerError, errors.New("unauthorized"))
+			return
+		} else if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("checking tenancy: "+err.Error()))
+			return
+		}
+		dsr := tc.DeliveryServiceRegexPost{}
+		if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil {
+			log.Errorln("failed to parse body: " + err.Error())
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+
+		regexID := 0
+		if err := db.QueryRow(`INSERT INTO regex (pattern, type) VALUES ($1, $2) RETURNING id`, dsr.Pattern, dsr.Type).Scan(&regexID); err != nil {
+			log.Errorln("inserting regex: " + err.Error())
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+
+		if _, err := db.Exec(`INSERT INTO deliveryservice_regex (deliveryservice, regex, set_number) values ($1, $2, $3)`, dsID, regexID, dsr.SetNumber); err != nil {
+			log.Errorln("inserting deliveryservice_regex: " + err.Error())
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+
+		typeName := ""
+		if err := db.QueryRow(`SELECT name from type where id = $1`, dsr.Type).Scan(&typeName); err != nil {
+			if err != sql.ErrNoRows {
+				log.Errorln("getting regex type: " + err.Error())
+			}
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+
+		respObj := tc.DeliveryServiceIDRegex{
+			ID:        regexID,
+			Pattern:   dsr.Pattern,
+			Type:      dsr.Type,
+			TypeName:  typeName,
+			SetNumber: dsr.SetNumber,
+		}
+		resp := struct {
+			Response tc.DeliveryServiceIDRegex `json:"response"`
+			tc.Alerts
+		}{respObj, tc.CreateAlerts(tc.SuccessLevel, "Delivery service regex creation was successful.")}
+
+		respBts, err := json.Marshal(&resp)
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("marshalling JSON: "+err.Error()))
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(respBts)
+	}
+}
+
+func Put(dbx *sqlx.DB) http.HandlerFunc {
+	db := dbx.DB
+	return func(w http.ResponseWriter, r *http.Request) {
+		handleErrs := tc.GetHandleErrorsFunc(w, r)
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			log.Errorf("unable to retrieve current user from context: %s", err)
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		params, err := api.GetCombinedParams(r)
+		if err != nil {
+			log.Errorf("unable to get parameters from request: %s", err)
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		dsIDStr, ok := params["dsid"]
+		if !ok {
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		dsID, err := strconv.Atoi(dsIDStr)
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		regexIDStr, ok := params["regexid"]
+		if !ok {
+			handleErrs(http.StatusInternalServerError, errors.New("no regex ID"))
+			return
+		}
+		regexID, err := strconv.Atoi(regexIDStr)
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("Regex ID '"+regexIDStr+"' not an integer"))
+			return
+		}
+		dsTenantID := 0
+		if err := db.QueryRow(`SELECT tenant_id from deliveryservice where id = $1`, dsID).Scan(&dsTenantID); err != nil {
+			if err != sql.ErrNoRows {
+				log.Errorln("getting deliveryservice name: " + err.Error())
+			}
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		if ok, err := tenant.IsResourceAuthorizedToUser(dsTenantID, *user, dbx); !ok {
+			handleErrs(http.StatusInternalServerError, errors.New("unauthorized"))
+			return
+		} else if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("checking tenancy: "+err.Error()))
+			return
+		}
+		dsr := tc.DeliveryServiceRegexPost{} // PUT uses same format as POST
+		if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil {
+			log.Errorln("failed to parse body: " + err.Error())
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		if _, err := db.Exec(`UPDATE regex SET pattern=$1, type=$2 WHERE id=$3`, dsr.Pattern, dsr.Type, regexID); err != nil {
+			log.Errorln("deliveryservicesregexes.Put: updating regex: " + err.Error())
+			handleErrs(http.StatusInternalServerError, errors.New("server error"))
+			return
+		}
+		if _, err := db.Exec(`UPDATE deliveryservice_regex SET set_number=$1 WHERE deliveryservice=$2 AND regex=$3`, dsr.SetNumber, dsID, regexID); err != nil {
+			log.Errorln("deliveryservicesregexes.Put: updating deliveryservice_regex: " + err.Error())
+			handleErrs(http.StatusInternalServerError, errors.New("server error"))
+			return
+		}
+		typeName := ""
+		if err := db.QueryRow(`SELECT name from type where id = $1`, dsr.Type).Scan(&typeName); err != nil {
+			if err != sql.ErrNoRows {
+				log.Errorln("getting regex type: " + err.Error())
+			}
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		respObj := tc.DeliveryServiceIDRegex{
+			ID:        regexID,
+			Pattern:   dsr.Pattern,
+			Type:      dsr.Type,
+			TypeName:  typeName,
+			SetNumber: dsr.SetNumber,
+		}
+		resp := struct {
+			Response tc.DeliveryServiceIDRegex `json:"response"`
+			tc.Alerts
+		}{respObj, tc.CreateAlerts(tc.SuccessLevel, "Delivery service regex creation was successful.")}
+		respBts, err := json.Marshal(&resp)
+		if err != nil {
+			handleErrs(http.StatusInternalServerError, errors.New("marshalling JSON: "+err.Error()))
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(respBts)
+	}
+}
+
+func Delete(dbx *sqlx.DB) http.HandlerFunc {
+	db := dbx.DB
+	return func(w http.ResponseWriter, r *http.Request) {
+		handleErrs := tc.GetHandleErrorsFunc(w, r)
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			log.Errorf("unable to retrieve current user from context: %s", err)
+			handleErrs(http.StatusInternalServerError, errors.New("server error"))
+			return
+		}
+		params, err := api.GetCombinedParams(r)
+		if err != nil {
+			log.Errorf("unable to get parameters from request: %s", err)
+			handleErrs(http.StatusInternalServerError, errors.New("server error"))
+			return
+		}
+		dsIDStr, ok := params["dsid"]
+		if !ok {
+			log.Errorf("deliveryservicesregexes.Delete: no dsid parameter")
+			handleErrs(http.StatusInternalServerError, errors.New("internal server error"))
+			return
+		}
+		dsID, err := strconv.Atoi(dsIDStr)
+		if err != nil {
+			handleErrs(http.StatusBadRequest, errors.New("delivery service ID is not an integer"))
+			return
+		}
+		regexIDStr, ok := params["regexid"]
+		if !ok {
+			handleErrs(http.StatusBadRequest, errors.New("no regex ID"))
+			return
+		}
+		regexID, err := strconv.Atoi(regexIDStr)
+		if err != nil {
+			handleErrs(http.StatusBadRequest, errors.New("Regex ID '"+regexIDStr+"' not an integer"))
+			return
+		}
+
+		tx, err := db.Begin()
+		if err != nil {
+			log.Errorln("could not begin transaction: " + err.Error())
+			handleErrs(http.StatusInternalServerError, errors.New("could not begin transaction: "+err.Error()))
+			return
+		}
+		commitTx := false
+		defer FinishTx(tx, &commitTx)
+
+		count := 0
+		if err := db.QueryRow(`SELECT count(*) from deliveryservice_regex where deliveryservice = $1`, dsID).Scan(&count); err != nil {
+			if err != sql.ErrNoRows {
+				handleErrs(http.StatusNotFound, errors.New("not found"))
+				return
+			}
+			log.Errorln(errors.New("getting deliveryservice regex count: " + err.Error()))
+			handleErrs(http.StatusInternalServerError, errors.New("internal server error"))
+			return
+		}
+		if count < 2 {
+			handleErrs(http.StatusBadRequest, errors.New("a delivery service must have at least one regex"))
+			return
+		}
+
+		dsTenantID := 0
+		if err := db.QueryRow(`SELECT tenant_id from deliveryservice where id = $1`, dsID).Scan(&dsTenantID); err != nil {
+			if err != sql.ErrNoRows {
+				log.Errorln("getting deliveryservice name: " + err.Error())
+			}
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+		if ok, err := tenant.IsResourceAuthorizedToUser(dsTenantID, *user, dbx); !ok {
+			handleErrs(http.StatusUnauthorized, errors.New("unauthorized"))
+			return
+		} else if err != nil {
+			log.Errorln("checking tenancy: " + err.Error())
+			handleErrs(http.StatusInternalServerError, errors.New("server error"))
+			return
+		}
+
+		result, err := tx.Exec(`DELETE FROM deliveryservice_regex WHERE deliveryservice = $1 and regex = $2`, dsID, regexID)
+		if err != nil {
+			log.Errorln("deliveryservicesregexes.Delete deleting delivery service regexes: " + err.Error())
+			handleErrs(http.StatusInternalServerError, errors.New("server error"))
+			return
+		}
+		rowsAffected, err := result.RowsAffected()
+		if err != nil {
+			log.Errorln("deliveryservicesregexes.Delete delete error: " + err.Error())
+			handleErrs(http.StatusInternalServerError, errors.New("server error"))
+			return
+		}
+		if rowsAffected != 1 {
+			if rowsAffected < 1 {
+				handleErrs(http.StatusNotFound, errors.New("not found"))
+				return
+			}
+			log.Errorf("this create affected too many rows: %d", rowsAffected)
+			handleErrs(http.StatusInternalServerError, errors.New("server error"))
+			return
+		}
+
+		log.Debugf("changelog for delete on object")
+		api.CreateChangeLogMsg(api.ApiChange, *user, dbx, fmt.Sprintf(`deleted deliveryservice_regex {"ds": %d, "regex": %d}`, dsID, regexID))
+		resp := struct {
+			tc.Alerts
+		}{tc.CreateAlerts(tc.SuccessLevel, "deliveryservice_regex was deleted.")}
+		respBts, err := json.Marshal(resp)
+		if err != nil {
+			log.Errorln("deliveryservicesregexes.Delete creating JSON: " + err.Error())
+			handleErrs(http.StatusInternalServerError, errors.New("server error"))
+			return
+		}
+		w.Header().Set(tc.ContentType, tc.ApplicationJson)
+		w.Write(respBts)
+		commitTx = true
+	}
+}
+
+// FinishTx commits the transaction if commit is true when it's called, otherwise it rolls back the transaction. This is designed to be called in a defer.
+func FinishTx(tx *sql.Tx, commit *bool) {
+	if tx == nil {
+		return
+	}
+	if !*commit {
+		tx.Rollback()
+		return
+	}
+	tx.Commit()
+}
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index cba2190..37a3b18 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -38,8 +38,9 @@ import (
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/coordinate"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/crconfig"
 	dsrequest "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice/request"
-	dsserver "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice/servers"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice/request/comment"
+	dsserver "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice/servers"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservicesregexes"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/division"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/hwinfo"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/parameter"
@@ -154,7 +155,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
 		{1.1, http.MethodDelete, `regions/{id}$`, api.DeleteHandler(region.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
 
 		// get all edge servers associated with a delivery service (from deliveryservice_server table)
-		{1.1, http.MethodGet, `deliveryservices/{id}/servers$`, api.ReadHandler(dsserver.GetRefType(), d.DB),auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodGet, `deliveryservices/{id}/servers$`, api.ReadHandler(dsserver.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
 
 		//Server
 		{1.1, http.MethodGet, `servers/checks$`, handlerToFunc(proxyHandler), 0, NoAuth, []Middleware{}},
@@ -224,6 +225,14 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
 		{1.3, http.MethodPost, `roles/?$`, api.CreateHandler(role.GetRefType(), d.DB), auth.PrivLevelAdmin, Authenticated, nil},
 		{1.3, http.MethodDelete, `roles/?$`, api.DeleteHandler(role.GetRefType(), d.DB), auth.PrivLevelAdmin, Authenticated, nil},
 
+		//Delivery Services Regexes
+		{1.1, http.MethodGet, `deliveryservices_regexes/?(\.json)?$`, deliveryservicesregexes.Get(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodGet, `deliveryservices/{dsid}/regexes/?(\.json)?$`, deliveryservicesregexes.DSGet(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodGet, `deliveryservices/{dsid}/regexes/{regexid}?(\.json)?$`, deliveryservicesregexes.DSGetID(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodPost, `deliveryservices/{dsid}/regexes/?(\.json)?$`, deliveryservicesregexes.Post(d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodPut, `deliveryservices/{dsid}/regexes/{regexid}?(\.json)?$`, deliveryservicesregexes.Put(d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodDelete, `deliveryservices/{dsid}/regexes/{regexid}?(\.json)?$`, deliveryservicesregexes.Delete(d.DB), auth.PrivLevelOperations, Authenticated, nil},
+
 		//Servers
 		{1.3, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler(d.DB), auth.PrivLevelOperations, Authenticated, nil},
 		{1.3, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},

-- 
To stop receiving notification emails like this one, please contact
mitchell852@apache.org.