distro: set the repository checksum dynamically

Instead of having a static repository checksum, set it dynamically from
the metadata that osbuild-composer last saw. This is implemented in
dnf-json, which returns the checksums for each repository on every call.

This enables the use of repositories that change over time, such as
fedora-updates. Note that the osbuild pipeline will break when such a
repository changes. This is intentional: pipelines have to be
reproducible.
This commit is contained in:
Lars Karlitski 2019-12-07 23:01:44 +01:00
parent 75218ad2d9
commit d3a0b788a2
20 changed files with 184 additions and 95 deletions

View file

@ -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())
}

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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],
})
}

View file

@ -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],
})
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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",

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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) {

View file

@ -5,6 +5,9 @@
"compose": {
"distro": "fedora-30",
"arch": "x86_64",
"checksums": {
"fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97"
},
"filename": "image.raw.xz",
"output-format": "ami",
"blueprint": {}

View file

@ -2,6 +2,9 @@
"compose": {
"distro": "fedora-30",
"arch": "x86_64",
"checksums": {
"fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97"
},
"output-format": "partitioned-disk",
"filename": "disk.img",
"blueprint": {}

View file

@ -2,6 +2,9 @@
"compose": {
"distro": "fedora-30",
"arch": "x86_64",
"checksums": {
"fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97"
},
"output-format": "ext4-filesystem",
"filename": "filesystem.img",
"blueprint": {}

View file

@ -2,6 +2,9 @@
"compose": {
"distro": "fedora-30",
"arch": "x86_64",
"checksums": {
"fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97"
},
"output-format": "openstack",
"filename": "image.qcow2",
"blueprint": {}

View file

@ -2,6 +2,9 @@
"compose": {
"distro": "fedora-30",
"arch": "x86_64",
"checksums": {
"fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97"
},
"output-format": "qcow2",
"filename": "image.qcow2",
"blueprint": {}

View file

@ -2,6 +2,9 @@
"compose": {
"distro": "fedora-30",
"arch": "x86_64",
"checksums": {
"fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97"
},
"output-format": "vhd",
"filename": "image.vhd",
"blueprint": {}

View file

@ -2,6 +2,9 @@
"compose": {
"distro": "fedora-30",
"arch": "x86_64",
"checksums": {
"fedora": "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97"
},
"output-format": "vmdk",
"filename": "disk.vmdk",
"blueprint": {}