summaryrefslogtreecommitdiff
path: root/cmd/blubberoid/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/blubberoid/main.go')
-rw-r--r--cmd/blubberoid/main.go119
1 files changed, 119 insertions, 0 deletions
diff --git a/cmd/blubberoid/main.go b/cmd/blubberoid/main.go
new file mode 100644
index 0000000..b15f603
--- /dev/null
+++ b/cmd/blubberoid/main.go
@@ -0,0 +1,119 @@
+// Package main provides the blubberoid server.
+//
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "path"
+
+ "github.com/pborman/getopt/v2"
+
+ "gerrit.wikimedia.org/r/blubber/config"
+ "gerrit.wikimedia.org/r/blubber/docker"
+)
+
+var (
+ showHelp *bool = getopt.BoolLong("help", 'h', "show help/usage")
+ address *string = getopt.StringLong("address", 'a', ":8748", "socket address/port to listen on (default ':8748')", "address:port")
+ endpoint *string = getopt.StringLong("endpoint", 'e', "/", "server endpoint (default '/')", "path")
+ policyURI *string = getopt.StringLong("policy", 'p', "", "policy file URI", "uri")
+ policy *config.Policy
+)
+
+func main() {
+ getopt.Parse()
+
+ if *showHelp {
+ getopt.Usage()
+ os.Exit(1)
+ }
+
+ if *policyURI != "" {
+ var err error
+
+ policy, err = config.ReadPolicyFromURI(*policyURI)
+
+ if err != nil {
+ log.Fatalf("Error loading policy from %s: %v\n", *policyURI, err)
+ }
+ }
+
+ // Ensure endpoint is always an absolute path starting and ending with "/"
+ *endpoint = path.Clean("/" + *endpoint)
+
+ if *endpoint != "/" {
+ *endpoint += "/"
+ }
+
+ log.Printf("listening on %s for requests to %s[variant]\n", *address, *endpoint)
+
+ http.HandleFunc(*endpoint, blubberoid)
+ log.Fatal(http.ListenAndServe(*address, nil))
+}
+
+func blubberoid(res http.ResponseWriter, req *http.Request) {
+ if len(req.URL.Path) <= len(*endpoint) {
+ res.WriteHeader(http.StatusNotFound)
+ res.Write(responseBody("request a variant at %s[variant]", *endpoint))
+ return
+ }
+
+ variant := req.URL.Path[len(*endpoint):]
+ body, err := ioutil.ReadAll(req.Body)
+
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ log.Printf("failed to read request body: %s\n", err)
+ return
+ }
+
+ cfg, err := config.ReadConfig(body)
+
+ if err != nil {
+ if config.IsValidationError(err) {
+ res.WriteHeader(http.StatusUnprocessableEntity)
+ res.Write(responseBody(config.HumanizeValidationError(err)))
+ return
+ }
+
+ res.WriteHeader(http.StatusBadRequest)
+ res.Write(responseBody(
+ "Failed to read config YAML from request body. "+
+ "Was it formatted correctly and encoded as binary data?\nerror: %s",
+ err.Error(),
+ ))
+ return
+ }
+
+ if policy != nil {
+ err = policy.Validate(*cfg)
+
+ if err != nil {
+ res.WriteHeader(http.StatusUnprocessableEntity)
+ res.Write(responseBody(
+ "Configuration fails policy check against:\npolicy: %s\nviolation: %v",
+ *policyURI, err,
+ ))
+ return
+ }
+ }
+
+ dockerFile, err := docker.Compile(cfg, variant)
+
+ if err != nil {
+ res.WriteHeader(http.StatusNotFound)
+ res.Write(responseBody(err.Error()))
+ return
+ }
+
+ res.Header().Set("Content-Type", "text/plain")
+ res.Write(dockerFile.Bytes())
+}
+
+func responseBody(msg string, a ...interface{}) []byte {
+ return []byte(fmt.Sprintf(msg+"\n", a...))
+}