diff --git a/CHANGELOG.md b/CHANGELOG.md index e1f00871c6..42200c843c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Added an optimistic quorum feature to Traffic Monitor to prevent false negative states from propagating to downstream components in the event of network isolation. - Traffic Ops Golang Endpoints - /api/1.1/cachegroupparameters/{{cachegroupID}}/{{parameterID}} `(DELETE)` + - /api/1.5/to_extensions/:id `(DELETE)` - /api/1.5/to_extensions `(POST)` ### Changed @@ -20,6 +21,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - /cachegroups/:parameterID/parameter/available - /cachegroup/:parameterID/parameter - /api_capabilities/:id + - /to_extensions/:id/delete - /regions/:region_name/phys_locations - /parameters/validate - /divisions/:division_name/regions diff --git a/docs/source/api/to_extensions_id.rst b/docs/source/api/to_extensions_id.rst new file mode 100644 index 0000000000..9ff52f6c3a --- /dev/null +++ b/docs/source/api/to_extensions_id.rst @@ -0,0 +1,76 @@ +.. +.. +.. Licensed 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. +.. + +.. _to-api-to_extensions-id: + +************************ +``to_extensions/{{ID}}`` +************************ +.. versionadded:: 1.5 + +``DELETE`` +========== +Deletes a Traffic Ops extension definition. This does **not** delete the actual extension file. + +:Auth. Required: Yes +:Roles Required: None\ [1]_ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------+ + | Name | Description | + +======+===========================================================================+ + | ID | The integral, unique identifier of the extension definition to be deleted | + +------+---------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/1.5/to_extensions/16 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Wed, 12 Dec 2018 16:33:52 GMT + Server: Mojolicious (Perl) + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: EB0Nu85azbGzaehDTAODP3NPqWbByIza1XQhgwtsW2WTXyK/dxQtncp0YiJXyO0tH9H+n+6BBfojBOb5h0dFPA== + Content-Length: 60 + + { "alerts": [ + { + "level": "success", + "text": "Extension deleted." + } + ]} + +.. [1] No roles are required to use this endpoint, however access is controlled by username. Only the reserved user ``extension`` is permitted the use of this endpoint. diff --git a/docs/source/api/to_extensions_id_delete.rst b/docs/source/api/to_extensions_id_delete.rst index 1d3b42026a..02756c359f 100644 --- a/docs/source/api/to_extensions_id_delete.rst +++ b/docs/source/api/to_extensions_id_delete.rst @@ -18,6 +18,8 @@ ******************************* ``to_extensions/{{ID}}/delete`` ******************************* +.. deprecated:: ATCv4 + Use the ``DELETE`` method of :ref:`to-api-to_extensions-id` instead. ``POST`` ======== @@ -69,6 +71,10 @@ Response Structure { "level": "success", "text": "Extension deleted." + }, + { + "level": "warning", + "text": "This endpoint is deprecated, please use 'DELETE /to_extensions/:id' instead" } ]} diff --git a/traffic_ops/app/lib/API/ToExtension.pm b/traffic_ops/app/lib/API/ToExtension.pm index ee7494f797..aa762ad6ea 100644 --- a/traffic_ops/app/lib/API/ToExtension.pm +++ b/traffic_ops/app/lib/API/ToExtension.pm @@ -196,19 +196,20 @@ sub delete { my $new_id = 1; my $id = $self->param('id'); + my $alt = "DELETE /to_extensions/:id"; # print Dumper($self->req); if ( $self->current_user()->{username} ne "extension" ) { - return $self->alert( { error => "Invalid user for this API. Only the \"extension\" user can use this." } ); + return $self->with_deprecation("Invalid user for this API. Only the \"extension\" user can use this.", "error", 400, $alt); } if ( !defined($id) ) { - return $self->alert( { error => "ToExtension delete requires an id." } ); + return $self->with_deprecation("ToExtension delete requires an id.", "error", 400, $alt); } else { my $delete = $self->db->resultset('ToExtension')->search( { id => $id } )->single(); if ( !defined($delete) ) { - return $self->alert( { error => "ToExtension with id " . $id . " not found." } ); + return $self->with_deprecation("ToExtension with id " . $id . " not found.", "error", 400, $alt); } if ( $delete->type->name =~ /^CHECK_EXTENSION_/ ) { my $open_type_id = &type_id( $self, 'CHECK_EXTENSION_OPEN_SLOT' ); @@ -229,7 +230,7 @@ sub delete { $delete->delete(); } } - return $self->success_message("Extension deleted."); + return $self->with_deprecation("Extension deleted.", "success", 200, $alt); } 1; diff --git a/traffic_ops/client/toextension.go b/traffic_ops/client/toextension.go index 5ac51f6ffd..2c2a40bfa3 100644 --- a/traffic_ops/client/toextension.go +++ b/traffic_ops/client/toextension.go @@ -43,8 +43,8 @@ func (to *Session) CreateTOExtension(toExtension tc.TOExtensionNullable) (tc.Ale // DeleteToExtension deletes a to_extension func (to *Session) DeleteTOExtension(id int) (tc.Alerts, ReqInf, error) { - URI := fmt.Sprintf("%s/%d/delete", API_TO_EXTENSION, id) - resp, remoteAddr, err := to.request(http.MethodPost, URI, nil) + URI := fmt.Sprintf("%s/%d", API_TO_EXTENSION, id) + resp, remoteAddr, err := to.request(http.MethodDelete, URI, nil) reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} if err != nil { return tc.Alerts{}, reqInf, err diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index ac257a26cc..b01dbd576b 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -590,6 +590,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { // TO Extensions {1.5, http.MethodPost, `to_extensions$`, toextension.CreateTOExtension, auth.PrivLevelReadOnly, Authenticated, nil, 380498599, noPerlBypass}, + {1.5, http.MethodDelete, `to_extensions/{id}$`, toextension.Delete, auth.PrivLevelReadOnly, Authenticated, nil, 380498299, noPerlBypass}, //Pattern based consistent hashing endpoint {1.4, http.MethodPost, `consistenthash/?$`, consistenthash.Post, auth.PrivLevelReadOnly, Authenticated, nil, 1960755076, noPerlBypass}, diff --git a/traffic_ops/traffic_ops_golang/toextension/toextension.go b/traffic_ops/traffic_ops_golang/toextension/toextension.go index c808b0e61f..8448b08399 100644 --- a/traffic_ops/traffic_ops_golang/toextension/toextension.go +++ b/traffic_ops/traffic_ops_golang/toextension/toextension.go @@ -26,6 +26,7 @@ import ( "net/http" "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/jmoiron/sqlx" @@ -123,7 +124,6 @@ func createCheckExt(toExt tc.TOExtensionNullable, tx *sqlx.Tx) (int, error, erro return 0, nil, fmt.Errorf("reset servercheck table for new check extension: %v", err) } return id, nil, nil - } func checkDupTOCheckExtension(colName, value string, tx *sqlx.Tx) (error, error) { @@ -154,3 +154,67 @@ func updateQuery() string { WHERE id=:id ` } + +// Delete is the handler for deleting to_extensions. +func Delete(w http.ResponseWriter, r *http.Request) { + inf, sysErr, userErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"}) + tx := inf.Tx.Tx + if sysErr != nil || userErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + if inf.User.UserName != "extension" { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusForbidden, errors.New("invalid user for this API. Only the \"extension\" user can use this"), nil) + return + } + + id := inf.IntParams["id"] + userErr, sysErr, errCode = deleteTOExtension(id, inf.Tx) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + + changeLogMsg := fmt.Sprintf("TO_EXTENSION: %d, ID: %d, ACTION: Deleted", id, id) + api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, inf.Tx.Tx) + api.WriteRespAlert(w, r, tc.SuccessLevel, "Extension deleted.") +} + +func deleteTOExtension(id int, tx *sqlx.Tx) (error, error, int) { + // Get Open Slot Type ID + openID, exists, err := dbhelpers.GetTypeIDByName("CHECK_EXTENSION_OPEN_SLOT", tx.Tx) + if !exists { + return nil, errors.New("expected type CHECK_EXTENSION_OPEN_SLOT does not exist in type table"), http.StatusInternalServerError + } else if err != nil { + return nil, fmt.Errorf("getting CHECK_EXTENSION_OPEN_SLOT type id: %v", err), http.StatusInternalServerError + } + + openTOExt := tc.TOExtensionNullable{ + Name: util.StrPtr("OPEN"), + Version: util.StrPtr("0"), + InfoURL: util.StrPtr(""), + ScriptFile: util.StrPtr(""), + IsActive: util.IntPtr(0), + AdditionConfigJSON: util.StrPtr(""), + ServercheckShortName: util.StrPtr(""), + TypeID: &openID, + ID: &id, + } + + result, err := tx.NamedExec(updateQuery(), openTOExt) + if err != nil { + return api.ParseDBError(err) + } + + if rowsAffected, err := result.RowsAffected(); err != nil { + return nil, fmt.Errorf("deleting TO Extension: getting rows affected: %v", err), http.StatusInternalServerError + } else if rowsAffected < 1 { + return fmt.Errorf("no TO Extension with that key found"), nil, http.StatusNotFound + } else if rowsAffected > 1 { + return nil, fmt.Errorf("TO Extension delete affected too many rows: %d", rowsAffected), http.StatusInternalServerError + } + + return nil, nil, http.StatusOK +}