blob: 75fbb65f2582672f221ff7cbbb5a57231379bd49 [file] [log] [blame]
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/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
"github.com/lib/pq"
)
func Get(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
q := `
SELECT ds.xml_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
WHERE ds.tenant_id = ANY($1)
`
accessibleTenants, err := tenant.GetUserTenantIDListTx(inf.Tx.Tx, inf.User.TenantID)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("getting accessible tenants for user - %v", err))
}
rows, err := inf.Tx.Tx.Query(q, pq.Array(accessibleTenants))
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("querying deliveryserviceregexes: "+err.Error()))
return
}
defer rows.Close()
dsRegexes := map[tc.DeliveryServiceName][]tc.DeliveryServiceRegex{}
for rows.Next() {
dsName := ""
setNumber := 0
pattern := ""
typeName := ""
if err = rows.Scan(&dsName, &setNumber, &pattern, &typeName); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("scanning deliveryserviceregexes: "+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})
}
api.WriteResp(w, r, respRegexes)
}
func DSGet(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"dsid"}, []string{"dsid"})
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
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
`
queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
"dsid": dbhelpers.WhereColumnInfo{"ds.ID", api.IsInt},
"id": dbhelpers.WhereColumnInfo{"r.id", api.IsInt}}
where, _, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
if len(errs) > 0 {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, util.JoinErrs(errs), nil)
return
}
accessibleTenants, err := tenant.GetUserTenantIDListTx(inf.Tx.Tx, inf.User.TenantID)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("getting accessible tenants for user - %v", err))
}
if len(where) > 0 {
where += " AND ds.tenant_id = ANY(:tenants) "
} else {
where = dbhelpers.BaseWhere + " ds.tenant_id = ANY(:tenants) "
}
queryValues["tenants"] = pq.Array(accessibleTenants)
query := q + where + " ORDER BY dsr.set_number ASC" + pagination
rows, err := inf.Tx.NamedQuery(query, queryValues)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("querying deliveryserviceregexes get: "+err.Error()))
return
}
defer rows.Close()
regexes := []tc.DeliveryServiceIDRegex{}
dsTenants := map[int]*int{}
for rows.Next() {
dsTenantID := util.IntPtr(0)
rx := tc.DeliveryServiceIDRegex{}
if err = rows.Scan(&dsTenantID, &rx.SetNumber, &rx.ID, &rx.Pattern, &rx.Type, &rx.TypeName); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("scanning deliveryserviceregexes get: "+err.Error()))
return
}
regexes = append(regexes, rx)
dsTenants[rx.ID] = dsTenantID
}
api.WriteResp(w, r, regexes)
}
func DSGetID(w http.ResponseWriter, r *http.Request) {
alerts := tc.CreateAlerts(tc.WarnLevel, "This endpoint is deprecated, please use GET '/deliveryservices/{dsid}/regexes' with query parameter id instead")
inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"dsid", "regexid"}, []string{"dsid", "regexid"})
if userErr != nil || sysErr != nil {
userErr = api.LogErr(r, errCode, userErr, sysErr)
alerts.AddNewAlert(tc.ErrorLevel, userErr.Error())
api.WriteAlerts(w, r, errCode, alerts)
return
}
defer inf.Close()
q := `
SELECT 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 ds.tenant_id = ANY($2)
AND r.ID = $3
ORDER BY dsr.set_number ASC
`
accessibleTenants, err := tenant.GetUserTenantIDListTx(inf.Tx.Tx, inf.User.TenantID)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("getting accessible tenants for user - %v", err))
}
rows, err := inf.Tx.Tx.Query(q, inf.IntParams["dsid"], pq.Array(accessibleTenants), inf.IntParams["regexid"])
if err != nil {
userErr = api.LogErr(r, errCode, userErr, errors.New("querying deliveryserviceregexes getid: "+err.Error()))
alerts.AddNewAlert(tc.ErrorLevel, userErr.Error())
api.WriteAlerts(w, r, errCode, alerts)
return
}
defer rows.Close()
regexes := []tc.DeliveryServiceIDRegex{}
for rows.Next() {
rx := tc.DeliveryServiceIDRegex{}
if err = rows.Scan(&rx.SetNumber, &rx.ID, &rx.Pattern, &rx.Type, &rx.TypeName); err != nil {
userErr = api.LogErr(r, errCode, userErr, errors.New("scanning deliveryserviceregexes getid: "+err.Error()))
alerts.AddNewAlert(tc.ErrorLevel, userErr.Error())
api.WriteAlerts(w, r, errCode, alerts)
return
}
regexes = append(regexes, rx)
}
api.WriteAlertsObj(w, r, http.StatusOK, alerts, regexes)
}
func Post(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"dsid"}, []string{"dsid"})
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
tx := inf.Tx.Tx
dsTenantID := 0
if err := tx.QueryRow(`SELECT tenant_id from deliveryservice where id = $1`, inf.IntParams["dsid"]).Scan(&dsTenantID); err != nil {
if err == sql.ErrNoRows {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil)
return
}
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("querying deliveryserviceregexes post: "+err.Error()))
return
}
if ok, err := tenant.IsResourceAuthorizedToUserTx(dsTenantID, inf.User, tx); !ok {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusUnauthorized, nil, nil)
return
} else if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("checking tenancy: "+err.Error()))
return
}
dsr := tc.DeliveryServiceRegexPost{}
if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("malformed JSON"), nil)
return
}
if err := validateDSRegexType(tx, dsr.Type); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
return
}
if err := validateDSRegexOrder(tx, inf.IntParams["dsid"], dsr.SetNumber); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
return
}
regexID := 0
if err := tx.QueryRow(`INSERT INTO regex (pattern, type) VALUES ($1, $2) RETURNING id`, dsr.Pattern, dsr.Type).Scan(&regexID); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("inserting deliveryserviceregex regex: "+err.Error()))
return
}
if _, err := tx.Exec(`INSERT INTO deliveryservice_regex (deliveryservice, regex, set_number) values ($1, $2, $3)`, inf.IntParams["dsid"], regexID, dsr.SetNumber); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("inserting deliveryserviceregex: "+err.Error()))
return
}
typeName := ""
if err := tx.QueryRow(`SELECT name from type where id = $1`, dsr.Type).Scan(&typeName); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("querying deliveryserviceregex type: "+err.Error()))
return
}
dsID := inf.IntParams["dsid"]
dsName, _, err := dbhelpers.GetDSNameFromID(inf.Tx.Tx, dsID)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting delivery service name from id: "+err.Error()))
return
}
respObj := tc.DeliveryServiceIDRegex{
ID: regexID,
Pattern: dsr.Pattern,
Type: dsr.Type,
TypeName: typeName,
SetNumber: dsr.SetNumber,
}
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+string(dsName)+", ID: "+strconv.Itoa(dsID)+", ACTION: Created a regular expression ("+dsr.Pattern+") in position "+strconv.Itoa(dsr.SetNumber), inf.User, inf.Tx.Tx)
api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Delivery service regex creation was successful.", respObj)
}
func Put(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"dsid", "regexid"}, []string{"dsid", "regexid"})
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
tx := inf.Tx.Tx
dsID := inf.IntParams["dsid"]
dsName, ok, err := dbhelpers.GetDSNameFromID(inf.Tx.Tx, dsID)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting delivery service name from id: "+err.Error()))
return
} else if !ok {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil)
return
}
regexID := inf.IntParams["regexid"]
dsTenantID := 0
if err := tx.QueryRow(`SELECT tenant_id from deliveryservice where id = $1`, dsID).Scan(&dsTenantID); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("querying deliveryserviceregex tenant: "+err.Error()))
return
}
if ok, err := tenant.IsResourceAuthorizedToUserTx(dsTenantID, inf.User, tx); !ok {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusUnauthorized, nil, nil)
return
} else if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("deliveryserviceregex put checking tenancy: "+err.Error()))
return
}
dsr := tc.DeliveryServiceRegexPost{} // PUT uses same format as POST
if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("malformed JSON"), nil)
return
}
if err := validateDSRegexOrder(tx, inf.IntParams["dsid"], dsr.SetNumber); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
return
}
if err := validateDSRegexType(tx, dsr.Type); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
return
}
if _, err := tx.Exec(`UPDATE regex SET pattern=$1, type=$2 WHERE id=$3`, dsr.Pattern, dsr.Type, regexID); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("deliveryservicesregexes.Put: updating regex: "+err.Error()))
return
}
if _, err := tx.Exec(`UPDATE deliveryservice_regex SET set_number=$1 WHERE deliveryservice=$2 AND regex=$3`, dsr.SetNumber, dsID, regexID); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("deliveryservicesregexes.Put: updating ds_regex: "+err.Error()))
return
}
typeName := ""
if err := tx.QueryRow(`SELECT name from type where id = $1`, dsr.Type).Scan(&typeName); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting ds regex type: "+err.Error()))
return
}
respObj := tc.DeliveryServiceIDRegex{
ID: regexID,
Pattern: dsr.Pattern,
Type: dsr.Type,
TypeName: typeName,
SetNumber: dsr.SetNumber,
}
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+string(dsName)+", ID: "+strconv.Itoa(dsID)+", ACTION: Updated a regular expression ("+dsr.Pattern+") in position "+strconv.Itoa(dsr.SetNumber), inf.User, inf.Tx.Tx)
api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Delivery service regex creation was successful.", respObj)
}
func validateDSRegexType(tx *sql.Tx, typeID int) error {
_, err := tc.ValidateTypeID(tx, &typeID, "regex")
return err
}
func validateDSRegexOrder(tx *sql.Tx, dsID int, order int) error {
var ds int
if order < 0 {
return errors.New("cannot add regex with order < 0")
}
err := tx.QueryRow(`
select deliveryservice from deliveryservice_regex
where deliveryservice = $1 and set_number = $2`,
dsID, order).Scan(&ds)
if err == nil {
return errors.New("cannot add regex, another regex with the same order exists")
}
return nil
}
func Delete(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"dsid", "regexid"}, []string{"dsid", "regexid"})
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
dsID := inf.IntParams["dsid"]
dsName, ok, err := dbhelpers.GetDSNameFromID(inf.Tx.Tx, dsID)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting delivery service name from id: "+err.Error()))
return
} else if !ok {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil)
return
}
regexID := inf.IntParams["regexid"]
count := 0
if err := inf.Tx.Tx.QueryRow(`SELECT count(*) from deliveryservice_regex where deliveryservice = $1`, dsID).Scan(&count); err != nil {
if err == sql.ErrNoRows {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil)
return
}
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting deliveryservice regex count: "+err.Error()))
return
}
if count < 2 {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("a delivery service must have at least one regex"), nil)
return
}
dsTenantID := 0
if err := inf.Tx.Tx.QueryRow(`SELECT tenant_id from deliveryservice where id = $1`, dsID).Scan(&dsTenantID); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting deliveryservice name: "+err.Error()))
return
}
if ok, err := tenant.IsResourceAuthorizedToUserTx(dsTenantID, inf.User, inf.Tx.Tx); !ok {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusUnauthorized, nil, nil)
return
} else if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("checking delete ds regexes tenancy : "+err.Error()))
return
}
dsrSetNumber := 0
if err := inf.Tx.Tx.QueryRow(`SELECT set_number FROM deliveryservice_regex WHERE regex = $1`, regexID).Scan(&dsrSetNumber); err != nil {
if err == sql.ErrNoRows {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("regex not found for this delivery service"), nil)
return
}
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("deliveryservicesregexes.Delete finding set number: "+err.Error()))
return
}
dsrType, dsrPattern := 0, ""
if err := inf.Tx.Tx.QueryRow(`SELECT type, pattern FROM regex WHERE id = $1`, regexID).Scan(&dsrType, &dsrPattern); err != nil {
if err == sql.ErrNoRows {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("regex not found"), nil)
return
}
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("deliveryservicesregexes.Delete finding type and pattern: "+err.Error()))
return
}
result, err := inf.Tx.Tx.Exec(`DELETE FROM deliveryservice_regex WHERE deliveryservice = $1 and regex = $2`, dsID, regexID)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("deliveryservicesregexes.Delete deleting delivery service regexes: "+err.Error()))
return
}
rowsAffected, err := result.RowsAffected()
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("deliveryservicesregexes.Delete delete error: "+err.Error()))
return
}
if rowsAffected < 1 {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil)
return
}
if rowsAffected > 1 {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("this create affected too many rows: %d", rowsAffected))
return
}
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+string(dsName)+", ID: "+strconv.Itoa(dsID)+", ACTION: Deleted a regular expression ("+dsrPattern+") in position "+strconv.Itoa(dsrSetNumber), inf.User, inf.Tx.Tx)
api.WriteRespAlert(w, r, tc.SuccessLevel, "deliveryservice_regex was deleted.")
}