diff --git a/Makefile b/Makefile index 627b1b8..7f9e03e 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ else LONG_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') endif -LDFLAGS := $(LDFLAGS) -X "main.Version=$(LONG_VERSION)" +LDFLAGS := $(LDFLAGS) -X "gitea.com/jolheiser/gpm/modules/config.Version=$(LONG_VERSION)" .PHONY: build build: diff --git a/README.md b/README.md index 77f7cd2..3b27702 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,19 @@ Using either a GPM server or local config, I can instead `gpm get cli` which fin * `add` - Add a local package * `remove` - Remove a local package * `list` - List local packages -* `config` - Change local configuration (GPM server) +* `config` - Change local configuration * `export` - Export local packages to JSON -* `import` - Import JSON to local packages -* `get` - Get a list of packages -e.g. `gpm get beaver survey toml homedir cli` to get all the modules needed for gpm itself (assuming the map resolves to the same packages) +* `import` - Import JSON to local packages. Either give a path to a `.json` file, or a URL to a GPM server export endpoint + * e.g. `https://gpm.jolheiser.com/export` +* `get` - Get a list of packages + * e.g. `gpm get beaver survey toml homedir cli` to get all the modules needed for gpm itself (assuming the map resolves to the same packages) +* `server` - Start a gpm server ### Server -If GPM doesn't find a package locally, it can call out to a configurable [gpm server](https://gitea.com/jolheiser/gpm-server) to find a package there. +If GPM doesn't find a package locally, it can call out to a configurable gpm server to find a package there instead. This makes it much simpler to have a central library of packages rather than exporting and importing between environments. -The `import` and `export` commands should work between the CLI and server for easy transitions. +Want to run your own server? It's very easy! This CLI comes packaged with the server inside, simply run `gpm server` to start up a GPM server. +Put it behind your favorite reverse proxy and it's ready to go! diff --git a/cmd/add.go b/cmd/add.go index 92c8bb4..f4f2344 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -1,8 +1,8 @@ package cmd import ( - "gitea.com/gpm/gpm/modules/config" "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/gpm/modules/config" "github.com/AlecAivazis/survey/v2" "github.com/urfave/cli/v2" "regexp" diff --git a/cmd/cmd.go b/cmd/cmd.go index fc333ba..f201987 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,7 +1,7 @@ package cmd import ( - "gitea.com/gpm/gpm/modules/config" + "gitea.com/jolheiser/gpm/modules/config" "github.com/urfave/cli/v2" ) diff --git a/cmd/config.go b/cmd/config.go index 99ea319..f41cf7a 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -1,8 +1,8 @@ package cmd import ( - "gitea.com/gpm/gpm/modules/config" "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/gpm/modules/config" "github.com/AlecAivazis/survey/v2" "github.com/urfave/cli/v2" ) @@ -17,7 +17,7 @@ var Config = cli.Command{ func doConfig(ctx *cli.Context) error { urlQuestion := &survey.Input{ Message: "gpm URL", - Default: "gpm.jolheiser.com", + Default: config.GPMURL, } var urlAnswer string diff --git a/cmd/export.go b/cmd/export.go index 54968d0..8c1ab7c 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -2,7 +2,7 @@ package cmd import ( "fmt" - "gitea.com/gpm/gpm/modules/config" + "gitea.com/jolheiser/gpm/modules/config" "github.com/urfave/cli/v2" ) diff --git a/cmd/get.go b/cmd/get.go index 33db441..fe02696 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -3,8 +3,8 @@ package cmd import ( "encoding/json" "fmt" - "gitea.com/gpm/gpm/modules/config" "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/gpm/modules/config" "github.com/AlecAivazis/survey/v2" "github.com/urfave/cli/v2" "io/ioutil" @@ -49,7 +49,7 @@ func doGet(ctx *cli.Context) error { for _, pkg := range pkgs { var url string if u, ok := local[pkg]; ok && !ctx.Bool("ignore-local") { - url = u + url = u.Import } else if !ctx.Bool("offline") { u, err := queryServer(ctx.String("url"), pkg) if err != nil { @@ -73,12 +73,8 @@ func doGet(ctx *cli.Context) error { return nil } -type response struct { - URL string `json:"url"` -} - func queryServer(server, name string) (string, error) { - endpoint := fmt.Sprintf("%s/%s", server, name) + endpoint := fmt.Sprintf("%s/package/%s", server, name) resp, err := http.Get(endpoint) if err != nil { return "", fmt.Errorf("could not query server at `%s`", endpoint) @@ -94,12 +90,12 @@ func queryServer(server, name string) (string, error) { } defer resp.Body.Close() - var data response - if err := json.Unmarshal(body, &data); err != nil { + var pkg config.Package + if err := json.Unmarshal(body, &pkg); err != nil { return "", err } - return data.URL, nil + return pkg.Import, nil } func goGet(url string) error { diff --git a/cmd/import.go b/cmd/import.go index 6947487..965a0cd 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -3,10 +3,12 @@ package cmd import ( "encoding/json" "errors" - "gitea.com/gpm/gpm/modules/config" "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/gpm/modules/config" "github.com/urfave/cli/v2" "io/ioutil" + "net/http" + "strings" ) var Import = cli.Command{ @@ -23,26 +25,45 @@ var Import = cli.Command{ } func doImport(ctx *cli.Context) error { - files := ctx.Args().Slice() - if len(files) == 0 { - return errors.New("must provide a list of JSON files to import from") + if ctx.NArg() == 0 { + return errors.New("must point to either a JSON file or gpm server export endpoint") } - for _, file := range files { - body, err := ioutil.ReadFile(file) + arg := ctx.Args().First() + isJSON := strings.HasSuffix(arg, ".json") + isHTTP := strings.HasPrefix(arg, "http") + + if !isJSON && !isHTTP { + return errors.New("must point to either a JSON file or gpm server export endpoint") + } + + var data []byte + var err error + if isJSON { + data, err = ioutil.ReadFile(arg) if err != nil { - beaver.Error(err) - continue + return err + } + } else if isHTTP { + resp, err := http.Get(arg) + if err != nil { + return err } - var cfg config.Config - if err := json.Unmarshal(body, &cfg); err != nil { - beaver.Error(err) - continue + data, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err } - config.AddPackages(ctx.Bool("force"), cfg.Packages...) + defer resp.Body.Close() + } + var cfg config.Config + if err := json.Unmarshal(data, &cfg); err != nil { + return err + } + config.AddPackages(ctx.Bool("force"), cfg.Packages...) + if err := config.Save(); err != nil { return err } diff --git a/cmd/list.go b/cmd/list.go index 047cf83..ca3bc06 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -1,8 +1,8 @@ package cmd import ( - "gitea.com/gpm/gpm/modules/config" "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/gpm/modules/config" "github.com/urfave/cli/v2" ) diff --git a/cmd/remove.go b/cmd/remove.go index 4312744..80b5b93 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -2,8 +2,8 @@ package cmd import ( "fmt" - "gitea.com/gpm/gpm/modules/config" "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/gpm/modules/config" "github.com/AlecAivazis/survey/v2" "github.com/urfave/cli/v2" "strings" diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..cd3d660 --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "fmt" + "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/gpm/modules/router" + "github.com/urfave/cli/v2" + "net/http" +) + +var Server = cli.Command{ + Name: "server", + Usage: "Start the gpm server", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "port", + Aliases: []string{"p"}, + Usage: "Port to run the gpm server on", + Value: "3333", + }, + }, + Action: doServer, +} + +func doServer(ctx *cli.Context) error { + beaver.Infof("Running gpm server at http://localhost:%s", ctx.String("port")) + if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), router.Init()); err != nil { + return err + } + return nil +} diff --git a/go.mod b/go.mod index f1c8545..768eaf3 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module gitea.com/gpm/gpm +module gitea.com/jolheiser/gpm go 1.13 @@ -6,6 +6,7 @@ require ( gitea.com/jolheiser/beaver v1.0.0 github.com/AlecAivazis/survey/v2 v2.0.5 github.com/BurntSushi/toml v0.3.1 + github.com/go-chi/chi v4.0.3+incompatible github.com/mitchellh/go-homedir v1.1.0 github.com/urfave/cli/v2 v2.1.1 ) diff --git a/go.sum b/go.sum index 5bf4c37..e9ee2c5 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSY github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= +github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= diff --git a/main.go b/main.go index 3dae3a3..0e865a4 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,13 @@ package main import ( - "gitea.com/gpm/gpm/cmd" "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/gpm/cmd" + "gitea.com/jolheiser/gpm/modules/config" "github.com/urfave/cli/v2" "os" ) -var Version = "develop" - func main() { // config loads on init @@ -16,7 +15,7 @@ func main() { app := cli.NewApp() app.Name = "gpm" app.Usage = "Go Package Manager" - app.Version = Version + app.Version = config.Version app.Commands = []*cli.Command{ &cmd.Add, &cmd.Remove, @@ -25,6 +24,7 @@ func main() { &cmd.Import, &cmd.Export, &cmd.Config, + &cmd.Server, } app.Flags = cmd.Flags app.EnableBashCompletion = true diff --git a/modules/config/config.go b/modules/config/config.go index 394eb9b..2d335b2 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -15,6 +15,7 @@ import ( var ( configPath string cfg *Config + Version = "develop" // Config items @@ -42,16 +43,16 @@ func init() { if _, err := os.Stat(configPath); os.IsNotExist(err) { if err := os.MkdirAll(path.Dir(configPath), os.ModePerm); err != nil { - beaver.Fatalf("could not create Sip home: %v", err) + beaver.Fatalf("could not create gpm home: %v", err) } if _, err := os.Create(configPath); err != nil { - beaver.Fatalf("could not create Sip config: %v", err) + beaver.Fatalf("could not create gpm config: %v", err) } } if _, err := toml.DecodeFile(configPath, &cfg); err != nil { - beaver.Fatalf("could not decode Sip config: %v", err) + beaver.Fatalf("could not decode gpm config: %v", err) } dupe := make(map[string]bool) @@ -85,14 +86,14 @@ func Save() error { } func Export() (string, error) { - data, err := json.Marshal(cfg) + data, err := json.Marshal(Packages) return string(data), err } -func PackageMap() map[string]string { - pkgs := make(map[string]string) +func PackageMap() map[string]Package { + pkgs := make(map[string]Package) for _, pkg := range Packages { - pkgs[pkg.Name] = pkg.Import + pkgs[pkg.Name] = pkg } return pkgs } diff --git a/modules/router/router.go b/modules/router/router.go new file mode 100644 index 0000000..82cd122 --- /dev/null +++ b/modules/router/router.go @@ -0,0 +1,70 @@ +package router + +import ( + "encoding/json" + "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/gpm/modules/config" + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" + "net/http" + "time" +) + +var cache map[string]config.Package + +func Init() *chi.Mux { + r := chi.NewRouter() + r.Use(middleware.RedirectSlashes) + r.Use(middleware.Recoverer) + r.Use(middleware.Timeout(30 * time.Second)) + + r.Get("/status", handleStatus) + r.Get("/export", handleExport) + r.Get("/package/{name}", handlePackage) + + cache = config.PackageMap() + + return r +} + +func handleStatus(res http.ResponseWriter, _ *http.Request) { + status, err := json.Marshal(map[string]interface{}{ + "version": config.Version, + "packages": len(config.Packages), + }) + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + _, _ = res.Write([]byte("{}")) + return + } + + _, _ = res.Write(status) +} + +func handleExport(res http.ResponseWriter, _ *http.Request) { + export, err := config.Export() + if err != nil { + beaver.Error(err) + return + } + + _, _ = res.Write([]byte(export)) +} + +func handlePackage(res http.ResponseWriter, req *http.Request) { + name := chi.URLParam(req, "name") + + if pkg, ok := cache[name]; ok { + data, err := json.Marshal(pkg) + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + _, _ = res.Write([]byte("{}")) + return + } + _, _ = res.Write(data) + return + } + + res.WriteHeader(http.StatusNotFound) + _, _ = res.Write([]byte("{}")) +}