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

[incubator-trafficcontrol] 01/17: create roles crud endpoints in golang

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

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

commit 7c8dde29781cc781a44dbae0cbf4567f9fd4ebb3
Author: Dylan Volz <Dy...@comcast.com>
AuthorDate: Mon Apr 9 15:45:11 2018 -0600

    create roles crud endpoints in golang
---
 lib/go-tc/v13/roles.go                       |  82 +++++++
 traffic_ops/traffic_ops_golang/role/roles.go | 333 +++++++++++++++++++++++++++
 traffic_ops/traffic_ops_golang/routes.go     |   8 +
 3 files changed, 423 insertions(+)

diff --git a/lib/go-tc/v13/roles.go b/lib/go-tc/v13/roles.go
new file mode 100644
index 0000000..3ecea8c
--- /dev/null
+++ b/lib/go-tc/v13/roles.go
@@ -0,0 +1,82 @@
+package v13
+
+/*
+ * 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.
+ */
+
+// A List of Roles Response
+// swagger:response RolesResponse
+// in: body
+type RolesResponse struct {
+	// in: body
+	Response []Role `json:"response"`
+}
+
+// A Single Role Response for Update and Create to depict what changed
+// swagger:response RoleResponse
+// in: body
+type RoleResponse struct {
+	// in: body
+	Response Role `json:"response"`
+}
+
+// Role ...
+type Role struct {
+	// ID of the Role
+	//
+	// required: true
+	ID int `json:"id" db:"id`
+
+	// Name of the Role
+	//
+	// required: true
+	Name string `json:"name" db:"name"`
+
+	// Description of the Role
+	//
+	// required: true
+	Description string `json:"description" db:"description"`
+
+	// Priv Level of the Role
+	//
+	// required: true
+	PrivLevel int `json:"privLevel" db:"priv_level"`
+}
+
+// RoleNullable ...
+type RoleNullable struct {
+	// ID of the Role
+	//
+	// required: true
+	ID *int `json:"id" db:"id"`
+
+	// Name of the Role
+	//
+	// required: true
+	Name *string `json:"name" db:"name"`
+
+	// Description of the Role
+	//
+	// required: true
+	Description *string `json:"description" db:"description"`
+
+	// Priv Level of the Role
+	//
+	// required: true
+	PrivLevel *int `json:"privLevel" db:"priv_level"`
+}
\ No newline at end of file
diff --git a/traffic_ops/traffic_ops_golang/role/roles.go b/traffic_ops/traffic_ops_golang/role/roles.go
new file mode 100644
index 0000000..16e77e8
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/role/roles.go
@@ -0,0 +1,333 @@
+package role
+
+/*
+ * 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 (
+	"errors"
+	"fmt"
+	"strconv"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc/v13"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tovalidate"
+	validation "github.com/go-ozzo/ozzo-validation"
+	"github.com/jmoiron/sqlx"
+	"github.com/lib/pq"
+)
+
+//we need a type alias to define functions on
+type TORole v13.RoleNullable
+
+//the refType is passed into the handlers where a copy of its type is used to decode the json.
+var refType = TORole{}
+
+func GetRefType() *TORole {
+	return &refType
+}
+
+func (role TORole) GetKeyFieldsInfo() []api.KeyFieldInfo {
+	return []api.KeyFieldInfo{{"id", api.GetIntKey}}
+}
+
+//Implementation of the Identifier, Validator interface functions
+func (role TORole) GetKeys() (map[string]interface{}, bool) {
+	if role.ID == nil {
+		return map[string]interface{}{"id": 0}, false
+	}
+	return map[string]interface{}{"id": *role.ID}, true
+}
+
+func (role TORole) GetAuditName() string {
+	if role.Name != nil {
+		return *role.Name
+	}
+	if role.ID != nil {
+		return strconv.Itoa(*role.ID)
+	}
+	return "0"
+}
+
+func (role TORole) GetType() string {
+	return "role"
+}
+
+func (role *TORole) SetKeys(keys map[string]interface{}) {
+	i, _ := keys["id"].(int) //this utilizes the non panicking type assertion, if the thrown away ok variable is false i will be the zero of the type, 0 here.
+	role.ID = &i
+}
+
+// Validate fulfills the api.Validator interface
+func (role TORole) Validate(db *sqlx.DB) []error {
+	errs := validation.Errors{
+		"name":        validation.Validate(role.Name, validation.Required),
+		"description": validation.Validate(role.Description, validation.Required),
+		"privLevel":   validation.Validate(role.PrivLevel, validation.Required)}
+	return tovalidate.ToErrors(errs)
+}
+
+//The TORole implementation of the Creator interface
+//all implementations of Creator should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a role with conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be appended to the
+//generic error message returned
+//The insert sql returns the id and lastUpdated values of the newly inserted role and have
+//to be added to the struct
+func (role *TORole) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+	rollbackTransaction := true
+	tx, err := db.Beginx()
+	defer func() {
+		if tx == nil || !rollbackTransaction {
+			return
+		}
+		err := tx.Rollback()
+		if err != nil {
+			log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+		}
+	}()
+
+	if err != nil {
+		log.Error.Printf("could not begin transaction: %v", err)
+		return tc.DBError, tc.SystemError
+	}
+	resultRows, err := tx.NamedQuery(insertQuery(), role)
+	if err != nil {
+		if pqErr, ok := err.(*pq.Error); ok {
+			err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr)
+			if eType == tc.DataConflictError {
+				return errors.New("a role with " + err.Error()), eType
+			}
+			return err, eType
+		} else {
+			log.Errorf("received non pq error: %++v from create execution", err)
+			return tc.DBError, tc.SystemError
+		}
+	}
+	defer resultRows.Close()
+
+	var id int
+	rowsAffected := 0
+	for resultRows.Next() {
+		rowsAffected++
+		if err := resultRows.Scan(&id); err != nil {
+			log.Error.Printf("could not scan id from insert: %s\n", err)
+			return tc.DBError, tc.SystemError
+		}
+	}
+	if rowsAffected == 0 {
+		err = errors.New("no role was inserted, no id was returned")
+		log.Errorln(err)
+		return tc.DBError, tc.SystemError
+	} else if rowsAffected > 1 {
+		err = errors.New("too many ids returned from role insert")
+		log.Errorln(err)
+		return tc.DBError, tc.SystemError
+	}
+	role.SetKeys(map[string]interface{}{"id": id})
+	err = tx.Commit()
+	if err != nil {
+		log.Errorln("Could not commit transaction: ", err)
+		return tc.DBError, tc.SystemError
+	}
+	rollbackTransaction = false
+	return nil, tc.NoError
+}
+
+func (role *TORole) Read(db *sqlx.DB, parameters map[string]string, user auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
+	var rows *sqlx.Rows
+
+	// Query Parameters to Database Query column mappings
+	// see the fields mapped in the SQL query
+	queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+		"name": dbhelpers.WhereColumnInfo{"name", nil},
+		"id":   dbhelpers.WhereColumnInfo{"id", api.IsInt},
+	}
+	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(parameters, queryParamsToQueryCols)
+	if len(errs) > 0 {
+		return nil, errs, tc.DataConflictError
+	}
+
+	query := selectQuery() + where + orderBy
+	log.Debugln("Query is ", query)
+
+	rows, err := db.NamedQuery(query, queryValues)
+	if err != nil {
+		log.Errorf("Error querying Roles: %v", err)
+		return nil, []error{tc.DBError}, tc.SystemError
+	}
+	defer rows.Close()
+
+	Roles := []interface{}{}
+	for rows.Next() {
+		var s TORole
+		if err = rows.StructScan(&s); err != nil {
+			log.Errorf("error parsing Role rows: %v", err)
+			return nil, []error{tc.DBError}, tc.SystemError
+		}
+		Roles = append(Roles, s)
+	}
+
+	return Roles, []error{}, tc.NoError
+}
+
+//The TORole implementation of the Updater interface
+//all implementations of Updater should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a role with conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be appended to the
+//generic error message returned
+func (role *TORole) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+	rollbackTransaction := true
+	tx, err := db.Beginx()
+	defer func() {
+		if tx == nil || !rollbackTransaction {
+			return
+		}
+		err := tx.Rollback()
+		if err != nil {
+			log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+		}
+	}()
+
+	if err != nil {
+		log.Error.Printf("could not begin transaction: %v", err)
+		return tc.DBError, tc.SystemError
+	}
+	log.Debugf("about to run exec query: %s with role: %++v", updateQuery(), role)
+	resultRows, err := tx.NamedQuery(updateQuery(), role)
+	if err != nil {
+		if pqErr, ok := err.(*pq.Error); ok {
+			err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr)
+			if eType == tc.DataConflictError {
+				return errors.New("a role with " + err.Error()), eType
+			}
+			return err, eType
+		} else {
+			log.Errorf("received error: %++v from update execution", err)
+			return tc.DBError, tc.SystemError
+		}
+	}
+	defer resultRows.Close()
+
+	rowsAffected := 0
+	for resultRows.Next() {
+		rowsAffected++
+	}
+
+	if rowsAffected != 1 {
+		if rowsAffected < 1 {
+			return errors.New("no role found with this id"), tc.DataMissingError
+		} else {
+			return fmt.Errorf("this update affected too many rows: %d", rowsAffected), tc.SystemError
+		}
+	}
+	err = tx.Commit()
+	if err != nil {
+		log.Errorln("Could not commit transaction: ", err)
+		return tc.DBError, tc.SystemError
+	}
+	rollbackTransaction = false
+	return nil, tc.NoError
+}
+
+//The Role implementation of the Deleter interface
+//all implementations of Deleter should use transactions and return the proper errorType
+func (role *TORole) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+	rollbackTransaction := true
+	tx, err := db.Beginx()
+	defer func() {
+		if tx == nil || !rollbackTransaction {
+			return
+		}
+		err := tx.Rollback()
+		if err != nil {
+			log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+		}
+	}()
+
+	if err != nil {
+		log.Error.Printf("could not begin transaction: %v", err)
+		return tc.DBError, tc.SystemError
+	}
+	log.Debugf("about to run exec query: %s with role: %++v", deleteQuery(), role)
+	result, err := tx.NamedExec(deleteQuery(), role)
+	if err != nil {
+		log.Errorf("received error: %++v from delete execution", err)
+		return tc.DBError, tc.SystemError
+	}
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		return tc.DBError, tc.SystemError
+	}
+	if rowsAffected != 1 {
+		if rowsAffected < 1 {
+			return errors.New("no role with that id found"), tc.DataMissingError
+		} else {
+			return fmt.Errorf("this create affected too many rows: %d", rowsAffected), tc.SystemError
+		}
+	}
+	err = tx.Commit()
+	if err != nil {
+		log.Errorln("Could not commit transaction: ", err)
+		return tc.DBError, tc.SystemError
+	}
+	rollbackTransaction = false
+	return nil, tc.NoError
+}
+
+func selectQuery() string {
+	query := `SELECT
+id,
+name,
+description,
+priv_level
+
+FROM role`
+	return query
+}
+
+func updateQuery() string {
+	query := `UPDATE
+role SET
+name=:name,
+description=:description,
+priv_level=:priv_level
+WHERE id=:id`
+	return query
+}
+
+func insertQuery() string {
+	query := `INSERT INTO role (
+name,
+description,
+priv_level) VALUES (
+:name,
+:description,
+:priv_level) RETURNING id`
+	return query
+}
+
+func deleteQuery() string {
+	query := `DELETE FROM role
+WHERE id=:id`
+	return query
+}
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 5763f70..ff7dc71 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -48,6 +48,7 @@ import (
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/profile"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/profileparameter"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/region"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/role"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/server"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/status"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/systeminfo"
@@ -214,6 +215,13 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
 		{1.3, http.MethodPut, `deliveryservices/{xmlID}/urisignkeys$`, saveDeliveryServiceURIKeysHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
 		{1.3, http.MethodDelete, `deliveryservices/{xmlID}/urisignkeys$`, removeDeliveryServiceURIKeysHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
 
+		//Roles
+		{1.3, http.MethodGet, `roles/?(\.json)?$`, api.ReadHandler(role.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.3, http.MethodGet, `roles/{id}$`, api.ReadHandler(role.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.3, http.MethodPut, `roles/{id}$`, api.UpdateHandler(role.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.3, http.MethodPost, `roles/?$`, api.CreateHandler(role.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.3, http.MethodDelete, `roles/{id}$`, api.DeleteHandler(role.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+
 		//Servers
 		{1.3, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler(d.DB), auth.PrivLevelOperations, Authenticated, nil},
 		{1.3, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},

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