From d728b0a9e096e90860eb83b069abd2d7472fc63d Mon Sep 17 00:00:00 2001 From: Dan Kirkwood Date: Tue, 3 Oct 2017 21:43:33 -0600 Subject: [PATCH] add parameters-wip endpoint with filters --- traffic_ops/tostructs/parameters.go | 33 +++++ traffic_ops/traffic_ops_golang/config_test.go | 1 - traffic_ops/traffic_ops_golang/parameters.go | 127 ++++++++++++++++++ .../traffic_ops_golang/parameters_test.go | 104 ++++++++++++++ traffic_ops/traffic_ops_golang/routes.go | 2 + 5 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 traffic_ops/tostructs/parameters.go create mode 100644 traffic_ops/traffic_ops_golang/parameters.go create mode 100644 traffic_ops/traffic_ops_golang/parameters_test.go diff --git a/traffic_ops/tostructs/parameters.go b/traffic_ops/tostructs/parameters.go new file mode 100644 index 0000000000..88a6484aba --- /dev/null +++ b/traffic_ops/tostructs/parameters.go @@ -0,0 +1,33 @@ +package tostructs + +/* + * 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. + */ + +type ParametersResponse struct { + Response []Parameter `json:"response"` +} + +type Parameter struct { + ConfigFile string `json:"configFile" db:"config_file"` + ID int `json:"id" db:"id"` + LastUpdated string `json:"lastUpdated" db:"last_updated"` + Name string `json:"name" db:"name"` + Secure bool `json:"secure" db:"secure"` + Value string `json:"value" db:"value"` +} diff --git a/traffic_ops/traffic_ops_golang/config_test.go b/traffic_ops/traffic_ops_golang/config_test.go index 3f3538829b..db2a81935b 100644 --- a/traffic_ops/traffic_ops_golang/config_test.go +++ b/traffic_ops/traffic_ops_golang/config_test.go @@ -196,7 +196,6 @@ func TestLoadConfig(t *testing.T) { t.Error("Good config -- unexpected error ", err) } - fmt.Printf("Cfg: %+v\n", cfg) if cfg.CertPath() != "/etc/pki/tls/certs/localhost.crt" { t.Error("Expected KeyPath() == /etc/pki/tls/private/localhost.key") } diff --git a/traffic_ops/traffic_ops_golang/parameters.go b/traffic_ops/traffic_ops_golang/parameters.go new file mode 100644 index 0000000000..f0a6325212 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/parameters.go @@ -0,0 +1,127 @@ +package main + +/* + * 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 ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/apache/incubator-trafficcontrol/lib/go-log" + "github.com/apache/incubator-trafficcontrol/traffic_ops/tostructs" + + "github.com/jmoiron/sqlx" +) + +const ParametersPrivLevel = PrivLevelReadOnly + +func parametersHandler(db *sqlx.DB) AuthRegexHandlerFunc { + return func(w http.ResponseWriter, r *http.Request, p PathParams, username string, privLevel int) { + handleErr := func(err error, status int) { + log.Errorf("%v %v\n", r.RemoteAddr, err) + w.WriteHeader(status) + fmt.Fprintf(w, http.StatusText(status)) + } + + q := r.URL.Query() + resp, err := getParametersResponse(q, db, privLevel) + if err != nil { + handleErr(err, http.StatusInternalServerError) + return + } + + respBts, err := json.Marshal(resp) + if err != nil { + handleErr(err, http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, "%s", respBts) + } +} + +func getParametersResponse(q url.Values, db *sqlx.DB, privLevel int) (*tostructs.ParametersResponse, error) { + parameters, err := getParameters(q, db, privLevel) + if err != nil { + return nil, fmt.Errorf("getting parameters response: %v", err) + } + + resp := tostructs.ParametersResponse{ + Response: parameters, + } + return &resp, nil +} + +func getParameters(v url.Values, db *sqlx.DB, privLevel int) ([]tostructs.Parameter, error) { + + var rows *sqlx.Rows + var err error + + // Query Parameters to Database Query column mappings + // see the fields mapped in the SQL query + queryParamsToSQLCols := map[string]string{ + "config_file": "p.config_file", + "id": "p.id", + "last_updated": "p.last_updated", + "name": "p.name", + "secure": "p.secure", + } + + query, queryValues := BuildQuery(v, selectParametersQuery(), queryParamsToSQLCols) + + log.Debugln("Query is ", query) + rows, err = db.NamedQuery(query, queryValues) + + if err != nil { + return nil, fmt.Errorf("querying: %v", err) + } + parameters := []tostructs.Parameter{} + + defer rows.Close() + + for rows.Next() { + var s tostructs.Parameter + if err = rows.StructScan(&s); err != nil { + return nil, fmt.Errorf("getting parameters: %v", err) + } + if s.Secure && privLevel < PrivLevelAdmin { + // Secure params only visible to admin + continue + } + parameters = append(parameters, s) + } + return parameters, nil +} + +func selectParametersQuery() string { + + query := `SELECT +config_file, +id, +last_updated, +name, +value, +secure + +FROM parameter p` + return query +} diff --git a/traffic_ops/traffic_ops_golang/parameters_test.go b/traffic_ops/traffic_ops_golang/parameters_test.go new file mode 100644 index 0000000000..561c63e07c --- /dev/null +++ b/traffic_ops/traffic_ops_golang/parameters_test.go @@ -0,0 +1,104 @@ +package main + +/* + * 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 ( + "net/url" + "testing" + + "github.com/apache/incubator-trafficcontrol/traffic_ops/tostructs" + "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/test" + "github.com/jmoiron/sqlx" + + sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1" +) + +func getTestParameters() []tostructs.Parameter { + parameters := []tostructs.Parameter{} + testParameter := tostructs.Parameter{ + ConfigFile: "global", + ID: 1, + LastUpdated: "lastUpdated", + Name: "paramname1", + Secure: false, + Value: "val1", + } + parameters = append(parameters, testParameter) + + testParameter2 := testParameter + testParameter2.Name = "paramname2" + testParameter2.Value = "val2" + testParameter2.ConfigFile = "some.config" + parameters = append(parameters, testParameter2) + + return parameters +} + +func TestGetParameters(t *testing.T) { + mockDB, mock, err := sqlmock.New() + defer mockDB.Close() + db := sqlx.NewDb(mockDB, "sqlmock") + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + testParameters := getTestParameters() + cols := test.ColsFromStructByTag("db", tostructs.Parameter{}) + rows := sqlmock.NewRows(cols) + + //TODO: drichardson - build helper to add these Rows from the struct values + // or by CSV if types get in the way + for _, ts := range testParameters { + rows = rows.AddRow( + ts.ConfigFile, + ts.ID, + ts.LastUpdated, + ts.Name, + ts.Secure, + ts.Value, + ) + } + mock.ExpectQuery("SELECT").WillReturnRows(rows) + v := url.Values{} + v.Set("dsId", "1") + + parameters, err := getParameters(v, db, PrivLevelAdmin) + if err != nil { + t.Errorf("getParameters expected: nil error, actual: %v", err) + } + + if len(parameters) != 2 { + t.Errorf("getParameters expected: len(parameters) == 1, actual: %v", len(parameters)) + } + +} + +type SortableParameters []tostructs.Parameter + +func (s SortableParameters) Len() int { + return len(s) +} +func (s SortableParameters) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s SortableParameters) Less(i, j int) bool { + return s[i].Name < s[j].Name +} diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go index a280932e1f..a7556339a4 100644 --- a/traffic_ops/traffic_ops_golang/routes.go +++ b/traffic_ops/traffic_ops_golang/routes.go @@ -56,6 +56,8 @@ func Routes(d ServerData) ([]Route, http.Handler, error) { {1.2, http.MethodGet, "divisions-wip.json$", wrapHeaders(wrapAuthWithData(divisionsHandler(d.DB), d.Insecure, d.Secrets[0], rd.PrivLevelStmt, DivisionsPrivLevel))}, {1.2, http.MethodGet, "hwinfo-wip$", wrapHeaders(wrapAuthWithData(hwInfoHandler(d.DB), d.Insecure, d.Secrets[0], rd.PrivLevelStmt, HWInfoPrivLevel))}, {1.2, http.MethodGet, "hwinfo-wip.json$", wrapHeaders(wrapAuthWithData(hwInfoHandler(d.DB), d.Insecure, d.Secrets[0], rd.PrivLevelStmt, HWInfoPrivLevel))}, + {1.2, http.MethodGet, "parameters-wip$", wrapHeaders(wrapAuthWithData(parametersHandler(d.DB), d.Insecure, d.Secrets[0], rd.PrivLevelStmt, ParametersPrivLevel))}, + {1.2, http.MethodGet, "parameters-wip.json$", wrapHeaders(wrapAuthWithData(parametersHandler(d.DB), d.Insecure, d.Secrets[0], rd.PrivLevelStmt, ParametersPrivLevel))}, }, rootHandler(d), nil }