feat(cli): move cli into main repo

This commit is contained in:
bergquist
2016-02-15 14:09:34 +01:00
parent fe4cdc59be
commit d59beec354
63 changed files with 6180 additions and 4 deletions

View File

@@ -0,0 +1,35 @@
package commands
import (
"github.com/codegangsta/cli"
)
type CommandLine interface {
ShowHelp()
ShowVersion()
Application() *cli.App
Args() cli.Args
Bool(name string) bool
Int(name string) int
String(name string) string
StringSlice(name string) []string
GlobalString(name string) string
FlagNames() (names []string)
Generic(name string) interface{}
}
type contextCommandLine struct {
*cli.Context
}
func (c *contextCommandLine) ShowHelp() {
cli.ShowCommandHelp(c.Context, c.Command.Name)
}
func (c *contextCommandLine) ShowVersion() {
cli.ShowVersion(c.Context)
}
func (c *contextCommandLine) Application() *cli.App {
return c.App
}

View File

@@ -0,0 +1,48 @@
package commands
import (
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
)
func runCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
return func(context *cli.Context) {
cmd := &contextCommandLine{context}
if err := command(cmd); err != nil {
log.Errorf("%v\n\n", err)
cmd.ShowHelp()
} else {
log.Info("Restart grafana after installing plugins . <service grafana-server restart>\n")
}
}
}
var Commands = []cli.Command{
{
Name: "install",
Usage: "installs stuff",
Action: runCommand(installCommand),
}, {
Name: "list-remote",
Usage: "list remote available plugins",
Action: runCommand(listremoteCommand),
}, {
Name: "upgrade",
Usage: "upgrades one plugin",
Action: runCommand(upgradeCommand),
}, {
Name: "upgrade-all",
Usage: "upgrades all your installed plugins",
Action: runCommand(upgradeAllCommand),
}, {
Name: "ls",
Usage: "list all installed plugins",
Action: runCommand(lsCommand),
}, {
Name: "remove",
Usage: "removes stuff",
Action: runCommand(removeCommand),
},
}

View File

@@ -0,0 +1,95 @@
package commandstest
import (
"github.com/codegangsta/cli"
)
type FakeFlagger struct {
Data map[string]interface{}
}
type FakeCommandLine struct {
LocalFlags, GlobalFlags *FakeFlagger
HelpShown, VersionShown bool
CliArgs []string
}
func (ff FakeFlagger) String(key string) string {
if value, ok := ff.Data[key]; ok {
return value.(string)
}
return ""
}
func (ff FakeFlagger) StringSlice(key string) []string {
if value, ok := ff.Data[key]; ok {
return value.([]string)
}
return []string{}
}
func (ff FakeFlagger) Int(key string) int {
if value, ok := ff.Data[key]; ok {
return value.(int)
}
return 0
}
func (ff FakeFlagger) Bool(key string) bool {
if value, ok := ff.Data[key]; ok {
return value.(bool)
}
return false
}
func (fcli *FakeCommandLine) String(key string) string {
return fcli.LocalFlags.String(key)
}
func (fcli *FakeCommandLine) StringSlice(key string) []string {
return fcli.LocalFlags.StringSlice(key)
}
func (fcli *FakeCommandLine) Int(key string) int {
return fcli.LocalFlags.Int(key)
}
func (fcli *FakeCommandLine) Bool(key string) bool {
if fcli.LocalFlags == nil {
return false
}
return fcli.LocalFlags.Bool(key)
}
func (fcli *FakeCommandLine) GlobalString(key string) string {
return fcli.GlobalFlags.String(key)
}
func (fcli *FakeCommandLine) Generic(name string) interface{} {
return fcli.LocalFlags.Data[name]
}
func (fcli *FakeCommandLine) FlagNames() []string {
flagNames := []string{}
for key := range fcli.LocalFlags.Data {
flagNames = append(flagNames, key)
}
return flagNames
}
func (fcli *FakeCommandLine) ShowHelp() {
fcli.HelpShown = true
}
func (fcli *FakeCommandLine) Application() *cli.App {
return cli.NewApp()
}
func (fcli *FakeCommandLine) Args() cli.Args {
return fcli.CliArgs
}
func (fcli *FakeCommandLine) ShowVersion() {
fcli.VersionShown = true
}

View File

@@ -0,0 +1,55 @@
package commandstest
import (
"os"
"time"
)
type FakeIoUtil struct {
FakeReadDir []os.FileInfo
FakeIsDirectory bool
}
func (util *FakeIoUtil) Stat(path string) (os.FileInfo, error) {
return FakeFileInfo{IsDirectory: util.FakeIsDirectory}, nil
}
func (util *FakeIoUtil) RemoveAll(path string) error {
return nil
}
func (util *FakeIoUtil) ReadDir(path string) ([]os.FileInfo, error) {
return util.FakeReadDir, nil
}
func (i *FakeIoUtil) ReadFile(filename string) ([]byte, error) {
return make([]byte, 0), nil
}
type FakeFileInfo struct {
IsDirectory bool
}
func (ffi FakeFileInfo) IsDir() bool {
return ffi.IsDirectory
}
func (ffi FakeFileInfo) Size() int64 {
return 1
}
func (ffi FakeFileInfo) Mode() os.FileMode {
return 0777
}
func (ffi FakeFileInfo) Name() string {
return ""
}
func (ffi FakeFileInfo) ModTime() time.Time {
return time.Time{}
}
func (ffi FakeFileInfo) Sys() interface{} {
return nil
}

View File

@@ -0,0 +1,146 @@
package commands
import (
"archive/zip"
"bytes"
"errors"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"regexp"
)
func validateInput(c CommandLine, pluginFolder string) error {
arg := c.Args().First()
if arg == "" {
return errors.New("please specify plugin to install")
}
pluginDir := c.GlobalString("path")
if pluginDir == "" {
return errors.New("missing path flag")
}
fileinfo, err := os.Stat(pluginDir)
if err != nil && !fileinfo.IsDir() {
return errors.New("path is not a directory")
}
return nil
}
func installCommand(c CommandLine) error {
pluginFolder := c.GlobalString("path")
if err := validateInput(c, pluginFolder); err != nil {
return err
}
pluginToInstall := c.Args().First()
version := c.Args().Get(1)
log.Infof("version: %v\n", version)
return InstallPlugin(pluginToInstall, pluginFolder, version)
}
func InstallPlugin(pluginName, pluginFolder, version string) error {
plugin, err := services.GetPlugin(pluginName)
if err != nil {
return err
}
v, err := SelectVersion(plugin, version)
if err != nil {
return err
}
url := v.Url
commit := v.Commit
downloadURL := url + "/archive/" + commit + ".zip"
log.Infof("installing %v @ %v\n", plugin.Id, version)
log.Infof("from url: %v\n", downloadURL)
log.Infof("on commit: %v\n", commit)
log.Infof("into: %v\n", pluginFolder)
err = downloadFile(plugin.Id, pluginFolder, downloadURL)
if err == nil {
log.Info("Installed %s successfully ✔\n", plugin.Id)
}
res := services.ReadPlugin(pluginFolder, pluginName)
for _, v := range res.Dependency.Plugins {
log.Infof("Depends on %s install!\n", v.Id)
//Todo: uncomment this code once the repo is more correct.
//InstallPlugin(v.Id, pluginFolder, "")
}
return err
}
func SelectVersion(plugin m.Plugin, version string) (m.Version, error) {
if version == "" {
return plugin.Versions[0], nil
}
for _, v := range plugin.Versions {
if v.Version == version {
return v, nil
}
}
return m.Version{}, errors.New("Could not find the version your looking for")
}
func RemoveGitBuildFromname(pluginname, filename string) string {
r := regexp.MustCompile("^[a-zA-Z0-9_.-]*/")
return r.ReplaceAllString(filename, pluginname+"/")
}
func downloadFile(pluginName, filepath, url string) (err error) {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
r, err := zip.NewReader(bytes.NewReader(body), resp.ContentLength)
if err != nil {
return err
}
for _, zf := range r.File {
newfile := path.Join(filepath, RemoveGitBuildFromname(pluginName, zf.Name))
if zf.FileInfo().IsDir() {
os.Mkdir(newfile, 0777)
} else {
dst, err := os.Create(newfile)
if err != nil {
log.Errorf("%v", err)
}
defer dst.Close()
src, err := zf.Open()
if err != nil {
log.Errorf("%v", err)
}
defer src.Close()
io.Copy(dst, src)
}
}
return nil
}

View File

@@ -0,0 +1,39 @@
package commands
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestFoldernameReplacement(t *testing.T) {
Convey("path containing git commit path", t, func() {
pluginName := "datasource-plugin-kairosdb"
paths := map[string]string{
"datasource-plugin-kairosdb-cc4a3965ef5d3eb1ae0ee4f93e9e78ec7db69e64/": "datasource-plugin-kairosdb/",
"datasource-plugin-kairosdb-cc4a3965ef5d3eb1ae0ee4f93e9e78ec7db69e64/README.md": "datasource-plugin-kairosdb/README.md",
"datasource-plugin-kairosdb-cc4a3965ef5d3eb1ae0ee4f93e9e78ec7db69e64/partials/": "datasource-plugin-kairosdb/partials/",
"datasource-plugin-kairosdb-cc4a3965ef5d3eb1ae0ee4f93e9e78ec7db69e64/partials/config.html": "datasource-plugin-kairosdb/partials/config.html",
}
Convey("should be replaced with plugin name", func() {
for k, v := range paths {
So(RemoveGitBuildFromname(pluginName, k), ShouldEqual, v)
}
})
})
Convey("path containing git commit path", t, func() {
pluginName := "app-example"
paths := map[string]string{
"app-plugin-example-3c28f65ac6fb7f1e234b0364b97081d836495439/": "app-example/",
}
Convey("should be replaced with plugin name", func() {
for k, v := range paths {
So(RemoveGitBuildFromname(pluginName, k), ShouldEqual, v)
}
})
})
}

View File

@@ -0,0 +1,20 @@
package commands
import (
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
)
func listremoteCommand(c CommandLine) error {
plugin, err := services.ListAllPlugins()
if err != nil {
return err
}
for _, i := range plugin.Plugins {
log.Infof("id: %v version:\n", i.Id)
}
return nil
}

View File

@@ -0,0 +1,49 @@
package commands
import (
"errors"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
)
var getPlugins func(path string) []m.InstalledPlugin
var GetStat m.IoUtil
func init() {
getPlugins = s.GetLocalPlugins
GetStat = s.IoUtil
}
func validateCommand(pluginDir string) error {
if pluginDir == "" {
return errors.New("missing path flag")
}
log.Info("plugindir: " + pluginDir + "\n")
pluginDirInfo, err := GetStat.Stat(pluginDir)
if err != nil {
return errors.New("missing path flag")
}
if pluginDirInfo.IsDir() == false {
return errors.New("plugin path is not a directory")
}
return nil
}
func lsCommand(c CommandLine) error {
pluginDir := c.GlobalString("path")
if err := validateCommand(pluginDir); err != nil {
return err
}
for _, plugin := range getPlugins(pluginDir) {
log.Infof("plugin: %s @ %s \n", plugin.Name, plugin.Info.Version)
}
return nil
}

View File

@@ -0,0 +1,47 @@
package commands
import (
"testing"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/commandstest"
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
. "github.com/smartystreets/goconvey/convey"
)
func TestMissingPath(t *testing.T) {
Convey("Missing path", t, func() {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"ls"},
GlobalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"path": "",
},
},
}
s.IoHelper = &commandstest.FakeIoUtil{}
Convey("should return error", func() {
err := lsCommand(commandLine)
So(err, ShouldNotBeNil)
})
})
Convey("Path is not a directory", t, func() {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"ls"},
GlobalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"path": "/var/lib/grafana/plugins",
},
},
}
GetStat = &commandstest.FakeIoUtil{
FakeIsDirectory: false,
}
Convey("should return error", func() {
err := lsCommand(commandLine)
So(err, ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,35 @@
package commands
import (
"errors"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
)
var getPluginss func(path string) []m.InstalledPlugin = services.GetLocalPlugins
var removePlugin func(pluginPath, id string) error = services.RemoveInstalledPlugin
func removeCommand(c CommandLine) error {
pluginPath := c.GlobalString("path")
localPlugins := getPluginss(pluginPath)
log.Info("remove!\n")
plugin := c.Args().First()
log.Info("plugin: " + plugin + "\n")
if plugin == "" {
return errors.New("Missing which plugin parameter")
}
log.Infof("plugins : \n%v\n", localPlugins)
for _, p := range localPlugins {
log.Infof("is %s == %s ? %v", p.Id, c.Args().First(), p.Id == c.Args().First())
if p.Id == c.Args().First() {
removePlugin(pluginPath, p.Id)
}
}
return nil
}

View File

@@ -0,0 +1,61 @@
package commands
import (
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
"github.com/hashicorp/go-version"
)
func ShouldUpgrade(installed string, remote m.Plugin) bool {
installedVersion, err1 := version.NewVersion(installed)
if err1 != nil {
return false
}
for _, v := range remote.Versions {
remoteVersion, err2 := version.NewVersion(v.Version)
if err2 == nil {
if installedVersion.LessThan(remoteVersion) {
return true
}
}
}
return false
}
func upgradeAllCommand(c CommandLine) error {
pluginDir := c.GlobalString("path")
localPlugins := services.GetLocalPlugins(pluginDir)
remotePlugins, err := services.ListAllPlugins()
if err != nil {
return err
}
pluginsToUpgrade := make([]m.InstalledPlugin, 0)
for _, localPlugin := range localPlugins {
for _, remotePlugin := range remotePlugins.Plugins {
if localPlugin.Id == remotePlugin.Id {
if ShouldUpgrade(localPlugin.Info.Version, remotePlugin) {
pluginsToUpgrade = append(pluginsToUpgrade, localPlugin)
}
}
}
}
for _, p := range pluginsToUpgrade {
log.Infof("lets upgrade %v \n", p)
services.RemoveInstalledPlugin(pluginDir, p.Id)
InstallPlugin(p.Id, pluginDir, "")
}
return nil
}

View File

@@ -0,0 +1,46 @@
package commands
import (
"testing"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
. "github.com/smartystreets/goconvey/convey"
)
func TestVersionComparsion(t *testing.T) {
Convey("Validate that version is outdated", t, func() {
versions := []m.Version{
{Version: "1.1.1"},
{Version: "2.0.0"},
}
shouldUpgrade := map[string]m.Plugin{
"0.0.0": {Versions: versions},
"1.0.0": {Versions: versions},
}
Convey("should return error", func() {
for k, v := range shouldUpgrade {
So(ShouldUpgrade(k, v), ShouldBeTrue)
}
})
})
Convey("Validate that version is ok", t, func() {
versions := []m.Version{
{Version: "1.1.1"},
{Version: "2.0.0"},
}
shouldNotUpgrade := map[string]m.Plugin{
"2.0.0": {Versions: versions},
"6.0.0": {Versions: versions},
}
Convey("should return error", func() {
for k, v := range shouldNotUpgrade {
So(ShouldUpgrade(k, v), ShouldBeFalse)
}
})
})
}

View File

@@ -0,0 +1,9 @@
package commands
import (
"errors"
)
func upgradeCommand(c CommandLine) error {
return errors.New("Not yet Implemented")
}

View File

@@ -0,0 +1,49 @@
package log
import (
"fmt"
)
var (
debugmode = false
)
func Debug(args ...interface{}) {
if debugmode {
fmt.Print(args...)
}
}
func Debugf(fmtString string, args ...interface{}) {
if debugmode {
fmt.Printf(fmtString, args...)
}
}
func Error(args ...interface{}) {
fmt.Print(args...)
}
func Errorf(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
}
func Info(args ...interface{}) {
fmt.Print(args...)
}
func Infof(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
}
func Warn(args ...interface{}) {
fmt.Print(args...)
}
func Warnf(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
}
func SetDebug(value bool) {
debugmode = value
}

View File

@@ -0,0 +1,69 @@
package main
import (
"fmt"
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/version"
"os"
"runtime"
)
func getGrafanaPluginPath() string {
os := runtime.GOOS
if os == "linux" {
return "/var/lib/grafana/plugins"
} else if os == "windows" {
return "C:\\opt\\grafana\\plugins" // :&
}
return "tmp_do/" //based on your OS!
}
func main() {
SetupLogging()
app := cli.NewApp()
app.Name = "Grafana cli"
app.Author = "raintank"
app.Email = "https://github.com/grafana/grafana"
app.Version = version.Version
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "path",
Usage: "path to the grafana installation",
Value: getGrafanaPluginPath(),
},
cli.BoolFlag{
Name: "debug, d",
Usage: "enable debug logging",
},
}
app.Commands = commands.Commands
app.CommandNotFound = cmdNotFound
if err := app.Run(os.Args); err != nil {
log.Errorf("%v", err)
}
}
func SetupLogging() {
for _, f := range os.Args {
if f == "-D" || f == "--debug" || f == "-debug" {
log.SetDebug(true)
}
}
}
func cmdNotFound(c *cli.Context, command string) {
fmt.Printf(
"%s: '%s' is not a %s command. See '%s --help'.\n",
c.App.Name,
command,
c.App.Name,
os.Args[0],
)
os.Exit(1)
}

View File

@@ -0,0 +1,48 @@
package models
import (
"os"
)
type InstalledPlugin struct {
Id string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Info PluginInfo `json:"info"`
Dependency Dependency `json:"dependencies"`
}
type Dependency struct {
GrafanaVersion string `json:"grafanaVersion"`
Plugins []Plugin `json:"plugins"`
}
type PluginInfo struct {
Version string `json:"version"`
Updated string `json:"updated"`
}
type Plugin struct {
Id string `json:"id"`
Category string `json:"category"`
Versions []Version `json:"versions"`
}
type Version struct {
Commit string `json:"commit"`
Url string `json:"url"`
Version string `json:"version"`
}
type PluginRepo struct {
Plugins []Plugin `json:"plugins"`
Version string `json:"version"`
}
type IoUtil interface {
Stat(path string) (os.FileInfo, error)
RemoveAll(path string) error
ReadDir(path string) ([]os.FileInfo, error)
ReadFile(filename string) ([]byte, error)
}

View File

@@ -0,0 +1,28 @@
package services
import (
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
"io/ioutil"
"os"
)
var IoUtil m.IoUtil = IoUtilImp{}
type IoUtilImp struct {
}
func (i IoUtilImp) Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}
func (i IoUtilImp) RemoveAll(path string) error {
return os.RemoveAll(path)
}
func (i IoUtilImp) ReadDir(path string) ([]os.FileInfo, error) {
return ioutil.ReadDir(path)
}
func (i IoUtilImp) ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}

View File

@@ -0,0 +1,70 @@
package services
import (
"encoding/json"
"errors"
"github.com/franela/goreq"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
"path"
)
var IoHelper m.IoUtil = IoUtilImp{}
func ListAllPlugins() (m.PluginRepo, error) {
res, _ := goreq.Request{Uri: "https://raw.githubusercontent.com/grafana/grafana-plugin-repository/master/repo.json"}.Do()
var resp m.PluginRepo
err := res.Body.FromJsonTo(&resp)
if err != nil {
return m.PluginRepo{}, errors.New("Could not load plugin data")
}
return resp, nil
}
func ReadPlugin(pluginDir, pluginName string) m.InstalledPlugin {
pluginDataPath := path.Join(pluginDir, pluginName, "plugin.json")
pluginData, _ := IoHelper.ReadFile(pluginDataPath)
res := m.InstalledPlugin{}
json.Unmarshal(pluginData, &res)
if res.Info.Version == "" {
res.Info.Version = "0.0.0"
}
if res.Id == "" {
res.Id = res.Name
}
return res
}
func GetLocalPlugins(pluginDir string) []m.InstalledPlugin {
result := make([]m.InstalledPlugin, 0)
files, _ := IoHelper.ReadDir(pluginDir)
for _, f := range files {
res := ReadPlugin(pluginDir, f.Name())
result = append(result, res)
}
return result
}
func RemoveInstalledPlugin(pluginPath, id string) error {
return IoHelper.RemoveAll(path.Join(pluginPath, id))
}
func GetPlugin(id string) (m.Plugin, error) {
resp, err := ListAllPlugins()
if err != nil {
}
for _, i := range resp.Plugins {
if i.Id == id {
return i, nil
}
}
return m.Plugin{}, errors.New("could not find plugin named \"" + id + "\"")
}

View File

@@ -0,0 +1,5 @@
package version
var (
Version = "0.0.2"
)