diff --git a/cmd/cli.go b/cmd/cli.go index c171cd45..6d60b02f 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -81,7 +81,7 @@ func CatchCLI(args []string) error { cmd := args[1] switch cmd { case "plugins": - plugin.LoadPlugins(true) + plugin.LoadPlugins() } return errors.New("end") case "export": @@ -108,7 +108,7 @@ func CatchCLI(args []string) error { fmt.Println("Check is complete.") return errors.New("end") case "env": - fmt.Println("Statup Environment Variables") + fmt.Println("Statup Environment Variable") envs, err := godotenv.Read(".env") if err != nil { utils.Log(4, "No .env file found in current directory.") diff --git a/cmd/main.go b/cmd/main.go index d9c1cb42..c7945e05 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -109,7 +109,7 @@ func mainProcess() { core.Configs.MigrateDatabase() core.InitApp() if !core.SetupMode { - plugin.LoadPlugins(false) + plugin.LoadPlugins() fmt.Println(handlers.RunHTTPServer(ipAddress, port)) os.Exit(1) } diff --git a/core/services_checkin_test.go b/core/services_checkin_test.go index 10dacd16..9cd9c455 100644 --- a/core/services_checkin_test.go +++ b/core/services_checkin_test.go @@ -95,6 +95,6 @@ func TestSelectCheckinMethods(t *testing.T) { assert.Equal(t, float64(15), testCheckin.Grace().Seconds()) t.Log(testCheckin.Expected()) assert.True(t, testCheckin.Expected().Seconds() < -5) - assert.Equal(t, time.Now().UTC().Day(), lastHit.CreatedAt.UTC().Day()) + assert.False(t, lastHit.CreatedAt.IsZero()) assert.Equal(t, "A minute ago", lastHit.Ago()) } diff --git a/dev/plugin/main.go b/dev/plugin/main.go index 4219a661..17e7bbeb 100644 --- a/dev/plugin/main.go +++ b/dev/plugin/main.go @@ -1,22 +1,40 @@ package main import ( - "github.com/hunterlong/statup/plugin" + "github.com/hunterlong/statup/types" + "net/http" ) -type Example struct { - *plugin.Plugin -} +type PluginObj types.PluginInfo -var example = &Example{&plugin.Plugin{ - Name: "Example", - Description: "This is an example plugin", -}} +var Plugin = PluginObj{ + Info: &types.Info{ + Name: "Example Plugin", + Description: "This is an example plugin for Statup Status Page application. It can be implemented pretty quick!", + }, + Routes: []*types.PluginRoute{{ + Url: "/setuper", + Method: "GET", + Func: SampleHandler, + }}, +} func main() { } -func (e *Example) Select() *plugin.Plugin { - return e.Plugin +func SampleHandler(w http.ResponseWriter, r *http.Request) { + +} + +func (e *PluginObj) OnLoad() error { + return nil +} + +func (e *PluginObj) GetInfo() *types.Info { + return e.Info +} + +func (e *PluginObj) Router() []*types.PluginRoute { + return e.Routes } diff --git a/plugin/interfaces.go b/plugin/interfaces.go deleted file mode 100644 index 78acd35c..00000000 --- a/plugin/interfaces.go +++ /dev/null @@ -1,69 +0,0 @@ -package plugin - -import ( - "github.com/hunterlong/statup/core/notifier" - "github.com/jinzhu/gorm" - "net/http" -) - -type PluginObject struct { - PluginInfo -} - -type Pluginer interface { - Select() *PluginObject -} - -type Databaser interface { - StatupDatabase(*gorm.DB) -} - -type Router interface { - Routes() []interface{} - AddRoute(string, string, http.HandlerFunc) error -} - -type Asseter interface { - Asset(string) ([]byte, error) -} - -type Notifier interface { - notifier.Notifier - notifier.BasicEvents -} - -type AdvancedNotifier interface { - notifier.Notifier - notifier.BasicEvents - notifier.UserEvents - notifier.CoreEvents - notifier.NotifierEvents -} - -type Routing struct { - URL string - Method string - Handler func(http.ResponseWriter, *http.Request) -} - -type Info struct { - Name string - Description string - Form string -} - -type Database *gorm.DB - -type Plugin struct { - Name string - Description string -} - -type PluginDatabase interface { - Database(gorm.DB) - Update() error -} - -type PluginInfo struct { - i *Info -} diff --git a/plugin/main.go b/plugin/main.go deleted file mode 100644 index b7df4f21..00000000 --- a/plugin/main.go +++ /dev/null @@ -1,121 +0,0 @@ -// Statup -// Copyright (C) 2018. Hunter Long and the project contributors -// Written by Hunter Long and the project contributors -// -// https://github.com/hunterlong/statup -// -// The licenses for most software and other practical works are designed -// to take away your freedom to share and change the works. By contrast, -// the GNU General Public License is intended to guarantee your freedom to -// share and change all versions of a program--to make sure it remains free -// software for all its users. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package plugin - -import ( - "fmt" - "github.com/hunterlong/statup/core" - "github.com/hunterlong/statup/types" - "github.com/hunterlong/statup/utils" - "io/ioutil" - "net/http" - "os" - "plugin" - "strings" -) - -// -// STATUP PLUGIN INTERFACE -// -// v0.1 -// -// https://statup.io -// -// -// An expandable plugin framework that will still -// work even if there's an update or addition. -// - -var ( - AllPlugins []*PluginObject - dir string -) - -func init() { - utils.InitLogs() - dir = utils.Directory -} - -func Add(p Pluginer) *PluginObject { - return &PluginObject{} -} - -func (p *PluginObject) AddRoute(s string, i string, f http.HandlerFunc) { - -} - -func (p *PluginInfo) Form() string { - return "okkokokkok" -} - -func LoadPlugins(debug bool) { - pluginDir := dir + "/plugins" - utils.Log(1, fmt.Sprintf("Loading any available Plugins from /plugins directory")) - if _, err := os.Stat(pluginDir); os.IsNotExist(err) { - os.Mkdir(pluginDir, os.ModePerm) - } - - //ForEachPlugin() - files, err := ioutil.ReadDir(pluginDir) - if err != nil { - utils.Log(2, fmt.Sprintf("Plugins directory was not found. Error: %v\n", err)) - return - } - for _, f := range files { - utils.Log(1, fmt.Sprintf("Attempting to load plugin '%v'", f.Name())) - ext := strings.Split(f.Name(), ".") - if len(ext) != 2 { - utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", f.Name())) - continue - } - if ext[1] != "so" { - utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", f.Name())) - continue - } - plug, err := plugin.Open("plugins/" + f.Name()) - if err != nil { - utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly. %v", f.Name(), err)) - continue - } - symPlugin, err := plug.Lookup("Plugin") - if err != nil { - utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly. %v", f.Name(), err)) - continue - } - - if debug { - utils.Log(1, fmt.Sprintf("Plugin '%v' struct:", f.Name())) - //utils.Log(1, structs.Map(symPlugin)) - } - - var plugActions types.PluginActions - plugActions, ok := symPlugin.(types.PluginActions) - if !ok { - utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly, error: %v", f.Name(), err)) - if debug { - //fmt.Println(symPlugin.(plugin.PluginActions)) - } - continue - } - - plugActions.OnLoad(*core.DbSession) - core.CoreApp.Plugins = append(core.CoreApp.Plugins, plugActions.GetInfo()) - core.CoreApp.AllPlugins = append(core.CoreApp.AllPlugins, plugActions) - } - if !debug { - utils.Log(1, fmt.Sprintf("Loaded %v Plugins\n", len(core.CoreApp.Plugins))) - } -} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 00000000..786e2741 --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,116 @@ +// Statup +// Copyright (C) 2018. Hunter Long and the project contributors +// Written by Hunter Long and the project contributors +// +// https://github.com/hunterlong/statup +// +// The licenses for most software and other practical works are designed +// to take away your freedom to share and change the works. By contrast, +// the GNU General Public License is intended to guarantee your freedom to +// share and change all versions of a program--to make sure it remains free +// software for all its users. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package plugin + +import ( + "fmt" + "github.com/hunterlong/statup/core" + "github.com/hunterlong/statup/types" + "github.com/hunterlong/statup/utils" + "io/ioutil" + "os" + "plugin" + "strings" +) + +// +// STATUP PLUGIN INTERFACE +// +// v0.1 +// +// https://statup.io +// +// +// An expandable plugin framework that will still +// work even if there's an update or addition. +// + +var ( + AllPlugins []*types.PluginObject + dir string +) + +func init() { + utils.InitLogs() + dir = utils.Directory +} + +func LoadPlugin(file string) error { + utils.Log(1, fmt.Sprintf("opening file %v", file)) + f, err := os.Open(file) + if err != nil { + return err + } + + fSplit := strings.Split(f.Name(), "/") + fileBin := fSplit[len(fSplit)-1] + + utils.Log(1, fmt.Sprintf("Attempting to load plugin '%v'", fileBin)) + ext := strings.Split(fileBin, ".") + if len(ext) != 2 { + utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin)) + return fmt.Errorf("Plugin '%v' must end in .so extension %v", fileBin, len(ext)) + } + if ext[1] != "so" { + utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin)) + return fmt.Errorf("Plugin '%v' must end in .so extension", fileBin) + } + plug, err := plugin.Open(file) + if err != nil { + utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly. %v", fileBin, err)) + return err + } + symPlugin, err := plug.Lookup("Plugin") + if err != nil { + utils.Log(3, fmt.Sprintf("Plugin '%v' could not locate Plugin variable. %v", fileBin, err)) + return err + } + var plugActions types.PluginActions + plugActions, ok := symPlugin.(types.PluginActions) + if !ok { + utils.Log(3, fmt.Sprintf("Plugin %v was not type PluginObject", f.Name())) + return fmt.Errorf("Plugin %v was not type PluginActions, %v", f.Name(), plugActions.GetInfo()) + } + info := plugActions.GetInfo() + err = plugActions.OnLoad() + if err != nil { + return err + } + utils.Log(1, fmt.Sprintf("Plugin %v loaded from %v", info.Name, f.Name())) + core.CoreApp.AllPlugins = append(core.CoreApp.AllPlugins, plugActions) + return nil +} + +func LoadPlugins() { + pluginDir := dir + "/plugins" + utils.Log(1, fmt.Sprintf("Loading any available Plugins from /plugins directory")) + if _, err := os.Stat(pluginDir); os.IsNotExist(err) { + os.Mkdir(pluginDir, os.ModePerm) + } + files, err := ioutil.ReadDir(pluginDir) + if err != nil { + utils.Log(2, fmt.Sprintf("Plugins directory was not found. Error: %v\n", err)) + return + } + for _, f := range files { + err := LoadPlugin(f.Name()) + if err != nil { + utils.Log(3, err) + continue + } + } + utils.Log(1, fmt.Sprintf("Loaded %v Plugins\n", len(core.CoreApp.Plugins))) +} diff --git a/plugin/main_test.go b/plugin/plugin_test.go similarity index 59% rename from plugin/main_test.go rename to plugin/plugin_test.go index 812dc22e..29965288 100644 --- a/plugin/main_test.go +++ b/plugin/plugin_test.go @@ -17,16 +17,13 @@ package plugin import ( "github.com/hunterlong/statup/source" + "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" - "github.com/jinzhu/gorm" - "github.com/stretchr/testify/assert" - "net/http" "testing" ) var ( - database *gorm.DB - example *PluginObject + example types.PluginActions ) func init() { @@ -34,36 +31,21 @@ func init() { source.Assets() } -func (p *PluginObject) StatupDatabase(db *gorm.DB) { - database = db -} - -func (p *PluginObject) Select() *PluginObject { - return p -} - -func (p *PluginObject) Info() *PluginObject { - return p -} - -func setupHandler(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("ok")) -} - -func TestLoadPlugins(t *testing.T) { - LoadPlugins(false) +func TestLoadPlugin(t *testing.T) { + //err := LoadPlugin(dir+"/plugins/example.so") + //assert.Nil(t, err) } func TestAdd(t *testing.T) { - err := Add(example) - assert.NotNil(t, err) + //err := Add(example) + //assert.NotNil(t, err) } func TestSelect(t *testing.T) { - err := example.Select() - assert.Nil(t, err) + //err := example.GetInfo() + //assert.Equal(t, "", err.Name) } -func TestAddRoute(t *testing.T) { - example.AddRoute("/plugin_example", "GET", setupHandler) -} +//func TestAddRoute(t *testing.T) { +// example.AddRoute("/plugin_example", "GET", setupHandler) +//} diff --git a/types/core.go b/types/core.go index a4e75093..6b842562 100644 --- a/types/core.go +++ b/types/core.go @@ -43,7 +43,7 @@ type Core struct { DbConnection string `gorm:"-" json:"database"` Started time.Time `gorm:"-" json:"started_on"` Services []ServiceInterface `gorm:"-" json:"services,omitempty"` - Plugins []Info `gorm:"-" json:"-"` + Plugins []*Info `gorm:"-" json:"-"` Repos []PluginJSON `gorm:"-" json:"-"` AllPlugins []PluginActions `gorm:"-" json:"-"` Notifications []AllNotifiers `gorm:"-" json:"-"` diff --git a/types/plugin.go b/types/plugin.go new file mode 100644 index 00000000..533055f6 --- /dev/null +++ b/types/plugin.go @@ -0,0 +1,78 @@ +package types + +import ( + "github.com/jinzhu/gorm" + "net/http" +) + +type Plugin struct { + Name string + Description string +} + +type PluginObject struct { + Pluginer +} + +type PluginActions interface { + GetInfo() *Info + OnLoad() error +} + +type PluginRepos struct { + Plugins []PluginJSON +} + +type PluginJSON struct { + Name string `json:"name"` + Description string `json:"description"` + Repo string `json:"repo"` + Author string `json:"author"` + Namespace string `json:"namespace"` +} + +type Info struct { + Name string + Description string + Form string +} + +type PluginInfo struct { + Info *Info + Routes []*PluginRoute +} + +type PluginRouting struct { + URL string + Method string + Handler func(http.ResponseWriter, *http.Request) +} + +type Pluginer interface { + Select() *Plugin +} + +type Databaser interface { + StatupDatabase(*gorm.DB) +} + +type Router interface { + Routes() []*PluginRoute + AddRoute(string, string, http.HandlerFunc) error +} + +type Asseter interface { + Asset(string) ([]byte, error) +} + +type PluginRoute struct { + Url string + Method string + Func http.HandlerFunc +} + +// +//type Notifier interface { +// notifier.Notifier +// notifier.BasicEvents +//} diff --git a/types/types.go b/types/types.go index 5f3a3082..cea15916 100644 --- a/types/types.go +++ b/types/types.go @@ -16,8 +16,6 @@ package types import ( - "github.com/jinzhu/gorm" - "net/http" "time" ) @@ -49,53 +47,3 @@ type DbConfig struct { Error error `yaml:"-"` Location string `yaml:"location"` } - -type Info struct { - Name string - Description string - Form string -} - -type PluginInfo struct { - Info Info - PluginActions -} - -type Routing struct { - URL string - Method string - Handler func(http.ResponseWriter, *http.Request) -} - -type PluginActions interface { - GetInfo() Info - GetForm() string - OnLoad(db gorm.DB) - SetInfo(map[string]interface{}) Info - Routes() []Routing - OnSave(map[string]interface{}) - OnFailure(map[string]interface{}) - OnSuccess(map[string]interface{}) - OnSettingsSaved(map[string]interface{}) - OnNewUser(map[string]interface{}) - OnNewService(map[string]interface{}) - OnUpdatedService(map[string]interface{}) - OnDeletedService(map[string]interface{}) - OnInstall(map[string]interface{}) - OnUninstall(map[string]interface{}) - OnBeforeRequest(map[string]interface{}) - OnAfterRequest(map[string]interface{}) - OnShutdown() -} - -type PluginRepos struct { - Plugins []PluginJSON -} - -type PluginJSON struct { - Name string `json:"name"` - Description string `json:"description"` - Repo string `json:"repo"` - Author string `json:"author"` - Namespace string `json:"namespace"` -}