diff --git a/cmd/dkl-local-server/http.go b/cmd/dkl-local-server/http.go index 7bad9e3..7870167 100644 --- a/cmd/dkl-local-server/http.go +++ b/cmd/dkl-local-server/http.go @@ -13,7 +13,6 @@ import ( "path/filepath" "regexp" "strings" - "time" "novit.nc/direktil/pkg/localconfig" ) @@ -273,7 +272,7 @@ func uploadConfig(w http.ResponseWriter, r *http.Request) { } err = func() (err error) { - backupPath := filepath.Join(archivesPath, "config."+time.Now().Format(time.RFC3339)+".yaml.gz") + backupPath := filepath.Join(archivesPath, "config."+ulid()+".yaml.gz") bck, err := os.Create(backupPath) if err != nil { diff --git a/cmd/dkl-local-server/main.go b/cmd/dkl-local-server/main.go index a2278d1..a8d8234 100644 --- a/cmd/dkl-local-server/main.go +++ b/cmd/dkl-local-server/main.go @@ -6,7 +6,10 @@ import ( "net/http" "path/filepath" + restful "github.com/emicklei/go-restful" "novit.nc/direktil/pkg/cas" + + "novit.nc/direktil/local-server/pkg/apiutils" ) const ( @@ -30,16 +33,19 @@ func main() { } casStore = cas.NewDir(filepath.Join(*dataDir, "cache")) - go casCleaner() + apiutils.Setup(func() { + restful.Add(buildWS()) + }) + // by default, serve a host resource by its IP - http.HandleFunc("/", serveHostByIP) + //http.HandleFunc("/", serveHostByIP) http.HandleFunc("/configs", uploadConfig) http.HandleFunc("/hosts", serveHosts) - http.HandleFunc("/hosts/", serveHost) + //http.HandleFunc("/hosts/", serveHost) http.HandleFunc("/clusters", serveClusters) http.HandleFunc("/clusters/", serveCluster) diff --git a/cmd/dkl-local-server/ulid.go b/cmd/dkl-local-server/ulid.go new file mode 100644 index 0000000..7a8ec84 --- /dev/null +++ b/cmd/dkl-local-server/ulid.go @@ -0,0 +1,22 @@ +package main + +import ( + "io" + "math/rand" + "time" + + ulidp "github.com/oklog/ulid" +) + +var ( + ulidCtx struct{ entropy io.Reader } +) + +func initUlid() { + entropy := ulidp.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0) + ulidCtx.entropy = entropy +} + +func ulid() string { + return ulidp.MustNew(ulidp.Now(), ulidCtx.entropy).String() +} diff --git a/cmd/dkl-local-server/ws-host.go b/cmd/dkl-local-server/ws-host.go new file mode 100644 index 0000000..64a55b3 --- /dev/null +++ b/cmd/dkl-local-server/ws-host.go @@ -0,0 +1,55 @@ +package main + +import ( + "log" + "net/http" + "path" + + restful "github.com/emicklei/go-restful" +) + +type wsHost struct { + prefix string + getHost func(req *restful.Request) string +} + +func (ws *wsHost) register(rws *restful.WebService) { + for _, what := range []string{ + "boot.img", + "boot.img.gz", + "boot.img.lz4", + "boot.iso", + "boot.tar", + "config", + "initrd", + "ipxe", + "kernel", + } { + rws.Route(rws.GET(ws.prefix + "/" + what).To(ws.render)) + } +} + +func (ws *wsHost) render(req *restful.Request, resp *restful.Response) { + hostname := ws.getHost(req) + if hostname == "" { + http.NotFound(resp.ResponseWriter, req.Request) + return + } + + cfg, err := readConfig() + if err != nil { + writeError(resp.ResponseWriter, err) + return + } + + host := cfg.Host(hostname) + if host == nil { + log.Print("no host named ", hostname) + http.NotFound(resp.ResponseWriter, req.Request) + return + } + + what := path.Base(req.Request.URL.Path) + + renderHost(resp.ResponseWriter, req.Request, what, host, cfg) +} diff --git a/cmd/dkl-local-server/ws.go b/cmd/dkl-local-server/ws.go new file mode 100644 index 0000000..d9a13f8 --- /dev/null +++ b/cmd/dkl-local-server/ws.go @@ -0,0 +1,64 @@ +package main + +import ( + "log" + "net" + "strings" + + "github.com/emicklei/go-restful" +) + +func buildWS() *restful.WebService { + ws := &restful.WebService{} + + ws.Route(ws.POST("/configs").To(wsUploadConfig)) + + (&wsHost{ + prefix: "", + getHost: detectHost, + }).register(ws) + + (&wsHost{ + prefix: "/hosts/{hostname}", + getHost: func(req *restful.Request) string { + return req.PathParameter("hostname") + }, + }).register(ws) + + return ws +} + +func detectHost(req *restful.Request) string { + r := req.Request + remoteAddr := r.RemoteAddr + + if *trustXFF { + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + remoteAddr = strings.Split(xff, ",")[0] + } + } + + hostIP, _, err := net.SplitHostPort(remoteAddr) + + if err != nil { + hostIP = remoteAddr + } + + cfg, err := readConfig() + if err != nil { + return "" + } + + host := cfg.HostByIP(hostIP) + + if host == nil { + log.Print("no host found for IP ", hostIP) + return "" + } + + return host.Name +} + +func wsUploadConfig(req *restful.Request, res *restful.Response) { + // TODO +} diff --git a/go.mod b/go.mod index 32249da..8996d89 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,19 @@ require ( github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e github.com/coreos/etcd v3.3.11+incompatible // indirect + github.com/emicklei/go-restful v2.8.1+incompatible + github.com/emicklei/go-restful-openapi v1.0.0 + github.com/go-openapi/spec v0.18.0 // indirect github.com/gobuffalo/events v1.2.0 // indirect github.com/gobuffalo/meta v0.0.0-20190126124307-c8fb6f4eb5a9 // indirect github.com/gobuffalo/packr v1.21.9 // indirect github.com/gofrs/uuid v3.2.0+incompatible // indirect - github.com/google/certificate-transparency-go v1.0.21 // indirect + github.com/google/certificate-transparency-go v1.0.21 + github.com/oklog/ulid v1.3.1 github.com/pierrec/lz4 v2.0.5+incompatible github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0 // indirect - golang.org/x/crypto v0.0.0-20190129210102-ccddf3741a0c // indirect - golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect + golang.org/x/crypto v0.0.0-20190129210102-ccddf3741a0c + golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc // indirect golang.org/x/tools v0.0.0-20190130015043-a06a922acc1b // indirect gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index 0a5a2e5..ea01d73 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -24,10 +28,22 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/emicklei/go-restful v2.8.1+incompatible h1:AyDqLHbJ1quqbWr/OWDw+PlIP8ZFoTmYrGYaxzrLbNg= +github.com/emicklei/go-restful v2.8.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful-openapi v1.0.0 h1:ZFk3RuCl8ZmG1yUAF/mSbXRi5cuyA/k5+EpHayuuTXM= +github.com/emicklei/go-restful-openapi v1.0.0/go.mod h1:Q+bHVYfUWv1fvC4FNTsz2AVvFSsXAC7RCiWjF1Sva1A= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/spec v0.18.0 h1:aIjeyG5mo5/FrvDkpKKEGZPmF9MPHahS72mzfVqeQXQ= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY= @@ -333,6 +349,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c= @@ -368,6 +386,8 @@ github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= diff --git a/pkg/apiutils/apiutils.go b/pkg/apiutils/apiutils.go new file mode 100644 index 0000000..89cf9f2 --- /dev/null +++ b/pkg/apiutils/apiutils.go @@ -0,0 +1,50 @@ +package apiutils + +import ( + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" +) + +// Prepare does the default API preparation. +func Prepare() { + restful.DefaultRequestContentType(restful.MIME_JSON) + restful.DefaultResponseContentType(restful.MIME_JSON) + restful.DefaultContainer.Router(restful.CurlyRouter{}) +} + +// Setup does the default API setup +func Setup(addWebServices func()) { + Prepare() + + addWebServices() + + SetupHealth() + SetupOpenAPI() + SetupCORSWildcard() +} + +func SetupHealth() { + // TODO +} + +// SetupOpenAPI creates the standard API documentation endpoint at the default location. +func SetupOpenAPI() { + SetupOpenAPIAt("/swagger.json") +} + +// SetupOpenAPI creates the standard API documentation endpoint at the defined location. +func SetupOpenAPIAt(apiPath string) { + config := restfulspec.Config{ + WebServices: restful.RegisteredWebServices(), + APIPath: apiPath, + } + restful.Add(restfulspec.NewOpenAPIService(config)) +} + +func SetupCORSWildcard() { + restful.Filter(restful.CrossOriginResourceSharing{ + CookiesAllowed: true, + Container: restful.DefaultContainer, + }.Filter) + restful.Filter(restful.OPTIONSFilter()) +}