diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..a05bd14 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,38 @@ +# Golang CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-go/ for more details +version: 2 +jobs: + build: + docker: + # specify the version + - image: circleci/golang:1.9 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + + working_directory: /go/src/github.com/aunem/transpose + steps: + - checkout + + - run: + name: Install Docker Compose + command: | + curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` > ~/docker-compose + chmod +x ~/docker-compose + sudo mv ~/docker-compose /usr/local/bin/docker-compose + + - setup_remote_docker + + - run: + name: Install deps + command: | + ls + make deps + + - run: + name: Integration test + command: | + make integration diff --git a/Gopkg.lock b/Gopkg.lock index 4a1b71f..b5580ba 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -5,7 +5,7 @@ branch = "poc" name = "github.com/aunem/transpose-plugins" packages = ["listener/http"] - revision = "fc37803b59edbbb30495388380b606bee226a36c" + revision = "fea78728c3d7f254246c5f6d3c70d60946e95eda" [[projects]] name = "github.com/davecgh/go-spew" @@ -84,7 +84,7 @@ "json/scanner", "json/token" ] - revision = "f40e974e75af4e271d97ce0fc917af5898ae7bda" + revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168" [[projects]] name = "github.com/inconshreveable/mousetrap" @@ -134,8 +134,8 @@ ".", "mem" ] - revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c" - version = "v1.0.2" + revision = "63644898a8da0bc22138abf860edaf5277b6102e" + version = "v1.1.0" [[projects]] name = "github.com/spf13/cast" @@ -146,8 +146,8 @@ [[projects]] name = "github.com/spf13/cobra" packages = ["."] - revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" - version = "v0.0.1" + revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4" + version = "v0.0.2" [[projects]] branch = "master" @@ -177,7 +177,7 @@ branch = "master" name = "golang.org/x/crypto" packages = ["ssh/terminal"] - revision = "88942b9c40a4c9d203b82b3731787b672d6e809b" + revision = "b2aa35443fbc700ab74c586ae79b81c171851023" [[projects]] branch = "master" @@ -191,7 +191,7 @@ "lex/httplex", "trace" ] - revision = "6078986fec03a1dcc236c34816c71b0e05018fda" + revision = "61147c48b25b599e5b561d2e9c4f3e1ef489ca41" [[projects]] branch = "master" @@ -200,7 +200,7 @@ "unix", "windows" ] - revision = "91ee8cde435411ca3f1cd365e8f20131aed4d0a1" + revision = "3b87a42e500a6dc65dae1a55d0b641295971163e" [[projects]] name = "golang.org/x/text" @@ -230,7 +230,7 @@ "googleapis/api/annotations", "googleapis/rpc/status" ] - revision = "ab0870e398d5dd054b868c0db1481ab029b9a9f2" + revision = "ce84044298496ef4b54b4a0a0909ba593cc60e30" [[projects]] name = "google.golang.org/grpc" @@ -259,14 +259,14 @@ "tap", "transport" ] - revision = "8e4536a86ab602859c20df5ebfd0bd4228d08655" - version = "v1.10.0" + revision = "d89cded64628466c4ab532d1f0ba5c220459ebe8" + version = "v1.11.2" [[projects]] name = "gopkg.in/yaml.v2" packages = ["."] - revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5" - version = "v2.1.1" + revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" + version = "v2.2.1" [solve-meta] analyzer-name = "dep" diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt index 10364df..a4902ba 100644 --- a/MAINTAINERS.txt +++ b/MAINTAINERS.txt @@ -1 +1,2 @@ -Patrick Barker -- grillz \ No newline at end of file + | | +Patrick Barker | @grillz | patrickbarkerco@gmail.com \ No newline at end of file diff --git a/Makefile b/Makefile index d28cf48..30ae16f 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,28 @@ .PHONY: deps deps: + go get -u github.com/golang/dep/cmd/dep docker-compose run --rm deps .PHONY: build - docker-compose run -rm build +build: + docker-compose run --rm build + +.PHONY: plugins +plugins: + docker-compose run --rm plugins .PHONY: integration -integration: - docker-compose -f integration-compose.yaml up +integration: plugins + docker-compose run --rm integration .PHONY: integration-down integration-down: - docker-compose -f integration-compose.yaml down + docker-compose down + +.PHONY: clean +clean: + go run main.go plugin clean .PHONY: proto-gateway proto-gateway: diff --git a/README.md b/README.md index 9eca2be..cf4c98d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![logo](docs/img/Gopher_logo.png) + # Transpose Transpose is a cloud native composable proxy with a focus on kubernetes written in go. @@ -14,7 +16,13 @@ Transpose is a cloud native composable proxy with a focus on kubernetes written * Cloud native ## Getting Started -Example config: + +##### Install +`go get -u github.com/aunem/transpose` + +Transpose depends on [dep](github.com/golang/dep) + +##### Example ```yaml apiVersion: alpha.aunem.com/v1 Kind: Transpose @@ -36,13 +44,6 @@ spec: spec: authUrl: my.auth.com clientID: transposeClient - response: - - name: hydraAuth - package: github.com/aunem/transpose-plugins/middleware/hydra - spec: - authUrl: my.auth.com - clientID: transposeClient - auditUrl: my.audit.com roundtrip: name: myroundtrip @@ -57,6 +58,27 @@ spec: see [start.md](docs/start.md) for more details +## Plugins + +### Listener +Plugin | Description | Build | Contexts Supported +--- | --- | --- | --- +[http](github.com/aunem/transpose-plugins/listener/http)| simple http listener | build data | http + +### Middleware +Plugin | Description | Build | Contexts Supported +--- | --- | --- | --- +[hydra](github.com/aunem/transpose-plugins/middleware/hydra)| hydra auth middleware | build data | http + +### Roundtrip +Plugin | Description | Build | Contexts Supported +--- | --- | --- | --- +[supermux](github.com/aunem/transpose-plugins/roundtrip/supermux)| an enhanced router | build data | http, grpc + + + +To develop plugins see [developing_plugins.md](docs/developing_plugins.md) + ## Inspiration * Envoy [github.com/envoyproxy/envoy](github.com/envoyproxy/envoy) @@ -64,17 +86,13 @@ see [start.md](docs/start.md) for more details * Gentleman [github.com/h2non/gentleman](github.com/h2non/gentleman) * Istio [github.com/istio/istio](github.com/istio/istio) * OpenFaas [github.com/openfaas/faas](github.com/openfaas/faas) - -## Libs - -* Oxy [github.com/vulcand/oxy](github.com/vulcand/oxy) -* Gorrilla Mux [github.com/gorilla/mux](github.com/gorilla/mux) +* Fluentd [github.com/fluent/fluentd](github.com/fluent/fluentd) ## Roadmap - [ ] Middleware plugins -- [ ] Roundtrip plugins -- [ ] Listener plugins +- [x] Roundtrip plugins +- [x] Listener plugins - [ ] HTTP/2 - [ ] GRPC - [ ] Data plane @@ -84,4 +102,7 @@ see [start.md](docs/start.md) for more details Development happens out of the [Makefile](./Makefile). The targets are fairly simple but if you have any issues contact us. -## Contact +## Contact and Questions + +Chat with us on discord: +https://discord.gg/q9mnWtd diff --git a/cmd/plugin_init.go b/cmd/plugin_init.go index b15e181..b49d360 100644 --- a/cmd/plugin_init.go +++ b/cmd/plugin_init.go @@ -3,12 +3,14 @@ package cmd import ( "fmt" + res "github.com/aunem/transpose/pkg/resolve" + "github.com/aunem/transpose/pkg/template" "github.com/aunem/transpose/pkg/utils" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) -var ptyp string -var pname string +var ptyp, pname, ppackage string var initCmd = &cobra.Command{ Use: "init", @@ -19,13 +21,24 @@ var initCmd = &cobra.Command{ func init() { pluginCmd.AddCommand(initCmd) - initCmd.Flags().StringVarP(&ptyp, "type", "t", "", "type of plugin (listener, middleware, or roundtrip)") - initCmd.Flags().StringVarP(&pname, "name", "n", "", "name of plugin") + initCmd.Flags().StringVarP(&ptyp, "type", "t", "", "type of plugin (listener, middleware, or roundtrip) (required)") + initCmd.Flags().StringVarP(&pname, "name", "n", "", "name of plugin (required)") + initCmd.Flags().StringVarP(&ppackage, "package", "p", "", "golang package (required)") initCmd.MarkFlagRequired("type") initCmd.MarkFlagRequired("name") + initCmd.MarkFlagRequired("package") } // Init plugins func Init(cmd *cobra.Command, args []string) { - fmt.Println("not yet implemented") + + fmt.Println("creating templates...") + p := template.NewPlugin(pname, ppackage, ptyp) + err := p.Template() + if err != nil { + log.Fatal(err) + } + fmt.Println("resolving dependencies...") + res.Init() + fmt.Println("successfully templated plugin repo!") } diff --git a/cmd/plugin_resolve.go b/cmd/plugin_resolve.go index bac4256..fe6e86c 100644 --- a/cmd/plugin_resolve.go +++ b/cmd/plugin_resolve.go @@ -16,7 +16,7 @@ import ( var localBuild bool var build bool -var reolveCmd = &cobra.Command{ +var resolveCmd = &cobra.Command{ Use: "resolve", Short: "resolve plugins", Long: utils.GetArt(), @@ -24,9 +24,9 @@ var reolveCmd = &cobra.Command{ } func init() { - pluginCmd.AddCommand(reolveCmd) - initCmd.Flags().BoolVarP(&localBuild, "local", "l", false, "build plugin from local gopath") - initCmd.Flags().BoolVarP(&build, "build", "b", false, "resolve all plugins anew") + pluginCmd.AddCommand(resolveCmd) + resolveCmd.Flags().BoolVarP(&localBuild, "local", "l", false, "build plugin from local gopath") + resolveCmd.Flags().BoolVarP(&build, "build", "b", false, "resolve all plugins anew") } // Resolve plugins diff --git a/cmd/root.go b/cmd/root.go index cd4210e..525fe69 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -24,14 +24,14 @@ var rootCmd = &cobra.Command{ } func init() { - cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(&name, "config-name", "", "name of the kubernetes config to sync to") rootCmd.PersistentFlags().StringVar(&namespace, "config-namespace", "local", "namespace of the kubernetes config to sync to, use 'local' for a local config, leave blank for current ns") viper.BindPFlag("config-name", rootCmd.PersistentFlags().Lookup("config-name")) viper.BindPFlag("config-namespace", rootCmd.PersistentFlags().Lookup("config-namespace")) } -func initConfig() { +// InitConfig initializes the config +func InitConfig() { r := strings.NewReplacer("_", "-") viper.SetEnvKeyReplacer(r) viper.AutomaticEnv() diff --git a/cmd/server.go b/cmd/server.go index 0abd8e5..e756143 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -23,6 +23,7 @@ var serverCmd = &cobra.Command{ // Serve starts a new transpose server func Serve(cmd *cobra.Command, args []string) { + InitConfig() log.Info("finding or creating bins...") utils.MakeBins() log.Info("resolving middleware plugin...") diff --git a/config.yaml b/config.yaml index 4d609c3..f82ec23 100644 --- a/config.yaml +++ b/config.yaml @@ -8,7 +8,7 @@ spec: localBuild: false listener: - name: mylistener + name: httplistener package: github.com/aunem/transpose-plugins/listener/http@poc spec: port: 8080 @@ -16,21 +16,34 @@ spec: # middleware: # request: - # - name: myplugin - # package: github.com/aunem/transpose-plugins/middleware/auth@poc + # - name: hydraAuth + # package: github.com/aunem/transpose-plugins/middleware/hydra@poc # spec: - # authUrl: my.auth.com - # clientID: transposeClient + # endpoint: http://localhost:4444 + # clientID: admin + # clientSecret: password + # scopes: + # - hydra + # - core + # resourceMap: + # - http: + # method: POST + # path: "/" + # action: create + # resource: "rn:myserver:root" # response: - # - name: myplugin - # package: github.com/oscea/transpose-plugins/middleware/auth@poc + # - name: hydraAuth + # package: github.com/aunem/transpose-plugins/middleware/hydra@poc # spec: - # authUrl: my.auth.com - # clientID: transposeClient - # auditUrl: my.audit.com + # endpoint: http://localhost:4444 + # clientID: admin + # clientSecret: password + # scopes: + # - hydra + # - core roundtrip: - name: myroundtrip + name: muxRoundtrip package: github.com/aunem/transpose-plugins/roundtrip/supermux@poc spec: http: diff --git a/docker-compose.yaml b/docker-compose.yaml index 8ce5cc0..e72c29f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,6 @@ version: '3' services: + # resolve deps deps: build: context: ./ @@ -8,6 +9,8 @@ services: - "./:/go/src/github.com/aunem/transpose" command: dep ensure working_dir: /go/src/github.com/aunem/transpose + + # build binary build: build: context: ./ @@ -16,3 +19,71 @@ services: - "./:/go/src/github.com/aunem/transpose" command: go build . working_dir: /go/src/github.com/aunem/transpose + + # resolve plugins + plugins: + build: + context: ./ + dockerfile: Dockerfile-integration + volumes: + - "./:/go/src/github.com/aunem/transpose" + command: go run main.go plugin resolve --build + working_dir: /go/src/github.com/aunem/transpose + + # transpose server + transpose: + build: + context: ./ + dockerfile: Dockerfile-integration + command: go run main.go serve + links: + - server + working_dir: /go/src/github.com/aunem/transpose + volumes: + - "./:/go/src/github.com/aunem/transpose" + expose: + - "80" + + # test server + server: + build: + context: ./ + dockerfile: Dockerfile-integration + command: go run integration/server/main.go + working_dir: /go/src/github.com/aunem/transpose + volumes: + - "./:/go/src/github.com/aunem/transpose" + + # integration tests + integration: + build: + context: ./ + dockerfile: Dockerfile-integration + command: go run integration/test/main.go + depends_on: + - transpose + - server + links: + - transpose + working_dir: /go/src/github.com/aunem/transpose + volumes: + - "./:/go/src/github.com/aunem/transpose" + +# # hydra auth middleware +# hydra: +# image: oryd/hydra +# volumes: +# - hydravolume:/root +# ports: +# - "4444:4444" +# - "4445:4445" +# command: +# host --dangerous-auto-logon --dangerous-force-http --disable-telemetry +# environment: +# - LOG_LEVEL=debug +# - ISSUER=http://localhost:4444 +# - CONSENT_URL=http://localhost:3000/consent +# - DATABASE_URL=memory +# - FORCE_ROOT_CLIENT_CREDENTIALS=admin:password +# - SYSTEM_SECRET=youReallyNeedToChangeThis +# restart: unless-stopped \ No newline at end of file diff --git a/docs/developing_plugins.md b/docs/developing_plugins.md new file mode 100644 index 0000000..e31a3ae --- /dev/null +++ b/docs/developing_plugins.md @@ -0,0 +1,23 @@ +# Developing plugins + +The fastest way to start developing a plugin is to use the CLI tool. + +install: +`go install github.com/aunem/transpose` + +check out the help command: +`transpose plugin init --help` + +example: + +```bash +PLUGIN_NAME="mynewplugin" +PLUGIN_PACKAGE="github.com/myuser/myrepo" +PLUGIN_TYPE="middleware" # middleware, listener, or roundtrip allowed + +transpose plugin init -n ${PLUGIN_NAME} -p ${PLUGIN_PACKAGE} -t ${PLUGIN_TYPE} +``` +this will scaffold you out a good start for your plugin, now lets walk though the files present... + + +...in progress \ No newline at end of file diff --git a/docs/img/Gopher_logo.png b/docs/img/Gopher_logo.png new file mode 100755 index 0000000..3a2a1cb Binary files /dev/null and b/docs/img/Gopher_logo.png differ diff --git a/docs/start.md b/docs/start.md index 8b3a794..34be091 100644 --- a/docs/start.md +++ b/docs/start.md @@ -1 +1,3 @@ -# Getting Started \ No newline at end of file +# Getting Started + +...in progress \ No newline at end of file diff --git a/examples/basic_http.yaml b/examples/basic_http.yaml new file mode 100644 index 0000000..48166fe --- /dev/null +++ b/examples/basic_http.yaml @@ -0,0 +1,53 @@ +apiVersion: alpha.aunem.com/v1 +kind: Transpose +metadata: + name: myProxy + namespace: default +spec: + debug: true + localBuild: false + + listener: + name: httplistener + package: github.com/aunem/transpose-plugins/listener/http@poc + spec: + port: 8080 + ssl: false + + middleware: + request: + - name: hydraAuth + package: github.com/aunem/transpose-plugins/middleware/hydra@poc + spec: + endpoint: http://localhost:4444 + clientID: admin + clientSecret: password + scopes: + - hydra + - core + resourceMap: + - http: + method: POST + path: "/" + action: create + resource: "rn:myserver:root" + response: + - name: hydraAuth + package: github.com/aunem/transpose-plugins/middleware/hydra@poc + spec: + endpoint: http://localhost:4444 + clientID: admin + clientSecret: password + scopes: + - hydra + - core + + roundtrip: + name: muxRoundtrip + package: github.com/aunem/transpose-plugins/roundtrip/supermux@poc + spec: + http: + - path: "/" + backend: + serviceName: server + servicePort: 80 diff --git a/integration-compose.yaml b/integration-compose.yaml deleted file mode 100644 index 0a0d12a..0000000 --- a/integration-compose.yaml +++ /dev/null @@ -1,32 +0,0 @@ -version: '3' -services: - transpose: - build: - context: ./ - dockerfile: Dockerfile-integration - command: go run main.go serve - links: - - server - working_dir: /go/src/github.com/aunem/transpose - volumes: - - "./:/go/src/github.com/aunem/transpose" - expose: - - "80" - server: - build: - context: ./ - dockerfile: Dockerfile-integration - command: go run integration/server/main.go - working_dir: /go/src/github.com/aunem/transpose - volumes: - - "./:/go/src/github.com/aunem/transpose" - client: - build: - context: ./ - dockerfile: Dockerfile-integration - command: go run integration/client/main.go - links: - - transpose - working_dir: /go/src/github.com/aunem/transpose - volumes: - - "./:/go/src/github.com/aunem/transpose" diff --git a/integration/test/main.go b/integration/test/main.go new file mode 100644 index 0000000..6043ab7 --- /dev/null +++ b/integration/test/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "bytes" + "fmt" + "log" + "net/http" + "os" + "time" +) + +func main() { + c := http.DefaultClient + fmt.Println("connecting to service...") + err := retry(30, 3*time.Second, func() error { + err0 := makeRequest(c) + return err0 + }) + if err != nil { + log.Fatal("could not connect to service: ", err) + } + fmt.Println("connected to service!") + fmt.Println("testing...") + for i := 0; i < 10; i++ { + err := makeRequest(c) + if err != nil { + log.Fatal(err) + } + time.Sleep(1 * time.Second) + } + os.Exit(0) +} + +func makeRequest(c *http.Client) error { + fmt.Println("making request...") + var jsonStr = []byte(`{"title":"Buy cheese and bread for breakfast."}`) + r, err := http.NewRequest(http.MethodPost, "http://transpose:8080", bytes.NewBuffer(jsonStr)) + if err != nil { + return err + } + log.Printf("request: %+v", r) + resp, err := c.Do(r) + if err != nil { + return err + } + fmt.Printf("resp: %+v", resp) + return nil +} + +func retry(attempts int, sleep time.Duration, callback func() error) (err error) { + for i := 0; ; i++ { + err = callback() + if err == nil { + return + } + + if i >= (attempts - 1) { + break + } + + time.Sleep(sleep) + + log.Println("retrying after error:", err) + } + return fmt.Errorf("after %d attempts, last error: %s", attempts, err) +} diff --git a/pkg/context/context.go b/pkg/context/context.go index f5a0d9c..fd6a64f 100644 --- a/pkg/context/context.go +++ b/pkg/context/context.go @@ -3,12 +3,14 @@ package context // Request is a generic incomming request interface type Request interface { GetID() string + GetMeta() map[string]string GetRequest() interface{} } // Response is a generic returned response interface type Response interface { GetID() string + GetMeta() map[string]string GetRequest() interface{} GetResponse() interface{} } diff --git a/pkg/context/http.go b/pkg/context/http.go index 5ac68ec..66c9ca6 100644 --- a/pkg/context/http.go +++ b/pkg/context/http.go @@ -10,14 +10,18 @@ import ( // HTTPRequest is passed to a plugins request method type HTTPRequest struct { ID string + Meta map[string]string Request *http.Request + RW http.ResponseWriter } // HTTPResponse is passed to a plugins response method type HTTPResponse struct { ID string + Meta map[string]string Request *http.Request Response *http.Response + RW http.ResponseWriter } // NewHTTPRequest returns a new request context @@ -34,6 +38,11 @@ func (req *HTTPRequest) GetID() string { return req.ID } +// GetMeta returns the request metadata +func (req *HTTPRequest) GetMeta() map[string]string { + return req.Meta +} + // GetRequest returns the request func (req *HTTPRequest) GetRequest() interface{} { return req.Request @@ -53,6 +62,11 @@ func (resp *HTTPResponse) GetID() string { return resp.ID } +// GetMeta returns the response metadata +func (resp *HTTPResponse) GetMeta() map[string]string { + return resp.Meta +} + // GetRequest returns the request func (resp *HTTPResponse) GetRequest() interface{} { return resp.Request diff --git a/pkg/listener/manager.go b/pkg/listener/manager.go index 65f9eef..49d69ad 100644 --- a/pkg/listener/manager.go +++ b/pkg/listener/manager.go @@ -47,8 +47,8 @@ func NewManager(spec config.TransposeSpec, mw *middleware.Manager, rt *roundtrip if err != nil { return nil, err } - log.Debug("loading spec...") - err = rtp.LoadSpec(spec.Listener.Spec) + log.Debug("running init function...") + err = rtp.Init(spec.Listener.Spec) if err != nil { return nil, err } diff --git a/pkg/listener/plugin.go b/pkg/listener/plugin.go index b4788fc..d348416 100644 --- a/pkg/listener/plugin.go +++ b/pkg/listener/plugin.go @@ -9,8 +9,8 @@ import ( type Plugin interface { // Listen Listen(mw *middleware.Manager, rt *roundtrip.Manager) error - // LoadSpec loads the spec on initialization and config updates - LoadSpec(spec interface{}) error + // Init is called on initialization and config updates + Init(spec interface{}) error // Stats can return arbitrary json stats Stats() ([]byte, error) } diff --git a/pkg/middleware/manager.go b/pkg/middleware/manager.go index dab2af2..6a1aa61 100644 --- a/pkg/middleware/manager.go +++ b/pkg/middleware/manager.go @@ -44,11 +44,7 @@ func NewManager(spec config.TransposeSpec) (*Manager, error) { if err != nil { return nil, err } - err = mw.Plugin.LoadSpec(plugin.Spec) - if err != nil { - return nil, err - } - err = mw.Plugin.Init() + err = mw.Plugin.Init(plugin.Spec) if err != nil { return nil, err } @@ -59,11 +55,7 @@ func NewManager(spec config.TransposeSpec) (*Manager, error) { if err != nil { return nil, err } - err = mw.Plugin.LoadSpec(plugin.Spec) - if err != nil { - return nil, err - } - err = mw.Plugin.Init() + err = mw.Plugin.Init(plugin.Spec) if err != nil { return nil, err } diff --git a/pkg/middleware/plugin.go b/pkg/middleware/plugin.go index b3112af..09edfb1 100644 --- a/pkg/middleware/plugin.go +++ b/pkg/middleware/plugin.go @@ -8,10 +8,8 @@ type Plugin interface { ProcessRequest(req context.Request) (context.Request, error) // ProcessResponse will be called on incoming responses ProcessResponse(resp context.Response) (context.Response, error) - // Init is ran once on plugin initialization - Init() error - // LoadSpec loads the spec on initialization and config updates - LoadSpec(spec interface{}) error + // Init is called on initialization and config updates + Init(spec interface{}) error // Stats can return arbitrary json stats Stats() (error, []byte) } diff --git a/pkg/resolve/resolve.go b/pkg/resolve/resolve.go index 275b279..795fc5d 100644 --- a/pkg/resolve/resolve.go +++ b/pkg/resolve/resolve.go @@ -27,6 +27,13 @@ const ( RoundtripType Type = "roundtrip" ) +// Init adds a package using dep +func Init() { + cmdName := "dep" + cmdArgs := []string{"init"} + ExecCommand(cmdName, cmdArgs) +} + // AddPkg adds a package using dep func AddPkg(pkg string) { cmdName := "dep" diff --git a/pkg/roundtrip/manager.go b/pkg/roundtrip/manager.go index bd83792..a87ab06 100644 --- a/pkg/roundtrip/manager.go +++ b/pkg/roundtrip/manager.go @@ -42,13 +42,8 @@ func NewManager(spec config.TransposeSpec) (*Manager, error) { if err != nil { return nil, err } - log.Debug("loading spec...") - err = rtp.LoadSpec(spec.Roundtrip.Spec) - if err != nil { - return nil, err - } - log.Debug("running init...") - err = rtp.Init() + log.Debug("running init function...") + err = rtp.Init(spec.Roundtrip.Spec) if err != nil { return nil, err } diff --git a/pkg/roundtrip/plugin.go b/pkg/roundtrip/plugin.go index 9992546..503ff87 100644 --- a/pkg/roundtrip/plugin.go +++ b/pkg/roundtrip/plugin.go @@ -6,10 +6,8 @@ import "github.com/aunem/transpose/pkg/context" type Plugin interface { // Roundtrip handles interactions with the downstream service/s Roundtrip(req context.Request) (context.Response, error) - // Init is ran once on plugin initialization - Init() error - // LoadSpec loads the spec on initialization and config updates - LoadSpec(spec interface{}) error + // Init is called on initialization and config updates + Init(spec interface{}) error // Stats can return arbitrary json stats Stats() ([]byte, error) } diff --git a/pkg/template/listener/README_tpl.go b/pkg/template/listener/README_tpl.go new file mode 100644 index 0000000..c32fba9 --- /dev/null +++ b/pkg/template/listener/README_tpl.go @@ -0,0 +1,45 @@ +package listener + +var readmeTpl = ` + +# {{ .Name }} + +// Your overview here + +## Spec +` + "```yaml " + ` +apiVersion: alpha.aunem.io/v1 +Kind: Transpose +Metadata: + name: myProxy + namespace: default +spec: + listener: + name: {{ .Name }} + package: {{ .Pkg }} + spec: + port: 80 + ssl: false + + roundtrip: + name: myroundtrip + package: github.com/aunem/transpose-plugins/roundtrip/supermux + spec: + http: + - path: "/" + backend: + serviceName: myservice + servicePort: 80 +` + "```" + ` + +## Contexts Supported +* Http + +## Compatibility +// your compatibility data goes here + +## Dependencies +// your dependencies go here + +## Test +` + "`go test ./...`" diff --git a/pkg/template/listener/example_config_tpl.go b/pkg/template/listener/example_config_tpl.go new file mode 100644 index 0000000..f655158 --- /dev/null +++ b/pkg/template/listener/example_config_tpl.go @@ -0,0 +1,25 @@ +package listener + +var exampleTpl = `apiVersion: alpha.aunem.io/v1 +Kind: Transpose +Metadata: + name: myProxy + namespace: default +spec: + listener: + name: {{ .Name }} + package: {{ .Repo }} + spec: + port: 80 + ssl: false + + roundtrip: + name: myroundtrip + package: github.com/aunem/transpose-plugins/roundtrip/supermux + spec: + http: + - path: "/" + backend: + serviceName: myservice + servicePort: 80 +` diff --git a/pkg/template/listener/main_test_tpl.go b/pkg/template/listener/main_test_tpl.go new file mode 100644 index 0000000..2f91717 --- /dev/null +++ b/pkg/template/listener/main_test_tpl.go @@ -0,0 +1,12 @@ +package listener + +var mainTestTpl = ` +package main_test + +func TestMain(m *testing.M) { + m.Run() +} + +func TestListen(t *testing.T) { +} +` diff --git a/pkg/template/listener/main_tpl.go b/pkg/template/listener/main_tpl.go new file mode 100644 index 0000000..2af4041 --- /dev/null +++ b/pkg/template/listener/main_tpl.go @@ -0,0 +1,45 @@ +package listener + +var mainTpl = `package main + +import ( + "fmt" + "path" + + "github.com/aunem/transpose/pkg/context" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +type {{ .Name }}Plugin struct {} + +// ListenerPlugin exports the plugin struct +var ListenerPlugin {{ .Name }}Plugin + +// Spec exports the spec data +var Spec {{ .Name }}Spec + +func main() {} + +// Listen implements the listener plugin inerface +func (h *{{ .Name }}Listener) Listen(mw *middleware.Manager, rt *roundtrip.Manager) error { + // Implement listener code here +} + +func (p *{{ .Name }}Plugin) Init(spec interface{}) error { + b, err := yaml.Marshal(spec) + if err != nil { + return err + } + err = yaml.Unmarshal(b, &Spec) + if err != nil { + return err + } + log.Debugf("loaded spec: %+v", Spec) + return nil +} + +func (p *{{ .Name }}Plugin) Stats() ([]byte, error) { + return nil, nil +} +` diff --git a/pkg/template/listener/manifest.go b/pkg/template/listener/manifest.go new file mode 100644 index 0000000..a495096 --- /dev/null +++ b/pkg/template/listener/manifest.go @@ -0,0 +1,10 @@ +package listener + +// Manifest is a map of file name to template string +var Manifest = map[string]string{ + "main.go": mainTpl, + "main_test.go": mainTestTpl, + "example-config.yaml": exampleTpl, + "README.md": readmeTpl, + "spec.go": specTpl, +} diff --git a/pkg/template/listener/spec_tpl.go b/pkg/template/listener/spec_tpl.go new file mode 100644 index 0000000..3e41b97 --- /dev/null +++ b/pkg/template/listener/spec_tpl.go @@ -0,0 +1,6 @@ +package listener + +var specTpl = `package main + +type {{ .Name }}Spec struct {} +` diff --git a/pkg/template/middleware/README_tpl.go b/pkg/template/middleware/README_tpl.go new file mode 100644 index 0000000..09040e5 --- /dev/null +++ b/pkg/template/middleware/README_tpl.go @@ -0,0 +1,51 @@ +package middleware + +var readmeTpl = ` + +# {{ .Name }} + +// Your overview here + +## Spec +` + "```yaml " + ` +apiVersion: alpha.aunem.io/v1 +Kind: Transpose +Metadata: + name: myProxy + namespace: default +spec: + listener: + name: mylistener + package: github.com/aunem/transpose-plugins/listener/http + spec: + port: 80 + ssl: false + + middleware: + name: {{ .Name }} + package: {{ .Pkg }} + spec: + my: spec + + roundtrip: + name: myroundtrip + package: github.com/aunem/transpose-plugins/roundtrip/supermux + spec: + http: + - path: "/" + backend: + serviceName: myservice + servicePort: 80 +` + "```" + ` + +## Contexts Supported +* Http + +## Compatibility +// your compatibility data goes here + +## Dependencies +// your dependencies go here + +## Test +` + "`go test ./...`" diff --git a/pkg/template/middleware/example_config_tpl.go b/pkg/template/middleware/example_config_tpl.go new file mode 100644 index 0000000..b155c25 --- /dev/null +++ b/pkg/template/middleware/example_config_tpl.go @@ -0,0 +1,32 @@ +package middleware + +var exampleTpl = `apiVersion: alpha.aunem.io/v1 +Kind: Transpose +Metadata: + name: myProxy + namespace: default +spec: + listener: + name: mylistener + package: github.com/aunem/transpose-plugins/listener/http + spec: + port: 80 + ssl: false + + middleware: + name: {{ .Name }} + package: {{ .Pkg }} + # Add your spec data here + spec: + my: spec + + roundtrip: + name: myroundtrip + package: github.com/aunem/transpose-plugins/roundtrip/supermux + spec: + http: + - path: "/" + backend: + serviceName: myservice + servicePort: 80 +` diff --git a/pkg/template/middleware/main_test_tpl.go b/pkg/template/middleware/main_test_tpl.go new file mode 100644 index 0000000..291760c --- /dev/null +++ b/pkg/template/middleware/main_test_tpl.go @@ -0,0 +1,15 @@ +package middleware + +var mainTestTpl = ` +package main_test + +func TestMain(m *testing.M) { + m.Run() +} + +func TestProcessRequest(t *testing.T) { +} + +func TestProcessResponse(t *testing.T) { +} +` diff --git a/pkg/template/middleware/main_tpl.go b/pkg/template/middleware/main_tpl.go new file mode 100644 index 0000000..0a6e8fb --- /dev/null +++ b/pkg/template/middleware/main_tpl.go @@ -0,0 +1,48 @@ +package middleware + +var mainTpl = `package main + +import ( + "fmt" + "path" + + "github.com/aunem/transpose/pkg/context" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +type {{ .Name }}Plugin struct {} + +// MiddlewarePlugin exports the plugin struct +var MiddlewarePlugin {{ .Name }}Plugin + +// Spec exports the spec data +var Spec {{ .Name }}Spec + +func main() {} + +func (p *{{ .Name }}Plugin) ProcessRequest(req context.Request) (context.Request, error) { + return nil, nil +} + +func (p *{{ .Name }}Plugin) ProcessResponse(resp context.Response) (context.Response, error) { + return nil, nil +} + +func (p *{{ .Name }}Plugin) Init(spec interface{}) error { + b, err := yaml.Marshal(spec) + if err != nil { + return err + } + err = yaml.Unmarshal(b, &Spec) + if err != nil { + return err + } + log.Debugf("loaded spec: %+v", Spec) + return nil +} + +func (p *{{ .Name }}Plugin) Stats() ([]byte, error) { + return nil, nil +} +` diff --git a/pkg/template/middleware/manifest.go b/pkg/template/middleware/manifest.go new file mode 100644 index 0000000..dc9f8b6 --- /dev/null +++ b/pkg/template/middleware/manifest.go @@ -0,0 +1,10 @@ +package middleware + +// Manifest is a map of file name to template string +var Manifest = map[string]string{ + "main.go": mainTpl, + "main_test.go": mainTestTpl, + "example-config.yaml": exampleTpl, + "README.md": readmeTpl, + "spec.go": specTpl, +} diff --git a/pkg/template/middleware/spec_tpl.go b/pkg/template/middleware/spec_tpl.go new file mode 100644 index 0000000..67aeaa4 --- /dev/null +++ b/pkg/template/middleware/spec_tpl.go @@ -0,0 +1,6 @@ +package middleware + +var specTpl = `package main + +type {{ .Name }}Spec struct {} +` diff --git a/pkg/template/roundtrip/README_tpl.go b/pkg/template/roundtrip/README_tpl.go new file mode 100644 index 0000000..d4141cc --- /dev/null +++ b/pkg/template/roundtrip/README_tpl.go @@ -0,0 +1,45 @@ +package roundtrip + +var readmeTpl = ` + +# {{ .Name }} + +// Your overview here + +## Spec +` + "```yaml " + ` +apiVersion: alpha.aunem.io/v1 +Kind: Transpose +Metadata: + name: myProxy + namespace: default +spec: + listener: + name: mylistener + package: github.com/aunem/transpose-plugins/listener/http + spec: + port: 80 + ssl: false + + roundtrip: + name: {{ .Name }} + package: {{ .Pkg }} + spec: + http: + - path: "/" + backend: + serviceName: myservice + servicePort: 80 +` + "```" + ` + +## Contexts Supported +* Http + +## Compatibility +// your compatibility data goes here + +## Dependencies +// your dependencies go here + +## Test +` + "`go test ./...`" diff --git a/pkg/template/roundtrip/example_config_tpl.go b/pkg/template/roundtrip/example_config_tpl.go new file mode 100644 index 0000000..6e07a9c --- /dev/null +++ b/pkg/template/roundtrip/example_config_tpl.go @@ -0,0 +1,25 @@ +package roundtrip + +var exampleTpl = `apiVersion: alpha.aunem.io/v1 +Kind: Transpose +Metadata: + name: myProxy + namespace: default +spec: + listener: + name: mylistener + package: github.com/aunem/transpose-plugins/listener/http + spec: + port: 80 + ssl: false + + roundtrip: + name: {{ .Name }} + package: {{ .Pkg }} + spec: + http: + - path: "/" + backend: + serviceName: myservice + servicePort: 80 +` diff --git a/pkg/template/roundtrip/main_test_tpl.go b/pkg/template/roundtrip/main_test_tpl.go new file mode 100644 index 0000000..d68ed98 --- /dev/null +++ b/pkg/template/roundtrip/main_test_tpl.go @@ -0,0 +1,12 @@ +package roundtrip + +var mainTestTpl = ` +package main_test + +func TestMain(m *testing.M) { + m.Run() +} + +func TestRoundtrip(t *testing.T) { +} +` diff --git a/pkg/template/roundtrip/main_tpl.go b/pkg/template/roundtrip/main_tpl.go new file mode 100644 index 0000000..bef51d0 --- /dev/null +++ b/pkg/template/roundtrip/main_tpl.go @@ -0,0 +1,44 @@ +package roundtrip + +var mainTpl = `package main + +import ( + "fmt" + "path" + + "github.com/aunem/transpose/pkg/context" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +type {{ .Name }}Plugin struct {} + +// RoundtripPlugin exports the plugin struct +var RoundtripPlugin {{ .Name }}Plugin + +// Spec exports the spec data +var Spec {{ .Name }}Spec + +func main() {} + +func (s *{{ .Name }}Plugin) Roundtrip(req context.Request) (context.Response, error) { + // your roundtrip code goes here +} + +func (p *{{ .Name }}Plugin) Init(spec interface{}) error { + b, err := yaml.Marshal(spec) + if err != nil { + return err + } + err = yaml.Unmarshal(b, &Spec) + if err != nil { + return err + } + log.Debugf("loaded spec: %+v", Spec) + return nil +} + +func (p *{{ .Name }}Plugin) Stats() ([]byte, error) { + return nil, nil +} +` diff --git a/pkg/template/roundtrip/manifest.go b/pkg/template/roundtrip/manifest.go new file mode 100644 index 0000000..2ed7649 --- /dev/null +++ b/pkg/template/roundtrip/manifest.go @@ -0,0 +1,10 @@ +package roundtrip + +// Manifest is a map of file name to template string +var Manifest = map[string]string{ + "main.go": mainTpl, + "main_test.go": mainTestTpl, + "example-config.yaml": exampleTpl, + "README.md": readmeTpl, + "spec.go": specTpl, +} diff --git a/pkg/template/roundtrip/spec_tpl.go b/pkg/template/roundtrip/spec_tpl.go new file mode 100644 index 0000000..bfd9aa8 --- /dev/null +++ b/pkg/template/roundtrip/spec_tpl.go @@ -0,0 +1,6 @@ +package roundtrip + +var specTpl = `package main + +type {{ .Name }}Spec struct {} +` diff --git a/pkg/template/template.go b/pkg/template/template.go index 38cdfe4..7de54fb 100644 --- a/pkg/template/template.go +++ b/pkg/template/template.go @@ -1 +1,91 @@ package template + +import ( + "fmt" + "os" + tpl "text/template" + + list "github.com/aunem/transpose/pkg/template/listener" + mw "github.com/aunem/transpose/pkg/template/middleware" + rt "github.com/aunem/transpose/pkg/template/roundtrip" +) + +//TODO: test this + +// TemplateDir represents a templated directory +type TemplateDir string + +var middlewaredir TemplateDir = "pkg/template/middleware" +var roundtripdir TemplateDir = "pkg/template/roundtrip" +var listenerdir TemplateDir = "pkg/template/listener" + +// Plugin represents the template data for a plugin +type Plugin struct { + Name, Pkg, Typ string +} + +// NewPlugin creates a new plugin +func NewPlugin(name, pkg, typ string) *Plugin { + return &Plugin{ + Name: name, + Pkg: pkg, + Typ: typ, + } +} + +// Template will take the plugin type and template the appropriate files +func (p *Plugin) Template() error { + switch p.Typ { + case "middleware": + for k, v := range mw.Manifest { + t, err := tpl.New("tpl").Parse(v) + if err != nil { + return err + } + f, err := os.Create(k) + if err != nil { + return err + } + defer f.Close() + t.Execute(f, p) + if err != nil { + return err + } + } + case "listener": + for k, v := range list.Manifest { + t, err := tpl.New("tpl").Parse(v) + if err != nil { + return err + } + f, err := os.Create(k) + if err != nil { + return err + } + defer f.Close() + t.Execute(f, p) + if err != nil { + return err + } + } + case "roundtrip": + for k, v := range rt.Manifest { + t, err := tpl.New("tpl").Parse(v) + if err != nil { + return err + } + f, err := os.Create(k) + if err != nil { + return err + } + defer f.Close() + t.Execute(f, p) + if err != nil { + return err + } + } + default: + return fmt.Errorf("plugin typ uknown: %+v", p.Typ) + } + return nil +} diff --git a/pkg/utils/retry.go b/pkg/utils/retry.go new file mode 100644 index 0000000..83dffb5 --- /dev/null +++ b/pkg/utils/retry.go @@ -0,0 +1,26 @@ +package utils + +import ( + "fmt" + "log" + "time" +) + +// Retry is a basic retry function +func Retry(attempts int, sleep time.Duration, callback func() error) (err error) { + for i := 0; ; i++ { + err = callback() + if err == nil { + return + } + + if i >= (attempts - 1) { + break + } + + time.Sleep(sleep) + + log.Println("retrying after error:", err) + } + return fmt.Errorf("after %d attempts, last error: %s", attempts, err) +}