From d65057e1648f44b9c9ee01d0cf0d520dd654cf58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Budai?= Date: Thu, 4 Jun 2020 15:56:19 +0200 Subject: [PATCH] weldr: add support for toml format in /blueprints/freeze This is needed for lorax parity. When multiple blueprints are being frozen in toml mode, the API returns an error. This is the same behaviour as in the /blueprints/info route. Fixes #667 --- internal/test/helpers.go | 35 +++++++++++++++++++ internal/weldr/api.go | 31 ++++++++++++++--- internal/weldr/api_test.go | 70 +++++++++++++++++++++++++++++--------- 3 files changed, 114 insertions(+), 22 deletions(-) diff --git a/internal/test/helpers.go b/internal/test/helpers.go index f41e17506..cd83f4927 100644 --- a/internal/test/helpers.go +++ b/internal/test/helpers.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/BurntSushi/toml" "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/osbuild/osbuild-composer/internal/distro" @@ -135,6 +136,40 @@ func TestRoute(t *testing.T, api API, external bool, method, path, body string, require.Equal(t, expected, reply) } +func TestTOMLRoute(t *testing.T, api API, external bool, method, path, body string, expectedStatus int, expectedTOML string, ignoreFields ...string) { + t.Helper() + + resp := SendHTTP(api, external, method, path, body) + if resp == nil { + t.Skip("This test is for internal testing only") + } + + replyTOML, err := ioutil.ReadAll(resp.Body) + require.NoErrorf(t, err, "%s: could not read response body", path) + + assert.Equalf(t, expectedStatus, resp.StatusCode, "SendHTTP failed for path %s: %v", path, string(replyTOML)) + + if expectedTOML == "" { + require.Lenf(t, replyTOML, 0, "%s: expected no response body, but got:\n%s", path, replyTOML) + } + + var reply, expected interface{} + err = toml.Unmarshal(replyTOML, &reply) + require.NoErrorf(t, err, "%s: json.Unmarshal failed for\n%s", path, string(replyTOML)) + + if expectedTOML == "*" { + return + } + + err = toml.Unmarshal([]byte(expectedTOML), &expected) + require.NoErrorf(t, err, "%s: expected TOML is invalid", path) + + dropFields(reply, ignoreFields...) + dropFields(expected, ignoreFields...) + + require.Equal(t, expected, reply) +} + func TestNonJsonRoute(t *testing.T, api API, external bool, method, path, body string, expectedStatus int, expectedResponse string) { response := SendHTTP(api, external, method, path, body) assert.Equalf(t, expectedStatus, response.StatusCode, "%s: status mismatch", path) diff --git a/internal/weldr/api.go b/internal/weldr/api.go index 0ab5f9bb3..fb3b082db 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -1240,11 +1240,32 @@ func (api *API) blueprintsFreezeHandler(writer http.ResponseWriter, request *htt blueprints = append(blueprints, blueprintFrozen{blueprint}) } - err := json.NewEncoder(writer).Encode(reply{ - Blueprints: blueprints, - Errors: errors, - }) - common.PanicOnError(err) + format := request.URL.Query().Get("format") + + 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 + } + + encoder := toml.NewEncoder(writer) + encoder.Indent = "" + err := encoder.Encode(blueprints[0].Blueprint) + common.PanicOnError(err) + } else { + err := json.NewEncoder(writer).Encode(reply{ + Blueprints: blueprints, + Errors: errors, + }) + common.PanicOnError(err) + } } func (api *API) blueprintsDiffHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { diff --git a/internal/weldr/api_test.go b/internal/weldr/api_test.go index 3a7d7ee91..1288c8ff4 100644 --- a/internal/weldr/api_test.go +++ b/internal/weldr/api_test.go @@ -12,15 +12,14 @@ import ( "testing" "time" + "github.com/osbuild/osbuild-composer/internal/blueprint" "github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/osbuild-composer/internal/distro" - "github.com/osbuild/osbuild-composer/internal/target" - - "github.com/osbuild/osbuild-composer/internal/blueprint" test_distro "github.com/osbuild/osbuild-composer/internal/distro/fedoratest" rpmmd_mock "github.com/osbuild/osbuild-composer/internal/mocks/rpmmd" "github.com/osbuild/osbuild-composer/internal/rpmmd" "github.com/osbuild/osbuild-composer/internal/store" + "github.com/osbuild/osbuild-composer/internal/target" "github.com/osbuild/osbuild-composer/internal/test" "github.com/BurntSushi/toml" @@ -326,21 +325,58 @@ func TestSetPkgEVRA(t *testing.T) { } func TestBlueprintsFreeze(t *testing.T) { - var cases = []struct { - Fixture rpmmd_mock.FixtureGenerator - Path string - ExpectedStatus int - ExpectedJSON string - }{ - {rpmmd_mock.BaseFixture, "/api/v0/blueprints/freeze/test", http.StatusOK, `{"blueprints":[{"blueprint":{"name":"test","description":"Test","version":"0.0.1","packages":[{"name":"dep-package1","version":"1.33-2.fc30.x86_64"},{"name":"dep-package3","version":"7:3.0.3-1.fc30.x86_64"}],"modules":[{"name":"dep-package2","version":"2.9-1.fc30.x86_64"}],"groups":[]}}],"errors":[]}`}, - } + t.Run("json", func(t *testing.T) { + var cases = []struct { + Fixture rpmmd_mock.FixtureGenerator + Path string + ExpectedStatus int + ExpectedJSON string + }{ + {rpmmd_mock.BaseFixture, "/api/v0/blueprints/freeze/test,test2", http.StatusOK, `{"blueprints":[{"blueprint":{"name":"test","description":"Test","version":"0.0.1","packages":[{"name":"dep-package1","version":"1.33-2.fc30.x86_64"},{"name":"dep-package3","version":"7:3.0.3-1.fc30.x86_64"}],"modules":[{"name":"dep-package2","version":"2.9-1.fc30.x86_64"}],"groups":[]}},{"blueprint":{"name":"test2","description":"Test","version":"0.0.0","packages":[{"name":"dep-package1","version":"1.33-2.fc30.x86_64"},{"name":"dep-package3","version":"7:3.0.3-1.fc30.x86_64"}],"modules":[{"name":"dep-package2","version":"2.9-1.fc30.x86_64"}],"groups":[]}}],"errors":[]}`}, + } - for _, c := range cases { - api, _ := createWeldrAPI(c.Fixture) - test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`) - test.TestRoute(t, api, false, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON) - test.SendHTTP(api, false, "DELETE", "/api/v0/blueprints/delete/test", ``) - } + for _, c := range cases { + api, _ := createWeldrAPI(c.Fixture) + test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`) + test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test2","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`) + test.TestRoute(t, api, false, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON) + } + }) + + t.Run("toml", func(t *testing.T) { + var cases = []struct { + Fixture rpmmd_mock.FixtureGenerator + Path string + ExpectedStatus int + ExpectedTOML string + }{ + {rpmmd_mock.BaseFixture, "/api/v0/blueprints/freeze/test?format=toml", http.StatusOK, "name=\"test\"\n description=\"Test\"\n version=\"0.0.1\"\n groups = []\n [[packages]]\n name=\"dep-package1\"\n version=\"1.33-2.fc30.x86_64\"\n [[packages]]\n name=\"dep-package3\"\n version=\"7:3.0.3-1.fc30.x86_64\"\n [[modules]]\n name=\"dep-package2\"\n version=\"2.9-1.fc30.x86_64\""}, + } + + for _, c := range cases { + api, _ := createWeldrAPI(c.Fixture) + test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`) + test.TestTOMLRoute(t, api, false, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedTOML) + } + }) + + t.Run("toml-multiple", func(t *testing.T) { + var cases = []struct { + Fixture rpmmd_mock.FixtureGenerator + Path string + ExpectedStatus int + ExpectedJSON string + }{ + {rpmmd_mock.BaseFixture, "/api/v0/blueprints/freeze/test,test2?format=toml", http.StatusBadRequest, "{\"status\":false,\"errors\":[{\"id\":\"HTTPError\",\"msg\":\"toml format only supported when requesting one blueprint\"}]}"}, + } + + for _, c := range cases { + api, _ := createWeldrAPI(c.Fixture) + test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`) + test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test2","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`) + test.TestRoute(t, api, false, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON) + } + }) } func TestBlueprintsDiff(t *testing.T) {