This is an automated email from the ASF dual-hosted git repository.
zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new 6ca8fda7f9 Tenants rfc3339 apiv5 (#7749)
6ca8fda7f9 is described below
commit 6ca8fda7f9fc3430e729a8ebd55a0f7d9f0c140c
Author: Kurtis Michie <[email protected]>
AuthorDate: Mon Aug 28 13:54:07 2023 -0600
Tenants rfc3339 apiv5 (#7749)
* Created APIv5 routing for Tenants. Unfinished
* Fixed pointers and mismatched Tenant Struct type
* Reworked PUT
* Fixed broken tests and updated missing validation from cachegroups
* Rearranged imports
* Added to CHANGELOG.md
* Changed format mismatch for t.ParentName to be pointer
* Uncommented out tests for v4, resetting back to original
* Moved import
* Fixed changelog conflicts
---
CHANGELOG.md | 1 +
docs/source/api/v5/tenants.rst | 4 +-
docs/source/api/v5/tenants_id.rst | 2 +-
lib/go-tc/tenants.go | 40 ++
traffic_ops/testing/api/v5/tenants_test.go | 83 ++--
traffic_ops/testing/api/v5/traffic_control_test.go | 2 +-
traffic_ops/traffic_ops_golang/apitenant/tenant.go | 464 +++++++++++++++++++++
.../traffic_ops_golang/cachegroup/cachegroups.go | 17 +-
traffic_ops/traffic_ops_golang/routing/routes.go | 8 +-
traffic_ops/v5-client/deliveryservice.go | 2 +-
traffic_ops/v5-client/deliveryservice_requests.go | 2 +-
traffic_ops/v5-client/origin.go | 2 +-
traffic_ops/v5-client/tenant.go | 20 +-
traffic_ops/v5-client/user.go | 2 +-
14 files changed, 584 insertions(+), 65 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c81da6f43..6dd1d9e647 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -162,6 +162,7 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
- [#7628](https://github.com/apache/trafficcontrol/pull/7628) *Traffic Ops*
Fixed an issue where certificate chain validation failed based on leading or
trailing whitespace.
- [#7686](https://github.com/apache/trafficcontrol/pull/7686) *Traffic Ops*
Fixed secured parameters being visible when role has proper permissions.
- [#7697](https://github.com/apache/trafficcontrol/pull/7697) *Traffic Ops*
Fixed `iloPassword` and `xmppPassword` checking for priv-level instead of using
permissions.
+- [#7749](https://github.com/apache/trafficcontrol/pull/7749) *Traffic Ops*
Fixes `tenants` v5 apis to respond with `RFC3339` date/time Format.
### Removed
- [#7271](https://github.com/apache/trafficcontrol/pull/7271) Removed
components in `infrastructre/docker/`, not in use as cdn-in-a-box performs the
same functionality.
diff --git a/docs/source/api/v5/tenants.rst b/docs/source/api/v5/tenants.rst
index 2d07db1664..8927fd46df 100644
--- a/docs/source/api/v5/tenants.rst
+++ b/docs/source/api/v5/tenants.rst
@@ -95,7 +95,7 @@ Response Structure
"id": 1,
"name": "root",
"active": true,
- "lastUpdated": "2018-12-10 19:11:17+00",
+ "lastUpdated": "2023-05-30T19:52:58.183642+00:00",
"parentId": null
}
]}
@@ -164,6 +164,6 @@ Response Structure
"id": 9,
"name": "test",
"active": true,
- "lastUpdated": "2018-12-11 19:37:16+00",
+ "lastUpdated": "2023-05-30T19:52:58.183642+00:00",
"parentId": 1
}}
diff --git a/docs/source/api/v5/tenants_id.rst
b/docs/source/api/v5/tenants_id.rst
index 3746a4577c..042b225ae7 100644
--- a/docs/source/api/v5/tenants_id.rst
+++ b/docs/source/api/v5/tenants_id.rst
@@ -91,7 +91,7 @@ Response Structure
"id": 9,
"name": "quest",
"active": true,
- "lastUpdated": "2018-12-11 20:30:54+00",
+ "lastUpdated": "2023-05-30T19:52:58.183642+00:00",
"parentId": 3
}}
diff --git a/lib/go-tc/tenants.go b/lib/go-tc/tenants.go
index c048a30d8c..ff27165953 100644
--- a/lib/go-tc/tenants.go
+++ b/lib/go-tc/tenants.go
@@ -19,6 +19,8 @@ package tc
* under the License.
*/
+import "time"
+
// GetTenantsResponse is the response for a request for a group of tenants.
type GetTenantsResponse struct {
Response []Tenant `json:"response"`
@@ -72,3 +74,41 @@ type TenantAlert struct {
Level string `json:"level"`
Text string `json:"text"`
}
+
+// GetTenantsResponseV50 is the response for a request for a group of tenants.
+type GetTenantsResponseV50 struct {
+ Response []TenantV5 `json:"response"`
+ Alerts
+}
+
+// TenantResponseV50 is the type of response from Traffic Ops to a PUT, POST,
+// or DELETE request made to its /tenants.
+type TenantResponseV50 struct {
+ Response TenantV5 `json:"response"`
+ Alerts
+}
+
+// A TenantV50 is a scope that can be applied to groups of users to limit their
+// Delivery Service-related actions to specific sets of similarly "Tenanted"
+// Delivery Services.
+type TenantV50 struct {
+ ID *int `json:"id" db:"id"`
+ Name *string `json:"name" db:"name"`
+ Active *bool `json:"active" db:"active"`
+ LastUpdated *time.Time `json:"lastUpdated" db:"last_updated"`
+ ParentID *int `json:"parentId" db:"parent_id"`
+ ParentName *string `json:"parentName,omitempty" db:"parent_name"`
+}
+
+// GetTenantsResponseV5 is the type of response from the tenants
+// Traffic Ops endpoint.
+// It always points to the type for the latest minor version of
GetTenantsResponseV5x APIv5.
+type GetTenantsResponseV5 = GetTenantsResponseV50
+
+// TenantResponseV5 is the type of response from Traffic Ops to a PUT, POST,
+// // or DELETE request made to its /tenants.
+// It always points to the type for the latest minor version of
TenantResponseV5x APIv5.
+type TenantResponseV5 = TenantResponseV50
+
+// TenantV5 always points to the type for the latest minor version of
TenantV5x APIv5.
+type TenantV5 = TenantV50
diff --git a/traffic_ops/testing/api/v5/tenants_test.go
b/traffic_ops/testing/api/v5/tenants_test.go
index dab8c60dc9..3c46776441 100644
--- a/traffic_ops/testing/api/v5/tenants_test.go
+++ b/traffic_ops/testing/api/v5/tenants_test.go
@@ -25,6 +25,7 @@ import (
"github.com/apache/trafficcontrol/lib/go-rfc"
"github.com/apache/trafficcontrol/lib/go-tc"
+ "github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/lib/go-util/assert"
"github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
"github.com/apache/trafficcontrol/traffic_ops/toclientlib"
@@ -38,7 +39,7 @@ func TestTenants(t *testing.T) {
currentTimeRFC := currentTime.Format(time.RFC1123)
tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
- methodTests := utils.TestCase[client.Session,
client.RequestOptions, tc.Tenant]{
+ methodTests := utils.TestCase[client.Session,
client.RequestOptions, tc.TenantV5]{
"GET": {
"NOT MODIFIED when NO CHANGES made": {
ClientSession: TOSession,
@@ -95,11 +96,11 @@ func TestTenants(t *testing.T) {
"POST": {
"OK when VALID request": {
ClientSession: TOSession,
- RequestBody: tc.Tenant{
- Active: true,
- Name: "tenant5",
- ParentName: "root",
- ParentID: GetTenantID(t,
"root")(),
+ RequestBody: tc.TenantV5{
+ Active: util.Ptr(true),
+ Name: util.Ptr("tenant5"),
+ ParentName: util.Ptr("root"),
+ ParentID:
util.Ptr(GetTenantID(t, "root")()),
},
Expectations:
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
validateTenantCreateUpdateFields(map[string]interface{}{"Name": "tenant5"})),
@@ -109,11 +110,11 @@ func TestTenants(t *testing.T) {
"OK when VALID request": {
EndpointID: GetTenantID(t,
"tenant4"),
ClientSession: TOSession,
- RequestBody: tc.Tenant{
- Active: false,
- Name: "newname",
- ParentName: "root",
- ParentID: GetTenantID(t,
"root")(),
+ RequestBody: tc.TenantV5{
+ Active: util.Ptr(false),
+ Name: util.Ptr("newname"),
+ ParentName: util.Ptr("root"),
+ ParentID:
util.Ptr(GetTenantID(t, "root")()),
},
Expectations:
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
validateTenantCreateUpdateFields(map[string]interface{}{"Name": "newname",
"Active": false})),
@@ -121,10 +122,10 @@ func TestTenants(t *testing.T) {
"BAD REQUEST when ROOT TENANT": {
EndpointID: GetTenantID(t, "root"),
ClientSession: TOSession,
- RequestBody: tc.Tenant{
- Active: false,
- Name: "tenant1",
- ParentName: "root",
+ RequestBody: tc.TenantV5{
+ Active: util.Ptr(false),
+ Name: util.Ptr("tenant1"),
+ ParentName: util.Ptr("root"),
},
Expectations:
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
},
@@ -132,22 +133,22 @@ func TestTenants(t *testing.T) {
EndpointID: GetTenantID(t,
"tenant2"),
ClientSession: TOSession,
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince:
{currentTimeRFC}}},
- RequestBody: tc.Tenant{
- Active: false,
- Name: "tenant2",
- ParentName: "root",
- ParentID: GetTenantID(t,
"root")(),
+ RequestBody: tc.TenantV5{
+ Active: util.Ptr(false),
+ Name: util.Ptr("tenant2"),
+ ParentName: util.Ptr("root"),
+ ParentID:
util.Ptr(GetTenantID(t, "root")()),
},
Expectations:
utils.CkRequest(utils.HasError(),
utils.HasStatus(http.StatusPreconditionFailed)),
},
"PRECONDITION FAILED when updating with IFMATCH
ETAG Header": {
EndpointID: GetTenantID(t,
"tenant2"),
ClientSession: TOSession,
- RequestBody: tc.Tenant{
- Active: false,
- Name: "tenant2",
- ParentName: "root",
- ParentID: GetTenantID(t,
"root")(),
+ RequestBody: tc.TenantV5{
+ Active: util.Ptr(false),
+ Name: util.Ptr("tenant2"),
+ ParentName: util.Ptr("root"),
+ ParentID:
util.Ptr(GetTenantID(t, "root")()),
},
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfMatch:
{rfc.ETag(currentTime)}}},
Expectations:
utils.CkRequest(utils.HasError(),
utils.HasStatus(http.StatusPreconditionFailed)),
@@ -204,16 +205,16 @@ func TestTenants(t *testing.T) {
func validateTenantFields(expectedResp map[string]interface{}) utils.CkReqFunc
{
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Tenant response to not
be nil.")
- tenantResp := resp.([]tc.Tenant)
+ tenantResp := resp.([]tc.TenantV5)
for field, expected := range expectedResp {
for _, tenant := range tenantResp {
switch field {
case "Active":
- assert.Equal(t, expected,
tenant.Active, "Expected Active to be %v, but got %b", expected, tenant.Active)
+ assert.Equal(t, expected,
*tenant.Active, "Expected Active to be %v, but got %b", expected, tenant.Active)
case "Name":
- assert.Equal(t, expected, tenant.Name,
"Expected Name to be %v, but got %s", expected, tenant.Name)
+ assert.Equal(t, expected, *tenant.Name,
"Expected Name to be %v, but got %s", expected, tenant.Name)
case "ParentName":
- assert.Equal(t, expected,
tenant.ParentName, "Expected ParentName to be %v, but got %s", expected,
tenant.ParentName)
+ assert.Equal(t, expected,
*tenant.ParentName, "Expected ParentName to be %v, but got %s", expected,
tenant.ParentName)
default:
t.Errorf("Expected field: %v, does not
exist in response", field)
}
@@ -225,8 +226,8 @@ func validateTenantFields(expectedResp
map[string]interface{}) utils.CkReqFunc {
func validateTenantCreateUpdateFields(expectedResp map[string]interface{})
utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Tenant response to not
be nil.")
- tenantResp := resp.(tc.Tenant)
- tenants := []tc.Tenant{tenantResp}
+ tenantResp := resp.(tc.TenantV5)
+ tenants := []tc.TenantV5{tenantResp}
validateTenantFields(expectedResp)(t, toclientlib.ReqInf{},
tenants, tc.Alerts{}, nil)
}
}
@@ -235,9 +236,9 @@ func validateTenantSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{},
alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Tenant response to not
be nil.")
var tenants []string
- tenantResp := resp.([]tc.Tenant)
+ tenantResp := resp.([]tc.TenantV5)
for _, tenant := range tenantResp {
- tenants = append(tenants, tenant.Name)
+ tenants = append(tenants, *tenant.Name)
}
assert.Equal(t, true, sort.StringsAreSorted(tenants), "List is
not sorted by their names: %v", tenants)
}
@@ -246,7 +247,7 @@ func validateTenantSort() utils.CkReqFunc {
func validateTenantDescSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{},
alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Tenant response to not
be nil.")
- tenantDescResp := resp.([]tc.Tenant)
+ tenantDescResp := resp.([]tc.TenantV5)
var descSortedList []string
var ascSortedList []string
assert.RequireGreaterOrEqual(t, len(tenantDescResp), 2, "Need
at least 2 Tenants in Traffic Ops to test desc sort, found: %d",
len(tenantDescResp))
@@ -257,11 +258,11 @@ func validateTenantDescSort() utils.CkReqFunc {
assert.RequireEqual(t, len(tenantsAscResp.Response),
len(tenantDescResp), "Expected descending order response length: %v, to match
ascending order response length %v", len(tenantsAscResp.Response),
len(tenantDescResp))
// Insert Tenant names to the front of a new list, so they are
now reversed to be in ascending order.
for _, tenant := range tenantDescResp {
- descSortedList = append([]string{tenant.Name},
descSortedList...)
+ descSortedList = append([]string{*tenant.Name},
descSortedList...)
}
// Insert Tenant names by appending to a new list, so they stay
in ascending order.
for _, tenant := range tenantsAscResp.Response {
- ascSortedList = append(ascSortedList, tenant.Name)
+ ascSortedList = append(ascSortedList, *tenant.Name)
}
assert.Exactly(t, ascSortedList, descSortedList, "Tenant
responses are not equal after reversal: %v - %v", ascSortedList, descSortedList)
}
@@ -269,7 +270,7 @@ func validateTenantDescSort() utils.CkReqFunc {
func validateTenantPagination(paginationParam string) utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
- paginationResp := resp.([]tc.Tenant)
+ paginationResp := resp.([]tc.TenantV5)
opts := client.NewRequestOptions()
opts.QueryParameters.Set("orderby", "id")
@@ -296,7 +297,7 @@ func GetTenantID(t *testing.T, name string) func() int {
tenants, _, err := TOSession.GetTenants(opts)
assert.RequireNoError(t, err, "Get Tenants Request failed with
error:", err)
assert.RequireEqual(t, 1, len(tenants.Response), "Expected
response object length 1, but got %d", len(tenants.Response))
- return tenants.Response[0].ID
+ return *tenants.Response[0].ID
}
}
@@ -314,13 +315,13 @@ func DeleteTestTenants(t *testing.T) {
assert.NoError(t, err, "Cannot get Tenants: %v - alerts: %+v", err,
tenants.Alerts)
for _, tenant := range tenants.Response {
- if tenant.Name == "root" {
+ if *tenant.Name == "root" {
continue
}
- alerts, _, err := TOSession.DeleteTenant(tenant.ID,
client.RequestOptions{})
+ alerts, _, err := TOSession.DeleteTenant(*tenant.ID,
client.RequestOptions{})
assert.NoError(t, err, "Unexpected error deleting Tenant '%s'
(#%d): %v - alerts: %+v", tenant.Name, tenant.ID, err, alerts.Alerts)
// Retrieve the Tenant to see if it got deleted
- opts.QueryParameters.Set("id", strconv.Itoa(tenant.ID))
+ opts.QueryParameters.Set("id", strconv.Itoa(*tenant.ID))
getTenants, _, err := TOSession.GetTenants(opts)
assert.NoError(t, err, "Error getting Tenant '%s' after
deletion: %v - alerts: %+v", tenant.Name, err, getTenants.Alerts)
assert.Equal(t, 0, len(getTenants.Response), "Expected Tenant
'%s' to be deleted, but it was found in Traffic Ops", tenant.Name)
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go
b/traffic_ops/testing/api/v5/traffic_control_test.go
index 1f453ae1c1..4154bb7f50 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -52,7 +52,7 @@ type TrafficControl struct {
Statuses []tc.StatusV5
`json:"statuses"`
StaticDNSEntries []tc.StaticDNSEntryV5
`json:"staticdnsentries"`
StatsSummaries []tc.StatsSummaryV5
`json:"statsSummaries"`
- Tenants []tc.Tenant
`json:"tenants"`
+ Tenants []tc.TenantV5
`json:"tenants"`
ServerCheckExtensions
[]tc.ServerCheckExtensionNullable `json:"servercheck_extensions"`
Topologies []tc.TopologyV5
`json:"topologies"`
Types []tc.TypeV5
`json:"types"`
diff --git a/traffic_ops/traffic_ops_golang/apitenant/tenant.go
b/traffic_ops/traffic_ops_golang/apitenant/tenant.go
index 929656490a..4047baa076 100644
--- a/traffic_ops/traffic_ops_golang/apitenant/tenant.go
+++ b/traffic_ops/traffic_ops_golang/apitenant/tenant.go
@@ -23,12 +23,14 @@ package apitenant
import (
"database/sql"
+ "encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"time"
+ "github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
"github.com/apache/trafficcontrol/lib/go-util"
@@ -36,7 +38,10 @@ import (
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
+
validation "github.com/go-ozzo/ozzo-validation"
+ "github.com/jmoiron/sqlx"
"github.com/lib/pq"
)
@@ -336,3 +341,462 @@ func deleteQuery() string {
WHERE id=:id`
return query
}
+
+func selectMaxLastUpdatedQuery(where string) string {
+ tableName := "tenant"
+ return `SELECT max(t) from (
+ SELECT max(last_updated) as t from ` + tableName + ` q ` +
where +
+ ` UNION ALL
+ select max(last_updated) as t from last_deleted l where l.table_name='`
+ tableName + `') as res`
+}
+
+// CreateTenant [Version : V5] function Process the *http.Request and creates
new tenant
+func CreateTenant(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()
+
+ tx := inf.Tx.Tx
+
+ defer r.Body.Close()
+
+ tenant, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ // Check if tenant is tenable
+ authorized, err := isTenantAuthorizedV5(&tenant, inf.User, tx)
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("checking tenant authorized: "+err.Error()))
+ return
+ }
+ if !authorized {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusForbidden,
errors.New("not authorized on this tenant"), nil)
+ return
+ }
+
+ resultRows, err := inf.Tx.NamedQuery(insertQuery(), tenant)
+ if err != nil {
+ userErr, sysErr, errCode = api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer resultRows.Close()
+
+ var id int
+ lastUpdated := time.Time{}
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err := resultRows.Scan(&id, &lastUpdated); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, errors.New("tenant create scanning: "+err.Error()))
+ return
+ }
+ }
+
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("tenant create: no tenant was inserted, no id was returned"))
+ return
+ } else if rowsAffected > 1 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("too many ids returned from tenant insert"))
+ return
+ }
+ tenant.ID = &id
+ tenant.LastUpdated = &lastUpdated
+
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "tenant was created.")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, tenant)
+ changeLogMsg := fmt.Sprintf("TENANT: %s, ID: %d, ACTION: Created
tenant", *tenant.Name, *tenant.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+ return
+}
+
+// GetTenant [Version : V5] function Process the *http.Request and writes the
response. It uses getTenant function.
+func GetTenant(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ user, _ := auth.GetCurrentUser(r.Context())
+ tenantID := user.TenantID
+ if tenantID == auth.TenantIDInvalid {
+ return
+ }
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime *time.Time
+ var usrErr error
+ var syErr error
+
+ var tenants []tc.TenantV5
+
+ api.DefaultSort(inf, "name")
+ tenants, usrErr, syErr, code, maxTime = getTenants(inf.Tx, inf.Params,
useIMS, r.Header, tenantID)
+ if usrErr != nil {
+ api.HandleErr(w, r, tx, code, fmt.Errorf("read tenant: get
tenant: "+usrErr.Error()), nil)
+ }
+ if syErr != nil {
+ api.HandleErr(w, r, tx, code, nil, fmt.Errorf("read tenant: get
tenant: "+syErr.Error()))
+ }
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ w.WriteHeader(http.StatusNotModified)
+ return
+ }
+
+ tenantNames := map[int]*string{}
+ for _, it := range tenants {
+ tenantNames[*it.ID] = it.Name
+ }
+ for _, it := range tenants {
+ if it.ParentID == nil || tenantNames[*it.ParentID] == nil {
+ // root tenant has no parent
+ continue
+ }
+ p := *tenantNames[*it.ParentID]
+ it.ParentName = &p // copy
+ }
+ api.WriteResp(w, r, tenants)
+}
+
+func getTenants(tx *sqlx.Tx, params map[string]string, useIMS bool, header
http.Header, id int) ([]tc.TenantV5, error, error, int, *time.Time) {
+ tenants := make([]tc.TenantV5, 0)
+ code := http.StatusOK
+
+ var maxTime time.Time
+ var runSecond bool
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "active": {Column: "q.active", Checker: nil},
+ "name": {Column: "q.name", Checker: nil},
+ "parent_name": {Column: "p.name", Checker: nil},
+ "id": {Column: "q.id", Checker: api.IsInt},
+ "parent_id": {Column: "q.parent_id", Checker: api.IsInt},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, util.JoinErrs(errs), nil, http.StatusBadRequest, nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = ims.TryIfModifiedSinceQuery(tx, header,
queryValues, selectMaxLastUpdatedQuery(where))
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ code = http.StatusNotModified
+ return tenants, nil, nil, code, &maxTime
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+
+ // Case where we need to run the second query
+ query := selectQuery(id) + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, nil, err, http.StatusInternalServerError, nil
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var t tc.TenantV5
+
+ if err = rows.Scan(
+ &t.ID,
+ &t.Name,
+ &t.Active,
+ &t.ParentID,
+ &t.LastUpdated,
+ &t.ParentName,
+ ); err != nil {
+ return nil, nil, err, http.StatusInternalServerError,
nil
+ }
+ tenants = append(tenants, t)
+ }
+
+ return tenants, nil, nil, http.StatusOK, nil
+}
+
+// UpdateTenant [Version : V5] function Process the *http.Request and updates
the tenant.
+func UpdateTenant(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()
+
+ tx := inf.Tx.Tx
+
+ defer r.Body.Close()
+ var tenant tc.TenantV5
+
+ tenant, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ if id, ok := inf.Params["id"]; !ok {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
errors.New("missing key: id"), nil)
+ return
+ } else {
+ idNum, err := strconv.Atoi(id)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
errors.New("couldn't convert ID into a numeric value: "+err.Error()), nil)
+ return
+ }
+ tenant.ID = &idNum
+
+ existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx,
idNum, "tenant")
+ if err == nil && found == false {
+ api.HandleErr(w, r, tx, http.StatusNotFound,
errors.New("no tenant found with this id"), nil)
+ return
+ }
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusNotFound, nil, err)
+ return
+ }
+ if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+ api.HandleErr(w, r, tx, http.StatusPreconditionFailed,
api.ResourceModifiedError, nil)
+ return
+ }
+
+ // Check if tenant is tenable
+ authorized, err := isTenantAuthorizedV5(&tenant, inf.User, tx)
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx,
http.StatusInternalServerError, nil, errors.New("checking tenant authorized:
"+err.Error()))
+ return
+ }
+ if !authorized {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusForbidden,
errors.New("not authorized on this tenant"), nil)
+ return
+ }
+
+ //Check if tenant is updatable
+ userErr, sysErr, statusCode := isUpdatableV5(&tenant, inf.Tx)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr,
sysErr)
+ return
+ }
+
+ rows, err := inf.Tx.NamedQuery(updateQuery(), tenant)
+ if err != nil {
+ userErr, sysErr, errCode = api.ParseDBError(err)
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer rows.Close()
+
+ if !rows.Next() {
+ api.HandleErr(w, r, tx, http.StatusNotFound,
errors.New("no tenant found with this id"), nil)
+ return
+ }
+ lastUpdated := time.Time{}
+ if err := rows.Scan(&lastUpdated); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, errors.New("scanning lastUpdated from tenant insert: "+err.Error()))
+ return
+ }
+ tenant.LastUpdated = &lastUpdated
+ if rows.Next() {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
nil, errors.New("tenant update affected too many rows: >1"))
+ return
+ }
+
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "tenant was
updated.")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, tenant)
+ changeLogMsg := fmt.Sprintf("TENANT: %s, ID: %d, ACTION:
Updated tenant", *tenant.Name, *tenant.ID)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User,
tx)
+ return
+ }
+}
+
+// DeleteTenant [Version : V5] function deletes the tenant passed.
+func DeleteTenant(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ ID := inf.Params["id"]
+ id, err := strconv.Atoi(ID)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusUnprocessableEntity,
fmt.Errorf("delete cachegroup: converted to type int: "+err.Error()), nil)
+ return
+ }
+
+ useIMS := false
+ var tenantV5 []tc.TenantV5
+
+ tenantV5, usrErr, syErr, code, maxTime := getTenants(inf.Tx,
inf.Params, useIMS, r.Header, id)
+ if userErr != nil {
+ api.HandleErr(w, r, tx, code, fmt.Errorf("delete tenant: get
tenant: "+usrErr.Error()), nil)
+ }
+ if sysErr != nil {
+ api.HandleErr(w, r, tx, code, nil, fmt.Errorf("delete tenant:
get tenant: "+syErr.Error()))
+ }
+ if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, *maxTime)
+ w.WriteHeader(http.StatusNotModified)
+ return
+ }
+
+ // Check if tenant is tenable
+ authorized, err := isTenantAuthorizedV5(&tenantV5[0], inf.User, tx)
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("checking tenant authorized: "+err.Error()))
+ return
+ }
+ if !authorized {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusForbidden,
errors.New("not authorized on this tenant"), nil)
+ return
+ }
+
+ res, err := tx.Exec("DELETE FROM tenant AS t WHERE t.ID=$1", id)
+ if err != nil {
+ usrErr, syErr, code := parseDeleteErr(err, id, tx)
+ api.HandleErr(w, r, tx, code, usrErr, syErr)
+ return
+ }
+ rowsAffected, err := res.RowsAffected()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("determining rows affected for delete cachegroup: %w", err))
+ return
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("no rows deleted for cachegroup"))
+ return
+ }
+
+ alertMessage := fmt.Sprint("tenant was deleted.")
+ alerts := tc.CreateAlerts(tc.SuccessLevel, alertMessage)
+ api.WriteAlerts(w, r, http.StatusOK, alerts)
+ changeLogMsg := fmt.Sprintf("TENANT: ID: %d, ACTION: Deleted tenant",
id)
+ api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+ return
+}
+
+// IsTenantAuthorized implements the Tenantable interface for TOTenant
+// returns true if the user has access on this tenant and on the ParentID if
changed.
+func isTenantAuthorizedV5(tenantV5 *tc.TenantV5, user *auth.CurrentUser, tx
*sql.Tx) (bool, error) {
+ var ok = false
+ var err error
+
+ if tenantV5 == nil {
+ // should never happen
+ return ok, err
+ }
+
+ if tenantV5.ID != nil && *tenantV5.ID != 0 {
+ // modifying an existing tenant
+ ok, err = tenant.IsResourceAuthorizedToUserTx(*tenantV5.ID,
user, tx)
+ if !ok || err != nil {
+ return ok, err
+ }
+
+ if tenantV5.ParentID == nil || *tenantV5.ParentID == 0 {
+ // not changing parent
+ return ok, err
+ }
+
+ // get current parentID to check if it's being changed
+ var parentID int
+ // If it's the root tenant, don't check for parent
+ if tenantV5.Name != nil && *tenantV5.Name != rootName {
+ err = tx.QueryRow(`SELECT parent_id FROM tenant WHERE
id = ` + strconv.Itoa(*tenantV5.ID)).Scan(&parentID)
+ if err != nil {
+ return false, err
+ }
+ if parentID == *tenantV5.ParentID {
+ // parent not being changed
+ return ok, err
+ }
+ }
+ }
+
+ // creating new tenant -- must specify a parent
+ if tenantV5.ParentID == nil || *tenantV5.ParentID == 0 {
+ return false, err
+ }
+
+ return tenant.IsResourceAuthorizedToUserTx(*tenantV5.ParentID, user, tx)
+}
+
+func isUpdatableV5(tenantV5 *tc.TenantV5, tx *sqlx.Tx) (error, error, int) {
+ if tenantV5.Name != nil && *tenantV5.Name == rootName {
+ return errors.New("trying to change the root tenant, which is
immutable"), nil, http.StatusBadRequest
+ }
+
+ // Perform SelectQuery
+ vals := []tc.TenantNullable{}
+ query := selectQuery(*tenantV5.ID)
+ rows, err := tx.Queryx(query)
+ if err != nil {
+ return nil, errors.New("querying tenant: " + err.Error()),
http.StatusInternalServerError
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var v tc.TenantNullable
+ if err = rows.StructScan(&v); err != nil {
+ return nil, errors.New("scanning tenant: " +
err.Error()), http.StatusInternalServerError
+ }
+ vals = append(vals, v)
+ }
+
+ // Ensure the new desired ParentID does not exist in the susequent list
of Children
+ for _, val := range vals {
+ if *tenantV5.ParentID == *val.ID {
+ return errors.New("trying to set existing child as new
parent"), nil, http.StatusBadRequest
+ }
+ }
+ return nil, nil, http.StatusOK
+}
+
+// readAndValidateJsonStruct populates select missing fields and validates
JSON body
+func readAndValidateJsonStruct(r *http.Request) (tc.TenantV5, error) {
+ var ten tc.TenantV5
+ if err := json.NewDecoder(r.Body).Decode(&ten); err != nil {
+ userErr := fmt.Errorf("error decoding POST request body into
TenantV5 struct %w", err)
+ return ten, userErr
+ }
+
+ // validate JSON body
+ rule :=
validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist
of only alphanumeric, dash, or underscore characters")
+ errs := tovalidate.ToErrors(validation.Errors{
+ "name": validation.Validate(ten.Name,
validation.Required, rule),
+ "active": validation.Validate(ten.Active), // only validate
it's boolean
+ "parentId": validation.Validate(ten.ParentID,
validation.Required, validation.Min(1)),
+ "parentName": nil,
+ })
+ if len(errs) > 0 {
+ userErr := util.JoinErrs(errs)
+ return ten, userErr
+ }
+ return ten, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
index 38ab868732..c507e97318 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
@@ -1008,7 +1008,6 @@ func GetCacheGroup(w http.ResponseWriter, r
*http.Request) {
}
func getCacheGroup(tx *sqlx.Tx, params map[string]string, useIMS bool, header
http.Header) ([]interface{}, time.Time, int, error, error) {
- //func getCacheGroup(tx *sqlx.Tx, params map[string]string, useIMS
bool, header http.Header) ([]tc.CacheGroupV5, time.Time, int, error, error) {
var runSecond bool
var maxTime time.Time
cgList := []interface{}{}
@@ -1154,6 +1153,12 @@ func CreateCacheGroup(w http.ResponseWriter, r
*http.Request) {
return
}
+ err = dgCg.ValidateTypeInTopology()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("update cachegroup: validating type in topology: "+err.Error()), err)
+ return
+ }
+
checkLastUpdated := `UPDATE cachegroup SET coordinate=$1 WHERE id=$2
RETURNING last_updated`
err = tx.QueryRow(
@@ -1433,8 +1438,16 @@ func readAndValidateJsonStruct(r *http.Request)
(tc.CacheGroupNullableV5, error)
// validate JSON body
rule :=
validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist
of only alphanumeric, dash, or underscore characters")
+ latitudeErr := "Must be a floating point number within the range +-90"
+ longitudeErr := "Must be a floating point number within the range +-180"
errs := tovalidate.ToErrors(validation.Errors{
- "name": validation.Validate(cg.Name, validation.Required, rule),
+ "name": validation.Validate(cg.Name,
validation.Required, rule),
+ "shortName":
validation.Validate(cg.ShortName, validation.Required, rule),
+ "latitude": validation.Validate(cg.Latitude,
validation.Min(-90.0).Error(latitudeErr),
validation.Max(90.0).Error(latitudeErr)),
+ "longitude":
validation.Validate(cg.Longitude, validation.Min(-180.0).Error(longitudeErr),
validation.Max(180.0).Error(longitudeErr)),
+ "parentCacheGroupID":
validation.Validate(cg.ParentCachegroupID, validation.Min(1)),
+ "secondaryParentCachegroupID":
validation.Validate(cg.SecondaryParentCachegroupID, validation.Min(1)),
+ "localizationMethods":
validation.Validate(cg.LocalizationMethods,
validation.By(tovalidate.IsPtrToSliceOfUniqueStringersICase("CZ", "DEEP_CZ",
"GEO"))),
})
if len(errs) > 0 {
userErr := util.JoinErrs(errs)
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 45a178f67e..0355a22164 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -445,10 +445,10 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `profileparameters/{profileId}/{parameterId}$`,
Handler: profileparameter.DeleteProfileParameter, RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE",
"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares:
nil, ID: 42483952931},
//Tenants
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `tenants/?$`, Handler:
api.ReadHandler(&apitenant.TOTenant{}), RequiredPrivLevel:
auth.PrivLevelReadOnly, RequiredPermissions: []string{"TENANT:READ"},
Authenticated: Authenticated, Middlewares: nil, ID: 467796781431},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `tenants/{id}$`, Handler:
api.UpdateHandler(&apitenant.TOTenant{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"TENANT:UPDATE",
"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
409413147831},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `tenants/?$`, Handler:
api.CreateHandler(&apitenant.TOTenant{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"TENANT:CREATE",
"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
41724801331},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `tenants/{id}$`, Handler:
api.DeleteHandler(&apitenant.TOTenant{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"TENANT:DELETE",
"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
41636555831},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `tenants/?$`, Handler: apitenant.GetTenant,
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions:
[]string{"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
467796781431},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `tenants/{id}$`, Handler: apitenant.UpdateTenant,
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions:
[]string{"TENANT:UPDATE", "TENANT:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 409413147831},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `tenants/?$`, Handler: apitenant.CreateTenant,
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions:
[]string{"TENANT:CREATE", "TENANT:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 41724801331},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `tenants/{id}$`, Handler: apitenant.DeleteTenant,
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions:
[]string{"TENANT:DELETE", "TENANT:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 41636555831},
//CRConfig
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `cdns/{cdn}/snapshot/?$`, Handler:
crconfig.SnapshotGetHandler, RequiredPrivLevel: auth.PrivLevelReadOnly,
RequiredPermissions: []string{"CDN-SNAPSHOT:READ"}, Authenticated:
Authenticated, Middlewares: nil, ID: 495727369531},
diff --git a/traffic_ops/v5-client/deliveryservice.go
b/traffic_ops/v5-client/deliveryservice.go
index 2a669d585a..b0e2ec2702 100644
--- a/traffic_ops/v5-client/deliveryservice.go
+++ b/traffic_ops/v5-client/deliveryservice.go
@@ -184,7 +184,7 @@ func (to *Session) CreateDeliveryService(ds
tc.DeliveryServiceV5, opts RequestOp
if len(ten.Response) == 0 {
return resp, reqInf, fmt.Errorf("no Tenant named '%s'",
*ds.Tenant)
}
- ds.TenantID = ten.Response[0].ID
+ ds.TenantID = *ten.Response[0].ID
}
reqInf, err := to.post(apiDeliveryServices, RequestOptions{Header:
opts.Header}, ds, &resp)
diff --git a/traffic_ops/v5-client/deliveryservice_requests.go
b/traffic_ops/v5-client/deliveryservice_requests.go
index 0198cb98c0..7336fde031 100644
--- a/traffic_ops/v5-client/deliveryservice_requests.go
+++ b/traffic_ops/v5-client/deliveryservice_requests.go
@@ -102,7 +102,7 @@ func (to *Session) CreateDeliveryServiceRequest(dsr
tc.DeliveryServiceRequestV5,
if err != nil || len(ten.Response) == 0 {
return resp, reqInf, fmt.Errorf("no Tenant named '%s'",
*ds.Tenant)
}
- ds.TenantID = ten.Response[0].ID
+ ds.TenantID = *ten.Response[0].ID
}
reqInf, err := to.post(apiDSRequests, opts, dsr, &resp)
diff --git a/traffic_ops/v5-client/origin.go b/traffic_ops/v5-client/origin.go
index c5f050ed88..0964b43c15 100644
--- a/traffic_ops/v5-client/origin.go
+++ b/traffic_ops/v5-client/origin.go
@@ -94,7 +94,7 @@ func (to *Session) originIDs(origin *tc.Origin) error {
if len(tenant.Response) == 0 {
return fmt.Errorf("no Tenant with name '%s'",
*origin.Tenant)
}
- origin.TenantID = &tenant.Response[0].ID
+ origin.TenantID = tenant.Response[0].ID
}
return nil
diff --git a/traffic_ops/v5-client/tenant.go b/traffic_ops/v5-client/tenant.go
index a7fddab8d8..049b256408 100644
--- a/traffic_ops/v5-client/tenant.go
+++ b/traffic_ops/v5-client/tenant.go
@@ -29,35 +29,35 @@ const apiTenants = "/tenants"
const apiTenantID = apiTenants + "/%d"
// GetTenants retrieves all Tenants stored in Traffic Ops.
-func (to *Session) GetTenants(opts RequestOptions) (tc.GetTenantsResponse,
toclientlib.ReqInf, error) {
- var data tc.GetTenantsResponse
+func (to *Session) GetTenants(opts RequestOptions) (tc.GetTenantsResponseV5,
toclientlib.ReqInf, error) {
+ var data tc.GetTenantsResponseV5
reqInf, err := to.get(apiTenants, opts, &data)
return data, reqInf, err
}
// CreateTenant creates the Tenant it's passed.
-func (to *Session) CreateTenant(t tc.Tenant, opts RequestOptions)
(tc.TenantResponse, toclientlib.ReqInf, error) {
- if t.ParentID == 0 && t.ParentName != "" {
+func (to *Session) CreateTenant(t tc.TenantV5, opts RequestOptions)
(tc.TenantResponseV5, toclientlib.ReqInf, error) {
+ if (t.ParentID == nil || *t.ParentID == 0) && (t.ParentName != nil ||
*t.ParentName != "") {
parentOpts := NewRequestOptions()
- parentOpts.QueryParameters.Set("name", t.ParentName)
+ parentOpts.QueryParameters.Set("name", *t.ParentName)
tenant, reqInf, err := to.GetTenants(parentOpts)
if err != nil {
- return tc.TenantResponse{Alerts: tenant.Alerts},
reqInf, err
+ return tc.TenantResponseV5{Alerts: tenant.Alerts},
reqInf, err
}
if len(tenant.Response) < 1 {
- return tc.TenantResponse{Alerts: tenant.Alerts},
reqInf, fmt.Errorf("no Tenant could be found for Parent Tenant '%s'",
t.ParentName)
+ return tc.TenantResponseV5{Alerts: tenant.Alerts},
reqInf, fmt.Errorf("no Tenant could be found for Parent Tenant '%s'",
*t.ParentName)
}
t.ParentID = tenant.Response[0].ID
}
- var data tc.TenantResponse
+ var data tc.TenantResponseV5
reqInf, err := to.post(apiTenants, opts, t, &data)
return data, reqInf, err
}
// UpdateTenant replaces the Tenant identified by 'id' with the one provided.
-func (to *Session) UpdateTenant(id int, t tc.Tenant, opts RequestOptions)
(tc.TenantResponse, toclientlib.ReqInf, error) {
- var data tc.TenantResponse
+func (to *Session) UpdateTenant(id int, t tc.TenantV5, opts RequestOptions)
(tc.TenantResponseV5, toclientlib.ReqInf, error) {
+ var data tc.TenantResponseV5
reqInf, err := to.put(fmt.Sprintf(apiTenantID, id), opts, t, &data)
return data, reqInf, err
}
diff --git a/traffic_ops/v5-client/user.go b/traffic_ops/v5-client/user.go
index 38b46be17c..2cfb8a6d13 100644
--- a/traffic_ops/v5-client/user.go
+++ b/traffic_ops/v5-client/user.go
@@ -62,7 +62,7 @@ func (to *Session) CreateUser(user tc.UserV4, opts
RequestOptions) (tc.CreateUse
if len(tenant.Response) < 1 {
return tc.CreateUserResponseV4{Alerts: tenant.Alerts},
toclientlib.ReqInf{}, fmt.Errorf("no such Tenant: '%s'", *user.Tenant)
}
- user.TenantID = tenant.Response[0].ID
+ user.TenantID = *tenant.Response[0].ID
}
if user.Role != "" {