You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ne...@apache.org on 2018/10/17 19:44:46 UTC

[trafficcontrol] branch master updated (06712ba -> 63e21f9)

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

neuman pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git.


    from 06712ba  adds support for single layer CDNs
     new 2359a63  Add TO Go plugin system
     new c416439  Add TO RPM description plugin list
     new 2348d11  Add TO Go cli flag to list plugins
     new 9d33613  TO Go move getting user to func, for plugin use
     new 19510fd  Add TO Go readme
     new 23561bf  Add TO Go plugin proxy, for microservices
     new d73df15  Add TO Go README note about auth
     new 63e21f9  Add TO Go plugin readme AddPlugin priority note

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 traffic_ops/build/build_rpm.sh                     |   4 +-
 traffic_ops/build/traffic_ops.spec                 |   5 +-
 traffic_ops/traffic_ops_golang/api/api.go          |  69 +++++++-
 traffic_ops/traffic_ops_golang/config/config.go    |  49 +++---
 traffic_ops/traffic_ops_golang/plugin/README.md    |  74 ++++++++
 .../traffic_ops_golang/plugin/hello_config.go      |  53 ++++++
 .../traffic_ops_golang}/plugin/hello_context.go    |  15 +-
 .../plugin/hello_shared_config.go                  |  18 +-
 .../traffic_ops_golang/plugin/hello_startup.go     |   6 +-
 .../traffic_ops_golang}/plugin/hello_world.go      |  15 +-
 traffic_ops/traffic_ops_golang/plugin/plugin.go    | 192 +++++++++++++++++++++
 traffic_ops/traffic_ops_golang/plugin/proxy.go     | 103 +++++++++++
 traffic_ops/traffic_ops_golang/routing.go          |  37 +++-
 .../traffic_ops_golang/traffic_ops_golang.go       |  13 +-
 traffic_ops/traffic_ops_golang/wrappers.go         |  81 +--------
 15 files changed, 594 insertions(+), 140 deletions(-)
 create mode 100644 traffic_ops/traffic_ops_golang/plugin/README.md
 create mode 100644 traffic_ops/traffic_ops_golang/plugin/hello_config.go
 copy {grove => traffic_ops/traffic_ops_golang}/plugin/hello_context.go (61%)
 copy grove/plugin/hello_context.go => traffic_ops/traffic_ops_golang/plugin/hello_shared_config.go (61%)
 copy grove/plugin/hello_world.go => traffic_ops/traffic_ops_golang/plugin/hello_startup.go (76%)
 copy {grove => traffic_ops/traffic_ops_golang}/plugin/hello_world.go (65%)
 create mode 100644 traffic_ops/traffic_ops_golang/plugin/plugin.go
 create mode 100644 traffic_ops/traffic_ops_golang/plugin/proxy.go


[trafficcontrol] 01/08: Add TO Go plugin system

Posted by ne...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 2359a634abe2564fa3778011287a7294adf8f873
Author: Robert Butts <ro...@apache.org>
AuthorDate: Thu Jul 5 08:02:31 2018 -0600

    Add TO Go plugin system
---
 traffic_ops/traffic_ops_golang/config/config.go    |  49 +++---
 .../traffic_ops_golang/plugin/hello_config.go      |  53 ++++++
 .../traffic_ops_golang/plugin/hello_context.go     |  34 ++++
 .../plugin/hello_shared_config.go                  |  33 ++++
 .../traffic_ops_golang/plugin/hello_startup.go     |  27 ++++
 .../traffic_ops_golang/plugin/hello_world.go       |  34 ++++
 traffic_ops/traffic_ops_golang/plugin/plugin.go    | 178 +++++++++++++++++++++
 traffic_ops/traffic_ops_golang/routing.go          |  20 ++-
 .../traffic_ops_golang/traffic_ops_golang.go       |   7 +-
 9 files changed, 409 insertions(+), 26 deletions(-)

diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go
index cef768e..78d8be7 100644
--- a/traffic_ops/traffic_ops_golang/config/config.go
+++ b/traffic_ops/traffic_ops_golang/config/config.go
@@ -60,29 +60,32 @@ type ConfigHypnotoad struct {
 
 // ConfigTrafficOpsGolang carries settings specific to traffic_ops_golang server
 type ConfigTrafficOpsGolang struct {
-	Port                     string         `json:"port"`
-	ProxyTimeout             int            `json:"proxy_timeout"`
-	ProxyKeepAlive           int            `json:"proxy_keep_alive"`
-	ProxyTLSTimeout          int            `json:"proxy_tls_timeout"`
-	ProxyReadHeaderTimeout   int            `json:"proxy_read_header_timeout"`
-	ReadTimeout              int            `json:"read_timeout"`
-	RequestTimeout           int            `json:"request_timeout"`
-	ReadHeaderTimeout        int            `json:"read_header_timeout"`
-	WriteTimeout             int            `json:"write_timeout"`
-	IdleTimeout              int            `json:"idle_timeout"`
-	LogLocationError         string         `json:"log_location_error"`
-	LogLocationWarning       string         `json:"log_location_warning"`
-	LogLocationInfo          string         `json:"log_location_info"`
-	LogLocationDebug         string         `json:"log_location_debug"`
-	LogLocationEvent         string         `json:"log_location_event"`
-	Insecure                 bool           `json:"insecure"`
-	MaxDBConnections         int            `json:"max_db_connections"`
-	DBMaxIdleConnections     int            `json:"db_max_idle_connections"`
-	DBConnMaxLifetimeSeconds int            `json:"db_conn_max_lifetime_seconds"`
-	BackendMaxConnections    map[string]int `json:"backend_max_connections"`
-	DBQueryTimeoutSeconds    int            `json:"db_query_timeout_seconds"`
-	ProfilingEnabled         bool           `json:"profiling_enabled"`
-	ProfilingLocation        string         `json:"profiling_location"`
+	Port                     string                     `json:"port"`
+	ProxyTimeout             int                        `json:"proxy_timeout"`
+	ProxyKeepAlive           int                        `json:"proxy_keep_alive"`
+	ProxyTLSTimeout          int                        `json:"proxy_tls_timeout"`
+	ProxyReadHeaderTimeout   int                        `json:"proxy_read_header_timeout"`
+	ReadTimeout              int                        `json:"read_timeout"`
+	RequestTimeout           int                        `json:"request_timeout"`
+	ReadHeaderTimeout        int                        `json:"read_header_timeout"`
+	WriteTimeout             int                        `json:"write_timeout"`
+	IdleTimeout              int                        `json:"idle_timeout"`
+	LogLocationError         string                     `json:"log_location_error"`
+	LogLocationWarning       string                     `json:"log_location_warning"`
+	LogLocationInfo          string                     `json:"log_location_info"`
+	LogLocationDebug         string                     `json:"log_location_debug"`
+	LogLocationEvent         string                     `json:"log_location_event"`
+	Insecure                 bool                       `json:"insecure"`
+	MaxDBConnections         int                        `json:"max_db_connections"`
+	DBMaxIdleConnections     int                        `json:"db_max_idle_connections"`
+	DBConnMaxLifetimeSeconds int                        `json:"db_conn_max_lifetime_seconds"`
+	BackendMaxConnections    map[string]int             `json:"backend_max_connections"`
+	DBQueryTimeoutSeconds    int                        `json:"db_query_timeout_seconds"`
+	Plugins                  []string                   `json:"plugins"`
+	PluginConfig             map[string]json.RawMessage `json:"plugin_config"`
+	PluginSharedConfig       map[string]interface{}     `json:"plugin_shared_config"`
+	ProfilingEnabled         bool                       `json:"profiling_enabled"`
+	ProfilingLocation        string                     `json:"profiling_location"`
 }
 
 // ConfigDatabase reflects the structure of the database.conf file
diff --git a/traffic_ops/traffic_ops_golang/plugin/hello_config.go b/traffic_ops/traffic_ops_golang/plugin/hello_config.go
new file mode 100644
index 0000000..04dbfe8
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/plugin/hello_config.go
@@ -0,0 +1,53 @@
+package plugin
+
+/*
+   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.
+*/
+
+import (
+	"encoding/json"
+
+	"github.com/apache/trafficcontrol/lib/go-log"
+)
+
+func init() {
+	AddPlugin(10000, Funcs{load: helloConfigLoad, onStartup: helloConfigStartup})
+}
+
+type HelloConfig struct {
+	Hello string `json:"hello"`
+}
+
+func helloConfigLoad(b json.RawMessage) interface{} {
+	cfg := HelloConfig{}
+	err := json.Unmarshal(b, &cfg)
+	if err != nil {
+		log.Debugln(`Hello! This is a config plugin! Unfortunately, your config JSON is not properly formatted. Config should look like: {"plugin_config": {"hello_config":{"hello": "anything can go here"}}}`)
+		return nil
+	}
+	log.Debugln("Hello! This is a config plugin! Successfully loaded config!")
+	return &cfg
+}
+
+func helloConfigStartup(d StartupData) {
+	if d.Cfg == nil {
+		log.Debugln("Hello! This is a config plugin! Unfortunately, your config is not set properly.")
+	}
+	cfg, ok := d.Cfg.(*HelloConfig)
+	if !ok {
+		// should never happen
+		log.Debugf("helloLoadConfig config '%v' type '%T' expected *HelloConfig\n", d.Cfg, d.Cfg)
+		return
+	}
+	log.Debugf("Hello! This is a config plugin! Your config is: %+v\n", cfg)
+}
diff --git a/traffic_ops/traffic_ops_golang/plugin/hello_context.go b/traffic_ops/traffic_ops_golang/plugin/hello_context.go
new file mode 100644
index 0000000..9e6b133
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/plugin/hello_context.go
@@ -0,0 +1,34 @@
+package plugin
+
+/*
+   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.
+*/
+
+import (
+	"github.com/apache/trafficcontrol/lib/go-log"
+)
+
+func init() {
+	AddPlugin(10000, Funcs{onStartup: helloCtxStart, onRequest: helloCtxOnReq})
+}
+
+func helloCtxStart(d StartupData) {
+	*d.Ctx = 42
+	log.Debugf("Hello! This is a context plugin! Start set context: %+v\n", *d.Ctx)
+}
+
+func helloCtxOnReq(d OnRequestData) IsRequestHandled {
+	ctx, ok := (*d.Ctx).(int)
+	log.Debugf("Hello! This is a context plugin! On Request got context: %+v %+v\n", ok, ctx)
+	return RequestUnhandled
+}
diff --git a/traffic_ops/traffic_ops_golang/plugin/hello_shared_config.go b/traffic_ops/traffic_ops_golang/plugin/hello_shared_config.go
new file mode 100644
index 0000000..3a16ede
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/plugin/hello_shared_config.go
@@ -0,0 +1,33 @@
+package plugin
+
+/*
+   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.
+*/
+
+import (
+	"encoding/json"
+
+	"github.com/apache/trafficcontrol/lib/go-log"
+)
+
+func init() {
+	AddPlugin(10000, Funcs{onStartup: helloSharedConfigStartup})
+}
+
+func helloSharedConfigStartup(d StartupData) {
+	if b, err := json.Marshal(d.SharedCfg); err == nil {
+		log.Debugln("Hello! This is a shared plugin data config! Your shared plugin config is: " + string(b))
+	} else {
+		log.Debugf("Hello! This is a shared plugin data config! Your shared plugin config is: %+v\n", d.SharedCfg)
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/plugin/hello_startup.go b/traffic_ops/traffic_ops_golang/plugin/hello_startup.go
new file mode 100644
index 0000000..9e6b21d
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/plugin/hello_startup.go
@@ -0,0 +1,27 @@
+package plugin
+
+/*
+   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.
+*/
+
+import (
+	"github.com/apache/trafficcontrol/lib/go-log"
+)
+
+func init() {
+	AddPlugin(10000, Funcs{onStartup: helloStartup})
+}
+
+func helloStartup(d StartupData) {
+	log.Debugln("Hello! This is a startup plugin! Config Version: " + d.AppCfg.Version)
+}
diff --git a/traffic_ops/traffic_ops_golang/plugin/hello_world.go b/traffic_ops/traffic_ops_golang/plugin/hello_world.go
new file mode 100644
index 0000000..ce36f28
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/plugin/hello_world.go
@@ -0,0 +1,34 @@
+package plugin
+
+/*
+   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.
+*/
+
+import (
+	"strings"
+)
+
+func init() {
+	AddPlugin(10000, Funcs{onRequest: hello})
+}
+
+const HelloPath = "/_hello"
+
+func hello(d OnRequestData) IsRequestHandled {
+	if !strings.HasPrefix(d.R.URL.Path, HelloPath) {
+		return RequestUnhandled
+	}
+	d.W.Header().Set("Content-Type", "text/plain")
+	d.W.Write([]byte("Hello, World!"))
+	return RequestHandled
+}
diff --git a/traffic_ops/traffic_ops_golang/plugin/plugin.go b/traffic_ops/traffic_ops_golang/plugin/plugin.go
new file mode 100644
index 0000000..14a6c6d
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/plugin/plugin.go
@@ -0,0 +1,178 @@
+package plugin
+
+/*
+   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.
+*/
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"os"
+	"path"
+	"runtime"
+	"sort"
+	"strings"
+	"time"
+
+	"github.com/apache/trafficcontrol/lib/go-log"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+)
+
+func Get(appCfg config.Config) Plugins {
+	log.Infof("plugin.Get given: %+v\n", appCfg.Plugins)
+	pluginSlice := getEnabled(appCfg.Plugins)
+	pluginCfg := loadConfig(pluginSlice, appCfg.PluginConfig)
+	ctx := map[string]*interface{}{}
+	return plugins{slice: pluginSlice, cfg: pluginCfg, ctx: ctx}
+}
+
+func getEnabled(enabled []string) pluginsSlice {
+	enabledM := map[string]struct{}{}
+	for _, name := range enabled {
+		enabledM[name] = struct{}{}
+	}
+	enabledPlugins := pluginsSlice{}
+	for _, plugin := range initPlugins {
+		if _, ok := enabledM[plugin.name]; !ok {
+			log.Infoln("getEnabled skipping: '" + plugin.name + "'")
+			continue
+		}
+		log.Infoln("plugin enabling: '" + plugin.name + "'")
+		enabledPlugins = append(enabledPlugins, plugin)
+	}
+	sort.Sort(enabledPlugins)
+	return enabledPlugins
+}
+
+func loadConfig(ps pluginsSlice, configJSON map[string]json.RawMessage) map[string]interface{} {
+	pluginConfigLoaders := loadFuncs(ps)
+	cfg := make(map[string]interface{}, len(configJSON))
+	for name, b := range configJSON {
+		if loadF := pluginConfigLoaders[name]; loadF != nil {
+			cfg[name] = loadF(b)
+		}
+	}
+	return cfg
+}
+
+func loadFuncs(ps pluginsSlice) map[string]LoadFunc {
+	lf := map[string]LoadFunc{}
+	for _, plugin := range ps {
+		if plugin.funcs.load == nil {
+			continue
+		}
+		lf[plugin.name] = LoadFunc(plugin.funcs.load)
+	}
+	return lf
+}
+
+type Plugins interface {
+	OnStartup(d StartupData)
+	OnRequest(d OnRequestData) bool
+}
+
+func AddPlugin(priority uint64, funcs Funcs) {
+	_, filename, _, ok := runtime.Caller(1)
+	if !ok {
+		fmt.Println(time.Now().Format(time.RFC3339Nano) + " Error plugin.AddPlugin: runtime.Caller failed, can't get plugin names") // print, because this is called in init, loggers don't exist yet
+		os.Exit(1)
+	}
+	pluginName := strings.TrimSuffix(path.Base(filename), ".go")
+	initPlugins = append(initPlugins, pluginObj{funcs: funcs, priority: priority, name: pluginName})
+}
+
+type Funcs struct {
+	load      LoadFunc
+	onStartup StartupFunc
+	onRequest OnRequestFunc
+}
+
+// Data is the common plugin data, given to most plugin hooks. This is designed to be embedded in the data structs for specific hooks.
+type Data struct {
+	Cfg       interface{}
+	Ctx       *interface{}
+	SharedCfg map[string]interface{}
+	RequestID uint64
+}
+
+type StartupData struct {
+	Data
+	AppCfg config.Config
+}
+
+type OnRequestData struct {
+	Data
+	W http.ResponseWriter
+	R *http.Request
+}
+
+type IsRequestHandled bool
+
+const (
+	RequestHandled   = IsRequestHandled(true)
+	RequestUnhandled = IsRequestHandled(false)
+)
+
+type LoadFunc func(json.RawMessage) interface{}
+type StartupFunc func(d StartupData)
+type OnRequestFunc func(d OnRequestData) IsRequestHandled
+
+type pluginObj struct {
+	funcs    Funcs
+	priority uint64
+	name     string
+}
+
+type plugins struct {
+	slice pluginsSlice
+	cfg   map[string]interface{}
+	ctx   map[string]*interface{}
+}
+
+type pluginsSlice []pluginObj
+
+func (p pluginsSlice) Len() int           { return len(p) }
+func (p pluginsSlice) Less(i, j int) bool { return p[i].priority < p[j].priority }
+func (p pluginsSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+// initPlugins is where plugins are registered via their init functions.
+var initPlugins = pluginsSlice{}
+
+func (ps plugins) OnStartup(d StartupData) {
+	for _, p := range ps.slice {
+		ictx := interface{}(nil)
+		ps.ctx[p.name] = &ictx
+		if p.funcs.onStartup == nil {
+			continue
+		}
+		d.Ctx = ps.ctx[p.name]
+		d.Cfg = ps.cfg[p.name]
+		p.funcs.onStartup(d)
+	}
+}
+
+// OnRequest returns a boolean whether to immediately stop processing the request. If a plugin returns true, this is immediately returned with no further plugins processed.
+func (ps plugins) OnRequest(d OnRequestData) bool {
+	for _, p := range ps.slice {
+		if p.funcs.onRequest == nil {
+			continue
+		}
+		d.Ctx = ps.ctx[p.name]
+		d.Cfg = ps.cfg[p.name]
+		if stop := p.funcs.onRequest(d); stop {
+			return true
+		}
+	}
+	return false
+}
diff --git a/traffic_ops/traffic_ops_golang/routing.go b/traffic_ops/traffic_ops_golang/routing.go
index 7f4fda8..8a19861 100644
--- a/traffic_ops/traffic_ops_golang/routing.go
+++ b/traffic_ops/traffic_ops_golang/routing.go
@@ -32,6 +32,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/plugin"
 
 	"github.com/jmoiron/sqlx"
 )
@@ -74,6 +75,7 @@ type ServerData struct {
 	config.Config
 	DB        *sqlx.DB
 	Profiling *bool // Yes this is a field in the config but we want to live reload this value and NOT the entire config
+	Plugins   plugin.Plugins
 }
 
 // CompiledRoute ...
@@ -170,7 +172,16 @@ func CompileRoutes(routes map[string][]PathHandler) map[string][]CompiledRoute {
 }
 
 // Handler - generic handler func used by the Handlers hooking into the routes
-func Handler(routes map[string][]CompiledRoute, catchall http.Handler, db *sqlx.DB, cfg *config.Config, getReqID func() uint64, w http.ResponseWriter, r *http.Request) {
+func Handler(
+	routes map[string][]CompiledRoute,
+	catchall http.Handler,
+	db *sqlx.DB,
+	cfg *config.Config,
+	getReqID func() uint64,
+	plugins plugin.Plugins,
+	w http.ResponseWriter,
+	r *http.Request,
+) {
 	reqID := getReqID()
 
 	reqIDStr := strconv.FormatUint(reqID, 10)
@@ -180,6 +191,11 @@ func Handler(routes map[string][]CompiledRoute, catchall http.Handler, db *sqlx.
 		log.Infoln(r.Method + " " + r.URL.Path + " handled (reqid " + reqIDStr + ") in " + time.Since(start).String())
 	}()
 
+	onReqData := plugin.OnRequestData{Data: plugin.Data{RequestID: reqID}, W: w, R: r}
+	if handled := plugins.OnRequest(onReqData); handled {
+		return
+	}
+
 	requested := r.URL.Path[1:]
 	mRoutes, ok := routes[r.Method]
 	if !ok {
@@ -221,7 +237,7 @@ func RegisterRoutes(d ServerData) error {
 	compiledRoutes := CompileRoutes(routes)
 	getReqID := nextReqIDGetter()
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		Handler(compiledRoutes, catchall, d.DB, &d.Config, getReqID, w, r)
+		Handler(compiledRoutes, catchall, d.DB, &d.Config, getReqID, d.Plugins, w, r)
 	})
 	return nil
 }
diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
index c042d51..c93e954 100644
--- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
+++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
@@ -35,6 +35,7 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/about"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/plugin"
 
 	"github.com/jmoiron/sqlx"
 	_ "github.com/lib/pq"
@@ -131,6 +132,8 @@ func main() {
 	db.SetMaxIdleConns(cfg.DBMaxIdleConnections)
 	db.SetConnMaxLifetime(time.Duration(cfg.DBConnMaxLifetimeSeconds) * time.Second)
 
+	// TODO combine
+	plugins := plugin.Get(cfg)
 	profiling := cfg.ProfilingEnabled
 
 	pprofMux := http.DefaultServeMux
@@ -146,11 +149,13 @@ func main() {
 		log.Errorln(debugServer.ListenAndServe())
 	}()
 
-	if err := RegisterRoutes(ServerData{DB: db, Config: cfg, Profiling: &profiling}); err != nil {
+	if err := RegisterRoutes(ServerData{DB: db, Config: cfg, Profiling: &profiling, Plugins: plugins}); err != nil {
 		log.Errorf("registering routes: %v\n", err)
 		return
 	}
 
+	plugins.OnStartup(plugin.StartupData{Data: plugin.Data{SharedCfg: cfg.PluginSharedConfig}, AppCfg: cfg})
+
 	log.Infof("Listening on " + cfg.Port)
 
 	server := &http.Server{


[trafficcontrol] 07/08: Add TO Go README note about auth

Posted by ne...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d73df15ad10c056e48fb865b524cd18f4aa12e0a
Author: Robert Butts <ro...@apache.org>
AuthorDate: Tue Oct 2 11:09:15 2018 -0600

    Add TO Go README note about auth
---
 traffic_ops/traffic_ops_golang/plugin/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/traffic_ops/traffic_ops_golang/plugin/README.md b/traffic_ops/traffic_ops_golang/plugin/README.md
index 820d2ad..75b81d2 100644
--- a/traffic_ops/traffic_ops_golang/plugin/README.md
+++ b/traffic_ops/traffic_ops_golang/plugin/README.md
@@ -31,7 +31,7 @@ The `Funcs` object contains functions for each hook, as well as a load function
 
 * `startup` is called when the application starts.
 
-* `onRequest` is called immediately when a request is received. It returns a boolean indicating whether to stop processing.
+* `onRequest` is called immediately when a request is received. It returns a boolean indicating whether to stop processing. Note this is called without authentication. If a plugin should be authenticated, it must do so itself. It is recommended to use `api.GetUserFromReq`, which will return an error if authentication fails.
 
 The simplest example is the `hello_world` plugin. See `plugin/hello_world.go`.
 


[trafficcontrol] 05/08: Add TO Go readme

Posted by ne...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 19510fd4c8ef8b8787666f5254af6f72690c2f7a
Author: Robert Butts <ro...@apache.org>
AuthorDate: Mon Oct 1 13:18:21 2018 -0600

    Add TO Go readme
    
    As requested on the mailing list:
    https://lists.apache.org/thread.html/3b1386a5cb5dc0ba1d89931781668a16f0ae99aabf5662c332b40605@%3Cdev.trafficcontrol.apache.org%3E
---
 traffic_ops/traffic_ops_golang/plugin/README.md | 74 +++++++++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/traffic_ops/traffic_ops_golang/plugin/README.md b/traffic_ops/traffic_ops_golang/plugin/README.md
new file mode 100644
index 0000000..820d2ad
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/plugin/README.md
@@ -0,0 +1,74 @@
+<!--
+    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.
+-->
+
+# Adding a Plugin
+
+To add a plugin, create a new `.go` file in the `traffic_ops_golang/plugin` directory. This file should have a unique name, to avoid conflicts. Consider prefixing it with your company name, website, or a UUID.
+
+The filename, sans `.go`, is the name of your plugin, and will be the key used for configuration in the remap file. For example, if your file is `f49e54fc-fd17-4e1c-92c6-67028fde8504-hello-world.go`, the name of your plugin is `f49e54fc-fd17-4e1c-92c6-67028fde8504-hello-world`.
+
+Plugins are registered via calls to `AddPlugin` inside an `init` function in the plugin's file.
+
+The `Funcs` object contains functions for each hook, as well as a load function for loading configuration from the remap file. The current hooks are `load`, `startup`, and `onRequest`. If your plugin does not use a hook, it may be nil.
+
+* `load` is called when the application starts, is given config data, and must return the loaded configuration object.
+
+* `startup` is called when the application starts.
+
+* `onRequest` is called immediately when a request is received. It returns a boolean indicating whether to stop processing.
+
+The simplest example is the `hello_world` plugin. See `plugin/hello_world.go`.
+
+```go
+import (
+	"strings"
+)
+func init() {
+	AddPlugin(10000, Funcs{onRequest: hello})
+}
+const HelloPath = "/_hello"
+func hello(d OnRequestData) IsRequestHandled {
+	if !strings.HasPrefix(d.R.URL.Path, HelloPath) {
+		return RequestUnhandled
+	}
+	d.W.Header().Set("Content-Type", "text/plain")
+	d.W.Write([]byte("Hello, World!"))
+	return RequestHandled
+}
+```
+
+The plugin is initialized via `AddPlugin`, and its `hello` function is set as the `startup` hook. The `hello` function has the signature of `plugin.StartupFunc`.
+
+# Examples
+
+Example plugins are included in the `/plugin` directory
+
+*hello_world*: Example of a simple HTTP endpoint.
+*hello_config*: Example of loading and using config file data.
+*hello_shared_config*: Example of loading and using config data which is shared among all plugins.
+*hello_context*: Example of passing context data between hook functions.
+*hello_startup*: Example of running a plugin function when the application starts.
+
+# Glossary
+
+Definitions of terms used in this document.
+
+*Plugin*: A self-contained component whose code is executed when certain events in the main application occur.
+*Hook*: A plugin function which is called when a certain event happens in the main application.
+*Plugin Data*: Application data given to a plugin, as a function parameter passed to a hook function, including configuration data, running state, and HTTP request state.


[trafficcontrol] 06/08: Add TO Go plugin proxy, for microservices

Posted by ne...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 23561bfefe97a7a84208eb08a4bbc604eca1aa4e
Author: Robert Butts <ro...@apache.org>
AuthorDate: Mon Oct 1 13:19:42 2018 -0600

    Add TO Go plugin proxy, for microservices
    
    As requested by the mailing list:
    https://lists.apache.org/thread.html/3b1386a5cb5dc0ba1d89931781668a16f0ae99aabf5662c332b40605@%3Cdev.trafficcontrol.apache.org%3E
---
 traffic_ops/traffic_ops_golang/plugin/proxy.go | 103 +++++++++++++++++++++++++
 1 file changed, 103 insertions(+)

diff --git a/traffic_ops/traffic_ops_golang/plugin/proxy.go b/traffic_ops/traffic_ops_golang/plugin/proxy.go
new file mode 100644
index 0000000..b2bda9e
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/plugin/proxy.go
@@ -0,0 +1,103 @@
+package plugin
+
+/*
+   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.
+*/
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"strings"
+
+	"github.com/apache/trafficcontrol/lib/go-log"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+// The proxy plugin reverse-proxies to other HTTP services, as configured.
+//
+// Configuration is in `cdn.conf` (like all plugins) and of the form `{"plugin_config": {"proxy":[{"path": "/my-extension-route", "uri": "https://example.net"}]}}`
+//
+// Users are required to be authenticated. For modifications such as removing authentication or amending the proxied request, forking this plugin is encouraged.
+
+func init() {
+	AddPlugin(10000, Funcs{load: proxyLoad, onRequest: proxyOnReq})
+}
+
+type ProxyConfig []ProxyRemap
+
+type ProxyRemap struct {
+	Path string   `json:"path"`
+	URI  *url.URL `json:"uri"`
+}
+
+func (r *ProxyRemap) UnmarshalJSON(b []byte) error {
+	type ProxyRemapJSON struct {
+		Path string `json:"path"`
+		URI  string `json:"uri"`
+	}
+	rj := ProxyRemapJSON{}
+	if err := json.Unmarshal(b, &rj); err != nil {
+		return err
+	}
+	uri, err := url.Parse(rj.URI)
+	if err != nil {
+		return err
+	}
+	r.Path = rj.Path
+	r.URI = uri
+	return nil
+}
+
+func proxyLoad(b json.RawMessage) interface{} {
+	cfg := ProxyConfig{}
+	err := json.Unmarshal(b, &cfg)
+	if err != nil {
+		log.Debugln(`plugin proxy: malformed config. Config should look like: {"plugin_config": {"proxy":[{"path": "/my-extension-route", "uri": "https://example.net"}]}}`)
+		return nil
+	}
+	log.Debugf("plugin proxy: loaded config %+v\n", cfg)
+	return &cfg
+}
+
+func proxyOnReq(d OnRequestData) IsRequestHandled {
+	if d.Cfg == nil {
+		return RequestUnhandled
+	}
+	cfg, ok := d.Cfg.(*ProxyConfig)
+	if !ok {
+		// should never happen
+		log.Errorf("plugin proxy config '%v' type '%T' expected *ProxyConfig\n", d.Cfg, d.Cfg)
+		return RequestUnhandled
+	}
+
+	for _, remap := range *cfg {
+		if !strings.HasPrefix(d.R.URL.Path, remap.Path) {
+			continue
+		}
+		return proxyHandle(d.W, d.R, d, remap.URI)
+	}
+	return RequestUnhandled
+}
+
+func proxyHandle(w http.ResponseWriter, r *http.Request, d OnRequestData, proxyURI *url.URL) IsRequestHandled {
+	usr, userErr, sysErr, errCode := api.GetUserFromReq(w, r, d.AppCfg.Secrets[0]) // require login
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, nil, errCode, userErr, sysErr)
+		return RequestHandled
+	}
+	rp := httputil.NewSingleHostReverseProxy(proxyURI)
+	rp.ServeHTTP(w, r)
+	return RequestHandled
+}


[trafficcontrol] 02/08: Add TO RPM description plugin list

Posted by ne...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c416439c701b78bf12a5921e0bfb3cc22d904873
Author: Robert Butts <ro...@apache.org>
AuthorDate: Thu Jul 12 16:06:25 2018 -0600

    Add TO RPM description plugin list
---
 traffic_ops/build/build_rpm.sh     | 4 ++--
 traffic_ops/build/traffic_ops.spec | 5 ++++-
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/traffic_ops/build/build_rpm.sh b/traffic_ops/build/build_rpm.sh
index 9fec83c..b7cf92b 100755
--- a/traffic_ops/build/build_rpm.sh
+++ b/traffic_ops/build/build_rpm.sh
@@ -29,7 +29,6 @@ function importFunctions() {
 	. "$functions_sh"
 }
 
-
 # ---------------------------------------
 function initBuildArea() {
 	echo "Initializing the build area."
@@ -53,7 +52,8 @@ function initBuildArea() {
 	cp -p bin/supermicro_udev_mapper.pl "$to_ort_dest"
 	tar -czvf "$to_ort_dest".tgz -C "$RPMBUILD"/SOURCES $(basename "$to_ort_dest") || \
 		 { echo "Could not create tar archive $to_ort_dest: $?"; exit 1; }
-	
+
+	export PLUGINS=$(grep -l -P '(?<!func )AddPlugin\(' ${TO_DIR}/traffic_ops_golang/plugin/*.go | xargs -I '{}' basename {} '.go')
 	echo "The build area has been initialized."
 }
 
diff --git a/traffic_ops/build/traffic_ops.spec b/traffic_ops/build/traffic_ops.spec
index b47251e..4d428ae 100644
--- a/traffic_ops/build/traffic_ops.spec
+++ b/traffic_ops/build/traffic_ops.spec
@@ -44,7 +44,10 @@ Requires(postun): /usr/sbin/userdel
 %define PACKAGEDIR %{prefix}
 
 %description
-Installs Traffic Ops.
+Traffic Ops is the tool for administration (configuration and monitoring) of all components in a Traffic Control CDN.
+
+This package provides Traffic Ops with the following plugins:
+%{getenv:PLUGINS}
 
 Built: %(date) by %{getenv: USER}
 


[trafficcontrol] 04/08: TO Go move getting user to func, for plugin use

Posted by ne...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9d3361316d5298cdebaabf7902b6ea8b3b3a1121
Author: Robert Butts <ro...@apache.org>
AuthorDate: Tue Sep 25 16:05:45 2018 -0600

    TO Go move getting user to func, for plugin use
---
 traffic_ops/traffic_ops_golang/api/api.go          | 69 ++++++++++++++++--
 traffic_ops/traffic_ops_golang/plugin/plugin.go    |  7 +-
 traffic_ops/traffic_ops_golang/routing.go          | 19 +++--
 .../traffic_ops_golang/traffic_ops_golang.go       |  2 +-
 traffic_ops/traffic_ops_golang/wrappers.go         | 81 ++--------------------
 5 files changed, 87 insertions(+), 91 deletions(-)

diff --git a/traffic_ops/traffic_ops_golang/api/api.go b/traffic_ops/traffic_ops_golang/api/api.go
index 6d3d5d0..d46df5f 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -37,6 +37,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tocookie"
 
 	"github.com/jmoiron/sqlx"
 	"github.com/lib/pq"
@@ -264,7 +265,7 @@ type APIInfo struct {
 //
 // It is encouraged to call APIInfo.Tx.Tx.Commit() manually when all queries are finished, to release database resources early, and also to return an error to the user if the commit failed.
 //
-// NewInfo guarantees the returned APIInfo.Tx is nil or valid, even if a returned error is not nil. Hence, it is safe to pass the Tx to HandleErr when this returns errors.
+// NewInfo guarantees the returned APIInfo.Tx is non-nil and APIInfo.Tx.Tx is nil or valid, even if a returned error is not nil. Hence, it is safe to pass the Tx.Tx to HandleErr when this returns errors.
 //
 // Close() must be called to free resources, and should be called in a defer immediately after NewInfo(), to finish the transaction.
 //
@@ -292,29 +293,29 @@ type APIInfo struct {
 func NewInfo(r *http.Request, requiredParams []string, intParamNames []string) (*APIInfo, error, error, int) {
 	db, err := getDB(r.Context())
 	if err != nil {
-		return &APIInfo{}, errors.New("getting db: " + err.Error()), nil, http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting db: " + err.Error()), nil, http.StatusInternalServerError
 	}
 	cfg, err := getConfig(r.Context())
 	if err != nil {
-		return &APIInfo{}, errors.New("getting config: " + err.Error()), nil, http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting config: " + err.Error()), nil, http.StatusInternalServerError
 	}
 	reqID, err := getReqID(r.Context())
 	if err != nil {
-		return &APIInfo{}, errors.New("getting reqID: " + err.Error()), nil, http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting reqID: " + err.Error()), nil, http.StatusInternalServerError
 	}
 
 	user, err := auth.GetCurrentUser(r.Context())
 	if err != nil {
-		return &APIInfo{}, errors.New("getting user: " + err.Error()), nil, http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting user: " + err.Error()), nil, http.StatusInternalServerError
 	}
 	params, intParams, userErr, sysErr, errCode := AllParams(r, requiredParams, intParamNames)
 	if userErr != nil || sysErr != nil {
-		return &APIInfo{}, userErr, sysErr, errCode
+		return &APIInfo{Tx: &sqlx.Tx{}}, userErr, sysErr, errCode
 	}
 	dbCtx, _ := context.WithTimeout(r.Context(), time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second) //only place we could call cancel here is in APIInfo.Close(), which already will rollback the transaction (which is all cancel will do.)
 	tx, err := db.BeginTxx(dbCtx, nil)                                                                 // must be last, MUST not return an error if this succeeds, without closing the tx
 	if err != nil {
-		return &APIInfo{}, userErr, errors.New("could not begin transaction: " + err.Error()), http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, userErr, errors.New("could not begin transaction: " + err.Error()), http.StatusInternalServerError
 	}
 	return &APIInfo{
 		Config:    cfg,
@@ -479,3 +480,57 @@ func ParseDBError(ierr error) (error, error, int) {
 
 	return nil, err, http.StatusInternalServerError
 }
+
+// GetUserFromReq returns the current user, any user error, any system error, and an error code to be returned if either error was not nil.
+// This also uses the given ResponseWriter to refresh the cookie, if it was valid.
+func GetUserFromReq(w http.ResponseWriter, r *http.Request, secret string) (auth.CurrentUser, error, error, int) {
+	cookie, err := r.Cookie(tocookie.Name)
+	if err != nil {
+		return auth.CurrentUser{}, errors.New("Unauthorized, please log in."), errors.New("error getting cookie: " + err.Error()), http.StatusUnauthorized
+	}
+
+	if cookie == nil {
+		return auth.CurrentUser{}, errors.New("Unauthorized, please log in."), nil, http.StatusUnauthorized
+	}
+
+	oldCookie, err := tocookie.Parse(secret, cookie.Value)
+	if err != nil {
+		return auth.CurrentUser{}, errors.New("Unauthorized, please log in."), errors.New("error parsing cookie: " + err.Error()), http.StatusUnauthorized
+	}
+
+	username := oldCookie.AuthData
+	if username == "" {
+		return auth.CurrentUser{}, errors.New("Unauthorized, please log in."), nil, http.StatusUnauthorized
+	}
+	db := (*sqlx.DB)(nil)
+	val := r.Context().Value(DBContextKey)
+	if val == nil {
+		return auth.CurrentUser{}, nil, errors.New("request context db missing"), http.StatusInternalServerError
+	}
+	switch v := val.(type) {
+	case *sqlx.DB:
+		db = v
+	default:
+		return auth.CurrentUser{}, nil, fmt.Errorf("request context db unknown type %T", val), http.StatusInternalServerError
+	}
+
+	cfg, err := GetConfig(r.Context())
+	if err != nil {
+		return auth.CurrentUser{}, nil, errors.New("request context config missing"), http.StatusInternalServerError
+	}
+
+	user, userErr, sysErr, code := auth.GetCurrentUserFromDB(db, username, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second)
+	if userErr != nil || sysErr != nil {
+		return auth.CurrentUser{}, userErr, sysErr, code
+	}
+
+	newCookieVal := tocookie.Refresh(oldCookie, secret)
+	http.SetCookie(w, &http.Cookie{Name: tocookie.Name, Value: newCookieVal, Path: "/", HttpOnly: true})
+	return user, nil, nil, http.StatusOK
+}
+
+func AddUserToReq(r *http.Request, u auth.CurrentUser) *http.Request {
+	ctx := r.Context()
+	ctx = context.WithValue(ctx, auth.CurrentUserKey, u)
+	return r.WithContext(ctx)
+}
diff --git a/traffic_ops/traffic_ops_golang/plugin/plugin.go b/traffic_ops/traffic_ops_golang/plugin/plugin.go
index 2c2ab79..998303f 100644
--- a/traffic_ops/traffic_ops_golang/plugin/plugin.go
+++ b/traffic_ops/traffic_ops_golang/plugin/plugin.go
@@ -97,7 +97,9 @@ func AddPlugin(priority uint64, funcs Funcs) {
 		fmt.Println(time.Now().Format(time.RFC3339Nano) + " Error plugin.AddPlugin: runtime.Caller failed, can't get plugin names") // print, because this is called in init, loggers don't exist yet
 		os.Exit(1)
 	}
+
 	pluginName := strings.TrimSuffix(path.Base(filename), ".go")
+	log.Debugln("AddPlugin adding " + pluginName)
 	initPlugins = append(initPlugins, pluginObj{funcs: funcs, priority: priority, name: pluginName})
 }
 
@@ -113,11 +115,11 @@ type Data struct {
 	Ctx       *interface{}
 	SharedCfg map[string]interface{}
 	RequestID uint64
+	AppCfg    config.Config
 }
 
 type StartupData struct {
 	Data
-	AppCfg config.Config
 }
 
 type OnRequestData struct {
@@ -173,12 +175,15 @@ func (ps plugins) OnStartup(d StartupData) {
 
 // OnRequest returns a boolean whether to immediately stop processing the request. If a plugin returns true, this is immediately returned with no further plugins processed.
 func (ps plugins) OnRequest(d OnRequestData) bool {
+	log.Debugln("DEBUG plugins.OnRequest calling %+v\n", len(ps.slice))
 	for _, p := range ps.slice {
 		if p.funcs.onRequest == nil {
+			log.Debugln("plugins.OnRequest plugging " + p.name + " - no onRequest func")
 			continue
 		}
 		d.Ctx = ps.ctx[p.name]
 		d.Cfg = ps.cfg[p.name]
+		log.Debugln("plugins.OnRequest plugging " + p.name)
 		if stop := p.funcs.onRequest(d); stop {
 			return true
 		}
diff --git a/traffic_ops/traffic_ops_golang/routing.go b/traffic_ops/traffic_ops_golang/routing.go
index 8a19861..a6a75ce 100644
--- a/traffic_ops/traffic_ops_golang/routing.go
+++ b/traffic_ops/traffic_ops_golang/routing.go
@@ -191,7 +191,16 @@ func Handler(
 		log.Infoln(r.Method + " " + r.URL.Path + " handled (reqid " + reqIDStr + ") in " + time.Since(start).String())
 	}()
 
-	onReqData := plugin.OnRequestData{Data: plugin.Data{RequestID: reqID}, W: w, R: r}
+	ctx := r.Context()
+	ctx = context.WithValue(ctx, api.DBContextKey, db)
+	ctx = context.WithValue(ctx, api.ConfigContextKey, cfg)
+	ctx = context.WithValue(ctx, api.ReqIDContextKey, reqID)
+
+	// plugins have no pre-parsed path params, but add an empty map so they can use the api helper funcs that require it.
+	pluginCtx := context.WithValue(ctx, api.PathParamsKey, map[string]string{})
+	pluginReq := r.WithContext(pluginCtx)
+
+	onReqData := plugin.OnRequestData{Data: plugin.Data{RequestID: reqID, AppCfg: *cfg}, W: w, R: pluginReq}
 	if handled := plugins.OnRequest(onReqData); handled {
 		return
 	}
@@ -213,12 +222,8 @@ func Handler(
 			params[v] = match[i+1]
 		}
 
-		ctx := r.Context()
-		ctx = context.WithValue(ctx, api.PathParamsKey, params)
-		ctx = context.WithValue(ctx, api.DBContextKey, db)
-		ctx = context.WithValue(ctx, api.ConfigContextKey, cfg)
-		ctx = context.WithValue(ctx, api.ReqIDContextKey, reqID)
-		r = r.WithContext(ctx)
+		routeCtx := context.WithValue(ctx, api.PathParamsKey, params)
+		r = r.WithContext(routeCtx)
 		compiledRoute.Handler(w, r)
 		return
 	}
diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
index 1941293..3fad34d 100644
--- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
+++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
@@ -160,7 +160,7 @@ func main() {
 		return
 	}
 
-	plugins.OnStartup(plugin.StartupData{Data: plugin.Data{SharedCfg: cfg.PluginSharedConfig}, AppCfg: cfg})
+	plugins.OnStartup(plugin.StartupData{Data: plugin.Data{SharedCfg: cfg.PluginSharedConfig, AppCfg: cfg}})
 
 	log.Infof("Listening on " + cfg.Port)
 
diff --git a/traffic_ops/traffic_ops_golang/wrappers.go b/traffic_ops/traffic_ops_golang/wrappers.go
index 914f706..6d3b0d9 100644
--- a/traffic_ops/traffic_ops_golang/wrappers.go
+++ b/traffic_ops/traffic_ops_golang/wrappers.go
@@ -22,10 +22,8 @@ package main
 import (
 	"bytes"
 	"compress/gzip"
-	"context"
 	"crypto/sha512"
 	"encoding/base64"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
@@ -38,9 +36,7 @@ import (
 	tc "github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/about"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
-	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tocookie"
-	"github.com/jmoiron/sqlx"
 )
 
 // ServerName - the server identifier
@@ -59,81 +55,16 @@ func (a AuthBase) GetWrapper(privLevelRequired int) Middleware {
 	}
 	return func(handlerFunc http.HandlerFunc) http.HandlerFunc {
 		return func(w http.ResponseWriter, r *http.Request) {
-			// TODO remove, and make username available to wrapLogTime
-			iw := &Interceptor{w: w}
-			w = iw
-			handleErr := func(status int, err error) {
-				errBytes, jsonErr := json.Marshal(tc.CreateErrorAlerts(err))
-				if jsonErr != nil {
-					log.Errorf("failed to marshal error: %s\n", jsonErr)
-					w.WriteHeader(http.StatusInternalServerError)
-					fmt.Fprintf(w, http.StatusText(http.StatusInternalServerError))
-					return
-				}
-				w.Header().Set(tc.ContentType, tc.ApplicationJson)
-				w.WriteHeader(status)
-				fmt.Fprintf(w, "%s", errBytes)
-			}
-
-			cookie, err := r.Cookie(tocookie.Name)
-			if err != nil {
-				log.Errorf("error getting cookie: %s", err)
-				handleErr(http.StatusUnauthorized, errors.New("Unauthorized, please log in."))
-				return
-			}
-
-			if cookie == nil {
-				handleErr(http.StatusUnauthorized, errors.New("Unauthorized, please log in."))
-				return
-			}
-
-			oldCookie, err := tocookie.Parse(a.secret, cookie.Value)
-			if err != nil {
-				log.Errorf("error parsing cookie: %s", err)
-				handleErr(http.StatusUnauthorized, errors.New("Unauthorized, please log in."))
-				return
-			}
-
-			username := oldCookie.AuthData
-			if username == "" {
-				handleErr(http.StatusUnauthorized, errors.New("Unauthorized, please log in."))
-				return
-			}
-			var DB *sqlx.DB
-			val := r.Context().Value(api.DBContextKey)
-			if val != nil {
-				switch v := val.(type) {
-				case *sqlx.DB:
-					DB = v
-				default:
-					handleErr(http.StatusInternalServerError, errors.New("No DB found"))
-				}
-			} else {
-				handleErr(http.StatusInternalServerError, errors.New("No DB found"))
-			}
-
-			cfg, err := api.GetConfig(r.Context())
-			if err != nil {
-				handleErr(http.StatusInternalServerError, errors.New("No config found"))
-			}
-
-			currentUserInfo, userErr, sysErr, code := auth.GetCurrentUserFromDB(DB, username, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second)
+			user, userErr, sysErr, errCode := api.GetUserFromReq(w, r, a.secret)
 			if userErr != nil || sysErr != nil {
-				api.HandleErr(w, r, nil, code, userErr, sysErr)
+				api.HandleErr(w, r, nil, errCode, userErr, sysErr)
 				return
 			}
-			if currentUserInfo.PrivLevel < privLevelRequired {
-				handleErr(http.StatusForbidden, errors.New("Forbidden."))
-				return
+			if user.PrivLevel < privLevelRequired {
+				api.HandleErr(w, r, nil, http.StatusForbidden, errors.New("Forbidden."), nil)
 			}
-
-			newCookieVal := tocookie.Refresh(oldCookie, a.secret)
-			http.SetCookie(w, &http.Cookie{Name: tocookie.Name, Value: newCookieVal, Path: "/", HttpOnly: true})
-
-			ctx := r.Context()
-			ctx = context.WithValue(ctx, auth.CurrentUserKey, currentUserInfo)
-
-			handlerFunc(w, r.WithContext(ctx))
+			r = api.AddUserToReq(r, user)
+			handlerFunc(w, r)
 		}
 	}
 }


[trafficcontrol] 08/08: Add TO Go plugin readme AddPlugin priority note

Posted by ne...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 63e21f9fa6eaaca754ed0efc2856904f10942127
Author: Robert Butts <ro...@apache.org>
AuthorDate: Tue Oct 2 11:14:32 2018 -0600

    Add TO Go plugin readme AddPlugin priority note
---
 traffic_ops/traffic_ops_golang/plugin/README.md | 2 +-
 traffic_ops/traffic_ops_golang/plugin/proxy.go  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/traffic_ops/traffic_ops_golang/plugin/README.md b/traffic_ops/traffic_ops_golang/plugin/README.md
index 75b81d2..715c54d 100644
--- a/traffic_ops/traffic_ops_golang/plugin/README.md
+++ b/traffic_ops/traffic_ops_golang/plugin/README.md
@@ -23,7 +23,7 @@ To add a plugin, create a new `.go` file in the `traffic_ops_golang/plugin` dire
 
 The filename, sans `.go`, is the name of your plugin, and will be the key used for configuration in the remap file. For example, if your file is `f49e54fc-fd17-4e1c-92c6-67028fde8504-hello-world.go`, the name of your plugin is `f49e54fc-fd17-4e1c-92c6-67028fde8504-hello-world`.
 
-Plugins are registered via calls to `AddPlugin` inside an `init` function in the plugin's file.
+Plugins are registered via calls to `AddPlugin` inside an `init` function in the plugin's file. The `AddPlugin` function takes a priority, and a set of hook functions. The priority is the order in which plugins are called, starting from 0. Note the priority of plugins included with Traffic Control use a base priority of 10000, unless priority order matters for them.
 
 The `Funcs` object contains functions for each hook, as well as a load function for loading configuration from the remap file. The current hooks are `load`, `startup`, and `onRequest`. If your plugin does not use a hook, it may be nil.
 
diff --git a/traffic_ops/traffic_ops_golang/plugin/proxy.go b/traffic_ops/traffic_ops_golang/plugin/proxy.go
index b2bda9e..57a5b4b 100644
--- a/traffic_ops/traffic_ops_golang/plugin/proxy.go
+++ b/traffic_ops/traffic_ops_golang/plugin/proxy.go
@@ -92,7 +92,7 @@ func proxyOnReq(d OnRequestData) IsRequestHandled {
 }
 
 func proxyHandle(w http.ResponseWriter, r *http.Request, d OnRequestData, proxyURI *url.URL) IsRequestHandled {
-	usr, userErr, sysErr, errCode := api.GetUserFromReq(w, r, d.AppCfg.Secrets[0]) // require login
+	_, userErr, sysErr, errCode := api.GetUserFromReq(w, r, d.AppCfg.Secrets[0]) // require login
 	if userErr != nil || sysErr != nil {
 		api.HandleErr(w, r, nil, errCode, userErr, sysErr)
 		return RequestHandled


[trafficcontrol] 03/08: Add TO Go cli flag to list plugins

Posted by ne...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 2348d118dcca9639f85600d1ac8a6d4e6b0ab7f5
Author: Robert Butts <ro...@apache.org>
AuthorDate: Wed Jul 18 08:41:24 2018 -0600

    Add TO Go cli flag to list plugins
---
 traffic_ops/traffic_ops_golang/plugin/plugin.go      | 9 +++++++++
 traffic_ops/traffic_ops_golang/traffic_ops_golang.go | 6 ++++++
 2 files changed, 15 insertions(+)

diff --git a/traffic_ops/traffic_ops_golang/plugin/plugin.go b/traffic_ops/traffic_ops_golang/plugin/plugin.go
index 14a6c6d..2c2ab79 100644
--- a/traffic_ops/traffic_ops_golang/plugin/plugin.go
+++ b/traffic_ops/traffic_ops_golang/plugin/plugin.go
@@ -29,6 +29,15 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
 )
 
+// List returns the list of plugins compiled into the calling executable.
+func List() []string {
+	l := []string{}
+	for _, p := range initPlugins {
+		l = append(l, p.name)
+	}
+	return l
+}
+
 func Get(appCfg config.Config) Plugins {
 	log.Infof("plugin.Get given: %+v\n", appCfg.Plugins)
 	pluginSlice := getEnabled(appCfg.Plugins)
diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
index c93e954..1941293 100644
--- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
+++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
@@ -29,6 +29,7 @@ import (
 	"os/signal"
 	"path/filepath"
 	"runtime/pprof"
+	"strings"
 	"time"
 
 	"github.com/apache/trafficcontrol/lib/go-log"
@@ -51,6 +52,7 @@ func init() {
 
 func main() {
 	showVersion := flag.Bool("version", false, "Show version and exit")
+	showPlugins := flag.Bool("plugins", false, "Show the list of plugins and exit")
 	configFileName := flag.String("cfg", "", "The config file path")
 	dbConfigFileName := flag.String("dbcfg", "", "The db config file path")
 	riakConfigFileName := flag.String("riakcfg", "", "The riak config file path")
@@ -60,6 +62,10 @@ func main() {
 		fmt.Println(about.About.RPMVersion)
 		os.Exit(0)
 	}
+	if *showPlugins {
+		fmt.Println(strings.Join(plugin.List(), "\n"))
+		os.Exit(0)
+	}
 	if len(os.Args) < 2 {
 		flag.Usage()
 		os.Exit(1)