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 147db8d363 Fixes Server Capabilities apis to respond with RFC3339 
date/time Format #7408 (#7482)
147db8d363 is described below

commit 147db8d363859de9595c1cd5105d6f0f16277ff7
Author: Jagan Parthiban <[email protected]>
AuthorDate: Thu May 18 23:37:34 2023 +0530

    Fixes Server Capabilities apis to respond with RFC3339 date/time Format 
#7408 (#7482)
    
    * Fixes Server Capabilities V5 Traffic Ops API to use and respond with 
RFC3339 date/time strings
    
    * Update CHANGELOG.md with issue details
    
    * Update .rst doc files for RF3339 in server capabilities.
    Issue: https://github.com/apache/trafficcontrol/issues/7465
    
    * Updated error message
---
 CHANGELOG.md                                       |   1 +
 docs/source/api/v5/server_capabilities.rst         |  20 +--
 lib/go-tc/server_capabilities.go                   |  30 ++++
 .../testing/api/v5/server_capabilities_test.go     |  54 ++++----
 traffic_ops/testing/api/v5/traffic_control_test.go |   2 +-
 traffic_ops/traffic_ops_golang/routing/routes.go   |   6 +-
 .../servercapability/servercapability.go           | 154 +++++++++++++++++++++
 traffic_ops/v5-client/servercapability.go          |  18 +--
 8 files changed, 235 insertions(+), 50 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 55737cb8fc..1c63c242c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 ### Fixed
 - [#7511](https://github.com/apache/trafficcontrol/pull/7511) *Traffic Ops* 
Fixed the changelog registration message to include the username instead of 
duplicate email entry.
 - [#7505](https://github.com/apache/trafficcontrol/pull/7505) *Traffic Portal* 
Fix an issue where a Delivery Service with Geo Limit Countries Set was unable 
to be updated.
+- [#7465](https://github.com/apache/trafficcontrol/issues/7465) *Traffic Ops* 
Fixes server_capabilities v5 apis to respond with RFC3339 date/time Format
 - [#7441](https://github.com/apache/trafficcontrol/pull/7441) *Traffic Ops* 
Fixed the invalidation jobs endpoint to respect CDN locks.
 - [#7413](https://github.com/apache/trafficcontrol/issues/7413) *Traffic Ops* 
Fixes service_category apis to respond with RFC3339 date/time Format
 - [#7414](https://github.com/apache/trafficcontrol/pull/7414) * Traffic 
Portal* Fixed DSR difference for DS required capability.
diff --git a/docs/source/api/v5/server_capabilities.rst 
b/docs/source/api/v5/server_capabilities.rst
index 4827ca8ad7..ad68ca501d 100644
--- a/docs/source/api/v5/server_capabilities.rst
+++ b/docs/source/api/v5/server_capabilities.rst
@@ -51,7 +51,7 @@ Response Structure
 ------------------
 :name:        The name of this :term:`Server Capability`
 :description: The description of this :term:`Server Capability`
-:lastUpdated: The date and time at which this :term:`Server Capability` was 
last updated, in ISO-like format
+:lastUpdated: The date and time at which this :term:`Server Capability` was 
last updated, in :rfc:`3339` Format
 
 .. code-block:: http
        :caption: Response Example
@@ -65,7 +65,7 @@ Response Structure
        Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 
GMT; Max-Age=3600; HttpOnly
        Whole-Content-Sha512: 
EH8jo8OrCu79Tz9xpgT3YRyKJ/p2NcTmbS3huwtqRByHz9H6qZLQjA59RIPaVSq3ZxsU6QhTaox5nBkQ9LPSAA==
        X-Server-Name: traffic_ops_golang/
-       Date: Mon, 07 Oct 2019 21:36:13 GMT
+       Date: Wed, 03 May 2023 07:03:45 GMT
        Content-Length: 68
 
        {
@@ -73,7 +73,7 @@ Response Structure
                        {
                                "name": "RAM",
                                "description": "ram server capability",
-                               "lastUpdated": "2019-10-07 20:38:24+00"
+                               "lastUpdated": 
"2023-05-03T12:24:40.409579+05:30"
                        }
                ]
        }
@@ -112,7 +112,7 @@ Response Structure
 ------------------
 :name:        The name of this :term:`Server Capability`
 :description: The description of this :term:`Server Capability`
-:lastUpdated: The date and time at which this :term:`Server Capability` was 
last updated, in ISO-like format
+:lastUpdated: The date and time at which this :term:`Server Capability` was 
last updated, in :rfc:`3339` Format
 
 .. code-block:: http
        :caption: Response Example
@@ -126,7 +126,7 @@ Response Structure
        Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 
GMT; Max-Age=3600; HttpOnly
        Whole-Content-Sha512: 
ysdopC//JQI79BRUa61s6M2HzHxYHpo5RdcuauOoqCYxiVOoUhNZfOVydVkv8zDN2qA374XKnym4kWj3VzQIXg==
        X-Server-Name: traffic_ops_golang/
-       Date: Mon, 07 Oct 2019 22:10:00 GMT
+       Date: Wed, 03 May 2023 07:02:02 GMT
        Content-Length: 137
 
        {
@@ -139,7 +139,7 @@ Response Structure
                "response": {
                        "name": "RAM",
                        "description": "ram server capability",
-                       "lastUpdated": "2019-10-07 22:10:00+00"
+                       "lastUpdated": "2023-05-03T12:24:40.409579+05:30"
                }
        }
 
@@ -177,7 +177,7 @@ Response Structure
 ------------------
 :name:        The name of this :term:`Server Capability`
 :description: The description of this :term:`Server Capability`
-:lastUpdated: The date and time at which this :term:`Server Capability` was 
last updated, in ISO-like format
+:lastUpdated: The date and time at which this :term:`Server Capability` was 
last updated, in :rfc:`3339` Format
 
 .. code-block:: http
        :caption: Response Example
@@ -191,7 +191,7 @@ Response Structure
        Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 
GMT; Max-Age=3600; HttpOnly
        Whole-Content-Sha512: 
ysdopC//JQI79BRUa61s6M2HzHxYHpo5RdcuauOoqCYxiVOoUhNZfOVydVkv8zDN2qA374XKnym4kWj3VzQIXg==
        X-Server-Name: traffic_ops_golang/
-       Date: Wed, 03 March 2021 21:22:08 GMT
+       Date: Wed, 03 May 2023 07:02:02 GMT
        Content-Length: 137
 
        {
@@ -204,7 +204,7 @@ Response Structure
                "response": {
                        "name": "HDD",
                        "description": "HDD server capability",
-                       "lastUpdated": "2021-03-03 21:22:08+00"
+                       "lastUpdated": "2023-05-03T12:24:40.409579+05:30"
                }
        }
 
@@ -251,7 +251,7 @@ Response Structure
        Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 
GMT; Max-Age=3600; HttpOnly
        Whole-Content-Sha512: 
8zCAATbCzcqiqigGVBy7WF1duDuXu1Wg2DBe9yfqTw/c+yhE2eUk73hFTA/Oqt0kocaN7+1GkbFdPkQPvbnRaA==
        X-Server-Name: traffic_ops_golang/
-       Date: Mon, 07 Oct 2019 20:44:40 GMT
+       Date: Wed, 03 May 2023 07:02:02 GMT
        Content-Length: 72
 
        {
diff --git a/lib/go-tc/server_capabilities.go b/lib/go-tc/server_capabilities.go
index 328cb6933f..943118e2a7 100644
--- a/lib/go-tc/server_capabilities.go
+++ b/lib/go-tc/server_capabilities.go
@@ -19,6 +19,8 @@ package tc
  * under the License.
  */
 
+import "time"
+
 // ServerCapabilitiesResponse contains the result data from a GET 
/server_capabilities request.
 type ServerCapabilitiesResponse struct {
        Response []ServerCapability `json:"response"`
@@ -57,3 +59,31 @@ type ServerCapabilityDetailResponseV41 struct {
        Response ServerCapabilityV41 `json:"response"`
        Alerts
 }
+
+// ServerCapabilityV5 is an alias for the latest minor version for the major 
version 5.
+type ServerCapabilityV5 ServerCapabilityV51
+
+// ServerCapabilityV51 contains information about a given serverCapability in 
Traffic Ops V5.
+type ServerCapabilityV51 struct {
+       Name        string    `json:"name" db:"name"`
+       LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+       Description string    `json:"description" db:"description"`
+}
+
+// ServerCapabilitiesResponseV5 is an alias for the latest minor version for 
the major version 5.
+type ServerCapabilitiesResponseV5 ServerCapabilitiesResponseV51
+
+// ServerCapabilitiesResponseV51 contains the result data from a GET(v5.1 and 
above) /server_capabilities request.
+type ServerCapabilitiesResponseV51 struct {
+       Response []ServerCapabilityV5 `json:"response"`
+       Alerts
+}
+
+// ServerCapabilityDetailResponseV5 is an alias for the latest minor version 
for the major version 5.
+type ServerCapabilityDetailResponseV5 ServerCapabilityDetailResponseV51
+
+// ServerCapabilityDetailResponseV51 contains the result data from a POST(v5.1 
and above) /server_capabilities request.
+type ServerCapabilityDetailResponseV51 struct {
+       Response ServerCapabilityV5 `json:"response"`
+       Alerts
+}
diff --git a/traffic_ops/testing/api/v5/server_capabilities_test.go 
b/traffic_ops/testing/api/v5/server_capabilities_test.go
index b5f678356d..36645126b1 100644
--- a/traffic_ops/testing/api/v5/server_capabilities_test.go
+++ b/traffic_ops/testing/api/v5/server_capabilities_test.go
@@ -36,7 +36,7 @@ func TestServerCapabilities(t *testing.T) {
                currentTime := time.Now().UTC().Add(-15 * time.Second)
                currentTimeRFC := currentTime.Format(time.RFC1123)
 
-               methodTests := utils.TestCase[client.Session, 
client.RequestOptions, tc.ServerCapabilityV41]{
+               methodTests := utils.TestCase[client.Session, 
client.RequestOptions, tc.ServerCapabilityV5]{
                        "GET": {
                                "OK when VALID request": {
                                        ClientSession: TOSession,
@@ -57,17 +57,17 @@ func TestServerCapabilities(t *testing.T) {
                        "POST": {
                                "BAD REQUEST when ALREADY EXISTS": {
                                        ClientSession: TOSession,
-                                       RequestBody: tc.ServerCapabilityV41{
-                                               ServerCapability: 
tc.ServerCapability{Name: "foo"},
-                                               Description:      "foo servers",
+                                       RequestBody: tc.ServerCapabilityV5{
+                                               Name:        "foo",
+                                               Description: "foo servers",
                                        },
                                        Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
                                },
                                "BAD REQUEST when INVALID NAME": {
                                        ClientSession: TOSession,
-                                       RequestBody: tc.ServerCapabilityV41{
-                                               ServerCapability: 
tc.ServerCapability{Name: "b@dname"},
-                                               Description:      "Server 
Capability",
+                                       RequestBody: tc.ServerCapabilityV5{
+                                               Name:        "b@dname",
+                                               Description: "Server 
Capability",
                                        },
                                        Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
                                },
@@ -76,9 +76,9 @@ func TestServerCapabilities(t *testing.T) {
                                "OK when VALID request": {
                                        ClientSession: TOSession,
                                        RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"blah"}}},
-                                       RequestBody: tc.ServerCapabilityV41{
-                                               ServerCapability: 
tc.ServerCapability{Name: "newname"},
-                                               Description:      "Server 
Capability for new name",
+                                       RequestBody: tc.ServerCapabilityV5{
+                                               Name:        "newname",
+                                               Description: "Server Capability 
for new name",
                                        },
                                        Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
                                                
validateServerCapabilitiesUpdateFields(map[string]interface{}{"Name": 
"newname"}),
@@ -87,9 +87,9 @@ func TestServerCapabilities(t *testing.T) {
                                "BAD REQUEST when NAME DOESNT EXIST": {
                                        ClientSession: TOSession,
                                        RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"invalid"}}},
-                                       RequestBody: tc.ServerCapabilityV41{
-                                               ServerCapability: 
tc.ServerCapability{Name: "newname"},
-                                               Description:      "Server 
Capability for new name",
+                                       RequestBody: tc.ServerCapabilityV5{
+                                               Name:        "newname",
+                                               Description: "Server Capability 
for new name",
                                        },
                                        Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
                                },
@@ -99,9 +99,9 @@ func TestServerCapabilities(t *testing.T) {
                                                QueryParameters: 
url.Values{"name": {"disk"}},
                                                Header:          
http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}},
                                        },
-                                       RequestBody: tc.ServerCapabilityV41{
-                                               ServerCapability: 
tc.ServerCapability{Name: "newname"},
-                                               Description:      "Server 
Capability for new name",
+                                       RequestBody: tc.ServerCapabilityV5{
+                                               Name:        "newname",
+                                               Description: "Server Capability 
for new name",
                                        },
                                        Expectations: 
utils.CkRequest(utils.HasError(), 
utils.HasStatus(http.StatusPreconditionFailed)),
                                },
@@ -111,9 +111,9 @@ func TestServerCapabilities(t *testing.T) {
                                                QueryParameters: 
url.Values{"name": {"disk"}},
                                                Header:          
http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}},
                                        },
-                                       RequestBody: tc.ServerCapabilityV41{
-                                               ServerCapability: 
tc.ServerCapability{Name: "newname"},
-                                               Description:      "Server 
Capability for new name",
+                                       RequestBody: tc.ServerCapabilityV5{
+                                               Name:        "newname",
+                                               Description: "Server Capability 
for new name",
                                        },
                                        Expectations: 
utils.CkRequest(utils.HasError(), 
utils.HasStatus(http.StatusPreconditionFailed)),
                                },
@@ -138,21 +138,21 @@ func TestServerCapabilities(t *testing.T) {
                                        switch method {
                                        case "GET":
                                                t.Run(name, func(t *testing.T) {
-                                                       resp, reqInf, err := 
testCase.ClientSession.GetServerCapabilities(testCase.RequestOpts)
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetServerCapabilitiesV5(testCase.RequestOpts)
                                                        for _, check := range 
testCase.Expectations {
                                                                check(t, 
reqInf, resp.Response, resp.Alerts, err)
                                                        }
                                                })
                                        case "POST":
                                                t.Run(name, func(t *testing.T) {
-                                                       resp, reqInf, err := 
testCase.ClientSession.CreateServerCapability(testCase.RequestBody, 
testCase.RequestOpts)
+                                                       resp, reqInf, err := 
testCase.ClientSession.CreateServerCapabilityV5(testCase.RequestBody, 
testCase.RequestOpts)
                                                        for _, check := range 
testCase.Expectations {
                                                                check(t, 
reqInf, resp.Response, resp.Alerts, err)
                                                        }
                                                })
                                        case "PUT":
                                                t.Run(name, func(t *testing.T) {
-                                                       resp, reqInf, err := 
testCase.ClientSession.UpdateServerCapability(testCase.RequestOpts.QueryParameters["name"][0],
 testCase.RequestBody, testCase.RequestOpts)
+                                                       resp, reqInf, err := 
testCase.ClientSession.UpdateServerCapabilityV5(testCase.RequestOpts.QueryParameters["name"][0],
 testCase.RequestBody, testCase.RequestOpts)
                                                        for _, check := range 
testCase.Expectations {
                                                                check(t, 
reqInf, resp.Response, resp.Alerts, err)
                                                        }
@@ -174,7 +174,7 @@ func TestServerCapabilities(t *testing.T) {
 func validateServerCapabilitiesUpdateFields(expectedResp 
map[string]interface{}) utils.CkReqFunc {
        return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
                assert.RequireNotNil(t, resp, "Expected Server Capabilities 
response to not be nil.")
-               serverCapabilitiesResp := resp.(tc.ServerCapabilityV41)
+               serverCapabilitiesResp := resp.(tc.ServerCapabilityV5)
                for field, expected := range expectedResp {
                        switch field {
                        case "Name":
@@ -190,7 +190,7 @@ func validateServerCapabilitiesSort() utils.CkReqFunc {
        return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, 
alerts tc.Alerts, _ error) {
                assert.RequireNotNil(t, resp, "Expected Server Capabilities 
response to not be nil.")
                var serverCapabilityNames []string
-               serverCapabilitiesResp := resp.([]tc.ServerCapabilityV41)
+               serverCapabilitiesResp := resp.([]tc.ServerCapabilityV5)
                for _, serverCapability := range serverCapabilitiesResp {
                        serverCapabilityNames = append(serverCapabilityNames, 
serverCapability.Name)
                }
@@ -200,13 +200,13 @@ func validateServerCapabilitiesSort() utils.CkReqFunc {
 
 func CreateTestServerCapabilities(t *testing.T) {
        for _, sc := range testData.ServerCapabilities {
-               resp, _, err := TOSession.CreateServerCapability(sc, 
client.RequestOptions{})
+               resp, _, err := TOSession.CreateServerCapabilityV5(sc, 
client.RequestOptions{})
                assert.RequireNoError(t, err, "Unexpected error creating Server 
Capability '%s': %v - alerts: %+v", sc.Name, err, resp.Alerts)
        }
 }
 
 func DeleteTestServerCapabilities(t *testing.T) {
-       serverCapabilities, _, err := 
TOSession.GetServerCapabilities(client.RequestOptions{})
+       serverCapabilities, _, err := 
TOSession.GetServerCapabilitiesV5(client.RequestOptions{})
        assert.NoError(t, err, "Cannot get Server Capabilities: %v - alerts: 
%+v", err, serverCapabilities.Alerts)
 
        for _, serverCapability := range serverCapabilities.Response {
@@ -215,7 +215,7 @@ func DeleteTestServerCapabilities(t *testing.T) {
                // Retrieve the Server Capability to see if it got deleted
                opts := client.NewRequestOptions()
                opts.QueryParameters.Set("name", serverCapability.Name)
-               getServerCapability, _, err := 
TOSession.GetServerCapabilities(opts)
+               getServerCapability, _, err := 
TOSession.GetServerCapabilitiesV5(opts)
                assert.NoError(t, err, "Error getting Server Capability '%s' 
after deletion: %v - alerts: %+v", serverCapability.Name, err, 
getServerCapability.Alerts)
                assert.Equal(t, 0, len(getServerCapability.Response), "Expected 
Server Capability '%s' to be deleted, but it was found in Traffic Ops", 
serverCapability.Name)
        }
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go 
b/traffic_ops/testing/api/v5/traffic_control_test.go
index b6673951c8..bd18b2b446 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -47,7 +47,7 @@ type TrafficControl struct {
        Roles                                             []tc.RoleV4           
                  `json:"roles"`
        Servers                                           []tc.ServerV4         
                  `json:"servers"`
        ServerServerCapabilities                          
[]tc.ServerServerCapability             `json:"serverServerCapabilities"`
-       ServerCapabilities                                
[]tc.ServerCapabilityV41                `json:"serverCapabilities"`
+       ServerCapabilities                                
[]tc.ServerCapabilityV5                 `json:"serverCapabilities"`
        ServiceCategories                                 
[]tc.ServiceCategoryV5                  `json:"serviceCategories"`
        Statuses                                          []tc.StatusNullable   
                  `json:"statuses"`
        StaticDNSEntries                                  []tc.StaticDNSEntry   
                  `json:"staticdnsentries"`
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index c5fbb8ba47..8a993a4752 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -325,9 +325,9 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `servers/{id}$`, Handler: server.Delete, 
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: 
[]string{"SERVER:DELETE", "SERVER:READ", "DELIVERY-SERVICE:READ", "CDN:READ", 
"PHYSICAL-LOCATION:READ", "CACHE-GROUP:READ", "TYPE:READ", "PROFILE:READ"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 49232223331},
 
                //Server Capability
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `server_capabilities$`, Handler: 
servercapability.GetServerCapability, RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: 
[]string{"SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 41040739131},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `server_capabilities$`, Handler: 
servercapability.CreateServerCapability, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"SERVER-CAPABILITY:CREATE", "SERVER-CAPABILITY:READ"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 407447070831},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPut, Path: `server_capabilities$`, Handler: 
servercapability.UpdateServerCapability, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"SERVER-CAPABILITY:UPDATE", "SERVER-CAPABILITY:READ"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 425437701091},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `server_capabilities$`, Handler: 
servercapability.GetServerCapabilityV5, RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: 
[]string{"SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 41040739131},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `server_capabilities$`, Handler: 
servercapability.CreateServerCapabilityV5, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"SERVER-CAPABILITY:CREATE", "SERVER-CAPABILITY:READ"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 407447070831},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPut, Path: `server_capabilities$`, Handler: 
servercapability.UpdateServerCapabilityV5, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"SERVER-CAPABILITY:UPDATE", "SERVER-CAPABILITY:READ"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 425437701091},
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `server_capabilities$`, Handler: 
servercapability.DeleteServerCapability, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"SERVER-CAPABILITY:DELETE", "SERVER-CAPABILITY:READ"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 43641503831},
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `multiple_servers_capabilities/?$`, Handler: 
server.AssignMultipleServersCapabilities, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", 
"SERVER:CREATE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:CREATE"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 407924192581},
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `multiple_servers_capabilities/?$`, Handler: 
server.DeleteMultipleServersCapabilities, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", 
"SERVER:DELETE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:DELETE"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 407924192781},
diff --git 
a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go 
b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
index 2f92436805..834a11e0e9 100644
--- a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
+++ b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
@@ -367,3 +367,157 @@ func (v *TOServerCapability) 
SelectMaxLastUpdatedQuery(where, orderBy, paginatio
 
 func (v *TOServerCapability) Create() (error, error, int) { return 
api.GenericCreateNameBasedID(v) }
 func (v *TOServerCapability) Delete() (error, error, int) { return 
api.GenericDelete(v) }
+
+func GetServerCapabilityV5(w http.ResponseWriter, r *http.Request) {
+       var sc tc.ServerCapabilityV5
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       tx := inf.Tx
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       // Query Parameters to Database Query column mappings
+       queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+               "name": {Column: "sc.name", Checker: nil},
+       }
+       if _, ok := inf.Params["orderby"]; !ok {
+               inf.Params["orderby"] = "name"
+       }
+       where, orderBy, pagination, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
+       if len(errs) > 0 {
+               api.HandleErr(w, r, tx.Tx, http.StatusBadRequest, 
util.JoinErrs(errs), nil)
+               return
+       }
+
+       selectQuery := "SELECT name, description, last_updated FROM 
server_capability sc"
+       query := selectQuery + where + orderBy + pagination
+       rows, err := tx.NamedQuery(query, queryValues)
+       if err != nil {
+               api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, 
fmt.Errorf("server capability read: error getting server capability(ies): %w", 
err))
+               return
+       }
+       defer log.Close(rows, "unable to close DB connection")
+
+       scList := []tc.ServerCapabilityV5{}
+       for rows.Next() {
+               if err = rows.Scan(&sc.Name, &sc.Description, &sc.LastUpdated); 
err != nil {
+                       api.HandleErr(w, r, tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("error getting server 
capability(ies): %w", err))
+                       return
+               }
+               scList = append(scList, sc)
+       }
+
+       api.WriteResp(w, r, scList)
+       return
+}
+
+func CreateServerCapabilityV5(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
+
+       sc, readValErr := readAndValidateJsonStructV5(r)
+       if readValErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+               return
+       }
+
+       // check if capability already exists
+       var count int
+       err := tx.QueryRow("SELECT count(*) from server_capability where name = 
$1", sc.Name).Scan(&count)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
fmt.Errorf("error: %w, when checking if server capability with name %s exists", 
err, sc.Name))
+               return
+       }
+       if count == 1 {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("server_capability name '%s' already exists.", sc.Name), nil)
+               return
+       }
+
+       // create server capability
+       query := `INSERT INTO server_capability (name, description) VALUES ($1, 
$2) RETURNING last_updated`
+       err = tx.QueryRow(query, sc.Name, sc.Description).Scan(&sc.LastUpdated)
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
fmt.Errorf("error: %w in creating server capability with name: %s", err, 
sc.Name), nil)
+                       return
+               }
+               usrErr, sysErr, code := api.ParseDBError(err)
+               api.HandleErr(w, r, tx, code, usrErr, sysErr)
+               return
+       }
+       alerts := tc.CreateAlerts(tc.SuccessLevel, "server capability was 
created.")
+       w.Header().Set("Location", 
fmt.Sprintf("/api/%d.%d/server_capabilities?name=%s", inf.Version.Major, 
inf.Version.Minor, sc.Name))
+       api.WriteAlertsObj(w, r, http.StatusCreated, alerts, sc)
+       return
+}
+
+func UpdateServerCapabilityV5(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
+       sc, readValErr := readAndValidateJsonStructV5(r)
+       if readValErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+               return
+       }
+
+       requestedName := inf.Params["name"]
+       // check if the entity was already updated
+       userErr, sysErr, errCode = api.CheckIfUnModifiedByName(r.Header, 
inf.Tx, requestedName, "server_capability")
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+
+       //update name and description of a capability
+       query := `UPDATE server_capability sc SET
+               name = $1,
+               description = $2
+       WHERE sc.name = $3
+       RETURNING sc.name, sc.description, sc.last_updated`
+
+       err := tx.QueryRow(query, sc.Name, sc.Description, 
requestedName).Scan(&sc.Name, &sc.Description, &sc.LastUpdated)
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("server capability with name: %s not found", sc.Name), nil)
+                       return
+               }
+               usrErr, sysErr, code := api.ParseDBError(err)
+               api.HandleErr(w, r, tx, code, usrErr, sysErr)
+               return
+       }
+       alerts := tc.CreateAlerts(tc.SuccessLevel, "server capability was 
updated")
+       api.WriteAlertsObj(w, r, http.StatusOK, alerts, sc)
+       return
+}
+
+func readAndValidateJsonStructV5(r *http.Request) (tc.ServerCapabilityV5, 
error) {
+       var sc tc.ServerCapabilityV5
+       if err := json.NewDecoder(r.Body).Decode(&sc); err != nil {
+               userErr := fmt.Errorf("error decoding POST request body into 
ServerCapabilityV5 struct %w", err)
+               return sc, 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(sc.Name, validation.Required, rule),
+       })
+       if len(errs) > 0 {
+               userErr := util.JoinErrs(errs)
+               return sc, userErr
+       }
+       return sc, nil
+}
diff --git a/traffic_ops/v5-client/servercapability.go 
b/traffic_ops/v5-client/servercapability.go
index 9e9d8567c3..766dec468e 100644
--- a/traffic_ops/v5-client/servercapability.go
+++ b/traffic_ops/v5-client/servercapability.go
@@ -26,27 +26,27 @@ import (
 // endpoint.
 const apiServerCapabilities = "/server_capabilities"
 
-// CreateServerCapability creates the given Server Capability.
-func (to *Session) CreateServerCapability(sc tc.ServerCapabilityV41, opts 
RequestOptions) (tc.ServerCapabilityDetailResponseV41, toclientlib.ReqInf, 
error) {
-       var scResp tc.ServerCapabilityDetailResponseV41
+// CreateServerCapabilityV5 creates the given Server Capability.
+func (to *Session) CreateServerCapabilityV5(sc tc.ServerCapabilityV5, opts 
RequestOptions) (tc.ServerCapabilityDetailResponseV5, toclientlib.ReqInf, 
error) {
+       var scResp tc.ServerCapabilityDetailResponseV5
        reqInf, err := to.post(apiServerCapabilities, opts, sc, &scResp)
        return scResp, reqInf, err
 }
 
-// GetServerCapabilities returns all the Server Capabilities in Traffic Ops.
-func (to *Session) GetServerCapabilities(opts RequestOptions) 
(tc.ServerCapabilitiesResponseV41, toclientlib.ReqInf, error) {
-       var data tc.ServerCapabilitiesResponseV41
+// GetServerCapabilitiesV5 returns all the Server Capabilities in Traffic Ops.
+func (to *Session) GetServerCapabilitiesV5(opts RequestOptions) 
(tc.ServerCapabilitiesResponseV5, toclientlib.ReqInf, error) {
+       var data tc.ServerCapabilitiesResponseV5
        reqInf, err := to.get(apiServerCapabilities, opts, &data)
        return data, reqInf, err
 }
 
-// UpdateServerCapability updates a Server Capability by name.
-func (to *Session) UpdateServerCapability(name string, sc 
tc.ServerCapabilityV41, opts RequestOptions) 
(tc.ServerCapabilityDetailResponseV41, toclientlib.ReqInf, error) {
+// UpdateServerCapabilityV5 updates a Server Capability by name.
+func (to *Session) UpdateServerCapabilityV5(name string, sc 
tc.ServerCapabilityV5, opts RequestOptions) 
(tc.ServerCapabilityDetailResponseV5, toclientlib.ReqInf, error) {
        if opts.QueryParameters == nil {
                opts.QueryParameters = url.Values{}
        }
        opts.QueryParameters.Set("name", name)
-       var data tc.ServerCapabilityDetailResponseV41
+       var data tc.ServerCapabilityDetailResponseV5
        reqInf, err := to.put(apiServerCapabilities, opts, sc, &data)
        return data, reqInf, err
 }

Reply via email to