diff --git a/cmd/osbuild-pipeline/main.go b/cmd/osbuild-pipeline/main.go index ea108a859..c7183968c 100644 --- a/cmd/osbuild-pipeline/main.go +++ b/cmd/osbuild-pipeline/main.go @@ -8,6 +8,7 @@ import ( "github.com/osbuild/osbuild-composer/internal/blueprint" "github.com/osbuild/osbuild-composer/internal/distro" + "github.com/osbuild/osbuild-composer/internal/rpmmd" ) func main() { @@ -36,7 +37,25 @@ func main() { panic("unknown distro: " + distroArg) } - pipeline, err := d.Pipeline(blueprint, format) + packages := make([]string, len(blueprint.Packages)) + for i, pkg := range blueprint.Packages { + packages[i] = pkg.Name + // If a package has version "*" the package name suffix must be equal to "-*-*.*" + // Using just "-*" would find any other package containing the package name + if pkg.Version != "" && pkg.Version != "*" { + packages[i] += "-" + pkg.Version + } else if pkg.Version == "*" { + packages[i] += "-*-*.*" + } + } + + rpmmd := rpmmd.NewRPMMD() + _, checksums, err := rpmmd.Depsolve(packages, d.Repositories()) + if err != nil { + panic(err.Error()) + } + + pipeline, err := d.Pipeline(blueprint, checksums, format) if err != nil { panic(err.Error()) } diff --git a/dnf-json b/dnf-json index c0f0c103e..238d37902 100644 --- a/dnf-json +++ b/dnf-json @@ -2,6 +2,7 @@ import datetime import dnf +import hashlib import json import sys @@ -24,7 +25,7 @@ def dnfrepo(desc, parent_conf=None): elif "metalink" in desc: repo.metalink = desc["metalink"] elif "mirrorlist" in desc: - repo.metalink = desc["mirrorlist"] + repo.mirrorlist = desc["mirrorlist"] else: assert False @@ -46,6 +47,30 @@ def exit_with_dnf_error(kind: str, reason: str): sys.exit(DNF_ERROR_EXIT_CODE) +def repo_checksums(base): + checksums = {} + for repo in base.repos.iter_enabled(): + # Uses the same algorithm as libdnf to find cache dir: + # https://github.com/rpm-software-management/libdnf/blob/master/libdnf/repo/Repo.cpp#L1288 + if repo.metalink: + url = repo.metalink + elif repo.mirrorlist: + url = repo.mirrorlist + elif repo.baseurl: + url = repo.baseurl[0] + else: + assert False + + digest = hashlib.sha256(url.encode()).hexdigest()[:16] + + with open(f"{base.conf.cachedir}/{repo.id}-{digest}/repodata/repomd.xml", "rb") as f: + repomd = f.read() + + checksums[repo.id] = "sha256:" + hashlib.sha256(repomd).hexdigest() + + return checksums + + call = json.load(sys.stdin) command = call["command"] arguments = call.get("arguments", {}) @@ -66,7 +91,10 @@ if command == "dump": "buildtime": timestamp_to_rfc3339(package.buildtime), "license": package.license }) - json.dump(packages, sys.stdout) + json.dump({ + "checksums": repo_checksums(base), + "packages": packages + }, sys.stdout) elif command == "depsolve": base = create_base(arguments.get("repos", {})) @@ -82,13 +110,16 @@ elif command == "depsolve": except dnf.exceptions.DepsolveError as e: exit_with_dnf_error("DepsolveError", f"There was a problem depsolving {arguments['package-specs']}: {e}") - packages = [] + dependencies = [] for package in base.transaction.install_set: - packages.append({ + dependencies.append({ "name": package.name, "epoch": package.epoch, "version": package.version, "release": package.release, "arch": package.arch }) - json.dump(packages, sys.stdout) + json.dump({ + "checksums": repo_checksums(base), + "dependencies": dependencies + }, sys.stdout) diff --git a/internal/distro/distro.go b/internal/distro/distro.go index 76a92aca9..a0cb3bb1d 100644 --- a/internal/distro/distro.go +++ b/internal/distro/distro.go @@ -30,7 +30,7 @@ type Distro interface { // Returns an osbuild pipeline that generates an image in the given // output format with all packages and customizations specified in the // given blueprint. - Pipeline(b *blueprint.Blueprint, outputFormat string) (*pipeline.Pipeline, error) + Pipeline(b *blueprint.Blueprint, checksums map[string]string, outputFormat string) (*pipeline.Pipeline, error) // Returns a osbuild runner that can be used on this distro. Runner() string diff --git a/internal/distro/distro_test.go b/internal/distro/distro_test.go index 29a01ffe3..580f43c9c 100644 --- a/internal/distro/distro_test.go +++ b/internal/distro/distro_test.go @@ -21,6 +21,7 @@ func TestDistro_Pipeline(t *testing.T) { type compose struct { Distro string `json:"distro"` OutputFormat string `json:"output-format"` + Checksums map[string]string `json:"checksums"` Blueprint *blueprint.Blueprint `json:"blueprint"` } var tt struct { @@ -45,7 +46,7 @@ func TestDistro_Pipeline(t *testing.T) { t.Errorf("unknown distro: %v", tt.Compose.Distro) return } - got, err := d.Pipeline(tt.Compose.Blueprint, tt.Compose.OutputFormat) + got, err := d.Pipeline(tt.Compose.Blueprint, tt.Compose.Checksums, tt.Compose.OutputFormat) if (err != nil) != (tt.Pipeline == nil) { t.Errorf("distro.Pipeline() error = %v", err) return diff --git a/internal/distro/fedora30/distro.go b/internal/distro/fedora30/distro.go index 03d92817a..c21389d57 100644 --- a/internal/distro/fedora30/distro.go +++ b/internal/distro/fedora30/distro.go @@ -221,7 +221,6 @@ func (r *Fedora30) Repositories() []rpmmd.RepoConfig { Id: "fedora", Name: "Fedora 30", Metalink: "https://mirrors.fedoraproject.org/metalink?repo=fedora-30&arch=x86_64", - Checksum: "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97", GPGKey: `-----BEGIN PGP PUBLIC KEY BLOCK----- mQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm @@ -271,17 +270,17 @@ func (r *Fedora30) FilenameFromType(outputFormat string) (string, string, error) return "", "", errors.New("invalid output format: " + outputFormat) } -func (r *Fedora30) Pipeline(b *blueprint.Blueprint, outputFormat string) (*pipeline.Pipeline, error) { +func (r *Fedora30) Pipeline(b *blueprint.Blueprint, checksums map[string]string, outputFormat string) (*pipeline.Pipeline, error) { output, exists := r.outputs[outputFormat] if !exists { return nil, errors.New("invalid output format: " + outputFormat) } p := &pipeline.Pipeline{} - p.SetBuild(r.buildPipeline(), "org.osbuild.fedora30") + p.SetBuild(r.buildPipeline(checksums), "org.osbuild.fedora30") packages := append(output.Packages, b.GetPackages()...) - p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(packages, output.ExcludedPackages))) + p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(checksums, packages, output.ExcludedPackages))) p.AddStage(pipeline.NewFixBLSStage()) // TODO support setting all languages and install corresponding langpack-* package @@ -347,7 +346,7 @@ func (r *Fedora30) Runner() string { return "org.osbuild.fedora30" } -func (r *Fedora30) buildPipeline() *pipeline.Pipeline { +func (r *Fedora30) buildPipeline(checksums map[string]string) *pipeline.Pipeline { packages := []string{ "dnf", "e2fsprogs", @@ -358,11 +357,11 @@ func (r *Fedora30) buildPipeline() *pipeline.Pipeline { "tar", } p := &pipeline.Pipeline{} - p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(packages, nil))) + p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(checksums, packages, nil))) return p } -func (r *Fedora30) dnfStageOptions(packages, excludedPackages []string) *pipeline.DNFStageOptions { +func (r *Fedora30) dnfStageOptions(checksums map[string]string, packages, excludedPackages []string) *pipeline.DNFStageOptions { options := &pipeline.DNFStageOptions{ ReleaseVersion: "30", BaseArchitecture: "x86_64", @@ -372,8 +371,8 @@ func (r *Fedora30) dnfStageOptions(packages, excludedPackages []string) *pipelin BaseURL: repo.BaseURL, MetaLink: repo.Metalink, MirrorList: repo.MirrorList, - Checksum: repo.Checksum, GPGKey: repo.GPGKey, + Checksum: checksums[repo.Id], }) } diff --git a/internal/distro/rhel82/distro.go b/internal/distro/rhel82/distro.go index 507a7752c..cfa7f0077 100644 --- a/internal/distro/rhel82/distro.go +++ b/internal/distro/rhel82/distro.go @@ -269,13 +269,11 @@ func (r *RHEL82) Repositories() []rpmmd.RepoConfig { Id: "baseos", Name: "BaseOS", BaseURL: "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191125.n.1/compose/BaseOS/x86_64/os", - Checksum: "sha256:30b905ab1538243de69e019573443b2a1e4edad7c1f7d32aa5a4fb014ff98060", }, { Id: "appstream", Name: "AppStream", BaseURL: "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191125.n.1/compose/AppStream/x86_64/os", - Checksum: "sha256:afd86d5b664ec87e209c5ff3cf011bcc6a40578394191c1d889b4ead17a072ae", }, } } @@ -296,17 +294,17 @@ func (r *RHEL82) FilenameFromType(outputFormat string) (string, string, error) { return "", "", errors.New("invalid output format: " + outputFormat) } -func (r *RHEL82) Pipeline(b *blueprint.Blueprint, outputFormat string) (*pipeline.Pipeline, error) { +func (r *RHEL82) Pipeline(b *blueprint.Blueprint, checksums map[string]string, outputFormat string) (*pipeline.Pipeline, error) { output, exists := r.outputs[outputFormat] if !exists { return nil, errors.New("invalid output format: " + outputFormat) } p := &pipeline.Pipeline{} - p.SetBuild(r.buildPipeline(), "org.osbuild.rhel82") + p.SetBuild(r.buildPipeline(checksums), "org.osbuild.rhel82") packages := append(output.Packages, b.GetPackages()...) - p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(packages, output.ExcludedPackages))) + p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(checksums, packages, output.ExcludedPackages))) p.AddStage(pipeline.NewFixBLSStage()) if output.IncludeFSTab { @@ -377,7 +375,7 @@ func (r *RHEL82) Runner() string { return "org.osbuild.rhel82" } -func (r *RHEL82) buildPipeline() *pipeline.Pipeline { +func (r *RHEL82) buildPipeline(checksums map[string]string) *pipeline.Pipeline { packages := []string{ "dnf", "dracut-config-generic", @@ -392,11 +390,11 @@ func (r *RHEL82) buildPipeline() *pipeline.Pipeline { "xfsprogs", } p := &pipeline.Pipeline{} - p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(packages, nil))) + p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(checksums, packages, nil))) return p } -func (r *RHEL82) dnfStageOptions(packages, excludedPackages []string) *pipeline.DNFStageOptions { +func (r *RHEL82) dnfStageOptions(checksums map[string]string, packages, excludedPackages []string) *pipeline.DNFStageOptions { options := &pipeline.DNFStageOptions{ ReleaseVersion: "8", BaseArchitecture: "x86_64", @@ -407,7 +405,7 @@ func (r *RHEL82) dnfStageOptions(packages, excludedPackages []string) *pipeline. BaseURL: repo.BaseURL, MetaLink: repo.Metalink, MirrorList: repo.MirrorList, - Checksum: repo.Checksum, + Checksum: checksums[repo.Id], }) } diff --git a/internal/distro/test/distro.go b/internal/distro/test/distro.go index 461e7f2b8..d49a5e0d9 100644 --- a/internal/distro/test/distro.go +++ b/internal/distro/test/distro.go @@ -33,7 +33,7 @@ func (d *TestDistro) FilenameFromType(outputFormat string) (string, string, erro return "", "", errors.New("invalid output format: " + outputFormat) } -func (d *TestDistro) Pipeline(b *blueprint.Blueprint, outputFormat string) (*pipeline.Pipeline, error) { +func (d *TestDistro) Pipeline(b *blueprint.Blueprint, checksums map[string]string, outputFormat string) (*pipeline.Pipeline, error) { return nil, errors.New("invalid output format: " + outputFormat) } diff --git a/internal/jobqueue/api_test.go b/internal/jobqueue/api_test.go index 07b66a1d3..e1a3b5977 100644 --- a/internal/jobqueue/api_test.go +++ b/internal/jobqueue/api_test.go @@ -43,13 +43,13 @@ func TestCreate(t *testing.T) { store := store.New(nil, distro.New("fedora-30")) api := jobqueue.New(nil, store) - err := store.PushCompose(id, &blueprint.Blueprint{}, "tar", nil) + err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"fedora": "test:foo"}, "tar", nil) if err != nil { t.Fatalf("error pushing compose: %v", err) } test.TestRoute(t, api, false, "POST", "/job-queue/v1/jobs", `{}`, http.StatusCreated, - `{"id":"ffffffff-ffff-ffff-ffff-ffffffffffff","output_type":"tar","pipeline":{"build":{"pipeline":{"stages":[{"name":"org.osbuild.dnf","options":{"repos":[{"metalink":"https://mirrors.fedoraproject.org/metalink?repo=fedora-30\u0026arch=x86_64","gpgkey":"-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm\nbbY4G3R0M3ir1xXmvUDvK0493/qOiFrjkplvzXFTGpPTi0ypqGgxc5d0ohRA1M75\nL+0AIlXoOgHQ358/c4uO8X0JAA1NYxCkAW1KSJgFJ3RjukrfqSHWthS1d4o8fhHy\nKJKEnirE5hHqB50dafXrBfgZdaOs3C6ppRIePFe2o4vUEapMTCHFw0woQR8Ah4/R\nn7Z9G9Ln+0Cinmy0nbIDiZJ+pgLAXCOWBfDUzcOjDGKvcpoZharA07c0q1/5ojzO\n4F0Fh4g/BUmtrASwHfcIbjHyCSr1j/3Iz883iy07gJY5Yhiuaqmp0o0f9fgHkG53\n2xCU1owmACqaIBNQMukvXRDtB2GJMuKa/asTZDP6R5re+iXs7+s9ohcRRAKGyAyc\nYKIQKcaA+6M8T7/G+TPHZX6HJWqJJiYB+EC2ERblpvq9TPlLguEWcmvjbVc31nyq\nSDoO3ncFWKFmVsbQPTbP+pKUmlLfJwtb5XqxNR5GEXSwVv4I7IqBmJz1MmRafnBZ\ng0FJUtH668GnldO20XbnSVBr820F5SISMXVwCXDXEvGwwiB8Lt8PvqzXnGIFDAu3\nDlQI5sxSqpPVWSyw08ppKT2Tpmy8adiBotLfaCFl2VTHwOae48X2dMPBvQARAQAB\ntDFGZWRvcmEgKDMwKSA8ZmVkb3JhLTMwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQI4BBMBAgAiBQJbbqxnAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK\nCRDvPBEfz8ZZudTnD/9170LL3nyTVUCFmBjT9wZ4gYnpwtKVPa/pKnxbbS+Bmmac\ng9TrT9pZbqOHrNJLiZ3Zx1Hp+8uxr3Lo6kbYwImLhkOEDrf4aP17HfQ6VYFbQZI8\nf79OFxWJ7si9+3gfzeh9UYFEqOQfzIjLWFyfnas0OnV/P+RMQ1Zr+vPRqO7AR2va\nN9wg+Xl7157dhXPCGYnGMNSoxCbpRs0JNlzvJMuAea5nTTznRaJZtK/xKsqLn51D\nK07k9MHVFXakOH8QtMCUglbwfTfIpO5YRq5imxlWbqsYWVQy1WGJFyW6hWC0+RcJ\nOx5zGtOfi4/dN+xJ+ibnbyvy/il7Qm+vyFhCYqIPyS5m2UVJUuao3eApE38k78/o\n8aQOTnFQZ+U1Sw+6woFTxjqRQBXlQm2+7Bt3bqGATg4sXXWPbmwdL87Ic+mxn/ml\nSMfQux/5k6iAu1kQhwkO2YJn9eII6HIPkW+2m5N1JsUyJQe4cbtZE5Yh3TRA0dm7\n+zoBRfCXkOW4krchbgww/ptVmzMMP7GINJdROrJnsGl5FVeid9qHzV7aZycWSma7\nCxBYB1J8HCbty5NjtD6XMYRrMLxXugvX6Q4NPPH+2NKjzX4SIDejS6JjgrP3KA3O\npMuo7ZHMfveBngv8yP+ZD/1sS6l+dfExvdaJdOdgFCnp4p3gPbw5+Lv70HrMjA==\n=BfZ/\n-----END PGP PUBLIC KEY BLOCK-----\n","checksum":"sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97"}],"packages":["dnf","e2fsprogs","policycoreutils","qemu-img","systemd","grub2-pc","tar"],"releasever":"30","basearch":"x86_64"}}]},"runner":"org.osbuild.fedora30"},"stages":[{"name":"org.osbuild.dnf","options":{"repos":[{"metalink":"https://mirrors.fedoraproject.org/metalink?repo=fedora-30\u0026arch=x86_64","gpgkey":"-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm\nbbY4G3R0M3ir1xXmvUDvK0493/qOiFrjkplvzXFTGpPTi0ypqGgxc5d0ohRA1M75\nL+0AIlXoOgHQ358/c4uO8X0JAA1NYxCkAW1KSJgFJ3RjukrfqSHWthS1d4o8fhHy\nKJKEnirE5hHqB50dafXrBfgZdaOs3C6ppRIePFe2o4vUEapMTCHFw0woQR8Ah4/R\nn7Z9G9Ln+0Cinmy0nbIDiZJ+pgLAXCOWBfDUzcOjDGKvcpoZharA07c0q1/5ojzO\n4F0Fh4g/BUmtrASwHfcIbjHyCSr1j/3Iz883iy07gJY5Yhiuaqmp0o0f9fgHkG53\n2xCU1owmACqaIBNQMukvXRDtB2GJMuKa/asTZDP6R5re+iXs7+s9ohcRRAKGyAyc\nYKIQKcaA+6M8T7/G+TPHZX6HJWqJJiYB+EC2ERblpvq9TPlLguEWcmvjbVc31nyq\nSDoO3ncFWKFmVsbQPTbP+pKUmlLfJwtb5XqxNR5GEXSwVv4I7IqBmJz1MmRafnBZ\ng0FJUtH668GnldO20XbnSVBr820F5SISMXVwCXDXEvGwwiB8Lt8PvqzXnGIFDAu3\nDlQI5sxSqpPVWSyw08ppKT2Tpmy8adiBotLfaCFl2VTHwOae48X2dMPBvQARAQAB\ntDFGZWRvcmEgKDMwKSA8ZmVkb3JhLTMwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQI4BBMBAgAiBQJbbqxnAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK\nCRDvPBEfz8ZZudTnD/9170LL3nyTVUCFmBjT9wZ4gYnpwtKVPa/pKnxbbS+Bmmac\ng9TrT9pZbqOHrNJLiZ3Zx1Hp+8uxr3Lo6kbYwImLhkOEDrf4aP17HfQ6VYFbQZI8\nf79OFxWJ7si9+3gfzeh9UYFEqOQfzIjLWFyfnas0OnV/P+RMQ1Zr+vPRqO7AR2va\nN9wg+Xl7157dhXPCGYnGMNSoxCbpRs0JNlzvJMuAea5nTTznRaJZtK/xKsqLn51D\nK07k9MHVFXakOH8QtMCUglbwfTfIpO5YRq5imxlWbqsYWVQy1WGJFyW6hWC0+RcJ\nOx5zGtOfi4/dN+xJ+ibnbyvy/il7Qm+vyFhCYqIPyS5m2UVJUuao3eApE38k78/o\n8aQOTnFQZ+U1Sw+6woFTxjqRQBXlQm2+7Bt3bqGATg4sXXWPbmwdL87Ic+mxn/ml\nSMfQux/5k6iAu1kQhwkO2YJn9eII6HIPkW+2m5N1JsUyJQe4cbtZE5Yh3TRA0dm7\n+zoBRfCXkOW4krchbgww/ptVmzMMP7GINJdROrJnsGl5FVeid9qHzV7aZycWSma7\nCxBYB1J8HCbty5NjtD6XMYRrMLxXugvX6Q4NPPH+2NKjzX4SIDejS6JjgrP3KA3O\npMuo7ZHMfveBngv8yP+ZD/1sS6l+dfExvdaJdOdgFCnp4p3gPbw5+Lv70HrMjA==\n=BfZ/\n-----END PGP PUBLIC KEY BLOCK-----\n","checksum":"sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97"}],"packages":["policycoreutils","selinux-policy-targeted","kernel","firewalld","chrony","langpacks-en"],"exclude_packages":["dracut-config-rescue"],"releasever":"30","basearch":"x86_64"}},{"name":"org.osbuild.fix-bls","options":{}},{"name":"org.osbuild.locale","options":{"language":"en_US"}},{"name":"org.osbuild.grub2","options":{"root_fs_uuid":"76a22bf4-f153-4541-b6c7-0332c0dfaeac","boot_fs_uuid":"00000000-0000-0000-0000-000000000000","kernel_opts":"ro biosdevname=0 net.ifnames=0"}},{"name":"org.osbuild.selinux","options":{"file_contexts":"etc/selinux/targeted/contexts/files/file_contexts"}}],"assembler":{"name":"org.osbuild.tar","options":{"filename":"root.tar.xz"}}},"targets":[]}`, "created", "uuid") + `{"id":"ffffffff-ffff-ffff-ffff-ffffffffffff","output_type":"tar","pipeline":{"build":{"pipeline":{"stages":[{"name":"org.osbuild.dnf","options":{"repos":[{"metalink":"https://mirrors.fedoraproject.org/metalink?repo=fedora-30\u0026arch=x86_64","gpgkey":"-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm\nbbY4G3R0M3ir1xXmvUDvK0493/qOiFrjkplvzXFTGpPTi0ypqGgxc5d0ohRA1M75\nL+0AIlXoOgHQ358/c4uO8X0JAA1NYxCkAW1KSJgFJ3RjukrfqSHWthS1d4o8fhHy\nKJKEnirE5hHqB50dafXrBfgZdaOs3C6ppRIePFe2o4vUEapMTCHFw0woQR8Ah4/R\nn7Z9G9Ln+0Cinmy0nbIDiZJ+pgLAXCOWBfDUzcOjDGKvcpoZharA07c0q1/5ojzO\n4F0Fh4g/BUmtrASwHfcIbjHyCSr1j/3Iz883iy07gJY5Yhiuaqmp0o0f9fgHkG53\n2xCU1owmACqaIBNQMukvXRDtB2GJMuKa/asTZDP6R5re+iXs7+s9ohcRRAKGyAyc\nYKIQKcaA+6M8T7/G+TPHZX6HJWqJJiYB+EC2ERblpvq9TPlLguEWcmvjbVc31nyq\nSDoO3ncFWKFmVsbQPTbP+pKUmlLfJwtb5XqxNR5GEXSwVv4I7IqBmJz1MmRafnBZ\ng0FJUtH668GnldO20XbnSVBr820F5SISMXVwCXDXEvGwwiB8Lt8PvqzXnGIFDAu3\nDlQI5sxSqpPVWSyw08ppKT2Tpmy8adiBotLfaCFl2VTHwOae48X2dMPBvQARAQAB\ntDFGZWRvcmEgKDMwKSA8ZmVkb3JhLTMwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQI4BBMBAgAiBQJbbqxnAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK\nCRDvPBEfz8ZZudTnD/9170LL3nyTVUCFmBjT9wZ4gYnpwtKVPa/pKnxbbS+Bmmac\ng9TrT9pZbqOHrNJLiZ3Zx1Hp+8uxr3Lo6kbYwImLhkOEDrf4aP17HfQ6VYFbQZI8\nf79OFxWJ7si9+3gfzeh9UYFEqOQfzIjLWFyfnas0OnV/P+RMQ1Zr+vPRqO7AR2va\nN9wg+Xl7157dhXPCGYnGMNSoxCbpRs0JNlzvJMuAea5nTTznRaJZtK/xKsqLn51D\nK07k9MHVFXakOH8QtMCUglbwfTfIpO5YRq5imxlWbqsYWVQy1WGJFyW6hWC0+RcJ\nOx5zGtOfi4/dN+xJ+ibnbyvy/il7Qm+vyFhCYqIPyS5m2UVJUuao3eApE38k78/o\n8aQOTnFQZ+U1Sw+6woFTxjqRQBXlQm2+7Bt3bqGATg4sXXWPbmwdL87Ic+mxn/ml\nSMfQux/5k6iAu1kQhwkO2YJn9eII6HIPkW+2m5N1JsUyJQe4cbtZE5Yh3TRA0dm7\n+zoBRfCXkOW4krchbgww/ptVmzMMP7GINJdROrJnsGl5FVeid9qHzV7aZycWSma7\nCxBYB1J8HCbty5NjtD6XMYRrMLxXugvX6Q4NPPH+2NKjzX4SIDejS6JjgrP3KA3O\npMuo7ZHMfveBngv8yP+ZD/1sS6l+dfExvdaJdOdgFCnp4p3gPbw5+Lv70HrMjA==\n=BfZ/\n-----END PGP PUBLIC KEY BLOCK-----\n","checksum":"test:foo"}],"packages":["dnf","e2fsprogs","policycoreutils","qemu-img","systemd","grub2-pc","tar"],"releasever":"30","basearch":"x86_64"}}]},"runner":"org.osbuild.fedora30"},"stages":[{"name":"org.osbuild.dnf","options":{"repos":[{"metalink":"https://mirrors.fedoraproject.org/metalink?repo=fedora-30\u0026arch=x86_64","gpgkey":"-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm\nbbY4G3R0M3ir1xXmvUDvK0493/qOiFrjkplvzXFTGpPTi0ypqGgxc5d0ohRA1M75\nL+0AIlXoOgHQ358/c4uO8X0JAA1NYxCkAW1KSJgFJ3RjukrfqSHWthS1d4o8fhHy\nKJKEnirE5hHqB50dafXrBfgZdaOs3C6ppRIePFe2o4vUEapMTCHFw0woQR8Ah4/R\nn7Z9G9Ln+0Cinmy0nbIDiZJ+pgLAXCOWBfDUzcOjDGKvcpoZharA07c0q1/5ojzO\n4F0Fh4g/BUmtrASwHfcIbjHyCSr1j/3Iz883iy07gJY5Yhiuaqmp0o0f9fgHkG53\n2xCU1owmACqaIBNQMukvXRDtB2GJMuKa/asTZDP6R5re+iXs7+s9ohcRRAKGyAyc\nYKIQKcaA+6M8T7/G+TPHZX6HJWqJJiYB+EC2ERblpvq9TPlLguEWcmvjbVc31nyq\nSDoO3ncFWKFmVsbQPTbP+pKUmlLfJwtb5XqxNR5GEXSwVv4I7IqBmJz1MmRafnBZ\ng0FJUtH668GnldO20XbnSVBr820F5SISMXVwCXDXEvGwwiB8Lt8PvqzXnGIFDAu3\nDlQI5sxSqpPVWSyw08ppKT2Tpmy8adiBotLfaCFl2VTHwOae48X2dMPBvQARAQAB\ntDFGZWRvcmEgKDMwKSA8ZmVkb3JhLTMwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQI4BBMBAgAiBQJbbqxnAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK\nCRDvPBEfz8ZZudTnD/9170LL3nyTVUCFmBjT9wZ4gYnpwtKVPa/pKnxbbS+Bmmac\ng9TrT9pZbqOHrNJLiZ3Zx1Hp+8uxr3Lo6kbYwImLhkOEDrf4aP17HfQ6VYFbQZI8\nf79OFxWJ7si9+3gfzeh9UYFEqOQfzIjLWFyfnas0OnV/P+RMQ1Zr+vPRqO7AR2va\nN9wg+Xl7157dhXPCGYnGMNSoxCbpRs0JNlzvJMuAea5nTTznRaJZtK/xKsqLn51D\nK07k9MHVFXakOH8QtMCUglbwfTfIpO5YRq5imxlWbqsYWVQy1WGJFyW6hWC0+RcJ\nOx5zGtOfi4/dN+xJ+ibnbyvy/il7Qm+vyFhCYqIPyS5m2UVJUuao3eApE38k78/o\n8aQOTnFQZ+U1Sw+6woFTxjqRQBXlQm2+7Bt3bqGATg4sXXWPbmwdL87Ic+mxn/ml\nSMfQux/5k6iAu1kQhwkO2YJn9eII6HIPkW+2m5N1JsUyJQe4cbtZE5Yh3TRA0dm7\n+zoBRfCXkOW4krchbgww/ptVmzMMP7GINJdROrJnsGl5FVeid9qHzV7aZycWSma7\nCxBYB1J8HCbty5NjtD6XMYRrMLxXugvX6Q4NPPH+2NKjzX4SIDejS6JjgrP3KA3O\npMuo7ZHMfveBngv8yP+ZD/1sS6l+dfExvdaJdOdgFCnp4p3gPbw5+Lv70HrMjA==\n=BfZ/\n-----END PGP PUBLIC KEY BLOCK-----\n","checksum":"test:foo"}],"packages":["policycoreutils","selinux-policy-targeted","kernel","firewalld","chrony","langpacks-en"],"exclude_packages":["dracut-config-rescue"],"releasever":"30","basearch":"x86_64"}},{"name":"org.osbuild.fix-bls","options":{}},{"name":"org.osbuild.locale","options":{"language":"en_US"}},{"name":"org.osbuild.grub2","options":{"root_fs_uuid":"76a22bf4-f153-4541-b6c7-0332c0dfaeac","boot_fs_uuid":"00000000-0000-0000-0000-000000000000","kernel_opts":"ro biosdevname=0 net.ifnames=0"}},{"name":"org.osbuild.selinux","options":{"file_contexts":"etc/selinux/targeted/contexts/files/file_contexts"}}],"assembler":{"name":"org.osbuild.tar","options":{"filename":"root.tar.xz"}}},"targets":[]}`, "created", "uuid") } func testUpdateTransition(t *testing.T, from, to string, expectedStatus int) { @@ -58,7 +58,7 @@ func testUpdateTransition(t *testing.T, from, to string, expectedStatus int) { api := jobqueue.New(nil, store) if from != "VOID" { - err := store.PushCompose(id, &blueprint.Blueprint{}, "tar", nil) + err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"fedora": "test:foo"}, "tar", nil) if err != nil { t.Fatalf("error pushing compose: %v", err) } diff --git a/internal/mocks/rpmmd/fixtures.go b/internal/mocks/rpmmd/fixtures.go index 2bfbb9504..6f736e6e0 100644 --- a/internal/mocks/rpmmd/fixtures.go +++ b/internal/mocks/rpmmd/fixtures.go @@ -178,10 +178,12 @@ func BaseFixture() Fixture { return Fixture{ fetchPackageList{ generatePackageList(), + map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"}, nil, }, depsolve{ createBaseDepsolveFixture(), + map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"}, nil, }, createBaseStoreFixture(), @@ -192,10 +194,12 @@ func NoComposesFixture() Fixture { return Fixture{ fetchPackageList{ generatePackageList(), + map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"}, nil, }, depsolve{ createBaseDepsolveFixture(), + map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"}, nil, }, createStoreWithoutComposesFixture(), @@ -206,9 +210,11 @@ func NonExistingPackage() Fixture { return Fixture{ fetchPackageList{ generatePackageList(), + map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"}, nil, }, depsolve{ + nil, nil, &rpmmd.DNFError{ Kind: "MarkingErrors", @@ -223,9 +229,11 @@ func BadDepsolve() Fixture { return Fixture{ fetchPackageList{ generatePackageList(), + map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"}, nil, }, depsolve{ + nil, nil, &rpmmd.DNFError{ Kind: "DepsolveError", @@ -240,12 +248,14 @@ func BadFetch() Fixture { return Fixture{ fetchPackageList{ ret: nil, + checksums: nil, err: &rpmmd.DNFError{ Kind: "FetchError", Reason: "There was a problem when fetching packages.", }, }, depsolve{ + nil, nil, &rpmmd.DNFError{ Kind: "DepsolveError", diff --git a/internal/mocks/rpmmd/rpmmd_mock.go b/internal/mocks/rpmmd/rpmmd_mock.go index 2a85da0be..229703837 100644 --- a/internal/mocks/rpmmd/rpmmd_mock.go +++ b/internal/mocks/rpmmd/rpmmd_mock.go @@ -7,10 +7,12 @@ import ( type fetchPackageList struct { ret rpmmd.PackageList + checksums map[string]string err error } type depsolve struct { ret []rpmmd.PackageSpec + checksums map[string]string err error } @@ -28,10 +30,10 @@ func NewRPMMDMock(fixture Fixture) rpmmd.RPMMD { return &rpmmdMock{Fixture: fixture} } -func (r *rpmmdMock) FetchPackageList(repos []rpmmd.RepoConfig) (rpmmd.PackageList, error) { - return r.Fixture.fetchPackageList.ret, r.Fixture.fetchPackageList.err +func (r *rpmmdMock) FetchPackageList(repos []rpmmd.RepoConfig) (rpmmd.PackageList, map[string]string, error) { + return r.Fixture.fetchPackageList.ret, r.Fixture.fetchPackageList.checksums, r.Fixture.fetchPackageList.err } -func (r *rpmmdMock) Depsolve(specs []string, repos []rpmmd.RepoConfig) ([]rpmmd.PackageSpec, error) { - return r.Fixture.depsolve.ret, r.Fixture.depsolve.err +func (r *rpmmdMock) Depsolve(specs []string, repos []rpmmd.RepoConfig) ([]rpmmd.PackageSpec, map[string]string, error) { + return r.Fixture.depsolve.ret, r.Fixture.fetchPackageList.checksums, r.Fixture.depsolve.err } diff --git a/internal/rpmmd/repository.go b/internal/rpmmd/repository.go index fc07970ac..1bf8a67f6 100644 --- a/internal/rpmmd/repository.go +++ b/internal/rpmmd/repository.go @@ -18,7 +18,6 @@ type RepoConfig struct { BaseURL string `json:"baseurl,omitempty"` Metalink string `json:"metalink,omitempty"` MirrorList string `json:"mirrorlist,omitempty"` - Checksum string `json:"checksum,omitempty"` GPGKey string `json:"gpgkey,omitempty"` } @@ -93,8 +92,8 @@ type PackageInfo struct { } type RPMMD interface { - FetchPackageList(repos []RepoConfig) (PackageList, error) - Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, error) + FetchPackageList(repos []RepoConfig) (PackageList, map[string]string, error) + Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, map[string]string, error) } type DNFError struct { @@ -172,26 +171,32 @@ func NewRPMMD() RPMMD { return &rpmmdImpl{} } -func (*rpmmdImpl) FetchPackageList(repos []RepoConfig) (PackageList, error) { +func (*rpmmdImpl) FetchPackageList(repos []RepoConfig) (PackageList, map[string]string, error) { var arguments = struct { Repos []RepoConfig `json:"repos"` }{repos} - var packages PackageList - err := runDNF("dump", arguments, &packages) - sort.Slice(packages, func(i, j int) bool { - return packages[i].Name < packages[j].Name + var reply struct { + Checksums map[string]string `json:"checksums"` + Packages PackageList `json:"packages"` + } + err := runDNF("dump", arguments, &reply) + sort.Slice(reply.Packages, func(i, j int) bool { + return reply.Packages[i].Name < reply.Packages[j].Name }) - return packages, err + return reply.Packages, reply.Checksums, err } -func (*rpmmdImpl) Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, error) { +func (*rpmmdImpl) Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, map[string]string, error) { var arguments = struct { PackageSpecs []string `json:"package-specs"` Repos []RepoConfig `json:"repos"` }{specs, repos} - var dependencies []PackageSpec - err := runDNF("depsolve", arguments, &dependencies) - return dependencies, err + var reply struct { + Checksums map[string]string `json:"checksums"` + Dependencies []PackageSpec `json:"dependencies"` + } + err := runDNF("depsolve", arguments, &reply) + return reply.Dependencies, reply.Checksums, err } func (packages PackageList) Search(globPatterns ...string) (PackageList, error) { @@ -247,6 +252,6 @@ func (packages PackageList) ToPackageInfos() []PackageInfo { } func (pkg *PackageInfo) FillDependencies(rpmmd RPMMD, repos []RepoConfig) (err error) { - pkg.Dependencies, err = rpmmd.Depsolve([]string{pkg.Name}, repos) + pkg.Dependencies, _, err = rpmmd.Depsolve([]string{pkg.Name}, repos) return } diff --git a/internal/store/store.go b/internal/store/store.go index 6d1c86c86..4a978e991 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -415,7 +415,7 @@ func (s *Store) DeleteBlueprintFromWorkspace(name string) { }) } -func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, composeType string, uploadTarget *target.Target) error { +func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, checksums map[string]string, composeType string, uploadTarget *target.Target) error { targets := []*target.Target{} if s.stateDir != nil { @@ -430,7 +430,7 @@ func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, compos targets = append(targets, uploadTarget) } - pipeline, err := s.distro.Pipeline(bp, composeType) + pipeline, err := s.distro.Pipeline(bp, checksums, composeType) if err != nil { return err } diff --git a/internal/weldr/api.go b/internal/weldr/api.go index 6838f9c7a..7a63df8de 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -610,7 +610,7 @@ func (api *API) projectsDepsolveHandler(writer http.ResponseWriter, request *htt names := strings.Split(params.ByName("projects"), ",") - packages, err := api.rpmmd.Depsolve(names, api.distro.Repositories()) + packages, _, err := api.rpmmd.Depsolve(names, api.distro.Repositories()) if err != nil { errors := responseError{ @@ -792,27 +792,7 @@ func (api *API) blueprintsDepsolveHandler(writer http.ResponseWriter, request *h return } - specs := make([]string, len(blueprint.Packages)) - for i, pkg := range blueprint.Packages { - specs[i] = pkg.Name - // If a package has version "*" the package name suffix must be equal to "-*-*.*" - // Using just "-*" would find any other package containing the package name - if pkg.Version != "" && pkg.Version != "*" { - specs[i] += "-" + pkg.Version - } else if pkg.Version == "*" { - specs[i] += "-*-*.*" - } - } - - var repos []rpmmd.RepoConfig - for _, repo := range api.distro.Repositories() { - repos = append(repos, repo) - } - for _, source := range api.store.GetAllSources() { - repos = append(repos, source.RepoConfig()) - } - - dependencies, err := api.rpmmd.Depsolve(specs, repos) + dependencies, _, err := api.depsolveBlueprint(blueprint) if err != nil { errors := responseError{ @@ -866,36 +846,24 @@ func (api *API) blueprintsFreezeHandler(writer http.ResponseWriter, request *htt } blueprint, _ := api.store.GetBlueprint(name) if blueprint == nil { - err := responseError{ + rerr := responseError{ ID: "UnknownBlueprint", Msg: fmt.Sprintf("%s: blueprint_not_found", name), } - errors = append(errors, err) - return + errors = append(errors, rerr) + break } - specs := make([]string, len(blueprint.Packages)) - for i, pkg := range blueprint.Packages { - specs[i] = pkg.Name - // If a package has version "*" the package name suffix must be equal to "-*-*.*" - // Using just "-*" would find any other package containing the package name - if pkg.Version != "" && pkg.Version != "*" { - specs[i] += "-" + pkg.Version - } else if pkg.Version == "*" { - specs[i] += "-*-*.*" + dependencies, _, err := api.depsolveBlueprint(blueprint) + if err != nil { + rerr := responseError{ + ID: "BlueprintsError", + Msg: fmt.Sprintf("%s: %s", name, err.Error()), } + errors = append(errors, rerr) + break } - var repos []rpmmd.RepoConfig - for _, repo := range api.distro.Repositories() { - repos = append(repos, repo) - } - for _, source := range api.store.GetAllSources() { - repos = append(repos, source.RepoConfig()) - } - - dependencies, _ := api.rpmmd.Depsolve(specs, repos) - for pkgIndex, pkg := range blueprint.Packages { i := sort.Search(len(dependencies), func(i int) bool { return dependencies[i].Name >= pkg.Name @@ -1243,7 +1211,17 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request bp := api.store.GetBlueprintCommitted(cr.BlueprintName) if bp != nil { - err := api.store.PushCompose(reply.BuildID, bp, cr.ComposeType, uploadTarget) + _, checksums, err := api.depsolveBlueprint(bp) + if err != nil { + errors := responseError{ + ID: "DepsolveError", + Msg: err.Error(), + } + statusResponseError(writer, http.StatusInternalServerError, errors) + return + } + + err = api.store.PushCompose(reply.BuildID, bp, checksums, cr.ComposeType, uploadTarget) // TODO: we should probably do some kind of blueprint validation in future // for now, let's just 500 and bail out @@ -1630,7 +1608,32 @@ func (api *API) fetchPackageList() (rpmmd.PackageList, error) { repos = append(repos, source.RepoConfig()) } - return api.rpmmd.FetchPackageList(repos) + packages, _, err := api.rpmmd.FetchPackageList(repos) + return packages, err +} + +func (api *API) depsolveBlueprint(bp *blueprint.Blueprint) ([]rpmmd.PackageSpec, map[string]string, error) { + specs := make([]string, len(bp.Packages)) + for i, pkg := range bp.Packages { + specs[i] = pkg.Name + // If a package has version "*" the package name suffix must be equal to "-*-*.*" + // Using just "-*" would find any other package containing the package name + if pkg.Version != "" && pkg.Version != "*" { + specs[i] += "-" + pkg.Version + } else if pkg.Version == "*" { + specs[i] += "-*-*.*" + } + } + + var repos []rpmmd.RepoConfig + for _, repo := range api.distro.Repositories() { + repos = append(repos, repo) + } + for _, source := range api.store.GetAllSources() { + repos = append(repos, source.RepoConfig()) + } + + return api.rpmmd.Depsolve(specs, repos) } func (api *API) uploadsScheduleHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { diff --git a/test/cases/ami_empty_blueprint.json b/test/cases/ami_empty_blueprint.json index b9e2deafd..d3fb931d0 100644 --- a/test/cases/ami_empty_blueprint.json +++ b/test/cases/ami_empty_blueprint.json @@ -5,6 +5,9 @@ "compose": { "distro": "fedora-30", "arch": "x86_64", + "checksums": { + "fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97" + }, "filename": "image.raw.xz", "output-format": "ami", "blueprint": {} diff --git a/test/cases/disk_empty_blueprint.json b/test/cases/disk_empty_blueprint.json index 75d192d87..c9b5e719f 100644 --- a/test/cases/disk_empty_blueprint.json +++ b/test/cases/disk_empty_blueprint.json @@ -2,6 +2,9 @@ "compose": { "distro": "fedora-30", "arch": "x86_64", + "checksums": { + "fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97" + }, "output-format": "partitioned-disk", "filename": "disk.img", "blueprint": {} diff --git a/test/cases/ext4_empty_blueprint.json b/test/cases/ext4_empty_blueprint.json index 0480516e3..9e080db4f 100644 --- a/test/cases/ext4_empty_blueprint.json +++ b/test/cases/ext4_empty_blueprint.json @@ -2,6 +2,9 @@ "compose": { "distro": "fedora-30", "arch": "x86_64", + "checksums": { + "fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97" + }, "output-format": "ext4-filesystem", "filename": "filesystem.img", "blueprint": {} diff --git a/test/cases/openstack_empty_blueprint.json b/test/cases/openstack_empty_blueprint.json index c2d3b16bb..cfa33627c 100644 --- a/test/cases/openstack_empty_blueprint.json +++ b/test/cases/openstack_empty_blueprint.json @@ -2,6 +2,9 @@ "compose": { "distro": "fedora-30", "arch": "x86_64", + "checksums": { + "fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97" + }, "output-format": "openstack", "filename": "image.qcow2", "blueprint": {} diff --git a/test/cases/qcow2_empty_blueprint.json b/test/cases/qcow2_empty_blueprint.json index c5b82924b..35f34e1b6 100644 --- a/test/cases/qcow2_empty_blueprint.json +++ b/test/cases/qcow2_empty_blueprint.json @@ -2,6 +2,9 @@ "compose": { "distro": "fedora-30", "arch": "x86_64", + "checksums": { + "fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97" + }, "output-format": "qcow2", "filename": "image.qcow2", "blueprint": {} diff --git a/test/cases/vhd_empty_blueprint.json b/test/cases/vhd_empty_blueprint.json index 052e7e7d8..690c0ca51 100644 --- a/test/cases/vhd_empty_blueprint.json +++ b/test/cases/vhd_empty_blueprint.json @@ -2,6 +2,9 @@ "compose": { "distro": "fedora-30", "arch": "x86_64", + "checksums": { + "fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97" + }, "output-format": "vhd", "filename": "image.vhd", "blueprint": {} diff --git a/test/cases/vmdk_empty_blueprint.json b/test/cases/vmdk_empty_blueprint.json index 0e72fc476..ac7ddf38b 100644 --- a/test/cases/vmdk_empty_blueprint.json +++ b/test/cases/vmdk_empty_blueprint.json @@ -2,6 +2,9 @@ "compose": { "distro": "fedora-30", "arch": "x86_64", + "checksums": { + "fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97" + }, "output-format": "vmdk", "filename": "disk.vmdk", "blueprint": {}