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/blueprint"
"github.com/osbuild/osbuild-composer/internal/distro" "github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
) )
func main() { func main() {
@ -36,7 +37,25 @@ func main() {
panic("unknown distro: " + distroArg) 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 { if err != nil {
panic(err.Error()) panic(err.Error())
} }

View file

@ -2,6 +2,7 @@
import datetime import datetime
import dnf import dnf
import hashlib
import json import json
import sys import sys
@ -24,7 +25,7 @@ def dnfrepo(desc, parent_conf=None):
elif "metalink" in desc: elif "metalink" in desc:
repo.metalink = desc["metalink"] repo.metalink = desc["metalink"]
elif "mirrorlist" in desc: elif "mirrorlist" in desc:
repo.metalink = desc["mirrorlist"] repo.mirrorlist = desc["mirrorlist"]
else: else:
assert False assert False
@ -46,6 +47,30 @@ def exit_with_dnf_error(kind: str, reason: str):
sys.exit(DNF_ERROR_EXIT_CODE) 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) call = json.load(sys.stdin)
command = call["command"] command = call["command"]
arguments = call.get("arguments", {}) arguments = call.get("arguments", {})
@ -66,7 +91,10 @@ if command == "dump":
"buildtime": timestamp_to_rfc3339(package.buildtime), "buildtime": timestamp_to_rfc3339(package.buildtime),
"license": package.license "license": package.license
}) })
json.dump(packages, sys.stdout) json.dump({
"checksums": repo_checksums(base),
"packages": packages
}, sys.stdout)
elif command == "depsolve": elif command == "depsolve":
base = create_base(arguments.get("repos", {})) base = create_base(arguments.get("repos", {}))
@ -82,13 +110,16 @@ elif command == "depsolve":
except dnf.exceptions.DepsolveError as e: except dnf.exceptions.DepsolveError as e:
exit_with_dnf_error("DepsolveError", f"There was a problem depsolving {arguments['package-specs']}: {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: for package in base.transaction.install_set:
packages.append({ dependencies.append({
"name": package.name, "name": package.name,
"epoch": package.epoch, "epoch": package.epoch,
"version": package.version, "version": package.version,
"release": package.release, "release": package.release,
"arch": package.arch "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 // Returns an osbuild pipeline that generates an image in the given
// output format with all packages and customizations specified in the // output format with all packages and customizations specified in the
// given blueprint. // 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. // Returns a osbuild runner that can be used on this distro.
Runner() string Runner() string

View file

@ -21,6 +21,7 @@ func TestDistro_Pipeline(t *testing.T) {
type compose struct { type compose struct {
Distro string `json:"distro"` Distro string `json:"distro"`
OutputFormat string `json:"output-format"` OutputFormat string `json:"output-format"`
Checksums map[string]string `json:"checksums"`
Blueprint *blueprint.Blueprint `json:"blueprint"` Blueprint *blueprint.Blueprint `json:"blueprint"`
} }
var tt struct { var tt struct {
@ -45,7 +46,7 @@ func TestDistro_Pipeline(t *testing.T) {
t.Errorf("unknown distro: %v", tt.Compose.Distro) t.Errorf("unknown distro: %v", tt.Compose.Distro)
return 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) { if (err != nil) != (tt.Pipeline == nil) {
t.Errorf("distro.Pipeline() error = %v", err) t.Errorf("distro.Pipeline() error = %v", err)
return return

View file

@ -221,7 +221,6 @@ func (r *Fedora30) Repositories() []rpmmd.RepoConfig {
Id: "fedora", Id: "fedora",
Name: "Fedora 30", Name: "Fedora 30",
Metalink: "https://mirrors.fedoraproject.org/metalink?repo=fedora-30&arch=x86_64", Metalink: "https://mirrors.fedoraproject.org/metalink?repo=fedora-30&arch=x86_64",
Checksum: "sha256:9f596e18f585bee30ac41c11fb11a83ed6b11d5b341c1cb56ca4015d7717cb97",
GPGKey: `-----BEGIN PGP PUBLIC KEY BLOCK----- GPGKey: `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm mQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm
@ -271,17 +270,17 @@ func (r *Fedora30) FilenameFromType(outputFormat string) (string, string, error)
return "", "", errors.New("invalid output format: " + outputFormat) 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] output, exists := r.outputs[outputFormat]
if !exists { if !exists {
return nil, errors.New("invalid output format: " + outputFormat) return nil, errors.New("invalid output format: " + outputFormat)
} }
p := &pipeline.Pipeline{} p := &pipeline.Pipeline{}
p.SetBuild(r.buildPipeline(), "org.osbuild.fedora30") p.SetBuild(r.buildPipeline(checksums), "org.osbuild.fedora30")
packages := append(output.Packages, b.GetPackages()...) 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()) p.AddStage(pipeline.NewFixBLSStage())
// TODO support setting all languages and install corresponding langpack-* package // TODO support setting all languages and install corresponding langpack-* package
@ -347,7 +346,7 @@ func (r *Fedora30) Runner() string {
return "org.osbuild.fedora30" return "org.osbuild.fedora30"
} }
func (r *Fedora30) buildPipeline() *pipeline.Pipeline { func (r *Fedora30) buildPipeline(checksums map[string]string) *pipeline.Pipeline {
packages := []string{ packages := []string{
"dnf", "dnf",
"e2fsprogs", "e2fsprogs",
@ -358,11 +357,11 @@ func (r *Fedora30) buildPipeline() *pipeline.Pipeline {
"tar", "tar",
} }
p := &pipeline.Pipeline{} p := &pipeline.Pipeline{}
p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(packages, nil))) p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(checksums, packages, nil)))
return p 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{ options := &pipeline.DNFStageOptions{
ReleaseVersion: "30", ReleaseVersion: "30",
BaseArchitecture: "x86_64", BaseArchitecture: "x86_64",
@ -372,8 +371,8 @@ func (r *Fedora30) dnfStageOptions(packages, excludedPackages []string) *pipelin
BaseURL: repo.BaseURL, BaseURL: repo.BaseURL,
MetaLink: repo.Metalink, MetaLink: repo.Metalink,
MirrorList: repo.MirrorList, MirrorList: repo.MirrorList,
Checksum: repo.Checksum,
GPGKey: repo.GPGKey, GPGKey: repo.GPGKey,
Checksum: checksums[repo.Id],
}) })
} }

View file

@ -269,13 +269,11 @@ func (r *RHEL82) Repositories() []rpmmd.RepoConfig {
Id: "baseos", Id: "baseos",
Name: "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", 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", Id: "appstream",
Name: "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", 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) 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] output, exists := r.outputs[outputFormat]
if !exists { if !exists {
return nil, errors.New("invalid output format: " + outputFormat) return nil, errors.New("invalid output format: " + outputFormat)
} }
p := &pipeline.Pipeline{} p := &pipeline.Pipeline{}
p.SetBuild(r.buildPipeline(), "org.osbuild.rhel82") p.SetBuild(r.buildPipeline(checksums), "org.osbuild.rhel82")
packages := append(output.Packages, b.GetPackages()...) 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()) p.AddStage(pipeline.NewFixBLSStage())
if output.IncludeFSTab { if output.IncludeFSTab {
@ -377,7 +375,7 @@ func (r *RHEL82) Runner() string {
return "org.osbuild.rhel82" return "org.osbuild.rhel82"
} }
func (r *RHEL82) buildPipeline() *pipeline.Pipeline { func (r *RHEL82) buildPipeline(checksums map[string]string) *pipeline.Pipeline {
packages := []string{ packages := []string{
"dnf", "dnf",
"dracut-config-generic", "dracut-config-generic",
@ -392,11 +390,11 @@ func (r *RHEL82) buildPipeline() *pipeline.Pipeline {
"xfsprogs", "xfsprogs",
} }
p := &pipeline.Pipeline{} p := &pipeline.Pipeline{}
p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(packages, nil))) p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(checksums, packages, nil)))
return p 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{ options := &pipeline.DNFStageOptions{
ReleaseVersion: "8", ReleaseVersion: "8",
BaseArchitecture: "x86_64", BaseArchitecture: "x86_64",
@ -407,7 +405,7 @@ func (r *RHEL82) dnfStageOptions(packages, excludedPackages []string) *pipeline.
BaseURL: repo.BaseURL, BaseURL: repo.BaseURL,
MetaLink: repo.Metalink, MetaLink: repo.Metalink,
MirrorList: repo.MirrorList, 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) 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) 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")) store := store.New(nil, distro.New("fedora-30"))
api := jobqueue.New(nil, store) 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 { if err != nil {
t.Fatalf("error pushing compose: %v", err) t.Fatalf("error pushing compose: %v", err)
} }
test.TestRoute(t, api, false, "POST", "/job-queue/v1/jobs", `{}`, http.StatusCreated, 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) { 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) api := jobqueue.New(nil, store)
if from != "VOID" { 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 { if err != nil {
t.Fatalf("error pushing compose: %v", err) t.Fatalf("error pushing compose: %v", err)
} }

View file

@ -178,10 +178,12 @@ func BaseFixture() Fixture {
return Fixture{ return Fixture{
fetchPackageList{ fetchPackageList{
generatePackageList(), generatePackageList(),
map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"},
nil, nil,
}, },
depsolve{ depsolve{
createBaseDepsolveFixture(), createBaseDepsolveFixture(),
map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"},
nil, nil,
}, },
createBaseStoreFixture(), createBaseStoreFixture(),
@ -192,10 +194,12 @@ func NoComposesFixture() Fixture {
return Fixture{ return Fixture{
fetchPackageList{ fetchPackageList{
generatePackageList(), generatePackageList(),
map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"},
nil, nil,
}, },
depsolve{ depsolve{
createBaseDepsolveFixture(), createBaseDepsolveFixture(),
map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"},
nil, nil,
}, },
createStoreWithoutComposesFixture(), createStoreWithoutComposesFixture(),
@ -206,9 +210,11 @@ func NonExistingPackage() Fixture {
return Fixture{ return Fixture{
fetchPackageList{ fetchPackageList{
generatePackageList(), generatePackageList(),
map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"},
nil, nil,
}, },
depsolve{ depsolve{
nil,
nil, nil,
&rpmmd.DNFError{ &rpmmd.DNFError{
Kind: "MarkingErrors", Kind: "MarkingErrors",
@ -223,9 +229,11 @@ func BadDepsolve() Fixture {
return Fixture{ return Fixture{
fetchPackageList{ fetchPackageList{
generatePackageList(), generatePackageList(),
map[string]string{"base": "sha256:f34848ca92665c342abd5816c9e3eda0e82180671195362bcd0080544a3bc2ac"},
nil, nil,
}, },
depsolve{ depsolve{
nil,
nil, nil,
&rpmmd.DNFError{ &rpmmd.DNFError{
Kind: "DepsolveError", Kind: "DepsolveError",
@ -240,12 +248,14 @@ func BadFetch() Fixture {
return Fixture{ return Fixture{
fetchPackageList{ fetchPackageList{
ret: nil, ret: nil,
checksums: nil,
err: &rpmmd.DNFError{ err: &rpmmd.DNFError{
Kind: "FetchError", Kind: "FetchError",
Reason: "There was a problem when fetching packages.", Reason: "There was a problem when fetching packages.",
}, },
}, },
depsolve{ depsolve{
nil,
nil, nil,
&rpmmd.DNFError{ &rpmmd.DNFError{
Kind: "DepsolveError", Kind: "DepsolveError",

View file

@ -7,10 +7,12 @@ import (
type fetchPackageList struct { type fetchPackageList struct {
ret rpmmd.PackageList ret rpmmd.PackageList
checksums map[string]string
err error err error
} }
type depsolve struct { type depsolve struct {
ret []rpmmd.PackageSpec ret []rpmmd.PackageSpec
checksums map[string]string
err error err error
} }
@ -28,10 +30,10 @@ func NewRPMMDMock(fixture Fixture) rpmmd.RPMMD {
return &rpmmdMock{Fixture: fixture} return &rpmmdMock{Fixture: fixture}
} }
func (r *rpmmdMock) FetchPackageList(repos []rpmmd.RepoConfig) (rpmmd.PackageList, error) { func (r *rpmmdMock) FetchPackageList(repos []rpmmd.RepoConfig) (rpmmd.PackageList, map[string]string, error) {
return r.Fixture.fetchPackageList.ret, r.Fixture.fetchPackageList.err 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) { func (r *rpmmdMock) Depsolve(specs []string, repos []rpmmd.RepoConfig) ([]rpmmd.PackageSpec, map[string]string, error) {
return r.Fixture.depsolve.ret, r.Fixture.depsolve.err 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"` BaseURL string `json:"baseurl,omitempty"`
Metalink string `json:"metalink,omitempty"` Metalink string `json:"metalink,omitempty"`
MirrorList string `json:"mirrorlist,omitempty"` MirrorList string `json:"mirrorlist,omitempty"`
Checksum string `json:"checksum,omitempty"`
GPGKey string `json:"gpgkey,omitempty"` GPGKey string `json:"gpgkey,omitempty"`
} }
@ -93,8 +92,8 @@ type PackageInfo struct {
} }
type RPMMD interface { type RPMMD interface {
FetchPackageList(repos []RepoConfig) (PackageList, error) FetchPackageList(repos []RepoConfig) (PackageList, map[string]string, error)
Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, error) Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, map[string]string, error)
} }
type DNFError struct { type DNFError struct {
@ -172,26 +171,32 @@ func NewRPMMD() RPMMD {
return &rpmmdImpl{} return &rpmmdImpl{}
} }
func (*rpmmdImpl) FetchPackageList(repos []RepoConfig) (PackageList, error) { func (*rpmmdImpl) FetchPackageList(repos []RepoConfig) (PackageList, map[string]string, error) {
var arguments = struct { var arguments = struct {
Repos []RepoConfig `json:"repos"` Repos []RepoConfig `json:"repos"`
}{repos} }{repos}
var packages PackageList var reply struct {
err := runDNF("dump", arguments, &packages) Checksums map[string]string `json:"checksums"`
sort.Slice(packages, func(i, j int) bool { Packages PackageList `json:"packages"`
return packages[i].Name < packages[j].Name }
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 { var arguments = struct {
PackageSpecs []string `json:"package-specs"` PackageSpecs []string `json:"package-specs"`
Repos []RepoConfig `json:"repos"` Repos []RepoConfig `json:"repos"`
}{specs, repos} }{specs, repos}
var dependencies []PackageSpec var reply struct {
err := runDNF("depsolve", arguments, &dependencies) Checksums map[string]string `json:"checksums"`
return dependencies, err Dependencies []PackageSpec `json:"dependencies"`
}
err := runDNF("depsolve", arguments, &reply)
return reply.Dependencies, reply.Checksums, err
} }
func (packages PackageList) Search(globPatterns ...string) (PackageList, error) { 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) { 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 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{} targets := []*target.Target{}
if s.stateDir != nil { if s.stateDir != nil {
@ -430,7 +430,7 @@ func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, compos
targets = append(targets, uploadTarget) targets = append(targets, uploadTarget)
} }
pipeline, err := s.distro.Pipeline(bp, composeType) pipeline, err := s.distro.Pipeline(bp, checksums, composeType)
if err != nil { if err != nil {
return err return err
} }

View file

@ -610,7 +610,7 @@ func (api *API) projectsDepsolveHandler(writer http.ResponseWriter, request *htt
names := strings.Split(params.ByName("projects"), ",") 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 { if err != nil {
errors := responseError{ errors := responseError{
@ -792,27 +792,7 @@ func (api *API) blueprintsDepsolveHandler(writer http.ResponseWriter, request *h
return return
} }
specs := make([]string, len(blueprint.Packages)) dependencies, _, err := api.depsolveBlueprint(blueprint)
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)
if err != nil { if err != nil {
errors := responseError{ errors := responseError{
@ -866,36 +846,24 @@ func (api *API) blueprintsFreezeHandler(writer http.ResponseWriter, request *htt
} }
blueprint, _ := api.store.GetBlueprint(name) blueprint, _ := api.store.GetBlueprint(name)
if blueprint == nil { if blueprint == nil {
err := responseError{ rerr := responseError{
ID: "UnknownBlueprint", ID: "UnknownBlueprint",
Msg: fmt.Sprintf("%s: blueprint_not_found", name), Msg: fmt.Sprintf("%s: blueprint_not_found", name),
} }
errors = append(errors, err) errors = append(errors, rerr)
return break
} }
specs := make([]string, len(blueprint.Packages)) dependencies, _, err := api.depsolveBlueprint(blueprint)
for i, pkg := range blueprint.Packages { if err != nil {
specs[i] = pkg.Name rerr := responseError{
// If a package has version "*" the package name suffix must be equal to "-*-*.*" ID: "BlueprintsError",
// Using just "-*" would find any other package containing the package name Msg: fmt.Sprintf("%s: %s", name, err.Error()),
if pkg.Version != "" && pkg.Version != "*" {
specs[i] += "-" + pkg.Version
} else if pkg.Version == "*" {
specs[i] += "-*-*.*"
} }
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 { for pkgIndex, pkg := range blueprint.Packages {
i := sort.Search(len(dependencies), func(i int) bool { i := sort.Search(len(dependencies), func(i int) bool {
return dependencies[i].Name >= pkg.Name 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) bp := api.store.GetBlueprintCommitted(cr.BlueprintName)
if bp != nil { 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 // TODO: we should probably do some kind of blueprint validation in future
// for now, let's just 500 and bail out // 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()) 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) { func (api *API) uploadsScheduleHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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