mitchell852 closed pull request #2256: Add TO Go profile parameters routes
URL: https://github.com/apache/incubator-trafficcontrol/pull/2256
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/lib/go-tc/parameters.go b/lib/go-tc/parameters.go
index 5949e89d2..c09928041 100644
--- a/lib/go-tc/parameters.go
+++ b/lib/go-tc/parameters.go
@@ -50,3 +50,42 @@ type ParameterNullable struct {
        Secure      *bool           `json:"secure" db:"secure"`
        Value       *string         `json:"value" db:"value"`
 }
+
+type ProfileParameterByName struct {
+       ConfigFile  string    `json:"configFile"`
+       ID          int       `json:"id"`
+       LastUpdated TimeNoMod `json:"lastUpdated"`
+       Name        string    `json:"name"`
+       Secure      bool      `json:"secure"`
+       Value       string    `json:"value"`
+}
+
+type ProfileParameterByNamePost struct {
+       ConfigFile string `json:"configFile"`
+       Name       string `json:"name"`
+       Secure     int    `json:"secure"`
+       Value      string `json:"value"`
+}
+
+type ProfileParameterPostRespObj struct {
+       ProfileParameterByNamePost
+       ID int64 `json:"id"`
+}
+
+type ProfileParameterPostResp struct {
+       Parameters  []ProfileParameterPostRespObj `json:"parameters"`
+       ProfileID   int                           `json:"profileId"`
+       ProfileName string                        `json:"profileName"`
+}
+
+type PostProfileParam struct {
+       ProfileID int64   `json:"profileId"`
+       ParamIDs  []int64 `json:"paramIds"`
+       Replace   bool    `json:"replace"`
+}
+
+type PostParamProfile struct {
+       ParamID    int64   `json:"paramId"`
+       ProfileIDs []int64 `json:"profileIds"`
+       Replace    bool    `json:"replace"`
+}
diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers.go 
b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
index 865245581..b10e958a2 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
@@ -415,3 +415,84 @@ func CreateHandler(typeRef Creator, db *sqlx.DB) 
http.HandlerFunc {
                fmt.Fprintf(w, "%s", respBts)
        }
 }
+
+// WriteResp takes any object, serializes it as JSON, and writes that to w. 
Any errors are logged and written to w via tc.GetHandleErrorsFunc.
+// This is a helper for the common case; not using this in unusual cases is 
perfectly acceptable.
+func WriteResp(w http.ResponseWriter, r *http.Request, v interface{}) {
+       resp := struct {
+               Response interface{} `json:"response"`
+       }{v}
+       respBts, err := json.Marshal(resp)
+       if err != nil {
+               log.Errorf("marshalling JSON for %T: %v", v, err)
+               tc.GetHandleErrorsFunc(w, r)(http.StatusInternalServerError, 
errors.New(http.StatusText(http.StatusInternalServerError)))
+               return
+       }
+       w.Header().Set("Content-Type", "application/json")
+       w.Write(respBts)
+}
+
+// HandleErr handles an API error, writing the given statusCode and userErr to 
the user, and logging the sysErr. If userErr is nil, the text of the HTTP 
statusCode is written.
+// This is a helper for the common case; not using this in unusual cases is 
perfectly acceptable.
+func HandleErr(w http.ResponseWriter, r *http.Request, statusCode int, userErr 
error, sysErr error) {
+       if sysErr != nil {
+               log.Errorln(r.RemoteAddr + " " + sysErr.Error())
+       }
+       if userErr == nil {
+               userErr = errors.New(http.StatusText(statusCode))
+       }
+       respBts, err := json.Marshal(tc.CreateErrorAlerts(userErr))
+       if err != nil {
+               log.Errorln("marshalling error: " + err.Error())
+               *r = *r.WithContext(context.WithValue(r.Context(), 
tc.StatusKey, http.StatusInternalServerError))
+               w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
+               return
+       }
+       *r = *r.WithContext(context.WithValue(r.Context(), tc.StatusKey, 
statusCode))
+       w.Header().Set(tc.ContentType, tc.ApplicationJson)
+       w.Write(respBts)
+}
+
+// RespWriter is a helper to allow a one-line response, for endpoints with a 
function that returns the object that needs to be written and an error.
+// This is a helper for the common case; not using this in unusual cases is 
perfectly acceptable.
+func RespWriter(w http.ResponseWriter, r *http.Request) func(v interface{}, 
err error) {
+       return func(v interface{}, err error) {
+               if err != nil {
+                       HandleErr(w, r, http.StatusInternalServerError, nil, 
err)
+                       return
+               }
+               WriteResp(w, r, v)
+       }
+}
+
+// IntParams parses integer parameters, and returns map of the given params, 
or an error. This guarantees if error is nil, all requested parameters 
successfully parsed and exist in the returned map, hence if error is nil 
there's no need to check for existence. The intParams may be nil if no integer 
parameters are required.
+// This is a helper for the common case; not using this in unusual cases is 
perfectly acceptable.
+func IntParams(params map[string]string, intParamNames []string) 
(map[string]int, error) {
+       intParams := map[string]int{}
+       for _, intParam := range intParamNames {
+               realParam, ok := params[intParam]
+               if !ok {
+                       return nil, errors.New("missing required integer 
parameter '" + intParam + "'")
+               }
+               intVal, err := strconv.Atoi(realParam)
+               if err != nil {
+                       return nil, errors.New("required parameter '" + 
intParam + "'" + " not an integer")
+               }
+               intParams[intParam] = intVal
+       }
+       return intParams, nil
+}
+
+// AllParams takes the request (in which the router has inserted context for 
path parameters), and an array of parameters required to be integers, and 
returns the map of combined parameters, and the map of int parameters; or a 
user or system error and the HTTP error code. The intParams may be nil if no 
integer parameters are required.
+// This is a helper for the common case; not using this in unusual cases is 
perfectly acceptable.
+func AllParams(req *http.Request, intParamNames []string) (map[string]string, 
map[string]int, error, error, int) {
+       params, err := GetCombinedParams(req)
+       if err != nil {
+               return nil, nil, errors.New("getting combined URI parameters: " 
+ err.Error()), nil, http.StatusBadRequest
+       }
+       intParams, err := IntParams(params, intParamNames)
+       if err != nil {
+               return nil, nil, nil, errors.New("getting combined URI 
parameters: " + err.Error()), http.StatusInternalServerError
+       }
+       return params, intParams, nil, nil, 0
+}
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/parameterprofilebyid.go 
b/traffic_ops/traffic_ops_golang/profileparameter/parameterprofilebyid.go
new file mode 100644
index 000000000..19bdc903d
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/profileparameter/parameterprofilebyid.go
@@ -0,0 +1,65 @@
+package profileparameter
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "errors"
+       "net/http"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+func GetProfileID(db *sql.DB) http.HandlerFunc {
+       return func(w http.ResponseWriter, r *http.Request) {
+               _, intParams, userErr, sysErr, errCode := api.AllParams(r, 
[]string{"id"})
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, errCode, userErr, sysErr)
+                       return
+               }
+               api.RespWriter(w, r)(getParametersByProfileID(intParams["id"], 
db))
+       }
+}
+
+func getParametersByProfileID(profileID int, db *sql.DB) 
([]tc.ProfileParameterByName, error) {
+       q := `
+SELECT
+parameter.id, parameter.name, parameter.value, parameter.config_file, 
parameter.secure, parameter.last_updated
+FROM parameter
+JOIN profile_parameter as pp ON pp.parameter = parameter.id
+JOIN profile on profile.id = pp.profile
+WHERE profile.id = $1
+`
+       rows, err := db.Query(q, profileID)
+       if err != nil {
+               return nil, errors.New("querying profile name parameters: " + 
err.Error())
+       }
+       defer rows.Close()
+       params := []tc.ProfileParameterByName{}
+       for rows.Next() {
+               p := tc.ProfileParameterByName{}
+               if err := rows.Scan(&p.ID, &p.Name, &p.Value, &p.ConfigFile, 
&p.Secure, &p.LastUpdated); err != nil {
+                       return nil, errors.New("scanning profile id parameters: 
" + err.Error())
+               }
+               params = append(params, p)
+       }
+       return params, nil
+}
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/parameterprofilebyname.go 
b/traffic_ops/traffic_ops_golang/profileparameter/parameterprofilebyname.go
new file mode 100644
index 000000000..3dd6f85a6
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/profileparameter/parameterprofilebyname.go
@@ -0,0 +1,65 @@
+package profileparameter
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "errors"
+       "net/http"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+func GetProfileName(db *sql.DB) http.HandlerFunc {
+       return func(w http.ResponseWriter, r *http.Request) {
+               params, _, userErr, sysErr, errCode := api.AllParams(r, nil)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, errCode, userErr, sysErr)
+                       return
+               }
+               api.RespWriter(w, r)(getParametersByProfileName(params["name"], 
db))
+       }
+}
+
+func getParametersByProfileName(profileName string, db *sql.DB) 
([]tc.ProfileParameterByName, error) {
+       q := `
+SELECT
+parameter.id, parameter.name, parameter.value, parameter.config_file, 
parameter.secure, parameter.last_updated
+FROM parameter
+JOIN profile_parameter as pp ON pp.parameter = parameter.id
+JOIN profile on profile.id = pp.profile
+WHERE profile.name = $1
+`
+       rows, err := db.Query(q, profileName)
+       if err != nil {
+               return nil, errors.New("querying profile name parameters: " + 
err.Error())
+       }
+       defer rows.Close()
+       params := []tc.ProfileParameterByName{}
+       for rows.Next() {
+               p := tc.ProfileParameterByName{}
+               if err := rows.Scan(&p.ID, &p.Name, &p.Value, &p.ConfigFile, 
&p.Secure, &p.LastUpdated); err != nil {
+                       return nil, errors.New("scanning profile name 
parameters: " + err.Error())
+               }
+               params = append(params, p)
+       }
+       return params, nil
+}
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/postparameterprofile.go 
b/traffic_ops/traffic_ops_golang/profileparameter/postparameterprofile.go
new file mode 100644
index 000000000..f704b359d
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/profileparameter/postparameterprofile.go
@@ -0,0 +1,118 @@
+package profileparameter
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "net/http"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+
+       "github.com/lib/pq"
+)
+
+func PostParamProfile(db *sql.DB) http.HandlerFunc {
+       return func(w http.ResponseWriter, r *http.Request) {
+               defer r.Body.Close()
+
+               paramProfile := tc.PostParamProfile{}
+               if err := json.NewDecoder(r.Body).Decode(&paramProfile); err != 
nil {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
errors.New("malformed JSON"), nil)
+                       return
+               }
+
+               if ok, err := paramExists(paramProfile.ParamID, db); err != nil 
{
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, fmt.Errorf("checking param ID %d existence: "+err.Error(), 
paramProfile.ParamID))
+                       return
+               } else if !ok {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
fmt.Errorf("no parameter with ID %d exists", paramProfile.ParamID), nil)
+                       return
+               }
+               if ok, err := profilesExist(paramProfile.ProfileIDs, db); err 
!= nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, fmt.Errorf("checking profiles IDs %v existence: "+err.Error(), 
paramProfile.ProfileIDs))
+                       return
+               } else if !ok {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
fmt.Errorf("profiles with IDs %v don't all exist", paramProfile.ProfileIDs), 
nil)
+                       return
+               }
+               if err := insertParameterProfile(paramProfile, db); err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, errors.New("posting parameter profile: "+err.Error()))
+                       return
+               }
+               // TODO create helper func
+               resp := struct {
+                       Response tc.PostParamProfile `json:"response"`
+                       tc.Alerts
+               }{paramProfile, tc.CreateAlerts(tc.SuccessLevel, 
fmt.Sprintf("%d profiles were assigned to the %d parameter", 
len(paramProfile.ProfileIDs), paramProfile.ParamID))}
+               respBts, err := json.Marshal(resp)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, errors.New("posting parameter profiles: "+err.Error()))
+                       return
+               }
+               w.Header().Set(tc.ContentType, tc.ApplicationJson)
+               w.Write(respBts)
+       }
+}
+
+func paramExists(id int64, db *sql.DB) (bool, error) {
+       count := 0
+       if err := db.QueryRow(`SELECT count(*) from parameter where id = $1`, 
id).Scan(&count); err != nil {
+               return false, errors.New("querying param existence from id: " + 
err.Error())
+       }
+       return count > 0, nil
+}
+
+func profilesExist(ids []int64, db *sql.DB) (bool, error) {
+       count := 0
+       if err := db.QueryRow(`SELECT count(*) from profile where id = 
ANY($1)`, pq.Array(ids)).Scan(&count); err != nil {
+               return false, errors.New("querying profiles existence from id: 
" + err.Error())
+       }
+       return count == len(ids), nil
+}
+
+func insertParameterProfile(post tc.PostParamProfile, db *sql.DB) error {
+       tx, err := db.Begin()
+       if err != nil {
+               return errors.New("beginning transaction: " + err.Error())
+       }
+       commitTx := false
+       defer FinishTx(tx, &commitTx)
+
+       if post.Replace {
+               if _, err := tx.Exec(`DELETE FROM profile_parameter WHERE 
parameter = $1`, post.ParamID); err != nil {
+                       return errors.New("deleting old parameter profile: " + 
err.Error())
+               }
+       }
+
+       q := `
+INSERT INTO profile_parameter (profile, parameter)
+VALUES (unnest($1::int[]), $2)
+ON CONFLICT DO NOTHING;
+`
+       if _, err := tx.Exec(q, pq.Array(post.ProfileIDs), post.ParamID); err 
!= nil {
+               return errors.New("inserting parameter profile: " + err.Error())
+       }
+       commitTx = true
+       return nil
+}
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/postprofileparameter.go 
b/traffic_ops/traffic_ops_golang/profileparameter/postprofileparameter.go
new file mode 100644
index 000000000..01bf7316b
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/profileparameter/postprofileparameter.go
@@ -0,0 +1,118 @@
+package profileparameter
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "net/http"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+
+       "github.com/lib/pq"
+)
+
+func PostProfileParam(db *sql.DB) http.HandlerFunc {
+       return func(w http.ResponseWriter, r *http.Request) {
+               defer r.Body.Close()
+
+               profileParam := tc.PostProfileParam{}
+               if err := json.NewDecoder(r.Body).Decode(&profileParam); err != 
nil {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
errors.New("malformed JSON"), nil)
+                       return
+               }
+
+               if ok, err := profileExists(profileParam.ProfileID, db); err != 
nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, fmt.Errorf("checking profile ID %d existence: "+err.Error(), 
profileParam.ProfileID))
+                       return
+               } else if !ok {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
fmt.Errorf("no profile with ID %d exists", profileParam.ProfileID), nil)
+                       return
+               }
+               if ok, err := paramsExist(profileParam.ParamIDs, db); err != 
nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, fmt.Errorf("checking parameters IDs %v existence: "+err.Error(), 
profileParam.ParamIDs))
+                       return
+               } else if !ok {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
fmt.Errorf("parameters with IDs %v don't all exist", profileParam.ParamIDs), 
nil)
+                       return
+               }
+               if err := insertProfileParameter(profileParam, db); err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, errors.New("posting profile parameter: "+err.Error()))
+                       return
+               }
+               // TODO create helper func
+               resp := struct {
+                       Response tc.PostProfileParam `json:"response"`
+                       tc.Alerts
+               }{profileParam, tc.CreateAlerts(tc.SuccessLevel, 
fmt.Sprintf("%d parameters were assigned to the %d profile", 
len(profileParam.ParamIDs), profileParam.ProfileID))}
+               respBts, err := json.Marshal(resp)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, errors.New("posting profile parameters by name: "+err.Error()))
+                       return
+               }
+               w.Header().Set(tc.ContentType, tc.ApplicationJson)
+               w.Write(respBts)
+       }
+}
+
+func profileExists(id int64, db *sql.DB) (bool, error) {
+       count := 0
+       if err := db.QueryRow(`SELECT count(*) from profile where id = $1`, 
id).Scan(&count); err != nil {
+               return false, errors.New("querying profile existence from id: " 
+ err.Error())
+       }
+       return count > 0, nil
+}
+
+func paramsExist(ids []int64, db *sql.DB) (bool, error) {
+       count := 0
+       if err := db.QueryRow(`SELECT count(*) from parameter where id = 
ANY($1)`, pq.Array(ids)).Scan(&count); err != nil {
+               return false, errors.New("querying parameters existence from 
id: " + err.Error())
+       }
+       return count == len(ids), nil
+}
+
+func insertProfileParameter(post tc.PostProfileParam, db *sql.DB) error {
+       tx, err := db.Begin()
+       if err != nil {
+               return errors.New("beginning transaction: " + err.Error())
+       }
+       commitTx := false
+       defer FinishTx(tx, &commitTx)
+
+       if post.Replace {
+               if _, err := tx.Exec(`DELETE FROM profile_parameter WHERE 
profile = $1`, post.ProfileID); err != nil {
+                       return errors.New("deleting old profile parameter: " + 
err.Error())
+               }
+       }
+
+       q := `
+INSERT INTO profile_parameter (profile, parameter)
+VALUES ($1, unnest($2::int[]))
+ON CONFLICT DO NOTHING;
+`
+       if _, err := tx.Exec(q, post.ProfileID, pq.Array(post.ParamIDs)); err 
!= nil {
+               return errors.New("inserting profile parameter: " + err.Error())
+       }
+       commitTx = true
+       return nil
+}
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/postprofileparametersbyid.go 
b/traffic_ops/traffic_ops_golang/profileparameter/postprofileparametersbyid.go
new file mode 100644
index 000000000..8c17b58ab
--- /dev/null
+++ 
b/traffic_ops/traffic_ops_golang/profileparameter/postprofileparametersbyid.go
@@ -0,0 +1,110 @@
+package profileparameter
+
+/*
+ * 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 (
+       "bytes"
+       "database/sql"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "io/ioutil"
+       "net/http"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+func PostProfileParamsByID(db *sql.DB) http.HandlerFunc {
+       return func(w http.ResponseWriter, r *http.Request) {
+               defer r.Body.Close()
+               _, intParams, userErr, sysErr, errCode := api.AllParams(r, 
[]string{"id"})
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, errCode, userErr, sysErr)
+                       return
+               }
+               bts, err := ioutil.ReadAll(r.Body)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
errors.New("body read failed"), nil)
+                       return
+               }
+               bts = bytes.TrimLeft(bts, " \n\t\r")
+               if len(bts) == 0 {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
errors.New("no body"), nil)
+                       return
+               }
+               profParams := []tc.ProfileParameterByNamePost{}
+               if bts[0] == '[' {
+                       if err := json.Unmarshal(bts, &profParams); err != nil {
+                               api.HandleErr(w, r, http.StatusBadRequest, 
errors.New("malformed JSON"), nil)
+                               return
+                       }
+               } else {
+                       param := tc.ProfileParameterByNamePost{}
+                       if err := json.Unmarshal(bts, &param); err != nil {
+                               api.HandleErr(w, r, 
http.StatusInternalServerError, errors.New("posting profile parameters by name: 
"+err.Error()), nil)
+                               return
+                       }
+                       profParams = append(profParams, param)
+               }
+
+               profileID := intParams["id"]
+               profileName, profileExists, err := 
getProfileNameFromID(profileID, db)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, fmt.Errorf("getting profile ID %d: "+err.Error(), profileID))
+                       return
+               }
+               if !profileExists {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
fmt.Errorf("no profile with ID %d exists", profileID), nil)
+                       return
+               }
+
+               insertedObjs, err := insertParametersForProfile(profileName, 
profParams, db)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, errors.New("posting profile parameters by name: "+err.Error()))
+                       return
+               }
+
+               // TODO create helper func
+               resp := struct {
+                       Response tc.ProfileParameterPostResp `json:"response"`
+                       tc.Alerts
+               }{tc.ProfileParameterPostResp{Parameters: insertedObjs, 
ProfileName: profileName, ProfileID: profileID}, 
tc.CreateAlerts(tc.SuccessLevel, "Assign parameters successfully to profile 
"+profileName)}
+               respBts, err := json.Marshal(resp)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, errors.New("posting profile parameters by name: "+err.Error()))
+                       return
+               }
+               w.Header().Set(tc.ContentType, tc.ApplicationJson)
+               w.Write(respBts)
+       }
+}
+
+// getProfileIDFromName returns the profile's name, whether a profile with ID 
exists, or any error.
+func getProfileNameFromID(id int, db *sql.DB) (string, bool, error) {
+       name := ""
+       if err := db.QueryRow(`SELECT name from profile where id = $1`, 
id).Scan(&name); err != nil {
+               if err == sql.ErrNoRows {
+                       return "", false, nil
+               }
+               return "", false, errors.New("querying profile name from id: " 
+ err.Error())
+       }
+       return name, true, nil
+}
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/postprofileparametersbyname.go
 
b/traffic_ops/traffic_ops_golang/profileparameter/postprofileparametersbyname.go
new file mode 100644
index 000000000..a4fffdfc2
--- /dev/null
+++ 
b/traffic_ops/traffic_ops_golang/profileparameter/postprofileparametersbyname.go
@@ -0,0 +1,184 @@
+package profileparameter
+
+/*
+ * 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 (
+       "bytes"
+       "database/sql"
+       "encoding/json"
+       "errors"
+       "io/ioutil"
+       "net/http"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+
+       "github.com/lib/pq"
+)
+
+func PostProfileParamsByName(db *sql.DB) http.HandlerFunc {
+       return func(w http.ResponseWriter, r *http.Request) {
+               defer r.Body.Close()
+               params, _, userErr, sysErr, errCode := api.AllParams(r, nil)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, errCode, userErr, sysErr)
+                       return
+               }
+               bts, err := ioutil.ReadAll(r.Body)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
errors.New("body read failed"), nil)
+                       return
+               }
+               bts = bytes.TrimLeft(bts, " \n\t\r")
+               if len(bts) == 0 {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
errors.New("no body"), nil)
+                       return
+               }
+               profParams := []tc.ProfileParameterByNamePost{}
+               if bts[0] == '[' {
+                       if err := json.Unmarshal(bts, &profParams); err != nil {
+                               api.HandleErr(w, r, http.StatusBadRequest, 
errors.New("malformed JSON"), nil)
+                               return
+                       }
+               } else {
+                       param := tc.ProfileParameterByNamePost{}
+                       if err := json.Unmarshal(bts, &param); err != nil {
+                               api.HandleErr(w, r, 
http.StatusInternalServerError, errors.New("posting profile parameters by name: 
"+err.Error()), nil)
+                               return
+                       }
+                       profParams = append(profParams, param)
+               }
+               profileName := params["name"]
+
+               profileID, profileExists, err := 
getProfileIDFromName(profileName, db)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, errors.New("getting profile '"+profileName+"' ID: "+err.Error()))
+                       return
+               }
+               if !profileExists {
+                       api.HandleErr(w, r, http.StatusBadRequest, 
errors.New("no profile with that name exists"), nil)
+                       return
+               }
+
+               insertedObjs, err := insertParametersForProfile(profileName, 
profParams, db)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, errors.New("posting profile parameters by name: "+err.Error()))
+                       return
+               }
+
+               // TODO create helper func
+               resp := struct {
+                       Response tc.ProfileParameterPostResp `json:"response"`
+                       tc.Alerts
+               }{tc.ProfileParameterPostResp{Parameters: insertedObjs, 
ProfileName: profileName, ProfileID: profileID}, 
tc.CreateAlerts(tc.SuccessLevel, "Assign parameters successfully to profile 
"+profileName)}
+               respBts, err := json.Marshal(resp)
+               if err != nil {
+                       api.HandleErr(w, r, http.StatusInternalServerError, 
nil, errors.New("posting profile parameters by name: "+err.Error()))
+                       return
+               }
+               w.Header().Set(tc.ContentType, tc.ApplicationJson)
+               w.Write(respBts)
+       }
+}
+
+// getProfileIDFromName returns the profile's ID, whether a profile with name 
exists, or any error.
+func getProfileIDFromName(name string, db *sql.DB) (int, bool, error) {
+       id := 0
+       if err := db.QueryRow(`SELECT id from profile where name = $1`, 
name).Scan(&id); err != nil {
+               if err == sql.ErrNoRows {
+                       return 0, false, nil
+               }
+               return 0, false, errors.New("querying profile id from name: " + 
err.Error())
+       }
+       return id, true, nil
+}
+
+// insertParametersForProfile returns the PostResp object, because the ID is 
needed, and the ID must be associated with the real key 
(name,value,config_file), so we might as well return the whole object.
+func insertParametersForProfile(profileName string, params 
[]tc.ProfileParameterByNamePost, db *sql.DB) ([]tc.ProfileParameterPostRespObj, 
error) {
+       tx, err := db.Begin()
+       if err != nil {
+               return nil, errors.New("beginning transaction: " + err.Error())
+       }
+       commitTx := false
+       defer FinishTx(tx, &commitTx)
+       insertParamsQ := `
+INSERT INTO parameter (name, config_file, value, secure)
+VALUES (unnest($1::text[]), unnest($2::text[]), unnest($3::text[]), 
unnest($4::bool[]))
+ON CONFLICT(name, config_file, value) DO UPDATE set name=EXCLUDED.name 
RETURNING id, name, config_file, value, secure;
+`
+       paramNames := make([]string, len(params))
+       paramConfigFiles := make([]string, len(params))
+       paramValues := make([]string, len(params))
+       paramSecures := make([]bool, len(params))
+       for i, param := range params {
+               paramNames[i] = param.Name
+               paramConfigFiles[i] = param.ConfigFile
+               paramValues[i] = param.Value
+               if param.Secure != 0 {
+                       paramSecures[i] = true
+               }
+       }
+       rows, err := tx.Query(insertParamsQ, pq.Array(paramNames), 
pq.Array(paramConfigFiles), pq.Array(paramValues), pq.Array(paramSecures))
+       if err != nil {
+               return nil, errors.New("querying post parameters for profile: " 
+ err.Error())
+       }
+       defer rows.Close()
+       ids := make([]int64, 0, len(params))
+       insertedObjs := []tc.ProfileParameterPostRespObj{}
+       for rows.Next() {
+               id := int64(0)
+               name := ""
+               configFile := ""
+               value := ""
+               secure := false
+               secureNum := 0
+               if err := rows.Scan(&id, &name, &configFile, &value, &secure); 
err != nil {
+                       return nil, errors.New("scanning new parameter IDs: " + 
err.Error())
+               }
+               if secure {
+                       secureNum = 1
+               }
+               ids = append(ids, id)
+               insertedObjs = append(insertedObjs, 
tc.ProfileParameterPostRespObj{ID: id, ProfileParameterByNamePost: 
tc.ProfileParameterByNamePost{Name: name, ConfigFile: configFile, Value: value, 
Secure: secureNum}})
+       }
+       insertProfileParamsQ := `
+INSERT INTO profile_parameter (profile, parameter)
+VALUES ((SELECT id FROM profile WHERE name = $1), unnest($2::int[]))
+ON CONFLICT DO NOTHING;
+`
+       if _, err := tx.Exec(insertProfileParamsQ, profileName, pq.Array(ids)); 
err != nil {
+               return nil, errors.New("inserting profile parameters: " + 
err.Error())
+       }
+
+       commitTx = true
+       return insertedObjs, nil
+}
+
+// FinishTx commits the transaction if commit is true when it's called, 
otherwise it rolls back the transaction. This is designed to be called in a 
defer.
+func FinishTx(tx *sql.Tx, commit *bool) {
+       if tx == nil {
+               return
+       }
+       if !*commit {
+               tx.Rollback()
+               return
+       }
+       tx.Commit()
+}
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/unassignedparams.go 
b/traffic_ops/traffic_ops_golang/profileparameter/unassignedparams.go
new file mode 100644
index 000000000..79ecc81fb
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/profileparameter/unassignedparams.go
@@ -0,0 +1,66 @@
+package profileparameter
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "errors"
+       "net/http"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+//     $self->db->resultset('ProfileParameter')->search( \%criteria, { 
prefetch => [ 'parameter', 'profile' ] } )->get_column('parameter')->all();
+
+// my $rs_data = $self->db->resultset("Parameter")->search( 'me.id' => { 'not 
in' => \@assigned_params } );
+
+func GetUnassigned(db *sql.DB) http.HandlerFunc {
+       return func(w http.ResponseWriter, r *http.Request) {
+               _, intParams, userErr, sysErr, errCode := api.AllParams(r, 
[]string{"id"})
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, errCode, userErr, sysErr)
+                       return
+               }
+               api.RespWriter(w, 
r)(getUnassignedParametersByProfileID(intParams["id"], db))
+       }
+}
+
+func getUnassignedParametersByProfileID(profileID int, db *sql.DB) 
([]tc.ProfileParameterByName, error) {
+       q := `
+SELECT
+parameter.id, parameter.name, parameter.value, parameter.config_file, 
parameter.secure, parameter.last_updated
+FROM parameter WHERE id NOT IN (SELECT parameter FROM profile_parameter as pp 
WHERE pp.profile = $1)
+`
+       rows, err := db.Query(q, profileID)
+       if err != nil {
+               return nil, errors.New("querying profile name parameters: " + 
err.Error())
+       }
+       defer rows.Close()
+       params := []tc.ProfileParameterByName{}
+       for rows.Next() {
+               p := tc.ProfileParameterByName{}
+               if err := rows.Scan(&p.ID, &p.Name, &p.Value, &p.ConfigFile, 
&p.Secure, &p.LastUpdated); err != nil {
+                       return nil, errors.New("scanning profile id parameters: 
" + err.Error())
+               }
+               params = append(params, p)
+       }
+       return params, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/routes.go 
b/traffic_ops/traffic_ops_golang/routes.go
index 37a3b187b..ea543538b 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -238,10 +238,17 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
                {1.3, http.MethodGet, `servers/{host_name}/update_status$`, 
server.GetServerUpdateStatusHandler(d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
 
                //ProfileParameters
-               {1.3, http.MethodGet, `profile_parameters/?(\.json)?$`, 
api.ReadHandler(profileparameter.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
-               {1.3, http.MethodGet, `profile_parameters/{id}$`, 
api.ReadHandler(profileparameter.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
-               {1.3, http.MethodPost, `profile_parameters/?$`, 
api.CreateHandler(profileparameter.GetRefType(), d.DB), 
auth.PrivLevelOperations, Authenticated, nil},
-               {1.3, http.MethodDelete, `profile_parameters/{id}$`, 
api.DeleteHandler(profileparameter.GetRefType(), d.DB), 
auth.PrivLevelOperations, Authenticated, nil},
+               {1.1, http.MethodGet, `profiles/{id}/parameters/?(\.json)?$`, 
profileparameter.GetProfileID(d.DB.DB), auth.PrivLevelReadOnly, Authenticated, 
nil},
+               {1.1, http.MethodGet, 
`profiles/{id}/unassigned_parameters/?(\.json)?$`, 
profileparameter.GetUnassigned(d.DB.DB), auth.PrivLevelReadOnly, Authenticated, 
nil},
+               {1.1, http.MethodGet, 
`profiles/name/{name}/parameters/?(\.json)?$`, 
profileparameter.GetProfileName(d.DB.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
+               {1.1, http.MethodGet, `parameters/profile/{name}/?(\.json)?$`, 
profileparameter.GetProfileName(d.DB.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
+               {1.1, http.MethodPost, `profiles/name/{name}/parameters/?$`, 
profileparameter.PostProfileParamsByName(d.DB.DB), auth.PrivLevelOperations, 
Authenticated, nil},
+               {1.1, http.MethodPost, `profiles/{id}/parameters/?$`, 
profileparameter.PostProfileParamsByID(d.DB.DB), auth.PrivLevelOperations, 
Authenticated, nil},
+               {1.1, http.MethodGet, `profileparameters/?(\.json)?$`, 
api.ReadHandler(profileparameter.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
+               {1.1, http.MethodPost, `profileparameters/?$`, 
api.CreateHandler(profileparameter.GetRefType(), d.DB), 
auth.PrivLevelOperations, Authenticated, nil},
+               {1.1, http.MethodPost, `profileparameter/?$`, 
profileparameter.PostProfileParam(d.DB.DB), auth.PrivLevelOperations, 
Authenticated, nil},
+               {1.1, http.MethodPost, `parameterprofile/?$`, 
profileparameter.PostParamProfile(d.DB.DB), auth.PrivLevelOperations, 
Authenticated, nil},
+               {1.1, http.MethodDelete, 
`profileparameters/{profileId}/{parameterId}$`, 
api.DeleteHandler(profileparameter.GetRefType(), d.DB), 
auth.PrivLevelOperations, Authenticated, nil},
 
                //SSLKeys deliveryservice endpoints here that are marked  
marked as '-wip' need to have tenancy checks added
                {1.3, http.MethodGet, 
`deliveryservices-wip/xmlId/{xmlID}/sslkeys$`, 
getDeliveryServiceSSLKeysByXMLIDHandler(d.DB, d.Config), auth.PrivLevelAdmin, 
Authenticated, nil},


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


With regards,
Apache Git Services

Reply via email to