From 20bb60f3fd3d05ce096bf52f888a268cc30efc1b Mon Sep 17 00:00:00 2001 From: Lars Karlitski Date: Tue, 3 Dec 2019 00:53:44 +0100 Subject: [PATCH] weldr: add support for toml blueprints --- go.mod | 1 + go.sum | 2 ++ internal/weldr/api.go | 62 ++++++++++++++++++++++++++++++++------ internal/weldr/api_test.go | 62 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 2bf27de35..e1cdc2cc2 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( github.com/Azure/azure-storage-blob-go v0.8.0 + github.com/BurntSushi/toml v0.3.1 github.com/aws/aws-sdk-go v1.25.37 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/gobwas/glob v0.2.3 diff --git a/go.sum b/go.sum index cc48dc82a..56e3f6a60 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZ github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-storage-blob-go v0.8.0 h1:53qhf0Oxa0nOjgbDeeYPUeyiNmafAFEY95rZLK0Tj6o= github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/aws/aws-sdk-go v1.25.37 h1:gBtB/F3dophWpsUQKN/Kni+JzYEH2mGHF4hWNtfED1w= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= diff --git a/internal/weldr/api.go b/internal/weldr/api.go index b31a631f7..38d4fe00c 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -2,15 +2,18 @@ package weldr import ( "encoding/json" + "errors" "fmt" "io" "log" "net" "net/http" + "net/url" "sort" "strconv" "strings" + "github.com/BurntSushi/toml" "github.com/google/uuid" "github.com/julienschmidt/httprouter" @@ -701,11 +704,44 @@ func (api *API) blueprintsInfoHandler(writer http.ResponseWriter, request *http. changes = append(changes, change{changed, blueprint.Name}) } - json.NewEncoder(writer).Encode(reply{ - Blueprints: blueprints, - Changes: changes, - Errors: []responseError{}, - }) + q, err := url.ParseQuery(request.URL.RawQuery) + if err != nil { + errors := responseError{ + ID: "InvalidChars", + Msg: fmt.Sprintf("invalid query string: %v", err), + } + statusResponseError(writer, http.StatusBadRequest, errors) + return + } + + format := q.Get("format") + if format == "json" || format == "" { + json.NewEncoder(writer).Encode(reply{ + Blueprints: blueprints, + Changes: changes, + Errors: []responseError{}, + }) + } else if format == "toml" { + // lorax concatenates multiple blueprints with `\n\n` here, + // which is never useful. Deviate by only returning the first + // blueprint. + if len(blueprints) > 1 { + errors := responseError{ + ID: "HTTPError", + Msg: "toml format only supported when requesting one blueprint", + } + statusResponseError(writer, http.StatusBadRequest, errors) + return + } + toml.NewEncoder(writer).Encode(blueprints[0]) + } else { + errors := responseError{ + ID: "InvalidChars", + Msg: fmt.Sprintf("invalid `format` parameter: %s", format), + } + statusResponseError(writer, http.StatusBadRequest, errors) + return + } } func (api *API) blueprintsDepsolveHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { @@ -1030,21 +1066,29 @@ func (api *API) blueprintsNewHandler(writer http.ResponseWriter, request *http.R } contentType := request.Header["Content-Type"] - if len(contentType) != 1 || contentType[0] != "application/json" { + if len(contentType) != 1 { errors := responseError{ ID: "BlueprintsError", - Msg: "'blueprint must be json'", + Msg: "missing Content-Type header", } statusResponseError(writer, http.StatusBadRequest, errors) return } var blueprint blueprint.Blueprint - err := json.NewDecoder(request.Body).Decode(&blueprint) + var err error + if contentType[0] == "application/json" { + err = json.NewDecoder(request.Body).Decode(&blueprint) + } else if contentType[0] == "text/x-toml" { + _, err = toml.DecodeReader(request.Body, &blueprint) + } else { + err = errors.New("blueprint must be in json or toml format") + } + if err != nil { errors := responseError{ ID: "BlueprintsError", - Msg: "400 Bad Request: The browser (or proxy) sent a request that this server could not understand.", + Msg: "400 Bad Request: The browser (or proxy) sent a request that this server could not understand: " + err.Error(), } statusResponseError(writer, http.StatusBadRequest, errors) return diff --git a/internal/weldr/api_test.go b/internal/weldr/api_test.go index a3b1f75b2..19d62f8b6 100644 --- a/internal/weldr/api_test.go +++ b/internal/weldr/api_test.go @@ -1,8 +1,10 @@ package weldr_test import ( + "bytes" "math/rand" "net/http" + "net/http/httptest" "os" "strconv" "testing" @@ -17,6 +19,7 @@ import ( "github.com/osbuild/osbuild-composer/internal/test" "github.com/osbuild/osbuild-composer/internal/weldr" + "github.com/BurntSushi/toml" "github.com/google/go-cmp/cmp" ) @@ -72,6 +75,29 @@ func TestBlueprintsNew(t *testing.T) { } } +func TestBlueprintsNewToml(t *testing.T) { + blueprint := ` +name = "test" +description = "Test" +version = "0.0.0" + +[[packages]] +name = "httpd" +version = "2.4.*"` + + req := httptest.NewRequest("POST", "/api/v0/blueprints/new", bytes.NewReader([]byte(blueprint))) + req.Header.Set("Content-Type", "text/x-toml") + recorder := httptest.NewRecorder() + + api, _ := createWeldrAPI(rpmmd_mock.BaseFixture) + api.ServeHTTP(recorder, req) + + r := recorder.Result() + if r.StatusCode != http.StatusOK { + t.Fatalf("unexpected status %v", r.StatusCode) + } +} + func TestBlueprintsWorkspace(t *testing.T) { var cases = []struct { Method string @@ -115,6 +141,42 @@ func TestBlueprintsInfo(t *testing.T) { } } +func TestBlueprintsInfoToml(t *testing.T) { + api, _ := createWeldrAPI(rpmmd_mock.BaseFixture) + test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test1","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`) + + req := httptest.NewRequest("GET", "/api/v0/blueprints/info/test1?format=toml", nil) + recorder := httptest.NewRecorder() + api.ServeHTTP(recorder, req) + + resp := recorder.Result() + if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected status %v", resp.StatusCode) + } + + var got blueprint.Blueprint + _, err := toml.DecodeReader(resp.Body, &got) + if err != nil { + t.Fatalf("error decoding toml file: %v", err) + } + + t.Logf("%v", got) + + expected := blueprint.Blueprint{ + Name: "test1", + Description: "Test", + Version: "0.0.0", + Packages: []blueprint.Package{ + { "httpd", "2.4.*" }, + }, + Groups: []blueprint.Group{}, + Modules: []blueprint.Package{}, + } + if diff := cmp.Diff(got, expected); diff != "" { + t.Fatalf("received unexpected blueprint: %s", diff) + } +} + func TestBlueprintsFreeze(t *testing.T) { var cases = []struct { Fixture rpmmd_mock.FixtureGenerator