debian-forge-composer/internal/weldr/api_test.go
Michael Vogt af0543d27c many: update images Manifest() API for PR#1107
This updates composer to use the updated API in images around the
seed handling for manifests, see images PR#1107 for details.

Note that this has no semantic changes yet. We could now simplfy
some things because images will auto-seed but that is for a followup.
2025-01-20 09:50:49 +01:00

2549 lines
119 KiB
Go

package weldr
import (
"archive/tar"
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
"time"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distro/test_distro"
"github.com/osbuild/images/pkg/distrofactory"
"github.com/osbuild/images/pkg/dnfjson"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/ostree/mock_ostree_repo"
"github.com/osbuild/images/pkg/reporegistry"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
dnfjson_mock "github.com/osbuild/osbuild-composer/internal/mocks/dnfjson"
rpmmd_mock "github.com/osbuild/osbuild-composer/internal/mocks/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"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var dnfjsonPath string
var testDistro2Name string = fmt.Sprintf("%s-2", test_distro.TestDistroNameBase)
func setupDNFJSON() {
// compile the mock-dnf-json binary to speed up tests
tmpdir, err := os.MkdirTemp("", "")
if err != nil {
panic(err)
}
dnfjsonPath = filepath.Join(tmpdir, "mock-dnf-json")
cmd := exec.Command("go", "build", "-o", dnfjsonPath, "../../cmd/mock-dnf-json")
if err := cmd.Run(); err != nil {
panic(err)
}
}
func createTestWeldrAPI(tempdir, hostDistroName, hostArchName string, fixtureGenerator rpmmd_mock.FixtureGenerator,
distroImageTypeDenylist map[string][]string) (*API, *store.Fixture) {
// create tempdir subdirectory for store
dbpath, err := os.MkdirTemp(tempdir, "")
if err != nil {
panic(err)
}
fixture := fixtureGenerator(dbpath, hostDistroName, hostArchName)
df := distrofactory.NewTestDefault()
distro := df.GetDistro(fixture.StoreFixture.HostDistroName)
if distro == nil {
panic(fmt.Errorf("unknown distro: %s", fixture.StoreFixture.HostDistroName))
}
_, err = distro.GetArch(fixture.StoreFixture.HostArchName)
if err != nil {
panic(fmt.Errorf("unknown arch: %s", fixture.StoreFixture.HostArchName))
}
// Determine the second arch, which is not the host arch
testArches := []string{
test_distro.TestArchName,
test_distro.TestArch2Name,
test_distro.TestArch3Name,
}
var otherArch string
for _, arch := range testArches {
if arch != fixture.StoreFixture.HostArchName {
otherArch = arch
break
}
}
hostDistroVer, err := strconv.Atoi(distro.Releasever())
if err != nil {
panic(fmt.Sprintf("failed to parse host distro version: %s", distro.Releasever()))
}
otherDistroName := fmt.Sprintf("%s-%d", test_distro.TestDistroNameBase, hostDistroVer+1)
otherDistro := df.GetDistro(otherDistroName)
if otherDistro == nil {
panic(fmt.Errorf("unknown distro: %s", otherDistroName))
}
_, err = otherDistro.GetArch(otherArch)
if err != nil {
panic(fmt.Errorf("unknown arch: %s", otherArch))
}
rr := reporegistry.NewFromDistrosRepoConfigs(rpmmd.DistrosRepoConfigs{
fixture.StoreFixture.HostDistroName: {
fixture.StoreFixture.HostArchName: {
{Name: "test-id", BaseURLs: []string{"http://example.com/test/os/x86_64"}, CheckGPG: common.ToPtr(true)},
},
otherArch: {
{Name: "test-id", BaseURLs: []string{"http://example.com/test/os/aarch64"}, CheckGPG: common.ToPtr(true)},
},
},
otherDistro.Name(): {
fixture.StoreFixture.HostArchName: {
{Name: "test-id-2", BaseURLs: []string{"http://example.com/test-2/os/x86_64"}, CheckGPG: common.ToPtr(true)},
},
},
})
solver := dnfjson.NewBaseSolver("") // test solver doesn't need a cache dir
// create tempdir subdirectory for solver response file
dspath, err := os.MkdirTemp(tempdir, "")
if err != nil {
panic(err)
}
respfile := fixture.ResponseGenerator(dspath)
solver.SetDNFJSONPath(dnfjsonPath, respfile)
testApi := NewTestAPI(solver, rr, nil, fixture.StoreFixture, fixture.Workers, "", distroImageTypeDenylist)
return testApi, fixture.StoreFixture
}
// ResolveContent transforms content source specs into resolved specs for serialization.
// For packages, it uses the dnfjson_mock.BaseDeps() every time, but retains
// the map keys from the input.
// For ostree commits it hashes the URL+Ref to create a checksum.
func ResolveContent(pkgs map[string][]rpmmd.PackageSet, containers map[string][]container.SourceSpec, commits map[string][]ostree.SourceSpec) (map[string][]rpmmd.PackageSpec, map[string][]container.Spec, map[string][]ostree.CommitSpec) {
pkgSpecs := make(map[string][]rpmmd.PackageSpec, len(pkgs))
for name := range pkgs {
pkgSpecs[name] = dnfjson_mock.BaseDeps()
}
containerSpecs := make(map[string][]container.Spec, len(containers))
for name := range containers {
containerSpecs[name] = make([]container.Spec, len(containers[name]))
for idx := range containers[name] {
containerSpecs[name][idx] = container.Spec{
Source: containers[name][idx].Source,
TLSVerify: containers[name][idx].TLSVerify,
LocalName: containers[name][idx].Name,
}
}
}
commitSpecs := make(map[string][]ostree.CommitSpec, len(commits))
for name := range commits {
commitSpecs[name] = make([]ostree.CommitSpec, len(commits[name]))
for idx := range commits[name] {
commitSpecs[name][idx] = ostree.CommitSpec{
Ref: commits[name][idx].Ref,
URL: commits[name][idx].URL,
Checksum: fmt.Sprintf("%x", sha256.Sum256([]byte(commits[name][idx].URL+commits[name][idx].Ref))),
}
fmt.Printf("Test distro spec: %+v\n", commitSpecs[name][idx])
}
}
return pkgSpecs, containerSpecs, commitSpecs
}
func TestBasic(t *testing.T) {
var cases = []struct {
Path string
ExpectedStatus int
ExpectedJSON string
}{
{"/api/status", http.StatusOK, `{"api":"1","db_supported":true,"db_version":"0","schema_version":"0","backend":"osbuild-composer","build":"devel","msgs":[]}`},
{"/api/v0/projects/source/list", http.StatusOK, `{"sources":["test-id"]}`},
{"/api/v0/projects/source/info", http.StatusNotFound, `{"errors":[{"code":404,"id":"HTTPError","msg":"Not Found"}],"status":false}`},
{"/api/v0/projects/source/info/", http.StatusNotFound, `{"errors":[{"code":404,"id":"HTTPError","msg":"Not Found"}],"status":false}`},
{"/api/v0/projects/source/info/foo", http.StatusOK, `{"errors":[{"id":"UnknownSource","msg":"foo is not a valid source"}],"sources":{}}`},
{"/api/v0/projects/source/info/test-id", http.StatusOK, `{"sources":{"test-id":{"name":"test-id","type":"yum-baseurl","url":"http://example.com/test/os/x86_64","check_gpg":true,"check_ssl":true,"system":true}},"errors":[]}`},
{"/api/v0/projects/source/info/*", http.StatusOK, `{"sources":{"test-id":{"name":"test-id","type":"yum-baseurl","url":"http://example.com/test/os/x86_64","check_gpg":true,"check_ssl":true,"system":true}},"errors":[]}`},
{"/api/v0/blueprints/list", http.StatusOK, `{"total":1,"offset":0,"limit":1,"blueprints":["test"]}`},
{"/api/v0/blueprints/info/", http.StatusNotFound, `{"errors":[{"code":404,"id":"HTTPError","msg":"Not Found"}],"status":false}`},
{"/api/v0/blueprints/info/foo", http.StatusOK, `{"blueprints":[],"changes":[],"errors":[{"id":"UnknownBlueprint","msg":"foo: "}]}`},
{"/api/v1/distros/list", http.StatusOK, `{"distros": ["test-distro-1", "test-distro-2"]}`},
{"/api/v1/compose/types", http.StatusOK, `{"types": [{"enabled":true, "name":"test_ostree_type"},{"enabled":true, "name":"test_type"}]}`},
{"/api/v1/compose/types?distro=test-distro-2", http.StatusOK, `{"types": [{"enabled":true, "name":"test_ostree_type"},{"enabled":true, "name":"test_type"}]}`},
{"/api/v1/compose/types?distro=fedora-1", http.StatusBadRequest, `{"status":false,"errors":[{"id":"DistroError","msg":"Invalid distro: fedora-1"}]}`},
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
}
}
func TestBlueprintsNew(t *testing.T) {
var cases = []struct {
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{"POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[],"version":""}`, http.StatusOK, `{"status":true}`},
{"POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`, http.StatusOK, `{"status":true}`},
{"POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages:}`, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"400 Bad Request: The browser (or proxy) sent a request that this server could not understand: unexpected EOF"}]}`},
{"POST", "/api/v0/blueprints/new", `{"name":"","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`, http.StatusBadRequest, `{"status":false,"errors":[{"id":"InvalidChars","msg":"Invalid characters in API path"}]}`},
{"POST", "/api/v0/blueprints/new", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"Missing blueprint"}]}`},
{"POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","distro":"test-distro-1","packages":[],"version":""}`, http.StatusOK, `{"status":true}`},
{"POST", "/api/v0/blueprints/new", `{"name":"test2","description":"Test 2","distro":"test-distro-2","packages":[],"version":""}`, http.StatusOK, `{"status":true}`},
{"POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","distro":"fedora-1","packages":[],"version":""}`, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"'fedora-1' is not a valid distribution (architecture 'test_arch')"}]}`},
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
}
}
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, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusOK, r.StatusCode)
}
func TestBlueprintsEmptyToml(t *testing.T) {
req := httptest.NewRequest("POST", "/api/v0/blueprints/new", bytes.NewReader(nil))
req.Header.Set("Content-Type", "text/x-toml")
recorder := httptest.NewRecorder()
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
func TestBlueprintsInvalidToml(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, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
func TestBlueprintsWorkspaceJSON(t *testing.T) {
var cases = []struct {
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{"POST", "/api/v0/blueprints/workspace", `{"name":"test","description":"Test","packages":[{"name":"systemd","version":"123"}],"version":"0.0.0"}`, http.StatusOK, `{"status":true}`},
{"POST", "/api/v0/blueprints/workspace", `{"name":"test","description":"Test","packages:}`, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"400 Bad Request: The browser (or proxy) sent a request that this server could not understand: unexpected EOF"}]}`},
{"POST", "/api/v0/blueprints/workspace", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"Missing blueprint"}]}`},
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
}
}
func TestBlueprintsWorkspaceTOML(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/workspace", bytes.NewReader([]byte(blueprint)))
req.Header.Set("Content-Type", "text/x-toml")
recorder := httptest.NewRecorder()
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusOK, r.StatusCode)
}
func TestBlueprintsWorkspaceEmptyTOML(t *testing.T) {
req := httptest.NewRequest("POST", "/api/v0/blueprints/workspace", bytes.NewReader(nil))
req.Header.Set("Content-Type", "text/x-toml")
recorder := httptest.NewRecorder()
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
func TestBlueprintsWorkspaceInvalidTOML(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/workspace", bytes.NewReader([]byte(blueprint)))
req.Header.Set("Content-Type", "text/x-toml")
recorder := httptest.NewRecorder()
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
func TestBlueprintsInfo(t *testing.T) {
var cases = []struct {
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{"GET", "/api/v0/blueprints/info/test1", ``, http.StatusOK, `{"blueprints":[{"name":"test1","description":"Test","distro":"","modules":[],"packages":[{"name":"httpd","version":"2.4.*"}],"groups":[],"version":"0.0.0"}],
"changes":[{"name":"test1","changed":false}], "errors":[]}`},
{"GET", "/api/v0/blueprints/info/test2", ``, http.StatusOK, `{"blueprints":[{"name":"test2","description":"Test","distro":"","modules":[],"packages":[{"name":"systemd","version":"123"}],"groups":[],"version":"0.0.0"}],
"changes":[{"name":"test2","changed":true}], "errors":[]}`},
{"GET", "/api/v0/blueprints/info/test3-non", ``, http.StatusOK, `{"blueprints":[],"changes":[],"errors":[{"id":"UnknownBlueprint","msg":"test3-non: "}]}`},
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test1","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test2","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/workspace", `{"name":"test2","description":"Test","packages":[{"name":"systemd","version":"123"}],"version":"0.0.0"}`)
test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/test2", ``)
test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/test1", ``)
}
}
func TestBlueprintsInfoToml(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
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()
require.Equal(t, http.StatusOK, resp.StatusCode)
var got blueprint.Blueprint
_, err := toml.NewDecoder(resp.Body).Decode(&got)
require.NoErrorf(t, err, "error decoding toml file")
expected := blueprint.Blueprint{
Name: "test1",
Description: "Test",
Version: "0.0.0",
Distro: "",
Packages: []blueprint.Package{
{
Name: "httpd",
Version: "2.4.*"},
},
Groups: []blueprint.Group{},
Modules: []blueprint.Package{},
}
require.Equalf(t, expected, got, "received unexpected blueprint")
}
func TestBlueprintsCustomizationInfoToml(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
// A test blueprint with all the available customizations filled in
// Converted from TOML to JSON for the POST
testBlueprint := `{
"name": "example-custom-base",
"description": "A base system with customizations",
"version": "0.0.1",
"distro": "test-distro-1",
"packages": [
{
"name": "tmux",
"version": "*"
},
{
"name": "git",
"version": "*"
},
{
"name": "vim-enhanced",
"version": "*"
}
],
"containers": [
{
"source": "quay.io/fedora/fedora:latest"
}
],
"customizations": {
"hostname": "custombase",
"kernel": {
"append": "nosmt=force"
},
"timezone": {
"timezone": "US/Eastern",
"ntpservers": [
"0.north-america.pool.ntp.org",
"1.north-america.pool.ntp.org"
]
},
"locale": {
"languages": [
"en_US.UTF-8"
],
"keyboard": "us"
},
"sshkey": [
{
"user": "root",
"key": "A SSH KEY FOR ROOT"
}
],
"firewall": {
"ports": [
"22:tcp",
"80:tcp",
"imap:tcp",
"53:tcp",
"53:udp"
],
"services": {
"enabled": [
"ftp",
"ntp",
"dhcp"
],
"disabled": [
"telnet"
]
}
},
"services": {
"enabled": [
"sshd",
"cockpit.socket",
"httpd"
],
"disabled": [
"postfix",
"telnetd"
],
"masked": [
"firewalld"
]
},
"user": [
{
"name": "admin",
"description": "Widget admin account",
"password": "$6$CHO2$3rN8eviE2t50lmVyBYihTgVRHcaecmeCk31LeOUleVK/R/aeWVHVZDi26zAH.o0ywBKH9Tc0/wm7sW/q39uyd1",
"home": "/srv/widget/",
"shell": "/usr/bin/bash",
"groups": [
"widget",
"users",
"students"
],
"uid": 1200
}
],
"group": [
{
"name": "widget"
},
{
"name": "students"
}
],
"filesystem": [
{
"mountpoint": "/",
"minsize": 2147483648
}
],
"openscap": {
"datastream": "/usr/share/xml/scap/ssg/content/ssg-rhel8-ds.xml",
"profile_id": "xccdf_org.ssgproject.content_profile_cis"
},
"partitioning_mode": "raw",
"rpm": {
"import_keys": {
"files": [
"/root/gpg-key"
]
}
},
"rhsm": {
"config": {
"dnf_plugins": {
"product_id": {
"enabled": true
},
"subscription_manager": {
"enabled": false
}
},
"subscription_manager": {
"rhsm": {
"manage_repos": true
},
"rhsmcertd": {
"auto_registration": false
}
}
}
}
}
}`
resp := test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", testBlueprint)
body, err := io.ReadAll(resp.Body)
require.Nil(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode, string(body))
req := httptest.NewRequest("GET", "/api/v0/blueprints/info/example-custom-base?format=toml", nil)
recorder := httptest.NewRecorder()
api.ServeHTTP(recorder, req)
resp = recorder.Result()
body, err = io.ReadAll(resp.Body)
require.Nil(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode, string(body))
var got blueprint.Blueprint
err = toml.Unmarshal(body, &got)
require.NoErrorf(t, err, "error decoding toml file")
expected := blueprint.Blueprint{
Name: "example-custom-base",
Description: "A base system with customizations",
Version: "0.0.1",
Distro: test_distro.TestDistro1Name,
Packages: []blueprint.Package{
blueprint.Package{Name: "tmux", Version: "*"},
blueprint.Package{Name: "git", Version: "*"},
blueprint.Package{Name: "vim-enhanced", Version: "*"},
},
Modules: []blueprint.Package{},
Groups: []blueprint.Group{},
Containers: []blueprint.Container{
blueprint.Container{
Source: "quay.io/fedora/fedora:latest",
},
},
Customizations: &blueprint.Customizations{
Hostname: common.ToPtr("custombase"),
Kernel: &blueprint.KernelCustomization{
Append: "nosmt=force",
},
SSHKey: []blueprint.SSHKeyCustomization{
blueprint.SSHKeyCustomization{User: "root", Key: "A SSH KEY FOR ROOT"},
},
User: []blueprint.UserCustomization{
blueprint.UserCustomization{
Name: "admin",
Description: common.ToPtr("Widget admin account"),
Password: common.ToPtr("$6$CHO2$3rN8eviE2t50lmVyBYihTgVRHcaecmeCk31LeOUleVK/R/aeWVHVZDi26zAH.o0ywBKH9Tc0/wm7sW/q39uyd1"),
Home: common.ToPtr("/srv/widget/"),
Shell: common.ToPtr("/usr/bin/bash"),
Groups: []string{"widget", "users", "students"},
UID: common.ToPtr(1200),
},
},
Group: []blueprint.GroupCustomization{
blueprint.GroupCustomization{
Name: "widget",
},
blueprint.GroupCustomization{
Name: "students",
},
},
Timezone: &blueprint.TimezoneCustomization{
Timezone: common.ToPtr("US/Eastern"),
NTPServers: []string{"0.north-america.pool.ntp.org", "1.north-america.pool.ntp.org"},
},
Locale: &blueprint.LocaleCustomization{
Languages: []string{"en_US.UTF-8"},
Keyboard: common.ToPtr("us"),
},
Firewall: &blueprint.FirewallCustomization{
Ports: []string{"22:tcp", "80:tcp", "imap:tcp", "53:tcp", "53:udp"},
Services: &blueprint.FirewallServicesCustomization{
Enabled: []string{"ftp", "ntp", "dhcp"},
Disabled: []string{"telnet"},
},
},
Services: &blueprint.ServicesCustomization{
Enabled: []string{"sshd", "cockpit.socket", "httpd"},
Disabled: []string{"postfix", "telnetd"},
Masked: []string{"firewalld"},
},
Filesystem: []blueprint.FilesystemCustomization{
blueprint.FilesystemCustomization{
Mountpoint: "/",
MinSize: 2147483648,
},
},
OpenSCAP: &blueprint.OpenSCAPCustomization{
DataStream: "/usr/share/xml/scap/ssg/content/ssg-rhel8-ds.xml",
ProfileID: "xccdf_org.ssgproject.content_profile_cis",
},
PartitioningMode: "raw",
RPM: &blueprint.RPMCustomization{
ImportKeys: &blueprint.RPMImportKeys{
Files: []string{"/root/gpg-key"},
},
},
RHSM: &blueprint.RHSMCustomization{
Config: &blueprint.RHSMConfig{
DNFPlugins: &blueprint.SubManDNFPluginsConfig{
ProductID: &blueprint.DNFPluginConfig{
Enabled: common.ToPtr(true),
},
SubscriptionManager: &blueprint.DNFPluginConfig{
Enabled: common.ToPtr(false),
},
},
SubscriptionManager: &blueprint.SubManConfig{
RHSMConfig: &blueprint.SubManRHSMConfig{
ManageRepos: common.ToPtr(true),
},
RHSMCertdConfig: &blueprint.SubManRHSMCertdConfig{
AutoRegistration: common.ToPtr(false),
},
},
},
},
},
}
require.Equalf(t, expected, got, string(body))
}
func TestNonExistentBlueprintsInfoToml(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
req := httptest.NewRequest("GET", "/api/v0/blueprints/info/test3-non?format=toml", nil)
recorder := httptest.NewRecorder()
api.ServeHTTP(recorder, req)
resp := recorder.Result()
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
}
func TestBlueprintsFreeze(t *testing.T) {
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, freezeTestResponse},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
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 distro=\"\"\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\""},
{rpmmd_mock.BaseFixture, "/api/v0/blueprints/freeze/missing?format=toml", http.StatusOK, ""},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
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 idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
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) {
var cases = []struct {
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{"GET", "/api/v0/blueprints/diff/test/NEWEST/WORKSPACE", ``, http.StatusOK, `{"diff":[{"new":{"Package":{"name":"systemd","version":"123"}},"old":null},{"new":null,"old":{"Package":{"name":"httpd","version":"2.4.*"}}}]}`},
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/workspace", `{"name":"test","description":"Test","packages":[{"name":"systemd","version":"123"}],"version":"0.0.0"}`)
test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/test", ``)
}
}
func TestBlueprintsDelete(t *testing.T) {
var cases = []struct {
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{"DELETE", "/api/v0/blueprints/delete/test", ``, http.StatusOK, `{"status":true}`},
{"DELETE", "/api/v0/blueprints/delete/test3-non", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"Unknown blueprint: test3-non"}]}`},
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/test", ``)
}
}
func TestBlueprintsChanges(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
// math/rand is good enough in this case
/* #nosec G404 */
rand.New(rand.NewSource(time.Now().UnixNano()))
/* #nosec G404 */
id := strconv.Itoa(rand.Int())
ignoreFields := []string{"commit", "timestamp"}
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"`+id+`","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
test.TestRoute(t, api, true, "GET", "/api/v0/blueprints/changes/failing"+id, ``, http.StatusOK, `{"blueprints":[],"errors":[{"id":"UnknownBlueprint","msg":"failing`+id+`"}],"limit":20,"offset":0}`, ignoreFields...)
test.TestRoute(t, api, true, "GET", "/api/v0/blueprints/changes/"+id, ``, http.StatusOK, `{"blueprints":[{"changes":[{"commit":"","message":"Recipe `+id+`, version 0.0.0 saved.","revision":null,"timestamp":""}],"name":"`+id+`","total":1}],"errors":[],"limit":20,"offset":0}`, ignoreFields...)
test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/"+id, ``)
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"`+id+`","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
test.TestRoute(t, api, true, "GET", "/api/v0/blueprints/changes/"+id, ``, http.StatusOK, `{"blueprints":[{"changes":[{"commit":"","message":"Recipe `+id+`, version 0.0.0 saved.","revision":null,"timestamp":""},{"commit":"","message":"Recipe `+id+`, version 0.0.0 saved.","revision":null,"timestamp":""}],"name":"`+id+`","total":2}],"errors":[],"limit":20,"offset":0}`, ignoreFields...)
test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/"+id, ``)
// Test with an empty Version
/* #nosec G404 */
id = strconv.Itoa(rand.Int())
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"`+id+`","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":""}`)
test.TestRoute(t, api, true, "GET", "/api/v0/blueprints/changes/"+id, ``, http.StatusOK, `{"blueprints":[{"changes":[{"commit":"","message":"Recipe `+id+`, version 0.0.0 saved.","revision":null,"timestamp":""}],"name":"`+id+`","total":1}],"errors":[],"limit":20,"offset":0}`, ignoreFields...)
test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/"+id, ``)
}
// TestBlueprintChange tests getting a single blueprint commit
func TestBlueprintChange(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
// math/rand is good enough in this case
/* #nosec G404 */
rand.New(rand.NewSource(time.Now().UnixNano()))
/* #nosec G404 */
id := strconv.Itoa(rand.Int())
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"`+id+`","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.1"}`)
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"`+id+`","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.2"}`)
resp := test.SendHTTP(api, true, "GET", "/api/v0/blueprints/changes/"+id, ``)
body, err := io.ReadAll(resp.Body)
require.Nil(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
var changes BlueprintsChangesV0
err = json.Unmarshal(body, &changes)
require.Nil(t, err)
require.Equal(t, 1, len(changes.BlueprintsChanges))
require.Equal(t, 2, len(changes.BlueprintsChanges[0].Changes))
commit := changes.BlueprintsChanges[0].Changes[1].Commit
// Get the blueprint's oldest commit
route := fmt.Sprintf("/api/v1/blueprints/change/%s/%s", id, commit)
resp = test.SendHTTP(api, true, "GET", route, ``)
body, err = io.ReadAll(resp.Body)
require.Nil(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
var bp blueprint.Blueprint
err = json.Unmarshal(body, &bp)
require.Nil(t, err)
require.Equal(t, "0.0.1", bp.Version)
}
func TestBlueprintsDepsolve(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, http.StatusOK, depsolveTestResponse},
{rpmmd_mock.NonExistingPackage, http.StatusOK, depsolvePackageNotExistError},
{rpmmd_mock.BadDepsolve, http.StatusOK, depsolveBadError},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"dep-package1","version":"*"}],"modules":[{"name":"dep-package3","version":"*"}],"version":"0.0.0"}`)
test.TestRoute(t, api, false, "GET", "/api/v0/blueprints/depsolve/test", ``, c.ExpectedStatus, c.ExpectedJSON)
test.SendHTTP(api, false, "DELETE", "/api/v0/blueprints/delete/test", ``)
})
}
}
// TestOldBlueprintsUndo run tests with blueprint changes after a service restart
// Old blueprints are not saved, after a restart the changes are listed, but cannot be recalled
func TestOldBlueprintsUndo(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.OldChangesFixture, nil)
t.Cleanup(sf.Cleanup)
// math/rand is good enough in this case
/* #nosec G404 */
rand.New(rand.NewSource(time.Now().UnixNano()))
/* #nosec G404 */
ignoreFields := []string{"commit", "timestamp"}
test.TestRoute(t, api, true, "GET", "/api/v0/blueprints/changes/test-old-changes", ``, http.StatusOK, oldBlueprintsUndoResponse, ignoreFields...)
resp := test.SendHTTP(api, true, "GET", "/api/v0/blueprints/changes/test-old-changes", ``)
body, err := io.ReadAll(resp.Body)
require.Nil(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
var changes BlueprintsChangesV0
err = json.Unmarshal(body, &changes)
require.Nil(t, err)
require.Equal(t, 1, len(changes.BlueprintsChanges))
require.Equal(t, 3, len(changes.BlueprintsChanges[0].Changes))
commit := changes.BlueprintsChanges[0].Changes[2].Commit
// Undo a known commit, that is old
test.TestRoute(t, api, true, "POST", "/api/v0/blueprints/undo/test-old-changes/"+commit, ``, http.StatusBadRequest, `{"errors":[{"id":"BlueprintsError", "msg":"no blueprint found for commit `+commit+`"}], "status":false}`)
// Check to make sure the undo is not present (can't undo something not there)
test.TestRoute(t, api, true, "GET", "/api/v0/blueprints/changes/test-old-changes", ``, http.StatusOK, oldBlueprintsUndoResponse, ignoreFields...)
// Check to make sure it didn't create an empty blueprint
test.TestRoute(t, api, true, "GET", "/api/v0/blueprints/list", ``, http.StatusOK, `{"total":1,"offset":0,"limit":1,"blueprints":["test-old-changes"]}`)
test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/test-old-changes", ``)
}
// TestNewBlueprintsUndo run tests with blueprint changes without a service restart
func TestNewBlueprintsUndo(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
// math/rand is good enough in this case
/* #nosec G404 */
rand.New(rand.NewSource(time.Now().UnixNano()))
/* #nosec G404 */
id := strconv.Itoa(rand.Int())
ignoreFields := []string{"commit", "timestamp"}
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"`+id+`","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.1"}`)
test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"`+id+`","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}, {"name": "tmux", "version":"*"}],"version":"0.1.0"}`)
test.TestRoute(t, api, true, "GET", "/api/v0/blueprints/changes/"+id, ``, http.StatusOK, `{"blueprints":[{"changes":[{"commit":"","message":"Recipe `+id+`, version 0.1.0 saved.","revision":null,"timestamp":""},{"commit":"","message":"Recipe `+id+`, version 0.0.1 saved.","revision":null,"timestamp":""}],"name":"`+id+`","total":2}],"errors":[],"limit":20,"offset":0}`, ignoreFields...)
resp := test.SendHTTP(api, true, "GET", "/api/v0/blueprints/changes/"+id, ``)
body, err := io.ReadAll(resp.Body)
require.Nil(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
var changes BlueprintsChangesV0
err = json.Unmarshal(body, &changes)
require.Nil(t, err)
require.Equal(t, 1, len(changes.BlueprintsChanges))
require.Equal(t, 2, len(changes.BlueprintsChanges[0].Changes))
commit := changes.BlueprintsChanges[0].Changes[1].Commit
// Undo an unknown commit
test.TestRoute(t, api, true, "POST", "/api/v0/blueprints/undo/"+id+"/d7e5fa641aad45300242a0f273827576e32bfc03", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownCommit","msg":"Unknown commit"}]}`)
// Undo a known commit
test.TestRoute(t, api, true, "POST", "/api/v0/blueprints/undo/"+id+"/"+commit, ``, http.StatusOK, `{"status":true}`)
// Check to make sure the undo is present
test.TestRoute(t, api, true, "GET", "/api/v0/blueprints/changes/"+id, ``, http.StatusOK, `{"blueprints":[{"changes":[{"commit":"","message":"`+id+`.toml reverted to commit `+commit+`","revision":null,"timestamp":""},{"commit":"","message":"Recipe `+id+`, version 0.1.0 saved.","revision":null,"timestamp":""},{"commit":"","message":"Recipe `+id+`, version 0.0.1 saved.","revision":null,"timestamp":""}],"name":"`+id+`","total":3}],"errors":[],"limit":20,"offset":0}`, ignoreFields...)
test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/"+id, ``)
}
func TestCompose(t *testing.T) {
// create two ostree repos, one to serve the default test_distro ref (for fallback tests) and one to serve a custom ref
distro1 := test_distro.DistroFactory(test_distro.TestDistro1Name)
require.NotNil(t, distro1)
ostreeRepoDefault := mock_ostree_repo.Setup(distro1.OSTreeRef())
defer ostreeRepoDefault.TearDown()
otherRef := "some/other/ref"
ostreeRepoOther := mock_ostree_repo.Setup(otherRef)
defer ostreeRepoOther.TearDown()
arch, err := distro1.GetArch(test_distro.TestArchName)
require.NoError(t, err)
imgType, err := arch.GetImageType(test_distro.TestImageTypeName)
require.NoError(t, err)
manifest, _, err := imgType.Manifest(nil, distro.ImageOptions{}, nil, nil)
require.NoError(t, err)
rPkgs, rContainers, rCommits := ResolveContent(manifest.GetPackageSetChains(), manifest.GetContainerSourceSpecs(), manifest.GetOSTreeSourceSpecs())
mf, err := manifest.Serialize(rPkgs, rContainers, rCommits, nil)
require.NoError(t, err)
ostreeImgType, err := arch.GetImageType(test_distro.TestImageTypeOSTree)
require.NoError(t, err)
ostreeOptions := ostree.ImageOptions{URL: ostreeRepoDefault.Server.URL}
ostreeManifest, _, err := ostreeImgType.Manifest(nil, distro.ImageOptions{OSTree: &ostreeOptions}, nil, nil)
require.NoError(t, err)
rPkgs, rContainers, rCommits = ResolveContent(ostreeManifest.GetPackageSetChains(), ostreeManifest.GetContainerSourceSpecs(), ostreeManifest.GetOSTreeSourceSpecs())
omf, err := ostreeManifest.Serialize(rPkgs, rContainers, rCommits, nil)
require.NoError(t, err)
expectedComposeLocal := &store.Compose{
Blueprint: &blueprint.Blueprint{
Name: "test",
Version: "0.0.0",
Packages: []blueprint.Package{},
Modules: []blueprint.Package{},
Groups: []blueprint.Group{},
Customizations: nil,
},
ImageBuild: store.ImageBuild{
QueueStatus: common.IBWaiting,
ImageType: imgType,
Size: imgType.Size(0),
Manifest: mf,
Targets: []*target.Target{
{
ImageName: imgType.Filename(),
OsbuildArtifact: target.OsbuildArtifact{
ExportFilename: imgType.Filename(),
ExportName: imgType.Exports()[0],
},
Name: target.TargetNameWorkerServer,
Options: &target.WorkerServerTargetOptions{},
},
},
},
Packages: dnfjson_mock.BaseDeps(),
}
expectedComposeLocalAndAws := &store.Compose{
Blueprint: &blueprint.Blueprint{
Name: "test",
Version: "0.0.0",
Packages: []blueprint.Package{},
Modules: []blueprint.Package{},
Groups: []blueprint.Group{},
Customizations: nil,
},
ImageBuild: store.ImageBuild{
QueueStatus: common.IBWaiting,
ImageType: imgType,
Size: imgType.Size(0),
Manifest: mf,
Targets: []*target.Target{
{
ImageName: imgType.Filename(),
OsbuildArtifact: target.OsbuildArtifact{
ExportFilename: imgType.Filename(),
ExportName: imgType.Exports()[0],
},
Name: target.TargetNameWorkerServer,
Options: &target.WorkerServerTargetOptions{},
},
{
Name: target.TargetNameAWS,
Status: common.IBWaiting,
ImageName: "test_upload",
OsbuildArtifact: target.OsbuildArtifact{
ExportFilename: imgType.Filename(),
ExportName: imgType.Exports()[0],
},
Options: &target.AWSTargetOptions{
Region: "frankfurt",
AccessKeyID: "accesskey",
SecretAccessKey: "secretkey",
Bucket: "clay",
Key: "imagekey",
BootMode: common.ToPtr(string(ec2types.BootModeValuesUefiPreferred)),
},
},
},
},
Packages: dnfjson_mock.BaseDeps(),
}
expectedComposeOSTree := &store.Compose{
Blueprint: &blueprint.Blueprint{
Name: "test",
Version: "0.0.0",
Packages: []blueprint.Package{},
Modules: []blueprint.Package{},
Groups: []blueprint.Group{},
Customizations: nil,
},
ImageBuild: store.ImageBuild{
QueueStatus: common.IBWaiting,
ImageType: ostreeImgType,
Size: ostreeImgType.Size(0),
Manifest: omf,
Targets: []*target.Target{
{
ImageName: ostreeImgType.Filename(),
OsbuildArtifact: target.OsbuildArtifact{
ExportFilename: ostreeImgType.Filename(),
ExportName: ostreeImgType.Exports()[0],
},
Name: target.TargetNameWorkerServer,
Options: &target.WorkerServerTargetOptions{},
},
},
},
Packages: dnfjson_mock.BaseDeps(),
}
ostreeOptionsOther := ostree.ImageOptions{ImageRef: otherRef, URL: ostreeRepoOther.Server.URL}
ostreeManifestOther, _, err := ostreeImgType.Manifest(nil, distro.ImageOptions{OSTree: &ostreeOptionsOther}, nil, nil)
require.NoError(t, err)
rPkgs, rContainers, rCommits = ResolveContent(ostreeManifestOther.GetPackageSetChains(), ostreeManifestOther.GetContainerSourceSpecs(), ostreeManifestOther.GetOSTreeSourceSpecs())
omfo, err := ostreeManifest.Serialize(rPkgs, rContainers, rCommits, nil)
require.NoError(t, err)
expectedComposeOSTreeOther := &store.Compose{
Blueprint: &blueprint.Blueprint{
Name: "test",
Version: "0.0.0",
Packages: []blueprint.Package{},
Modules: []blueprint.Package{},
Groups: []blueprint.Group{},
Customizations: nil,
},
ImageBuild: store.ImageBuild{
QueueStatus: common.IBWaiting,
ImageType: ostreeImgType,
Size: ostreeImgType.Size(0),
Manifest: omfo,
Targets: []*target.Target{
{
ImageName: ostreeImgType.Filename(),
OsbuildArtifact: target.OsbuildArtifact{
ExportFilename: ostreeImgType.Filename(),
ExportName: ostreeImgType.Exports()[0],
},
Name: target.TargetNameWorkerServer,
Options: &target.WorkerServerTargetOptions{},
},
},
},
Packages: dnfjson_mock.BaseDeps(),
}
// For 2nd distribution
distro2 := test_distro.DistroFactory(testDistro2Name)
require.NotNil(t, distro2)
arch2, err := distro2.GetArch(test_distro.TestArchName)
require.NoError(t, err)
imgType2, err := arch2.GetImageType(test_distro.TestImageTypeName)
require.NoError(t, err)
manifest2, _, err := imgType.Manifest(nil, distro.ImageOptions{}, nil, nil)
require.NoError(t, err)
rPkgs, rContainers, rCommits = ResolveContent(manifest2.GetPackageSetChains(), manifest2.GetContainerSourceSpecs(), manifest2.GetOSTreeSourceSpecs())
mf2, err := manifest2.Serialize(rPkgs, rContainers, rCommits, nil)
require.NoError(t, err)
expectedComposeGoodDistro := &store.Compose{
Blueprint: &blueprint.Blueprint{
Name: "test-distro-2",
Version: "0.0.0",
Packages: []blueprint.Package{},
Modules: []blueprint.Package{},
Groups: []blueprint.Group{},
Customizations: nil,
Distro: testDistro2Name,
},
ImageBuild: store.ImageBuild{
QueueStatus: common.IBWaiting,
ImageType: imgType2,
Size: imgType2.Size(0),
Manifest: mf2,
Targets: []*target.Target{
{
ImageName: imgType2.Filename(),
OsbuildArtifact: target.OsbuildArtifact{
ExportFilename: imgType2.Filename(),
ExportName: imgType2.Exports()[0],
},
Name: target.TargetNameWorkerServer,
Options: &target.WorkerServerTargetOptions{},
},
},
},
Packages: dnfjson_mock.BaseDeps(),
}
var cases = map[string]struct {
External bool
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
ExpectedCompose *store.Compose
IgnoreFields []string
}{
"bad-request": {
true,
"POST",
"/api/v0/compose",
fmt.Sprintf(`{"blueprint_name": "http-server","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
http.StatusBadRequest,
`{"status":false,"errors":[{"id":"UnknownBlueprint","msg":"Unknown blueprint name: http-server"}]}`,
nil,
[]string{"build_id", "warnings"},
},
"local": {
false,
"POST",
"/api/v0/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
http.StatusOK,
`{"status": true}`,
expectedComposeLocal,
[]string{"build_id", "warnings"},
},
"aws": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type":"%s","branch":"master","upload":{"image_name":"test_upload","provider":"aws","settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}}`, test_distro.TestImageTypeName),
http.StatusOK,
`{"status": true}`,
expectedComposeLocalAndAws,
[]string{"build_id", "warnings"},
},
"good-distro": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test-distro-2","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
http.StatusOK,
`{"status": true}`,
expectedComposeGoodDistro,
[]string{"build_id", "warnings"},
},
"unknown-distro": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test-fedora-1","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
http.StatusBadRequest,
`{"status": false,"errors":[{"id":"DistroError", "msg":"Unknown distribution: fedora-1 for arch test_arch"}]}`,
nil,
[]string{"build_id", "warnings"},
},
"bad-arch": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test-badarch","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
http.StatusBadRequest,
`{"status": false,"errors":[{"id":"DistroError", "msg":"Unknown distribution: test-distro-1 for arch badarch"}]}`,
nil,
[]string{"build_id", "warnings"},
},
"cross-arch": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test-crossarch","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
http.StatusBadRequest,
`{"status": false,"errors":[{"id":"ComposePushErrored", "msg":"No worker for arch 'test_arch2' available"}]}`,
nil,
[]string{"build_id", "warnings"},
},
"imaginary": {
false,
"POST",
"/api/v1/compose",
`{"blueprint_name": "test-distro-2","compose_type": "imaginary_type","branch": "master"}`,
http.StatusBadRequest,
`{"status": false,"errors":[{"id":"ComposeError", "msg":"Failed to get compose type \"imaginary_type\": invalid image type: imaginary_type"}]}`,
nil,
[]string{"build_id", "warnings"},
},
// === OSTree params ===
// Ref + Parent = error (parent without URL)
"ostree-no-url": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type":"%s","branch":"master","ostree":{"ref":"refid","parent":"parentid","url":""}}`, test_distro.TestImageTypeOSTree),
http.StatusBadRequest,
`{"status": false, "errors":[{"id":"ManifestCreationFailed","msg":"failed to initialize osbuild manifest: ostree parent ref specified, but no URL to retrieve it"}]}`,
expectedComposeOSTree,
[]string{"build_id", "warnings"},
},
// Valid Ref + URL = OK
"ostree-valid": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type":"%s","branch":"master","ostree":{"ref":"%s","parent":"","url":"%s"}}`, test_distro.TestImageTypeOSTree, ostreeRepoOther.OSTreeRef, ostreeRepoOther.Server.URL),
http.StatusOK,
`{"status": true}`,
expectedComposeOSTreeOther,
[]string{"build_id", "warnings"},
},
// Ref + invalid URL = error
"ostree-invalid-url": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type":"%s","branch":"master","ostree":{"ref":"whatever","parent":"","url":"invalid-url"}}`, test_distro.TestImageTypeOSTree),
http.StatusBadRequest,
`{"status":false,"errors":[{"id":"OSTreeOptionsError","msg":"error sending request to ostree repository \"invalid-url/refs/heads/whatever\": Get \"invalid-url/refs/heads/whatever\": unsupported protocol scheme \"\""}]}`,
nil,
[]string{"build_id", "warnings"},
},
// Bad Ref + URL = error
"ostree-bad-ref": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type":"%s","branch":"master","ostree":{"ref":"/bad/ref","parent":"","url":"http://ostree/"}}`, test_distro.TestImageTypeOSTree),
http.StatusBadRequest,
`{"status":false,"errors":[{"id":"OSTreeOptionsError","msg":"Invalid ostree ref or commit \"/bad/ref\""}]}`,
expectedComposeOSTree,
[]string{"build_id", "warnings"},
},
// Incorrect Ref + URL = the parameters are okay, but the ostree repo returns 404
"ostree-404": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type":"%s","branch":"master","ostree":{"ref":"%s","parent":"","url":"%s"}}`, test_distro.TestImageTypeOSTree, "the/wrong/ref", ostreeRepoDefault.Server.URL),
http.StatusBadRequest,
fmt.Sprintf(`{"status":false,"errors":[{"id":"OSTreeOptionsError","msg":"ostree repository \"%s/refs/heads/the/wrong/ref\" returned status: 404 Not Found"}]}`, ostreeRepoDefault.Server.URL),
expectedComposeOSTree,
[]string{"build_id", "warnings"},
},
// Ref + Parent + URL = OK
"ostree-all-params": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type":"%s","branch":"master","ostree":{"ref":"%s","parent":"%s","url":"%s"}}`, test_distro.TestImageTypeOSTree, "the/new/ref", ostreeRepoOther.OSTreeRef, ostreeRepoOther.Server.URL),
http.StatusOK,
`{"status":true}`,
expectedComposeOSTreeOther,
[]string{"build_id", "warnings"},
},
// Parent + URL = OK
"ostree-parent-url": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type":"%s","branch":"master","ostree":{"ref":"","parent":"%s","url":"%s"}}`, test_distro.TestImageTypeOSTree, ostreeRepoDefault.OSTreeRef, ostreeRepoDefault.Server.URL),
http.StatusOK,
`{"status":true}`,
expectedComposeOSTree,
[]string{"build_id", "warnings"},
},
// URL only = OK (uses default ref, so we need to specify URL for ostree repo with default ref)
"ostree-url-only": {
false,
"POST",
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type":"%s","branch":"master","ostree":{"ref":"","parent":"","url":"%s"}}`, test_distro.TestImageTypeOSTree, ostreeRepoDefault.Server.URL),
http.StatusOK,
`{"status":true}`,
expectedComposeOSTree,
[]string{"build_id", "warnings"},
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.NoComposesFixture, nil)
t.Cleanup(sf.Cleanup)
_, err = api.workers.RegisterWorker("", arch.Name())
require.NoError(t, err)
test.TestRoute(t, api, c.External, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, c.IgnoreFields...)
if c.ExpectedStatus != http.StatusOK {
return
}
composes := sf.Store.GetAllComposes()
require.Equalf(t, 1, len(composes), "%s: %s: bad compose count in store", name, c.Path)
// I have no idea how to get the compose in better way
var composeStruct store.Compose
for _, c := range composes {
composeStruct = c
break
}
require.NotNilf(t, composeStruct.ImageBuild.Manifest, "%s: %s: the compose in the store did not contain a blueprint", name, c.Path)
if diff := cmp.Diff(composeStruct, *c.ExpectedCompose, test.IgnoreDates(), test.IgnoreUuids(), test.Ignore("Targets.Options.Location"), test.CompareImageTypes()); diff != "" {
t.Errorf("%s: %s: compose in store isn't the same as expected, diff:\n%s", name, c.Path, diff)
}
})
}
}
func TestComposeDelete(t *testing.T) {
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
t.Skip("This test is for internal testing only")
}
var cases = []struct {
Path string
ExpectedJSON string
ExpectedIDsInStore []string
}{
{"/api/v0/compose/delete/30000000-0000-0000-0000-000000000002", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000002","status":true}],"errors":[]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000003", "30000000-0000-0000-0000-000000000004"}},
{"/api/v0/compose/delete/30000000-0000-0000-0000-000000000002,30000000-0000-0000-0000-000000000003", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000002","status":true},{"uuid":"30000000-0000-0000-0000-000000000003","status":true}],"errors":[]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000004"}},
{"/api/v0/compose/delete/30000000-0000-0000-0000-000000000003,30000000-0000-0000-0000-000000000000", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000003","status":true}],"errors":[{"id":"BuildInWrongState","msg":"Compose 30000000-0000-0000-0000-000000000000 is not in FINISHED or FAILED."}]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000002", "30000000-0000-0000-0000-000000000004"}},
{"/api/v0/compose/delete/30000000-0000-0000-0000-000000000003,30000000-0000-0000-0000", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000003","status":true}],"errors":[{"id":"UnknownUUID","msg":"30000000-0000-0000-0000 is not a valid uuid"}]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000002", "30000000-0000-0000-0000-000000000004"}},
{"/api/v0/compose/delete/30000000-0000-0000-0000-000000000003,42000000-0000-0000-0000-000000000000", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000003","status":true}],"errors":[{"id":"UnknownUUID","msg":"compose 42000000-0000-0000-0000-000000000000 doesn't exist"}]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000002", "30000000-0000-0000-0000-000000000004"}},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, false, "DELETE", c.Path, "", http.StatusOK, c.ExpectedJSON)
idsInStore := []string{}
for id := range sf.Store.GetAllComposes() {
idsInStore = append(idsInStore, id.String())
}
require.ElementsMatch(t, c.ExpectedIDsInStore, idsInStore, "%s: composes in store are different", c.Path)
})
}
}
func TestComposeStatus(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/status/30000000-0000-0000-0000-000000000000,30000000-0000-0000-0000-000000000002", ``, http.StatusOK, fmt.Sprintf(`{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"WAITING","job_created":1574857140},{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/status/*", ``, http.StatusOK, fmt.Sprintf(`{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"WAITING","job_created":1574857140},{"id":"30000000-0000-0000-0000-000000000001","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"RUNNING","job_created":1574857140,"job_started":1574857140},{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000004","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/status/*?name=test", ``, http.StatusOK, fmt.Sprintf(`{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"WAITING","job_created":1574857140},{"id":"30000000-0000-0000-0000-000000000001","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"RUNNING","job_created":1574857140,"job_started":1574857140},{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000004","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/status/*?status=FINISHED", ``, http.StatusOK, fmt.Sprintf(`{"uuids":[{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000004","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", fmt.Sprintf("/api/v0/compose/status/*?type=%s", test_distro.TestImageTypeName), ``, http.StatusOK, fmt.Sprintf(`{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"WAITING","job_created":1574857140},{"id":"30000000-0000-0000-0000-000000000001","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"RUNNING","job_created":1574857140,"job_started":1574857140},{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000004","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/status/30000000-0000-0000-0000-000000000000", ``, http.StatusOK, fmt.Sprintf(`{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"WAITING","job_created":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","bucket":"clay","key":"imagekey"}}]}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BadJobJSONFixture, "GET", "/api/v0/compose/status/*", ``, http.StatusOK, fmt.Sprintf(`{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"WAITING","job_created":1574857140},{"id":"30000000-0000-0000-0000-000000000001","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"RUNNING","job_created":1574857140,"job_started":1574857140},{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000004","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BadJobJSONFixture, "GET", "/api/v0/compose/status/30000000-0000-0000-0000-000000000005", ``, http.StatusOK, `{"uuids":[]}`},
}
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
t.Skip("This test is for internal testing only")
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, "id", "job_created", "job_started")
}
}
func TestComposeInfo(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/info/30000000-0000-0000-0000-000000000000", ``, http.StatusOK, fmt.Sprintf(`{"id":"30000000-0000-0000-0000-000000000000","config":"","blueprint":{"name":"test","description":"","distro":"","version":"0.0.0","packages":[],"modules":[],"groups":[]},"commit":"","deps":{"packages":[]},"compose_type":"%s","queue_status":"WAITING","image_size":0}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/info/30000000-0000-0000-0000-000000000000", ``, http.StatusOK, fmt.Sprintf(`{"id":"30000000-0000-0000-0000-000000000000","config":"","blueprint":{"name":"test","description":"","distro":"","version":"0.0.0","packages":[],"modules":[],"groups":[]},"commit":"","deps":{"packages":[]},"compose_type":"%s","queue_status":"WAITING","image_size":0,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","bucket":"clay","key":"imagekey"}}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/info/30000000-0000-0000-0000", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownUUID","msg":"30000000-0000-0000-0000 is not a valid build uuid"}]}`},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/info/42000000-0000-0000-0000-000000000000", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownUUID","msg":"42000000-0000-0000-0000-000000000000 is not a valid build uuid"}]}`},
}
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
t.Skip("This test is for internal testing only")
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
}
}
func TestComposeLogs(t *testing.T) {
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
t.Skip("This test is for internal testing only")
}
emptyManifest := `{"version":"2","pipelines":[{"name":"build"},{"name":"os"}],"sources":{}}`
var successCases = []struct {
Path string
ExpectedContentDisposition string
ExpectedContentType string
ExpectedFileName string
ExpectedFileContent string
}{
{"/api/v0/compose/logs/30000000-0000-0000-0000-000000000002", "attachment; filename=30000000-0000-0000-0000-000000000002-logs.tar", "application/x-tar", "logs/osbuild.log", "The compose result is empty.\n"},
{"/api/v1/compose/logs/30000000-0000-0000-0000-000000000002", "attachment; filename=30000000-0000-0000-0000-000000000002-logs.tar", "application/x-tar", "logs/osbuild.log", "The compose result is empty.\n"},
{"/api/v0/compose/metadata/30000000-0000-0000-0000-000000000002", "attachment; filename=30000000-0000-0000-0000-000000000002-metadata.tar", "application/x-tar", "30000000-0000-0000-0000-000000000002.json", emptyManifest},
{"/api/v1/compose/metadata/30000000-0000-0000-0000-000000000002", "attachment; filename=30000000-0000-0000-0000-000000000002-metadata.tar", "application/x-tar", "30000000-0000-0000-0000-000000000002.json", emptyManifest},
{"/api/v0/compose/results/30000000-0000-0000-0000-000000000002", "attachment; filename=30000000-0000-0000-0000-000000000002.tar", "application/x-tar", "30000000-0000-0000-0000-000000000002.json", emptyManifest},
{"/api/v1/compose/results/30000000-0000-0000-0000-000000000002", "attachment; filename=30000000-0000-0000-0000-000000000002.tar", "application/x-tar", "30000000-0000-0000-0000-000000000002.json", emptyManifest},
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range successCases {
response := test.SendHTTP(api, false, "GET", c.Path, "")
require.Equalf(t, http.StatusOK, response.StatusCode, "%s: unexpected status code", c.Path)
require.Equalf(t, c.ExpectedContentDisposition, response.Header.Get("content-disposition"), "%s: header mismatch", c.Path)
require.Equalf(t, c.ExpectedContentType, response.Header.Get("content-type"), "%s: header mismatch", c.Path)
tr := tar.NewReader(response.Body)
h, err := tr.Next()
require.NoErrorf(t, err, "untarring failed with error")
require.Falsef(t, h.ModTime.After(time.Now()), "ModTime cannot be in the future")
require.Equalf(t, c.ExpectedFileName, h.Name, "%s: unexpected file name", c.Path)
var buffer bytes.Buffer
// vulnerability already tested
/* #nosec G110 */
_, err = io.Copy(&buffer, tr)
require.NoErrorf(t, err, "cannot copy untar result")
require.Equalf(t, c.ExpectedFileContent, buffer.String(), "%s: unexpected log content", c.Path)
}
var failureCases = []struct {
Path string
ExpectedJSON string
}{
{"/api/v1/compose/logs/30000000-0000-0000-0000", `{"status":false,"errors":[{"id":"UnknownUUID","msg":"30000000-0000-0000-0000 is not a valid build uuid"}]}`},
{"/api/v1/compose/logs/42000000-0000-0000-0000-000000000000", `{"status":false,"errors":[{"id":"UnknownUUID","msg":"Compose 42000000-0000-0000-0000-000000000000 doesn't exist"}]}`},
{"/api/v1/compose/logs/30000000-0000-0000-0000-000000000000", `{"status":false,"errors":[{"id":"BuildInWrongState","msg":"Build 30000000-0000-0000-0000-000000000000 not in FINISHED or FAILED state."}]}`},
{"/api/v1/compose/metadata/30000000-0000-0000-0000-000000000000", `{"status":false,"errors":[{"id":"BuildInWrongState","msg":"Build 30000000-0000-0000-0000-000000000000 is in wrong state: WAITING"}]}`},
{"/api/v1/compose/results/30000000-0000-0000-0000-000000000000", `{"status":false,"errors":[{"id":"BuildInWrongState","msg":"Build 30000000-0000-0000-0000-000000000000 is in wrong state: WAITING"}]}`},
{"/api/v1/compose/metadata/30000000-0000-0000-0000-000000000001", `{"status":false,"errors":[{"id":"BuildInWrongState","msg":"Build 30000000-0000-0000-0000-000000000001 is in wrong state: RUNNING"}]}`},
{"/api/v1/compose/results/30000000-0000-0000-0000-000000000001", `{"status":false,"errors":[{"id":"BuildInWrongState","msg":"Build 30000000-0000-0000-0000-000000000001 is in wrong state: RUNNING"}]}`},
}
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
t.Skip("This test is for internal testing only")
}
for _, c := range failureCases {
test.TestRoute(t, api, false, "GET", c.Path, "", http.StatusBadRequest, c.ExpectedJSON)
}
}
func TestComposeLog(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Method string
Path string
ExpectedStatus int
ExpectedResponse string
}{
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/log/30000000-0000-0000-0000-000000000000", http.StatusOK, `{"status":false,"errors":[{"id":"BuildInWrongState","msg":"Build 30000000-0000-0000-0000-000000000000 has not started yet. No logs to view."}]}` + "\n"},
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/log/30000000-0000-0000-0000-000000000001", http.StatusOK, `Build 30000000-0000-0000-0000-000000000001 is still running.` + "\n"},
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/log/30000000-0000-0000-0000-000000000002", http.StatusOK, `The compose result is empty.` + "\n"},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/log/30000000-0000-0000-0000-000000000002", http.StatusOK, `The compose result is empty.` + "\n"},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/log/30000000-0000-0000-0000", http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownUUID","msg":"30000000-0000-0000-0000 is not a valid build uuid"}]}` + "\n"},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/log/42000000-0000-0000-0000-000000000000", http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownUUID","msg":"Compose 42000000-0000-0000-0000-000000000000 doesn't exist"}]}` + "\n"},
}
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
t.Skip("This test is for internal testing only")
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.TestNonJsonRoute(t, api, false, "GET", c.Path, "", c.ExpectedStatus, c.ExpectedResponse)
}
}
func TestComposeQueue(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/queue", ``, http.StatusOK, fmt.Sprintf(`{"new":[{"blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"WAITING"}],"run":[{"blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"RUNNING"}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/queue", ``, http.StatusOK, fmt.Sprintf(`{"new":[{"blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"WAITING","uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","bucket":"clay","key":"imagekey"}}]}],"run":[{"blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"RUNNING"}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.NoComposesFixture, "GET", "/api/v0/compose/queue", ``, http.StatusOK, `{"new":[],"run":[]}`},
{rpmmd_mock.BadJobJSONFixture, "GET", "/api/v0/compose/queue", ``, http.StatusOK, fmt.Sprintf(`{"new":[{"blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"WAITING"}],"run":[{"blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"RUNNING"}]}`, test_distro.TestImageTypeName)},
}
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
t.Skip("This test is for internal testing only")
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, "id", "job_created", "job_started")
})
}
}
func TestComposeFinished(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/finished", ``, http.StatusOK, fmt.Sprintf(`{"finished":[{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000004","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/finished", ``, http.StatusOK, fmt.Sprintf(`{"finished":[{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"FINISHED","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","bucket":"clay","key":"imagekey"}}]},{"id":"30000000-0000-0000-0000-000000000004","blueprint":"test","version":"0.0.0","compose_type":"%[1]s","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"FINISHED","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","bucket":"clay","key":"imagekey"}}]}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.NoComposesFixture, "GET", "/api/v0/compose/finished", ``, http.StatusOK, `{"finished":[]}`},
}
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
t.Skip("This test is for internal testing only")
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, "id", "job_created", "job_started")
})
}
}
func TestComposeFailed(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/failed", ``, http.StatusOK, fmt.Sprintf(`{"failed":[{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"%s","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/failed", ``, http.StatusOK, fmt.Sprintf(`{"failed":[{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"%s","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"FAILED","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","bucket":"clay","key":"imagekey"}}]}]}`, test_distro.TestImageTypeName)},
{rpmmd_mock.NoComposesFixture, "GET", "/api/v0/compose/failed", ``, http.StatusOK, `{"failed":[]}`},
}
if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
t.Skip("This test is for internal testing only")
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, "id", "job_created", "job_started")
})
}
}
func TestSourcesNew(t *testing.T) {
var cases = []struct {
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{"POST", "/api/v0/projects/source/new", ``, http.StatusBadRequest, `{"errors": [{"id": "ProjectsError","msg": "Missing source"}],"status":false}`},
// Bad JSON, missing quote after name
{"POST", "/api/v0/projects/source/new", `{"name: "fish","url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false}`, http.StatusBadRequest, `{"errors": [{"id": "ProjectsError","msg": "Problem parsing POST body: invalid character 'f' after object key"}],"status":false}`},
{"POST", "/api/v0/projects/source/new", `{"name": "fish","url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false}`, http.StatusOK, `{"status":true}`},
{"POST", "/api/v0/projects/source/new", `{"url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false}`, http.StatusBadRequest, `{"errors": [{"id": "ProjectsError","msg": "Problem parsing POST body: 'name' field is missing from request"}],"status":false}`},
{"POST", "/api/v0/projects/source/new", `{"name": "fish", "type": "yum-baseurl","check_ssl": false,"check_gpg": false}`, http.StatusBadRequest, `{"errors": [{"id": "ProjectsError","msg": "Problem parsing POST body: 'url' field is missing from request"}],"status":false}`},
{"POST", "/api/v0/projects/source/new", `{"name": "fish", "url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","check_ssl": false,"check_gpg": false}`, http.StatusBadRequest, `{"errors": [{"id": "ProjectsError","msg": "Problem parsing POST body: 'type' field is missing from request"}],"status":false}`},
{"POST", "/api/v0/projects/source/new", `{"name": "test-id", "url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false}`, http.StatusBadRequest, `{"errors": [{"id": "SystemSource","msg": "test-id is a system source, it cannot be changed."}],"status":false}`},
{"POST", "/api/v1/projects/source/new", `{"id": "test-id", "name": "test system repo", "url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false}`, http.StatusBadRequest, `{"errors": [{"id": "SystemSource","msg": "test-id is a system source, it cannot be changed."}],"status":false}`},
{"POST", "/api/v1/projects/source/new", `{"id": "fish","name":"fish repo","url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false,"distros":["test-distro-1", "test-distro-2"]}`, http.StatusOK, `{"status":true}`},
{"POST", "/api/v1/projects/source/new", `{"id": "fish","name":"fish repo","url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false,"distros":["fedora-1"]}`, http.StatusBadRequest, `{"status":false, "errors":[{"id":"ProjectsError", "msg":"Invalid distributions: fedora-1"}]}`},
{"POST", "/api/v1/projects/source/new", `{"id": "fish","name":"fish repo","url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": true,"check_repogpg":true,"gpgkeys": ["https://repourl/path/to/key.pub"]}`, http.StatusOK, `{"status":true}`},
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
test.SendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``)
}
}
func TestSourcesNewTomlV0(t *testing.T) {
sources := []string{`
name = "fish"
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = false
`, `[fish]
name = "fish"
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = false
`}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, source := range sources {
req := httptest.NewRequest("POST", "/api/v0/projects/source/new", bytes.NewReader([]byte(source)))
req.Header.Set("Content-Type", "text/x-toml")
recorder := httptest.NewRecorder()
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusOK, r.StatusCode)
test.SendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``)
}
}
// Empty TOML, and invalid TOML should return an error
func TestSourcesNewWrongTomlV0(t *testing.T) {
sources := []string{``, `
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = false
`}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, source := range sources {
req := httptest.NewRequest("POST", "/api/v0/projects/source/new", bytes.NewReader([]byte(source)))
req.Header.Set("Content-Type", "text/x-toml")
recorder := httptest.NewRecorder()
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
}
// TestSourcesNewTomlV1 tests the v1 sources API with id and name
func TestSourcesNewTomlV1(t *testing.T) {
sources := []string{`
id = "fish"
name = "fish or cut bait"
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = false
`, `[fish]
name = "fish or cut bait"
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = false
`, `[fish]
id = "fish"
name = "fish or cut bait"
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = false
`, `id = "fish"
name = "fish or cut bait"
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = true
check_repogpg = true
gpgkeys = ['''-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.10 (GNU/Linux)
mQENBEt+xXMBCACkA1ZtcO4H7ZUG/0aL4RlZIozsorXzFrrTAsJEHvdy+rHCH3xR
cFz6IMbfCOdV+oKxlDP7PS0vWKfqxwkenOUut5o9b32uDdFMW4IbFXEQ94AuSQpS
jo8PlVMm/51pmmRxdJzyPnr0YD38mVK6qUEYLI/4zXSgFk493GT8Y4m3N18O/+ye
PnOOItj7qbrCMASoBx1TG8Zdg8ufehMnfb85x4xxAebXkqJQpEVTjt4lj4p6BhrW
R+pIW/nBUrz3OsV7WwPKjSLjJtTJFxYX+RFSCqOdfusuysoOxpIHOx1WxjGUOB5j
fnhmq41nWXf8ozb58zSpjDrJ7jGQ9pdUpAtRABEBAAG0HkJyaWFuIEMuIExhbmUg
PGJjbEByZWRoYXQuY29tPokBOAQTAQIAIgUCS37FcwIbAwYLCQgHAwIGFQgCCQoL
BBYCAwECHgECF4AACgkQEX6MFo7+On9dgAf9Hi2K1MKcmLkDeSUIXkXIAw0nAzl2
UDGLWEdDqAgFxP6UaCVtOIRCr7z4EDOQoxD7mkdekbH2W5GcTO4h8MQBHYD9EkY7
H/lTKchlFfsmafOoA3Y/tDLPKu+OIfH9Mqn2Mf7wMYGrnWSRNKYgvC5zkMgkhoPU
mSPPHyBabsdS/Kg5ZAf43ac/MXY9V8Mk6zqbBlj6QYqjJ0nBD6vwozrDQ5gJtDUL
mQho13zPn4lBJl9YJVjcgRB2WbzgSZOln0DfV22Seai66vnr5NyaOIw5B9QLSNhN
EaPFswEDLKCsns9dkDuGFX52/Mt/i7JySvwhMBqHElPzWmwCHeY45M8gBYhGBBAR
AgAGBQJLfsbpAAoJECH7Y/6XEsLNuasAn0Q0jB4Ea/95EREUkCFTm9L6nOpAAJ9t
QzwGXhrLFZzOdRWYiWcCQbX5/7kBDQRLfsVzAQgAvN5jr95pJthv2w9co9/7omhM
5rAnr9WJfbMLLiUfPPUvpL24RGO6SKy03aiVTUjlaHc+cGqOciwnNKMCSt+noyG2
kNnAESTDtCivpsjonaFP8jA3TqL0QK+yzBRKJnMnLEY1nWE1FtkMRccXvzi0Z/XQ
VhiWQyTvDFoKtepBFrH9UqWbNHyki22aighumUsW01pcPH2ogSj+HR01r7SfI/y2
EkE6loHQfCDycHmlqYV+X6GZEvf1qu2+EHEQChsHIAxWyshsxM/ZPmx/8e5S3Xmj
l7h/6E9wcsIpvnf504sLX5j4Km9I5HgJSRxHxgRPpqJ2/XiClAJanO5gCw0RdQAR
AQABiQEfBBgBAgAJBQJLfsVzAhsMAAoJEBF+jBaO/jp/SqEH/iArzrfVOhZQGuy1
KmG0+/FdJGqAEHP5HWpsaeYJok1VmhTPZd4IVFBz/bGJYyvsrPU0pJ6QLkdGxNnb
KulJocgkW5MKEL/CRc54ESKwYngigmbY4qLwhS+gB3BJg1TvoHD810MSj4wdxNNo
6JQmFmuoDsLRwaRYbKQDz95XXoGQtmV1o57T05WkLuC5OmHqnWv3rggVC8madpUJ
moUUvUWgU1qyXe3PrgMGFOibWIl7lPZ08nzKXBRvSK/xoTGxl+570AevfVHMu5Uk
Yu2U6D6/DYohtTYp0s1ekS5KQkCJM7lfqecDsQhfVfOfR0w4aF8k8u3HmWdOfUz+
9+2ZsBo=
=myjM
-----END PGP PUBLIC KEY BLOCK-----''']
`}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, source := range sources {
req := httptest.NewRequest("POST", "/api/v1/projects/source/new", bytes.NewReader([]byte(source)))
req.Header.Set("Content-Type", "text/x-toml")
recorder := httptest.NewRecorder()
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusOK, r.StatusCode)
test.SendHTTP(api, true, "DELETE", "/api/v1/projects/source/delete/fish", ``)
}
}
func TestSourcesInfoTomlV1(t *testing.T) {
source := `
id = "fish"
name = "fish"
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
rhsm = true
`
sourceStr := `{"check_gpg":false,"check_repogpg":false,"check_ssl":false,"id":"fish","name":"fish","rhsm":true,"system":false,"type":"yum-baseurl","url":"https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"}`
req := httptest.NewRequest("POST", "/api/v1/projects/source/new", bytes.NewReader([]byte(source)))
req.Header.Set("Content-Type", "text/x-toml")
recorder := httptest.NewRecorder()
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusOK, r.StatusCode)
test.TestRoute(t, api, true, "GET", "/api/v1/projects/source/info/fish", ``, 200, `{"sources":{"fish":`+sourceStr+`},"errors":[]}`)
test.TestRoute(t, api, true, "GET", "/api/v1/projects/source/info/fish?format=json", ``, 200, `{"sources":{"fish":`+sourceStr+`},"errors":[]}`)
}
func TestSourcesInfoGPGKeysV1(t *testing.T) {
sourceStr := `{"id":"fish","name":"fish repo","type":"yum-baseurl","url":"https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","check_gpg":true,"check_repogpg":true,"check_ssl":false,"gpgkeys":["https://repourl/path/to/key.pub"],"rhsm":false,"system":false}`
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
test.SendHTTP(api, true, "POST", "/api/v1/projects/source/new", sourceStr)
test.TestRoute(t, api, true, "GET", "/api/v1/projects/source/info/fish", ``, 200, `{"sources":{"fish":`+sourceStr+`},"errors":[]}`)
test.TestRoute(t, api, true, "GET", "/api/v1/projects/source/info/fish?format=json", ``, 200, `{"sources":{"fish":`+sourceStr+`},"errors":[]}`)
}
// TestSourcesNewWrongTomlV1 Tests that Empty TOML, and invalid TOML should return an error
func TestSourcesNewWrongTomlV1(t *testing.T) {
sources := []string{``, `
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = false
`, `
[fish]
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = false
`, `
id = "fish"
url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
type = "yum-baseurl"
check_ssl = false
check_gpg = false
`}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, source := range sources {
req := httptest.NewRequest("POST", "/api/v1/projects/source/new", bytes.NewReader([]byte(source)))
req.Header.Set("Content-Type", "text/x-toml")
recorder := httptest.NewRecorder()
api.ServeHTTP(recorder, req)
r := recorder.Result()
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
}
func TestSourcesInfo(t *testing.T) {
sourceStr := `{"name":"fish","type":"yum-baseurl","url":"https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","check_gpg":false,"check_ssl":false,"system":false}`
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
test.SendHTTP(api, true, "POST", "/api/v0/projects/source/new", sourceStr)
test.TestRoute(t, api, true, "GET", "/api/v0/projects/source/info/fish", ``, 200, `{"sources":{"fish":`+sourceStr+`},"errors":[]}`)
test.TestRoute(t, api, true, "GET", "/api/v0/projects/source/info/fish?format=json", ``, 200, `{"sources":{"fish":`+sourceStr+`},"errors":[]}`)
test.TestRoute(t, api, true, "GET", "/api/v0/projects/source/info/fish?format=son", ``, 400, `{"status":false,"errors":[{"id":"InvalidChars","msg":"invalid format parameter: son"}]}`)
}
func TestSourcesInfoToml(t *testing.T) {
sourceStr := `{"name":"fish","type":"yum-baseurl","url":"https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","check_gpg":false,"check_ssl":false,"system":false}`
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
test.SendHTTP(api, true, "POST", "/api/v0/projects/source/new", sourceStr)
req := httptest.NewRequest("GET", "/api/v0/projects/source/info/fish?format=toml", nil)
recorder := httptest.NewRecorder()
api.ServeHTTP(recorder, req)
resp := recorder.Result()
var sources map[string]store.SourceConfig
_, err := toml.NewDecoder(resp.Body).Decode(&sources)
require.NoErrorf(t, err, "error decoding toml file")
expected := map[string]store.SourceConfig{
"fish": {
Name: "fish",
Type: "yum-baseurl",
URL: "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/",
},
}
require.Equal(t, expected, sources)
}
func TestSourcesDelete(t *testing.T) {
var cases = []struct {
Method string
Path string
Body string
ExpectedStatus int
ExpectedJSON string
}{
{"DELETE", "/api/v0/projects/source/delete/", ``, http.StatusNotFound, `{"status":false,"errors":[{"code":404,"id":"HTTPError","msg":"Not Found"}]}`},
{"DELETE", "/api/v0/projects/source/delete/fish", ``, http.StatusOK, `{"status":true}`},
{"DELETE", "/api/v0/projects/source/delete/unknown", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownSource","msg":"unknown is not a valid source."}]}`},
}
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, rpmmd_mock.BaseFixture, nil)
t.Cleanup(sf.Cleanup)
for _, c := range cases {
test.SendHTTP(api, true, "POST", "/api/v0/projects/source/new", `{"name": "fish","url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false}`)
test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
test.SendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``)
}
}
func TestProjectsDepsolve(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Path string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.NonExistingPackage, "/api/v0/projects/depsolve/fash", http.StatusBadRequest, `{"status":false,"errors":[{"id":"ProjectsError","msg":"BadRequest: running osbuild-depsolve-dnf failed:\nDNF error occurred: MarkingErrors: Error occurred when marking packages for installation: Problems in request:\nmissing packages: fash"}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/projects/depsolve/fish", http.StatusOK, `{"projects":[{"name":"dep-package3","epoch":7,"version":"3.0.3","release":"1.fc30","arch":"x86_64","check_gpg":true, "checksum":"sha256:62278d360aa5045eb202af39fe85743a4b5615f0c9c7439a04d75d785db4c720"},{"name":"dep-package1","epoch":0,"version":"1.33","release":"2.fc30","arch":"x86_64","check_gpg":true, "checksum":"sha256:fe3951d112c3b1c84dc8eac57afe0830df72df1ca0096b842f4db5d781189893"},{"name":"dep-package2","epoch":0,"version":"2.9","release":"1.fc30","arch":"x86_64","check_gpg":true, "checksum":"sha256:5797c0b0489681596b5b3cd7165d49870b85b69d65e08770946380a3dcd49ea2"}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/projects/depsolve/fish?distro=test-distro-2", http.StatusOK, `{"projects":[{"name":"dep-package3","epoch":7,"version":"3.0.3","release":"1.fc30","arch":"x86_64","check_gpg":true, "checksum":"sha256:62278d360aa5045eb202af39fe85743a4b5615f0c9c7439a04d75d785db4c720"},{"name":"dep-package1","epoch":0,"version":"1.33","release":"2.fc30","arch":"x86_64","check_gpg":true, "checksum":"sha256:fe3951d112c3b1c84dc8eac57afe0830df72df1ca0096b842f4db5d781189893"},{"name":"dep-package2","epoch":0,"version":"2.9","release":"1.fc30","arch":"x86_64","check_gpg":true, "checksum":"sha256:5797c0b0489681596b5b3cd7165d49870b85b69d65e08770946380a3dcd49ea2"}]}`},
{rpmmd_mock.BadDepsolve, "/api/v0/projects/depsolve/go2rpm", http.StatusBadRequest, `{"status":false,"errors":[{"id":"ProjectsError","msg":"BadRequest: running osbuild-depsolve-dnf failed:\nDNF error occurred: DepsolveError: There was a problem depsolving ['go2rpm']: \n Problem: conflicting requests\n - nothing provides askalono-cli needed by go2rpm-1-4.fc31.noarch"}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/projects/depsolve/fish?distro=fedora-1", http.StatusBadRequest, `{"status":false,"errors":[{"id":"DistroError","msg":"Invalid distro: fedora-1"}]}`},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
})
}
}
func TestProjectsInfo(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Path string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, "/api/v0/projects/info", http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownProject","msg":"No packages specified."}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/projects/info/", http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownProject","msg":"No packages specified."}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/projects/info/nonexistingpkg", http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownProject","msg":"No packages have been found."}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/projects/info/*", http.StatusOK, projectsInfoResponse},
{rpmmd_mock.BaseFixture, "/api/v0/projects/info/package2*,package16", http.StatusOK, projectsInfoFilteredResponse},
{rpmmd_mock.BadFetch, "/api/v0/projects/info/badpackage1", http.StatusBadRequest, `{"status":false,"errors":[{"id":"ProjectsError","msg":"msg: DNF error occurred: FetchError: There was a problem when fetching packages."}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/projects/info/package16?distro=test-distro-2", http.StatusOK, projectsInfoPackage16Response},
{rpmmd_mock.BaseFixture, "/api/v0/projects/info/package16?distro=fedora-1", http.StatusBadRequest, `{"status":false,"errors":[{"id":"DistroError","msg":"Invalid distro: fedora-1"}]}`},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
})
}
}
func TestModulesInfo(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Path string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, "/api/v0/modules/info", http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownModule","msg":"No packages specified."}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/modules/info/", http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownModule","msg":"No packages specified."}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/modules/info/nonexistingpkg", http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownModule","msg":"No packages have been found."}]}`},
{rpmmd_mock.BadDepsolve, "/api/v0/modules/info/baddepsolve", http.StatusBadRequest, `{"status":false,"errors":[{"id":"ModulesError","msg":"Cannot depsolve package package1: running osbuild-depsolve-dnf failed:\nDNF error occurred: DepsolveError: There was a problem depsolving ['go2rpm']: \n Problem: conflicting requests\n - nothing provides askalono-cli needed by go2rpm-1-4.fc31.noarch"}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/modules/info/package2*,package16", http.StatusOK, modulesInfoFilteredResponse},
{rpmmd_mock.BaseFixture, "/api/v0/modules/info/*", http.StatusOK, modulesInfoResponse},
{rpmmd_mock.BadFetch, "/api/v0/modules/info/badpackage1", http.StatusBadRequest, `{"status":false,"errors":[{"id":"ModulesError","msg":"msg: DNF error occurred: FetchError: There was a problem when fetching packages."}]}`},
{rpmmd_mock.BaseFixture, "/api/v1/modules/info/package2*,package16", http.StatusOK, modulesInfoFilteredResponse},
{rpmmd_mock.BaseFixture, "/api/v1/modules/info/package16?distro=test-distro-2", http.StatusOK, modulesInfoPackage16Response},
{rpmmd_mock.BaseFixture, "/api/v1/modules/info/package16?distro=fedora-1", http.StatusBadRequest, `{"status":false,"errors":[{"id":"DistroError","msg":"Invalid distro: fedora-1"}]}`},
}
for _, c := range cases {
t.Run("Path = %s", func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
})
}
}
func TestProjectsList(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Path string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, "/api/v0/projects/list", http.StatusOK, projectsListResponse},
{rpmmd_mock.BaseFixture, "/api/v0/projects/list/", http.StatusOK, projectsListResponse},
{rpmmd_mock.BadFetch, "/api/v0/projects/list/", http.StatusBadRequest, `{"status":false,"errors":[{"id":"ProjectsError","msg":"msg: DNF error occurred: FetchError: There was a problem when fetching packages."}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/projects/list?offset=1&limit=1", http.StatusOK, projectsList1Response},
{rpmmd_mock.BaseFixture, "/api/v0/projects/list?distro=test-distro-2&offset=1&limit=1", http.StatusOK, projectsList1Response},
{rpmmd_mock.BaseFixture, "/api/v0/projects/list?distro=fedora-1&offset=1&limit=1", http.StatusBadRequest, `{"status":false,"errors":[{"id":"DistroError","msg":"Invalid distro: fedora-1"}]}`},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
})
}
}
func TestModulesList(t *testing.T) {
var cases = []struct {
Fixture rpmmd_mock.FixtureGenerator
Path string
ExpectedStatus int
ExpectedJSON string
}{
{rpmmd_mock.BaseFixture, "/api/v0/modules/list", http.StatusOK, modulesListResponse},
{rpmmd_mock.BaseFixture, "/api/v0/modules/list/", http.StatusOK, modulesListResponse},
{rpmmd_mock.BaseFixture, "/api/v0/modules/list/nonexistingpkg", http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownModule","msg":"No packages have been found."}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/modules/list/package2*,package16", http.StatusOK, modulesListFilteredResponse},
{rpmmd_mock.BadFetch, "/api/v0/modules/list/badpackage1", http.StatusBadRequest, `{"status":false,"errors":[{"id":"ModulesError","msg":"msg: DNF error occurred: FetchError: There was a problem when fetching packages."}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/modules/list/package2*,package16?offset=1&limit=1", http.StatusOK, `{"total":4,"offset":1,"limit":1,"modules":[{"name":"package2","group_type":"rpm"}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/modules/list/*", http.StatusOK, modulesListResponse},
{rpmmd_mock.BaseFixture, "/api/v0/modules/list/package2*,package16?distro=test-distro-2&offset=1&limit=1", http.StatusOK, `{"total":4,"offset":1,"limit":1,"modules":[{"name":"package2","group_type":"rpm"}]}`},
{rpmmd_mock.BaseFixture, "/api/v0/modules/list/package2*,package16?distro=fedora-1&offset=1&limit=1", http.StatusBadRequest, `{"status":false,"errors":[{"id":"DistroError","msg":"Invalid distro: fedora-1"}]}`},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), test_distro.TestDistro1Name, test_distro.TestArchName, c.Fixture, nil)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
})
}
}
func TestComposeTypes_ImageTypeDenylist(t *testing.T) {
var cases = []struct {
Path string
ImageTypeDenylist map[string][]string
ExpectedStatus int
ExpectedJSON string
}{
{
"/api/v1/compose/types",
map[string][]string{},
http.StatusOK,
fmt.Sprintf(`{"types": [{"enabled":true, "name":%q},{"enabled":true, "name":%q}]}`, test_distro.TestImageTypeName, test_distro.TestImageType2Name),
},
{
"/api/v1/compose/types?distro=test-distro-2",
map[string][]string{},
http.StatusOK,
fmt.Sprintf(`{"types": [{"enabled":true, "name":%q},{"enabled":true, "name":%q}]}`, test_distro.TestImageTypeName, test_distro.TestImageType2Name),
},
{
"/api/v1/compose/types",
map[string][]string{testDistro2Name: {test_distro.TestImageTypeName}},
http.StatusOK,
fmt.Sprintf(`{"types": [{"enabled":true, "name":%q}]}`, test_distro.TestImageType2Name),
},
{
"/api/v1/compose/types?distro=test-distro-2",
map[string][]string{testDistro2Name: {test_distro.TestImageTypeName}},
http.StatusOK,
fmt.Sprintf(`{"types": [{"enabled":true, "name":%q}]}`, test_distro.TestImageType2Name),
},
{
"/api/v1/compose/types",
map[string][]string{testDistro2Name: {test_distro.TestImageTypeName, test_distro.TestImageType2Name}},
http.StatusOK,
`{"types": null}`,
},
{
"/api/v1/compose/types?distro=test-distro-2",
map[string][]string{testDistro2Name: {test_distro.TestImageTypeName, test_distro.TestImageType2Name}},
http.StatusOK,
`{"types": null}`,
},
{
"/api/v1/compose/types",
map[string][]string{"*": {test_distro.TestImageTypeName}},
http.StatusOK,
fmt.Sprintf(`{"types": [{"enabled":true, "name":%q}]}`, test_distro.TestImageType2Name),
},
{
"/api/v1/compose/types",
map[string][]string{"*": {test_distro.TestImageTypeName, test_distro.TestImageType2Name}},
http.StatusOK,
`{"types": null}`,
},
{
"/api/v1/compose/types",
map[string][]string{testDistro2Name: {"*"}},
http.StatusOK,
`{"types": null}`,
},
{
"/api/v1/compose/types?distro=test-distro-2",
map[string][]string{test_distro.TestDistro1Name: {"*"}},
http.StatusOK,
fmt.Sprintf(`{"types": [{"enabled":true, "name":%q}, {"enabled":true, "name":%q}]}`,
test_distro.TestImageTypeName, test_distro.TestImageType2Name),
},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), testDistro2Name, test_distro.TestArch2Name, rpmmd_mock.BaseFixture, c.ImageTypeDenylist)
t.Cleanup(sf.Cleanup)
test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
})
}
}
func TestComposePOST_ImageTypeDenylist(t *testing.T) {
distro2 := test_distro.DistroFactory(testDistro2Name)
require.NotNil(t, distro2)
arch, err := distro2.GetArch(test_distro.TestArch2Name)
require.NoError(t, err)
imgType, err := arch.GetImageType(test_distro.TestImageTypeName)
require.NoError(t, err)
imgType2, err := arch.GetImageType(test_distro.TestImageType2Name)
require.NoError(t, err)
manifest, _, err := imgType.Manifest(nil, distro.ImageOptions{}, nil, nil)
require.NoError(t, err)
rPkgs, rContainers, rCommits := ResolveContent(manifest.GetPackageSetChains(), manifest.GetContainerSourceSpecs(), manifest.GetOSTreeSourceSpecs())
mf, err := manifest.Serialize(rPkgs, rContainers, rCommits, nil)
require.NoError(t, err)
expectedComposeLocal := &store.Compose{
Blueprint: &blueprint.Blueprint{
Name: "test",
Version: "0.0.0",
Packages: []blueprint.Package{},
Modules: []blueprint.Package{},
Groups: []blueprint.Group{},
Customizations: nil,
},
ImageBuild: store.ImageBuild{
QueueStatus: common.IBWaiting,
ImageType: imgType,
Size: imgType.Size(0),
Manifest: mf,
Targets: []*target.Target{
{
ImageName: imgType.Filename(),
OsbuildArtifact: target.OsbuildArtifact{
ExportFilename: imgType.Filename(),
ExportName: imgType.Exports()[0],
},
Name: target.TargetNameWorkerServer,
Options: &target.WorkerServerTargetOptions{},
},
},
},
Packages: dnfjson_mock.BaseDeps(),
}
expectedComposeLocal2 := &store.Compose{
Blueprint: &blueprint.Blueprint{
Name: "test",
Version: "0.0.0",
Packages: []blueprint.Package{},
Modules: []blueprint.Package{},
Groups: []blueprint.Group{},
Customizations: nil,
},
ImageBuild: store.ImageBuild{
QueueStatus: common.IBWaiting,
ImageType: imgType2,
Size: imgType2.Size(0),
Manifest: mf,
Targets: []*target.Target{
{
ImageName: imgType2.Filename(),
OsbuildArtifact: target.OsbuildArtifact{
ExportFilename: imgType2.Filename(),
ExportName: imgType2.Exports()[0],
},
Name: target.TargetNameWorkerServer,
Options: &target.WorkerServerTargetOptions{},
},
},
},
Packages: dnfjson_mock.BaseDeps(),
}
var cases = []struct {
Path string
Body string
imageTypeDenylist map[string][]string
ExpectedStatus int
ExpectedJSON string
ExpectedCompose *store.Compose
IgnoreFields []string
}{
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
map[string][]string{},
http.StatusOK,
`{"status":true}`,
expectedComposeLocal,
[]string{"build_id", "warnings"},
},
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageType2Name),
map[string][]string{},
http.StatusOK,
`{"status": true}`,
expectedComposeLocal2,
[]string{"build_id", "warnings"},
},
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
map[string][]string{testDistro2Name: {test_distro.TestImageTypeName}},
http.StatusBadRequest,
fmt.Sprintf(`{"status":false,"errors":[{"id":"ComposeError","msg":"Failed to get compose type \"%[1]s\": image type \"%[1]s\" for distro \"%[2]s\" is denied by configuration"}]}`,
test_distro.TestImageTypeName, testDistro2Name),
expectedComposeLocal,
[]string{"build_id", "warnings"},
},
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageType2Name),
map[string][]string{testDistro2Name: {test_distro.TestImageTypeName}},
http.StatusOK,
`{"status": true}`,
expectedComposeLocal2,
[]string{"build_id", "warnings"},
},
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
map[string][]string{testDistro2Name: {test_distro.TestImageTypeName, test_distro.TestImageType2Name}},
http.StatusBadRequest,
fmt.Sprintf(`{"status":false,"errors":[{"id":"ComposeError","msg":"Failed to get compose type \"%[1]s\": image type \"%[1]s\" for distro \"%[2]s\" is denied by configuration"}]}`,
test_distro.TestImageTypeName, testDistro2Name),
expectedComposeLocal,
[]string{"build_id", "warnings"},
},
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageType2Name),
map[string][]string{testDistro2Name: {test_distro.TestImageTypeName, test_distro.TestImageType2Name}},
http.StatusBadRequest,
fmt.Sprintf(`{"status":false,"errors":[{"id":"ComposeError","msg":"Failed to get compose type \"%[1]s\": image type \"%[1]s\" for distro \"%[2]s\" is denied by configuration"}]}`,
test_distro.TestImageType2Name, testDistro2Name),
expectedComposeLocal2,
[]string{"build_id", "warnings"},
},
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
map[string][]string{"*": {test_distro.TestImageTypeName}},
http.StatusBadRequest,
fmt.Sprintf(`{"status":false,"errors":[{"id":"ComposeError","msg":"Failed to get compose type \"%[1]s\": image type \"%[1]s\" for distro \"%[2]s\" is denied by configuration"}]}`,
test_distro.TestImageTypeName, testDistro2Name),
expectedComposeLocal,
[]string{"build_id", "warnings"},
},
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
map[string][]string{testDistro2Name: {"*"}},
http.StatusBadRequest,
fmt.Sprintf(`{"status":false,"errors":[{"id":"ComposeError","msg":"Failed to get compose type \"%[1]s\": image type \"%[1]s\" for distro \"%[2]s\" is denied by configuration"}]}`,
test_distro.TestImageTypeName, testDistro2Name),
expectedComposeLocal,
[]string{"build_id", "warnings"},
},
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageTypeName),
map[string][]string{fmt.Sprintf("%s*", test_distro.TestDistroNameBase): {fmt.Sprintf("%s*", test_distro.TestImageTypeName)}},
http.StatusBadRequest,
fmt.Sprintf(`{"status":false,"errors":[{"id":"ComposeError","msg":"Failed to get compose type \"%[1]s\": image type \"%[1]s\" for distro \"%[2]s\" is denied by configuration"}]}`,
test_distro.TestImageTypeName, testDistro2Name),
expectedComposeLocal,
[]string{"build_id", "warnings"},
},
{
"/api/v1/compose",
fmt.Sprintf(`{"blueprint_name": "test","compose_type": "%s","branch": "master"}`, test_distro.TestImageType2Name),
map[string][]string{fmt.Sprintf("%s*", test_distro.TestDistroNameBase): {fmt.Sprintf("%s*", test_distro.TestImageTypeName)}},
http.StatusBadRequest,
fmt.Sprintf(`{"status":false,"errors":[{"id":"ComposeError","msg":"Failed to get compose type \"%[1]s\": image type \"%[1]s\" for distro \"%[2]s\" is denied by configuration"}]}`,
test_distro.TestImageType2Name, testDistro2Name),
expectedComposeLocal,
[]string{"build_id", "warnings"},
},
}
for idx, c := range cases {
t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) {
api, sf := createTestWeldrAPI(t.TempDir(), distro2.Name(), arch.Name(), rpmmd_mock.NoComposesFixture, c.imageTypeDenylist)
t.Cleanup(sf.Cleanup)
_, err = api.workers.RegisterWorker("", arch.Name())
require.NoError(t, err)
test.TestRoute(t, api, true, "POST", c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, c.IgnoreFields...)
if c.ExpectedStatus != http.StatusOK {
return
}
composes := sf.Store.GetAllComposes()
require.Equalf(t, 1, len(composes), "%s: bad compose count in store", c.Path)
var composeStruct store.Compose
for _, c := range composes {
composeStruct = c
break
}
require.NotNilf(t, composeStruct.ImageBuild.Manifest, "%s: the compose in the store did not contain a blueprint", c.Path)
if diff := cmp.Diff(composeStruct, *c.ExpectedCompose, test.IgnoreDates(), test.IgnoreUuids(), test.Ignore("Targets.Options.Location"), test.CompareImageTypes()); diff != "" {
t.Errorf("%s: compose in store isn't the same as expected, diff:\n%s", c.Path, diff)
}
})
}
}
func TestExpandBlueprintNoGlob(t *testing.T) {
packages := []blueprint.Package{
{Name: "tmux", Version: "3.3a"},
{Name: "openssh-server", Version: "*"},
{Name: "grub2", Version: "*"},
}
// Sorted list of dependencies
dependencies := []rpmmd.PackageSpec{
{
Name: "grub2",
Epoch: 1,
Version: "2.06",
Release: "94.fc38",
Arch: "noarch",
},
{
Name: "openssh-server",
Epoch: 0,
Version: "9.0p1",
Release: "15.fc38",
Arch: "x86_64",
},
{
Name: "tmux",
Epoch: 0,
Version: "3.3a",
Release: "3.fc38",
Arch: "x86_64",
},
}
newPackages, err := expandBlueprintGlobs(dependencies, packages)
require.NoError(t, err, "Error expanding globs")
expected := []blueprint.Package{
{Name: "grub2", Version: "1:2.06-94.fc38.noarch"},
{Name: "openssh-server", Version: "9.0p1-15.fc38.x86_64"},
{Name: "tmux", Version: "3.3a-3.fc38.x86_64"},
}
assert.Equal(t, expected, newPackages)
}
func TestExpandBlueprintError(t *testing.T) {
// Test that a missing package in deps returns an error
packages := []blueprint.Package{
{Name: "tmux", Version: "*"},
{Name: "dep-package0", Version: "*"},
}
// Sorted list of dependencies
dependencies := []rpmmd.PackageSpec{
{
Name: "openssh-server",
Epoch: 0,
Version: "9.0p1",
Release: "15.fc38",
Arch: "x86_64",
},
{
Name: "tmux",
Epoch: 0,
Version: "3.3a",
Release: "3.fc38",
Arch: "x86_64",
},
}
_, err := expandBlueprintGlobs(dependencies, packages)
require.EqualError(t, err, "dep-package0 missing from depsolve results")
}
func TestExpandBlueprintGlobs(t *testing.T) {
packages := []blueprint.Package{
{Name: "tmux", Version: "*"},
{Name: "openssh-*", Version: "*"},
{Name: "test-?-*", Version: "*"},
{Name: "test-three-*", Version: "11.1"},
{Name: "test-*", Version: "*"},
}
// Sorted list of dependencies
dependencies := []rpmmd.PackageSpec{
{
Name: "openssh-clients",
Epoch: 0,
Version: "9.0p1",
Release: "15.fc38",
Arch: "x86_64",
},
{
Name: "openssh-server",
Epoch: 0,
Version: "9.0p1",
Release: "15.fc38",
Arch: "x86_64",
},
{
Name: "test-1-one",
Epoch: 0,
Version: "1.0.0",
Release: "1.fc38",
Arch: "x86_64",
},
{
Name: "test-2-two",
Epoch: 2,
Version: "1.0.0",
Release: "1.fc38",
Arch: "x86_64",
},
{
Name: "test-three-3",
Epoch: 0,
Version: "11.1",
Release: "1.fc38",
Arch: "x86_64",
},
{
Name: "tmux",
Epoch: 0,
Version: "3.3a",
Release: "3.fc38",
Arch: "x86_64",
},
}
newPackages, err := expandBlueprintGlobs(dependencies, packages)
require.NoError(t, err, "Error expanding globs")
expected := []blueprint.Package{
{Name: "openssh-clients", Version: "9.0p1-15.fc38.x86_64"},
{Name: "openssh-server", Version: "9.0p1-15.fc38.x86_64"},
{Name: "test-1-one", Version: "1.0.0-1.fc38.x86_64"},
{Name: "test-2-two", Version: "2:1.0.0-1.fc38.x86_64"},
{Name: "test-three-3", Version: "11.1-1.fc38.x86_64"},
{Name: "tmux", Version: "3.3a-3.fc38.x86_64"},
}
assert.Equal(t, expected, newPackages)
}
func TestMain(m *testing.M) {
setupDNFJSON()
defer os.RemoveAll(dnfjsonPath)
code := m.Run()
os.Exit(code)
}