summaryrefslogtreecommitdiff
path: root/cmd/blubberoid/main.go
diff options
context:
space:
mode:
authorDan Duvall <dduvall@wikimedia.org>2018-09-19 11:06:46 -0700
committerDan Duvall <dduvall@wikimedia.org>2018-12-12 15:02:22 -0800
commit4df63a83c93ba0f591eb78110e184bfef0f76535 (patch)
tree9a0c97f54432cf7ecb30ff910d5f514b51f02ed9 /cmd/blubberoid/main.go
parent3da9f201cad3b09b4e7d70dcaf023c70b4bcc026 (diff)
downloadblubber-4df63a83c93ba0f591eb78110e184bfef0f76535.tar.gz
Provide OpenAPI spec for Blubberoid
Wrote an OpenAPI 3.0 spec for Blubberoid that provides `x-amples` entries compatible with service-checker. The written spec includes basic schema for Blubber config objects that may be later factored out for use in validation. Note that OpenAPI 3.0 supports only the v4 draft of the JSON Schema standard, so some parts of the configuration could not be fully described. Specifically, v4 does not include the `patternProperties` definition introduced in the JSON Schema v6 draft that would allow us to describe `variants` and `runs.environment` and everything beneath. Blubberoid was refactored slightly to incorporate the new spec as well as assume JSON as the canonical and default configuration format. It was also refactored to include a versioned namespace ("v1") after the server endpoint. Bug: T205920 Change-Id: I28a341aa503b8920d802715660d4c4d62be45475
Diffstat (limited to 'cmd/blubberoid/main.go')
-rw-r--r--cmd/blubberoid/main.go90
1 files changed, 67 insertions, 23 deletions
diff --git a/cmd/blubberoid/main.go b/cmd/blubberoid/main.go
index d436390..19604c1 100644
--- a/cmd/blubberoid/main.go
+++ b/cmd/blubberoid/main.go
@@ -3,27 +3,32 @@
package main
import (
- "encoding/json"
+ "bytes"
"fmt"
"io/ioutil"
"log"
"mime"
"net/http"
+ "net/url"
"os"
"path"
+ "strings"
+ "text/template"
"github.com/pborman/getopt/v2"
"gerrit.wikimedia.org/r/blubber/config"
"gerrit.wikimedia.org/r/blubber/docker"
+ "gerrit.wikimedia.org/r/blubber/meta"
)
var (
- showHelp = getopt.BoolLong("help", 'h', "show help/usage")
- address = getopt.StringLong("address", 'a', ":8748", "socket address/port to listen on (default ':8748')", "address:port")
- endpoint = getopt.StringLong("endpoint", 'e', "/", "server endpoint (default '/')", "path")
- policyURI = getopt.StringLong("policy", 'p', "", "policy file URI", "uri")
- policy *config.Policy
+ showHelp = getopt.BoolLong("help", 'h', "show help/usage")
+ address = getopt.StringLong("address", 'a', ":8748", "socket address/port to listen on (default ':8748')", "address:port")
+ endpoint = getopt.StringLong("endpoint", 'e', "/", "server endpoint (default '/')", "path")
+ policyURI = getopt.StringLong("policy", 'p', "", "policy file URI", "uri")
+ policy *config.Policy
+ openAPISpec []byte
)
func main() {
@@ -51,7 +56,10 @@ func main() {
*endpoint += "/"
}
- log.Printf("listening on %s for requests to %s[variant]\n", *address, *endpoint)
+ // Evaluate OpenAPI spec template and store results for ?spec requests
+ openAPISpec = readOpenAPISpec()
+
+ log.Printf("listening on %s for requests to %sv1/[variant]\n", *address, *endpoint)
http.HandleFunc(*endpoint, blubberoid)
log.Fatal(http.ListenAndServe(*address, nil))
@@ -59,12 +67,35 @@ func main() {
func blubberoid(res http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) <= len(*endpoint) {
+ if req.URL.RawQuery == "spec" {
+ res.Header().Set("Content-Type", "text/plain")
+ res.Write(openAPISpec)
+ return
+ }
+
res.WriteHeader(http.StatusNotFound)
- res.Write(responseBody("request a variant at %s[variant]", *endpoint))
+ res.Write(responseBody("request a variant at %sv1/[variant]", *endpoint))
+ return
+ }
+
+ requestPath := req.URL.Path[len(*endpoint):]
+ pathSegments := strings.Split(requestPath, "/")
+
+ // Request should have been to v1/[variant]
+ if len(pathSegments) != 2 || pathSegments[0] != "v1" {
+ res.WriteHeader(http.StatusNotFound)
+ res.Write(responseBody("request a variant at %sv1/[variant]", *endpoint))
+ return
+ }
+
+ variant, err := url.PathUnescape(pathSegments[1])
+
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ log.Printf("failed to unescape variant name '%s': %s\n", pathSegments[1], err)
return
}
- variant := req.URL.Path[len(*endpoint):]
body, err := ioutil.ReadAll(req.Body)
if err != nil {
@@ -73,25 +104,25 @@ func blubberoid(res http.ResponseWriter, req *http.Request) {
return
}
- switch mt, _, _ := mime.ParseMediaType(req.Header.Get("content-type")); mt {
+ var cfg *config.Config
+ mediaType, _, _ := mime.ParseMediaType(req.Header.Get("content-type"))
+
+ // Default to application/json
+ if mediaType == "" {
+ mediaType = "application/json"
+ }
+
+ switch mediaType {
case "application/json":
- // Enforce strict JSON syntax if specified, even though the config parser
- // would technically handle anything that's at least valid YAML
- if !json.Valid(body) {
- res.WriteHeader(http.StatusBadRequest)
- res.Write(responseBody("'%s' media type given but request contains invalid JSON", mt))
- return
- }
+ cfg, err = config.ReadConfig(body)
case "application/yaml", "application/x-yaml":
- // Let the config parser validate YAML syntax
+ cfg, err = config.ReadYAMLConfig(body)
default:
res.WriteHeader(http.StatusUnsupportedMediaType)
- res.Write(responseBody("'%s' media type is not supported", mt))
+ res.Write(responseBody("'%s' media type is not supported", mediaType))
return
}
- cfg, err := config.ReadYAMLConfig(body)
-
if err != nil {
if config.IsValidationError(err) {
res.WriteHeader(http.StatusUnprocessableEntity)
@@ -101,8 +132,8 @@ func blubberoid(res http.ResponseWriter, req *http.Request) {
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",
+ "Failed to read '%s' config from request body. Error: %s",
+ mediaType,
err.Error(),
))
return
@@ -136,3 +167,16 @@ func blubberoid(res http.ResponseWriter, req *http.Request) {
func responseBody(msg string, a ...interface{}) []byte {
return []byte(fmt.Sprintf(msg+"\n", a...))
}
+
+func readOpenAPISpec() []byte {
+ var buffer bytes.Buffer
+ tmpl, _ := template.New("spec").Parse(openAPISpecTemplate)
+
+ tmpl.Execute(&buffer, struct {
+ Version string
+ }{
+ Version: meta.FullVersion(),
+ })
+
+ return buffer.Bytes()
+}