diff options
Diffstat (limited to 'cmd/blubberoid/main.go')
-rw-r--r-- | cmd/blubberoid/main.go | 119 |
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...)) +} |