store: add image struct into compose struct
As a part of f4991cb1 ComposeEntry struct was removed from store package.
This change made sense because this struct is connected more with API than
with store - store uses its own Compose struct. In addition, converters
between Compose and ComposeEntry were added. Unfortunately, ComposeEntry
contains ImageSize which was not stored in Compose but retrieved from store
using GetImage method. This made those converters dependent on the store,
which was messy.
To solve this issue this commit adds image struct into Compose struct.
The content of image struct is generated on the worker side - when the worker
sets the compose status to FINISHED, it also sends Image struct with detailed
information about the result.
This commit is contained in:
parent
b2880cacc6
commit
f89a9671be
7 changed files with 127 additions and 88 deletions
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/osbuild/osbuild-composer/internal/distro"
|
"github.com/osbuild/osbuild-composer/internal/distro"
|
||||||
"github.com/osbuild/osbuild-composer/internal/jobqueue"
|
"github.com/osbuild/osbuild-composer/internal/jobqueue"
|
||||||
|
"github.com/osbuild/osbuild-composer/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ComposerClient struct {
|
type ComposerClient struct {
|
||||||
|
|
@ -53,9 +54,9 @@ func (c *ComposerClient) AddJob() (*jobqueue.Job, error) {
|
||||||
return job, nil
|
return job, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ComposerClient) UpdateJob(job *jobqueue.Job, status string) error {
|
func (c *ComposerClient) UpdateJob(job *jobqueue.Job, status string, image *store.Image) error {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
json.NewEncoder(&b).Encode(&jobqueue.JobStatus{status})
|
json.NewEncoder(&b).Encode(&jobqueue.JobStatus{status, image})
|
||||||
req, err := http.NewRequest("PATCH", "http://localhost/job-queue/v1/jobs/"+job.ID.String(), &b)
|
req, err := http.NewRequest("PATCH", "http://localhost/job-queue/v1/jobs/"+job.ID.String(), &b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -82,23 +83,23 @@ func handleJob(client *ComposerClient, distro distro.Distro) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client.UpdateJob(job, "RUNNING")
|
client.UpdateJob(job, "RUNNING", nil)
|
||||||
|
|
||||||
fmt.Printf("Running job %s\n", job.ID.String())
|
fmt.Printf("Running job %s\n", job.ID.String())
|
||||||
err, errs := job.Run(distro)
|
image, err, errs := job.Run(distro)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
client.UpdateJob(job, "FAILED")
|
client.UpdateJob(job, "FAILED", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
client.UpdateJob(job, "FAILED")
|
client.UpdateJob(job, "FAILED", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.UpdateJob(job, "FINISHED")
|
client.UpdateJob(job, "FINISHED", image)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
||||||
|
|
@ -93,12 +93,10 @@ func (api *API) addJobHandler(writer http.ResponseWriter, request *http.Request,
|
||||||
nextJob := api.store.PopCompose()
|
nextJob := api.store.PopCompose()
|
||||||
|
|
||||||
writer.WriteHeader(http.StatusCreated)
|
writer.WriteHeader(http.StatusCreated)
|
||||||
json.NewEncoder(writer).Encode(replyBody{nextJob.ComposeID, nextJob.Pipeline, nextJob.Targets})
|
json.NewEncoder(writer).Encode(replyBody{nextJob.ComposeID, nextJob.Pipeline, nextJob.Targets, nextJob.OutputType})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) updateJobHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
func (api *API) updateJobHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
||||||
type requestBody JobStatus
|
|
||||||
|
|
||||||
contentType := request.Header["Content-Type"]
|
contentType := request.Header["Content-Type"]
|
||||||
if len(contentType) != 1 || contentType[0] != "application/json" {
|
if len(contentType) != 1 || contentType[0] != "application/json" {
|
||||||
statusResponseError(writer, http.StatusUnsupportedMediaType)
|
statusResponseError(writer, http.StatusUnsupportedMediaType)
|
||||||
|
|
@ -111,14 +109,14 @@ func (api *API) updateJobHandler(writer http.ResponseWriter, request *http.Reque
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var body requestBody
|
var body JobStatus
|
||||||
err = json.NewDecoder(request.Body).Decode(&body)
|
err = json.NewDecoder(request.Body).Decode(&body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusResponseError(writer, http.StatusBadRequest, "invalid status: "+err.Error())
|
statusResponseError(writer, http.StatusBadRequest, "invalid status: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = api.store.UpdateCompose(id, body.Status)
|
err = api.store.UpdateCompose(id, body.Status, body.Image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case *store.NotFoundError:
|
case *store.NotFoundError:
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ func TestCreate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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","pipeline":{"build":{"pipeline":{"stages":[{"name":"org.osbuild.dnf","options":{"repos":[{"metalink":"https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever\u0026arch=$basearch","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-$releasever\u0026arch=$basearch","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":[{"image_name":"","name":"org.osbuild.local","options":{"location":"/var/lib/osbuild-composer/outputs/ffffffff-ffff-ffff-ffff-ffffffffffff"},"status":"RUNNING"}]}`, "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-$releasever\u0026arch=$basearch","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-$releasever\u0026arch=$basearch","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":[{"image_name":"","name":"org.osbuild.local","options":{"location":"/var/lib/osbuild-composer/outputs/ffffffff-ffff-ffff-ffff-ffffffffffff"},"status":"RUNNING"}]}`, "created", "uuid")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUpdateTransition(t *testing.T, from, to string, expectedStatus int) {
|
func testUpdateTransition(t *testing.T, from, to string, expectedStatus int) {
|
||||||
|
|
|
||||||
|
|
@ -8,36 +8,40 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/osbuild/osbuild-composer/internal/distro"
|
"github.com/osbuild/osbuild-composer/internal/distro"
|
||||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||||
|
"github.com/osbuild/osbuild-composer/internal/store"
|
||||||
"github.com/osbuild/osbuild-composer/internal/target"
|
"github.com/osbuild/osbuild-composer/internal/target"
|
||||||
"github.com/osbuild/osbuild-composer/internal/upload/awsupload"
|
"github.com/osbuild/osbuild-composer/internal/upload/awsupload"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Job struct {
|
type Job struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
Pipeline *pipeline.Pipeline `json:"pipeline"`
|
Pipeline *pipeline.Pipeline `json:"pipeline"`
|
||||||
Targets []*target.Target `json:"targets"`
|
Targets []*target.Target `json:"targets"`
|
||||||
|
OutputType string `json:"output_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JobStatus struct {
|
type JobStatus struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Image *store.Image `json:"image"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (job *Job) Run(d distro.Distro) (error, []error) {
|
func (job *Job) Run(d distro.Distro) (*store.Image, error, []error) {
|
||||||
build := pipeline.Build{
|
build := pipeline.Build{
|
||||||
Runner: d.Runner(),
|
Runner: d.Runner(),
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFile, err := ioutil.TempFile("", "osbuild-worker-build-env-*")
|
buildFile, err := ioutil.TempFile("", "osbuild-worker-build-env-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err, nil
|
||||||
}
|
}
|
||||||
defer os.Remove(buildFile.Name())
|
defer os.Remove(buildFile.Name())
|
||||||
|
|
||||||
err = json.NewEncoder(buildFile).Encode(build)
|
err = json.NewEncoder(buildFile).Encode(build)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
|
|
@ -50,22 +54,22 @@ func (job *Job) Run(d distro.Distro) (error, []error) {
|
||||||
|
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewEncoder(stdin).Encode(job.Pipeline)
|
err = json.NewEncoder(stdin).Encode(job.Pipeline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err, nil
|
||||||
}
|
}
|
||||||
stdin.Close()
|
stdin.Close()
|
||||||
|
|
||||||
|
|
@ -75,14 +79,21 @@ func (job *Job) Run(d distro.Distro) (error, []error) {
|
||||||
}
|
}
|
||||||
err = json.NewDecoder(stdout).Decode(&result)
|
err = json.NewDecoder(stdout).Decode(&result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cmd.Wait()
|
err = cmd.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filename, mimeType, err := d.FilenameFromType(job.OutputType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var image store.Image
|
||||||
|
|
||||||
var r []error
|
var r []error
|
||||||
|
|
||||||
for _, t := range job.Targets {
|
for _, t := range job.Targets {
|
||||||
|
|
@ -102,6 +113,25 @@ func (job *Job) Run(d distro.Distro) (error, []error) {
|
||||||
r = append(r, err)
|
r = append(r, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imagePath := options.Location + "/" + filename
|
||||||
|
file, err := os.Open(imagePath)
|
||||||
|
if err != nil {
|
||||||
|
r = append(r, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStat, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
image = store.Image{
|
||||||
|
Path: imagePath,
|
||||||
|
Mime: mimeType,
|
||||||
|
Size: fileStat.Size(),
|
||||||
|
}
|
||||||
|
|
||||||
case *target.AWSTargetOptions:
|
case *target.AWSTargetOptions:
|
||||||
a, err := awsupload.New(options.Region, options.AccessKeyID, options.SecretAccessKey)
|
a, err := awsupload.New(options.Region, options.AccessKeyID, options.SecretAccessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -132,5 +162,5 @@ func (job *Job) Run(d distro.Distro) (error, []error) {
|
||||||
r = append(r, nil)
|
r = append(r, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, r
|
return &image, nil, r
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,19 +51,20 @@ type Compose struct {
|
||||||
JobCreated time.Time `json:"job_created"`
|
JobCreated time.Time `json:"job_created"`
|
||||||
JobStarted time.Time `json:"job_started"`
|
JobStarted time.Time `json:"job_started"`
|
||||||
JobFinished time.Time `json:"job_finished"`
|
JobFinished time.Time `json:"job_finished"`
|
||||||
|
Image *Image `json:"image"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Job contains the information about a compose a worker needs to process it.
|
// A Job contains the information about a compose a worker needs to process it.
|
||||||
type Job struct {
|
type Job struct {
|
||||||
ComposeID uuid.UUID
|
ComposeID uuid.UUID
|
||||||
Pipeline *pipeline.Pipeline
|
Pipeline *pipeline.Pipeline
|
||||||
Targets []*target.Target
|
Targets []*target.Target
|
||||||
|
OutputType string
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Image represents the image resulting from a compose.
|
// An Image represents the image resulting from a compose.
|
||||||
type Image struct {
|
type Image struct {
|
||||||
File *os.File
|
Path string
|
||||||
Name string
|
|
||||||
Mime string
|
Mime string
|
||||||
Size int64
|
Size int64
|
||||||
}
|
}
|
||||||
|
|
@ -223,6 +224,14 @@ func (s *Store) ListBlueprints() []string {
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetCompose(id uuid.UUID) (Compose, bool) {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
|
compose, exists := s.Composes[id]
|
||||||
|
return compose, exists
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) GetAllComposes() map[uuid.UUID]Compose {
|
func (s *Store) GetAllComposes() map[uuid.UUID]Compose {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
|
|
@ -431,9 +440,10 @@ func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, compos
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
s.pendingJobs <- Job{
|
s.pendingJobs <- Job{
|
||||||
ComposeID: composeID,
|
ComposeID: composeID,
|
||||||
Pipeline: pipeline,
|
Pipeline: pipeline,
|
||||||
Targets: targets,
|
Targets: targets,
|
||||||
|
OutputType: composeType,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -457,7 +467,7 @@ func (s *Store) PopCompose() Job {
|
||||||
return job
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) UpdateCompose(composeID uuid.UUID, status string) error {
|
func (s *Store) UpdateCompose(composeID uuid.UUID, status string, image *Image) error {
|
||||||
return s.change(func() error {
|
return s.change(func() error {
|
||||||
compose, exists := s.Composes[composeID]
|
compose, exists := s.Composes[composeID]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
|
@ -484,6 +494,11 @@ func (s *Store) UpdateCompose(composeID uuid.UUID, status string) error {
|
||||||
for _, t := range compose.Targets {
|
for _, t := range compose.Targets {
|
||||||
t.Status = status
|
t.Status = status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if status == "FINISHED" {
|
||||||
|
compose.Image = image
|
||||||
|
}
|
||||||
|
|
||||||
s.Composes[composeID] = compose
|
s.Composes[composeID] = compose
|
||||||
default:
|
default:
|
||||||
return &InvalidRequestError{"invalid state transition"}
|
return &InvalidRequestError{"invalid state transition"}
|
||||||
|
|
@ -492,44 +507,6 @@ func (s *Store) UpdateCompose(composeID uuid.UUID, status string) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetImage(composeID uuid.UUID) (*Image, error) {
|
|
||||||
s.mu.RLock()
|
|
||||||
defer s.mu.RUnlock()
|
|
||||||
|
|
||||||
if compose, exists := s.Composes[composeID]; exists {
|
|
||||||
if compose.QueueStatus != "FINISHED" {
|
|
||||||
return nil, &InvalidRequestError{"compose was not finished"}
|
|
||||||
}
|
|
||||||
name, mime, err := s.distro.FilenameFromType(compose.OutputType)
|
|
||||||
if err != nil {
|
|
||||||
panic("invalid output type")
|
|
||||||
}
|
|
||||||
for _, t := range compose.Targets {
|
|
||||||
switch options := t.Options.(type) {
|
|
||||||
case *target.LocalTargetOptions:
|
|
||||||
file, err := os.Open(options.Location + "/" + name)
|
|
||||||
if err == nil {
|
|
||||||
fileStat, err := file.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, &NotFoundError{"image info could not be found"}
|
|
||||||
}
|
|
||||||
size := fileStat.Size()
|
|
||||||
|
|
||||||
return &Image{
|
|
||||||
File: file,
|
|
||||||
Name: name,
|
|
||||||
Mime: mime,
|
|
||||||
Size: size,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, &NotFoundError{"image could not be found"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, &NotFoundError{"compose could not be found"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) PushSource(source SourceConfig) {
|
func (s *Store) PushSource(source SourceConfig) {
|
||||||
s.change(func() error {
|
s.change(func() error {
|
||||||
s.Sources[source.Name] = source
|
s.Sources[source.Name] = source
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -1370,21 +1372,51 @@ func (api *API) composeImageHandler(writer http.ResponseWriter, request *http.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
image, err := api.store.GetImage(uuid)
|
compose, exists := api.store.GetCompose(uuid)
|
||||||
if err != nil {
|
if !exists {
|
||||||
errors := responseError{
|
errors := responseError{
|
||||||
ID: "BuildMissingFile",
|
ID: "BuildMissingFile",
|
||||||
Msg: fmt.Sprintf("Build %s is missing image file %s", uuidString, image.Name),
|
Msg: fmt.Sprintf("Compose %s doesn't exist", uuidString),
|
||||||
}
|
}
|
||||||
statusResponseError(writer, http.StatusBadRequest, errors)
|
statusResponseError(writer, http.StatusBadRequest, errors)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Header().Set("Content-Disposition", "attachment; filename="+uuid.String()+"-"+image.Name)
|
if compose.QueueStatus != "FINISHED" {
|
||||||
writer.Header().Set("Content-Type", image.Mime)
|
errors := responseError{
|
||||||
writer.Header().Set("Content-Length", fmt.Sprintf("%d", image.Size))
|
ID: "BuildInWrongState",
|
||||||
|
Msg: fmt.Sprintf("Build %s is in wrong state: %s", uuidString, compose.QueueStatus),
|
||||||
|
}
|
||||||
|
statusResponseError(writer, http.StatusBadRequest, errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
io.Copy(writer, image.File)
|
if compose.Image == nil {
|
||||||
|
errors := responseError{
|
||||||
|
ID: "BuildMissingFile",
|
||||||
|
Msg: fmt.Sprintf("Compose %s doesn't have an image assigned", uuidString),
|
||||||
|
}
|
||||||
|
statusResponseError(writer, http.StatusBadRequest, errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imageName := filepath.Base(compose.Image.Path)
|
||||||
|
|
||||||
|
file, err := os.Open(compose.Image.Path)
|
||||||
|
if err != nil {
|
||||||
|
errors := responseError{
|
||||||
|
ID: "BuildMissingFile",
|
||||||
|
Msg: fmt.Sprintf("Build %s is missing file %s!", uuidString, imageName),
|
||||||
|
}
|
||||||
|
statusResponseError(writer, http.StatusBadRequest, errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Header().Set("Content-Disposition", "attachment; filename="+uuid.String()+"-"+imageName)
|
||||||
|
writer.Header().Set("Content-Type", compose.Image.Mime)
|
||||||
|
writer.Header().Set("Content-Length", fmt.Sprintf("%d", compose.Image.Size))
|
||||||
|
|
||||||
|
io.Copy(writer, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) composeFinishedHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
func (api *API) composeFinishedHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package weldr
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/osbuild/osbuild-composer/internal/store"
|
"github.com/osbuild/osbuild-composer/internal/store"
|
||||||
|
"log"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,13 +42,13 @@ func composeToComposeEntry(id uuid.UUID, compose store.Compose, includeUploads b
|
||||||
composeEntry.JobStarted = float64(compose.JobStarted.UnixNano()) / 1000000000
|
composeEntry.JobStarted = float64(compose.JobStarted.UnixNano()) / 1000000000
|
||||||
|
|
||||||
case "FINISHED":
|
case "FINISHED":
|
||||||
//image, err := s.GetImage(id)
|
if compose.Image != nil {
|
||||||
//imageSize := int64(0)
|
composeEntry.ImageSize = compose.Image.Size
|
||||||
//if err == nil {
|
} else {
|
||||||
// imageSize = image.Size
|
log.Printf("finished compose with id %s has nil image\n", id.String())
|
||||||
//}
|
composeEntry.ImageSize = 0
|
||||||
// TODO: this is currently broken!
|
}
|
||||||
composeEntry.ImageSize = int64(0)
|
|
||||||
composeEntry.JobCreated = float64(compose.JobCreated.UnixNano()) / 1000000000
|
composeEntry.JobCreated = float64(compose.JobCreated.UnixNano()) / 1000000000
|
||||||
composeEntry.JobStarted = float64(compose.JobStarted.UnixNano()) / 1000000000
|
composeEntry.JobStarted = float64(compose.JobStarted.UnixNano()) / 1000000000
|
||||||
composeEntry.JobFinished = float64(compose.JobFinished.UnixNano()) / 1000000000
|
composeEntry.JobFinished = float64(compose.JobFinished.UnixNano()) / 1000000000
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue