diff --git a/Makefile b/Makefile index 49da0702..7a856284 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ build-aci: ./acifile run-docker: - docker run -p 8080:8080 --name=bcs -v $(shell echo $$PWD)/static:/static dghubble/bcs:latest + docker run -p 8080:8080 --name=bcs --rm -v $(shell echo $$PWD)/static:/static dghubble/bcs:latest run-rkt: rkt --insecure-options=image run --no-overlay bin/bcs-0.0.1-linux-amd64.aci diff --git a/server/boot_provider.go b/api/boot_provider.go similarity index 78% rename from server/boot_provider.go rename to api/boot_provider.go index 3cd15a49..c79eaa8f 100644 --- a/server/boot_provider.go +++ b/api/boot_provider.go @@ -1,19 +1,12 @@ -package server +package api import ( "fmt" ) -// CoreOSBootConfig references a CoreOS pproduction image and initrd. -var CoreOSBootConfig = &BootConfig{ - Kernel: "/static/coreos_production_pxe.vmlinuz", - Initrd: []string{"/static/coreos_production_pxe_image.cpio.gz"}, - Cmdline: map[string]interface{}{}, -} - // BootConfig describes the boot configuration of a host. type BootConfig struct { - // the URL of the kernel boot image + // the URL of the kernel boot image Kernel string `json:"kernel"` // the initrd URLs which will be flattened into a single filesystem Initrd []string `json:"initrd"` @@ -21,7 +14,7 @@ type BootConfig struct { Cmdline map[string]interface{} `json:"cmdline"` } -// A BootConfigProvider provides a mapping from MAC addresses to BootConfigs. +// A BootConfigProvider provides a mapping from MAC addresses to BootConfigs. type BootConfigProvider interface { Add(addr string, config *BootConfig) Get(addr string) (*BootConfig, error) @@ -53,4 +46,3 @@ func (p *bootConfigProvider) Get(addr string) (*BootConfig, error) { } return nil, fmt.Errorf("no boot config for %s", addr) } - diff --git a/api/ipxe.go b/api/ipxe.go new file mode 100644 index 00000000..cecfbcd0 --- /dev/null +++ b/api/ipxe.go @@ -0,0 +1,58 @@ +package api + +import ( + "bytes" + "fmt" + "net/http" + "text/template" +) + +const ipxeBootstrap = `#!ipxe +chain config?uuid=${uuid} +` + +var ipxeTemplate = template.Must(template.New("ipxe boot").Parse(`#!ipxe +kernel {{.Kernel}} cloud-config-url=cloud/config?uuid=${uuid} {{range $key, $value := .Cmdline}} {{if $value}}{{$key}}={{$value}}{{else}}{{$key}}{{end}}{{end}} +initrd {{ range $element := .Initrd }} {{$element}}{{end}} +boot +`)) + +// ipxeMux handles iPXE requests for boot (config) scripts. +func ipxeMux(bootConfigs BootConfigProvider) http.Handler { + mux := http.NewServeMux() + mux.Handle("/ipxe/boot.ipxe", ipxeInspect()) + mux.Handle("/ipxe/config", ipxeBoot(bootConfigs)) + return mux +} + +// ipxeInspect returns a handler that responds with an iPXE script to gather +// client machine data and chain load the real boot script. +func ipxeInspect() http.Handler { + fn := func(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, ipxeBootstrap) + } + return http.HandlerFunc(fn) +} + +// ipxeBoot returns a handler which renders an iPXE boot config script based +// on the machine attribtue query parameters. +func ipxeBoot(bootConfigs BootConfigProvider) http.Handler { + fn := func(w http.ResponseWriter, req *http.Request) { + params := req.URL.Query() + UUID := params.Get("uuid") + bootConfig, err := bootConfigs.Get(UUID) + if err != nil { + http.Error(w, err.Error(), 404) + return + } + + var buf bytes.Buffer + err = ipxeTemplate.Execute(&buf, bootConfig) + if err != nil { + http.Error(w, err.Error(), 404) + return + } + buf.WriteTo(w) + } + return http.HandlerFunc(fn) +} diff --git a/api/pixiecore.go b/api/pixiecore.go new file mode 100644 index 00000000..0bff9243 --- /dev/null +++ b/api/pixiecore.go @@ -0,0 +1,24 @@ +package api + +import ( + "encoding/json" + "log" + "net/http" + "strings" +) + +const pixiecorePath = "/v1/boot/" + +// pixiecoreHandler implements the Pixiecore API Server Spec. +func pixiecoreHandler(bootConfigs BootConfigProvider) http.Handler { + fn := func(w http.ResponseWriter, req *http.Request) { + remainder := strings.TrimPrefix(req.URL.String(), pixiecorePath) + bootConfig, err := bootConfigs.Get(remainder) + if err != nil { + http.Error(w, err.Error(), 404) + return + } + json.NewEncoder(w).Encode(bootConfig) + } + return http.HandlerFunc(fn) +} diff --git a/api/server.go b/api/server.go new file mode 100644 index 00000000..4def4f45 --- /dev/null +++ b/api/server.go @@ -0,0 +1,29 @@ +package api + +import ( + "net/http" +) + +// Server serves iPXE/Pixiecore boot configs and hosts images. +type Server struct { + bootConfigs BootConfigProvider +} + +// NewServer returns a new Server which uses the given BootConfigProvider. +func NewServer(bootConfigs BootConfigProvider) *Server { + return &Server{ + bootConfigs: bootConfigs, + } +} + +// HTTPHandler returns a HTTP handler for the server. +func (s *Server) HTTPHandler() http.Handler { + mux := http.NewServeMux() + // iPXE + mux.Handle("/ipxe/", ipxeMux(s.bootConfigs)) + // Pixiecore API Server + mux.Handle(pixiecorePath, pixiecoreHandler(s.bootConfigs)) + // Kernel and Initrd Images + mux.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.Dir("static")))) + return mux +} diff --git a/cmd/main.go b/cmd/main.go index 2acba954..47f87f33 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,19 +4,47 @@ import ( "log" "net/http" - "github.com/coreos/coreos-baremetal/server" + "github.com/coreos/coreos-baremetal/api" ) const address = ":8080" +// Example Boot Configs + +var CoreOSStable = &api.BootConfig{ + Kernel: "http://stable.release.core-os.net/amd64-usr/current/coreos_production_pxe.vmlinuz", + Initrd: []string{"http://stable.release.core-os.net/amd64-usr/current/coreos_production_pxe_image.cpio.gz"}, + Cmdline: map[string]interface{}{}, +} + +var CoreOSBeta = &api.BootConfig{ + Kernel: "http://beta.release.core-os.net/amd64-usr/current/coreos_production_pxe.vmlinuz", + Initrd: []string{"http://beta.release.core-os.net/amd64-usr/current/coreos_production_pxe_image.cpio.gz"}, + Cmdline: map[string]interface{}{}, +} + +var CoreOSAlpha = &api.BootConfig{ + Kernel: "http://alpha.release.core-os.net/amd64-usr/current/coreos_production_pxe.vmlinuz", + Initrd: []string{"http://alpha.release.core-os.net/amd64-usr/current/coreos_production_pxe_image.cpio.gz"}, + Cmdline: map[string]interface{}{}, +} + +var CoreOSLocal = &api.BootConfig{ + Kernel: "/images/kernel/coreos_production_pxe.vmlinuz", + Initrd: []string{"/images/initrd/coreos_production_pxe_image.cpio.gz"}, + Cmdline: map[string]interface{}{}, +} + func main() { - bootConfigProvider := server.NewBootConfigProvider() - bootConfigProvider.Add(server.DefaultAddr, server.CoreOSBootConfig) - srv := server.NewServer(bootConfigProvider) - h := srv.HTTPHandler() + // initial boot configs + bootConfigs := api.NewBootConfigProvider() + bootConfigs.Add(api.DefaultAddr, CoreOSStable) + // api server + server := api.NewServer(bootConfigs) + h := server.HTTPHandler() log.Printf("Starting boot config server") err := http.ListenAndServe(address, h) if err != nil { log.Fatal("ListenAndServe: ", err) } -} \ No newline at end of file +} diff --git a/server/server.go b/server/server.go deleted file mode 100644 index c4def352..00000000 --- a/server/server.go +++ /dev/null @@ -1,45 +0,0 @@ -package server - -import ( - "net/http" - "encoding/json" - "log" - "strings" -) - -// Server manages boot and cloud configs for hosts by MAC address or UUID. -type Server struct { - bootConfigProvider BootConfigProvider -} - -// NewServer returns a new Server which uses the given BootConfigProvider. -func NewServer(bootConfigProvider BootConfigProvider) *Server { - return &Server{ - bootConfigProvider: bootConfigProvider, - } -} - -// HTTPHandler returns a HTTP handler for the server. -func (s *Server) HTTPHandler() http.Handler { - mux := http.NewServeMux() - // Pixiecore API Server - mux.Handle("/v1/boot/", pixiecoreHandler(s.bootConfigProvider)) - // Kernel and File Server - mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) - return mux -} - -// pixiecoreHandler implements the Pixiecore API Server Spec. -func pixiecoreHandler(bootConfigs BootConfigProvider) http.Handler { - fn := func(w http.ResponseWriter, req *http.Request) { - remainder := strings.TrimPrefix(req.URL.String(), "/v1/boot/") - log.Printf("render boot config for %s", remainder) - bootConfig, err := bootConfigs.Get(remainder) - if err != nil { - http.Error(w, err.Error(), 404) - return - } - json.NewEncoder(w).Encode(bootConfig) - } - return http.HandlerFunc(fn) -}