diff --git a/go.mod b/go.mod index bbab21ef..bbfc8216 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/project-flogo/contrib +module github.com/AkshayGadikar/contrib require ( github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 diff --git a/trigger/ping/examples/ping/README.md b/trigger/ping/examples/ping/README.md new file mode 100644 index 00000000..cffab312 --- /dev/null +++ b/trigger/ping/examples/ping/README.md @@ -0,0 +1,49 @@ +# Flogo Ping Example + +## Install + +To install run the following commands: +``` +flogo create -f flogo.json +cd Ping +flogo build +``` + +## Testing + +Run: +``` +bin/Ping +``` + +Then open another terminal and run: +``` +curl http://localhost:9096/ping/details +``` + +You should then see something like: +``` +{ + "Version":"1.1", + "Appversion":"", + "Appdescription":"" +}Details:{ + "NumGoroutine":2, + "Alloc":762472, + "TotalAlloc":762472, + "Sys":69926912, + "Mallocs":1078, + "Frees":101, + "LiveObjects":977, + "NumGC":0 +} +``` +##### +``` +curl http://localhost:9096/ping +``` + +You should then see something like: +``` +{"response":"Ping successful"} +``` diff --git a/trigger/ping/examples/ping/flogo.json b/trigger/ping/examples/ping/flogo.json new file mode 100644 index 00000000..d3d4c77f --- /dev/null +++ b/trigger/ping/examples/ping/flogo.json @@ -0,0 +1,25 @@ +{ + "name": "Ping", + "type": "flogo:app", + "version": "1.0.0", + "title": "Ping Trigger", + "description": "This is a simple ping example.", + "triggers": [ + { + "name": "ping", + "id": "flogo-ping", + "ref": "github.com/project-flogo/contrib/trigger/ping", + "description": "Ping Example", + "settings": { + "port": "9096", + "version": "1.1", + "appversion":"", + "appdescription":"", + "password":"Test12345" + }, + "handlers": [] + } + ], + "actions": [], + "resources": [] +} \ No newline at end of file diff --git a/trigger/ping/metadata.go b/trigger/ping/metadata.go new file mode 100644 index 00000000..e1f23462 --- /dev/null +++ b/trigger/ping/metadata.go @@ -0,0 +1,59 @@ +package ping + +import( + "runtime" + "encoding/json" + "strings" +) + +const password = "Test12345" + +type Settings struct { + Port int `md:"port,required"` + Version string `md:"version"` + AppVersion string `md:"appversion"` + AppDescription string `md:"appdescription"` + Password string `md:"password"` +} + +type MemoryStats struct{ + NumGoroutine int + Alloc, + TotalAlloc, + Sys, + Mallocs, + Frees, + LiveObjects uint64 + NumGC uint32 +} + +func PrintMemUsage() string{ + var rtm runtime.MemStats + runtime.ReadMemStats(&rtm) + var t MemoryStats + + t.NumGoroutine = runtime.NumGoroutine() + + // Misc memory stats + t.Alloc = rtm.Alloc + t.TotalAlloc = rtm.TotalAlloc + t.Sys = rtm.Sys + t.Mallocs = rtm.Mallocs + t.Frees = rtm.Frees + + // Live objects = Mallocs - Frees + t.LiveObjects = t.Mallocs - t.Frees + + //GC stats + t.NumGC = rtm.NumGC + + result, _ := json.Marshal(t) + return string(result) +} + +func Valid(token string) bool{ + if strings.Compare(token, password) == 0{ + return true + } + return false +} diff --git a/trigger/ping/trigger.go b/trigger/ping/trigger.go new file mode 100644 index 00000000..98227317 --- /dev/null +++ b/trigger/ping/trigger.go @@ -0,0 +1,131 @@ +package ping + +import ( + "fmt" + "strconv" + "encoding/json" + "io" + "net/http" + "github.com/project-flogo/core/trigger" +) + +// DefaultPort is the default port for Ping service +const DefaultPort = "9096" + +var triggerMd = trigger.NewMetadata(&Settings{}) +var statsResult string + +func init() { + trigger.Register(&Trigger{}, &Factory{}) +} + +type Factory struct { +} + +// Metadata implements trigger.Factory.Metadata +func (*Factory) Metadata() *trigger.Metadata { + return triggerMd +} + +// Trigger is the ping trigger +type Trigger struct { + metadata *trigger.Metadata + config *trigger.Config + response string + Server *http.Server +} + +// New implements trigger.Factory.New +func (f *Factory) New(config *trigger.Config) (trigger.Trigger, error) { + type PingResponse struct { + Version string + Appversion string + Appdescription string + } + response := PingResponse{ + Version: config.Settings["version"].(string), + Appversion: config.Settings["appversion"].(string), + Appdescription: config.Settings["appdescription"].(string), + } + + data, err := json.Marshal(response) + if err != nil { + fmt.Errorf("Ping service data formation error",err) + } + + port := strconv.Itoa(config.Settings["port"].(int)) + if len(port) == 0 { + port = DefaultPort + } + + mux := http.NewServeMux() + server := &http.Server{ + Addr: ":" + port, + Handler: mux, + } + trigger := &Trigger{ + metadata: f.Metadata(), + config: config, + response: string(data), + Server: server, + } + mux.HandleFunc("/ping", trigger.PingResponseHandlerShort) + mux.HandleFunc("/ping/details", trigger.PingResponseHandlerDetail) + statsResult = PrintMemUsage() + return trigger, nil +} + +// Init implements trigger.Init +func (t *Trigger) Initialize(ctx trigger.InitContext) error { + return nil +} + +func (t *Trigger) PingResponseHandlerShort(w http.ResponseWriter, req *http.Request) { + token := t.config.Settings["password"].(string) + if(Valid(token)) { + io.WriteString(w, "{\"response\":\"Ping successful\"}\n") + }else{ + io.WriteString(w, "Error : Invalid User !! \n") + } +} + +//PingResponseHandlerDetail handles simple response +func (t *Trigger) PingResponseHandlerDetail(w http.ResponseWriter, req *http.Request) { + token := t.config.Settings["password"].(string) + if(Valid(token)) { + io.WriteString(w, t.response + "\n") + io.WriteString(w, "Details :\n") + io.WriteString(w, statsResult + "\n") + }else{ + io.WriteString(w, "Error : Invalid User !! \n") + } + + //Another way to get trace : more details + /* + tr := trace.New("TraceTest", req.URL.Path) + defer tr.Finish() + fmt.Println(reflect.TypeOf(tr).String()) + fmt.Println("Trace:") + fmt.Printf("%+v\n", tr) + fmt.Println(tr)*/ +} + +// Start implements util.Managed.Start +func (t *Trigger) Start() error { + go func() { + if err := t.Server.ListenAndServe(); err != http.ErrServerClosed { + fmt.Errorf("Ping service err:", err) + } + }() + return nil +} + +// Stop implements util.Managed.Stop +func (t *Trigger) Stop() error { + if err := t.Server.Shutdown(nil); err != nil { + fmt.Errorf("[mashling-ping-service] Ping service error when stopping:", err) + return err + } + return nil +} + diff --git a/trigger/swagger/activity.go b/trigger/swagger/activity.go new file mode 100644 index 00000000..f80356e9 --- /dev/null +++ b/trigger/swagger/activity.go @@ -0,0 +1,148 @@ +package swagger + +import( + "fmt" + "io" + "net/http" + "strconv" + "strings" + "os" + "regexp" + "github.com/project-flogo/core/data/metadata" + "github.com/project-flogo/core/support/log" + "github.com/project-flogo/core/trigger" +) + +var triggerMd = trigger.NewMetadata(&Settings{}, &HandlerSettings{}) +const DefaultPort = "9096" + +func init() { + trigger.Register(&Trigger{}, &Factory{}) +} + +type Factory struct { +} + +// Metadata implements trigger.Factory.Metadata +func (*Factory) Metadata() *trigger.Metadata { + return triggerMd +} + +// Trigger is the swagger trigger +type Trigger struct { + metadata *trigger.Metadata + settings *Settings + config *trigger.Config + Server *http.Server + logger log.Logger + response string +} + +// New implements trigger.Factory.New +func (f *Factory) New(config *trigger.Config) (trigger.Trigger, error) { + s := &Settings{} + err := metadata.MapToStruct(config.Settings, s, true) + if err != nil { + return nil, err + } + port := strconv.Itoa(config.Settings["port"].(int)) + if len(port) == 0 { + port = DefaultPort + } + + mux := http.NewServeMux() + server := &http.Server{ + Addr: ":" + port, + Handler: mux, + } + trigger := &Trigger{ + metadata: f.Metadata(), + config: config, + response: "", + Server: server, + } + mux.HandleFunc("/swagger/", trigger.SwaggerHandler) + mux.HandleFunc("/swagger", trigger.DefaultSwaggerHandler) + return trigger, nil +} + +func (t *Trigger) DefaultSwaggerHandler(w http.ResponseWriter, req *http.Request) { + hostName, err := os.Hostname() + if err != nil { + fmt.Errorf("Error in getting hostname: ", err) + } + response, _ := Swagger(hostName, t.config, "") + io.WriteString(w, string(response)) +} + +func (t *Trigger) SwaggerHandler(w http.ResponseWriter, req *http.Request) { + var response []byte + match, _ := regexp.MatchString("/swagger/[A-Za-z0-9]+$", req.URL.Path) + if(match) { + vars := strings.Split(req.URL.Path, "/") + triggerName := vars[2] + hostName, err := os.Hostname() + if err != nil { + fmt.Errorf("Error in getting hostname: ", err) + } + response, _ = Swagger(hostName, t.config, triggerName) + }else{ + response = []byte("404 page not found\n") + } + io.WriteString(w, string(response)) +} + +// Start implements util.Managed.Start +func (t *Trigger) Start() error { + go func() { + if err := t.Server.ListenAndServe(); err != http.ErrServerClosed { + fmt.Errorf("Ping service err:", err) + } + }() + return nil +} + +// Stop implements util.Managed.Stop +func (t *Trigger) Stop() error { + if err := t.Server.Shutdown(nil); err != nil { + fmt.Errorf("[mashling-ping-service] Ping service error when stopping:", err) + return err + } + return nil +} + +func Swagger(hostname string, config *trigger.Config, triggerName string) ([]byte, error) { + var endpoints []Endpoint + for _, tConfig := range config.AppConfig["Trigger"].([]*trigger.Config) { + if tConfig.Id == "" || tConfig.Id == triggerName { + if tConfig.Ref == "github.com/project-flogo/contrib/trigger/rest" || tConfig.Ref == "github.com/project-flogo/core/swagger" { + for _, handler := range tConfig.Handlers { + var endpoint Endpoint + endpoint.Name = tConfig.Id + endpoint.Method = handler.Settings["method"].(string) + endpoint.Path = handler.Settings["path"].(string) + endpoint.Description = tConfig.Settings["description"].(string) + hostname = hostname + ": "+strconv.Itoa(tConfig.Settings["port"].(int)) + var beginDelim, endDelim rune + switch tConfig.Ref { + case "github.com/project-flogo/contrib/trigger/rest": + beginDelim = ':' + endDelim = '/' + default: + beginDelim = '{' + endDelim = '}' + } + endpoint.BeginDelim = beginDelim + endpoint.EndDelim = endDelim + endpoints = append(endpoints, endpoint) + } + } + } + } + return Generate(hostname, config.AppConfig["Name"].(string), config.AppConfig["Version"].(string), config.AppConfig["Description"].(string), endpoints) +} + + +func (t *Trigger) Initialize(ctx trigger.InitContext) error { + return nil +} \ No newline at end of file diff --git a/trigger/swagger/examples/swagger/README.md b/trigger/swagger/examples/swagger/README.md new file mode 100644 index 00000000..45b0f573 --- /dev/null +++ b/trigger/swagger/examples/swagger/README.md @@ -0,0 +1,92 @@ +# Swagger Feature Example + +## Install + +To install run the following commands: +``` +flogo create -f flogo.json +cd swagger +flogo build +``` + +## Testing + +Run: +``` +bin/swagger +``` + +Then open another terminal and run: +``` +### Format : curl http://localhost:1234/swagger/<triggerName> ### + +curl http://localhost:1234/swagger/swagdocs +``` + +You should then see something like: +``` +{ + "host": "Temporarys-MacBook-Pro.local: 1234", + "info": { + "description": "1.0.0", + "title": "swagger", + "version": "This is a simple proxy." + }, + "paths": { + "/swagger": { + "get": { + "description": "Simple swagger doc Trigger", + "parameters": [], + "responses": { + "200": { + "description": "Simple swagger doc Trigger" + }, + "default": { + "description": "error" + } + }, + "tags": [ + "swagdocs" + ] + } + } + }, + "swagger": "2.0" +} +``` +##### +``` +curl http://localhost:1234/swagger/MyProxy +``` + +You should then see something like: +``` +{ + "host": "Temporarys-MacBook-Pro.local: 9096", + "info": { + "description": "1.0.0", + "title": "swagger", + "version": "This is a simple proxy." + }, + "paths": { + "/pets": { + "get": { + "description": "Simple REST Trigger", + "parameters": [], + "responses": { + "200": { + "description": "Simple REST Trigger" + }, + "default": { + "description": "error" + } + }, + "tags": [ + "MyProxy" + ] + } + } + }, + "swagger": "2.0" +} +``` diff --git a/trigger/swagger/examples/swagger/flogo.json b/trigger/swagger/examples/swagger/flogo.json new file mode 100644 index 00000000..6f3716db --- /dev/null +++ b/trigger/swagger/examples/swagger/flogo.json @@ -0,0 +1,101 @@ +{ + "name": "swagger", + "type": "flogo:app", + "version": "1.0.0", + "description": "This is a simple proxy.", + "properties": null, + "channels": null, + "triggers": [ + { + "name": "swagger", + "id": "swagdocs", + "ref": "github.com/project-flogo/core/swagger", + "settings": { + "port": "1234", + "description": "Simple swagger doc Trigger" + }, + "handlers": [ + { + "settings": { + "method": "GET", + "path": "/{triggerName}/swagger" + }, + "actions": [ + { + "id": "swaggertrigger" + } + ] + } + ] + }, + { + "name": "flogo-rest", + "id": "MyProxy", + "ref": "github.com/project-flogo/contrib/trigger/rest", + "settings": { + "port": "9096", + "description": "Simple REST Trigger" + }, + "handlers": [ + { + "settings": { + "method": "GET", + "path": "/pets" + }, + "actions": [ + { + "id": "microgateway:jwt" + } + ] + } + ] + } + ], + "resources": [ + { + "id": "microgateway:jwt", + "compressed": false, + "data": { + "name": "Pets", + "steps": [ + { + "service": "PetStorePets", + "input": { + "pathParams.petId": "1" + } + } + ], + "responses": [], + "services": [ + { + "name": "PetStorePets", + "description": "Get pets by ID from the petstore", + "ref": "github.com/project-flogo/contrib/activity/rest", + "settings": { + "uri": "https://petstore.swagger.io/v2/pet/:petId", + "method": "GET" + } + } + ] + } + } + ], + "actions": [ + { + "ref": "github.com/project-flogo/microgateway", + "settings": { + "uri": "microgateway:jwt" + }, + "id": "swaggertrigger", + "metadata": null + }, + { + "ref": "github.com/project-flogo/microgateway", + "settings": { + "uri": "microgateway:jwt" + }, + "id": "microgateway:jwt", + "metadata": null + } + ] +} \ No newline at end of file diff --git a/trigger/swagger/metadata.go b/trigger/swagger/metadata.go new file mode 100644 index 00000000..ae18dad5 --- /dev/null +++ b/trigger/swagger/metadata.go @@ -0,0 +1,22 @@ +package swagger + + +// Endpoint represents an endpoint in a Swagger 2.0 document. +type Endpoint struct { + Name string `md:"name"` + Description string `md:"description"` + Path string `md:"path"` + Method string `md:"method"` + BeginDelim rune `md:"begin_delim"` + EndDelim rune `md:"end_delim"` +} + + +type Settings struct { + Port int `md:"port,required"` +} + +type HandlerSettings struct { + Method string `md:"method,required,allowed(GET,POST,PUT,PATCH,DELETE)"` + Path string `md:"path,required"` +} diff --git a/trigger/swagger/swagger.go b/trigger/swagger/swagger.go new file mode 100644 index 00000000..dea49b42 --- /dev/null +++ b/trigger/swagger/swagger.go @@ -0,0 +1,75 @@ +package swagger + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +// Generate generates a Swagger 2.0 document based off of the provided endpoints. +func Generate(host string, name string, description string, version string, endpoints []Endpoint) ([]byte, error) { + paths := map[string]interface{}{} + + for _, endpoint := range endpoints { + path := map[string]interface{}{} + parameters, scrubbedPath := swaggerParametersExtractor(endpoint.Path, endpoint.BeginDelim, endpoint.EndDelim) + ok := map[string]interface{}{ + "description": endpoint.Description, + } + path[strings.ToLower(endpoint.Method)] = map[string]interface{}{ + "description": endpoint.Description, + "tags": []interface{}{endpoint.Name}, + "parameters": parameters, + "responses": map[string]interface{}{ + "200": ok, + "default": map[string]interface{}{ + "description": "error", + }, + }, + } + paths[scrubbedPath] = path + } + + swagger := map[string]interface{}{ + "swagger": "2.0", + "info": map[string]interface{}{ + "version": version, + "title": name, + "description": description, + }, + "host": host, + "paths": paths, + } + docs, err := json.MarshalIndent(&swagger, "", " ") + if err != nil { + return nil, err + } + return docs, err +} + +func swaggerParametersExtractor(path string, beginDelim rune, endDelim rune) ([]interface{}, string) { + parameters := []interface{}{} + routePath := []rune(path) + for i := 0; i < len(routePath); i++ { + if routePath[i] == beginDelim { + key := bytes.Buffer{} + for i++; i < len(routePath) && routePath[i] != endDelim; i++ { + if routePath[i] != ' ' && routePath[i] != '\t' { + key.WriteRune(routePath[i]) + } + } + if beginDelim == ':' { + path = strings.Replace(path, fmt.Sprintf(":%s", key.String()), fmt.Sprintf("{%s}", key.String()), 1) + } + parameter := map[string]interface{}{ + "name": key.String(), + "in": "path", + "required": true, + "type": "string", + } + parameters = append(parameters, parameter) + } + } + return parameters, path +}