// Run server. func (c *config) Run() error { session = c.session log.Infof("Bind to: %s", c.bind) log.Infof("Using server URI: %s", c.serverURI) logr := handlers.LoggingHandler(os.Stderr, c.router) return http.ListenAndServe(c.bind, logr) }
// deleteDoc delete document. func (c *config) deleteDoc(endpoint, path string) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var newPath bytes.Buffer err := templ.ExecuteTemplate(&newPath, endpoint, mux.Vars(r)) if err != nil { log.Fatal(err.Error()) } log.Infof("etcd path: %s", newPath.String()) data, code, err := c.session.Get(newPath.String(), false, "") if err != nil { c.writeError(w, r, err, code) return } if code, err := c.session.Delete(newPath.String()); err != nil { c.writeError(w, r, err, code) return } c.write(w, r, data) } }
// getDoc get document. func (c *config) getDoc(endpoint, path string, collection bool, dirName string) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var newPath bytes.Buffer table := false if collection == true && strings.ToLower(r.URL.Query().Get("table")) == "true" { table = true } err := templ.ExecuteTemplate(&newPath, endpoint, mux.Vars(r)) if err != nil { log.Fatal(err.Error()) } log.Infof("etcd path: %s", newPath.String()) doc, code, err := c.session.Get(newPath.String(), table, dirName) if err != nil { c.writeError(w, r, err, code) return } c.write(w, r, doc) } }
// RouteEtcd add route for etcd. func (c *config) RouteEtcd(collection, collectionPath, resource, resourcePath, schema, dirName string) { log.Infof("Add collection: %s collection path: %s", collection, collectionPath) log.Infof("Add resource: %s resource path: %s schema: %s", resource, resourcePath, schema) if templ == nil { templ = template.Must(template.New(collection).Parse(collectionPath)) } else { template.Must(templ.New(collection).Parse(collectionPath)) } template.Must(templ.New(resource).Parse(resourcePath)) c.router.HandleFunc(collection, c.getDoc(collection, collectionPath, true, dirName)).Methods("GET") c.router.HandleFunc(resource, c.getDoc(resource, resourcePath, false, dirName)).Methods("GET") c.router.HandleFunc(resource, c.putOrPatchDoc(resource, resourcePath, schema)).Methods("PUT") c.router.HandleFunc(resource, c.putOrPatchDoc(resource, resourcePath, schema)).Methods("PATCH") c.router.HandleFunc(resource, c.deleteDoc(resource, resourcePath)).Methods("DELETE") }
// RouteTempl add route for Go Text Template. func (c *config) RouteTemplate(endpoint, templ string) { if templates == nil { templates = template.Must(template.New("main").Funcs(funcs).ParseGlob(c.templDir + "/*.tmpl")) vm = otto.New() } url := endpoint log.Infof("Add endpoint: %s template: %s", url, templ) c.router.HandleFunc(url, c.getTemplate(templ)).Methods("GET") }
func (c *config) Connect() (Session, error) { log.Infof("Connect to etcd peers: %s", c.peers) cl, err := c.newClient() if err != nil { return nil, err } return &session{ client: cl, keysAPI: client.NewKeysAPI(cl), }, nil }
func (c *config) validateDoc(doc []byte, path string, schema string) (int, []error) { // Prepare document and JSON schema. docLoader := gojsonschema.NewStringLoader(string(doc)) log.Infof("Using schema URI: %s/%s", c.schemaURI, schema) schemaLoader := gojsonschema.NewReferenceLoader(c.schemaURI + "/" + schema) // Validate document using JSON schema. res, err := gojsonschema.Validate(schemaLoader, docLoader) if err != nil { return http.StatusInternalServerError, []error{err} } if !res.Valid() { var errors []error for _, e := range res.Errors() { errors = append(errors, fmt.Errorf("%s: %s", strings.Replace(e.Context().String("/"), "(root)", path, 1), e.Description())) } return http.StatusBadRequest, errors } return http.StatusOK, nil }
func (cfg *Config) Load(c *cli.Context) { // Enable debug. if c.GlobalBool("debug") { log.SetDebug() } // Default path for config file. u, _ := user.Current() cfgs := []string{ u.HomeDir + "/.etcdrest.json", u.HomeDir + "/.etcdrest.yaml", u.HomeDir + "/.etcdrest.yml", u.HomeDir + "/.etcdrest.toml", u.HomeDir + "/.etcdrest.tml", "/etc/etcdrest.json", "/etc/etcdrest.yaml", "/etc/etcdrest.yml", "/etc/etcdrest.toml", "/etc/etcdrest.tml", "/app/etc/etcdrest.json", "/app/etc/etcdrest.yaml", "/app/etc/etcdrest.yml", "/app/etc/etcdrest.toml", "/app/etc/etcdrest.tml", } // Check if we have an arg. for config file and that it exist's. if c.GlobalString("config") != "" { if _, err := os.Stat(c.GlobalString("config")); os.IsNotExist(err) { log.Fatalf("Config file doesn't exist: %s", c.GlobalString("config")) } cfgs = append([]string{c.GlobalString("config")}, cfgs...) } for _, fn := range cfgs { if _, err := os.Stat(fn); os.IsNotExist(err) { continue } log.Infof("Using config file: %s", fn) // Load config file. b, err := ioutil.ReadFile(fn) if err != nil { log.Fatal(err.Error()) } switch filepath.Ext(fn) { case ".json": if err := json.Unmarshal(b, cfg); err != nil { log.Fatal(err.Error()) } case ".yaml", ".yml": if err := yaml.Unmarshal(b, cfg); err != nil { log.Fatal(err.Error()) } case ".toml", ".tml": if err := toml.Unmarshal(b, cfg); err != nil { log.Fatal(err.Error()) } default: log.Fatal("unsupported data format") } // Validate config using JSON schema. break } // Override configuration. if c.GlobalString("templ-dir") != "" { cfg.TemplDir = c.GlobalString("templ-dir") } if c.GlobalString("schema-uri") != "" { cfg.SchemaURI = c.GlobalString("schema-uri") } if c.GlobalString("bind") != "" { cfg.Bind = c.GlobalString("bind") } if c.GlobalString("server-uri") != "" { cfg.ServerURI = c.GlobalString("server-uri") } if c.GlobalBool("envelope") { cfg.Envelope = true } if c.GlobalBool("no-indent") { cfg.Indent = true } // Override etcd configuration. if c.GlobalString("peers") != "" { cfg.Etcd.Peers = c.GlobalString("peers") } if c.GlobalString("cert") != "" { cfg.Etcd.Cert = c.GlobalString("cert") } if c.GlobalString("key") != "" { cfg.Etcd.Key = c.GlobalString("key") } if c.GlobalString("ca") != "" { cfg.Etcd.CA = c.GlobalString("ca") } if c.GlobalString("user") != "" { cfg.Etcd.User = c.GlobalString("user") } if c.GlobalDuration("timeout") != 0 { cfg.Etcd.Timeout = c.GlobalDuration("timeout") } if c.GlobalDuration("command-timeout") != 0 { cfg.Etcd.CmdTimeout = c.GlobalDuration("command-timeout") } }
// RouteStatic add route for file system path. func (c *config) RouteStatic(endpoint, path string) { log.Infof("Add endpoint: %s path: %s", endpoint, path) c.router.PathPrefix(endpoint + "/").Handler(http.StripPrefix(endpoint+"/", http.FileServer(http.Dir(path)))) http.Handle(endpoint+"/", c.router) }
// putOrPatchDoc put or patch document. func (c *config) putOrPatchDoc(endpoint, path, schema string) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var newPath bytes.Buffer err := templ.ExecuteTemplate(&newPath, endpoint, mux.Vars(r)) if err != nil { log.Fatal(err.Error()) } log.Infof("etcd path: %s", newPath.String()) // Get request body. body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576)) if err != nil { c.writeError(w, r, err, http.StatusInternalServerError) return } if err := r.Body.Close(); err != nil { c.writeError(w, r, err, http.StatusInternalServerError) return } // Patch document using JSON patch RFC 6902. var doc []byte if r.Method == "PATCH" { data, code, err := c.session.Get(newPath.String(), false, "") if err != nil { c.writeError(w, r, err, code) return } origDoc, err := json.Marshal(&data) if err != nil { c.writeError(w, r, err, http.StatusInternalServerError) return } doc, err = c.patchDoc(origDoc, body) if err != nil { c.writeError(w, r, err, code) return } } else { doc = body } // Validate document using JSON schema if code, errors := c.validateDoc(doc, newPath.String(), schema); errors != nil { c.writeErrors(w, r, errors, code) return } var data interface{} if err := json.Unmarshal(doc, &data); err != nil { c.writeError(w, r, err, http.StatusInternalServerError) return } // Create document. if code, err := c.session.Put(newPath.String(), data); err != nil { c.writeError(w, r, err, code) return } c.write(w, r, data) } }